Skip to content

Commit

Permalink
rpcserver: Add invalidate/reconsiderblock support.
Browse files Browse the repository at this point in the history
This implements the invalidateblock and reconsiderblock RPC commands
including the required help strings.
  • Loading branch information
davecgh committed Jan 7, 2021
1 parent 970a56e commit 034e279
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 3 deletions.
1 change: 1 addition & 0 deletions dcrjson/jsonrpcerr.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const (
ErrRPCRawTxString RPCErrorCode = -32602
ErrRPCDecodeHexString RPCErrorCode = -22
ErrRPCDuplicateTx RPCErrorCode = -40
ErrRPCReconsiderFailure RPCErrorCode = -50
)

// Errors that are specific to btcd.
Expand Down
17 changes: 17 additions & 0 deletions internal/rpcserver/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,23 @@ type Chain interface {
// TSpendCountVotes returns the votes for the specified tspend up to
// the specified block.
TSpendCountVotes(*chainhash.Hash, *dcrutil.Tx) (int64, int64, error)

// InvalidateBlock manually invalidates the provided block as if the block
// had violated a consensus rule and marks all of its descendants as having
// a known invalid ancestor. It then reorganizes the chain as necessary so
// the branch with the most cumulative proof of work that is still valid
// becomes the main chain.
InvalidateBlock(*chainhash.Hash) error

// ReconsiderBlock removes the known invalid status of the provided block
// and all of its ancestors along with the known invalid ancestor status
// from all of its descendants that are neither themselves marked as having
// failed validation nor descendants of another such block. Therefore, it
// allows the affected blocks to be reconsidered under the current consensus
// rules. It then potentially reorganizes the chain as necessary so the
// block with the most cumulative proof of work that is valid becomes the
// tip of the main chain.
ReconsiderBlock(*chainhash.Hash) error
}

// Clock represents a clock for use with the RPC server. The purpose of this
Expand Down
83 changes: 83 additions & 0 deletions internal/rpcserver/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,12 @@ var rpcHandlersBeforeInit = map[types.Method]commandHandler{
"gettxoutsetinfo": handleGetTxOutSetInfo,
"getwork": handleGetWork,
"help": handleHelp,
"invalidateblock": handleInvalidateBlock,
"livetickets": handleLiveTickets,
"missedtickets": handleMissedTickets,
"node": handleNode,
"ping": handlePing,
"reconsiderblock": handleReconsiderBlock,
"regentemplate": handleRegenTemplate,
"searchrawtransactions": handleSearchRawTransactions,
"sendrawtransaction": handleSendRawTransaction,
Expand Down Expand Up @@ -3889,6 +3891,35 @@ func handleHelp(_ context.Context, s *Server, cmd interface{}) (interface{}, err
return help, nil
}

// handleInvalidateBlock implements the invalidateblock command.
func handleInvalidateBlock(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
c := cmd.(*types.InvalidateBlockCmd)
hash, err := chainhash.NewHashFromStr(c.BlockHash)
if err != nil {
return nil, rpcDecodeHexError(c.BlockHash)
}

chain := s.cfg.Chain
err = chain.InvalidateBlock(hash)
if err != nil {
if errors.Is(err, blockchain.ErrUnknownBlock) {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: fmt.Sprintf("Block not found: %v", hash),
}
}

if errors.Is(err, blockchain.ErrInvalidateGenesisBlock) {
return nil, rpcInvalidError("%v", err)
}

context := fmt.Sprintf("Failed to invalidate block %s", hash)
return nil, rpcInternalError(err.Error(), context)
}

return nil, nil
}

// handleLiveTickets implements the livetickets command.
func handleLiveTickets(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
lt, err := s.cfg.Chain.LiveTickets()
Expand Down Expand Up @@ -3934,6 +3965,58 @@ func handlePing(_ context.Context, s *Server, cmd interface{}) (interface{}, err
return nil, nil
}

// handleReconsiderBlock implements the reconsiderblock command.
func handleReconsiderBlock(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
c := cmd.(*types.ReconsiderBlockCmd)
hash, err := chainhash.NewHashFromStr(c.BlockHash)
if err != nil {
return nil, rpcDecodeHexError(c.BlockHash)
}

chain := s.cfg.Chain
err = chain.ReconsiderBlock(hash)
if err != nil {
if errors.Is(err, blockchain.ErrUnknownBlock) {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: fmt.Sprintf("Block not found: %v", hash),
}
}

// Use separate error code for failed validation.
allRuleErrs := func(err error) bool {
var rErr blockchain.RuleError
if !errors.As(err, &rErr) {
return false
}

var mErr blockchain.MultiError
if errors.As(err, &mErr) {
for _, e := range mErr {
if !errors.As(e, &rErr) {
return false
}
}
}

return true
}
if allRuleErrs(err) {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCReconsiderFailure,
Message: fmt.Sprintf("Reconsidering block %s led to one or "+
"more validation failures: %v", hash, err),
}
}

// Fall back to an internal error.
context := fmt.Sprintf("Error while reconsidering block %s", hash)
return nil, rpcInternalError(err.Error(), context)
}

return nil, nil
}

// handleRegenTemplate implements the regentemplate command.
func handleRegenTemplate(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
bt := s.cfg.BlockTemplater
Expand Down
14 changes: 14 additions & 0 deletions internal/rpcserver/rpcserverhandlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ type testRPCChain struct {
headerByHeight wire.BlockHeader
headerByHeightErr error
heightRangeFn func(startHeight, endHeight int64) ([]chainhash.Hash, error)
invalidateBlockErr error
isCurrent bool
liveTickets []chainhash.Hash
liveTicketsErr error
Expand All @@ -176,6 +177,7 @@ type testRPCChain struct {
missedTicketsErr error
nextThresholdState blockchain.ThresholdStateTuple
nextThresholdStateErr error
reconsiderBlockErr error
stateLastChangedHeight int64
stateLastChangedHeightErr error
ticketPoolValue dcrutil.Amount
Expand Down Expand Up @@ -321,6 +323,12 @@ func (c *testRPCChain) HeightRange(startHeight, endHeight int64) ([]chainhash.Ha
return c.heightRangeFn(startHeight, endHeight)
}

// InvalidateBlock returns a mocked error from manually invalidating a given
// block.
func (c *testRPCChain) InvalidateBlock(hash *chainhash.Hash) error {
return c.invalidateBlockErr
}

// IsCurrent returns a mocked bool representing whether or not the chain
// believes it is current.
func (c *testRPCChain) IsCurrent() bool {
Expand Down Expand Up @@ -368,6 +376,12 @@ func (c *testRPCChain) NextThresholdState(hash *chainhash.Hash, version uint32,
return c.nextThresholdState, c.nextThresholdStateErr
}

// ReconsiderBlock returns a mocked error from manually reconsidering a given
// block.
func (c *testRPCChain) ReconsiderBlock(hash *chainhash.Hash) error {
return c.reconsiderBlockErr
}

// StateLastChangedHeight returns a mocked height at which the provided
// consensus deployment agenda last changed state.
func (c *testRPCChain) StateLastChangedHeight(hash *chainhash.Hash, version uint32, deploymentID string) (int64, error) {
Expand Down
18 changes: 15 additions & 3 deletions internal/rpcserver/rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -724,15 +724,25 @@ var helpDescsEnUS = map[string]string{
"help--result0": "List of commands",
"help--result1": "Help for specified command",

// InvalidateBlockCmd help.
"invalidateblock--synopsis": "Permanently invalidates a block as if it had violated consensus rules.\n" +
"Use reconsiderblock to remove the invalid status.",
"invalidateblock-blockhash": "The hash of the block 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.",

// RebroadcastMissed help.
"rebroadcastmissed--synopsis": "Asks the daemon to rebroadcast missed votes.\n",
"rebroadcastmissed--synopsis": "Asks the daemon to rebroadcast missed votes.",

// RebroadcastWinnersCmd help.
"rebroadcastwinners--synopsis": "Asks the daemon to rebroadcast the winners of the voting lottery.",

// RebroadcastWinnerCmd help.
"rebroadcastwinners--synopsis": "Asks the daemon to rebroadcast the winners of the voting lottery.\n",
// ReconsiderBlockCmd help.
"reconsiderblock--synopsis": "Reconsiders a block for validation and best chain selection by removing any invalid status from it and its ancestors.\n" +
"Any descendants that are neither themselves marked as having failed validation, nor descendants of another such block, are also made eligibile for best chain selection.",
"reconsiderblock-blockhash": "The hash of the block to reconsider",

// SearchRawTransactionsCmd help.
"searchrawtransactions--synopsis": "Returns raw data for transactions involving the passed address.\n" +
Expand Down Expand Up @@ -1033,10 +1043,12 @@ var rpcResultTypes = map[types.Method][]interface{}{
"getwork": {(*types.GetWorkResult)(nil), (*bool)(nil)},
"getcoinsupply": {(*int64)(nil)},
"help": {(*string)(nil), (*string)(nil)},
"invalidateblock": nil,
"livetickets": {(*types.LiveTicketsResult)(nil)},
"missedtickets": {(*types.MissedTicketsResult)(nil)},
"node": nil,
"ping": nil,
"reconsiderblock": nil,
"regentemplate": nil,
"searchrawtransactions": {(*string)(nil), (*[]types.SearchRawTransactionsResult)(nil)},
"sendrawtransaction": {(*string)(nil)},
Expand Down

0 comments on commit 034e279

Please sign in to comment.