parent
85af882c13
commit
b145868a4b
8 changed files with 368 additions and 111 deletions
|
@ -465,7 +465,10 @@ func (a *Account) LockedOutpoints() []btcjson.TransactionInput {
|
||||||
locked := make([]btcjson.TransactionInput, len(a.lockedOutpoints))
|
locked := make([]btcjson.TransactionInput, len(a.lockedOutpoints))
|
||||||
i := 0
|
i := 0
|
||||||
for op := range a.lockedOutpoints {
|
for op := range a.lockedOutpoints {
|
||||||
locked[i] = btcjson.TransactionInput{op.Hash.String(), op.Index}
|
locked[i] = btcjson.TransactionInput{
|
||||||
|
Txid: op.Hash.String(),
|
||||||
|
Vout: op.Index,
|
||||||
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return locked
|
return locked
|
||||||
|
|
157
acctmgr.go
157
acctmgr.go
|
@ -29,6 +29,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors relating to accounts.
|
// Errors relating to accounts.
|
||||||
|
@ -36,6 +37,7 @@ var (
|
||||||
ErrAccountExists = errors.New("account already exists")
|
ErrAccountExists = errors.New("account already exists")
|
||||||
ErrWalletExists = errors.New("wallet already exists")
|
ErrWalletExists = errors.New("wallet already exists")
|
||||||
ErrNotFound = errors.New("not found")
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrNoAccounts = errors.New("no accounts")
|
||||||
)
|
)
|
||||||
|
|
||||||
// AcctMgr is the global account manager for all opened accounts.
|
// AcctMgr is the global account manager for all opened accounts.
|
||||||
|
@ -70,6 +72,8 @@ type removeAccountCmd struct {
|
||||||
a *Account
|
a *Account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type quitCmd struct{}
|
||||||
|
|
||||||
// AccountManager manages a collection of accounts.
|
// AccountManager manages a collection of accounts.
|
||||||
type AccountManager struct {
|
type AccountManager struct {
|
||||||
// The accounts accessed through the account manager are not safe for
|
// The accounts accessed through the account manager are not safe for
|
||||||
|
@ -81,6 +85,9 @@ type AccountManager struct {
|
||||||
|
|
||||||
ds *DiskSyncer
|
ds *DiskSyncer
|
||||||
rm *RescanManager
|
rm *RescanManager
|
||||||
|
|
||||||
|
wg sync.WaitGroup
|
||||||
|
quit chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccountManager returns a new AccountManager.
|
// NewAccountManager returns a new AccountManager.
|
||||||
|
@ -89,6 +96,7 @@ func NewAccountManager() *AccountManager {
|
||||||
bsem: make(chan struct{}, 1),
|
bsem: make(chan struct{}, 1),
|
||||||
cmdChan: make(chan interface{}),
|
cmdChan: make(chan interface{}),
|
||||||
rescanMsgs: make(chan RescanMsg, 1),
|
rescanMsgs: make(chan RescanMsg, 1),
|
||||||
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
am.ds = NewDiskSyncer(am)
|
am.ds = NewDiskSyncer(am)
|
||||||
am.rm = NewRescanManager(am.rescanMsgs)
|
am.rm = NewRescanManager(am.rescanMsgs)
|
||||||
|
@ -100,12 +108,29 @@ func (am *AccountManager) Start() {
|
||||||
// Ready the semaphore - can't grab unless the manager has started.
|
// Ready the semaphore - can't grab unless the manager has started.
|
||||||
am.bsem <- struct{}{}
|
am.bsem <- struct{}{}
|
||||||
|
|
||||||
|
am.wg.Add(4)
|
||||||
go am.accountHandler()
|
go am.accountHandler()
|
||||||
go am.rescanListener()
|
go am.rescanListener()
|
||||||
go am.ds.Start()
|
go am.ds.Start()
|
||||||
go am.rm.Start()
|
go am.rm.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop shuts down the account manager by stoping all signaling all goroutines
|
||||||
|
// started by Start to close.
|
||||||
|
func (am *AccountManager) Stop() {
|
||||||
|
am.rm.Stop()
|
||||||
|
am.ds.Stop()
|
||||||
|
close(am.quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForShutdown blocks until all goroutines started by Start and stopped
|
||||||
|
// with Stop have finished.
|
||||||
|
func (am *AccountManager) WaitForShutdown() {
|
||||||
|
am.rm.WaitForShutdown()
|
||||||
|
am.ds.WaitForShutdown()
|
||||||
|
am.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// accountData is a helper structure to let us centralise logic for adding
|
// accountData is a helper structure to let us centralise logic for adding
|
||||||
// and removing accounts.
|
// and removing accounts.
|
||||||
type accountData struct {
|
type accountData struct {
|
||||||
|
@ -394,49 +419,57 @@ func openAccounts() *accountData {
|
||||||
func (am *AccountManager) accountHandler() {
|
func (am *AccountManager) accountHandler() {
|
||||||
ad := openAccounts()
|
ad := openAccounts()
|
||||||
|
|
||||||
for c := range am.cmdChan {
|
out:
|
||||||
switch cmd := c.(type) {
|
for {
|
||||||
case *openAccountsCmd:
|
select {
|
||||||
// Write all old accounts before proceeding.
|
case c := <-am.cmdChan:
|
||||||
for _, a := range ad.nameToAccount {
|
switch cmd := c.(type) {
|
||||||
if err := am.ds.FlushAccount(a); err != nil {
|
case *openAccountsCmd:
|
||||||
log.Errorf("Cannot write previously "+
|
// Write all old accounts before proceeding.
|
||||||
"scheduled account file: %v", err)
|
for _, a := range ad.nameToAccount {
|
||||||
|
if err := am.ds.FlushAccount(a); err != nil {
|
||||||
|
log.Errorf("Cannot write previously "+
|
||||||
|
"scheduled account file: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ad = openAccounts()
|
||||||
|
case *accessAccountRequest:
|
||||||
|
a, ok := ad.nameToAccount[cmd.name]
|
||||||
|
if !ok {
|
||||||
|
a = nil
|
||||||
|
}
|
||||||
|
cmd.resp <- a
|
||||||
|
|
||||||
|
case *accessAccountByAddressRequest:
|
||||||
|
a, ok := ad.addressToAccount[cmd.address]
|
||||||
|
if !ok {
|
||||||
|
a = nil
|
||||||
|
}
|
||||||
|
cmd.resp <- a
|
||||||
|
|
||||||
|
case *accessAllRequest:
|
||||||
|
s := make([]*Account, 0, len(ad.nameToAccount))
|
||||||
|
for _, a := range ad.nameToAccount {
|
||||||
|
s = append(s, a)
|
||||||
|
}
|
||||||
|
cmd.resp <- s
|
||||||
|
|
||||||
|
case *addAccountCmd:
|
||||||
|
ad.addAccount(cmd.a)
|
||||||
|
case *removeAccountCmd:
|
||||||
|
ad.removeAccount(cmd.a)
|
||||||
|
|
||||||
|
case *markAddressForAccountCmd:
|
||||||
|
// TODO(oga) make sure we own account
|
||||||
|
ad.addressToAccount[cmd.address] = cmd.account
|
||||||
}
|
}
|
||||||
|
|
||||||
ad = openAccounts()
|
case <-am.quit:
|
||||||
case *accessAccountRequest:
|
break out
|
||||||
a, ok := ad.nameToAccount[cmd.name]
|
|
||||||
if !ok {
|
|
||||||
a = nil
|
|
||||||
}
|
|
||||||
cmd.resp <- a
|
|
||||||
|
|
||||||
case *accessAccountByAddressRequest:
|
|
||||||
a, ok := ad.addressToAccount[cmd.address]
|
|
||||||
if !ok {
|
|
||||||
a = nil
|
|
||||||
}
|
|
||||||
cmd.resp <- a
|
|
||||||
|
|
||||||
case *accessAllRequest:
|
|
||||||
s := make([]*Account, 0, len(ad.nameToAccount))
|
|
||||||
for _, a := range ad.nameToAccount {
|
|
||||||
s = append(s, a)
|
|
||||||
}
|
|
||||||
cmd.resp <- s
|
|
||||||
|
|
||||||
case *addAccountCmd:
|
|
||||||
ad.addAccount(cmd.a)
|
|
||||||
case *removeAccountCmd:
|
|
||||||
ad.removeAccount(cmd.a)
|
|
||||||
|
|
||||||
case *markAddressForAccountCmd:
|
|
||||||
// TODO(oga) make sure we own account
|
|
||||||
ad.addressToAccount[cmd.address] = cmd.account
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
am.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// rescanListener listens for messages from the rescan manager and marks
|
// rescanListener listens for messages from the rescan manager and marks
|
||||||
|
@ -540,18 +573,22 @@ func (am *AccountManager) Account(name string) (*Account, error) {
|
||||||
|
|
||||||
// AccountByAddress returns the account specified by address, or
|
// AccountByAddress returns the account specified by address, or
|
||||||
// ErrNotFound as an error if the account is not found.
|
// ErrNotFound as an error if the account is not found.
|
||||||
func (am *AccountManager) AccountByAddress(addr btcutil.Address) (*Account,
|
func (am *AccountManager) AccountByAddress(addr btcutil.Address) (*Account, error) {
|
||||||
error) {
|
|
||||||
respChan := make(chan *Account)
|
respChan := make(chan *Account)
|
||||||
am.cmdChan <- &accessAccountByAddressRequest{
|
req := accessAccountByAddressRequest{
|
||||||
address: addr.EncodeAddress(),
|
address: addr.EncodeAddress(),
|
||||||
resp: respChan,
|
resp: respChan,
|
||||||
}
|
}
|
||||||
resp := <-respChan
|
select {
|
||||||
if resp == nil {
|
case am.cmdChan <- &req:
|
||||||
return nil, ErrNotFound
|
resp := <-respChan
|
||||||
|
if resp == nil {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
case <-am.quit:
|
||||||
|
return nil, ErrNoAccounts
|
||||||
}
|
}
|
||||||
return resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkAddressForAccount labels the given account as containing the provided
|
// MarkAddressForAccount labels the given account as containing the provided
|
||||||
|
@ -561,15 +598,18 @@ func (am *AccountManager) MarkAddressForAccount(address btcutil.Address,
|
||||||
// TODO(oga) really this entire dance should be carried out implicitly
|
// TODO(oga) really this entire dance should be carried out implicitly
|
||||||
// instead of requiring explicit messaging from the account to the
|
// instead of requiring explicit messaging from the account to the
|
||||||
// manager.
|
// manager.
|
||||||
am.cmdChan <- &markAddressForAccountCmd{
|
req := markAddressForAccountCmd{
|
||||||
address: address.EncodeAddress(),
|
address: address.EncodeAddress(),
|
||||||
account: account,
|
account: account,
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case am.cmdChan <- &req:
|
||||||
|
case <-am.quit:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address looks up an address if it is known to wallet at all.
|
// Address looks up an address if it is known to wallet at all.
|
||||||
func (am *AccountManager) Address(addr btcutil.Address) (wallet.WalletAddress,
|
func (am *AccountManager) Address(addr btcutil.Address) (wallet.WalletAddress, error) {
|
||||||
error) {
|
|
||||||
a, err := am.AccountByAddress(addr)
|
a, err := am.AccountByAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -581,25 +621,38 @@ func (am *AccountManager) Address(addr btcutil.Address) (wallet.WalletAddress,
|
||||||
// AllAccounts returns a slice of all managed accounts.
|
// AllAccounts returns a slice of all managed accounts.
|
||||||
func (am *AccountManager) AllAccounts() []*Account {
|
func (am *AccountManager) AllAccounts() []*Account {
|
||||||
respChan := make(chan []*Account)
|
respChan := make(chan []*Account)
|
||||||
am.cmdChan <- &accessAllRequest{
|
req := accessAllRequest{
|
||||||
resp: respChan,
|
resp: respChan,
|
||||||
}
|
}
|
||||||
return <-respChan
|
select {
|
||||||
|
case am.cmdChan <- &req:
|
||||||
|
return <-respChan
|
||||||
|
case <-am.quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAccount adds an account to the collection managed by an AccountManager.
|
// AddAccount adds an account to the collection managed by an AccountManager.
|
||||||
func (am *AccountManager) AddAccount(a *Account) {
|
func (am *AccountManager) AddAccount(a *Account) {
|
||||||
am.cmdChan <- &addAccountCmd{
|
req := addAccountCmd{
|
||||||
a: a,
|
a: a,
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case am.cmdChan <- &req:
|
||||||
|
case <-am.quit:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAccount removes an account to the collection managed by an
|
// RemoveAccount removes an account to the collection managed by an
|
||||||
// AccountManager.
|
// AccountManager.
|
||||||
func (am *AccountManager) RemoveAccount(a *Account) {
|
func (am *AccountManager) RemoveAccount(a *Account) {
|
||||||
am.cmdChan <- &removeAccountCmd{
|
req := removeAccountCmd{
|
||||||
a: a,
|
a: a,
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case am.cmdChan <- &req:
|
||||||
|
case <-am.quit:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterNewAccount adds a new memory account to the account manager,
|
// RegisterNewAccount adds a new memory account to the account manager,
|
||||||
|
|
65
cmd.go
65
cmd.go
|
@ -32,8 +32,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg *config
|
cfg *config
|
||||||
server *rpcServer
|
server *rpcServer
|
||||||
|
shutdownChan = make(chan struct{})
|
||||||
|
|
||||||
curBlock = struct {
|
curBlock = struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
@ -102,6 +103,36 @@ func accessClient() (*rpcClient, error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clientConnect(certs []byte, newClient chan<- *rpcClient) {
|
||||||
|
const initialWait = 5 * time.Second
|
||||||
|
wait := initialWait
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-server.quit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := newRPCClient(certs)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to open chain server client "+
|
||||||
|
"connection: %v", err)
|
||||||
|
time.Sleep(wait)
|
||||||
|
wait <<= 1
|
||||||
|
if wait > time.Minute {
|
||||||
|
wait = time.Minute
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wait = initialWait
|
||||||
|
client.Start()
|
||||||
|
newClient <- client
|
||||||
|
|
||||||
|
client.WaitForShutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Work around defer not working after os.Exit.
|
// Work around defer not working after os.Exit.
|
||||||
if err := walletMain(); err != nil {
|
if err := walletMain(); err != nil {
|
||||||
|
@ -160,30 +191,18 @@ func walletMain() error {
|
||||||
// Start HTTP server to serve wallet client connections.
|
// Start HTTP server to serve wallet client connections.
|
||||||
server.Start()
|
server.Start()
|
||||||
|
|
||||||
|
// Shutdown the server if an interrupt signal is received.
|
||||||
|
addInterruptHandler(server.Stop)
|
||||||
|
|
||||||
// Start client connection to a btcd chain server. Attempt
|
// Start client connection to a btcd chain server. Attempt
|
||||||
// reconnections if the client could not be successfully connected.
|
// reconnections if the client could not be successfully connected.
|
||||||
clientChan := make(chan *rpcClient)
|
clientChan := make(chan *rpcClient)
|
||||||
go clientAccess(clientChan)
|
go clientAccess(clientChan)
|
||||||
const initialWait = 5 * time.Second
|
go clientConnect(certs, clientChan)
|
||||||
wait := initialWait
|
|
||||||
for {
|
|
||||||
client, err := newRPCClient(certs)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Unable to open chain server client "+
|
|
||||||
"connection: %v", err)
|
|
||||||
time.Sleep(wait)
|
|
||||||
wait <<= 1
|
|
||||||
if wait > time.Minute {
|
|
||||||
wait = time.Minute
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
wait = initialWait
|
// Wait for the server to shutdown either due to a stop RPC request
|
||||||
|
// or an interrupt.
|
||||||
client.Start()
|
server.WaitForShutdown()
|
||||||
clientChan <- client
|
log.Info("Shutdown complete")
|
||||||
|
return nil
|
||||||
client.WaitForShutdown()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
22
disksync.go
22
disksync.go
|
@ -204,6 +204,9 @@ type DiskSyncer struct {
|
||||||
// Account manager for this DiskSyncer. This is only
|
// Account manager for this DiskSyncer. This is only
|
||||||
// needed to grab the account manager semaphore.
|
// needed to grab the account manager semaphore.
|
||||||
am *AccountManager
|
am *AccountManager
|
||||||
|
|
||||||
|
quit chan struct{}
|
||||||
|
shutdown chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDiskSyncer creates a new DiskSyncer.
|
// NewDiskSyncer creates a new DiskSyncer.
|
||||||
|
@ -215,6 +218,8 @@ func NewDiskSyncer(am *AccountManager) *DiskSyncer {
|
||||||
writeBatch: make(chan *writeBatchRequest),
|
writeBatch: make(chan *writeBatchRequest),
|
||||||
exportAccount: make(chan *exportRequest),
|
exportAccount: make(chan *exportRequest),
|
||||||
am: am,
|
am: am,
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
shutdown: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,6 +228,14 @@ func (ds *DiskSyncer) Start() {
|
||||||
go ds.handler()
|
go ds.handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ds *DiskSyncer) Stop() {
|
||||||
|
close(ds.quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds *DiskSyncer) WaitForShutdown() {
|
||||||
|
<-ds.shutdown
|
||||||
|
}
|
||||||
|
|
||||||
// handler runs the disk syncer. It manages a set of "dirty" account files
|
// handler runs the disk syncer. It manages a set of "dirty" account files
|
||||||
// which must be written to disk, and synchronizes all writes in a single
|
// which must be written to disk, and synchronizes all writes in a single
|
||||||
// goroutine. Periodic flush operations may be signaled by an AccountManager.
|
// goroutine. Periodic flush operations may be signaled by an AccountManager.
|
||||||
|
@ -239,6 +252,7 @@ func (ds *DiskSyncer) handler() {
|
||||||
var timer <-chan time.Time
|
var timer <-chan time.Time
|
||||||
var sem chan struct{}
|
var sem chan struct{}
|
||||||
schedule := newSyncSchedule(netdir)
|
schedule := newSyncSchedule(netdir)
|
||||||
|
out:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-sem: // Now have exclusive access of the account manager
|
case <-sem: // Now have exclusive access of the account manager
|
||||||
|
@ -288,8 +302,16 @@ func (ds *DiskSyncer) handler() {
|
||||||
a := er.a
|
a := er.a
|
||||||
dir := er.dir
|
dir := er.dir
|
||||||
er.err <- a.writeAll(dir)
|
er.err <- a.writeAll(dir)
|
||||||
|
|
||||||
|
case <-ds.quit:
|
||||||
|
err := schedule.flush()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot write accounts: %v", err)
|
||||||
|
}
|
||||||
|
break out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(ds.shutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlushAccount writes all scheduled account files to disk for a single
|
// FlushAccount writes all scheduled account files to disk for a single
|
||||||
|
|
24
rescan.go
24
rescan.go
|
@ -17,6 +17,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/conformal/btcutil"
|
"github.com/conformal/btcutil"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
)
|
)
|
||||||
|
@ -68,6 +70,8 @@ type RescanManager struct {
|
||||||
status chan interface{} // rescanProgress and rescanFinished
|
status chan interface{} // rescanProgress and rescanFinished
|
||||||
msgs chan RescanMsg
|
msgs chan RescanMsg
|
||||||
jobCompleteChan chan chan struct{}
|
jobCompleteChan chan chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
quit chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRescanManager creates a new RescanManger. If msgChan is non-nil,
|
// NewRescanManager creates a new RescanManger. If msgChan is non-nil,
|
||||||
|
@ -80,15 +84,25 @@ func NewRescanManager(msgChan chan RescanMsg) *RescanManager {
|
||||||
status: make(chan interface{}, 1),
|
status: make(chan interface{}, 1),
|
||||||
msgs: msgChan,
|
msgs: msgChan,
|
||||||
jobCompleteChan: make(chan chan struct{}, 1),
|
jobCompleteChan: make(chan chan struct{}, 1),
|
||||||
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the goroutines to run the RescanManager.
|
// Start starts the goroutines to run the RescanManager.
|
||||||
func (m *RescanManager) Start() {
|
func (m *RescanManager) Start() {
|
||||||
|
m.wg.Add(2)
|
||||||
go m.jobHandler()
|
go m.jobHandler()
|
||||||
go m.rpcHandler()
|
go m.rpcHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *RescanManager) Stop() {
|
||||||
|
close(m.quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RescanManager) WaitForShutdown() {
|
||||||
|
m.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
type rescanBatch struct {
|
type rescanBatch struct {
|
||||||
addrs map[*Account][]btcutil.Address
|
addrs map[*Account][]btcutil.Address
|
||||||
outpoints map[btcwire.OutPoint]struct{}
|
outpoints map[btcwire.OutPoint]struct{}
|
||||||
|
@ -146,6 +160,7 @@ func (m *RescanManager) jobHandler() {
|
||||||
curBatch := newRescanBatch()
|
curBatch := newRescanBatch()
|
||||||
nextBatch := newRescanBatch()
|
nextBatch := newRescanBatch()
|
||||||
|
|
||||||
|
out:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case job := <-m.addJob:
|
case job := <-m.addJob:
|
||||||
|
@ -205,8 +220,16 @@ func (m *RescanManager) jobHandler() {
|
||||||
// Unexpected status message
|
// Unexpected status message
|
||||||
panic(s)
|
panic(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case <-m.quit:
|
||||||
|
break out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(m.sendJob)
|
||||||
|
if m.msgs != nil {
|
||||||
|
close(m.msgs)
|
||||||
|
}
|
||||||
|
m.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// rpcHandler reads jobs sent by the jobHandler and sends the rpc requests
|
// rpcHandler reads jobs sent by the jobHandler and sends the rpc requests
|
||||||
|
@ -228,6 +251,7 @@ func (m *RescanManager) rpcHandler() {
|
||||||
m.MarkFinished(rescanFinished{err})
|
m.MarkFinished(rescanFinished{err})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RescanJob is a job to be processed by the RescanManager. The job includes
|
// RescanJob is a job to be processed by the RescanManager. The job includes
|
||||||
|
|
10
rpcclient.go
10
rpcclient.go
|
@ -260,6 +260,7 @@ type rpcClient struct {
|
||||||
*btcrpcclient.Client // client to btcd
|
*btcrpcclient.Client // client to btcd
|
||||||
enqueueNotification chan notification
|
enqueueNotification chan notification
|
||||||
dequeueNotification chan notification
|
dequeueNotification chan notification
|
||||||
|
quit chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,6 +268,7 @@ func newRPCClient(certs []byte) (*rpcClient, error) {
|
||||||
client := rpcClient{
|
client := rpcClient{
|
||||||
enqueueNotification: make(chan notification),
|
enqueueNotification: make(chan notification),
|
||||||
dequeueNotification: make(chan notification),
|
dequeueNotification: make(chan notification),
|
||||||
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
initializedClient := make(chan struct{})
|
initializedClient := make(chan struct{})
|
||||||
ntfnCallbacks := btcrpcclient.NotificationHandlers{
|
ntfnCallbacks := btcrpcclient.NotificationHandlers{
|
||||||
|
@ -317,7 +319,13 @@ func (c *rpcClient) Stop() {
|
||||||
log.Warn("Disconnecting chain server client connection")
|
log.Warn("Disconnecting chain server client connection")
|
||||||
c.Client.Shutdown()
|
c.Client.Shutdown()
|
||||||
}
|
}
|
||||||
close(c.enqueueNotification)
|
|
||||||
|
select {
|
||||||
|
case <-c.quit:
|
||||||
|
default:
|
||||||
|
close(c.quit)
|
||||||
|
close(c.enqueueNotification)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rpcClient) WaitForShutdown() {
|
func (c *rpcClient) WaitForShutdown() {
|
||||||
|
|
129
rpcserver.go
129
rpcserver.go
|
@ -213,11 +213,13 @@ type rpcServer struct {
|
||||||
|
|
||||||
upgrader websocket.Upgrader
|
upgrader websocket.Upgrader
|
||||||
|
|
||||||
requests requestChan
|
requests chan handlerJob
|
||||||
|
|
||||||
addWSClient chan *websocketClient
|
addWSClient chan *websocketClient
|
||||||
removeWSClient chan *websocketClient
|
removeWSClient chan *websocketClient
|
||||||
broadcasts chan []byte
|
broadcasts chan []byte
|
||||||
|
|
||||||
|
quit chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRPCServer creates a new server for serving RPC client connections, both
|
// newRPCServer creates a new server for serving RPC client connections, both
|
||||||
|
@ -232,10 +234,11 @@ func newRPCServer(listenAddrs []string) (*rpcServer, error) {
|
||||||
// Allow all origins.
|
// Allow all origins.
|
||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
},
|
},
|
||||||
requests: make(requestChan),
|
requests: make(chan handlerJob),
|
||||||
addWSClient: make(chan *websocketClient),
|
addWSClient: make(chan *websocketClient),
|
||||||
removeWSClient: make(chan *websocketClient),
|
removeWSClient: make(chan *websocketClient),
|
||||||
broadcasts: make(chan []byte),
|
broadcasts: make(chan []byte),
|
||||||
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for existence of cert file and key file
|
// Check for existence of cert file and key file
|
||||||
|
@ -292,8 +295,9 @@ func (s *rpcServer) Start() {
|
||||||
// A duplicator for notifications intended for all clients runs
|
// A duplicator for notifications intended for all clients runs
|
||||||
// in another goroutines. Any such notifications are sent to
|
// in another goroutines. Any such notifications are sent to
|
||||||
// the allClients channel and then sent to each connected client.
|
// the allClients channel and then sent to each connected client.
|
||||||
|
s.wg.Add(2)
|
||||||
go s.NotificationHandler()
|
go s.NotificationHandler()
|
||||||
go s.requests.handler()
|
go s.RequestHandler()
|
||||||
|
|
||||||
log.Trace("Starting RPC server")
|
log.Trace("Starting RPC server")
|
||||||
|
|
||||||
|
@ -349,16 +353,56 @@ func (s *rpcServer) Start() {
|
||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
go func(listener net.Listener) {
|
go func(listener net.Listener) {
|
||||||
log.Infof("RPCS: RPC server listening on %s", listener.Addr())
|
log.Infof("RPCS: RPC server listening on %s", listener.Addr())
|
||||||
if err := httpServer.Serve(listener); err != nil {
|
_ = httpServer.Serve(listener)
|
||||||
log.Errorf("Listener for %s exited with error: %v",
|
|
||||||
listener.Addr(), err)
|
|
||||||
}
|
|
||||||
log.Tracef("RPCS: RPC listener done for %s", listener.Addr())
|
log.Tracef("RPCS: RPC listener done for %s", listener.Addr())
|
||||||
s.wg.Done()
|
s.wg.Done()
|
||||||
}(listener)
|
}(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop gracefully shuts down the rpc server by stopping and disconnecting all
|
||||||
|
// clients, disconnecting the chain server connection, and closing the wallet's
|
||||||
|
// account files.
|
||||||
|
func (s *rpcServer) Stop() {
|
||||||
|
// If the server is changed to run more than one rpc handler at a time,
|
||||||
|
// to prevent a double channel close, this should be replaced with an
|
||||||
|
// atomic test-and-set.
|
||||||
|
select {
|
||||||
|
case <-s.quit:
|
||||||
|
log.Warnf("Server already shutting down")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warn("Server shutting down")
|
||||||
|
|
||||||
|
// Stop all the listeners. There will not be any listeners if
|
||||||
|
// listening is disabled.
|
||||||
|
for _, listener := range s.listeners {
|
||||||
|
err := listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot close listener %s: %v",
|
||||||
|
listener.Addr(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect the connected chain server, if any.
|
||||||
|
client, err := accessClient()
|
||||||
|
if err == nil {
|
||||||
|
client.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the account manager and finish all pending account file writes.
|
||||||
|
AcctMgr.Stop()
|
||||||
|
|
||||||
|
// Signal the remaining goroutines to stop.
|
||||||
|
close(s.quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rpcServer) WaitForShutdown() {
|
||||||
|
s.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// ErrNoAuth represents an error where authentication could not succeed
|
// ErrNoAuth represents an error where authentication could not succeed
|
||||||
// due to a missing Authorization HTTP header.
|
// due to a missing Authorization HTTP header.
|
||||||
var ErrNoAuth = errors.New("no auth")
|
var ErrNoAuth = errors.New("no auth")
|
||||||
|
@ -789,6 +833,7 @@ func (s *rpcServer) NotifyConnectionStatus(wsc *websocketClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *rpcServer) NotificationHandler() {
|
func (s *rpcServer) NotificationHandler() {
|
||||||
|
out:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case c := <-s.addWSClient:
|
case c := <-s.addWSClient:
|
||||||
|
@ -801,8 +846,11 @@ func (s *rpcServer) NotificationHandler() {
|
||||||
delete(s.wsClients, wsc)
|
delete(s.wsClients, wsc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case <-s.quit:
|
||||||
|
break out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// requestHandler is a handler function to handle an unmarshaled and parsed
|
// requestHandler is a handler function to handle an unmarshaled and parsed
|
||||||
|
@ -841,6 +889,7 @@ var rpcHandlers = map[string]requestHandler{
|
||||||
"settxfee": SetTxFee,
|
"settxfee": SetTxFee,
|
||||||
"signmessage": SignMessage,
|
"signmessage": SignMessage,
|
||||||
"signrawtransaction": SignRawTransaction,
|
"signrawtransaction": SignRawTransaction,
|
||||||
|
"stop": Stop,
|
||||||
"validateaddress": ValidateAddress,
|
"validateaddress": ValidateAddress,
|
||||||
"verifymessage": VerifyMessage,
|
"verifymessage": VerifyMessage,
|
||||||
"walletlock": WalletLock,
|
"walletlock": WalletLock,
|
||||||
|
@ -859,7 +908,6 @@ var rpcHandlers = map[string]requestHandler{
|
||||||
"listreceivedbyaccount": Unimplemented,
|
"listreceivedbyaccount": Unimplemented,
|
||||||
"move": Unimplemented,
|
"move": Unimplemented,
|
||||||
"setaccount": Unimplemented,
|
"setaccount": Unimplemented,
|
||||||
"stop": Unimplemented,
|
|
||||||
|
|
||||||
// Standard bitcoind methods which won't be implemented by btcwallet.
|
// Standard bitcoind methods which won't be implemented by btcwallet.
|
||||||
"encryptwallet": Unsupported,
|
"encryptwallet": Unsupported,
|
||||||
|
@ -901,36 +949,42 @@ type handlerJob struct {
|
||||||
response chan<- handlerResponse
|
response chan<- handlerResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
type requestChan chan handlerJob
|
// RequestHandler reads and processes client requests from the request channel.
|
||||||
|
// Each request is run with exclusive access to the account manager.
|
||||||
|
func (s *rpcServer) RequestHandler() {
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case r := <-s.requests:
|
||||||
|
AcctMgr.Grab()
|
||||||
|
result, err := r.handler(r.request)
|
||||||
|
AcctMgr.Release()
|
||||||
|
|
||||||
// handler reads and processes client requests from the channel. Each
|
var jsonErr *btcjson.Error
|
||||||
// request is run with exclusive access to the account manager.
|
if err != nil {
|
||||||
func (c requestChan) handler() {
|
jsonErr = &btcjson.Error{Message: err.Error()}
|
||||||
for r := range c {
|
switch e := err.(type) {
|
||||||
AcctMgr.Grab()
|
case btcjson.Error:
|
||||||
result, err := r.handler(r.request)
|
*jsonErr = e
|
||||||
AcctMgr.Release()
|
case DeserializationError:
|
||||||
|
jsonErr.Code = btcjson.ErrDeserialization.Code
|
||||||
var jsonErr *btcjson.Error
|
case InvalidParameterError:
|
||||||
if err != nil {
|
jsonErr.Code = btcjson.ErrInvalidParameter.Code
|
||||||
jsonErr = &btcjson.Error{Message: err.Error()}
|
case ParseError:
|
||||||
switch e := err.(type) {
|
jsonErr.Code = btcjson.ErrParse.Code
|
||||||
case btcjson.Error:
|
case InvalidAddressOrKeyError:
|
||||||
*jsonErr = e
|
jsonErr.Code = btcjson.ErrInvalidAddressOrKey.Code
|
||||||
case DeserializationError:
|
default: // All other errors get the wallet error code.
|
||||||
jsonErr.Code = btcjson.ErrDeserialization.Code
|
jsonErr.Code = btcjson.ErrWallet.Code
|
||||||
case InvalidParameterError:
|
}
|
||||||
jsonErr.Code = btcjson.ErrInvalidParameter.Code
|
|
||||||
case ParseError:
|
|
||||||
jsonErr.Code = btcjson.ErrParse.Code
|
|
||||||
case InvalidAddressOrKeyError:
|
|
||||||
jsonErr.Code = btcjson.ErrInvalidAddressOrKey.Code
|
|
||||||
default: // All other errors get the wallet error code.
|
|
||||||
jsonErr.Code = btcjson.ErrWallet.Code
|
|
||||||
}
|
}
|
||||||
|
r.response <- handlerResponse{result, jsonErr}
|
||||||
|
|
||||||
|
case <-s.quit:
|
||||||
|
break out
|
||||||
}
|
}
|
||||||
r.response <- handlerResponse{result, jsonErr}
|
|
||||||
}
|
}
|
||||||
|
s.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unimplemented handles an unimplemented RPC request with the
|
// Unimplemented handles an unimplemented RPC request with the
|
||||||
|
@ -2492,6 +2546,13 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop handles the stop command by shutting down the process after the request
|
||||||
|
// is handled.
|
||||||
|
func Stop(icmd btcjson.Cmd) (interface{}, error) {
|
||||||
|
server.Stop()
|
||||||
|
return "btcwallet stopping.", nil
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateAddress handles the validateaddress command.
|
// ValidateAddress handles the validateaddress command.
|
||||||
func ValidateAddress(icmd btcjson.Cmd) (interface{}, error) {
|
func ValidateAddress(icmd btcjson.Cmd) (interface{}, error) {
|
||||||
cmd, ok := icmd.(*btcjson.ValidateAddressCmd)
|
cmd, ok := icmd.(*btcjson.ValidateAddressCmd)
|
||||||
|
|
67
signal.go
Normal file
67
signal.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// interruptChannel is used to receive SIGINT (Ctrl+C) signals.
|
||||||
|
var interruptChannel chan os.Signal
|
||||||
|
|
||||||
|
// addHandlerChannel is used to add an interrupt handler to the list of handlers
|
||||||
|
// to be invoked on SIGINT (Ctrl+C) signals.
|
||||||
|
var addHandlerChannel = make(chan func())
|
||||||
|
|
||||||
|
// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the
|
||||||
|
// interruptChannel and invokes the registered interruptCallbacks accordingly.
|
||||||
|
// It also listens for callback registration. It must be run as a goroutine.
|
||||||
|
func mainInterruptHandler() {
|
||||||
|
// interruptCallbacks is a list of callbacks to invoke when a
|
||||||
|
// SIGINT (Ctrl+C) is received.
|
||||||
|
var interruptCallbacks []func()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-interruptChannel:
|
||||||
|
log.Info("Received SIGINT (Ctrl+C). Shutting down...")
|
||||||
|
// run handlers in LIFO order.
|
||||||
|
for i := range interruptCallbacks {
|
||||||
|
idx := len(interruptCallbacks) - 1 - i
|
||||||
|
interruptCallbacks[idx]()
|
||||||
|
}
|
||||||
|
|
||||||
|
case handler := <-addHandlerChannel:
|
||||||
|
interruptCallbacks = append(interruptCallbacks, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) is
|
||||||
|
// received.
|
||||||
|
func addInterruptHandler(handler func()) {
|
||||||
|
// Create the channel and start the main interrupt handler which invokes
|
||||||
|
// all other callbacks and exits if not already done.
|
||||||
|
if interruptChannel == nil {
|
||||||
|
interruptChannel = make(chan os.Signal, 1)
|
||||||
|
signal.Notify(interruptChannel, os.Interrupt)
|
||||||
|
go mainInterruptHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
addHandlerChannel <- handler
|
||||||
|
}
|
Loading…
Reference in a new issue