From c9c87951608158a5cb75c37bfa96a60eefcdb6eb Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Mon, 8 Apr 2024 17:45:24 +0900 Subject: [PATCH 1/2] blockchain: add Equals method to blockNode Helper function for the added IsAncestor in the follow up commit. Returns true if all the fields (except for parent and ancestor) are equal. --- blockchain/blockindex.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index ca3235f7..9bfa3348 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -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. From bc6396ddfd097f93e2eaf0d1346ab80735eaa169 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Mon, 19 Dec 2022 14:57:33 +0900 Subject: [PATCH 2/2] blockchain: Add IsAncestor method to blockNode IsAncestor() provides functionality for testing if a block node is an ancestor of anther block node. --- blockchain/blockindex.go | 22 ++++++ blockchain/chain_test.go | 156 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index 9bfa3348..5273cb48 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -274,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. diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index cd5c761b..259a643f 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -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()) + } +}