diff --git a/acctmgr.go b/acctmgr.go index ace8165..b8d7480 100644 --- a/acctmgr.go +++ b/acctmgr.go @@ -43,6 +43,7 @@ type AccountManager struct { // binary semaphore channel to prevent incorrect access. bsem chan struct{} + openAccounts chan struct{} accessAccount chan *accessAccountRequest accessAll chan *accessAllRequest add chan *Account @@ -55,6 +56,7 @@ type AccountManager struct { func NewAccountManager() *AccountManager { am := &AccountManager{ bsem: make(chan struct{}, 1), + openAccounts: make(chan struct{}, 1), accessAccount: make(chan *accessAccountRequest), accessAll: make(chan *accessAllRequest), add: make(chan *Account), @@ -81,6 +83,20 @@ func (am *AccountManager) Start() { for { select { + case <-am.openAccounts: + // Write all old accounts before proceeding. + for e := l.Front(); e != nil; e = e.Next() { + a := e.Value.(*Account) + am.ds.FlushAccount(a) + } + + m = OpenAccounts() + + l.Init() + for _, a := range m { + l.PushBack(a) + } + case access := <-am.accessAccount: a, ok := m[access.name] access.resp <- &accessAccountResponse{ @@ -129,6 +145,10 @@ func (am *AccountManager) Release() { am.bsem <- struct{}{} } +func (am *AccountManager) OpenAccounts() { + am.openAccounts <- struct{}{} +} + type accessAccountRequest struct { name string resp chan *accessAccountResponse @@ -196,7 +216,7 @@ func (am *AccountManager) RegisterNewAccount(a *Account) error { // Rollback rolls back each managed Account to the state before the block // specified by height and hash was connected to the main chain. func (am *AccountManager) Rollback(height int32, hash *btcwire.ShaHash) { - log.Debugf("Rolling back tx history since block height %v", height) + log.Infof("Rolling back tx history since block height %v", height) for _, a := range am.AllAccounts() { a.TxStore.Rollback(height) @@ -204,13 +224,6 @@ func (am *AccountManager) Rollback(height int32, hash *btcwire.ShaHash) { } } -// Rollback reverts each stored Account to a state before the block -// with the passed chainheight and block hash was connected to the main -// chain. This is used to remove transactions and utxos for each wallet -// that occured on a chain no longer considered to be the main chain. -func (a *Account) Rollback(height int32, hash *btcwire.ShaHash) { -} - // BlockNotify notifies all frontends of any changes from the new block, // including changed balances. Each account is then set to be synced // with the latest block. diff --git a/cmd.go b/cmd.go index 0725d80..10393ca 100644 --- a/cmd.go +++ b/cmd.go @@ -146,10 +146,9 @@ func main() { // Check and update any old file locations. updateOldFileLocations() + // Start account manager and open accounts. go AcctMgr.Start() - - // Open all account saved to disk. - OpenAccounts() + AcctMgr.OpenAccounts() // Read CA file to verify a btcd TLS connection. cafile, err := ioutil.ReadFile(cfg.CAFile) @@ -323,7 +322,7 @@ func OpenSavedAccount(name string, cfg *config) (*Account, error) { } // OpenAccounts attempts to open all saved accounts. -func OpenAccounts() { +func OpenAccounts() map[string]*Account { // If the network (account) directory is missing, but the temporary // directory exists, move it. This is unlikely to happen, but possible, // if writing out every account file at once to a tmp directory (as is @@ -335,7 +334,7 @@ func OpenAccounts() { if !fileExists(netDir) && fileExists(tmpNetDir) { if err := Rename(tmpNetDir, netDir); err != nil { log.Errorf("Cannot move temporary network dir: %v", err) - return + return nil } } @@ -346,13 +345,15 @@ func OpenAccounts() { switch err.(type) { case *WalletOpenError: log.Errorf("Default account wallet file unreadable: %v", err) - return + return nil default: log.Warnf("Non-critical problem opening an account file: %v", err) } } - AcctMgr.AddAccount(a) + accounts := map[string]*Account{ + "": a, + } // Read all filenames in the account directory, and look for any // filenames matching '*-wallet.bin'. These are wallets for @@ -361,7 +362,7 @@ func OpenAccounts() { if err != nil { // Can't continue. log.Errorf("Unable to open account directory: %v", err) - return + return nil } defer accountDir.Close() fileNames, err := accountDir.Readdirnames(0) @@ -370,20 +371,20 @@ func OpenAccounts() { // at least try to open some accounts. log.Errorf("Unable to read all account files: %v", err) } - var accounts []string + var accountNames []string for _, file := range fileNames { if strings.HasSuffix(file, "-wallet.bin") { name := strings.TrimSuffix(file, "-wallet.bin") - accounts = append(accounts, name) + accountNames = append(accountNames, name) } } // Open all additional accounts. - for _, a := range accounts { + for _, acctName := range accountNames { // Log txstore/utxostore errors as these will be recovered // from with a rescan, but wallet errors must be returned // to the caller. - a, err := OpenSavedAccount(a, cfg) + a, err := OpenSavedAccount(acctName, cfg) if err != nil { switch err.(type) { case *WalletOpenError: @@ -393,9 +394,10 @@ func OpenAccounts() { log.Warnf("Non-critical error opening an account file: %v", err) } } else { - AcctMgr.AddAccount(a) + accounts[acctName] = a } } + return accounts } var accessServer = make(chan *AccessCurrentServerConn) diff --git a/ntfns.go b/ntfns.go index 7469d59..fae8558 100644 --- a/ntfns.go +++ b/ntfns.go @@ -20,6 +20,10 @@ package main import ( "encoding/hex" + "fmt" + "sync" + "time" + "github.com/conformal/btcjson" "github.com/conformal/btcscript" "github.com/conformal/btcutil" @@ -27,8 +31,6 @@ import ( "github.com/conformal/btcwallet/wallet" "github.com/conformal/btcwire" "github.com/conformal/btcws" - "sync" - "time" ) func parseBlock(block *btcws.BlockDetails) (*tx.BlockDetails, error) { @@ -47,7 +49,7 @@ func parseBlock(block *btcws.BlockDetails) (*tx.BlockDetails, error) { }, nil } -type notificationHandler func(btcjson.Cmd) +type notificationHandler func(btcjson.Cmd) error var notificationHandlers = map[string]notificationHandler{ btcws.BlockConnectedNtfnMethod: NtfnBlockConnected, @@ -57,36 +59,31 @@ var notificationHandlers = map[string]notificationHandler{ } // NtfnRecvTx handles the btcws.RecvTxNtfn notification. -func NtfnRecvTx(n btcjson.Cmd) { +func NtfnRecvTx(n btcjson.Cmd) error { rtx, ok := n.(*btcws.RecvTxNtfn) if !ok { - log.Errorf("%v handler: unexpected type", n.Method()) - return + return fmt.Errorf("%v handler: unexpected type", n.Method()) } bs, err := GetCurBlock() if err != nil { - log.Errorf("%v handler: cannot get current block: %v", n.Method(), err) - return + return fmt.Errorf("%v handler: cannot get current block: %v", n.Method(), err) } rawTx, err := hex.DecodeString(rtx.HexTx) if err != nil { - log.Errorf("%v handler: bad hexstring: err", n.Method(), err) - return + return fmt.Errorf("%v handler: bad hexstring: err", n.Method(), err) } tx_, err := btcutil.NewTxFromBytes(rawTx) if err != nil { - log.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err) - return + return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err) } var block *tx.BlockDetails if rtx.Block != nil { block, err = parseBlock(rtx.Block) if err != nil { - log.Errorf("%v handler: bad block: %v", n.Method(), err) - return + return fmt.Errorf("%v handler: bad block: %v", n.Method(), err) } } @@ -131,7 +128,10 @@ func NtfnRecvTx(n btcjson.Cmd) { } for _, a := range accounts { - record := a.TxStore.InsertRecvTxOut(tx_, uint32(outIdx), false, received, block) + record, err := a.TxStore.InsertRecvTxOut(tx_, uint32(outIdx), false, received, block) + if err != nil { + return err + } AcctMgr.ds.ScheduleTxStoreWrite(a) // Notify frontends of tx. If the tx is unconfirmed, it is always @@ -163,6 +163,8 @@ func NtfnRecvTx(n btcjson.Cmd) { NotifyWalletBalanceUnconfirmed(allClients, a.name, unconfirmed) } } + + return nil } // NtfnBlockConnected handles btcd notifications resulting from newly @@ -172,16 +174,14 @@ func NtfnRecvTx(n btcjson.Cmd) { // to mark wallet files with a possibly-better earliest block height, // and will greatly reduce rescan times for wallets created with an // out of sync btcd. -func NtfnBlockConnected(n btcjson.Cmd) { +func NtfnBlockConnected(n btcjson.Cmd) error { bcn, ok := n.(*btcws.BlockConnectedNtfn) if !ok { - log.Errorf("%v handler: unexpected type", n.Method()) - return + return fmt.Errorf("%v handler: unexpected type", n.Method()) } hash, err := btcwire.NewShaHashFromStr(bcn.Hash) if err != nil { - log.Errorf("%v handler: invalid hash string", n.Method()) - return + return fmt.Errorf("%v handler: invalid hash string", n.Method()) } // Update the blockstamp for the newly-connected block. @@ -211,21 +211,21 @@ func NtfnBlockConnected(n btcjson.Cmd) { // Pass notification to frontends too. marshaled, _ := n.MarshalJSON() allClients <- marshaled + + return nil } // NtfnBlockDisconnected handles btcd notifications resulting from // blocks disconnected from the main chain in the event of a chain // switch and notifies frontends of the new blockchain height. -func NtfnBlockDisconnected(n btcjson.Cmd) { +func NtfnBlockDisconnected(n btcjson.Cmd) error { bdn, ok := n.(*btcws.BlockDisconnectedNtfn) if !ok { - log.Errorf("%v handler: unexpected type", n.Method()) - return + return fmt.Errorf("%v handler: unexpected type", n.Method()) } hash, err := btcwire.NewShaHashFromStr(bdn.Hash) if err != nil { - log.Errorf("%v handler: invalid hash string", n.Method()) - return + return fmt.Errorf("%v handler: invalid hash string", n.Method()) } // Rollback Utxo and Tx data stores. @@ -234,32 +234,32 @@ func NtfnBlockDisconnected(n btcjson.Cmd) { // Pass notification to frontends too. marshaled, _ := n.MarshalJSON() allClients <- marshaled + + return nil } // NtfnRedeemingTx handles btcd redeemingtx notifications resulting from a // transaction spending a watched outpoint. -func NtfnRedeemingTx(n btcjson.Cmd) { +func NtfnRedeemingTx(n btcjson.Cmd) error { cn, ok := n.(*btcws.RedeemingTxNtfn) if !ok { - log.Errorf("%v handler: unexpected type", n.Method()) - return + return fmt.Errorf("%v handler: unexpected type", n.Method()) } rawTx, err := hex.DecodeString(cn.HexTx) if err != nil { - log.Errorf("%v handler: bad hexstring: err", n.Method(), err) - return + return fmt.Errorf("%v handler: bad hexstring: err", n.Method(), err) } tx_, err := btcutil.NewTxFromBytes(rawTx) if err != nil { - log.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err) - return + return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err) } block, err := parseBlock(cn.Block) if err != nil { - log.Errorf("%v handler: bad block: %v", n.Method(), err) - return + return fmt.Errorf("%v handler: bad block: %v", n.Method(), err) } AcctMgr.RecordSpendingTx(tx_, block) + + return nil } diff --git a/rpcclient.go b/rpcclient.go index 3a3bcd1..f0bc16e 100644 --- a/rpcclient.go +++ b/rpcclient.go @@ -110,6 +110,15 @@ func (btcd *BtcdRPCConn) Connected() bool { } } +// Close forces closing the current btcd connection. +func (btcd *BtcdRPCConn) Close() { + select { + case <-btcd.closed: + default: + close(btcd.closed) + } +} + // AddRPCRequest is used to add an RPCRequest to the pool of requests // being manaaged by a btcd RPC connection. type AddRPCRequest struct { diff --git a/rpcserver.go b/rpcserver.go index abe7570..10a1f86 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -207,8 +207,31 @@ func WalletRequestProcessor() { case n := <-handleNtfn: if f, ok := notificationHandlers[n.Method()]; ok { AcctMgr.Grab() - f(n) + err := f(n) AcctMgr.Release() + switch err { + case nil: + // ignore + + case tx.ErrInconsistantStore: + // Likely due to a mis-ordered btcd notification. + // To recover, close server connection and reopen + // all accounts from their last good state saved + // to disk. This will trigger the handshake on + // next connect, and a rescan of one or two blocks + // to catch up rather than throwing away all tx + // history and rescanning everything. + s := CurrentServerConn() + if btcd, ok := s.(*BtcdRPCConn); ok { + AcctMgr.Grab() + btcd.Close() + AcctMgr.OpenAccounts() + AcctMgr.Release() + } + + default: // other non-nil + log.Warn(err) + } } } } @@ -1341,7 +1364,11 @@ func SendBeforeReceiveHistorySync(add, done, remove chan btcwire.ShaHash, func handleSendRawTxReply(icmd btcjson.Cmd, txIDStr string, a *Account, txInfo *CreatedTx) (interface{}, *btcjson.Error) { // Add to transaction store. - stx := a.TxStore.InsertSignedTx(txInfo.tx, nil) + stx, err := a.TxStore.InsertSignedTx(txInfo.tx, nil) + if err != nil { + log.Warnf("Error adding sent tx history: %v", err) + return nil, &btcjson.ErrInternal + } AcctMgr.ds.ScheduleTxStoreWrite(a) // Notify frontends of new SendTx. diff --git a/tx/tx.go b/tx/tx.go index 69cee00..4ade4ad 100644 --- a/tx/tx.go +++ b/tx/tx.go @@ -42,6 +42,10 @@ var ( // object is marked with a version that is no longer supported // during deserialization. ErrUnsupportedVersion = errors.New("version no longer supported") + + // ErrInconsistantStore represents an error for when an inconsistancy + // is detected during inserting or returning transaction records. + ErrInconsistantStore = errors.New("inconsistant transaction store") ) // Record is a common interface shared by SignedTx and RecvTxOut transaction @@ -264,7 +268,7 @@ func (s *Store) ReadFrom(r io.Reader) (int64, error) { // It is an error for the backing transaction to have // not already been read. if _, ok := s.txs[rtx.blockTx()]; !ok { - return n64, errors.New("missing backing transaction") + return n64, ErrInconsistantStore } // Add entries to store. @@ -290,7 +294,7 @@ func (s *Store) ReadFrom(r io.Reader) (int64, error) { // It is an error for the backing transaction to have // not already been read. if _, ok := s.txs[stx.blockTx()]; !ok { - return n64, errors.New("missing backing transaction") + return n64, ErrInconsistantStore } // Add entries to store. @@ -371,7 +375,7 @@ func (s *Store) WriteTo(w io.Writer) (int64, error) { // store, returning the record. Duplicates and double spend correction is // handled automatically. Transactions may be added without block details, // and later added again with block details once the tx has been mined. -func (s *Store) InsertSignedTx(tx *btcutil.Tx, block *BlockDetails) *SignedTx { +func (s *Store) InsertSignedTx(tx *btcutil.Tx, block *BlockDetails) (*SignedTx, error) { var created time.Time if block == nil { created = time.Now() @@ -387,8 +391,11 @@ func (s *Store) InsertSignedTx(tx *btcutil.Tx, block *BlockDetails) *SignedTx { block: block, } - s.insertTx(tx, st) - return st.record(s).(*SignedTx) + err := s.insertTx(tx, st) + if err != nil { + return nil, ErrInconsistantStore + } + return st.record(s).(*SignedTx), nil } // Rollback removes block details for all transactions at or beyond a @@ -398,33 +405,36 @@ func (s *Store) InsertSignedTx(tx *btcutil.Tx, block *BlockDetails) *SignedTx { // chain are added to the store. func (s *Store) Rollback(height int32) { for e := s.sorted.Front(); e != nil; e = e.Next() { - tx := e.Value.(txRecord) - if details := tx.Block(); details != nil { - txSha := tx.TxSha() - oldKey := blockTx{*txSha, details.Height} - if details.Height >= height { - tx.setBlock(nil) + record := e.Value.(txRecord) + block := record.Block() + if block == nil { + // Unmined, no block details to remove. + continue + } + txSha := record.TxSha() + if block.Height >= height { + oldKey := blockTx{*txSha, block.Height} + record.setBlock(nil) - switch v := tx.(type) { - case *signedTx: - k := oldKey - delete(s.signed, k) - k.height = -1 - s.signed[k] = v + switch v := record.(type) { + case *signedTx: + k := oldKey + delete(s.signed, k) + k.height = -1 + s.signed[k] = v - case *recvTxOut: - k := blockOutPoint{v.outpoint, details.Height} - delete(s.recv, k) - k.height = -1 - s.recv[k] = v - } + case *recvTxOut: + k := blockOutPoint{v.outpoint, block.Height} + delete(s.recv, k) + k.height = -1 + s.recv[k] = v + } - if utx, ok := s.txs[oldKey]; ok { - k := oldKey - delete(s.txs, k) - k.height = -1 - s.txs[k] = utx - } + if utx, ok := s.txs[oldKey]; ok { + k := oldKey + delete(s.txs, k) + k.height = -1 + s.txs[k] = utx } } } @@ -449,7 +459,7 @@ func (s *Store) UnminedSignedTxs() []*btcutil.Tx { // with non-nil BlockDetails to update the record and all other records // using the transaction with the block. func (s *Store) InsertRecvTxOut(tx *btcutil.Tx, outIdx uint32, - change bool, received time.Time, block *BlockDetails) *RecvTxOut { + change bool, received time.Time, block *BlockDetails) (*RecvTxOut, error) { rt := &recvTxOut{ outpoint: *btcwire.NewOutPoint(tx.Sha(), outIdx), @@ -457,17 +467,27 @@ func (s *Store) InsertRecvTxOut(tx *btcutil.Tx, outIdx uint32, received: received, block: block, } - s.insertTx(tx, rt) - return rt.record(s).(*RecvTxOut) + err := s.insertTx(tx, rt) + if err != nil { + return nil, err + } + return rt.record(s).(*RecvTxOut), nil } -func (s *Store) insertTx(utx *btcutil.Tx, record txRecord) { +func (s *Store) insertTx(utx *btcutil.Tx, record txRecord) error { if ds := s.findDoubleSpend(utx); ds != nil { switch { case ds.txSha == *utx.Sha(): // identical tx if ds.height != record.Height() { - s.setTxBlock(utx.Sha(), record.Block()) - return + // Detect insert inconsistancies. If matching + // tx was found, but this record's block is unset, + // a rollback was missed. + block := record.Block() + if block == nil { + return ErrInconsistantStore + } + s.setTxBlock(utx.Sha(), block) + return nil } default: @@ -479,6 +499,7 @@ func (s *Store) insertTx(utx *btcutil.Tx, record txRecord) { } s.insertUniqueTx(utx, record) + return nil } func (s *Store) insertUniqueTx(utx *btcutil.Tx, record txRecord) { @@ -605,11 +626,6 @@ func (s *Store) removeDoubleSpends(oldKey *blockTx) { } func (s *Store) setTxBlock(txSha *btcwire.ShaHash, block *BlockDetails) { - if block == nil { - // Nothing to update. - return - } - // Lookup unmined backing tx. prevKey := blockTx{*txSha, -1} tx := s.txs[prevKey] diff --git a/tx/tx_test.go b/tx/tx_test.go index ba12727..23ce4a4 100644 --- a/tx/tx_test.go +++ b/tx/tx_test.go @@ -78,16 +78,18 @@ func TestTxStore(t *testing.T) { tests := []struct { name string - f func(*Store) *Store + f func(*Store) (*Store, error) + err error bal, unc int64 unspents map[btcwire.OutPoint]struct{} unmined map[btcwire.ShaHash]struct{} }{ { name: "new store", - f: func(_ *Store) *Store { - return NewStore() + f: func(_ *Store) (*Store, error) { + return NewStore(), nil }, + err: nil, bal: 0, unc: 0, unspents: map[btcwire.OutPoint]struct{}{}, @@ -95,10 +97,36 @@ func TestTxStore(t *testing.T) { }, { name: "txout insert", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil) + if err != nil { + return nil, err + } + // If the above succeeded, try using the record. This will + // dereference the tx and panic if the above didn't catch + // an inconsistant insert. + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, + bal: 0, + unc: TstRecvTx.MsgTx().TxOut[0].Value, + unspents: map[btcwire.OutPoint]struct{}{ + *btcwire.NewOutPoint(TstRecvTx.Sha(), 0): struct{}{}, + }, + unmined: map[btcwire.ShaHash]struct{}{}, + }, + { + name: "insert duplicate unconfirmed", + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil + }, + err: nil, bal: 0, unc: TstRecvTx.MsgTx().TxOut[0].Value, unspents: map[btcwire.OutPoint]struct{}{ @@ -108,10 +136,15 @@ func TestTxStore(t *testing.T) { }, { name: "confirmed txout insert", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: TstRecvTx.MsgTx().TxOut[0].Value, unc: 0, unspents: map[btcwire.OutPoint]struct{}{ @@ -121,10 +154,15 @@ func TestTxStore(t *testing.T) { }, { name: "insert duplicate confirmed", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: TstRecvTx.MsgTx().TxOut[0].Value, unc: 0, unspents: map[btcwire.OutPoint]struct{}{ @@ -134,23 +172,27 @@ func TestTxStore(t *testing.T) { }, { name: "insert duplicate unconfirmed", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, - bal: TstRecvTx.MsgTx().TxOut[0].Value, - unc: 0, - unspents: map[btcwire.OutPoint]struct{}{ - *btcwire.NewOutPoint(TstRecvTx.Sha(), 0): struct{}{}, - }, - unmined: map[btcwire.ShaHash]struct{}{}, + err: ErrInconsistantStore, }, { name: "insert double spend with new txout value", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstDoubleSpendTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstDoubleSpendTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: TstDoubleSpendTx.MsgTx().TxOut[0].Value, unc: 0, unspents: map[btcwire.OutPoint]struct{}{ @@ -160,10 +202,15 @@ func TestTxStore(t *testing.T) { }, { name: "insert unconfirmed signed tx", - f: func(s *Store) *Store { - s.InsertSignedTx(TstSpendingTx, nil) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertSignedTx(TstSpendingTx, nil) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: 0, unc: 0, unspents: map[btcwire.OutPoint]struct{}{}, @@ -173,10 +220,15 @@ func TestTxStore(t *testing.T) { }, { name: "insert unconfirmed signed tx again", - f: func(s *Store) *Store { - s.InsertSignedTx(TstSpendingTx, nil) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertSignedTx(TstSpendingTx, nil) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: 0, unc: 0, unspents: map[btcwire.OutPoint]struct{}{}, @@ -186,10 +238,15 @@ func TestTxStore(t *testing.T) { }, { name: "insert change (index 0)", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstSpendingTx, 0, true, time.Now(), nil) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstSpendingTx, 0, true, time.Now(), nil) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: 0, unc: TstSpendingTx.MsgTx().TxOut[0].Value, unspents: map[btcwire.OutPoint]struct{}{ @@ -201,10 +258,15 @@ func TestTxStore(t *testing.T) { }, { name: "insert output back to this own wallet (index 1)", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstSpendingTx, 1, true, time.Now(), nil) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstSpendingTx, 1, true, time.Now(), nil) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: 0, unc: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value, unspents: map[btcwire.OutPoint]struct{}{ @@ -217,10 +279,15 @@ func TestTxStore(t *testing.T) { }, { name: "confirmed signed tx", - f: func(s *Store) *Store { - s.InsertSignedTx(TstSpendingTx, TstSignedTxBlockDetails) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertSignedTx(TstSpendingTx, TstSignedTxBlockDetails) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value, unc: 0, unspents: map[btcwire.OutPoint]struct{}{ @@ -231,10 +298,11 @@ func TestTxStore(t *testing.T) { }, { name: "rollback after spending tx", - f: func(s *Store) *Store { + f: func(s *Store) (*Store, error) { s.Rollback(TstSignedTxBlockDetails.Height + 1) - return s + return s, nil }, + err: nil, bal: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value, unc: 0, unspents: map[btcwire.OutPoint]struct{}{ @@ -245,10 +313,11 @@ func TestTxStore(t *testing.T) { }, { name: "rollback spending tx block", - f: func(s *Store) *Store { + f: func(s *Store) (*Store, error) { s.Rollback(TstSignedTxBlockDetails.Height) - return s + return s, nil }, + err: nil, bal: 0, unc: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value, unspents: map[btcwire.OutPoint]struct{}{ @@ -261,10 +330,11 @@ func TestTxStore(t *testing.T) { }, { name: "rollback double spend tx block", - f: func(s *Store) *Store { + f: func(s *Store) (*Store, error) { s.Rollback(TstRecvTxBlockDetails.Height) - return s + return s, nil }, + err: nil, bal: 0, unc: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value, unspents: map[btcwire.OutPoint]struct{}{ @@ -277,10 +347,15 @@ func TestTxStore(t *testing.T) { }, { name: "insert original recv txout", - f: func(s *Store) *Store { - s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) - return s + f: func(s *Store) (*Store, error) { + r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails) + if err != nil { + return nil, err + } + _ = r.TxInfo("", 100, btcwire.MainNet) + return s, nil }, + err: nil, bal: TstRecvTx.MsgTx().TxOut[0].Value, unc: 0, unspents: map[btcwire.OutPoint]struct{}{ @@ -292,10 +367,17 @@ func TestTxStore(t *testing.T) { var s *Store for _, test := range tests { - s = test.f(s) + tmpStore, err := test.f(s) + if err != test.err { + t.Fatalf("%s: error mismatch: expected: %v, got: %v", test.name, test.err, err) + } + if test.err != nil { + continue + } + s = tmpStore bal := s.Balance(1, TstRecvCurrentHeight) if bal != test.bal { - t.Errorf("%s: balance mismatch: expected %d, got %d", test.name, test.bal, bal) + t.Errorf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal) } unc := s.Balance(0, TstRecvCurrentHeight) - bal if unc != test.unc {