diff --git a/sweep/store.go b/sweep/store.go index 287646a7..6d74dfe5 100644 --- a/sweep/store.go +++ b/sweep/store.go @@ -39,6 +39,8 @@ var ( utxnFinalizedKndrTxnKey = []byte("finalized-kndr-txn") byteOrder = binary.BigEndian + + errNoTxHashesBucket = errors.New("tx hashes bucket does not exist") ) // SweeperStore stores published txes. @@ -53,6 +55,9 @@ type SweeperStore interface { // GetLastPublishedTx returns the last tx that we called NotifyPublishTx // for. GetLastPublishedTx() (*wire.MsgTx, error) + + // ListSweeps lists all the sweeps we have successfully published. + ListSweeps() ([]chainhash.Hash, error) } type sweeperStore struct { @@ -173,7 +178,7 @@ func (s *sweeperStore) NotifyPublishTx(sweepTx *wire.MsgTx) error { txHashesBucket := tx.ReadWriteBucket(txHashesBucketKey) if txHashesBucket == nil { - return errors.New("tx hashes bucket does not exist") + return errNoTxHashesBucket } var b bytes.Buffer @@ -230,7 +235,7 @@ func (s *sweeperStore) IsOurTx(hash chainhash.Hash) (bool, error) { err := kvdb.View(s.db, func(tx kvdb.ReadTx) error { txHashesBucket := tx.ReadBucket(txHashesBucketKey) if txHashesBucket == nil { - return errors.New("tx hashes bucket does not exist") + return errNoTxHashesBucket } ours = txHashesBucket.Get(hash[:]) != nil @@ -244,5 +249,32 @@ func (s *sweeperStore) IsOurTx(hash chainhash.Hash) (bool, error) { return ours, nil } +// ListSweeps lists all the sweep transactions we have in the sweeper store. +func (s *sweeperStore) ListSweeps() ([]chainhash.Hash, error) { + var sweepTxns []chainhash.Hash + + if err := kvdb.View(s.db, func(tx kvdb.ReadTx) error { + txHashesBucket := tx.ReadBucket(txHashesBucketKey) + if txHashesBucket == nil { + return errNoTxHashesBucket + } + + return txHashesBucket.ForEach(func(resKey, _ []byte) error { + txid, err := chainhash.NewHash(resKey) + if err != nil { + return err + } + + sweepTxns = append(sweepTxns, *txid) + + return nil + }) + }); err != nil { + return nil, err + } + + return sweepTxns, nil +} + // Compile-time constraint to ensure sweeperStore implements SweeperStore. var _ SweeperStore = (*sweeperStore)(nil) diff --git a/sweep/store_mock.go b/sweep/store_mock.go index ba8b2f2b..ba6d5d67 100644 --- a/sweep/store_mock.go +++ b/sweep/store_mock.go @@ -41,5 +41,15 @@ func (s *MockSweeperStore) GetLastPublishedTx() (*wire.MsgTx, error) { return s.lastTx, nil } +// ListSweeps lists all the sweeps we have successfully published. +func (s *MockSweeperStore) ListSweeps() ([]chainhash.Hash, error) { + var txns []chainhash.Hash + for tx := range s.ourTxes { + txns = append(txns, tx) + } + + return txns, nil +} + // Compile-time constraint to ensure MockSweeperStore implements SweeperStore. var _ SweeperStore = (*MockSweeperStore)(nil) diff --git a/sweep/store_test.go b/sweep/store_test.go index 8d83e1fa..3738f6c9 100644 --- a/sweep/store_test.go +++ b/sweep/store_test.go @@ -150,4 +150,28 @@ func testStore(t *testing.T, createStore func() (SweeperStore, error)) { if ours { t.Fatal("expected tx to be not ours") } + + txns, err := store.ListSweeps() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Create a map containing the sweeps we expect to be returned by list + // sweeps. + expected := map[chainhash.Hash]bool{ + tx1.TxHash(): true, + tx2.TxHash(): true, + } + + if len(txns) != len(expected) { + t.Fatalf("expected: %v sweeps, got: %v", len(expected), + len(txns)) + } + + for _, tx := range txns { + _, ok := expected[tx] + if !ok { + t.Fatalf("unexpected tx: %v", tx) + } + } } diff --git a/sweep/sweeper.go b/sweep/sweeper.go index cd93a121..9ff9b92b 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -9,6 +9,7 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" @@ -1262,6 +1263,11 @@ func DefaultNextAttemptDeltaFunc(attempts int) int32 { return 1 + rand.Int31n(1<