mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-12 10:30:49 +01:00
Merge pull request #1918 from kcalvinalvin/2022-11-06-implement-getchaintips
blockchain, btcjson: Implement getchaintips rpc call
This commit is contained in:
commit
f7e9fba086
9 changed files with 805 additions and 21 deletions
|
@ -377,6 +377,44 @@ func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) {
|
|||
bi.Unlock()
|
||||
}
|
||||
|
||||
// InactiveTips returns all the block nodes that aren't in the best chain.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) InactiveTips(bestChain *chainView) []*blockNode {
|
||||
bi.RLock()
|
||||
defer bi.RUnlock()
|
||||
|
||||
// Look through the entire blockindex and look for nodes that aren't in
|
||||
// the best chain. We're gonna keep track of all the orphans and the parents
|
||||
// of the orphans.
|
||||
orphans := make(map[chainhash.Hash]*blockNode)
|
||||
orphanParent := make(map[chainhash.Hash]*blockNode)
|
||||
for hash, node := range bi.index {
|
||||
found := bestChain.Contains(node)
|
||||
if !found {
|
||||
orphans[hash] = node
|
||||
orphanParent[node.parent.hash] = node.parent
|
||||
}
|
||||
}
|
||||
|
||||
// If an orphan isn't pointed to by another orphan, it is a chain tip.
|
||||
//
|
||||
// We can check this by looking for the orphan in the orphan parent map.
|
||||
// If the orphan exists in the orphan parent map, it means that another
|
||||
// orphan is pointing to it.
|
||||
tips := make([]*blockNode, 0, len(orphans))
|
||||
for hash, orphan := range orphans {
|
||||
_, found := orphanParent[hash]
|
||||
if !found {
|
||||
tips = append(tips, orphan)
|
||||
}
|
||||
|
||||
delete(orphanParent, hash)
|
||||
}
|
||||
|
||||
return tips
|
||||
}
|
||||
|
||||
// flushToDB writes all dirty block nodes to the database. If all writes
|
||||
// succeed, this clears the dirty set.
|
||||
func (bi *blockIndex) flushToDB() error {
|
||||
|
|
|
@ -1280,6 +1280,119 @@ func (b *BlockChain) BestSnapshot() *BestState {
|
|||
return snapshot
|
||||
}
|
||||
|
||||
// TipStatus is the status of a chain tip.
|
||||
type TipStatus byte
|
||||
|
||||
const (
|
||||
// StatusUnknown indicates that the tip status isn't any of the defined
|
||||
// statuses.
|
||||
StatusUnknown TipStatus = iota
|
||||
|
||||
// StatusActive indicates that the tip is considered active and is in
|
||||
// the best chain.
|
||||
StatusActive
|
||||
|
||||
// StatusInvalid indicates that this tip or any of the ancestors of this
|
||||
// tip are invalid.
|
||||
StatusInvalid
|
||||
|
||||
// StatusValidFork is given if:
|
||||
// 1: Not a part of the best chain.
|
||||
// 2: Is not invalid.
|
||||
// 3: Has the block data stored to disk.
|
||||
StatusValidFork
|
||||
)
|
||||
|
||||
// String returns the status flags as string.
|
||||
func (ts TipStatus) String() string {
|
||||
switch ts {
|
||||
case StatusActive:
|
||||
return "active"
|
||||
case StatusInvalid:
|
||||
return "invalid"
|
||||
case StatusValidFork:
|
||||
return "valid-fork"
|
||||
}
|
||||
return fmt.Sprintf("unknown: %b", ts)
|
||||
}
|
||||
|
||||
// ChainTip represents the last block in a branch of the block tree.
|
||||
type ChainTip struct {
|
||||
// Height of the tip.
|
||||
Height int32
|
||||
|
||||
// BlockHash hash of the tip.
|
||||
BlockHash chainhash.Hash
|
||||
|
||||
// BranchLen is length of the fork point of this chain from the main chain.
|
||||
// Returns 0 if the chain tip is a part of the best chain.
|
||||
BranchLen int32
|
||||
|
||||
// Status is the validity status of the branch this tip is in.
|
||||
Status TipStatus
|
||||
}
|
||||
|
||||
// ChainTips returns all the chain tips the node itself is aware of. Each tip is
|
||||
// represented by its height, block hash, branch length, and status.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) ChainTips() []ChainTip {
|
||||
b.chainLock.RLock()
|
||||
defer b.chainLock.RUnlock()
|
||||
|
||||
// Grab all the inactive tips.
|
||||
tips := b.index.InactiveTips(b.bestChain)
|
||||
|
||||
// Add the current tip.
|
||||
tips = append(tips, b.bestChain.Tip())
|
||||
|
||||
chainTips := make([]ChainTip, 0, len(tips))
|
||||
|
||||
// Go through all the tips and grab the height, hash, branch length, and the block
|
||||
// status.
|
||||
for _, tip := range tips {
|
||||
var status TipStatus
|
||||
switch {
|
||||
// The tip is considered active if it's in the best chain.
|
||||
case b.bestChain.Contains(tip):
|
||||
status = StatusActive
|
||||
|
||||
// This block or any of the ancestors of this block are invalid.
|
||||
case tip.status.KnownInvalid():
|
||||
status = StatusInvalid
|
||||
|
||||
// If the tip meets the following criteria:
|
||||
// 1: Not a part of the best chain.
|
||||
// 2: Is not invalid.
|
||||
// 3: Has the block data stored to disk.
|
||||
//
|
||||
// The tip is considered a valid fork.
|
||||
//
|
||||
// We can check if a tip is a valid-fork by checking that
|
||||
// its data is available. Since the behavior is to give a
|
||||
// block node the statusDataStored status once it passes
|
||||
// the proof of work checks and basic chain validity checks.
|
||||
//
|
||||
// We can't use the KnownValid status since it's only given
|
||||
// to blocks that passed the validation AND were a part of
|
||||
// the bestChain.
|
||||
case tip.status.HaveData():
|
||||
status = StatusValidFork
|
||||
}
|
||||
|
||||
chainTip := ChainTip{
|
||||
Height: tip.height,
|
||||
BlockHash: tip.hash,
|
||||
BranchLen: tip.height - b.bestChain.FindFork(tip).height,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
chainTips = append(chainTips, chainTip)
|
||||
}
|
||||
|
||||
return chainTips
|
||||
}
|
||||
|
||||
// HeaderByHash returns the block header identified by the given hash or an
|
||||
// error if it doesn't exist. Note that this will return headers from both the
|
||||
// main and side chains.
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -964,3 +965,193 @@ func TestIntervalBlockHashes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChainTips(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
chainTipGen func() (*BlockChain, map[chainhash.Hash]ChainTip)
|
||||
}{
|
||||
{
|
||||
name: "one active chain tip",
|
||||
chainTipGen: func() (*BlockChain, map[chainhash.Hash]ChainTip) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> 3
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 3)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.SetStatusFlags(node, statusDataStored)
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
activeTip := ChainTip{
|
||||
Height: 3,
|
||||
BlockHash: (tip(branch0Nodes)).hash,
|
||||
BranchLen: 0,
|
||||
Status: StatusActive,
|
||||
}
|
||||
chainTips := make(map[chainhash.Hash]ChainTip)
|
||||
chainTips[activeTip.BlockHash] = activeTip
|
||||
|
||||
return chain, chainTips
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one active chain tip, one unknown chain tip",
|
||||
chainTipGen: func() (*BlockChain, map[chainhash.Hash]ChainTip) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> 3 ... -> 10 -> 11 -> 12 -> 13 (active)
|
||||
// \-> 11a -> 12a (unknown)
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 13)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.SetStatusFlags(node, statusDataStored)
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
branch1Nodes := chainedNodes(branch0Nodes[9], 2)
|
||||
for _, node := range branch1Nodes {
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
|
||||
activeTip := ChainTip{
|
||||
Height: 13,
|
||||
BlockHash: (tip(branch0Nodes)).hash,
|
||||
BranchLen: 0,
|
||||
Status: StatusActive,
|
||||
}
|
||||
unknownTip := ChainTip{
|
||||
Height: 12,
|
||||
BlockHash: (tip(branch1Nodes)).hash,
|
||||
BranchLen: 2,
|
||||
Status: StatusUnknown,
|
||||
}
|
||||
chainTips := make(map[chainhash.Hash]ChainTip)
|
||||
chainTips[activeTip.BlockHash] = activeTip
|
||||
chainTips[unknownTip.BlockHash] = unknownTip
|
||||
|
||||
return chain, chainTips
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 inactive tip, 1 invalid tip, 1 active tip",
|
||||
chainTipGen: func() (*BlockChain, map[chainhash.Hash]ChainTip) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> 3 (active)
|
||||
// \ -> 1a (valid-fork)
|
||||
// \ -> 1b (invalid)
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 3)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.SetStatusFlags(node, statusDataStored)
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
branch1Nodes := chainedNodes(chain.bestChain.Genesis(), 1)
|
||||
for _, node := range branch1Nodes {
|
||||
chain.index.SetStatusFlags(node, statusDataStored)
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
|
||||
branch2Nodes := chainedNodes(chain.bestChain.Genesis(), 1)
|
||||
for _, node := range branch2Nodes {
|
||||
chain.index.SetStatusFlags(node, statusDataStored)
|
||||
chain.index.SetStatusFlags(node, statusValidateFailed)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
|
||||
activeTip := ChainTip{
|
||||
Height: tip(branch0Nodes).height,
|
||||
BlockHash: (tip(branch0Nodes)).hash,
|
||||
BranchLen: 0,
|
||||
Status: StatusActive,
|
||||
}
|
||||
|
||||
inactiveTip := ChainTip{
|
||||
Height: tip(branch1Nodes).height,
|
||||
BlockHash: (tip(branch1Nodes)).hash,
|
||||
BranchLen: 1,
|
||||
Status: StatusValidFork,
|
||||
}
|
||||
|
||||
invalidTip := ChainTip{
|
||||
Height: tip(branch2Nodes).height,
|
||||
BlockHash: (tip(branch2Nodes)).hash,
|
||||
BranchLen: 1,
|
||||
Status: StatusInvalid,
|
||||
}
|
||||
|
||||
chainTips := make(map[chainhash.Hash]ChainTip)
|
||||
chainTips[activeTip.BlockHash] = activeTip
|
||||
chainTips[inactiveTip.BlockHash] = inactiveTip
|
||||
chainTips[invalidTip.BlockHash] = invalidTip
|
||||
|
||||
return chain, chainTips
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
chain, expectedChainTips := test.chainTipGen()
|
||||
gotChainTips := chain.ChainTips()
|
||||
if len(gotChainTips) != len(expectedChainTips) {
|
||||
t.Errorf("TestChainTips Failed test %s. Expected %d "+
|
||||
"chain tips, got %d", test.name, len(expectedChainTips), len(gotChainTips))
|
||||
}
|
||||
|
||||
for _, gotChainTip := range gotChainTips {
|
||||
testChainTip, found := expectedChainTips[gotChainTip.BlockHash]
|
||||
if !found {
|
||||
t.Errorf("TestChainTips Failed test %s. Couldn't find an expected "+
|
||||
"chain tip with height %d, hash %s, branchlen %d, status \"%s\"",
|
||||
test.name, testChainTip.Height, testChainTip.BlockHash.String(),
|
||||
testChainTip.BranchLen, testChainTip.Status.String())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(testChainTip, gotChainTip) {
|
||||
t.Errorf("TestChainTips Failed test %s. Expected chain tip with "+
|
||||
"height %d, hash %s, branchlen %d, status \"%s\" but got "+
|
||||
"height %d, hash %s, branchlen %d, status \"%s\"", test.name,
|
||||
testChainTip.Height, testChainTip.BlockHash.String(),
|
||||
testChainTip.BranchLen, testChainTip.Status.String(),
|
||||
gotChainTip.Height, gotChainTip.BlockHash.String(),
|
||||
gotChainTip.BranchLen, gotChainTip.Status.String())
|
||||
}
|
||||
|
||||
switch testChainTip.Status {
|
||||
case StatusActive:
|
||||
if testChainTip.Status.String() != "active" {
|
||||
t.Errorf("TestChainTips Fail: Expected string of \"active\", got \"%s\"",
|
||||
testChainTip.Status.String())
|
||||
}
|
||||
case StatusInvalid:
|
||||
if testChainTip.Status.String() != "invalid" {
|
||||
t.Errorf("TestChainTips Fail: Expected string of \"invalid\", got \"%s\"",
|
||||
testChainTip.Status.String())
|
||||
}
|
||||
case StatusValidFork:
|
||||
if testChainTip.Status.String() != "valid-fork" {
|
||||
t.Errorf("TestChainTips Fail: Expected string of \"valid-fork\", got \"%s\"",
|
||||
testChainTip.Status.String())
|
||||
}
|
||||
case StatusUnknown:
|
||||
if testChainTip.Status.String() != fmt.Sprintf("unknown: %b", testChainTip.Status) {
|
||||
t.Errorf("TestChainTips Fail: Expected string of \"unknown\", got \"%s\"",
|
||||
testChainTip.Status.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,14 @@ type GetBlockVerboseTxResult struct {
|
|||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
}
|
||||
|
||||
// GetChainTipsResult models the data from the getchaintips command.
|
||||
type GetChainTipsResult struct {
|
||||
Height int32 `json:"height"`
|
||||
Hash string `json:"hash"`
|
||||
BranchLen int32 `json:"branchlen"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// GetChainTxStatsResult models the data from the getchaintxstats command.
|
||||
type GetChainTxStatsResult struct {
|
||||
Time int64 `json:"time"`
|
||||
|
|
|
@ -168,26 +168,27 @@ the method name for further details such as parameter and return information.
|
|||
|8|[getblockcount](#getblockcount)|Y|Returns the number of blocks in the longest block chain.|
|
||||
|9|[getblockhash](#getblockhash)|Y|Returns hash of the block in best block chain at the given height.|
|
||||
|10|[getblockheader](#getblockheader)|Y|Returns the block header of the block.|
|
||||
|11|[getconnectioncount](#getconnectioncount)|N|Returns the number of active connections to other peers.|
|
||||
|12|[getdifficulty](#getdifficulty)|Y|Returns the proof-of-work difficulty as a multiple of the minimum difficulty.|
|
||||
|13|[getgenerate](#getgenerate)|N|Return if the server is set to generate coins (mine) or not.|
|
||||
|14|[gethashespersec](#gethashespersec)|N|Returns a recent hashes per second performance measurement while generating coins (mining).|
|
||||
|15|[getinfo](#getinfo)|Y|Returns a JSON object containing various state info.|
|
||||
|16|[getmempoolinfo](#getmempoolinfo)|N|Returns a JSON object containing mempool-related information.|
|
||||
|17|[getmininginfo](#getmininginfo)|N|Returns a JSON object containing mining-related information.|
|
||||
|18|[getnettotals](#getnettotals)|Y|Returns a JSON object containing network traffic statistics.|
|
||||
|19|[getnetworkhashps](#getnetworkhashps)|Y|Returns the estimated network hashes per second for the block heights provided by the parameters.|
|
||||
|20|[getpeerinfo](#getpeerinfo)|N|Returns information about each connected network peer as an array of json objects.|
|
||||
|21|[getrawmempool](#getrawmempool)|Y|Returns an array of hashes for all of the transactions currently in the memory pool.|
|
||||
|22|[getrawtransaction](#getrawtransaction)|Y|Returns information about a transaction given its hash.|
|
||||
|23|[help](#help)|Y|Returns a list of all commands or help for a specified command.|
|
||||
|24|[ping](#ping)|N|Queues a ping to be sent to each connected peer.|
|
||||
|25|[sendrawtransaction](#sendrawtransaction)|Y|Submits the serialized, hex-encoded transaction to the local peer and relays it to the network.<br /><font color="orange">btcd does not yet implement the `allowhighfees` parameter, so it has no effect</font>|
|
||||
|26|[setgenerate](#setgenerate) |N|Set the server to generate coins (mine) or not.<br/>NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the `--miningaddr` option to provide which payment addresses to pay created blocks to for this RPC to function.|
|
||||
|27|[stop](#stop)|N|Shutdown btcd.|
|
||||
|28|[submitblock](#submitblock)|Y|Attempts to submit a new serialized, hex-encoded block to the network.|
|
||||
|29|[validateaddress](#validateaddress)|Y|Verifies the given address is valid. NOTE: Since btcd does not have a wallet integrated, btcd will only return whether the address is valid or not.|
|
||||
|30|[verifychain](#verifychain)|N|Verifies the block chain database.|
|
||||
|11|[getchaintips](#getchaintips)|Y|Returns information about all known tips in the block tree, including the main chain as well as orphaned branches.|
|
||||
|12|[getconnectioncount](#getconnectioncount)|N|Returns the number of active connections to other peers.|
|
||||
|13|[getdifficulty](#getdifficulty)|Y|Returns the proof-of-work difficulty as a multiple of the minimum difficulty.|
|
||||
|14|[getgenerate](#getgenerate)|N|Return if the server is set to generate coins (mine) or not.|
|
||||
|15|[gethashespersec](#gethashespersec)|N|Returns a recent hashes per second performance measurement while generating coins (mining).|
|
||||
|16|[getinfo](#getinfo)|Y|Returns a JSON object containing various state info.|
|
||||
|17|[getmempoolinfo](#getmempoolinfo)|N|Returns a JSON object containing mempool-related information.|
|
||||
|18|[getmininginfo](#getmininginfo)|N|Returns a JSON object containing mining-related information.|
|
||||
|19|[getnettotals](#getnettotals)|Y|Returns a JSON object containing network traffic statistics.|
|
||||
|20|[getnetworkhashps](#getnetworkhashps)|Y|Returns the estimated network hashes per second for the block heights provided by the parameters.|
|
||||
|21|[getpeerinfo](#getpeerinfo)|N|Returns information about each connected network peer as an array of json objects.|
|
||||
|22|[getrawmempool](#getrawmempool)|Y|Returns an array of hashes for all of the transactions currently in the memory pool.|
|
||||
|23|[getrawtransaction](#getrawtransaction)|Y|Returns information about a transaction given its hash.|
|
||||
|24|[help](#help)|Y|Returns a list of all commands or help for a specified command.|
|
||||
|25|[ping](#ping)|N|Queues a ping to be sent to each connected peer.|
|
||||
|26|[sendrawtransaction](#sendrawtransaction)|Y|Submits the serialized, hex-encoded transaction to the local peer and relays it to the network.<br /><font color="orange">btcd does not yet implement the `allowhighfees` parameter, so it has no effect</font>|
|
||||
|27|[setgenerate](#setgenerate) |N|Set the server to generate coins (mine) or not.<br/>NOTE: Since btcd does not have the wallet integrated to provide payment addresses, btcd must be configured via the `--miningaddr` option to provide which payment addresses to pay created blocks to for this RPC to function.|
|
||||
|28|[stop](#stop)|N|Shutdown btcd.|
|
||||
|29|[submitblock](#submitblock)|Y|Attempts to submit a new serialized, hex-encoded block to the network.|
|
||||
|30|[validateaddress](#validateaddress)|Y|Verifies the given address is valid. NOTE: Since btcd does not have a wallet integrated, btcd will only return whether the address is valid or not.|
|
||||
|31|[verifychain](#verifychain)|N|Verifies the block chain database.|
|
||||
|
||||
<a name="MethodDetails" />
|
||||
|
||||
|
@ -319,6 +320,18 @@ the method name for further details such as parameter and return information.
|
|||
|Example Return (verbose=true)|`{`<br /> `"hash": "00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e",`<br /> `"confirmations": 392076,`<br /> `"height": 100000,`<br /> `"version": 2,`<br /> `"merkleroot": "d574f343976d8e70d91cb278d21044dd8a396019e6db70755a0a50e4783dba38",`<br /> `"time": 1376123972,`<br /> `"nonce": 1005240617,`<br /> `"bits": "1c00f127",`<br /> `"difficulty": 271.75767393,`<br /> `"previousblockhash": "000000004956cc2edd1a8caa05eacfa3c69f4c490bfc9ace820257834115ab35",`<br /> `"nextblockhash": "0000000000629d100db387f37d0f37c51118f250fb0946310a8c37316cbc4028"`<br />`}`|
|
||||
[Return to Overview](#MethodOverview)<br />
|
||||
|
||||
***
|
||||
<a name="getchaintips"/>
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|Method|getchaintips|
|
||||
|Parameters|None|
|
||||
|Description|Returns information about all known tips in the block tree, including the main chain as well as orphaned branches|
|
||||
|Returns|`(A json object array)`<br />`height`: `(numeric)` The height of the chain tip.<br />`hash`: `(string)` The block hash of the chain tip.<br />`branchlen`: `(numeric)` Returns zero for main chain. Otherwise is the length of branch connecting the tip to the main chain.<br />`status`: `(string)` Status of the chain. Returns "active" for the main chain.`|
|
||||
|Example Return|`["{"height": 1, "hash": "78b945a390c561cf8b9ccf0598be15d7d85c67022bf71083c0b0bd8042fc30d7", "branchlen": 1, "status": "valid-fork"}, {"height": 1, "hash": "584c830a4783c6331e59cb984686cfec14bccc596fe8bbd1660b90cda359b42a", "branchlen": 0, "status": "active"}"]`|
|
||||
[Return to Overview](#MethodOverview)<br />
|
||||
|
||||
***
|
||||
<a name="getconnectioncount"/>
|
||||
|
||||
|
|
350
integration/getchaintips_test.go
Normal file
350
integration/getchaintips_test.go
Normal file
|
@ -0,0 +1,350 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/integration/rpctest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getBlockFromString(t *testing.T, hexStr string) *btcutil.Block {
|
||||
t.Helper()
|
||||
|
||||
serializedBlock, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't decode hex string of %s", hexStr)
|
||||
}
|
||||
|
||||
block, err := btcutil.NewBlockFromBytes(serializedBlock)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't make a new block from bytes. "+
|
||||
"Decoded hex string: %s", hexStr)
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
// compareMultipleChainTips checks that all the expected chain tips are included in got chain tips and
|
||||
// verifies that the got chain tip matches the expected chain tip.
|
||||
func compareMultipleChainTips(t *testing.T, gotChainTips, expectedChainTips []*btcjson.GetChainTipsResult) error {
|
||||
if len(gotChainTips) != len(expectedChainTips) {
|
||||
return fmt.Errorf("Expected %d chaintips but got %d", len(expectedChainTips), len(gotChainTips))
|
||||
}
|
||||
|
||||
gotChainTipsMap := make(map[string]btcjson.GetChainTipsResult)
|
||||
for _, gotChainTip := range gotChainTips {
|
||||
gotChainTipsMap[gotChainTip.Hash] = *gotChainTip
|
||||
}
|
||||
|
||||
for _, expectedChainTip := range expectedChainTips {
|
||||
gotChainTip, found := gotChainTipsMap[expectedChainTip.Hash]
|
||||
if !found {
|
||||
return fmt.Errorf("Couldn't find expected chaintip with hash %s", expectedChainTip.Hash)
|
||||
}
|
||||
|
||||
require.Equal(t, gotChainTip, *expectedChainTip)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGetChainTips(t *testing.T) {
|
||||
// block1Hex is a block that builds on top of the regtest genesis block.
|
||||
// Has blockhash of "36c056247e8c0589f6307995e4e13acf2b2b79cad9ecd5a4eeab2131ed0ecde5".
|
||||
block1Hex := "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf18891" +
|
||||
"0f71881025ae0d41ce8748b79ac40e5f3197af3bb83a594def7943aff0fce504c638ea6d63f" +
|
||||
"fff7f2000000000010200000000010100000000000000000000000000000000000000000000" +
|
||||
"00000000000000000000ffffffff025100ffffffff0200f2052a010000001600149b0f9d020" +
|
||||
"8b3b425246e16830562a63bf1c701180000000000000000266a24aa21a9ede2f61c3f71d1de" +
|
||||
"fd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block2Hex is a block that builds on top of block1Hex.
|
||||
// Has blockhash of "664b51334782a4ad16e8471b530dcd0027c75b8c25187b41dfc85ecd353295c6".
|
||||
block2Hex := "00000020e5cd0eed3121abeea4d5ecd9ca792b2bcf3ae1e4957930f689058c7e2456c0" +
|
||||
"362a78a11b875d31af2ea493aa5b6b623e0d481f11e69f7147ab974be9da087f3e24696f63f" +
|
||||
"fff7f2001000000010200000000010100000000000000000000000000000000000000000000" +
|
||||
"00000000000000000000ffffffff025200ffffffff0200f2052a0100000016001470fea1feb" +
|
||||
"4969c1f237753ae29c0217c6637835c0000000000000000266a24aa21a9ede2f61c3f71d1de" +
|
||||
"fd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block3Hex is a block that builds on top of block2Hex.
|
||||
// Has blockhash of "17a5c5cb90ecde5a46dd195d434eea46b653e35e4517070eade429db3ac83944".
|
||||
block3Hex := "00000020c6953235cd5ec8df417b18258c5bc72700cd0d531b47e816ada4824733514b" +
|
||||
"66c3ad4d567a36c20df07ea0b7fce1e4b4ee5be3eaf0b946b0ae73f3a74d47f0cf99696f63f" +
|
||||
"fff7f2000000000010200000000010100000000000000000000000000000000000000000000" +
|
||||
"00000000000000000000ffffffff025300ffffffff0200f2052a010000001600140e835869b" +
|
||||
"154f647d11376634b5e8c785e7d21060000000000000000266a24aa21a9ede2f61c3f71d1de" +
|
||||
"fd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block4Hex is a block that builds on top of block3Hex.
|
||||
// Has blockhash of "7b357f3073c4397d6d069a32a09141c32560f3c62233ca138eb5e03c5991f45c".
|
||||
block4Hex := "000000204439c83adb29e4ad0e0717455ee353b646ea4e435d19dd465adeec90cbc5a5" +
|
||||
"17ab639a5dd622e90f5f9feffc1c7c28f47a2caf85c21d7dd52cd223a7164619e37a6a6f63f" +
|
||||
"fff7f2004000000010200000000010100000000000000000000000000000000000000000000" +
|
||||
"00000000000000000000ffffffff025400ffffffff0200f2052a01000000160014a157c74b4" +
|
||||
"42a3e11b45cf5273f8c0c032c5a40ed0000000000000000266a24aa21a9ede2f61c3f71d1de" +
|
||||
"fd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000" +
|
||||
"000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block2aHex is a block that builds on top of block1Hex.
|
||||
// Has blockhash of "5181a4e34cc23ed95c69749dedf4cc7ebd659243bc1683372f8940c8cd8f9b68".
|
||||
block2aHex := "00000020e5cd0eed3121abeea4d5ecd9ca792b2bcf3ae1e4957930f689058c7e2456c" +
|
||||
"036f7d4ebe524260c9b6c2b5e3d105cad0b7ddfaeaa29971363574fc1921a3f2f7ad66b6f63" +
|
||||
"ffff7f200100000001020000000001010000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000ffffffff025200ffffffff0200f2052a0100000016001466fca22d" +
|
||||
"0e4679d119ea1e127c984746a1f7e66c0000000000000000266a24aa21a9ede2f61c3f71d1d" +
|
||||
"efd3fa999dfa36953755c690689799962b48bebd836974e8cf9012000000000000000000000" +
|
||||
"0000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block3aHex is a block that builds on top of block2aHex.
|
||||
// Has blockhash of "0b0216936d1a5c01362256d06a9c9a2b13768fa2f2748549a71008af36dd167f".
|
||||
block3aHex := "00000020689b8fcdc840892f378316bc439265bd7eccf4ed9d74695cd93ec24ce3a48" +
|
||||
"15161a430ce5cae955b1254b753bc95854d942947855d3ae59002de9773b7fe65fdf16b6f63" +
|
||||
"ffff7f200100000001020000000001010000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000ffffffff025300ffffffff0200f2052a0100000016001471da0afb" +
|
||||
"883c228b18af6bd0cabc471aebe8d1750000000000000000266a24aa21a9ede2f61c3f71d1d" +
|
||||
"efd3fa999dfa36953755c690689799962b48bebd836974e8cf9012000000000000000000000" +
|
||||
"0000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block4aHex is a block that builds on top of block3aHex.
|
||||
// Has blockhash of "65a00a026eaa83f6e7a7f4a920faa090f3f9d3565a56df2362db2ab2fa14ccec".
|
||||
block4aHex := "000000207f16dd36af0810a7498574f2a28f76132b9a9c6ad0562236015c1a6d93160" +
|
||||
"20b951fa5ee5072d88d6aef9601999307dbd8d96dad067b80bfe04afe81c7a8c21beb706f63" +
|
||||
"ffff7f200000000001020000000001010000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000ffffffff025400ffffffff0200f2052a01000000160014fd1f118c" +
|
||||
"95a712b8adef11c3cc0643bcb6b709f10000000000000000266a24aa21a9ede2f61c3f71d1d" +
|
||||
"efd3fa999dfa36953755c690689799962b48bebd836974e8cf9012000000000000000000000" +
|
||||
"0000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block5aHex is a block that builds on top of block4aHex.
|
||||
// Has blockhash of "5c8814bc034a4c37fa5ccdc05e09b45a771bd7505d68092f21869a912737ee10".
|
||||
block5aHex := "00000020eccc14fab22adb6223df565a56d3f9f390a0fa20a9f4a7e7f683aa6e020aa" +
|
||||
"0656331bd4fcd3db611de7fbf72ef3dff0b85b244b5a983d5c0270e728214f67f9aaa766f63" +
|
||||
"ffff7f200600000001020000000001010000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000ffffffff025500ffffffff0200f2052a0100000016001438335896" +
|
||||
"ad1d087e3541436a5b293c0d23ad27e60000000000000000266a24aa21a9ede2f61c3f71d1d" +
|
||||
"efd3fa999dfa36953755c690689799962b48bebd836974e8cf9012000000000000000000000" +
|
||||
"0000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// block4bHex is a block that builds on top of block3aHex.
|
||||
// Has blockhash of "130458e795cc46f2759195e92737426fb0ada2a07f98434551ffb7500b23c161".
|
||||
block4bHex := "000000207f16dd36af0810a7498574f2a28f76132b9a9c6ad0562236015c1a6d93160" +
|
||||
"20b14f9ce93d0144c383fea72f408b06b268a1523a029b825a1edfa15b367f6db2cfd7d6f63" +
|
||||
"ffff7f200200000001020000000001010000000000000000000000000000000000000000000" +
|
||||
"000000000000000000000ffffffff025400ffffffff0200f2052a0100000016001405b5ba2d" +
|
||||
"1e549c4c84a623de3575948d3ef8a27f0000000000000000266a24aa21a9ede2f61c3f71d1d" +
|
||||
"efd3fa999dfa36953755c690689799962b48bebd836974e8cf9012000000000000000000000" +
|
||||
"0000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// Set up regtest chain.
|
||||
r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal("TestGetChainTips fail. Unable to create primary harness: ", err)
|
||||
}
|
||||
if err := r.SetUp(true, 0); err != nil {
|
||||
t.Fatalf("TestGetChainTips fail. Unable to setup test chain: %v", err)
|
||||
}
|
||||
defer r.TearDown()
|
||||
|
||||
// Immediately call getchaintips after setting up regtest.
|
||||
gotChainTips, err := r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// We expect a single genesis block.
|
||||
expectedChainTips := []*btcjson.GetChainTipsResult{
|
||||
{
|
||||
Height: 0,
|
||||
Hash: chaincfg.RegressionNetParams.GenesisHash.String(),
|
||||
BranchLen: 0,
|
||||
Status: "active",
|
||||
},
|
||||
}
|
||||
err = compareMultipleChainTips(t, gotChainTips, expectedChainTips)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetChainTips fail. Error: %v", err)
|
||||
}
|
||||
|
||||
// Submit 4 blocks.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4
|
||||
blockStrings := []string{block1Hex, block2Hex, block3Hex, block4Hex}
|
||||
for _, blockString := range blockStrings {
|
||||
block := getBlockFromString(t, blockString)
|
||||
err = r.Client.SubmitBlock(block, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
gotChainTips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedChainTips = []*btcjson.GetChainTipsResult{
|
||||
{
|
||||
Height: 4,
|
||||
Hash: getBlockFromString(t, blockStrings[len(blockStrings)-1]).Hash().String(),
|
||||
BranchLen: 0,
|
||||
Status: "active",
|
||||
},
|
||||
}
|
||||
err = compareMultipleChainTips(t, gotChainTips, expectedChainTips)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetChainTips fail. Error: %v", err)
|
||||
}
|
||||
|
||||
// Submit 2 blocks that don't build on top of the current active tip.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4 (active)
|
||||
// \ -> 2a -> 3a (valid-fork)
|
||||
blockStrings = []string{block2aHex, block3aHex}
|
||||
for _, blockString := range blockStrings {
|
||||
block := getBlockFromString(t, blockString)
|
||||
err = r.Client.SubmitBlock(block, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
gotChainTips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedChainTips = []*btcjson.GetChainTipsResult{
|
||||
{
|
||||
Height: 4,
|
||||
Hash: getBlockFromString(t, block4Hex).Hash().String(),
|
||||
BranchLen: 0,
|
||||
Status: "active",
|
||||
},
|
||||
{
|
||||
Height: 3,
|
||||
Hash: getBlockFromString(t, block3aHex).Hash().String(),
|
||||
BranchLen: 2,
|
||||
Status: "valid-fork",
|
||||
},
|
||||
}
|
||||
err = compareMultipleChainTips(t, gotChainTips, expectedChainTips)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetChainTips fail. Error: %v", err)
|
||||
}
|
||||
|
||||
// Submit a single block that don't build on top of the current active tip.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4 (active)
|
||||
// \ -> 2a -> 3a -> 4a (valid-fork)
|
||||
block := getBlockFromString(t, block4aHex)
|
||||
err = r.Client.SubmitBlock(block, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gotChainTips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedChainTips = []*btcjson.GetChainTipsResult{
|
||||
{
|
||||
Height: 4,
|
||||
Hash: getBlockFromString(t, block4Hex).Hash().String(),
|
||||
BranchLen: 0,
|
||||
Status: "active",
|
||||
},
|
||||
{
|
||||
Height: 4,
|
||||
Hash: getBlockFromString(t, block4aHex).Hash().String(),
|
||||
BranchLen: 3,
|
||||
Status: "valid-fork",
|
||||
},
|
||||
}
|
||||
err = compareMultipleChainTips(t, gotChainTips, expectedChainTips)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetChainTips fail. Error: %v", err)
|
||||
}
|
||||
|
||||
// Submit a single block that changes the active branch to 5a.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4 (valid-fork)
|
||||
// \ -> 2a -> 3a -> 4a -> 5a (active)
|
||||
block = getBlockFromString(t, block5aHex)
|
||||
err = r.Client.SubmitBlock(block, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotChainTips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedChainTips = []*btcjson.GetChainTipsResult{
|
||||
{
|
||||
Height: 4,
|
||||
Hash: getBlockFromString(t, block4Hex).Hash().String(),
|
||||
BranchLen: 3,
|
||||
Status: "valid-fork",
|
||||
},
|
||||
{
|
||||
Height: 5,
|
||||
Hash: getBlockFromString(t, block5aHex).Hash().String(),
|
||||
BranchLen: 0,
|
||||
Status: "active",
|
||||
},
|
||||
}
|
||||
err = compareMultipleChainTips(t, gotChainTips, expectedChainTips)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetChainTips fail. Error: %v", err)
|
||||
}
|
||||
|
||||
// Submit a single block that builds on top of 3a.
|
||||
//
|
||||
// Our chain view looks like so:
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4 (valid-fork)
|
||||
// \ -> 2a -> 3a -> 4a -> 5a (active)
|
||||
// \ -> 4b (valid-fork)
|
||||
block = getBlockFromString(t, block4bHex)
|
||||
err = r.Client.SubmitBlock(block, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotChainTips, err = r.Client.GetChainTips()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedChainTips = []*btcjson.GetChainTipsResult{
|
||||
{
|
||||
Height: 4,
|
||||
Hash: getBlockFromString(t, block4Hex).Hash().String(),
|
||||
BranchLen: 3,
|
||||
Status: "valid-fork",
|
||||
},
|
||||
{
|
||||
Height: 5,
|
||||
Hash: getBlockFromString(t, block5aHex).Hash().String(),
|
||||
BranchLen: 0,
|
||||
Status: "active",
|
||||
},
|
||||
{
|
||||
Height: 4,
|
||||
Hash: getBlockFromString(t, block4bHex).Hash().String(),
|
||||
BranchLen: 1,
|
||||
Status: "valid-fork",
|
||||
},
|
||||
}
|
||||
|
||||
err = compareMultipleChainTips(t, gotChainTips, expectedChainTips)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetChainTips fail. Error: %v", err)
|
||||
}
|
||||
}
|
|
@ -685,6 +685,44 @@ func (c *Client) GetBlockHeaderVerbose(blockHash *chainhash.Hash) (*btcjson.GetB
|
|||
return c.GetBlockHeaderVerboseAsync(blockHash).Receive()
|
||||
}
|
||||
|
||||
// FutureGetChainTipsResult is a future promise to deliver the result of a
|
||||
// GetChainTips RPC invocation (or an applicable error).
|
||||
type FutureGetChainTipsResult chan *Response
|
||||
|
||||
// Receive waits for the Response promised by the future and returns the
|
||||
// data structure of all the chain tips the node is aware of.
|
||||
func (r FutureGetChainTipsResult) Receive() ([]*btcjson.GetChainTipsResult, error) {
|
||||
res, err := ReceiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal result as a string.
|
||||
var chainTips []*btcjson.GetChainTipsResult
|
||||
err = json.Unmarshal(res, &chainTips)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chainTips, nil
|
||||
}
|
||||
|
||||
// GetChainTipsAsync 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 GetChainTips for the blocking version and more details.
|
||||
func (c *Client) GetChainTipsAsync() FutureGetChainTipsResult {
|
||||
cmd := btcjson.NewGetChainTipsCmd()
|
||||
return c.SendCmd(cmd)
|
||||
}
|
||||
|
||||
// GetChainTips returns a slice of data structure with information about all the
|
||||
// current chain tips that this node is aware of.
|
||||
func (c *Client) GetChainTips() ([]*btcjson.GetChainTipsResult, error) {
|
||||
return c.GetChainTipsAsync().Receive()
|
||||
}
|
||||
|
||||
// FutureGetMempoolEntryResult is a future promise to deliver the result of a
|
||||
// GetMempoolEntryAsync RPC invocation (or an applicable error).
|
||||
type FutureGetMempoolEntryResult chan *Response
|
||||
|
|
25
rpcserver.go
25
rpcserver.go
|
@ -146,6 +146,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
|
|||
"getblockhash": handleGetBlockHash,
|
||||
"getblockheader": handleGetBlockHeader,
|
||||
"getblocktemplate": handleGetBlockTemplate,
|
||||
"getchaintips": handleGetChainTips,
|
||||
"getcfilter": handleGetCFilter,
|
||||
"getcfilterheader": handleGetCFilterHeader,
|
||||
"getconnectioncount": handleGetConnectionCount,
|
||||
|
@ -231,7 +232,6 @@ var rpcAskWallet = map[string]struct{}{
|
|||
// Commands that are currently unimplemented, but should ultimately be.
|
||||
var rpcUnimplemented = map[string]struct{}{
|
||||
"estimatepriority": {},
|
||||
"getchaintips": {},
|
||||
"getmempoolentry": {},
|
||||
"getnetworkinfo": {},
|
||||
"getwork": {},
|
||||
|
@ -266,6 +266,7 @@ var rpcLimited = map[string]struct{}{
|
|||
"getblockcount": {},
|
||||
"getblockhash": {},
|
||||
"getblockheader": {},
|
||||
"getchaintips": {},
|
||||
"getcfilter": {},
|
||||
"getcfilterheader": {},
|
||||
"getcurrentnet": {},
|
||||
|
@ -2206,6 +2207,28 @@ func handleGetBlockTemplate(s *rpcServer, cmd interface{}, closeChan <-chan stru
|
|||
}
|
||||
}
|
||||
|
||||
// handleGetChainTips implements the getchaintips command.
|
||||
func handleGetChainTips(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
chainTips := s.cfg.Chain.ChainTips()
|
||||
|
||||
ret := make([]btcjson.GetChainTipsResult, 0, len(chainTips))
|
||||
for _, chainTip := range chainTips {
|
||||
ret = append(ret, struct {
|
||||
Height int32 "json:\"height\""
|
||||
Hash string "json:\"hash\""
|
||||
BranchLen int32 "json:\"branchlen\""
|
||||
Status string "json:\"status\""
|
||||
}{
|
||||
Height: chainTip.Height,
|
||||
Hash: chainTip.BlockHash.String(),
|
||||
BranchLen: chainTip.BranchLen,
|
||||
Status: chainTip.Status.String(),
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// handleGetCFilter implements the getcfilter command.
|
||||
func handleGetCFilter(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||
if s.cfg.CfIndex == nil {
|
||||
|
|
|
@ -349,6 +349,15 @@ var helpDescsEnUS = map[string]string{
|
|||
"getblocktemplate--condition2": "mode=proposal, accepted",
|
||||
"getblocktemplate--result1": "An error string which represents why the proposal was rejected or nothing if accepted",
|
||||
|
||||
// GetChainTipsResult help.
|
||||
"getchaintipsresult-chaintips": "The chaintips that this node is aware of",
|
||||
"getchaintipsresult-height": "The height of the chain tip",
|
||||
"getchaintipsresult-hash": "The block hash of the chain tip",
|
||||
"getchaintipsresult-branchlen": "Returns zero for main chain. Otherwise is the length of branch connecting the tip to the main chain",
|
||||
"getchaintipsresult-status": "Status of the chain. Returns \"active\" for the main chain",
|
||||
// GetChainTipsCmd help.
|
||||
"getchaintips--synopsis": "Returns information about all known tips in the block tree, including the main chain as well as orphaned branches.",
|
||||
|
||||
// GetCFilterCmd help.
|
||||
"getcfilter--synopsis": "Returns a block's committed filter given its hash.",
|
||||
"getcfilter-filtertype": "The type of filter to return (0=regular)",
|
||||
|
@ -730,6 +739,7 @@ var rpcResultTypes = map[string][]interface{}{
|
|||
"getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)},
|
||||
"getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil},
|
||||
"getblockchaininfo": {(*btcjson.GetBlockChainInfoResult)(nil)},
|
||||
"getchaintips": {(*[]btcjson.GetChainTipsResult)(nil)},
|
||||
"getcfilter": {(*string)(nil)},
|
||||
"getcfilterheader": {(*string)(nil)},
|
||||
"getconnectioncount": {(*int32)(nil)},
|
||||
|
|
Loading…
Add table
Reference in a new issue