Repository: decred/dcrd
Pull request #2289 (2020-07-22 19:37:59)

multi: add SanityChecker interface.
This adds the sanity checker interface and update the function signature of `CheckBlockSanity`. Associated tests and mocks updated/added.

Work towards #2069.

davecgh: Would you please rebase to the latest master to resolve the merge conflicts so I can merge this?  It should be a simple `git rebase --onto master HEAD^` from the branch (after updating master of course).

Patch:
diff --git a/internal/rpcserver/interface.go b/internal/rpcserver/interface.go
index 364dc095..18f6ec69 100644
--- a/internal/rpcserver/interface.go
+++ b/internal/rpcserver/interface.go
@@ -455,3 +455,13 @@ type LogManager interface {
 	// is invalid.
 	ParseAndSetDebugLevels(debugLevel string) error
 }
+
+// SanityChecker represents a block sanity checker for use with the RPC server.
+//
+// The interface contract requires that all of these methods are safe for
+// concurrent access.
+type SanityChecker interface {
+	// CheckBlockSanity checks the correctness of the provided block
+	// per consensus.
+	CheckBlockSanity(block *dcrutil.Block) error
+}
diff --git a/internal/rpcserver/rpcserver.go b/internal/rpcserver/rpcserver.go
index 58873597..99a8dc8b 100644
--- a/internal/rpcserver/rpcserver.go
+++ b/internal/rpcserver/rpcserver.go
@@ -4786,8 +4786,7 @@ func verifyChain(_ context.Context, s *Server, level, depth int64) error {
 
 		// Level 1 does basic chain sanity checks.
 		if level > 0 {
-			err := blockchain.CheckBlockSanity(block, s.cfg.TimeSource,
-				s.cfg.ChainParams)
+			err := s.cfg.SanityChecker.CheckBlockSanity(block)
 			if err != nil {
 				log.Errorf("Verify is unable to validate "+
 					"block at hash %v height %d: %v",
@@ -5593,12 +5592,13 @@ type Config struct {
 
 	// These fields allow the RPC server to interface with the local block
 	// chain data and state.
-	TimeSource   blockchain.MedianTimeSource
-	Chain        Chain
-	ChainParams  *chaincfg.Params
-	DB           database.DB
-	FeeEstimator FeeEstimator
-	Services     wire.ServiceFlag
+	TimeSource    blockchain.MedianTimeSource
+	Chain         Chain
+	SanityChecker SanityChecker
+	ChainParams   *chaincfg.Params
+	DB            database.DB
+	FeeEstimator  FeeEstimator
+	Services      wire.ServiceFlag
 
 	// SubsidyCache defines a cache for efficient access to consensus-critical
 	// subsidy calculations.
diff --git a/rpcadaptors.go b/rpcadaptors.go
index 501f1d79..84f0ea27 100644
--- a/rpcadaptors.go
+++ b/rpcadaptors.go
@@ -14,6 +14,7 @@ import (
 	"github.com/decred/dcrd/blockchain/v3"
 	"github.com/decred/dcrd/blockchain/v3/indexers"
 	"github.com/decred/dcrd/chaincfg/chainhash"
+	"github.com/decred/dcrd/chaincfg/v3"
 	"github.com/decred/dcrd/dcrutil/v3"
 	"github.com/decred/dcrd/internal/fees"
 	"github.com/decred/dcrd/internal/mempool"
@@ -496,3 +497,22 @@ func (*rpcLogManager) SupportedSubsystems() []string {
 func (*rpcLogManager) ParseAndSetDebugLevels(debugLevel string) error {
 	return parseAndSetDebugLevels(debugLevel)
 }
+
+// rpcSanityChecker provides a block sanity checker for use with the RPC and
+// implements the rpcserver.SanityChecker interface.
+type rpcSanityChecker struct {
+	timeSource  blockchain.MedianTimeSource
+	chainParams *chaincfg.Params
+}
+
+// Ensure rpcSanityChecker implements the rpcserver.SanityChecker interface.
+var _ rpcserver.SanityChecker = (*rpcSanityChecker)(nil)
+
+// CheckBlockSanity checks the correctness of the provided block
+// per consensus.  An appropriate error is returned if anything is
+// invalid.
+//
+// This function is part of the rpcserver.SanityChecker interface implementation.
+func (s *rpcSanityChecker) CheckBlockSanity(block *dcrutil.Block) error {
+	return blockchain.CheckBlockSanity(block, s.timeSource, s.chainParams)
+}
diff --git a/server.go b/server.go
index 2091edf9..50c8652a 100644
--- a/server.go
+++ b/server.go
@@ -3265,19 +3265,20 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 		}
 
 		s.rpcServer, err = rpcserver.New(&rpcserver.Config{
-			Listeners:    rpcListeners,
-			ConnMgr:      &rpcConnManager{&s},
-			SyncMgr:      &rpcSyncMgr{&s, s.blockManager},
-			FeeEstimator: &rpcFeeEstimator{s.feeEstimator},
-			TimeSource:   s.timeSource,
-			Services:     s.services,
-			AddrManager:  &rpcAddrManager{s.addrManager},
-			Clock:        &rpcClock{},
-			SubsidyCache: s.subsidyCache,
-			Chain:        &rpcChain{s.chain},
-			ChainParams:  chainParams,
-			DB:           db,
-			TxMemPool:    s.txMemPool,
+			Listeners:     rpcListeners,
+			ConnMgr:       &rpcConnManager{&s},
+			SyncMgr:       &rpcSyncMgr{&s, s.blockManager},
+			FeeEstimator:  &rpcFeeEstimator{s.feeEstimator},
+			TimeSource:    s.timeSource,
+			Services:      s.services,
+			AddrManager:   &rpcAddrManager{s.addrManager},
+			Clock:         &rpcClock{},
+			SubsidyCache:  s.subsidyCache,
+			Chain:         &rpcChain{s.chain},
+			ChainParams:   chainParams,
+			SanityChecker: &rpcSanityChecker{s.timeSource, chainParams},
+			DB:            db,
+			TxMemPool:     s.txMemPool,
 			BgBlkTmplGenerator: func() *mining.BgBlkTmplGenerator {
 				return s.bg
 			},


Test patch:
diff --git a/internal/rpcserver/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go
index aa719742..25559891 100644
--- a/internal/rpcserver/rpcserverhandlers_test.go
+++ b/internal/rpcserver/rpcserverhandlers_test.go
@@ -654,6 +654,17 @@ func (l *testLogManager) SupportedSubsystems() []string {
 	return l.supportedSubsystems
 }
 
+// testSanityChecker provides a mock implementation that checks the sanity
+// state of a block.
+type testSanityChecker struct {
+	checkBlockSanityErr error
+}
+
+// testSanityChecker returns mock sanity state of the provided block.
+func (s *testSanityChecker) CheckBlockSanity(block *dcrutil.Block) error {
+	return s.checkBlockSanityErr
+}
+
 // ParseAndSetDebugLevels provides a mock implementation for parsing the
 // specified debug level and setting the levels accordingly.
 func (l *testLogManager) ParseAndSetDebugLevels(debugLevel string) error {
@@ -719,20 +730,21 @@ var block432100 = func() wire.MsgBlock {
 }()
 
 type rpcTest struct {
-	name             string
-	handler          commandHandler
-	cmd              interface{}
-	mockChainParams  *chaincfg.Params
-	mockChain        *testRPCChain
-	mockAddrManager  *testAddrManager
-	mockFeeEstimator *testFeeEstimator
-	mockSyncManager  *testSyncManager
-	mockConnManager  *testConnManager
-	mockClock        *testClock
-	mockLogManager   *testLogManager
-	result           interface{}
-	wantErr          bool
-	errCode          dcrjson.RPCErrorCode
+	name              string
+	handler           commandHandler
+	cmd               interface{}
+	mockChainParams   *chaincfg.Params
+	mockChain         *testRPCChain
+	mockSanityChecker *testSanityChecker
+	mockAddrManager   *testAddrManager
+	mockFeeEstimator  *testFeeEstimator
+	mockSyncManager   *testSyncManager
+	mockConnManager   *testConnManager
+	mockClock         *testClock
+	mockLogManager    *testLogManager
+	result            interface{}
+	wantErr           bool
+	errCode           dcrjson.RPCErrorCode
 }
 
 // defaultChainParams provides a default chaincfg.Params to be used throughout
@@ -878,6 +890,15 @@ func defaultMockRPCChain() *testRPCChain {
 	}
 }
 
+// defaultMockSanityChecker provides a default mock sanity checker to be used
+// throughout the tests. Tests can override these defaults by calling
+// defaultMockSanityChecker, updating fields as necessary on the returned
+// *testSanityChecker, and then setting rpcTest.mockSanityChecker as that
+// *testSanityChecker.
+func defaultMockSanityChecker() *testSanityChecker {
+	return &testSanityChecker{}
+}
+
 // defaultMockAddrManager provides a default mock address manager to be used
 // throughout the tests. Tests can override these defaults by calling
 // defaultMockAddrManager, updating fields as necessary on the returned
@@ -990,17 +1011,18 @@ func defaultMockLogManager() *testLogManager {
 // the tests.  Defaults can be overridden by tests through the rpcTest struct.
 func defaultMockConfig(chainParams *chaincfg.Params) *Config {
 	return &Config{
-		ChainParams:  chainParams,
-		Chain:        defaultMockRPCChain(),
-		AddrManager:  defaultMockAddrManager(),
-		FeeEstimator: defaultMockFeeEstimator(),
-		SyncMgr:      defaultMockSyncManager(),
-		ConnMgr:      defaultMockConnManager(),
-		Clock:        &testClock{},
-		LogManager:   defaultMockLogManager(),
-		TimeSource:   blockchain.NewMedianTime(),
-		Services:     wire.SFNodeNetwork | wire.SFNodeCF,
-		SubsidyCache: standalone.NewSubsidyCache(chainParams),
+		ChainParams:   chainParams,
+		Chain:         defaultMockRPCChain(),
+		SanityChecker: defaultMockSanityChecker(),
+		AddrManager:   defaultMockAddrManager(),
+		FeeEstimator:  defaultMockFeeEstimator(),
+		SyncMgr:       defaultMockSyncManager(),
+		ConnMgr:       defaultMockConnManager(),
+		Clock:         &testClock{},
+		LogManager:    defaultMockLogManager(),
+		TimeSource:    blockchain.NewMedianTime(),
+		Services:      wire.SFNodeNetwork | wire.SFNodeCF,
+		SubsidyCache:  standalone.NewSubsidyCache(chainParams),
 		NetInfo: []types.NetworksResult{{
 			Name:                      "IPV4",
 			Limited:                   false,
@@ -3913,6 +3935,9 @@ func testRPCServerHandler(t *testing.T, tests []rpcTest) {
 			if test.mockLogManager != nil {
 				rpcserverConfig.LogManager = test.mockLogManager
 			}
+			if test.mockSanityChecker != nil {
+				rpcserverConfig.SanityChecker = test.mockSanityChecker
+			}
 
 			testServer := &Server{cfg: *rpcserverConfig}
 			result, err := test.handler(nil, testServer, test.cmd)