diff --git a/internal/blockchain/chain.go b/internal/blockchain/chain.go index d6764d5f..8b405741 100644 --- a/internal/blockchain/chain.go +++ b/internal/blockchain/chain.go @@ -257,10 +257,6 @@ type BlockChain struct { calcVoterVersionIntervalCache map[[chainhash.HashSize]byte]uint32 calcStakeVersionCache map[[chainhash.HashSize]byte]uint32 - // spendPruner prunes spend journal data for disconnected blocks - // if there are no consumers left for it. - spendPruner *spendpruner.SpendJournalPruner - // bulkImportMode provides a mechanism to indicate that several validation // checks can be avoided when bulk importing blocks already known to be valid. // It is protected by the chain lock. @@ -813,9 +809,6 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block, log.Debugf("New target %08x (%064x)", node.bits, newDiff) } - // Notify the spend pruner of the connected block. - go b.spendPruner.NotifyConnectedBlock(block.Hash()) - // Notify the caller that the block was connected to the main chain. // The caller would typically want to react with actions such as // updating wallets. @@ -971,13 +964,6 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block, parent *dcrutil.Blo return err } - // Prune the associated spend data for the provided block hash if there - // are no spend dependencies for it. - err = b.spendPruner.MaybePruneSpendData(block.Hash(), nil) - if err != nil { - return err - } - // This node's parent is now the end of the best chain. b.bestChain.SetTip(node.parent) @@ -2231,45 +2217,11 @@ func (q *ChainQueryerAdapter) Best() (int64, *chainhash.Hash) { return snapshot.Height, &snapshot.Hash } -// Ancestor returns the ancestor of the provided block at the provided height. -// -// This function is safe for concurrent access and is part of the -// indexers.ChainQueryer interface. -func (q *ChainQueryerAdapter) Ancestor(block *chainhash.Hash, height int64) *chainhash.Hash { - node := q.index.LookupNode(block) - ancestor := node.Ancestor(height) - return &ancestor.hash -} - -// AddSpendConsumer adds the provided spend consumer to the spend pruner. -func (q *ChainQueryerAdapter) AddSpendConsumer(consumer spendpruner.SpendConsumer) { - q.spendPruner.AddConsumer(consumer) -} - -// RemoveSpendConsumerDependency removes the provided spend consumer dependency -// associated with the provided block hash from the spend pruner. -func (q *ChainQueryerAdapter) RemoveSpendConsumerDependency(dbTx database.Tx, blockHash *chainhash.Hash, consumerID string) error { - return q.spendPruner.RemoveSpendConsumerDependency(dbTx, blockHash, consumerID) -} - -// FetchSpendConsumer returns the spend journal consumer associated with the -// provided id. -func (q *ChainQueryerAdapter) FetchSpendConsumer(id string) (spendpruner.SpendConsumer, error) { - return q.spendPruner.FetchConsumer(id) -} - // BlockHeaderByHash returns the block header identified by the given hash. func (q *ChainQueryerAdapter) BlockHeaderByHash(hash *chainhash.Hash) (wire.BlockHeader, error) { return q.HeaderByHash(hash) } -// SpendPrunerHandler processes incoming spending pruner signals. -// -// This must be run as a goroutine. -func (b *BlockChain) SpendPrunerHandler(ctx context.Context) { - b.spendPruner.HandleSignals(ctx) -} - // Config is a descriptor which specifies the blockchain instance configuration. type Config struct { // DB defines the database which houses the blocks and will be used to @@ -2441,8 +2393,8 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) { return nil, err } - b.spendPruner, err = spendpruner.NewSpendJournalPruner(b.db, b.RemoveSpendEntry) - if err != nil { + // Drop the spendpruner consumer dependencies bucket if it exists. + if err := spendpruner.DropConsumerDepsBucket(b.db); err != nil { return nil, err } diff --git a/internal/blockchain/indexers/common.go b/internal/blockchain/indexers/common.go index d62576c7..5ad1c771 100644 --- a/internal/blockchain/indexers/common.go +++ b/internal/blockchain/indexers/common.go @@ -19,7 +19,6 @@ import ( "github.com/decred/dcrd/database/v3" "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/internal/blockchain/progresslog" - "github.com/decred/dcrd/internal/blockchain/spendpruner" "github.com/decred/dcrd/wire" ) @@ -80,21 +79,6 @@ type ChainQueryer interface { // BlockByHash returns the block of the provided hash. BlockByHash(*chainhash.Hash) (*dcrutil.Block, error) - // Ancestor returns the ancestor of the provided block at the - // provided height. - Ancestor(block *chainhash.Hash, height int64) *chainhash.Hash - - // AddSpendConsumer adds the provided spend consumer. - AddSpendConsumer(consumer spendpruner.SpendConsumer) - - // RemoveSpendConsumerDependency removes the provided spend consumer - // dependency associated with the provided block hash. - RemoveSpendConsumerDependency(dbTx database.Tx, blockHash *chainhash.Hash, consumerID string) error - - // FetchSpendConsumer returns the spend journal consumer associated with - // the provided id. - FetchSpendConsumer(id string) (spendpruner.SpendConsumer, error) - // PrevScripts returns a source of previous transaction scripts and their // associated versions spent by the given block. PrevScripts(database.Tx, *dcrutil.Block) (PrevScripter, error) diff --git a/internal/blockchain/indexers/spendconsumer.go b/internal/blockchain/indexers/spendconsumer.go deleted file mode 100644 index f04a23e0..00000000 --- a/internal/blockchain/indexers/spendconsumer.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2021 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package indexers - -import ( - "sync" - - "github.com/decred/dcrd/chaincfg/chainhash" -) - -// SpendConsumer represents an indexer dependent on -// spend journal entries. -type SpendConsumer struct { - id string - queryer ChainQueryer - tipHash *chainhash.Hash - mtx sync.Mutex -} - -// NewSpendConsumer initializes a spend consumer. -func NewSpendConsumer(id string, tipHash *chainhash.Hash, queryer ChainQueryer) *SpendConsumer { - return &SpendConsumer{ - id: id, - queryer: queryer, - tipHash: tipHash, - } -} - -// ID returns the identifier of the consumer. -func (s *SpendConsumer) ID() string { - return s.id -} - -// UpdateTip sets the tip of the consumer to the provided hash. -func (s *SpendConsumer) UpdateTip(hash *chainhash.Hash) { - s.mtx.Lock() - s.tipHash = hash - s.mtx.Unlock() -} - -// NeedSpendData checks whether the associated spend journal entry -// for the provided block hash will be needed by the indexer. -func (s *SpendConsumer) NeedSpendData(hash *chainhash.Hash) (bool, error) { - // The indexer does not need spend journal data if it has not - // been initialized. - s.mtx.Lock() - tipHash := s.tipHash - s.mtx.Unlock() - if tipHash == nil { - return false, nil - } - - tipHeader, err := s.queryer.BlockHeaderByHash(tipHash) - if err != nil { - return false, err - } - - header, err := s.queryer.BlockHeaderByHash(hash) - if err != nil { - return false, err - } - - // The spend consumer does not need the spend journal - // data associated with the provided block hash if - // its current tip is below the provided hash. - if header.Height > tipHeader.Height { - return false, nil - } - - // The spend consumer does not need the spend data associated - // with the provided block hash if the block is not an ancestor - // of the current tip. - blockHash := s.queryer.Ancestor(tipHash, int64(header.Height)) - if !blockHash.IsEqual(hash) { - return false, nil - } - - return true, nil -} diff --git a/internal/blockchain/indexers/spendconsumer_test.go b/internal/blockchain/indexers/spendconsumer_test.go deleted file mode 100644 index d00eddf1..00000000 --- a/internal/blockchain/indexers/spendconsumer_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2021-2022 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. -package indexers - -import ( - "testing" - - "github.com/decred/dcrd/blockchain/v5/chaingen" - "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/chaincfg/v3" - "github.com/decred/dcrd/database/v3" - "github.com/decred/dcrd/wire" -) - -// TestSpendConsumer ensures the spend consumer behaves as expected. -func TestSpendConsumer(t *testing.T) { - dbPath := t.TempDir() - - db, err := database.Create("ffldb", dbPath, wire.SimNet) - if err != nil { - t.Fatalf("error creating db: %v", err) - } - defer db.Close() - - chain, err := newTestChain() - if err != nil { - t.Fatal(err) - } - - g, err := chaingen.MakeGenerator(chaincfg.SimNetParams()) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - // Add four blocks to the chain. - bk1 := addBlock(t, chain, &g, "bk1") - bk2 := addBlock(t, chain, &g, "bk2") - bk3 := addBlock(t, chain, &g, "bk3") - bk4 := addBlock(t, chain, &g, "bk4") - - id := "testConsumer" - consumer := NewSpendConsumer(id, bk3.Hash(), chain) - - // Ensure the consumer is correctly identified. - if id != consumer.ID() { - t.Fatalf("expected consumer id to be %v, got %v", id, consumer.ID()) - } - - // Ensure bk2 is needed by the consumer since it is an ancestor - // of the current tip, bk3. - needed, err := consumer.NeedSpendData(bk2.Hash()) - if err != nil { - t.Fatalf("unexpected NeedSpendData error: %v", err) - } - - if !needed { - t.Fatalf("expected the spend data for block bk2 to be needed") - } - - // Ensure bk4 is not needed by the address index since it is an ancestor - // of the current tip, bk4. - needed, err = consumer.NeedSpendData(bk4.Hash()) - if err != nil { - t.Fatalf("unexpected NeedSpendData error: %v", err) - } - - if needed { - t.Fatalf("expected the spend data for block bk4 to not be needed") - } - - // Ensure finding the ancestor of an unknown block hash fails. - _, err = consumer.NeedSpendData(&chainhash.Hash{0}) - if err == nil { - t.Fatal("expected an unknown block error") - } - - // Update the consumer tip to bk1. - consumer.UpdateTip(bk1.Hash()) - - // Ensure bk2 is now no longer needed by the consumer since it is - // not an ancestor of the current tip, bk1. - needed, err = consumer.NeedSpendData(bk2.Hash()) - if err != nil { - t.Fatalf("unexpected NeedSpendData error: %v", err) - } - - if needed { - t.Fatalf("expected the spend data for block bk2 to not be needed") - } -} diff --git a/internal/blockchain/indexers/txindex_test.go b/internal/blockchain/indexers/txindex_test.go index 64e4d753..c9437a16 100644 --- a/internal/blockchain/indexers/txindex_test.go +++ b/internal/blockchain/indexers/txindex_test.go @@ -17,7 +17,6 @@ import ( "github.com/decred/dcrd/database/v3" _ "github.com/decred/dcrd/database/v3/ffldb" "github.com/decred/dcrd/dcrutil/v4" - "github.com/decred/dcrd/internal/blockchain/spendpruner" "github.com/decred/dcrd/wire" ) @@ -30,7 +29,6 @@ type testChain struct { keyedByHeight map[int64]*dcrutil.Block keyedByHash map[chainhash.Hash]*dcrutil.Block orphans map[chainhash.Hash]*dcrutil.Block - consumers map[string]spendpruner.SpendConsumer removedSpendDeps map[chainhash.Hash][]string mtx sync.Mutex } @@ -41,7 +39,6 @@ func newTestChain() (*testChain, error) { keyedByHeight: make(map[int64]*dcrutil.Block), keyedByHash: make(map[chainhash.Hash]*dcrutil.Block), orphans: make(map[chainhash.Hash]*dcrutil.Block), - consumers: make(map[string]spendpruner.SpendConsumer), removedSpendDeps: make(map[chainhash.Hash][]string), } genesis := dcrutil.NewBlock(chaincfg.SimNetParams().GenesisBlock) @@ -167,13 +164,6 @@ func (tc *testChain) Ancestor(block *chainhash.Hash, height int64) *chainhash.Ha } } -// AddSpendConsumer adds the provided spend consumer. -func (tc *testChain) AddSpendConsumer(consumer spendpruner.SpendConsumer) { - tc.mtx.Lock() - tc.consumers[consumer.ID()] = consumer - tc.mtx.Unlock() -} - // RemoveSpendConsumerDependency removes the provided spend consumer dependency // associated with the provided block hash. func (tc *testChain) RemoveSpendConsumerDependency(_ database.Tx, blockHash *chainhash.Hash, consumerID string) error { @@ -211,19 +201,6 @@ func (tc *testChain) IsRemovedSpendConsumerDependency(blockHash *chainhash.Hash, return false } -// FetchSpendConsumer returns the spend journal consumer associated with -// the provided id. -func (tc *testChain) FetchSpendConsumer(id string) (spendpruner.SpendConsumer, error) { - tc.mtx.Lock() - defer tc.mtx.Unlock() - consumer, ok := tc.consumers[id] - if !ok { - return nil, fmt.Errorf("no spend consumer found with id %s", id) - } - - return consumer, nil -} - // ChainParams returns the parameters of the chain. func (tc *testChain) ChainParams() *chaincfg.Params { return chaincfg.SimNetParams() diff --git a/internal/blockchain/spendpruner/db.go b/internal/blockchain/spendpruner/db.go index 6b358660..a0cfd1c0 100644 --- a/internal/blockchain/spendpruner/db.go +++ b/internal/blockchain/spendpruner/db.go @@ -4,93 +4,31 @@ package spendpruner +// NB: This package is scehduled for removal once DropConsumerDepsBucket no +// longer needs to be called to remove unneeded persisted data. + import ( - "bytes" + "errors" - "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database/v3" ) -const ( - // depsSeparator is the character used in separating spend journal consumer - // dependencies when serializing. - depsSeparator = ',' -) - var ( // spendConsumerDepsBucketName is the name of the bucket used in storing // spend journal consumer dependencies. spendConsumerDepsBucketName = []byte("spendconsumerdeps") ) -// initConsumerDepsBucket creates the spend consumer dependencies bucket if it -// does not exist. -func initConsumerDepsBucket(db database.DB) error { - // Create the spend consumer dependencies bucket if it does not exist yet. +// DropConsumerDepsBucket removes the spend pruner consumer dependencies bucket +// if it exists. +func DropConsumerDepsBucket(db database.DB) error { return db.Update(func(dbTx database.Tx) error { meta := dbTx.Metadata() - _, err := meta.CreateBucketIfNotExists(spendConsumerDepsBucketName) - return err + err := meta.DeleteBucket(spendConsumerDepsBucketName) + if !errors.Is(err, database.ErrBucketNotFound) { + return err + } + + return nil }) } - -// serializeSpendConsumerDeps returns serialized bytes of the provided spend -// journal consumer dependencies. -func serializeSpendConsumerDeps(deps []string) []byte { - var buf bytes.Buffer - for idx := 0; idx < len(deps); idx++ { - buf.WriteString(deps[idx]) - if idx < len(deps)-1 { - buf.WriteByte(depsSeparator) - } - } - - return buf.Bytes() -} - -// deserializeSpendConsumerDeps returns deserialized spend consumer -// dependencies from the provided serialized bytes. -func deserializeSpendConsumerDeps(serializedBytes []byte) []string { - depBytes := bytes.Split(serializedBytes, []byte{depsSeparator}) - deps := make([]string, len(depBytes)) - for idx := 0; idx < len(depBytes); idx++ { - deps[idx] = string(depBytes[idx]) - } - - return deps -} - -// dbUpdateSpendConsumerDeps uses an existing database transaction to update -// the spend consumer dependency entry for the provided block hash. -func dbUpdateSpendConsumerDeps(dbTx database.Tx, blockHash chainhash.Hash, consumerDeps []string) error { - depsBucket := dbTx.Metadata().Bucket(spendConsumerDepsBucketName) - - // Remove the dependency entry if there are no spend consumer dependencies - // left for the block hash. - if len(consumerDeps) == 0 { - return depsBucket.Delete(blockHash[:]) - } - - // Update the dependency entry. - serialized := serializeSpendConsumerDeps(consumerDeps) - return depsBucket.Put(blockHash[:], serialized) -} - -// dbFetchSpendConsumerDeps uses an existing database transaction to fetch all -// spend consumer dependency entries in the database. -func dbFetchSpendConsumerDeps(dbTx database.Tx) (map[chainhash.Hash][]string, error) { - depsBucket := dbTx.Metadata().Bucket(spendConsumerDepsBucketName) - consumerDeps := make(map[chainhash.Hash][]string) - cursor := depsBucket.Cursor() - for ok := cursor.First(); ok; ok = cursor.Next() { - hash, err := chainhash.NewHash(cursor.Key()) - if err != nil { - return nil, err - } - - deps := deserializeSpendConsumerDeps(cursor.Value()) - consumerDeps[*hash] = deps - } - - return consumerDeps, nil -} diff --git a/internal/blockchain/spendpruner/db_test.go b/internal/blockchain/spendpruner/db_test.go deleted file mode 100644 index e99bad64..00000000 --- a/internal/blockchain/spendpruner/db_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) 2021 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package spendpruner - -import ( - "bytes" - "os" - "path/filepath" - "testing" - - "github.com/decred/dcrd/database/v3" - _ "github.com/decred/dcrd/database/v3/ffldb" - "github.com/decred/dcrd/wire" -) - -// TestSerializeConsumerDeps ensures consumer dependencies serialize as -// intended. -func TestSerializeConsumerDeps(t *testing.T) { - tests := []struct { - name string - deps []string - expected []byte - }{{ - name: "no dependencies", - deps: []string{}, - expected: []byte{}, - }, { - name: "one dependency", - deps: []string{"alpha"}, - expected: []byte("alpha"), - }, { - name: "odd number of dependencies", - deps: []string{"alpha", "bravo", "charlie"}, - expected: []byte("alpha,bravo,charlie"), - }, { - name: "even number of dependencies", - deps: []string{"alpha", "bravo", "charlie", "echo"}, - expected: []byte("alpha,bravo,charlie,echo"), - }} - - for _, test := range tests { - serialized := serializeSpendConsumerDeps(test.deps) - if !bytes.Equal(serialized, test.expected) { - t.Errorf("%q: unexpected serialized mismatch, "+ - "expected %q, got %q", test.name, test.expected, serialized) - continue - } - } -} - -// TestDeserializeConsumerDeps ensures consumer dependencies deserialize as -// intended. -func TestDeserializeConsumerDeps(t *testing.T) { - tests := []struct { - name string - serialized []byte - expected []string - }{{ - name: "no dependencies", - serialized: []byte{}, - expected: []string{}, - }, { - name: "one dependency", - serialized: []byte("alpha"), - expected: []string{"alpha"}, - }, { - name: "odd number of dependencies", - serialized: []byte("alpha,bravo,charlie"), - expected: []string{"alpha", "bravo", "charlie"}, - }, { - name: "even number of dependencies", - serialized: []byte("alpha,bravo,charlie,echo"), - expected: []string{"alpha", "bravo", "charlie", "echo"}, - }} - - for _, test := range tests { - deserialized := deserializeSpendConsumerDeps(test.serialized) - for idx := range test.expected { - if deserialized[idx] != test.expected[idx] { - t.Errorf("%q: unexpected dependency mismatch at index %d, "+ - "expected %q, got %q", test.name, idx, - test.expected[idx], deserialized[idx]) - continue - } - } - } -} - -// createdDB creates the test database. This is intended to be used for testing -// purposes only. -func createDB() (database.DB, func(), error) { - dbPath := filepath.Join(os.TempDir(), "spdb") - - err := os.MkdirAll(dbPath, 0700) - if err != nil { - return nil, nil, err - } - - db, err := database.Create("ffldb", dbPath, wire.SimNet) - if err != nil { - return nil, nil, err - } - - err = initConsumerDepsBucket(db) - if err != nil { - return nil, nil, err - } - - teardown := func() { - db.Close() - os.RemoveAll(dbPath) - } - - return db, teardown, nil -} diff --git a/internal/blockchain/spendpruner/error.go b/internal/blockchain/spendpruner/error.go deleted file mode 100644 index 8886e253..00000000 --- a/internal/blockchain/spendpruner/error.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2021 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package spendpruner - -// ErrorKind identifies a kind of error. It has full support for errors.Is and -// errors.As, so the caller can directly check against an error kind when -// determining the reason for an error. -type ErrorKind string - -// These constants are used to identify a specific PruneError. -const ( - // ErrNoConsumer indicates a spend journal consumer hash does not exist. - ErrNoConsumer = ErrorKind("ErrNoConsumer") - - // ErrLoadSpendDeps indicates an error loading consumer - // spend dependencies. - ErrLoadSpendDeps = ErrorKind("ErrLoadSpendDeps") - - // ErrNeedSpendData indicates an error asserting a spend data dependency. - ErrNeedSpendData = ErrorKind("ErrNeedSpendData") - - // ErrUpdateConsumerDeps indicates an error updating consumer spend - // dependencies. - ErrUpdateConsumerDeps = ErrorKind("ErrUpdateConsumerDeps") -) - -// Error satisfies the error interface and prints human-readable errors. -func (e ErrorKind) Error() string { - return string(e) -} - -// PruneError identifies a spend journal pruner error. It has full support for -// errors.Is and errors.As, so the caller can ascertain the specific reason -// for the error by checking the underlying error. -type PruneError struct { - Description string - Err error -} - -// Error satisfies the error interface and prints human-readable errors. -func (e PruneError) Error() string { - return e.Description -} - -// Unwrap returns the underlying wrapped error. -func (e PruneError) Unwrap() error { - return e.Err -} - -// pruneError creates a PruneError given a set of arguments. -func pruneError(kind ErrorKind, desc string) PruneError { - return PruneError{Err: kind, Description: desc} -} diff --git a/internal/blockchain/spendpruner/error_test.go b/internal/blockchain/spendpruner/error_test.go deleted file mode 100644 index da2e470b..00000000 --- a/internal/blockchain/spendpruner/error_test.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) 2021 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package spendpruner - -import ( - "errors" - "io" - "testing" -) - -// TestErrorKindStringer tests the stringized output for the ErrorKind type. -func TestErrorKindStringer(t *testing.T) { - tests := []struct { - in ErrorKind - want string - }{ - {ErrNoConsumer, "ErrNoConsumer"}, - {ErrLoadSpendDeps, "ErrLoadSpendDeps"}, - {ErrNeedSpendData, "ErrNeedSpendData"}, - {ErrUpdateConsumerDeps, "ErrUpdateConsumerDeps"}, - } - - for i, test := range tests { - result := test.in.Error() - if result != test.want { - t.Errorf("%d: got: %s want: %s", i, result, test.want) - continue - } - } -} - -// TestPruneError tests the error output for the PruneError type. -func TestPruneError(t *testing.T) { - tests := []struct { - in PruneError - want string - }{{ - PruneError{Description: "duplicate block"}, - "duplicate block", - }, { - PruneError{Description: "human-readable error"}, - "human-readable error", - }} - - for i, test := range tests { - result := test.in.Error() - if result != test.want { - t.Errorf("#%d: got: %s want: %s", i, result, test.want) - continue - } - } -} - -// TestPruneErrorKindIsAs ensures both ErrorKind and PruneError can be -// identified as being a specific error kind via errors.Is and unwrapped -// via errors.As. -func TestPruneErrorKindIsAs(t *testing.T) { - tests := []struct { - name string - err error - target error - wantMatch bool - wantAs ErrorKind - }{{ - name: "ErrNoConsumer == ErrNoConsumer", - err: ErrNoConsumer, - target: ErrNoConsumer, - wantMatch: true, - wantAs: ErrNoConsumer, - }, { - name: "PruneError.ErrNoConsumer == ErrNoConsumer", - err: pruneError(ErrNoConsumer, ""), - target: ErrNoConsumer, - wantMatch: true, - wantAs: ErrNoConsumer, - }, { - name: "PruneError.ErrNoConsumer == PruneError.ErrNoConsumer", - err: pruneError(ErrNoConsumer, ""), - target: pruneError(ErrNoConsumer, ""), - wantMatch: true, - wantAs: ErrNoConsumer, - }, { - name: "ErrNoConsumer != ErrNeedSpendData", - err: ErrNoConsumer, - target: ErrNeedSpendData, - wantMatch: false, - wantAs: ErrNoConsumer, - }, { - name: "PruneError.ErrNoConsumer != ErrNeedSpendData", - err: pruneError(ErrNoConsumer, ""), - target: ErrNeedSpendData, - wantMatch: false, - wantAs: ErrNoConsumer, - }, { - name: "ErrNoConsumer != PruneError.ErrNeedSpendData", - err: ErrNoConsumer, - target: pruneError(ErrNeedSpendData, ""), - wantMatch: false, - wantAs: ErrNoConsumer, - }, { - name: "PruneError.ErrNoConsumer != PruneError.ErrNeedSpendData", - err: pruneError(ErrNoConsumer, ""), - target: pruneError(ErrNeedSpendData, ""), - wantMatch: false, - wantAs: ErrNoConsumer, - }, { - name: "PruneError.ErrNoConsumer != io.EOF", - err: pruneError(ErrNoConsumer, ""), - target: io.EOF, - wantMatch: false, - wantAs: ErrNoConsumer, - }} - - for _, test := range tests { - // Ensure the error matches or not depending on the expected result. - result := errors.Is(test.err, test.target) - if result != test.wantMatch { - t.Errorf("%s: incorrect error identification -- got %v, want %v", - test.name, result, test.wantMatch) - continue - } - - // Ensure the underlying error kind can be unwrapped and is the - // expected kind. - var kind ErrorKind - if !errors.As(test.err, &kind) { - t.Errorf("%s: unable to unwrap to error kind", test.name) - continue - } - if kind != test.wantAs { - t.Errorf("%s: unexpected unwrapped error kind -- got %v, want %v", - test.name, kind, test.wantAs) - continue - } - } -} diff --git a/internal/blockchain/spendpruner/interface.go b/internal/blockchain/spendpruner/interface.go deleted file mode 100644 index 99be2fcf..00000000 --- a/internal/blockchain/spendpruner/interface.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2021 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package spendpruner - -import ( - "github.com/decred/dcrd/chaincfg/chainhash" -) - -// SpendConsumer describes the requirements for implementing a spend journal -// consumer. -// -// All functions MUST be safe for concurrent access. -type SpendConsumer interface { - // ID returns the identifier of the consumer. - ID() string - - // NeedSpendData checks whether the associated spend journal entry for the - // provided block hash will be needed by the consumer. - NeedSpendData(hash *chainhash.Hash) (bool, error) -} diff --git a/internal/blockchain/spendpruner/log.go b/internal/blockchain/spendpruner/log.go deleted file mode 100644 index 4c4183a0..00000000 --- a/internal/blockchain/spendpruner/log.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2021 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package spendpruner - -import ( - "github.com/decred/slog" -) - -// log is a logger that is initialized with no output filters. This means -// the package will not perform any logging by default until the caller -// requests it. The default amount of logging is none. -var ( - log = slog.Disabled -) - -// UseLogger uses a specified Logger to output package logging info. -func UseLogger(logger slog.Logger) { - log = logger -} diff --git a/internal/blockchain/spendpruner/pruner.go b/internal/blockchain/spendpruner/pruner.go deleted file mode 100644 index b83820bb..00000000 --- a/internal/blockchain/spendpruner/pruner.go +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) 2021-2022 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package spendpruner - -import ( - "context" - "fmt" - "sync" - - "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/database/v3" -) - -// spendPrunerEventType represents a spend pruner event message. -type spendPrunerEventType int - -const ( - // spBlockConnected indicates a new block was connected to the chain. - spBlockConnected spendPrunerEventType = iota - - // spBlockDisconnected indicates there are no spend dependencies for the - // block disconnected from the chain. - spBlockDisconnected -) - -// SpendJournalNotification represents the notification received by the pruner -// on block connections and disconnections. -type SpendJournalNotification struct { - BlockHash *chainhash.Hash - Event spendPrunerEventType - Done chan bool -} - -// SpendJournalPruner represents a spend journal pruner that ensures spend -// journal entries needed by consumers are retained until no longer needed. -type SpendJournalPruner struct { - // This removes the spend journal entry of the provided block hash if it - // is not part of the main chain. - removeSpendEntry func(hash *chainhash.Hash) error - - // These fields track spend consumers and their spend journal dependencies. - dependents map[chainhash.Hash][]string - dependentsMtx sync.RWMutex - consumers map[string]SpendConsumer - consumersMtx sync.RWMutex - - // This field relays block connection and disconnection signals for - // processing. - ch chan SpendJournalNotification - - // This field provides access to the database. - db database.DB -} - -// NewSpendJournalPruner initializes a spend journal pruner. -func NewSpendJournalPruner(db database.DB, removeSpendEntry func(hash *chainhash.Hash) error) (*SpendJournalPruner, error) { - err := initConsumerDepsBucket(db) - if err != nil { - return nil, err - } - - spendPruner := &SpendJournalPruner{ - db: db, - removeSpendEntry: removeSpendEntry, - dependents: make(map[chainhash.Hash][]string), - consumers: make(map[string]SpendConsumer), - ch: make(chan SpendJournalNotification), - } - - err = spendPruner.loadSpendConsumerDeps() - if err != nil { - return nil, err - } - - return spendPruner, nil -} - -// AddConsumer adds a spend journal consumer to the pruner. -func (s *SpendJournalPruner) AddConsumer(consumer SpendConsumer) { - s.consumersMtx.Lock() - s.consumers[consumer.ID()] = consumer - s.consumersMtx.Unlock() -} - -// FetchConsumer returns the spend journal consumer associated with the -// provided id. -func (s *SpendJournalPruner) FetchConsumer(id string) (SpendConsumer, error) { - s.consumersMtx.RLock() - defer s.consumersMtx.RUnlock() - consumer, ok := s.consumers[id] - if !ok { - msg := fmt.Sprintf("no spend consumer found with id %s", id) - return nil, pruneError(ErrNoConsumer, msg) - } - - return consumer, nil -} - -// DependencyExists determines whether there are spend consumer dependencies -// for the provided block hash. -func (s *SpendJournalPruner) DependencyExists(blockHash *chainhash.Hash) bool { - s.dependentsMtx.RLock() - defer s.dependentsMtx.RUnlock() - - _, ok := s.dependents[*blockHash] - return ok -} - -// NotifyConnectedBlock signals the spend pruner of the provided -// connected block hash. -func (s *SpendJournalPruner) NotifyConnectedBlock(blockHash *chainhash.Hash) { - s.ch <- SpendJournalNotification{ - BlockHash: blockHash, - Event: spBlockConnected, - } -} - -// dependencyExistsInternal determines whether a spend consumer depends on -// the spend data of the provided block hash. -func (s *SpendJournalPruner) dependencyExistsInternal(blockHash *chainhash.Hash, consumerID string) bool { - s.dependentsMtx.RLock() - dependents, ok := s.dependents[*blockHash] - s.dependentsMtx.RUnlock() - if !ok { - // The dependency does not exist if the block hash is not - // a key for dependents. - return false - } - - for _, id := range dependents { - if consumerID == id { - return true - } - } - - return false -} - -// addSpendConsumerDeps adds an entry for each spend consumer dependent on -// journal data for the provided block hash. -func (s *SpendJournalPruner) addSpendConsumerDeps(blockHash *chainhash.Hash) error { - s.consumersMtx.RLock() - defer s.consumersMtx.RUnlock() - - for _, consumer := range s.consumers { - if s.dependencyExistsInternal(blockHash, consumer.ID()) { - // Dependency already created, skip. - continue - } - - needSpendData, err := consumer.NeedSpendData(blockHash) - if err != nil { - msg := fmt.Sprintf("unable to assert dependency: %s", err) - return pruneError(ErrNeedSpendData, msg) - } - - if !needSpendData { - continue - } - - // Add a spend dependency entry of the block hash for the consumer. - s.dependentsMtx.Lock() - dependents, ok := s.dependents[*blockHash] - if !ok { - dependents = []string{consumer.ID()} - s.dependents[*blockHash] = dependents - s.dependentsMtx.Unlock() - - continue - } - - dependents = append(dependents, consumer.ID()) - s.dependents[*blockHash] = dependents - s.dependentsMtx.Unlock() - } - - s.dependentsMtx.Lock() - dependents := s.dependents[*blockHash] - s.dependentsMtx.Unlock() - - // Update the persisted spend consumer deps entry for - // the provided block hash. - err := s.db.Update(func(tx database.Tx) error { - err := dbUpdateSpendConsumerDeps(tx, *blockHash, dependents) - return err - }) - if err != nil { - msg := fmt.Sprintf("unable to update persisted consumer "+ - "dependencies for block hash %v: %v", blockHash, err) - return pruneError(ErrUpdateConsumerDeps, msg) - } - - return nil -} - -// RemoveSpendConsumerDependency removes the provided spend consumer dependency -// associated with the provided block hash from the spend pruner. The block -// hash is removed as a key of the dependents map once all its dependency -// entries are removed. -func (s *SpendJournalPruner) RemoveSpendConsumerDependency(dbTx database.Tx, blockHash *chainhash.Hash, consumerID string) error { - s.dependentsMtx.Lock() - dependents, ok := s.dependents[*blockHash] - if !ok { - s.dependentsMtx.Unlock() - // No entry for block hash found, do nothing. - return nil - } - - for idx := 0; idx < len(dependents); idx++ { - if dependents[idx] == consumerID { - dependents = append(dependents[:idx], dependents[idx+1:]...) - s.dependents[*blockHash] = dependents - break - } - } - s.dependentsMtx.Unlock() - - if len(dependents) == 0 { - s.dependentsMtx.Lock() - delete(s.dependents, *blockHash) - s.dependentsMtx.Unlock() - go func() { - s.ch <- SpendJournalNotification{ - BlockHash: blockHash, - Event: spBlockDisconnected, - } - }() - } - - // Update the tracked spend journal entry for the provided - // block hash. - err := dbUpdateSpendConsumerDeps(dbTx, *blockHash, dependents) - if err != nil { - msg := fmt.Sprintf("unable to update consumer dependencies "+ - "entry for block hash %v: %v", blockHash, err) - return pruneError(ErrUpdateConsumerDeps, msg) - } - - return nil -} - -// removeSpendConsumerDeps removes the key/value pair of spend consumer -// dependencies and the provided block hash from the the prune set as well -// as the database. -func (s *SpendJournalPruner) removeSpendConsumerDeps(blockHash *chainhash.Hash) error { - s.dependentsMtx.Lock() - delete(s.dependents, *blockHash) - s.dependentsMtx.Unlock() - - // Remove the tracked spend journal entry for the provided - // block hash. - err := s.db.Update(func(tx database.Tx) error { - return dbUpdateSpendConsumerDeps(tx, *blockHash, nil) - }) - if err != nil { - msg := fmt.Sprintf("unable to remove persisted consumer dependencies "+ - "entry for block hash %v: %v", blockHash, err) - return pruneError(ErrUpdateConsumerDeps, msg) - } - - return nil -} - -// loadSpendConsumerDeps loads persisted consumer spend dependencies from -// the database. -func (s *SpendJournalPruner) loadSpendConsumerDeps() error { - return s.db.View(func(tx database.Tx) error { - consumerDeps, err := dbFetchSpendConsumerDeps(tx) - if err != nil { - msg := fmt.Sprintf("unable to load spend consumer "+ - "dependencies: %v", err) - return pruneError(ErrLoadSpendDeps, msg) - } - - s.dependentsMtx.Lock() - for k, v := range consumerDeps { - s.dependents[k] = v - } - s.dependentsMtx.Unlock() - - return nil - }) -} - -// HandleSignals processes incoming signals to the spend pruner. -func (s *SpendJournalPruner) HandleSignals(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - - case ntfn := <-s.ch: - signalDone := func() { - if ntfn.Done != nil { - close(ntfn.Done) - } - } - - switch ntfn.Event { - case spBlockDisconnected: - err := s.removeSpendEntry(ntfn.BlockHash) - if err != nil { - log.Errorf("unable to prune spend data for "+ - "block hash (%s): %v", ntfn.BlockHash, err) - - } - - signalDone() - - case spBlockConnected: - if !s.DependencyExists(ntfn.BlockHash) { - // Do nothing if there are no spend journal dependencies - // for the the connected block. - signalDone() - continue - } - - // Remove the key/value pair of persisted spend consumer - // dependencies and the provided connected block hash from - // the prune set. - err := s.removeSpendConsumerDeps(ntfn.BlockHash) - if err != nil { - log.Error(err) - } - - signalDone() - - default: - log.Errorf("unknown spend journal notification type: %d", - ntfn.Event) - - signalDone() - } - } - } -} - -// MaybePruneSpendData firsts adds consumer spend dependencies for the provided -// blockhash if any. If there are no dependencies the spend journal entry -// associated with the provided block hash is pruned. -func (s *SpendJournalPruner) MaybePruneSpendData(blockHash *chainhash.Hash, done chan bool) error { - err := s.addSpendConsumerDeps(blockHash) - if err != nil { - return err - } - - if s.DependencyExists(blockHash) { - // Do nothing if there are spend dependencies for the provided block - // hash. - return nil - } - - go func() { - s.ch <- SpendJournalNotification{ - BlockHash: blockHash, - Event: spBlockDisconnected, - Done: done, - } - }() - - return nil -} diff --git a/internal/blockchain/spendpruner/pruner_test.go b/internal/blockchain/spendpruner/pruner_test.go deleted file mode 100644 index 6458bfa6..00000000 --- a/internal/blockchain/spendpruner/pruner_test.go +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright (c) 2021 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package spendpruner - -import ( - "context" - "errors" - "fmt" - "sync" - "testing" - "time" - - "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/database/v3" -) - -// testSpendConsumer represents a mock spend consumer for testing purposes only. -type testSpendConsumer struct { - id string - queryer *testChain - tip *chainhash.Hash - needSpendData bool - needSpendDataErr error -} - -// ID returns the identifier of the consumer. -func (t *testSpendConsumer) ID() string { - return t.id -} - -// NeedSpendData checks whether the associated spend journal entry -// for the provided block hash will be needed by the consumer. -func (t *testSpendConsumer) NeedSpendData(hash *chainhash.Hash) (bool, error) { - return t.needSpendData, t.needSpendDataErr -} - -// testChain represents a mock chain for testing purposes only. -type testChain struct { - removedSpendEntries map[chainhash.Hash]struct{} - removeSpendEntryErr error - mtx sync.Mutex -} - -// RemoveSpendEntry purges the associated spend journal entry of the provided -// block hash. -func (t *testChain) RemoveSpendEntry(hash *chainhash.Hash) error { - if t.removeSpendEntryErr != nil { - return t.removeSpendEntryErr - } - - t.mtx.Lock() - t.removedSpendEntries[*hash] = struct{}{} - t.mtx.Unlock() - - return nil -} - -// TestSpendPruner ensures the spend pruner works as intended. -func TestSpendPruner(t *testing.T) { - chain := &testChain{ - removedSpendEntries: make(map[chainhash.Hash]struct{}), - } - - consumerA := &testSpendConsumer{ - id: "consumer_a", - queryer: chain, - tip: &chainhash.Hash{0}, - needSpendData: true, - } - - consumerB := &testSpendConsumer{ - id: "consumer_b", - queryer: chain, - tip: &chainhash.Hash{0}, - needSpendData: false, - needSpendDataErr: fmt.Errorf("unable to confirm spend data need"), - } - - db, teardown, err := createDB() - if err != nil { - t.Fatal(err) - } - - defer teardown() - - ctx, cancel := context.WithCancel(context.Background()) - pruner, err := NewSpendJournalPruner(db, chain.RemoveSpendEntry) - if err != nil { - t.Fatal(err) - } - - go pruner.HandleSignals(ctx) - - pruner.AddConsumer(consumerA) - pruner.AddConsumer(consumerB) - - // Ensure adding the same consumer does not create a duplicate. - pruner.AddConsumer(consumerB) - - pruner.consumersMtx.Lock() - consumers := len(pruner.consumers) - pruner.consumersMtx.Unlock() - - if consumers != 2 { - t.Fatalf("expected 2 consumers, got %d", consumers) - } - - // Ensure adding dependents fails if a consumer errors checking if the - // data is needed. - hashA := &chainhash.Hash{'a'} - err = pruner.addSpendConsumerDeps(hashA) - if !errors.Is(err, ErrNeedSpendData) { - t.Fatalf("expected a spend data error, got %v", err) - } - - fetchHashSpendDependents := func(pruner *SpendJournalPruner, hash *chainhash.Hash) ([]string, bool) { - pruner.dependentsMtx.Lock() - dependents, ok := pruner.dependents[*hash] - pruner.dependentsMtx.Unlock() - - return dependents, ok - } - - isRemovedSpendEntry := func(chain *testChain, hash *chainhash.Hash) bool { - chain.mtx.Lock() - _, ok := chain.removedSpendEntries[*hash] - chain.mtx.Unlock() - - return ok - } - - consumerB.needSpendDataErr = nil - - // Ensure adding dependents creates entries for consumers that need - // the spend data of the provided block hash. - err = pruner.addSpendConsumerDeps(hashA) - if err != nil { - t.Fatalf("unexpected error adding consumer dependencies: %v", err) - } - - // Ensure there is only one dependent entry for hashA, belonging to - // consumerA. - dependents, ok := fetchHashSpendDependents(pruner, hashA) - if !ok { - t.Fatalf("expected dependents to have an entry for %s", hashA) - } - - if len(dependents) != 1 { - t.Fatalf("expected one dependent entry for hashA, got %d", - len(dependents)) - } - - if dependents[0] != consumerA.ID() { - t.Fatalf("expected hashA's dependent to be %s, got %s", - consumerA.ID(), dependents[0]) - } - - // Ensure there is no existing dependency for consumerB on hashA. - exists := pruner.dependencyExistsInternal(hashA, consumerB.ID()) - if exists { - t.Fatal("unexpected dependency found for consumerB on hashA") - } - - // Ensure there is an existing dependency for consumerA on hashA. - exists = pruner.dependencyExistsInternal(hashA, consumerA.ID()) - if !exists { - t.Fatal("expected dependency found for consumerA on hashA") - } - - // Ensure consumerC and consumerA now have dependencies on hashA. - consumerC := &testSpendConsumer{ - id: "consumer_c", - tip: &chainhash.Hash{0}, - needSpendData: true, - } - - pruner.AddConsumer(consumerC) - - setConsumerNeedSpendData := func(state bool) { - consumerA.needSpendData = state - consumerB.needSpendData = state - consumerC.needSpendData = state - } - - removeSpendConsumerDep := func(pruner *SpendJournalPruner, blockHash *chainhash.Hash, consumerID string) error { - return pruner.db.Update(func(tx database.Tx) error { - return pruner.RemoveSpendConsumerDependency(tx, blockHash, consumerID) - }) - } - - err = pruner.addSpendConsumerDeps(hashA) - if err != nil { - t.Fatalf("unexpected error adding spend data dependents "+ - "for hashA: %v", err) - } - - exists = pruner.DependencyExists(hashA) - if !exists { - t.Fatal("expected existing dependencies for hashA") - } - - exists = pruner.dependencyExistsInternal(hashA, consumerC.ID()) - if !exists { - t.Fatal("expected dependency found for consumerC on hashA") - } - - exists = pruner.dependencyExistsInternal(hashA, consumerA.ID()) - if !exists { - t.Fatal("expected dependency found for consumerA on hashA") - } - - // Ensure there are now two dependent entries for hashA. - dependents, _ = fetchHashSpendDependents(pruner, hashA) - if len(dependents) != 2 { - t.Fatalf("expected two dependent entry for hashA, got %d", - len(dependents)) - } - - // Ensure removing a non-existent dependency does nothing. - err = removeSpendConsumerDep(pruner, hashA, "consumer_x") - if err != nil { - t.Fatalf("unexpected error removing non-existent spend "+ - "consumer dependency %v", err) - } - - // Ensure removing dependencies for a non-existent block hash does nothing. - hashT := &chainhash.Hash{'T'} - err = removeSpendConsumerDep(pruner, hashT, "consumer_x") - if err != nil { - t.Fatalf("unexpected error removing spend "+ - "consumer dependency for non-existent block hash %v", err) - } - - dependents, _ = fetchHashSpendDependents(pruner, hashA) - if len(dependents) != 2 { - t.Fatalf("expected two dependent entry for hashA, got %d", - len(dependents)) - } - - // Update spend consumers to not need spend data for upcoming tests. - setConsumerNeedSpendData(false) - - // Ensure the spend pruner does not remove the spend entries if - // there are existing dependencies for it. - err = pruner.MaybePruneSpendData(hashA, nil) - if err != nil { - t.Fatalf("[MaybePruneSpendData] unexpected error: %v", err) - } - - ok = isRemovedSpendEntry(chain, hashA) - if ok { - t.Fatal("unexpected hashA spend data pruned") - } - - // Ensure the spend pruner does remove spend entries for - // a hash if there are no existing spend dependencies for it. - hashX := &chainhash.Hash{'x'} - done := make(chan bool) - err = pruner.MaybePruneSpendData(hashX, done) - if err != nil { - t.Fatalf("[MaybePruneSpendData] unexpected error: %v", err) - } - - select { - case <-done: - // Do nothing. - case <-time.After(time.Second * 3): - t.Fatal("timeout waiting for remove spend journal done signal") - } - - ok = isRemovedSpendEntry(chain, hashX) - if !ok { - t.Fatalf("expected hashX spend data to be pruned") - } - - // Remove the HashA dependency for consumerC. - removeSpendConsumerDep(pruner, hashA, consumerC.ID()) - - dependents, _ = fetchHashSpendDependents(pruner, hashA) - if len(dependents) != 1 { - t.Fatalf("expected no dependent entry for hashA, got %d", - len(dependents)) - } - - // Trigger a spend journal prune for HashA's entry by removing the - // last dependency for it. - removeSpendConsumerDep(pruner, hashA, consumerA.ID()) - - // Ensure the pruner no longer has a dependent entry for HashA. - _, ok = pruner.dependents[*hashA] - if ok { - t.Fatal("expected no dependent entry for hashA") - } - - ok = isRemovedSpendEntry(chain, hashX) - if !ok { - t.Fatal("expected hashA spend data to be pruned") - } - - // Update spend consumers to need spend data for upcoming tests. - setConsumerNeedSpendData(true) - - // Create consumer dependencies for hashB and hashC. - hashB := &chainhash.Hash{'b'} - err = pruner.addSpendConsumerDeps(hashB) - if err != nil { - t.Fatalf("unexpected error adding consumer dependencies "+ - "for hashB: %v", err) - } - - hashC := &chainhash.Hash{'c'} - err = pruner.addSpendConsumerDeps(hashC) - if err != nil { - t.Fatalf("unexpected error adding consumer dependencies "+ - "for hashC: %v", err) - } - - if !pruner.DependencyExists(hashB) { - t.Fatal("expected consumer dependencies for hashB") - } - - // Trigger a consumer dependencies purge by signalling hashB as a - // connected block. - go pruner.NotifyConnectedBlock(hashB) - time.Sleep(time.Millisecond * 100) - - if pruner.DependencyExists(hashB) { - t.Fatal("expected no consumer dependencies entry for hashB") - } - - // Ensure the spend pruner does nothing if the provided connected block - // does not have any spend consumer dependencies. - go pruner.NotifyConnectedBlock(hashT) - time.Sleep(time.Millisecond * 100) - - // Ensure there are consumer dependencies for hashC before shutting down. - if !pruner.DependencyExists(hashC) { - t.Fatal("expected consumer dependencies for hashC") - } - - pruner.dependentsMtx.Lock() - expected := len(pruner.dependents[*hashC]) - pruner.dependentsMtx.Unlock() - - cancel() - - // Load the spend pruner from the database. - pruner, err = NewSpendJournalPruner(db, chain.RemoveSpendEntry) - if err != nil { - t.Fatal(err) - } - - // Ensure the consumer dependencies for hashC were loaded from - // the database. - if !pruner.DependencyExists(hashC) { - t.Fatal("expected consumer dependencies for hashC") - } - - // Ensure the expected number of dependents for hashC are identical - // after loading from the database. - deps, _ := fetchHashSpendDependents(pruner, hashC) - - if len(deps) != expected { - t.Fatalf("dependencies mismatch, expected count of %d, got %d", - expected, len(deps)) - } -} diff --git a/server.go b/server.go index 4c378eed..791250eb 100644 --- a/server.go +++ b/server.go @@ -3072,14 +3072,6 @@ func (s *server) Run(ctx context.Context) { } } - // Start the chain's spend pruner handler which processes spend journal - // prune signals. - s.wg.Add(1) - go func(ctx context.Context, s *server) { - s.chain.SpendPrunerHandler(ctx) - s.wg.Done() - }(ctx, s) - // Start the chain's index subscriber. s.wg.Add(1) go func(ctx context.Context, s *server) {