[NOD-269] Implement GetChainFromBlock api-call (#364)

* [NOD-269] Added a skeleton for getChainFromBlock.

* [NOD-269] Made startHash and includeBlocks optional.

* [NOD-269] Implemented chainBlock collection.

* [NOD-269] Extracted GetBlockVerboseResult building to its own method.

* [NOD-269] Implemented the IncludeBlocks part of GetChainFromBlock.

* [NOD-269] Added a comment for NewGetChainFromBlockCmd.

* [NOD-269] Made IsInSelectedPathChain return an error.

* [NOD-269] Fixed a very wrong comment.

* [NOD-269] Made SelectedPathChain allocate only the required amount of space.

* [NOD-269] Renamed pathChain to parentChain.

* [NOD-269] Split handleGetChainFromBlock to separate functions.

* [NOD-269] Fixed some grammar.
This commit is contained in:
stasatdaglabs 2019-08-18 13:31:54 +03:00 committed by Svarog
parent 534cb2bf5b
commit 5f49115cac
9 changed files with 285 additions and 57 deletions

View File

@ -1382,9 +1382,9 @@ func (dag *BlockDAG) acceptingBlock(node *blockNode) (*blockNode, error) {
}
// If the node is a chain-block itself, the accepting block is its chain-child
if dag.IsInSelectedPathChain(node.hash) {
if dag.IsInSelectedParentChain(node.hash) {
for _, child := range node.children {
if dag.IsInSelectedPathChain(child.hash) {
if dag.IsInSelectedParentChain(child.hash) {
return child, nil
}
}
@ -1409,21 +1409,70 @@ func (dag *BlockDAG) acceptingBlock(node *blockNode) (*blockNode, error) {
// oldestChainBlockWithBlueScoreGreaterThan finds the oldest chain block with a blue score
// greater than blueScore. If no such block exists, this method returns nil
func (dag *BlockDAG) oldestChainBlockWithBlueScoreGreaterThan(blueScore uint64) *blockNode {
chainBlockIndex, ok := util.SearchSlice(len(dag.virtual.selectedPathChainSlice), func(i int) bool {
selectedPathNode := dag.virtual.selectedPathChainSlice[i]
chainBlockIndex, ok := util.SearchSlice(len(dag.virtual.selectedParentChainSlice), func(i int) bool {
selectedPathNode := dag.virtual.selectedParentChainSlice[i]
return selectedPathNode.blueScore > blueScore
})
if !ok {
return nil
}
return dag.virtual.selectedPathChainSlice[chainBlockIndex]
return dag.virtual.selectedParentChainSlice[chainBlockIndex]
}
// IsInSelectedPathChain returns whether or not a block hash is found in the selected path
// IsInSelectedParentChain returns whether or not a block hash is found in the selected
// parent chain.
//
// This method MUST be called with the DAG lock held
func (dag *BlockDAG) IsInSelectedPathChain(blockHash *daghash.Hash) bool {
return dag.virtual.selectedPathChainSet.containsHash(blockHash)
func (dag *BlockDAG) IsInSelectedParentChain(blockHash *daghash.Hash) bool {
return dag.virtual.selectedParentChainSet.containsHash(blockHash)
}
// SelectedParentChain returns the selected parent chain starting from startHash (exclusive) up
// to the virtual (exclusive). If startHash is nil then the genesis block is used.
//
// This method MUST be called with the DAG lock held
func (dag *BlockDAG) SelectedParentChain(startHash *daghash.Hash) ([]*daghash.Hash, error) {
if startHash == nil {
startHash = dag.genesis.hash
}
if !dag.IsInSelectedParentChain(startHash) {
return nil, fmt.Errorf("startHash %s is not the selected parent chain", startHash)
}
// Find the index of the startHash in the selectedParentChainSlice
startHashIndex := len(dag.virtual.selectedParentChainSlice) - 1
for startHashIndex >= 0 {
node := dag.virtual.selectedParentChainSlice[startHashIndex]
if node.hash.IsEqual(startHash) {
break
}
startHashIndex--
}
// Copy all the hashes starting from startHashIndex (exclusive)
hashes := make([]*daghash.Hash, len(dag.virtual.selectedParentChainSlice)-startHashIndex)
for i, node := range dag.virtual.selectedParentChainSlice[startHashIndex+1:] {
hashes[i] = node.hash
}
return hashes, nil
}
// BluesTxsAcceptanceData returns the acceptance data of all the transactions that
// were accepted by the block with hash blockHash.
func (dag *BlockDAG) BluesTxsAcceptanceData(blockHash *daghash.Hash) (MultiBlockTxsAcceptanceData, error) {
node := dag.index.LookupNode(blockHash)
if node == nil {
err := fmt.Errorf("block %s is not known", blockHash)
return nil, err
}
_, bluesTxsAcceptanceData, err := dag.pastUTXO(node)
if err != nil {
return nil, err
}
return bluesTxsAcceptanceData, nil
}
// ChainHeight return the chain-height of the selected tip. In other words - it returns
@ -1486,7 +1535,7 @@ func (dag *BlockDAG) BlockLocatorFromHash(hash *daghash.Hash) BlockLocator {
defer dag.dagLock.RUnlock()
node := dag.index.LookupNode(hash)
if node != nil {
for !dag.IsInSelectedPathChain(node.hash) {
for !dag.IsInSelectedParentChain(node.hash) {
node = node.selectedParent
}
}

View File

@ -562,7 +562,7 @@ func dbFetchTxAcceptingBlock(dbTx database.Tx, txID *daghash.TxID, dag *blockdag
if err != nil {
return nil, err
}
if dag.IsInSelectedPathChain(blockHash) {
if dag.IsInSelectedParentChain(blockHash) {
return blockHash, nil
}
}

View File

@ -15,14 +15,14 @@ type virtualBlock struct {
utxoSet *FullUTXOSet
blockNode
// selectedPathChainSet is a block set that includes all the blocks
// selectedParentChainSet is a block set that includes all the blocks
// that belong to the chain of selected parents from the virtual block.
selectedPathChainSet blockSet
selectedParentChainSet blockSet
// selectedPathChainSlice is an ordered slice that includes all the
// selectedParentChainSlice is an ordered slice that includes all the
// blocks that belong the the chain of selected parents from the
// virtual block.
selectedPathChainSlice []*blockNode
selectedParentChainSlice []*blockNode
}
// newVirtualBlock creates and returns a new VirtualBlock.
@ -31,8 +31,8 @@ func newVirtualBlock(tips blockSet, phantomK uint32) *virtualBlock {
var virtual virtualBlock
virtual.phantomK = phantomK
virtual.utxoSet = NewFullUTXOSet()
virtual.selectedPathChainSet = newSet()
virtual.selectedPathChainSlice = nil
virtual.selectedParentChainSet = newSet()
virtual.selectedParentChainSlice = nil
virtual.setTips(tips)
return &virtual
@ -41,10 +41,10 @@ func newVirtualBlock(tips blockSet, phantomK uint32) *virtualBlock {
// clone creates and returns a clone of the virtual block.
func (v *virtualBlock) clone() *virtualBlock {
return &virtualBlock{
phantomK: v.phantomK,
utxoSet: v.utxoSet,
blockNode: v.blockNode,
selectedPathChainSet: v.selectedPathChainSet,
phantomK: v.phantomK,
utxoSet: v.utxoSet,
blockNode: v.blockNode,
selectedParentChainSet: v.selectedParentChainSet,
}
}
@ -56,10 +56,10 @@ func (v *virtualBlock) clone() *virtualBlock {
func (v *virtualBlock) setTips(tips blockSet) {
oldSelectedParent := v.selectedParent
v.blockNode = *newBlockNode(nil, tips, v.phantomK)
v.updateSelectedPathSet(oldSelectedParent)
v.updateSelectedParentSet(oldSelectedParent)
}
// updateSelectedPathSet updates the selectedPathSet to match the
// updateSelectedParentSet updates the selectedParentSet to match the
// new selected parent of the virtual block.
// Every time the new selected parent is not a child of
// the old one, it updates the selected path by removing from
@ -67,11 +67,11 @@ func (v *virtualBlock) setTips(tips blockSet) {
// parent and are not selected ancestors of the new one, and adding
// blocks that are selected ancestors of the new selected parent
// and aren't selected ancestors of the old one.
func (v *virtualBlock) updateSelectedPathSet(oldSelectedParent *blockNode) {
func (v *virtualBlock) updateSelectedParentSet(oldSelectedParent *blockNode) {
var intersectionNode *blockNode
nodesToAdd := make([]*blockNode, 0)
for node := v.blockNode.selectedParent; intersectionNode == nil && node != nil; node = node.selectedParent {
if v.selectedPathChainSet.contains(node) {
if v.selectedParentChainSet.contains(node) {
intersectionNode = node
} else {
nodesToAdd = append(nodesToAdd, node)
@ -79,19 +79,19 @@ func (v *virtualBlock) updateSelectedPathSet(oldSelectedParent *blockNode) {
}
if intersectionNode == nil && oldSelectedParent != nil {
panic("updateSelectedPathSet: Cannot find intersection node. The block index may be corrupted.")
panic("updateSelectedParentSet: Cannot find intersection node. The block index may be corrupted.")
}
// Remove the nodes in the set from the oldSelectedParent down to the intersectionNode
removeCount := 0
if intersectionNode != nil {
for node := oldSelectedParent; !node.hash.IsEqual(intersectionNode.hash); node = node.selectedParent {
v.selectedPathChainSet.remove(node)
v.selectedParentChainSet.remove(node)
removeCount++
}
}
// Remove the last removeCount nodes from the slice
v.selectedPathChainSlice = v.selectedPathChainSlice[:len(v.selectedPathChainSlice)-removeCount]
v.selectedParentChainSlice = v.selectedParentChainSlice[:len(v.selectedParentChainSlice)-removeCount]
// Reverse nodesToAdd, since we collected them in reverse order
for left, right := 0, len(nodesToAdd)-1; left < right; left, right = left+1, right-1 {
@ -99,9 +99,9 @@ func (v *virtualBlock) updateSelectedPathSet(oldSelectedParent *blockNode) {
}
// Add the nodes to the set and to the slice
for _, node := range nodesToAdd {
v.selectedPathChainSet.add(node)
v.selectedParentChainSet.add(node)
}
v.selectedPathChainSlice = append(v.selectedPathChainSlice, nodesToAdd...)
v.selectedParentChainSlice = append(v.selectedParentChainSlice, nodesToAdd...)
}
// SetTips replaces the tips of the virtual block with the blocks in the

View File

@ -123,14 +123,14 @@ func TestSelectedPath(t *testing.T) {
virtual.AddTip(tip)
}
// For now we don't have any DAG, just chain, the selected path should include all the blocks on the chain.
if !reflect.DeepEqual(virtual.selectedPathChainSet, firstPath) {
if !reflect.DeepEqual(virtual.selectedParentChainSet, firstPath) {
t.Fatalf("TestSelectedPath: selectedPathSet doesn't include the expected values. got %v, want %v", virtual.selectedParent, firstPath)
}
// We expect that selectedPathChainSlice should have all the blocks we've added so far
// We expect that selectedParentChainSlice should have all the blocks we've added so far
wantLen := 11
gotLen := len(virtual.selectedPathChainSlice)
gotLen := len(virtual.selectedParentChainSlice)
if wantLen != gotLen {
t.Fatalf("TestSelectedPath: selectedPathChainSlice doesn't have the expected length. got %d, want %d", gotLen, wantLen)
t.Fatalf("TestSelectedPath: selectedParentChainSlice doesn't have the expected length. got %d, want %d", gotLen, wantLen)
}
secondPath := initialPath.clone()
@ -141,14 +141,14 @@ func TestSelectedPath(t *testing.T) {
virtual.AddTip(tip)
}
// Because we added a chain that is much longer than the previous chain, the selected path should be re-organized.
if !reflect.DeepEqual(virtual.selectedPathChainSet, secondPath) {
if !reflect.DeepEqual(virtual.selectedParentChainSet, secondPath) {
t.Fatalf("TestSelectedPath: selectedPathSet didn't handle the re-org as expected. got %v, want %v", virtual.selectedParent, firstPath)
}
// We expect that selectedPathChainSlice should have all the blocks we've added so far except the old chain
// We expect that selectedParentChainSlice should have all the blocks we've added so far except the old chain
wantLen = 106
gotLen = len(virtual.selectedPathChainSlice)
gotLen = len(virtual.selectedParentChainSlice)
if wantLen != gotLen {
t.Fatalf("TestSelectedPath: selectedPathChainSlice doesn't have"+
t.Fatalf("TestSelectedPath: selectedParentChainSlice doesn't have"+
"the expected length, possibly because it didn't handle the re-org as expected. got %d, want %d", gotLen, wantLen)
}
@ -158,23 +158,23 @@ func TestSelectedPath(t *testing.T) {
virtual.AddTip(tip)
}
// Because we added a very short chain, the selected path should not be affected.
if !reflect.DeepEqual(virtual.selectedPathChainSet, secondPath) {
if !reflect.DeepEqual(virtual.selectedParentChainSet, secondPath) {
t.Fatalf("TestSelectedPath: selectedPathSet did an unexpected re-org. got %v, want %v", virtual.selectedParent, firstPath)
}
// We expect that selectedPathChainSlice not to change
// We expect that selectedParentChainSlice not to change
wantLen = 106
gotLen = len(virtual.selectedPathChainSlice)
gotLen = len(virtual.selectedParentChainSlice)
if wantLen != gotLen {
t.Fatalf("TestSelectedPath: selectedPathChainSlice doesn't"+
t.Fatalf("TestSelectedPath: selectedParentChainSlice doesn't"+
"have the expected length, possibly due to unexpected did an unexpected re-org. got %d, want %d", gotLen, wantLen)
}
// We call updateSelectedPathSet manually without updating the tips, to check if it panics
// We call updateSelectedParentSet manually without updating the tips, to check if it panics
virtual2 := newVirtualBlock(nil, phantomK)
defer func() {
if r := recover(); r == nil {
t.Fatalf("updateSelectedPathSet didn't panic")
t.Fatalf("updateSelectedParentSet didn't panic")
}
}()
virtual2.updateSelectedPathSet(buildNode(setFromSlice()))
virtual2.updateSelectedParentSet(buildNode(setFromSlice()))
}

View File

@ -331,6 +331,21 @@ func NewGetCFilterHeaderCmd(hash string,
}
}
// GetChainFromBlockCmd defines the getChainFromBlock JSON-RPC command.
type GetChainFromBlockCmd struct {
StartHash *string `json:"startHash"`
IncludeBlocks *bool `json:"includeBlocks"`
}
// NewGetChainFromBlockCmd returns a new instance which can be used to issue a
// GetChainFromBlock JSON-RPC command.
func NewGetChainFromBlockCmd(startHash *string, includeBlocks *bool) *GetChainFromBlockCmd {
return &GetChainFromBlockCmd{
StartHash: startHash,
IncludeBlocks: includeBlocks,
}
}
// GetDAGTipsCmd defines the getDagTips JSON-RPC command.
type GetDAGTipsCmd struct{}
@ -789,6 +804,7 @@ func init() {
MustRegisterCmd("getBlockTemplate", (*GetBlockTemplateCmd)(nil), flags)
MustRegisterCmd("getCFilter", (*GetCFilterCmd)(nil), flags)
MustRegisterCmd("getCFilterHeader", (*GetCFilterHeaderCmd)(nil), flags)
MustRegisterCmd("getChainFromBlock", (*GetChainFromBlockCmd)(nil), flags)
MustRegisterCmd("getDagTips", (*GetDAGTipsCmd)(nil), flags)
MustRegisterCmd("getConnectionCount", (*GetConnectionCountCmd)(nil), flags)
MustRegisterCmd("getDifficulty", (*GetDifficultyCmd)(nil), flags)

View File

@ -22,7 +22,7 @@ import (
func TestDAGSvrCmds(t *testing.T) {
t.Parallel()
testID := int(1)
testID := 1
tests := []struct {
name string
newCmd func() (interface{}, error)
@ -352,6 +352,20 @@ func TestDAGSvrCmds(t *testing.T) {
FilterType: wire.GCSFilterExtended,
},
},
{
name: "getChainFromBlock",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getChainFromBlock", "123", true)
},
staticCmd: func() interface{} {
return btcjson.NewGetChainFromBlockCmd(btcjson.String("123"), btcjson.Bool(true))
},
marshalled: `{"jsonrpc":"1.0","method":"getChainFromBlock","params":["123",true],"id":1}`,
unmarshalled: &btcjson.GetChainFromBlockCmd{
StartHash: btcjson.String("123"),
IncludeBlocks: btcjson.Bool(true),
},
},
{
name: "getDagTips",
newCmd: func() (interface{}, error) {

View File

@ -521,3 +521,9 @@ type AcceptedBlock struct {
Hash string `json:"hash"`
AcceptedTxIds []string `json:"acceptedTxIds"`
}
// GetChainFromBlockResult models the data from the getChainFromBlock command.
type GetChainFromBlockResult struct {
SelectedParentChain []ChainBlock `json:"selectedParentChain"`
Blocks []GetBlockVerboseResult `json:"blocks"`
}

View File

@ -147,6 +147,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"getBlockTemplate": handleGetBlockTemplate,
"getCFilter": handleGetCFilter,
"getCFilterHeader": handleGetCFilterHeader,
"getChainFromBlock": handleGetChainFromBlock,
"getConnectionCount": handleGetConnectionCount,
"getCurrentNet": handleGetCurrentNet,
"getDifficulty": handleGetDifficulty,
@ -219,6 +220,7 @@ var rpcLimited = map[string]struct{}{
"getBlockHeader": {},
"getCFilter": {},
"getCFilterHeader": {},
"getChainFromBlock": {},
"getCurrentNet": {},
"getDifficulty": {},
"getHeaders": {},
@ -1087,6 +1089,18 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
return nil, internalRPCError(err.Error(), context)
}
blockReply, err := buildGetBlockVerboseResult(s, blk, c.VerboseTx == nil || !*c.VerboseTx)
if err != nil {
return nil, err
}
return blockReply, nil
}
func buildGetBlockVerboseResult(s *Server, block *util.Block, isVerboseTx bool) (*btcjson.GetBlockVerboseResult, error) {
hash := block.Hash()
params := s.cfg.DAGParams
blockHeader := block.MsgBlock().Header
// Get the block chain height.
blockChainHeight, err := s.cfg.DAG.BlockChainHeightByHash(hash)
if err != nil {
@ -1111,10 +1125,8 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
return nil, internalRPCError(err.Error(), context)
}
params := s.cfg.DAGParams
blockHeader := &blk.MsgBlock().Header
blockReply := btcjson.GetBlockVerboseResult{
Hash: c.Hash,
result := &btcjson.GetBlockVerboseResult{
Hash: hash.String(),
Version: blockHeader.Version,
VersionHex: fmt.Sprintf("%08x", blockHeader.Version),
HashMerkleRoot: blockHeader.HashMerkleRoot.String(),
@ -1124,22 +1136,22 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
Time: blockHeader.Timestamp.Unix(),
Confirmations: blockConfirmations,
Height: blockChainHeight,
Size: int32(len(blkBytes)),
Size: int32(block.MsgBlock().SerializeSize()),
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
Difficulty: getDifficultyRatio(blockHeader.Bits, params),
NextHashes: nextHashStrings,
}
if c.VerboseTx == nil || !*c.VerboseTx {
transactions := blk.Transactions()
if isVerboseTx {
transactions := block.Transactions()
txNames := make([]string, len(transactions))
for i, tx := range transactions {
txNames[i] = tx.ID().String()
}
blockReply.Tx = txNames
result.Tx = txNames
} else {
txns := blk.Transactions()
txns := block.Transactions()
rawTxns := make([]btcjson.TxRawResult, len(txns))
for i, tx := range txns {
var acceptingBlock *daghash.Hash
@ -1156,16 +1168,16 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
confirmations = &txConfirmations
}
rawTxn, err := createTxRawResult(params, tx.MsgTx(), tx.ID().String(),
blockHeader, hash.String(), acceptingBlock, confirmations, false)
&blockHeader, hash.String(), acceptingBlock, confirmations, false)
if err != nil {
return nil, err
}
rawTxns[i] = *rawTxn
}
blockReply.RawTx = rawTxns
result.RawTx = rawTxns
}
return blockReply, nil
return result, nil
}
// softForkStatus converts a ThresholdState state into a human readable string
@ -2190,6 +2202,118 @@ func handleGetCFilterHeader(s *Server, cmd interface{}, closeChan <-chan struct{
return hash.String(), nil
}
// handleGetChainFromBlock implements the getChainFromBlock command.
func handleGetChainFromBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.GetChainFromBlockCmd)
var startHash *daghash.Hash
if c.StartHash != nil {
err := daghash.Decode(startHash, *c.StartHash)
if err != nil {
return nil, rpcDecodeHexError(*c.StartHash)
}
}
s.cfg.DAG.RLock()
defer s.cfg.DAG.RUnlock()
// If startHash is not in the selected parent chain, there's nothing
// to do; return an error.
if !s.cfg.DAG.IsInSelectedParentChain(startHash) {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCBlockNotFound,
Message: "Block not found in selected parent chain",
}
}
// Retrieve the selected parent chain.
selectedParentChain, err := s.cfg.DAG.SelectedParentChain(startHash)
if err != nil {
return nil, err
}
// Collect chainBlocks.
chainBlocks, err := collectChainBlocks(s, selectedParentChain)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: fmt.Sprintf("could not collect chain blocks: %s", err),
}
}
result := &btcjson.GetChainFromBlockResult{
SelectedParentChain: chainBlocks,
Blocks: nil,
}
// If the user specified to include the blocks, collect them as well.
if c.IncludeBlocks != nil && *c.IncludeBlocks {
getBlockVerboseResults, err := hashesToGetBlockVerboseResults(s, selectedParentChain)
if err != nil {
return nil, err
}
result.Blocks = getBlockVerboseResults
}
return result, nil
}
func collectChainBlocks(s *Server, selectedParentChain []*daghash.Hash) ([]btcjson.ChainBlock, error) {
chainBlocks := make([]btcjson.ChainBlock, 0, len(selectedParentChain))
for _, hash := range selectedParentChain {
acceptanceData, err := s.cfg.DAG.BluesTxsAcceptanceData(hash)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: fmt.Sprintf("could not retrieve acceptance data for block %s", hash),
}
}
acceptedBlocks := make([]btcjson.AcceptedBlock, 0, len(acceptanceData))
for blockHash, blockAcceptanceData := range acceptanceData {
acceptedTxIds := make([]string, 0, len(blockAcceptanceData))
for _, txAcceptanceData := range blockAcceptanceData {
if txAcceptanceData.IsAccepted {
acceptedTxIds = append(acceptedTxIds, txAcceptanceData.Tx.Hash().String())
}
}
acceptedBlock := btcjson.AcceptedBlock{
Hash: blockHash.String(),
AcceptedTxIds: acceptedTxIds,
}
acceptedBlocks = append(acceptedBlocks, acceptedBlock)
}
chainBlock := btcjson.ChainBlock{
Hash: hash.String(),
AcceptedBlocks: acceptedBlocks,
}
chainBlocks = append(chainBlocks, chainBlock)
}
return chainBlocks, nil
}
func hashesToGetBlockVerboseResults(s *Server, hashes []*daghash.Hash) ([]btcjson.GetBlockVerboseResult, error) {
getBlockVerboseResults := make([]btcjson.GetBlockVerboseResult, 0, len(hashes))
for _, blockHash := range hashes {
block, err := s.cfg.DAG.BlockByHash(blockHash)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: fmt.Sprintf("could not retrieve block %s.", blockHash),
}
}
getBlockVerboseResult, err := buildGetBlockVerboseResult(s, block, false)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: fmt.Sprintf("could not build getBlockVerboseResult for block %s.", blockHash),
}
}
getBlockVerboseResults = append(getBlockVerboseResults, *getBlockVerboseResult)
}
return getBlockVerboseResults, nil
}
// handleGetConnectionCount implements the getConnectionCount command.
func handleGetConnectionCount(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
return s.cfg.ConnMgr.ConnectedCount(), nil

View File

@ -90,6 +90,14 @@ var helpDescsEnUS = map[string]string{
"vout-n": "The index of this transaction output",
"vout-scriptPubKey": "The public key script used to pay coins as a JSON object",
// ChainBlock help.
"chainBlock-hash": "The hash of the chain block",
"chainBlock-acceptedBlocks": "The blocks accepted by this chain block",
// AcceptedBlock help.
"acceptedBlock-hash": "The hash of the accepted block",
"acceptedBlock-acceptedTxIds": "The transactions in this block accepted by the chain block",
// TxRawDecodeResult help.
"txRawDecodeResult-txId": "The hash of the transaction",
"txRawDecodeResult-version": "The transaction version",
@ -342,6 +350,16 @@ var helpDescsEnUS = map[string]string{
"getCFilter-hash": "The hash of the block",
"getCFilter--result0": "The block's committed filter",
// GetChainFromBlockCmd help.
"getChainFromBlock--synopsis": "Return the selected parent chain starting from startHash up to the virtual.",
"getChainFromBlock-startHash": "Hash of the bottom of the requested chain. If this hash is unknown or is not a chain block - returns an error.",
"getChainFromBlock-includeBlocks": "If set to true - the block contents would be also included.",
"getChainFromBlock--result0": "The selected parent chain.",
// GetChainFromBlockResult help.
"getChainFromBlockResult-selectedParentChain": "List of ChainBlocks from Virtual.SelectedTip to StartHash (excluding StartHash) ordered bottom-to-top.",
"getChainFromBlockResult-blocks": "If includeBlocks=true - contains the contents of all chain and accepted blocks in the SelectedParentChain. Otherwise - omitted.",
// GetCFilterHeaderCmd help.
"getCFilterHeader--synopsis": "Returns a block's compact filter header given its hash.",
"getCFilterHeader-filterType": "The type of filter header to return (0=regular, 1=extended)",
@ -662,6 +680,7 @@ var rpcResultTypes = map[string][]interface{}{
"getBlockDagInfo": {(*btcjson.GetBlockDAGInfoResult)(nil)},
"getCFilter": {(*string)(nil)},
"getCFilterHeader": {(*string)(nil)},
"getChainFromBlock": {(*btcjson.GetChainFromBlockResult)(nil)},
"getConnectionCount": {(*int32)(nil)},
"getCurrentNet": {(*uint32)(nil)},
"getDifficulty": {(*float64)(nil)},