diff --git a/wtxmgr/db.go b/wtxmgr/db.go index 0c92e17..88dc7a6 100644 --- a/wtxmgr/db.go +++ b/wtxmgr/db.go @@ -341,13 +341,23 @@ func (it *blockIterator) prev() bool { return true } -func (it *blockIterator) delete() error { - err := it.c.Delete() - if err != nil { - str := "failed to delete block record" - storeError(ErrDatabase, str, err) - } - return nil +// unavailable until https://github.com/boltdb/bolt/issues/620 is fixed. +// func (it *blockIterator) delete() error { +// err := it.c.Delete() +// if err != nil { +// str := "failed to delete block record" +// storeError(ErrDatabase, str, err) +// } +// return nil +// } + +func (it *blockIterator) reposition(height int32) { + it.c.Seek(keyBlockRecord(height)) +} + +func deleteBlockRecord(ns walletdb.ReadWriteBucket, height int32) error { + k := keyBlockRecord(height) + return ns.NestedReadWriteBucket(bucketBlocks).Delete(k) } // Transaction records are keyed as such: @@ -1177,13 +1187,18 @@ func (it *unminedCreditIterator) next() bool { return true } -func (it *unminedCreditIterator) delete() error { - err := it.c.Delete() - if err != nil { - str := "failed to delete unmined credit" - return storeError(ErrDatabase, str, err) - } - return nil +// unavailable until https://github.com/boltdb/bolt/issues/620 is fixed. +// func (it *unminedCreditIterator) delete() error { +// err := it.c.Delete() +// if err != nil { +// str := "failed to delete unmined credit" +// return storeError(ErrDatabase, str, err) +// } +// return nil +// } + +func (it *unminedCreditIterator) reposition(txHash *chainhash.Hash, index uint32) { + it.c.Seek(canonicalOutPoint(txHash, index)) } // Outpoints spent by unmined transactions are saved in the unmined inputs diff --git a/wtxmgr/example_test.go b/wtxmgr/example_test.go index 34016af..7f418e5 100644 --- a/wtxmgr/example_test.go +++ b/wtxmgr/example_test.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" ) @@ -44,7 +45,7 @@ var exampleBlock100 = makeBlockMeta(100) // This example demonstrates reporting the Store balance given an unmined and // mined transaction given 0, 1, and 6 block confirmations. func ExampleStore_Balance() { - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { fmt.Println(err) @@ -54,17 +55,24 @@ func ExampleStore_Balance() { // Prints balances for 0 block confirmations, 1 confirmation, and 6 // confirmations. printBalances := func(syncHeight int32) { - zeroConfBal, err := s.Balance(0, syncHeight) + dbtx, err := db.BeginReadTx() if err != nil { fmt.Println(err) return } - oneConfBal, err := s.Balance(1, syncHeight) + defer dbtx.Rollback() + ns := dbtx.ReadBucket(namespaceKey) + zeroConfBal, err := s.Balance(ns, 0, syncHeight) if err != nil { fmt.Println(err) return } - sixConfBal, err := s.Balance(6, syncHeight) + oneConfBal, err := s.Balance(ns, 1, syncHeight) + if err != nil { + fmt.Println(err) + return + } + sixConfBal, err := s.Balance(ns, 6, syncHeight) if err != nil { fmt.Println(err) return @@ -74,12 +82,14 @@ func ExampleStore_Balance() { // Insert a transaction which outputs 10 BTC unmined and mark the output // as a credit. - err = s.InsertTx(exampleTxRecordA, nil) - if err != nil { - fmt.Println(err) - return - } - err = s.AddCredit(exampleTxRecordA, nil, 0, false) + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) + err := s.InsertTx(ns, exampleTxRecordA, nil) + if err != nil { + return err + } + return s.AddCredit(ns, exampleTxRecordA, nil, 0, false) + }) if err != nil { fmt.Println(err) return @@ -88,7 +98,10 @@ func ExampleStore_Balance() { // Mine the transaction in block 100 and print balances again with a // sync height of 100 and 105 blocks. - err = s.InsertTx(exampleTxRecordA, &exampleBlock100) + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) + return s.InsertTx(ns, exampleTxRecordA, &exampleBlock100) + }) if err != nil { fmt.Println(err) return @@ -103,38 +116,43 @@ func ExampleStore_Balance() { } func ExampleStore_Rollback() { - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { fmt.Println(err) return } - // Insert a transaction which outputs 10 BTC in a block at height 100. - err = s.InsertTx(exampleTxRecordA, &exampleBlock100) - if err != nil { - fmt.Println(err) - return - } + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) - // Rollback everything from block 100 onwards. - err = s.Rollback(100) - if err != nil { - fmt.Println(err) - return - } + // Insert a transaction which outputs 10 BTC in a block at height 100. + err := s.InsertTx(ns, exampleTxRecordA, &exampleBlock100) + if err != nil { + return err + } - // Assert that the transaction is now unmined. - details, err := s.TxDetails(&exampleTxRecordA.Hash) + // Rollback everything from block 100 onwards. + err = s.Rollback(ns, 100) + if err != nil { + return err + } + + // Assert that the transaction is now unmined. + details, err := s.TxDetails(ns, &exampleTxRecordA.Hash) + if err != nil { + return err + } + if details == nil { + return fmt.Errorf("no details found") + } + fmt.Println(details.Block.Height) + return nil + }) if err != nil { fmt.Println(err) return } - if details == nil { - fmt.Println("No details found") - return - } - fmt.Println(details.Block.Height) // Output: // -1 @@ -149,20 +167,28 @@ func Example_basicUsage() { return } - // Create or open a db namespace for the transaction store. - ns, err := db.Namespace([]byte("txstore")) + // Open a read-write transaction to operate on the database. + dbtx, err := db.BeginReadWriteTx() + if err != nil { + fmt.Println(err) + return + } + defer dbtx.Commit() + + // Create a bucket for the transaction store. + b, err := dbtx.CreateTopLevelBucket([]byte("txstore")) if err != nil { fmt.Println(err) return } - // Create (or open) the transaction store in the provided namespace. - err = wtxmgr.Create(ns) + // Create and open the transaction store in the provided namespace. + err = wtxmgr.Create(b) if err != nil { fmt.Println(err) return } - s, err := wtxmgr.Open(ns, &chaincfg.TestNet3Params) + s, err := wtxmgr.Open(b, &chaincfg.TestNet3Params) if err != nil { fmt.Println(err) return @@ -170,12 +196,12 @@ func Example_basicUsage() { // Insert an unmined transaction that outputs 10 BTC to a wallet address // at output 0. - err = s.InsertTx(exampleTxRecordA, nil) + err = s.InsertTx(b, exampleTxRecordA, nil) if err != nil { fmt.Println(err) return } - err = s.AddCredit(exampleTxRecordA, nil, 0, false) + err = s.AddCredit(b, exampleTxRecordA, nil, 0, false) if err != nil { fmt.Println(err) return @@ -183,31 +209,31 @@ func Example_basicUsage() { // Insert a second transaction which spends the output, and creates two // outputs. Mark the second one (5 BTC) as wallet change. - err = s.InsertTx(exampleTxRecordB, nil) + err = s.InsertTx(b, exampleTxRecordB, nil) if err != nil { fmt.Println(err) return } - err = s.AddCredit(exampleTxRecordB, nil, 1, true) + err = s.AddCredit(b, exampleTxRecordB, nil, 1, true) if err != nil { fmt.Println(err) return } // Mine each transaction in a block at height 100. - err = s.InsertTx(exampleTxRecordA, &exampleBlock100) + err = s.InsertTx(b, exampleTxRecordA, &exampleBlock100) if err != nil { fmt.Println(err) return } - err = s.InsertTx(exampleTxRecordB, &exampleBlock100) + err = s.InsertTx(b, exampleTxRecordB, &exampleBlock100) if err != nil { fmt.Println(err) return } // Print the one confirmation balance. - bal, err := s.Balance(1, 100) + bal, err := s.Balance(b, 1, 100) if err != nil { fmt.Println(err) return @@ -215,7 +241,7 @@ func Example_basicUsage() { fmt.Println(bal) // Fetch unspent outputs. - utxos, err := s.UnspentOutputs() + utxos, err := s.UnspentOutputs(b) if err != nil { fmt.Println(err) } diff --git a/wtxmgr/query_test.go b/wtxmgr/query_test.go index d67e25f..f5a2f50 100644 --- a/wtxmgr/query_test.go +++ b/wtxmgr/query_test.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/walletdb" . "github.com/btcsuite/btcwallet/wtxmgr" ) @@ -63,7 +64,7 @@ func deepCopyTxDetails(d *TxDetails) *TxDetails { return &cpy } -func (q *queryState) compare(t *testing.T, s *Store, changeDesc string) { +func (q *queryState) compare(t *testing.T, s *Store, ns walletdb.ReadBucket, changeDesc string) { defer func() { if t.Failed() { t.Fatalf("Store state queries failed after '%s'", changeDesc) @@ -95,11 +96,11 @@ func (q *queryState) compare(t *testing.T, s *Store, changeDesc string) { return false, nil } } - err := s.RangeTransactions(0, -1, checkBlock(fwdBlocks)) + err := s.RangeTransactions(ns, 0, -1, checkBlock(fwdBlocks)) if err != nil { t.Fatalf("Failed in RangeTransactions (forwards iteration): %v", err) } - err = s.RangeTransactions(-1, 0, checkBlock(revBlocks)) + err = s.RangeTransactions(ns, -1, 0, checkBlock(revBlocks)) if err != nil { t.Fatalf("Failed in RangeTransactions (reverse iteration): %v", err) } @@ -110,7 +111,7 @@ func (q *queryState) compare(t *testing.T, s *Store, changeDesc string) { if blk.Height == -1 { blk = nil } - d, err := s.UniqueTxDetails(&txHash, blk) + d, err := s.UniqueTxDetails(ns, &txHash, blk) if err != nil { t.Fatal(err) } @@ -128,7 +129,7 @@ func (q *queryState) compare(t *testing.T, s *Store, changeDesc string) { // TxDetails (not looking up a tx at any particular // height) matches the last. detail := &details[len(details)-1] - d, err := s.TxDetails(&txHash) + d, err := s.TxDetails(ns, &txHash) if err != nil { t.Fatal(err) } @@ -237,13 +238,13 @@ func TestStoreQueries(t *testing.T) { type queryTest struct { desc string - updates func() // Unwinds from t.Fatal if the update errors. + updates func(ns walletdb.ReadWriteBucket) // Unwinds from t.Fatal if the update errors. state *queryState } var tests []queryTest // Create the store and test initial state. - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) @@ -251,19 +252,19 @@ func TestStoreQueries(t *testing.T) { lastState := newQueryState() tests = append(tests, queryTest{ desc: "initial store", - updates: func() {}, + updates: func(walletdb.ReadWriteBucket) {}, state: lastState, }) // simplify error handling - insertTx := func(rec *TxRecord, block *BlockMeta) { - err := s.InsertTx(rec, block) + insertTx := func(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) { + err := s.InsertTx(ns, rec, block) if err != nil { t.Fatal(err) } } - addCredit := func(s *Store, rec *TxRecord, block *BlockMeta, index uint32, change bool) { - err := s.AddCredit(rec, block, index, change) + addCredit := func(s *Store, ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) { + err := s.AddCredit(ns, rec, block, index, change) if err != nil { t.Fatal(err) } @@ -275,8 +276,8 @@ func TestStoreQueries(t *testing.T) { } return rec } - rollback := func(height int32) { - err := s.Rollback(height) + rollback := func(ns walletdb.ReadWriteBucket, height int32) { + err := s.Rollback(ns, height) if err != nil { t.Fatal(err) } @@ -300,7 +301,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "insert tx A unmined", - updates: func() { insertTx(recA, nil) }, + updates: func(ns walletdb.ReadWriteBucket) { insertTx(ns, recA, nil) }, state: newState, }) @@ -318,7 +319,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "mark unconfirmed txA:0 as credit", - updates: func() { addCredit(s, recA, nil, 0, true) }, + updates: func(ns walletdb.ReadWriteBucket) { addCredit(s, ns, recA, nil, 0, true) }, state: newState, }) @@ -343,7 +344,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "insert tx B unmined", - updates: func() { insertTx(recB, nil) }, + updates: func(ns walletdb.ReadWriteBucket) { insertTx(ns, recB, nil) }, state: newState, }) newState = lastState.deepCopy() @@ -359,7 +360,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "mark txB:0 as non-change credit", - updates: func() { addCredit(s, recB, nil, 0, false) }, + updates: func(ns walletdb.ReadWriteBucket) { addCredit(s, ns, recB, nil, 0, false) }, state: newState, }) @@ -373,7 +374,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "mine tx A", - updates: func() { insertTx(recA, &b100) }, + updates: func(ns walletdb.ReadWriteBucket) { insertTx(ns, recA, &b100) }, state: newState, }) @@ -385,13 +386,20 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "mine tx B", - updates: func() { insertTx(recB, &b101) }, + updates: func(ns walletdb.ReadWriteBucket) { insertTx(ns, recB, &b101) }, state: newState, }) for _, tst := range tests { - tst.updates() - tst.state.compare(t, s, tst.desc) + err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) + tst.updates(ns) + tst.state.compare(t, s, ns, tst.desc) + return nil + }) + if err != nil { + t.Fatal(err) + } } // Run some additional query tests with the current store's state: @@ -402,62 +410,70 @@ func TestStoreQueries(t *testing.T) { // - Verify that breaking early on RangeTransactions stops further // iteration. - missingTx := spendOutput(&recB.Hash, 0, 40e8) - missingRec := newTxRecordFromMsgTx(missingTx, timeNow()) - missingBlock := makeBlockMeta(102) - missingDetails, err := s.TxDetails(&missingRec.Hash) - if err != nil { - t.Fatal(err) - } - if missingDetails != nil { - t.Errorf("Expected no details, found details for tx %v", missingDetails.Hash) - } - missingUniqueTests := []struct { - hash *chainhash.Hash - block *Block - }{ - {&missingRec.Hash, &b100.Block}, - {&missingRec.Hash, &missingBlock.Block}, - {&missingRec.Hash, nil}, - {&recB.Hash, &b100.Block}, - {&recB.Hash, &missingBlock.Block}, - {&recB.Hash, nil}, - } - for _, tst := range missingUniqueTests { - missingDetails, err = s.UniqueTxDetails(tst.hash, tst.block) + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) + + missingTx := spendOutput(&recB.Hash, 0, 40e8) + missingRec := newTxRecordFromMsgTx(missingTx, timeNow()) + missingBlock := makeBlockMeta(102) + missingDetails, err := s.TxDetails(ns, &missingRec.Hash) if err != nil { t.Fatal(err) } if missingDetails != nil { t.Errorf("Expected no details, found details for tx %v", missingDetails.Hash) } - } + missingUniqueTests := []struct { + hash *chainhash.Hash + block *Block + }{ + {&missingRec.Hash, &b100.Block}, + {&missingRec.Hash, &missingBlock.Block}, + {&missingRec.Hash, nil}, + {&recB.Hash, &b100.Block}, + {&recB.Hash, &missingBlock.Block}, + {&recB.Hash, nil}, + } + for _, tst := range missingUniqueTests { + missingDetails, err = s.UniqueTxDetails(ns, tst.hash, tst.block) + if err != nil { + t.Fatal(err) + } + if missingDetails != nil { + t.Errorf("Expected no details, found details for tx %v", missingDetails.Hash) + } + } - iterations := 0 - err = s.RangeTransactions(0, -1, func([]TxDetails) (bool, error) { - iterations++ - return true, nil + iterations := 0 + err = s.RangeTransactions(ns, 0, -1, func([]TxDetails) (bool, error) { + iterations++ + return true, nil + }) + if iterations != 1 { + t.Errorf("RangeTransactions (forwards) ran func %d times", iterations) + } + iterations = 0 + err = s.RangeTransactions(ns, -1, 0, func([]TxDetails) (bool, error) { + iterations++ + return true, nil + }) + if iterations != 1 { + t.Errorf("RangeTransactions (reverse) ran func %d times", iterations) + } + // Make sure it also breaks early after one iteration through unmined transactions. + rollback(ns, b101.Height) + iterations = 0 + err = s.RangeTransactions(ns, -1, 0, func([]TxDetails) (bool, error) { + iterations++ + return true, nil + }) + if iterations != 1 { + t.Errorf("RangeTransactions (reverse) ran func %d times", iterations) + } + return nil }) - if iterations != 1 { - t.Errorf("RangeTransactions (forwards) ran func %d times", iterations) - } - iterations = 0 - err = s.RangeTransactions(-1, 0, func([]TxDetails) (bool, error) { - iterations++ - return true, nil - }) - if iterations != 1 { - t.Errorf("RangeTransactions (reverse) ran func %d times", iterations) - } - // Make sure it also breaks early after one iteration through unmined transactions. - rollback(b101.Height) - iterations = 0 - err = s.RangeTransactions(-1, 0, func([]TxDetails) (bool, error) { - iterations++ - return true, nil - }) - if iterations != 1 { - t.Errorf("RangeTransactions (reverse) ran func %d times", iterations) + if err != nil { + t.Fatal(err) } // None of the above tests have tested RangeTransactions with multiple @@ -472,7 +488,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests[:0:0], queryTest{ desc: "move tx B to block 100", - updates: func() { insertTx(recB, &b100) }, + updates: func(ns walletdb.ReadWriteBucket) { insertTx(ns, recB, &b100) }, state: newState, }) newState = lastState.deepCopy() @@ -483,7 +499,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "rollback block 100", - updates: func() { rollback(b100.Height) }, + updates: func(ns walletdb.ReadWriteBucket) { rollback(ns, b100.Height) }, state: newState, }) @@ -510,7 +526,7 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "insert duplicate tx A", - updates: func() { insertTx(recA, &b100); insertTx(recA, nil) }, + updates: func(ns walletdb.ReadWriteBucket) { insertTx(ns, recA, &b100); insertTx(ns, recA, nil) }, state: newState, }) newState = lastState.deepCopy() @@ -524,20 +540,27 @@ func TestStoreQueries(t *testing.T) { lastState = newState tests = append(tests, queryTest{ desc: "mine duplicate tx A", - updates: func() { insertTx(recA, &b101) }, + updates: func(ns walletdb.ReadWriteBucket) { insertTx(ns, recA, &b101) }, state: newState, }) for _, tst := range tests { - tst.updates() - tst.state.compare(t, s, tst.desc) + err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) + tst.updates(ns) + tst.state.compare(t, s, ns, tst.desc) + return nil + }) + if err != nil { + t.Fatal(err) + } } } func TestPreviousPkScripts(t *testing.T) { t.Parallel() - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) @@ -593,14 +616,14 @@ func TestPreviousPkScripts(t *testing.T) { recD = newTxRecordFromMsgTx(txD) ) - insertTx := func(rec *TxRecord, block *BlockMeta) { - err := s.InsertTx(rec, block) + insertTx := func(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) { + err := s.InsertTx(ns, rec, block) if err != nil { t.Fatal(err) } } - addCredit := func(rec *TxRecord, block *BlockMeta, index uint32) { - err := s.AddCredit(rec, block, index, false) + addCredit := func(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32) { + err := s.AddCredit(ns, rec, block, index, false) if err != nil { t.Fatal(err) } @@ -611,8 +634,8 @@ func TestPreviousPkScripts(t *testing.T) { block *Block scripts [][]byte } - runTest := func(tst *scriptTest) { - scripts, err := s.PreviousPkScripts(tst.rec, tst.block) + runTest := func(ns walletdb.ReadWriteBucket, tst *scriptTest) { + scripts, err := s.PreviousPkScripts(ns, tst.rec, tst.block) if err != nil { t.Fatal(err) } @@ -634,12 +657,19 @@ func TestPreviousPkScripts(t *testing.T) { } } + dbtx, err := db.BeginReadWriteTx() + if err != nil { + t.Fatal(err) + } + defer dbtx.Commit() + ns := dbtx.ReadWriteBucket(namespaceKey) + // Insert transactions A-C unmined, but mark no credits yet. Until // these are marked as credits, PreviousPkScripts should not return // them. - insertTx(recA, nil) - insertTx(recB, nil) - insertTx(recC, nil) + insertTx(ns, recA, nil) + insertTx(ns, recB, nil) + insertTx(ns, recC, nil) b100 := makeBlockMeta(100) b101 := makeBlockMeta(101) @@ -653,7 +683,7 @@ func TestPreviousPkScripts(t *testing.T) { {recC, &b100.Block, nil}, } for _, tst := range tests { - runTest(&tst) + runTest(ns, &tst) } if t.Failed() { t.Fatal("Failed after unmined tx inserts") @@ -662,11 +692,11 @@ func TestPreviousPkScripts(t *testing.T) { // Mark credits. Tx C output 1 not marked as a credit: tx D will spend // both later but when C is mined, output 1's script should not be // returned. - addCredit(recA, nil, 0) - addCredit(recA, nil, 1) - addCredit(recB, nil, 0) - addCredit(recB, nil, 1) - addCredit(recC, nil, 0) + addCredit(ns, recA, nil, 0) + addCredit(ns, recA, nil, 1) + addCredit(ns, recB, nil, 0) + addCredit(ns, recB, nil, 1) + addCredit(ns, recC, nil, 0) tests = []scriptTest{ {recA, nil, nil}, {recA, &b100.Block, nil}, @@ -676,23 +706,23 @@ func TestPreviousPkScripts(t *testing.T) { {recC, &b100.Block, nil}, } for _, tst := range tests { - runTest(&tst) + runTest(ns, &tst) } if t.Failed() { t.Fatal("Failed after marking unmined credits") } // Mine tx A in block 100. Test results should be identical. - insertTx(recA, &b100) + insertTx(ns, recA, &b100) for _, tst := range tests { - runTest(&tst) + runTest(ns, &tst) } if t.Failed() { t.Fatal("Failed after mining tx A") } // Mine tx B in block 101. - insertTx(recB, &b101) + insertTx(ns, recB, &b101) tests = []scriptTest{ {recA, nil, nil}, {recA, &b100.Block, nil}, @@ -702,7 +732,7 @@ func TestPreviousPkScripts(t *testing.T) { {recC, &b101.Block, nil}, } for _, tst := range tests { - runTest(&tst) + runTest(ns, &tst) } if t.Failed() { t.Fatal("Failed after mining tx B") @@ -710,7 +740,7 @@ func TestPreviousPkScripts(t *testing.T) { // Mine tx C in block 101 (same block as tx B) to test debits from the // same block. - insertTx(recC, &b101) + insertTx(ns, recC, &b101) tests = []scriptTest{ {recA, nil, nil}, {recA, &b100.Block, nil}, @@ -720,7 +750,7 @@ func TestPreviousPkScripts(t *testing.T) { {recC, &b101.Block, [][]byte{scriptB0, scriptB1}}, } for _, tst := range tests { - runTest(&tst) + runTest(ns, &tst) } if t.Failed() { t.Fatal("Failed after mining tx C") @@ -728,11 +758,11 @@ func TestPreviousPkScripts(t *testing.T) { // Insert tx D, which spends C:0 and C:1. However, only C:0 is marked // as a credit, and only that output script should be returned. - insertTx(recD, nil) + insertTx(ns, recD, nil) tests = append(tests, scriptTest{recD, nil, [][]byte{scriptC0}}) tests = append(tests, scriptTest{recD, &b101.Block, nil}) for _, tst := range tests { - runTest(&tst) + runTest(ns, &tst) } if t.Failed() { t.Fatal("Failed after inserting tx D") diff --git a/wtxmgr/tx.go b/wtxmgr/tx.go index 9efe9c7..dd619aa 100644 --- a/wtxmgr/tx.go +++ b/wtxmgr/tx.go @@ -198,18 +198,26 @@ func (s *Store) moveMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord, recKey, return err } - // For all mined transactions with unspent credits spent by this - // transaction, mark each spent, remove from the unspents map, and - // insert a debit record for the spent credit. + // For all transaction inputs, remove the previous output marker from the + // unmined inputs bucket. For any mined transactions with unspent credits + // spent by this transaction, mark each spent, remove from the unspents map, + // and insert a debit record for the spent credit. debitIncidence := indexedIncidence{ incidence: incidence{txHash: rec.Hash, block: block.Block}, // index set for each rec input below. } for i, input := range rec.MsgTx.TxIn { unspentKey, credKey := existsUnspent(ns, &input.PreviousOutPoint) + + err = deleteRawUnminedInput(ns, unspentKey) + if err != nil { + return err + } + if credKey == nil { continue } + debitIncidence.index = uint32(i) amt, err := spendCredit(ns, credKey, &debitIncidence) if err != nil { @@ -225,11 +233,6 @@ func (s *Store) moveMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord, recKey, if err != nil { return err } - - err = deleteRawUnminedInput(ns, unspentKey) - if err != nil { - return err - } } // For each output of the record that is marked as a credit, if the @@ -261,10 +264,6 @@ func (s *Store) moveMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord, recKey, cred.amount = amount cred.change = change - err = it.delete() - if err != nil { - return err - } err = putUnspentCredit(ns, &cred) if err != nil { return err @@ -273,12 +272,33 @@ func (s *Store) moveMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord, recKey, if err != nil { return err } + + // reposition cursor before deleting, since the above puts have + // invalidated the cursor. + it.reposition(&rec.Hash, index) + + // Avoid cursor deletion until bolt issue #620 is resolved. + // err = it.delete() + // if err != nil { + // return err + // } + minedBalance += amount } if it.err != nil { return it.err } + // Delete all possible credits outside of the iteration since the cursor + // deletion is broken. + for i := 0; i < len(rec.MsgTx.TxOut); i++ { + k := canonicalOutPoint(&rec.Hash, uint32(i)) + err = deleteRawUnminedCredit(ns, k) + if err != nil { + return err + } + } + err = putMinedBalance(ns, minedBalance) if err != nil { return err @@ -502,10 +522,16 @@ func (s *Store) rollback(ns walletdb.ReadWriteBucket, height int32) error { // It is necessary to keep these in memory and fix the unmined // transactions later since blocks are removed in increasing order. var coinBaseCredits []wire.OutPoint + var heightsToRemove []int32 - it := makeBlockIterator(ns, height) - for it.next() { + it := makeReverseBlockIterator(ns) + for it.prev() { b := &it.elem + if it.elem.Height < height { + break + } + + heightsToRemove = append(heightsToRemove, it.elem.Height) log.Infof("Rolling back %d transactions from block %v height %d", len(b.transactions), b.Hash, b.Height) @@ -665,15 +691,29 @@ func (s *Store) rollback(ns walletdb.ReadWriteBucket, height int32) error { } } - err = it.delete() - if err != nil { - return err - } + // reposition cursor before deleting this k/v pair and advancing to the + // previous. + it.reposition(it.elem.Height) + + // Avoid cursor deletion until bolt issue #620 is resolved. + // err = it.delete() + // if err != nil { + // return err + // } } if it.err != nil { return it.err } + // Delete the block records outside of the iteration since cursor deletion + // is broken. + for _, h := range heightsToRemove { + err = deleteBlockRecord(ns, h) + if err != nil { + return err + } + } + for _, op := range coinBaseCredits { opKey := canonicalOutPoint(&op.Hash, op.Index) unminedKey := existsRawUnminedInput(ns, opKey) diff --git a/wtxmgr/tx_test.go b/wtxmgr/tx_test.go index 322b618..c2765ba 100644 --- a/wtxmgr/tx_test.go +++ b/wtxmgr/tx_test.go @@ -56,32 +56,38 @@ func testDB() (walletdb.DB, func(), error) { return db, func() { os.RemoveAll(tmpDir) }, err } -func testStore() (*Store, func(), error) { +var namespaceKey = []byte("txstore") + +func testStore() (*Store, walletdb.DB, func(), error) { tmpDir, err := ioutil.TempDir("", "wtxmgr_test") if err != nil { - return nil, func() {}, err + return nil, nil, func() {}, err } db, err := walletdb.Create("bdb", filepath.Join(tmpDir, "db")) if err != nil { teardown := func() { os.RemoveAll(tmpDir) } - return nil, teardown, err + return nil, nil, teardown, err } teardown := func() { db.Close() os.RemoveAll(tmpDir) } - ns, err := db.Namespace([]byte("txstore")) - if err != nil { - return nil, teardown, err - } - err = Create(ns) - if err != nil { - return nil, teardown, err - } - s, err := Open(ns, &chaincfg.TestNet3Params) - return s, teardown, err + var s *Store + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns, err := tx.CreateTopLevelBucket(namespaceKey) + if err != nil { + return err + } + err = Create(ns) + if err != nil { + return err + } + s, err = Open(ns, &chaincfg.TestNet3Params) + return err + }) + return s, db, teardown, err } func serializeTx(tx *btcutil.Tx) []byte { @@ -122,14 +128,14 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { tests := []struct { name string - f func(*Store) (*Store, error) + f func(*Store, walletdb.ReadWriteBucket) (*Store, error) bal, unc btcutil.Amount unspents map[wire.OutPoint]struct{} unmined map[chainhash.Hash]struct{} }{ { name: "new store", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { return s, nil }, bal: 0, @@ -139,17 +145,17 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "txout insert", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, nil) + err = s.InsertTx(ns, rec, nil) if err != nil { return nil, err } - err = s.AddCredit(rec, nil, 0, false) + err = s.AddCredit(ns, rec, nil, 0, false) return s, err }, bal: 0, @@ -166,17 +172,17 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert duplicate unconfirmed", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, nil) + err = s.InsertTx(ns, rec, nil) if err != nil { return nil, err } - err = s.AddCredit(rec, nil, 0, false) + err = s.AddCredit(ns, rec, nil, 0, false) return s, err }, bal: 0, @@ -193,17 +199,17 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "confirmed txout insert", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, TstRecvTxBlockDetails) + err = s.InsertTx(ns, rec, TstRecvTxBlockDetails) if err != nil { return nil, err } - err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) + err = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), @@ -218,17 +224,17 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert duplicate confirmed", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, TstRecvTxBlockDetails) + err = s.InsertTx(ns, rec, TstRecvTxBlockDetails) if err != nil { return nil, err } - err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) + err = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), @@ -243,8 +249,8 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "rollback confirmed credit", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstRecvTxBlockDetails.Height) + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { + err := s.Rollback(ns, TstRecvTxBlockDetails.Height) return s, err }, bal: 0, @@ -261,17 +267,17 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert confirmed double spend", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, TstRecvTxBlockDetails) + err = s.InsertTx(ns, rec, TstRecvTxBlockDetails) if err != nil { return nil, err } - err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) + err = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value), @@ -286,12 +292,12 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert unconfirmed debit", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, nil) + err = s.InsertTx(ns, rec, nil) return s, err }, bal: 0, @@ -303,12 +309,12 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert unconfirmed debit again", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, TstRecvTxBlockDetails) + err = s.InsertTx(ns, rec, TstRecvTxBlockDetails) return s, err }, bal: 0, @@ -320,17 +326,17 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert change (index 0)", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, nil) + err = s.InsertTx(ns, rec, nil) if err != nil { return nil, err } - err = s.AddCredit(rec, nil, 0, true) + err = s.AddCredit(ns, rec, nil, 0, true) return s, err }, bal: 0, @@ -347,16 +353,16 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert output back to this own wallet (index 1)", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, nil) + err = s.InsertTx(ns, rec, nil) if err != nil { return nil, err } - err = s.AddCredit(rec, nil, 1, true) + err = s.AddCredit(ns, rec, nil, 1, true) return s, err }, bal: 0, @@ -377,12 +383,12 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "confirm signed tx", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, TstSignedTxBlockDetails) + err = s.InsertTx(ns, rec, TstSignedTxBlockDetails) return s, err }, bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), @@ -401,8 +407,8 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "rollback after spending tx", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstSignedTxBlockDetails.Height + 1) + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { + err := s.Rollback(ns, TstSignedTxBlockDetails.Height+1) return s, err }, bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), @@ -421,8 +427,8 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "rollback spending tx block", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstSignedTxBlockDetails.Height) + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { + err := s.Rollback(ns, TstSignedTxBlockDetails.Height) return s, err }, bal: 0, @@ -443,8 +449,8 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "rollback double spend tx block", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstRecvTxBlockDetails.Height) + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { + err := s.Rollback(ns, TstRecvTxBlockDetails.Height) return s, err }, bal: 0, @@ -460,16 +466,16 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, { name: "insert original recv txout", - f: func(s *Store) (*Store, error) { + f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } - err = s.InsertTx(rec, TstRecvTxBlockDetails) + err = s.InsertTx(ns, rec, TstRecvTxBlockDetails) if err != nil { return nil, err } - err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) + err = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), @@ -481,88 +487,101 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { }, } - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } for _, test := range tests { - tmpStore, err := test.f(s) - if err != nil { - t.Fatalf("%s: got error: %v", test.name, err) - } - s = tmpStore - bal, err := s.Balance(1, TstRecvCurrentHeight) - if err != nil { - t.Fatalf("%s: Confirmed Balance failed: %v", test.name, err) - } - if bal != test.bal { - t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal) - } - unc, err := s.Balance(0, TstRecvCurrentHeight) - if err != nil { - t.Fatalf("%s: Unconfirmed Balance failed: %v", test.name, err) - } - unc -= bal - if unc != test.unc { - t.Fatalf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc) - } - - // Check that unspent outputs match expected. - unspent, err := s.UnspentOutputs() - if err != nil { - t.Fatalf("%s: failed to fetch unspent outputs: %v", test.name, err) - } - for _, cred := range unspent { - if _, ok := test.unspents[cred.OutPoint]; !ok { - t.Errorf("%s: unexpected unspent output: %v", test.name, cred.OutPoint) + err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) + tmpStore, err := test.f(s, ns) + if err != nil { + t.Fatalf("%s: got error: %v", test.name, err) } - delete(test.unspents, cred.OutPoint) - } - if len(test.unspents) != 0 { - t.Fatalf("%s: missing expected unspent output(s)", test.name) - } - - // Check that unmined txs match expected. - unmined, err := s.UnminedTxs() - if err != nil { - t.Fatalf("%s: cannot load unmined transactions: %v", test.name, err) - } - for _, tx := range unmined { - txHash := tx.TxHash() - if _, ok := test.unmined[txHash]; !ok { - t.Fatalf("%s: unexpected unmined tx: %v", test.name, txHash) + s = tmpStore + bal, err := s.Balance(ns, 1, TstRecvCurrentHeight) + if err != nil { + t.Fatalf("%s: Confirmed Balance failed: %v", test.name, err) + } + if bal != test.bal { + t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal) + } + unc, err := s.Balance(ns, 0, TstRecvCurrentHeight) + if err != nil { + t.Fatalf("%s: Unconfirmed Balance failed: %v", test.name, err) + } + unc -= bal + if unc != test.unc { + t.Fatalf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc) } - delete(test.unmined, txHash) - } - if len(test.unmined) != 0 { - t.Fatalf("%s: missing expected unmined tx(s)", test.name) - } + // Check that unspent outputs match expected. + unspent, err := s.UnspentOutputs(ns) + if err != nil { + t.Fatalf("%s: failed to fetch unspent outputs: %v", test.name, err) + } + for _, cred := range unspent { + if _, ok := test.unspents[cred.OutPoint]; !ok { + t.Errorf("%s: unexpected unspent output: %v", test.name, cred.OutPoint) + } + delete(test.unspents, cred.OutPoint) + } + if len(test.unspents) != 0 { + t.Fatalf("%s: missing expected unspent output(s)", test.name) + } + + // Check that unmined txs match expected. + unmined, err := s.UnminedTxs(ns) + if err != nil { + t.Fatalf("%s: cannot load unmined transactions: %v", test.name, err) + } + for _, tx := range unmined { + txHash := tx.TxHash() + if _, ok := test.unmined[txHash]; !ok { + t.Fatalf("%s: unexpected unmined tx: %v", test.name, txHash) + } + delete(test.unmined, txHash) + } + if len(test.unmined) != 0 { + t.Fatalf("%s: missing expected unmined tx(s)", test.name) + } + return nil + }) + if err != nil { + t.Fatal(err) + } } } func TestFindingSpentCredits(t *testing.T) { t.Parallel() - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } + dbtx, err := db.BeginReadWriteTx() + if err != nil { + t.Fatal(err) + } + defer dbtx.Commit() + ns := dbtx.ReadWriteBucket(namespaceKey) + // Insert transaction and credit which will be spent. recvRec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { t.Fatal(err) } - err = s.InsertTx(recvRec, TstRecvTxBlockDetails) + err = s.InsertTx(ns, recvRec, TstRecvTxBlockDetails) if err != nil { t.Fatal(err) } - err = s.AddCredit(recvRec, TstRecvTxBlockDetails, 0, false) + err = s.AddCredit(ns, recvRec, TstRecvTxBlockDetails, 0, false) if err != nil { t.Fatal(err) } @@ -573,16 +592,16 @@ func TestFindingSpentCredits(t *testing.T) { t.Fatal(err) } - err = s.InsertTx(spendingRec, TstSignedTxBlockDetails) + err = s.InsertTx(ns, spendingRec, TstSignedTxBlockDetails) if err != nil { t.Fatal(err) } - err = s.AddCredit(spendingRec, TstSignedTxBlockDetails, 0, false) + err = s.AddCredit(ns, spendingRec, TstSignedTxBlockDetails, 0, false) if err != nil { t.Fatal(err) } - bal, err := s.Balance(1, TstSignedTxBlockDetails.Height) + bal, err := s.Balance(ns, 1, TstSignedTxBlockDetails.Height) if err != nil { t.Fatal(err) } @@ -590,7 +609,7 @@ func TestFindingSpentCredits(t *testing.T) { if bal != expectedBal { t.Fatalf("bad balance: %v != %v", bal, expectedBal) } - unspents, err := s.UnspentOutputs() + unspents, err := s.UnspentOutputs(ns) if err != nil { t.Fatal(err) } @@ -634,12 +653,19 @@ func spendOutput(txHash *chainhash.Hash, index uint32, outputValues ...int64) *w func TestCoinbases(t *testing.T) { t.Parallel() - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } + dbtx, err := db.BeginReadWriteTx() + if err != nil { + t.Fatal(err) + } + defer dbtx.Commit() + ns := dbtx.ReadWriteBucket(namespaceKey) + b100 := BlockMeta{ Block: Block{Height: 100}, Time: time.Now(), @@ -652,15 +678,15 @@ func TestCoinbases(t *testing.T) { } // Insert coinbase and mark outputs 0 and 2 as credits. - err = s.InsertTx(cbRec, &b100) + err = s.InsertTx(ns, cbRec, &b100) if err != nil { t.Fatal(err) } - err = s.AddCredit(cbRec, &b100, 0, false) + err = s.AddCredit(ns, cbRec, &b100, 0, false) if err != nil { t.Fatal(err) } - err = s.AddCredit(cbRec, &b100, 2, false) + err = s.AddCredit(ns, cbRec, &b100, 2, false) if err != nil { t.Fatal(err) } @@ -746,7 +772,7 @@ func TestCoinbases(t *testing.T) { }, } for i, tst := range balTests { - bal, err := s.Balance(tst.minConf, tst.height) + bal, err := s.Balance(ns, tst.minConf, tst.height) if err != nil { t.Fatalf("Balance test %d: Store.Balance failed: %v", i, err) } @@ -766,11 +792,11 @@ func TestCoinbases(t *testing.T) { if err != nil { t.Fatal(err) } - err = s.InsertTx(spenderARec, nil) + err = s.InsertTx(ns, spenderARec, nil) if err != nil { t.Fatal(err) } - err = s.AddCredit(spenderARec, nil, 0, false) + err = s.AddCredit(ns, spenderARec, nil, 0, false) if err != nil { t.Fatal(err) } @@ -827,7 +853,7 @@ func TestCoinbases(t *testing.T) { } balTestsBeforeMaturity := balTests for i, tst := range balTests { - bal, err := s.Balance(tst.minConf, tst.height) + bal, err := s.Balance(ns, tst.minConf, tst.height) if err != nil { t.Fatalf("Balance test %d: Store.Balance failed: %v", i, err) } @@ -844,7 +870,7 @@ func TestCoinbases(t *testing.T) { Block: Block{Height: b100.Height + coinbaseMaturity}, Time: time.Now(), } - err = s.InsertTx(spenderARec, &bMaturity) + err = s.InsertTx(ns, spenderARec, &bMaturity) if err != nil { t.Fatal(err) } @@ -910,7 +936,7 @@ func TestCoinbases(t *testing.T) { }, } for i, tst := range balTests { - bal, err := s.Balance(tst.minConf, tst.height) + bal, err := s.Balance(ns, tst.minConf, tst.height) if err != nil { t.Fatalf("Balance test %d: Store.Balance failed: %v", i, err) } @@ -934,16 +960,16 @@ func TestCoinbases(t *testing.T) { if err != nil { t.Fatal(err) } - err = s.InsertTx(spenderBRec, &bMaturity) + err = s.InsertTx(ns, spenderBRec, &bMaturity) if err != nil { t.Fatal(err) } - err = s.AddCredit(spenderBRec, &bMaturity, 0, false) + err = s.AddCredit(ns, spenderBRec, &bMaturity, 0, false) if err != nil { t.Fatal(err) } for i, tst := range balTests { - bal, err := s.Balance(tst.minConf, tst.height) + bal, err := s.Balance(ns, tst.minConf, tst.height) if err != nil { t.Fatalf("Balance test %d: Store.Balance failed: %v", i, err) } @@ -957,13 +983,13 @@ func TestCoinbases(t *testing.T) { // Reorg out the block that matured the coinbase and check balances // again. - err = s.Rollback(bMaturity.Height) + err = s.Rollback(ns, bMaturity.Height) if err != nil { t.Fatal(err) } balTests = balTestsBeforeMaturity for i, tst := range balTests { - bal, err := s.Balance(tst.minConf, tst.height) + bal, err := s.Balance(ns, tst.minConf, tst.height) if err != nil { t.Fatalf("Balance test %d: Store.Balance failed: %v", i, err) } @@ -979,7 +1005,7 @@ func TestCoinbases(t *testing.T) { // more transactions in the store (since the previous outputs referenced // by the spending tx no longer exist), and the balance will always be // zero. - err = s.Rollback(b100.Height) + err = s.Rollback(ns, b100.Height) if err != nil { t.Fatal(err) } @@ -1009,7 +1035,7 @@ func TestCoinbases(t *testing.T) { }, } for i, tst := range balTests { - bal, err := s.Balance(tst.minConf, tst.height) + bal, err := s.Balance(ns, tst.minConf, tst.height) if err != nil { t.Fatalf("Balance test %d: Store.Balance failed: %v", i, err) } @@ -1020,7 +1046,7 @@ func TestCoinbases(t *testing.T) { if t.Failed() { t.Fatal("Failed balance checks after reorging coinbase block") } - unminedTxs, err := s.UnminedTxs() + unminedTxs, err := s.UnminedTxs(ns) if err != nil { t.Fatal(err) } @@ -1033,12 +1059,19 @@ func TestCoinbases(t *testing.T) { func TestMoveMultipleToSameBlock(t *testing.T) { t.Parallel() - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } + dbtx, err := db.BeginReadWriteTx() + if err != nil { + t.Fatal(err) + } + defer dbtx.Commit() + ns := dbtx.ReadWriteBucket(namespaceKey) + b100 := BlockMeta{ Block: Block{Height: 100}, Time: time.Now(), @@ -1051,15 +1084,15 @@ func TestMoveMultipleToSameBlock(t *testing.T) { } // Insert coinbase and mark both outputs as credits. - err = s.InsertTx(cbRec, &b100) + err = s.InsertTx(ns, cbRec, &b100) if err != nil { t.Fatal(err) } - err = s.AddCredit(cbRec, &b100, 0, false) + err = s.AddCredit(ns, cbRec, &b100, 0, false) if err != nil { t.Fatal(err) } - err = s.AddCredit(cbRec, &b100, 1, false) + err = s.AddCredit(ns, cbRec, &b100, 1, false) if err != nil { t.Fatal(err) } @@ -1072,15 +1105,15 @@ func TestMoveMultipleToSameBlock(t *testing.T) { if err != nil { t.Fatal(err) } - err = s.InsertTx(spenderARec, nil) + err = s.InsertTx(ns, spenderARec, nil) if err != nil { t.Fatal(err) } - err = s.AddCredit(spenderARec, nil, 0, false) + err = s.AddCredit(ns, spenderARec, nil, 0, false) if err != nil { t.Fatal(err) } - err = s.AddCredit(spenderARec, nil, 1, false) + err = s.AddCredit(ns, spenderARec, nil, 1, false) if err != nil { t.Fatal(err) } @@ -1090,15 +1123,15 @@ func TestMoveMultipleToSameBlock(t *testing.T) { if err != nil { t.Fatal(err) } - err = s.InsertTx(spenderBRec, nil) + err = s.InsertTx(ns, spenderBRec, nil) if err != nil { t.Fatal(err) } - err = s.AddCredit(spenderBRec, nil, 0, false) + err = s.AddCredit(ns, spenderBRec, nil, 0, false) if err != nil { t.Fatal(err) } - err = s.AddCredit(spenderBRec, nil, 1, false) + err = s.AddCredit(ns, spenderBRec, nil, 1, false) if err != nil { t.Fatal(err) } @@ -1110,24 +1143,24 @@ func TestMoveMultipleToSameBlock(t *testing.T) { Block: Block{Height: b100.Height + coinbaseMaturity}, Time: time.Now(), } - err = s.InsertTx(spenderARec, &bMaturity) + err = s.InsertTx(ns, spenderARec, &bMaturity) if err != nil { t.Fatal(err) } - err = s.InsertTx(spenderBRec, &bMaturity) + err = s.InsertTx(ns, spenderBRec, &bMaturity) if err != nil { t.Fatal(err) } // Check that both transactions can be queried at the maturity block. - detailsA, err := s.UniqueTxDetails(&spenderARec.Hash, &bMaturity.Block) + detailsA, err := s.UniqueTxDetails(ns, &spenderARec.Hash, &bMaturity.Block) if err != nil { t.Fatal(err) } if detailsA == nil { t.Fatal("No details found for first spender") } - detailsB, err := s.UniqueTxDetails(&spenderBRec.Hash, &bMaturity.Block) + detailsB, err := s.UniqueTxDetails(ns, &spenderBRec.Hash, &bMaturity.Block) if err != nil { t.Fatal(err) } @@ -1177,7 +1210,7 @@ func TestMoveMultipleToSameBlock(t *testing.T) { }, } for i, tst := range balTests { - bal, err := s.Balance(tst.minConf, tst.height) + bal, err := s.Balance(ns, tst.minConf, tst.height) if err != nil { t.Fatalf("Balance test %d: Store.Balance failed: %v", i, err) } @@ -1188,7 +1221,7 @@ func TestMoveMultipleToSameBlock(t *testing.T) { if t.Failed() { t.Fatal("Failed balance checks after moving both coinbase spenders") } - unminedTxs, err := s.UnminedTxs() + unminedTxs, err := s.UnminedTxs(ns) if err != nil { t.Fatal(err) } @@ -1203,25 +1236,32 @@ func TestMoveMultipleToSameBlock(t *testing.T) { func TestInsertUnserializedTx(t *testing.T) { t.Parallel() - s, teardown, err := testStore() + s, db, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } + dbtx, err := db.BeginReadWriteTx() + if err != nil { + t.Fatal(err) + } + defer dbtx.Commit() + ns := dbtx.ReadWriteBucket(namespaceKey) + tx := newCoinBase(50e8) rec, err := NewTxRecordFromMsgTx(tx, timeNow()) if err != nil { t.Fatal(err) } b100 := makeBlockMeta(100) - err = s.InsertTx(stripSerializedTx(rec), &b100) + err = s.InsertTx(ns, stripSerializedTx(rec), &b100) if err != nil { t.Fatalf("Insert for stripped TxRecord failed: %v", err) } // Ensure it can be retreived successfully. - details, err := s.UniqueTxDetails(&rec.Hash, &b100.Block) + details, err := s.UniqueTxDetails(ns, &rec.Hash, &b100.Block) if err != nil { t.Fatal(err) } @@ -1239,11 +1279,11 @@ func TestInsertUnserializedTx(t *testing.T) { if err != nil { t.Fatal(err) } - err = s.InsertTx(rec, nil) + err = s.InsertTx(ns, rec, nil) if err != nil { t.Fatal(err) } - details, err = s.UniqueTxDetails(&rec.Hash, nil) + details, err = s.UniqueTxDetails(ns, &rec.Hash, nil) if err != nil { t.Fatal(err) }