Skip to content

Commit

Permalink
Merge pull request #2153 from kcalvinalvin/2024-04-02-add-is-ancestor
Browse files Browse the repository at this point in the history
blockchain: Add IsAncestor method to blockNode
  • Loading branch information
Roasbeef authored Apr 10, 2024
2 parents e4b32e0 + bc6396d commit 5d50f7c
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
36 changes: 36 additions & 0 deletions blockchain/blockindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,20 @@ func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode {
return &node
}

// Equals compares all the fields of the block node except for the parent and
// ancestor and returns true if they're equal.
func (node *blockNode) Equals(other *blockNode) bool {
return node.hash == other.hash &&
node.workSum.Cmp(other.workSum) == 0 &&
node.height == other.height &&
node.version == other.version &&
node.bits == other.bits &&
node.nonce == other.nonce &&
node.timestamp == other.timestamp &&
node.merkleRoot == other.merkleRoot &&
node.status == other.status
}

// Header constructs a block header from the node and returns it.
//
// This function is safe for concurrent access.
Expand Down Expand Up @@ -260,6 +274,28 @@ func (node *blockNode) RelativeAncestorCtx(distance int32) HeaderCtx {
return ancestor
}

// IsAncestor returns if the other node is an ancestor of this block node.
func (node *blockNode) IsAncestor(otherNode *blockNode) bool {
// Return early as false if the otherNode is nil.
if otherNode == nil {
return false
}

ancestor := node.Ancestor(otherNode.height)
if ancestor == nil {
return false
}

// If the otherNode has the same height as me, then the returned
// ancestor will be me. Return false since I'm not an ancestor of me.
if node.height == ancestor.height {
return false
}

// Return true if the fetched ancestor is other node.
return ancestor.Equals(otherNode)
}

// RelativeAncestor returns the ancestor block node a relative 'distance' blocks
// before this node. This is equivalent to calling Ancestor with the node's
// height minus provided distance.
Expand Down
156 changes: 156 additions & 0 deletions blockchain/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1155,3 +1155,159 @@ func TestChainTips(t *testing.T) {
}
}
}

func TestIsAncestor(t *testing.T) {
// Construct a synthetic block chain with a block index consisting of
// the following structure.
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
tip := tstTip
chain := newFakeChain(&chaincfg.MainNetParams)
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 3)
for _, node := range branch0Nodes {
chain.index.SetStatusFlags(node, statusDataStored)
chain.index.SetStatusFlags(node, statusValid)
chain.index.AddNode(node)
}
chain.bestChain.SetTip(tip(branch0Nodes))

branch1Nodes := chainedNodes(chain.bestChain.Genesis(), 1)
for _, node := range branch1Nodes {
chain.index.SetStatusFlags(node, statusDataStored)
chain.index.SetStatusFlags(node, statusValid)
chain.index.AddNode(node)
}

branch2Nodes := chainedNodes(chain.bestChain.Genesis(), 1)
for _, node := range branch2Nodes {
chain.index.SetStatusFlags(node, statusDataStored)
chain.index.SetStatusFlags(node, statusValidateFailed)
chain.index.AddNode(node)
}

// Is 1 an ancestor of 3?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeTrue := branch0Nodes[2].IsAncestor(branch0Nodes[0])
if !shouldBeTrue {
t.Errorf("TestIsAncestor fail. Node %s is an ancestor of node %s but got false",
branch0Nodes[0].hash.String(), branch0Nodes[2].hash.String())
}

// Is 1 an ancestor of 2?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeTrue = branch0Nodes[1].IsAncestor(branch0Nodes[0])
if !shouldBeTrue {
t.Errorf("TestIsAncestor fail. Node %s is an ancestor of node %s but got false",
branch0Nodes[0].hash.String(), branch0Nodes[1].hash.String())
}

// Is the genesis an ancestor of 1?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeTrue = branch0Nodes[0].IsAncestor(chain.bestChain.Genesis())
if !shouldBeTrue {
t.Errorf("TestIsAncestor fail. The genesis block is an ancestor of all blocks "+
"but got false for node %s",
branch0Nodes[0].hash.String())
}

// Is the genesis an ancestor of 1a?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeTrue = branch1Nodes[0].IsAncestor(chain.bestChain.Genesis())
if !shouldBeTrue {
t.Errorf("TestIsAncestor fail. The genesis block is an ancestor of all blocks "+
"but got false for node %s",
branch1Nodes[0].hash.String())
}

// Is the genesis an ancestor of 1b?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeTrue = branch2Nodes[0].IsAncestor(chain.bestChain.Genesis())
if !shouldBeTrue {
t.Errorf("TestIsAncestor fail. The genesis block is an ancestor of all blocks "+
"but got false for node %s",
branch2Nodes[0].hash.String())
}

// Is 1 an ancestor of 1a?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeFalse := branch1Nodes[0].IsAncestor(branch0Nodes[0])
if shouldBeFalse {
t.Errorf("TestIsAncestor fail. Node %s is in a different branch than "+
"node %s but got true", branch1Nodes[0].hash.String(),
branch0Nodes[0].hash.String())
}

// Is 1 an ancestor of 1b?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeFalse = branch2Nodes[0].IsAncestor(branch0Nodes[0])
if shouldBeFalse {
t.Errorf("TestIsAncestor fail. Node %s is in a different branch than "+
"node %s but got true", branch2Nodes[0].hash.String(),
branch0Nodes[0].hash.String())
}

// Is 1a an ancestor of 1b?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeFalse = branch2Nodes[0].IsAncestor(branch1Nodes[0])
if shouldBeFalse {
t.Errorf("TestIsAncestor fail. Node %s is in a different branch than "+
"node %s but got true", branch2Nodes[0].hash.String(),
branch1Nodes[0].hash.String())
}

// Is 1 an ancestor of 1?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeFalse = branch0Nodes[0].IsAncestor(branch0Nodes[0])
if shouldBeFalse {
t.Errorf("TestIsAncestor fail. Node is not an ancestor of itself but got true for node %s",
branch0Nodes[0].hash.String())
}

// Is the geneis an ancestor of genesis?
//
// genesis -> 1 -> 2 -> 3 (active)
// \ -> 1a (valid-fork)
// \ -> 1b (invalid)
shouldBeFalse = chain.bestChain.Genesis().IsAncestor(chain.bestChain.Genesis())
if shouldBeFalse {
t.Errorf("TestIsAncestor fail. Node is not an ancestor of itself but got true for node %s",
chain.bestChain.Genesis().hash.String())
}

// Is a block from another chain an ancestor of 1b?
fakeChain := newFakeChain(&chaincfg.TestNet3Params)
shouldBeFalse = branch2Nodes[0].IsAncestor(fakeChain.bestChain.Genesis())
if shouldBeFalse {
t.Errorf("TestIsAncestor fail. Node %s is in a different chain than "+
"node %s but got true", fakeChain.bestChain.Genesis().hash.String(),
branch2Nodes[0].hash.String())
}
}

0 comments on commit 5d50f7c

Please sign in to comment.