mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-10-14 00:59:33 +00:00
[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:
parent
534cb2bf5b
commit
5f49115cac
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -41,10 +41,10 @@ func newVirtualBlock(tips blockSet, phantomK uint32) *virtualBlock {
|
|||||||
// clone creates and returns a clone of the virtual block.
|
// clone creates and returns a clone of the virtual block.
|
||||||
func (v *virtualBlock) clone() *virtualBlock {
|
func (v *virtualBlock) clone() *virtualBlock {
|
||||||
return &virtualBlock{
|
return &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
|
||||||
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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"`
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user