mirror of
https://github.com/btcsuite/btcd.git
synced 2025-02-22 22:25:45 +01:00
Merge pull request #2197 from Crypt-iQ/2183_eugene
main, rpcclient, integration: add rpccalls for invalidate and reconsiderblock
This commit is contained in:
commit
93e729194e
4 changed files with 327 additions and 2 deletions
244
integration/invalidate_reconsider_block_test.go
Normal file
244
integration/invalidate_reconsider_block_test.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/integration/rpctest"
|
||||
)
|
||||
|
||||
func TestInvalidateAndReconsiderBlock(t *testing.T) {
|
||||
// Set up regtest chain.
|
||||
r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, nil, "")
|
||||
if err != nil {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail."+
|
||||
"Unable to create primary harness: %v", err)
|
||||
}
|
||||
if err := r.SetUp(true, 0); err != nil {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Unable to setup test chain: %v", err)
|
||||
}
|
||||
defer r.TearDown()
|
||||
|
||||
// Generate 4 blocks.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4
|
||||
_, err = r.Client.Generate(4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Cache the active tip hash.
|
||||
block4ActiveTipHash, err := r.Client.GetBestBlockHash()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Cache block 1 hash as this will be our chaintip after we invalidate block 2.
|
||||
block1Hash, err := r.Client.GetBlockHash(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Invalidate block 2.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 (active)
|
||||
// \ -> 2 -> 3 -> 4 (invalid)
|
||||
block2Hash, err := r.Client.GetBlockHash(2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = r.Client.InvalidateBlock(block2Hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Assert that block 1 is the active chaintip.
|
||||
bestHash, err := r.Client.GetBestBlockHash()
|
||||
if *bestHash != *block1Hash {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected the "+
|
||||
"best block hash to be block 1 with hash %s but got %s",
|
||||
block1Hash.String(), bestHash.String())
|
||||
}
|
||||
|
||||
// Generate 2 blocks.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2a -> 3a (active)
|
||||
// \ -> 2 -> 3 -> 4 (invalid)
|
||||
_, err = r.Client.Generate(2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Cache the active tip hash for the current active tip.
|
||||
block3aActiveTipHash, err := r.Client.GetBestBlockHash()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tips, err := r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Assert that there are two branches.
|
||||
if len(tips) != 2 {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Expected 2 chaintips but got %d", len(tips))
|
||||
}
|
||||
|
||||
for _, tip := range tips {
|
||||
if tip.Hash == block4ActiveTipHash.String() &&
|
||||
tip.Status != "invalid" {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected "+
|
||||
"invalidated branch tip of %s to be invalid but got %s",
|
||||
tip.Hash, tip.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// Reconsider the invalidated block 2.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2a -> 3a (valid-fork)
|
||||
// \ -> 2 -> 3 -> 4 (active)
|
||||
err = r.Client.ReconsiderBlock(block2Hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Assert that there are two branches.
|
||||
if len(tips) != 2 {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Expected 2 chaintips but got %d", len(tips))
|
||||
}
|
||||
|
||||
var checkedTips int
|
||||
for _, tip := range tips {
|
||||
if tip.Hash == block4ActiveTipHash.String() {
|
||||
if tip.Status != "active" {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected "+
|
||||
"the reconsidered branch tip of %s to be active but got %s",
|
||||
tip.Hash, tip.Status)
|
||||
}
|
||||
|
||||
checkedTips++
|
||||
}
|
||||
|
||||
if tip.Hash == block3aActiveTipHash.String() {
|
||||
if tip.Status != "valid-fork" {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected "+
|
||||
"invalidated branch tip of %s to be valid-fork but got %s",
|
||||
tip.Hash, tip.Status)
|
||||
}
|
||||
checkedTips++
|
||||
}
|
||||
}
|
||||
|
||||
if checkedTips != 2 {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Expected to check %d chaintips, checked %d", 2, checkedTips)
|
||||
}
|
||||
|
||||
// Invalidate block 3a.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2a -> 3a (invalid)
|
||||
// \ -> 2 -> 3 -> 4 (active)
|
||||
err = r.Client.InvalidateBlock(block3aActiveTipHash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Assert that there are two branches.
|
||||
if len(tips) != 2 {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Expected 2 chaintips but got %d", len(tips))
|
||||
}
|
||||
|
||||
checkedTips = 0
|
||||
for _, tip := range tips {
|
||||
if tip.Hash == block4ActiveTipHash.String() {
|
||||
if tip.Status != "active" {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected "+
|
||||
"an active branch tip of %s but got %s",
|
||||
tip.Hash, tip.Status)
|
||||
}
|
||||
|
||||
checkedTips++
|
||||
}
|
||||
|
||||
if tip.Hash == block3aActiveTipHash.String() {
|
||||
if tip.Status != "invalid" {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected "+
|
||||
"the invalidated tip of %s to be invalid but got %s",
|
||||
tip.Hash, tip.Status)
|
||||
}
|
||||
checkedTips++
|
||||
}
|
||||
}
|
||||
|
||||
if checkedTips != 2 {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Expected to check %d chaintips, checked %d", 2, checkedTips)
|
||||
}
|
||||
|
||||
// Reconsider block 3a.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2a -> 3a (valid-fork)
|
||||
// \ -> 2 -> 3 -> 4 (active)
|
||||
err = r.Client.ReconsiderBlock(block3aActiveTipHash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Assert that there are two branches.
|
||||
if len(tips) != 2 {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Expected 2 chaintips but got %d", len(tips))
|
||||
}
|
||||
|
||||
checkedTips = 0
|
||||
for _, tip := range tips {
|
||||
if tip.Hash == block4ActiveTipHash.String() {
|
||||
if tip.Status != "active" {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected "+
|
||||
"an active branch tip of %s but got %s",
|
||||
tip.Hash, tip.Status)
|
||||
}
|
||||
|
||||
checkedTips++
|
||||
}
|
||||
|
||||
if tip.Hash == block3aActiveTipHash.String() {
|
||||
if tip.Status != "valid-fork" {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. Expected "+
|
||||
"the reconsidered tip of %s to be a valid-fork but got %s",
|
||||
tip.Hash, tip.Status)
|
||||
}
|
||||
checkedTips++
|
||||
}
|
||||
}
|
||||
|
||||
if checkedTips != 2 {
|
||||
t.Fatalf("TestInvalidateAndReconsiderBlock fail. "+
|
||||
"Expected to check %d chaintips, checked %d", 2, checkedTips)
|
||||
}
|
||||
}
|
|
@ -1419,3 +1419,38 @@ func (c *Client) GetDescriptorInfoAsync(descriptor string) FutureGetDescriptorIn
|
|||
func (c *Client) GetDescriptorInfo(descriptor string) (*btcjson.GetDescriptorInfoResult, error) {
|
||||
return c.GetDescriptorInfoAsync(descriptor).Receive()
|
||||
}
|
||||
|
||||
// FutureReconsiderBlockResult is a future promise to deliver the result of a
|
||||
// ReconsiderBlockAsync RPC invocation (or an applicable error).
|
||||
type FutureReconsiderBlockResult chan *Response
|
||||
|
||||
// Receive waits for the Response promised by the future and returns the raw
|
||||
// block requested from the server given its hash.
|
||||
func (r FutureReconsiderBlockResult) Receive() error {
|
||||
_, err := ReceiveFuture(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReconsiderBlockAsync returns an instance of a type that can be used to get the
|
||||
// result of the RPC at some future time by invoking the Receive function on the
|
||||
// returned instance.
|
||||
//
|
||||
// See ReconsiderBlock for the blocking version and more details.
|
||||
func (c *Client) ReconsiderBlockAsync(
|
||||
blockHash *chainhash.Hash) FutureReconsiderBlockResult {
|
||||
|
||||
hash := ""
|
||||
if blockHash != nil {
|
||||
hash = blockHash.String()
|
||||
}
|
||||
|
||||
cmd := btcjson.NewReconsiderBlockCmd(hash)
|
||||
return c.SendCmd(cmd)
|
||||
}
|
||||
|
||||
// ReconsiderBlock reconsiders an verifies a specific block and the branch that
|
||||
// the block is included in. If the block is valid on reconsideration, the chain
|
||||
// will reorg to that block if it has more PoW than the current tip.
|
||||
func (c *Client) ReconsiderBlock(blockHash *chainhash.Hash) error {
|
||||
return c.ReconsiderBlockAsync(blockHash).Receive()
|
||||
}
|
||||
|
|
40
rpcserver.go
40
rpcserver.go
|
@ -170,8 +170,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,
|
||||
|
@ -241,9 +243,7 @@ var rpcUnimplemented = map[string]struct{}{
|
|||
"getmempoolentry": {},
|
||||
"getnetworkinfo": {},
|
||||
"getwork": {},
|
||||
"invalidateblock": {},
|
||||
"preciousblock": {},
|
||||
"reconsiderblock": {},
|
||||
}
|
||||
|
||||
// Commands that are available to a limited user
|
||||
|
@ -284,6 +284,8 @@ var rpcLimited = map[string]struct{}{
|
|||
"getrawmempool": {},
|
||||
"getrawtransaction": {},
|
||||
"gettxout": {},
|
||||
"invalidateblock": {},
|
||||
"reconsiderblock": {},
|
||||
"searchrawtransactions": {},
|
||||
"sendrawtransaction": {},
|
||||
"submitblock": {},
|
||||
|
@ -2850,6 +2852,23 @@ 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)
|
||||
|
||||
invalidateHash, err := chainhash.NewHashFromStr(c.BlockHash)
|
||||
if err != nil {
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCDeserialization,
|
||||
Message: fmt.Sprintf("Failed to deserialize blockhash from string of %s",
|
||||
invalidateHash),
|
||||
}
|
||||
}
|
||||
|
||||
err = s.cfg.Chain.InvalidateBlock(invalidateHash)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// handleHelp implements the help command.
|
||||
func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
c := cmd.(*btcjson.HelpCmd)
|
||||
|
@ -3123,6 +3142,23 @@ func fetchMempoolTxnsForAddress(s *rpcServer, addr btcutil.Address, numToSkip, n
|
|||
return mpTxns[numToSkip:rangeEnd], numToSkip
|
||||
}
|
||||
|
||||
// handleReconsiderBlock implements the reconsiderblock command.
|
||||
func handleReconsiderBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
c := cmd.(*btcjson.ReconsiderBlockCmd)
|
||||
|
||||
reconsiderHash, err := chainhash.NewHashFromStr(c.BlockHash)
|
||||
if err != nil {
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCDeserialization,
|
||||
Message: fmt.Sprintf("Failed to deserialize blockhash from string of %s",
|
||||
reconsiderHash),
|
||||
}
|
||||
}
|
||||
|
||||
err = s.cfg.Chain.ReconsiderBlock(reconsiderHash)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// handleSearchRawTransactions implements the searchrawtransactions command.
|
||||
func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
// Respond with an error if the address index is not enabled.
|
||||
|
|
|
@ -544,6 +544,10 @@ var helpDescsEnUS = map[string]string{
|
|||
"gettxout-vout": "The index of the output",
|
||||
"gettxout-includemempool": "Include the mempool when true",
|
||||
|
||||
// InvalidateBlockCmd help.
|
||||
"invalidateblock--synopsis": "Invalidates the block of the given block hash. To re-validate the invalidated block, use the reconsiderblock rpc",
|
||||
"invalidateblock-blockhash": "The block hash of the block to invalidate",
|
||||
|
||||
// HelpCmd help.
|
||||
"help--synopsis": "Returns a list of all commands or help for a specified command.",
|
||||
"help-command": "The command to retrieve help for",
|
||||
|
@ -681,6 +685,10 @@ var helpDescsEnUS = map[string]string{
|
|||
"loadtxfilter-addresses": "Array of addresses to add to the transaction filter",
|
||||
"loadtxfilter-outpoints": "Array of outpoints to add to the transaction filter",
|
||||
|
||||
// ReconsiderBlockCmd help.
|
||||
"reconsiderblock--synopsis": "Reconsiders the block of the given block hash. Can be used to re-validate blocks invalidated with invalidateblock",
|
||||
"reconsiderblock-blockhash": "The block hash of the block to reconsider",
|
||||
|
||||
// Rescan help.
|
||||
"rescan--synopsis": "Rescan block chain for transactions to addresses.\n" +
|
||||
"When the endblock parameter is omitted, the rescan continues through the best block in the main chain.\n" +
|
||||
|
@ -788,7 +796,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,
|
||||
|
|
Loading…
Add table
Reference in a new issue