[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 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 { for _, child := range node.children {
if dag.IsInSelectedPathChain(child.hash) { if dag.IsInSelectedParentChain(child.hash) {
return child, nil 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 // oldestChainBlockWithBlueScoreGreaterThan finds the oldest chain block with a blue score
// greater than blueScore. If no such block exists, this method returns nil // greater than blueScore. If no such block exists, this method returns nil
func (dag *BlockDAG) oldestChainBlockWithBlueScoreGreaterThan(blueScore uint64) *blockNode { func (dag *BlockDAG) oldestChainBlockWithBlueScoreGreaterThan(blueScore uint64) *blockNode {
chainBlockIndex, ok := util.SearchSlice(len(dag.virtual.selectedPathChainSlice), func(i int) bool { chainBlockIndex, ok := util.SearchSlice(len(dag.virtual.selectedParentChainSlice), func(i int) bool {
selectedPathNode := dag.virtual.selectedPathChainSlice[i] selectedPathNode := dag.virtual.selectedParentChainSlice[i]
return selectedPathNode.blueScore > blueScore return selectedPathNode.blueScore > blueScore
}) })
if !ok { if !ok {
return nil 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 // This method MUST be called with the DAG lock held
func (dag *BlockDAG) IsInSelectedPathChain(blockHash *daghash.Hash) bool { func (dag *BlockDAG) IsInSelectedParentChain(blockHash *daghash.Hash) bool {
return dag.virtual.selectedPathChainSet.containsHash(blockHash) 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 // 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() defer dag.dagLock.RUnlock()
node := dag.index.LookupNode(hash) node := dag.index.LookupNode(hash)
if node != nil { if node != nil {
for !dag.IsInSelectedPathChain(node.hash) { for !dag.IsInSelectedParentChain(node.hash) {
node = node.selectedParent node = node.selectedParent
} }
} }

View File

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

View File

@ -15,14 +15,14 @@ type virtualBlock struct {
utxoSet *FullUTXOSet utxoSet *FullUTXOSet
blockNode 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. // 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 // blocks that belong the the chain of selected parents from the
// virtual block. // virtual block.
selectedPathChainSlice []*blockNode selectedParentChainSlice []*blockNode
} }
// newVirtualBlock creates and returns a new VirtualBlock. // newVirtualBlock creates and returns a new VirtualBlock.
@ -31,8 +31,8 @@ func newVirtualBlock(tips blockSet, phantomK uint32) *virtualBlock {
var virtual virtualBlock var virtual virtualBlock
virtual.phantomK = phantomK virtual.phantomK = phantomK
virtual.utxoSet = NewFullUTXOSet() virtual.utxoSet = NewFullUTXOSet()
virtual.selectedPathChainSet = newSet() virtual.selectedParentChainSet = newSet()
virtual.selectedPathChainSlice = nil virtual.selectedParentChainSlice = nil
virtual.setTips(tips) virtual.setTips(tips)
return &virtual return &virtual
@ -44,7 +44,7 @@ func (v *virtualBlock) clone() *virtualBlock {
phantomK: v.phantomK, phantomK: v.phantomK,
utxoSet: v.utxoSet, utxoSet: v.utxoSet,
blockNode: v.blockNode, blockNode: v.blockNode,
selectedPathChainSet: v.selectedPathChainSet, selectedParentChainSet: v.selectedParentChainSet,
} }
} }
@ -56,10 +56,10 @@ func (v *virtualBlock) clone() *virtualBlock {
func (v *virtualBlock) setTips(tips blockSet) { func (v *virtualBlock) setTips(tips blockSet) {
oldSelectedParent := v.selectedParent oldSelectedParent := v.selectedParent
v.blockNode = *newBlockNode(nil, tips, v.phantomK) 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. // new selected parent of the virtual block.
// Every time the new selected parent is not a child of // Every time the new selected parent is not a child of
// the old one, it updates the selected path by removing from // 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 // parent and are not selected ancestors of the new one, and adding
// blocks that are selected ancestors of the new selected parent // blocks that are selected ancestors of the new selected parent
// and aren't selected ancestors of the old one. // and aren't selected ancestors of the old one.
func (v *virtualBlock) updateSelectedPathSet(oldSelectedParent *blockNode) { func (v *virtualBlock) updateSelectedParentSet(oldSelectedParent *blockNode) {
var intersectionNode *blockNode var intersectionNode *blockNode
nodesToAdd := make([]*blockNode, 0) nodesToAdd := make([]*blockNode, 0)
for node := v.blockNode.selectedParent; intersectionNode == nil && node != nil; node = node.selectedParent { for node := v.blockNode.selectedParent; intersectionNode == nil && node != nil; node = node.selectedParent {
if v.selectedPathChainSet.contains(node) { if v.selectedParentChainSet.contains(node) {
intersectionNode = node intersectionNode = node
} else { } else {
nodesToAdd = append(nodesToAdd, node) nodesToAdd = append(nodesToAdd, node)
@ -79,19 +79,19 @@ func (v *virtualBlock) updateSelectedPathSet(oldSelectedParent *blockNode) {
} }
if intersectionNode == nil && oldSelectedParent != nil { 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 // Remove the nodes in the set from the oldSelectedParent down to the intersectionNode
removeCount := 0 removeCount := 0
if intersectionNode != nil { if intersectionNode != nil {
for node := oldSelectedParent; !node.hash.IsEqual(intersectionNode.hash); node = node.selectedParent { for node := oldSelectedParent; !node.hash.IsEqual(intersectionNode.hash); node = node.selectedParent {
v.selectedPathChainSet.remove(node) v.selectedParentChainSet.remove(node)
removeCount++ removeCount++
} }
} }
// Remove the last removeCount nodes from the slice // 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 // Reverse nodesToAdd, since we collected them in reverse order
for left, right := 0, len(nodesToAdd)-1; left < right; left, right = left+1, right-1 { 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 // Add the nodes to the set and to the slice
for _, node := range nodesToAdd { 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 // 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) virtual.AddTip(tip)
} }
// For now we don't have any DAG, just chain, the selected path should include all the blocks on the chain. // 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) 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 wantLen := 11
gotLen := len(virtual.selectedPathChainSlice) gotLen := len(virtual.selectedParentChainSlice)
if wantLen != gotLen { 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() secondPath := initialPath.clone()
@ -141,14 +141,14 @@ func TestSelectedPath(t *testing.T) {
virtual.AddTip(tip) virtual.AddTip(tip)
} }
// Because we added a chain that is much longer than the previous chain, the selected path should be re-organized. // 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) 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 wantLen = 106
gotLen = len(virtual.selectedPathChainSlice) gotLen = len(virtual.selectedParentChainSlice)
if wantLen != gotLen { 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) "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) virtual.AddTip(tip)
} }
// Because we added a very short chain, the selected path should not be affected. // 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) 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 wantLen = 106
gotLen = len(virtual.selectedPathChainSlice) gotLen = len(virtual.selectedParentChainSlice)
if wantLen != gotLen { 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) "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) virtual2 := newVirtualBlock(nil, phantomK)
defer func() { defer func() {
if r := recover(); r == nil { 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. // GetDAGTipsCmd defines the getDagTips JSON-RPC command.
type GetDAGTipsCmd struct{} type GetDAGTipsCmd struct{}
@ -789,6 +804,7 @@ func init() {
MustRegisterCmd("getBlockTemplate", (*GetBlockTemplateCmd)(nil), flags) MustRegisterCmd("getBlockTemplate", (*GetBlockTemplateCmd)(nil), flags)
MustRegisterCmd("getCFilter", (*GetCFilterCmd)(nil), flags) MustRegisterCmd("getCFilter", (*GetCFilterCmd)(nil), flags)
MustRegisterCmd("getCFilterHeader", (*GetCFilterHeaderCmd)(nil), flags) MustRegisterCmd("getCFilterHeader", (*GetCFilterHeaderCmd)(nil), flags)
MustRegisterCmd("getChainFromBlock", (*GetChainFromBlockCmd)(nil), flags)
MustRegisterCmd("getDagTips", (*GetDAGTipsCmd)(nil), flags) MustRegisterCmd("getDagTips", (*GetDAGTipsCmd)(nil), flags)
MustRegisterCmd("getConnectionCount", (*GetConnectionCountCmd)(nil), flags) MustRegisterCmd("getConnectionCount", (*GetConnectionCountCmd)(nil), flags)
MustRegisterCmd("getDifficulty", (*GetDifficultyCmd)(nil), flags) MustRegisterCmd("getDifficulty", (*GetDifficultyCmd)(nil), flags)

View File

@ -22,7 +22,7 @@ import (
func TestDAGSvrCmds(t *testing.T) { func TestDAGSvrCmds(t *testing.T) {
t.Parallel() t.Parallel()
testID := int(1) testID := 1
tests := []struct { tests := []struct {
name string name string
newCmd func() (interface{}, error) newCmd func() (interface{}, error)
@ -352,6 +352,20 @@ func TestDAGSvrCmds(t *testing.T) {
FilterType: wire.GCSFilterExtended, 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", name: "getDagTips",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {

View File

@ -521,3 +521,9 @@ type AcceptedBlock struct {
Hash string `json:"hash"` Hash string `json:"hash"`
AcceptedTxIds []string `json:"acceptedTxIds"` 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, "getBlockTemplate": handleGetBlockTemplate,
"getCFilter": handleGetCFilter, "getCFilter": handleGetCFilter,
"getCFilterHeader": handleGetCFilterHeader, "getCFilterHeader": handleGetCFilterHeader,
"getChainFromBlock": handleGetChainFromBlock,
"getConnectionCount": handleGetConnectionCount, "getConnectionCount": handleGetConnectionCount,
"getCurrentNet": handleGetCurrentNet, "getCurrentNet": handleGetCurrentNet,
"getDifficulty": handleGetDifficulty, "getDifficulty": handleGetDifficulty,
@ -219,6 +220,7 @@ var rpcLimited = map[string]struct{}{
"getBlockHeader": {}, "getBlockHeader": {},
"getCFilter": {}, "getCFilter": {},
"getCFilterHeader": {}, "getCFilterHeader": {},
"getChainFromBlock": {},
"getCurrentNet": {}, "getCurrentNet": {},
"getDifficulty": {}, "getDifficulty": {},
"getHeaders": {}, "getHeaders": {},
@ -1087,6 +1089,18 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
return nil, internalRPCError(err.Error(), context) 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. // Get the block chain height.
blockChainHeight, err := s.cfg.DAG.BlockChainHeightByHash(hash) blockChainHeight, err := s.cfg.DAG.BlockChainHeightByHash(hash)
if err != nil { if err != nil {
@ -1111,10 +1125,8 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
return nil, internalRPCError(err.Error(), context) return nil, internalRPCError(err.Error(), context)
} }
params := s.cfg.DAGParams result := &btcjson.GetBlockVerboseResult{
blockHeader := &blk.MsgBlock().Header Hash: hash.String(),
blockReply := btcjson.GetBlockVerboseResult{
Hash: c.Hash,
Version: blockHeader.Version, Version: blockHeader.Version,
VersionHex: fmt.Sprintf("%08x", blockHeader.Version), VersionHex: fmt.Sprintf("%08x", blockHeader.Version),
HashMerkleRoot: blockHeader.HashMerkleRoot.String(), HashMerkleRoot: blockHeader.HashMerkleRoot.String(),
@ -1124,22 +1136,22 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
Time: blockHeader.Timestamp.Unix(), Time: blockHeader.Timestamp.Unix(),
Confirmations: blockConfirmations, Confirmations: blockConfirmations,
Height: blockChainHeight, Height: blockChainHeight,
Size: int32(len(blkBytes)), Size: int32(block.MsgBlock().SerializeSize()),
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
Difficulty: getDifficultyRatio(blockHeader.Bits, params), Difficulty: getDifficultyRatio(blockHeader.Bits, params),
NextHashes: nextHashStrings, NextHashes: nextHashStrings,
} }
if c.VerboseTx == nil || !*c.VerboseTx { if isVerboseTx {
transactions := blk.Transactions() transactions := block.Transactions()
txNames := make([]string, len(transactions)) txNames := make([]string, len(transactions))
for i, tx := range transactions { for i, tx := range transactions {
txNames[i] = tx.ID().String() txNames[i] = tx.ID().String()
} }
blockReply.Tx = txNames result.Tx = txNames
} else { } else {
txns := blk.Transactions() txns := block.Transactions()
rawTxns := make([]btcjson.TxRawResult, len(txns)) rawTxns := make([]btcjson.TxRawResult, len(txns))
for i, tx := range txns { for i, tx := range txns {
var acceptingBlock *daghash.Hash var acceptingBlock *daghash.Hash
@ -1156,16 +1168,16 @@ func handleGetBlock(s *Server, cmd interface{}, closeChan <-chan struct{}) (inte
confirmations = &txConfirmations confirmations = &txConfirmations
} }
rawTxn, err := createTxRawResult(params, tx.MsgTx(), tx.ID().String(), rawTxn, err := createTxRawResult(params, tx.MsgTx(), tx.ID().String(),
blockHeader, hash.String(), acceptingBlock, confirmations, false) &blockHeader, hash.String(), acceptingBlock, confirmations, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rawTxns[i] = *rawTxn 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 // 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 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. // handleGetConnectionCount implements the getConnectionCount command.
func handleGetConnectionCount(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { func handleGetConnectionCount(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
return s.cfg.ConnMgr.ConnectedCount(), nil 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-n": "The index of this transaction output",
"vout-scriptPubKey": "The public key script used to pay coins as a JSON object", "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 help.
"txRawDecodeResult-txId": "The hash of the transaction", "txRawDecodeResult-txId": "The hash of the transaction",
"txRawDecodeResult-version": "The transaction version", "txRawDecodeResult-version": "The transaction version",
@ -342,6 +350,16 @@ var helpDescsEnUS = map[string]string{
"getCFilter-hash": "The hash of the block", "getCFilter-hash": "The hash of the block",
"getCFilter--result0": "The block's committed filter", "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. // GetCFilterHeaderCmd help.
"getCFilterHeader--synopsis": "Returns a block's compact filter header given its hash.", "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)", "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)}, "getBlockDagInfo": {(*btcjson.GetBlockDAGInfoResult)(nil)},
"getCFilter": {(*string)(nil)}, "getCFilter": {(*string)(nil)},
"getCFilterHeader": {(*string)(nil)}, "getCFilterHeader": {(*string)(nil)},
"getChainFromBlock": {(*btcjson.GetChainFromBlockResult)(nil)},
"getConnectionCount": {(*int32)(nil)}, "getConnectionCount": {(*int32)(nil)},
"getCurrentNet": {(*uint32)(nil)}, "getCurrentNet": {(*uint32)(nil)},
"getDifficulty": {(*float64)(nil)}, "getDifficulty": {(*float64)(nil)},