diff --git a/blockchain/chain.go b/blockchain/chain.go index 00e2f3a6..0e48b694 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -1642,6 +1642,115 @@ func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Has return headers } +// InvalidateBlock takes a block hash and invalidates it. +// +// This function is safe for concurrent access. +func (b *BlockChain) InvalidateBlock(hash *chainhash.Hash) error { + return b.invalidateBlock(hash) +} + +// invalidateBlock takes a block hash and invalidates it. +func (b *BlockChain) invalidateBlock(hash *chainhash.Hash) error { + node := b.index.LookupNode(hash) + if node == nil { + err := fmt.Errorf("block %s is not known", hash) + return err + } + + // No need to invalidate if its already invalid. + if node.status.KnownInvalid() { + err := fmt.Errorf("block %s is already invalid", hash) + return err + } + + if node.parent == nil { + err := fmt.Errorf("block %s has no parent", hash) + return err + } + + b.index.SetStatusFlags(node, statusValidateFailed) + b.index.UnsetStatusFlags(node, statusValid) + + b.chainLock.Lock() + defer b.chainLock.Unlock() + detachNodes, attachNodes := b.getReorganizeNodes(node.parent) + + err := b.reorganizeChain(detachNodes, attachNodes) + if err != nil { + return err + } + + for i, e := 0, detachNodes.Front(); e != nil; i, e = i+1, e.Next() { + n := e.Value.(*blockNode) + + b.index.SetStatusFlags(n, statusInvalidAncestor) + b.index.UnsetStatusFlags(n, statusValid) + } + + if writeErr := b.index.flushToDB(); writeErr != nil { + log.Warnf("Error flushing block index changes to disk: %v", writeErr) + } + + return nil +} + +// ReconsiderBlock takes a block hash and allows it to be revalidated. +// +// This function is safe for concurrent access. +func (b *BlockChain) ReconsiderBlock(hash *chainhash.Hash) error { + return b.reconsiderBlock(hash) +} + +// reconsiderBlock takes a block hash and allows it to be revalidated. +func (b *BlockChain) reconsiderBlock(hash *chainhash.Hash) error { + node := b.index.LookupNode(hash) + if node == nil { + err := fmt.Errorf("block %s is not known", hash) + return err + } + + // No need to reconsider, it is already valid. + if node.status.KnownValid() { + err := fmt.Errorf("block %s is already valid", hash) + return err + } + + // Keep a reference to the first node in the chain of invalid + // blocks so we can reprocess after status flags are updated. + firstNode := node + + // Find previous node to the point where the blocks are valid again. + for n := node; n.status.KnownInvalid(); n = n.parent { + b.index.UnsetStatusFlags(n, statusInvalidAncestor) + b.index.UnsetStatusFlags(n, statusValidateFailed) + + firstNode = n + } + + var blk *btcutil.Block + err := b.db.View(func(dbTx database.Tx) error { + var err error + blk, err = dbFetchBlockByNode(dbTx, firstNode) + return err + }) + if err != nil { + return err + } + + // Process it all again. This will take care of the + // orphans as well. + _, _, err = b.ProcessBlock(blk, BFNoDupBlockCheck) + if err != nil { + return err + } + + if writeErr := b.index.flushToDB(); writeErr != nil { + log.Warnf("Error flushing block index changes to disk: %v", writeErr) + } + + return nil +} + // ClaimTrie returns the claimTrie associated wit hthe chain. func (b *BlockChain) ClaimTrie() *claimtrie.ClaimTrie { return b.claimTrie diff --git a/blockchain/process.go b/blockchain/process.go index a48b6e50..8aaa91bc 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -29,6 +29,10 @@ const ( // not be performed. BFNoPoWCheck + // BFNoDupBlockCheck signals if the block should skip existence + // checks. + BFNoDupBlockCheck + // BFNone is a convenience value to specifically indicate no flags. BFNone BehaviorFlags = 0 ) @@ -148,24 +152,26 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo blockHash := block.Hash() log.Tracef("Processing block %v", blockHash) - // The block must not already exist in the main chain or side chains. - exists, err := b.blockExists(blockHash) - if err != nil { - return false, false, err - } - if exists { - str := fmt.Sprintf("already have block %v", blockHash) - return false, false, ruleError(ErrDuplicateBlock, str) - } + if flags&BFNoDupBlockCheck != BFNoDupBlockCheck { + // The block must not already exist in the main chain or side chains. + exists, err := b.blockExists(blockHash) + if err != nil { + return false, false, err + } + if exists { + str := fmt.Sprintf("already have block %v", blockHash) + return false, false, ruleError(ErrDuplicateBlock, str) + } - // The block must not already exist as an orphan. - if _, exists := b.orphans[*blockHash]; exists { - str := fmt.Sprintf("already have block (orphan) %v", blockHash) - return false, false, ruleError(ErrDuplicateBlock, str) + // The block must not already exist as an orphan. + if _, exists := b.orphans[*blockHash]; exists { + str := fmt.Sprintf("already have block (orphan) %v", blockHash) + return false, false, ruleError(ErrDuplicateBlock, str) + } } // Perform preliminary sanity checks on the block and its transactions. - err = checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags) + err := checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags) if err != nil { return false, false, err } diff --git a/rpcserver.go b/rpcserver.go index 5c20436d..4797d64f 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -168,8 +168,10 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "getrawtransaction": handleGetRawTransaction, "gettxout": handleGetTxOut, "help": handleHelp, + "invalidateblock": handleInvalidateBlock, "node": handleNode, "ping": handlePing, + "reconsiderblock": handleReconsiderBlock, "searchrawtransactions": handleSearchRawTransactions, "sendrawtransaction": handleSendRawTransaction, "setgenerate": handleSetGenerate, @@ -237,9 +239,7 @@ var rpcUnimplemented = map[string]struct{}{ "getchaintips": {}, "getmempoolentry": {}, "getwork": {}, - "invalidateblock": {}, "preciousblock": {}, - "reconsiderblock": {}, } // Commands that are available to a limited user @@ -2977,6 +2977,30 @@ func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i return txOutReply, nil } +// handleInvalidateBlock implements the invalidateblock command +func handleInvalidateBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.InvalidateBlockCmd) + + hash, err := chainhash.NewHashFromStr(c.BlockHash) + if err != nil { + return nil, err + } + + return nil, s.cfg.Chain.InvalidateBlock(hash) +} + +// handleReconsiderBlock implements the reconsiderblock command +func handleReconsiderBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.ReconsiderBlockCmd) + + hash, err := chainhash.NewHashFromStr(c.BlockHash) + if err != nil { + return nil, err + } + + return nil, s.cfg.Chain.ReconsiderBlock(hash) +} + // handleHelp implements the help command. func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*btcjson.HelpCmd) diff --git a/rpcserverhelp.go b/rpcserverhelp.go index a124e492..038b3495 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -568,10 +568,18 @@ var helpDescsEnUS = map[string]string{ "help--result0": "List of commands", "help--result1": "Help for specified command", + // InvalidateBlockCmd + "invalidateblock--synopsis": "Invalidate a block.", + "invalidateblock-blockhash": "Hash of the block you want to invalidate", + // PingCmd help. "ping--synopsis": "Queues a ping to be sent to each connected peer.\n" + "Ping times are provided by getpeerinfo via the pingtime and pingwait fields.", + // ReconsiderBlockCmd + "reconsiderblock--synopsis": "Reconsider a block for validation.", + "reconsiderblock-blockhash": "Hash of the block you want to reconsider", + // SearchRawTransactionsCmd help. "searchrawtransactions--synopsis": "Returns raw data for transactions involving the passed address.\n" + "Returned transactions are pulled from both the database, and transactions currently in the mempool.\n" + @@ -848,7 +856,9 @@ var rpcResultTypes = map[string][]interface{}{ "gettxout": {(*btcjson.GetTxOutResult)(nil)}, "node": nil, "help": {(*string)(nil), (*string)(nil)}, + "invalidateblock": nil, "ping": nil, + "reconsiderblock": nil, "searchrawtransactions": {(*string)(nil), (*[]btcjson.SearchRawTransactionsResult)(nil)}, "sendrawtransaction": {(*string)(nil)}, "setgenerate": nil,