Skip to content

Commit 5d19b98

Browse files
authored
Merge pull request #88 from kcalvinalvin/2023-11-17-better-ancestor
blockchain: better Ancestor with skiplists
2 parents 877de3e + 79ef5a3 commit 5d19b98

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

blockchain/bench_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,16 @@ func BenchmarkIsCoinBaseTx(b *testing.B) {
2929
IsCoinBaseTx(tx)
3030
}
3131
}
32+
33+
func BenchmarkAncestor(b *testing.B) {
34+
height := 1 << 19
35+
blockNodes := chainedNodes(nil, height)
36+
37+
b.ResetTimer()
38+
for i := 0; i < b.N; i++ {
39+
blockNodes[len(blockNodes)-1].Ancestor(0)
40+
for j := 0; j <= 19; j++ {
41+
blockNodes[len(blockNodes)-1].Ancestor(1 << j)
42+
}
43+
}
44+
}

blockchain/blockindex.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ type blockNode struct {
9191
// parent is the parent block for this node.
9292
parent *blockNode
9393

94+
// ancestor is a block that is more than one block back from this node.
95+
ancestor *blockNode
96+
9497
// hash is the double sha 256 of the block.
9598
hash chainhash.Hash
9699

@@ -137,6 +140,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *block
137140
node.parent = parent
138141
node.height = parent.height + 1
139142
node.workSum = node.workSum.Add(parent.workSum, node.workSum)
143+
node.buildAncestor()
140144
}
141145
}
142146

@@ -168,6 +172,26 @@ func (node *blockNode) Header() wire.BlockHeader {
168172
}
169173
}
170174

175+
// invertLowestOne turns the lowest 1 bit in the binary representation of a number into a 0.
176+
func invertLowestOne(n int32) int32 {
177+
return n & (n - 1)
178+
}
179+
180+
// getAncestorHeight returns a suitable ancestor for the node at the given height.
181+
func getAncestorHeight(height int32) int32 {
182+
// We pop off two 1 bits of the height.
183+
// This results in a maximum of 330 steps to go back to an ancestor
184+
// from height 1<<29.
185+
return invertLowestOne(invertLowestOne(height))
186+
}
187+
188+
// buildAncestor sets an ancestor for the given blocknode.
189+
func (node *blockNode) buildAncestor() {
190+
if node.parent != nil {
191+
node.ancestor = node.parent.Ancestor(getAncestorHeight(node.height))
192+
}
193+
}
194+
171195
// Ancestor returns the ancestor block node at the provided height by following
172196
// the chain backwards from this node. The returned block will be nil when a
173197
// height is requested that is after the height of the passed node or is less
@@ -179,9 +203,22 @@ func (node *blockNode) Ancestor(height int32) *blockNode {
179203
return nil
180204
}
181205

206+
// Traverse back until we find the desired node.
182207
n := node
183-
for ; n != nil && n.height != height; n = n.parent {
184-
// Intentionally left blank
208+
for n != nil && n.height != height {
209+
// If there's an ancestor available, use it. Otherwise, just
210+
// follow the parent.
211+
if n.ancestor != nil {
212+
// Calculate the height for this ancestor and
213+
// check if we can take the ancestor skip.
214+
if getAncestorHeight(n.height) >= height {
215+
n = n.ancestor
216+
continue
217+
}
218+
}
219+
220+
// We couldn't take the ancestor skip so traverse back to the parent.
221+
n = n.parent
185222
}
186223

187224
return n

blockchain/blockindex_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2023 The utreexo developers
2+
// Use of this source code is governed by an ISC
3+
// license that can be found in the LICENSE file.
4+
5+
package blockchain
6+
7+
import (
8+
"math/rand"
9+
"testing"
10+
)
11+
12+
func TestAncestor(t *testing.T) {
13+
height := 500_000
14+
blockNodes := chainedNodes(nil, height)
15+
16+
for i, blockNode := range blockNodes {
17+
// Grab a random node that's a child of this node
18+
// and try to fetch the current blockNode with Ancestor.
19+
randNode := blockNodes[rand.Intn(height-i)+i]
20+
got := randNode.Ancestor(blockNode.height)
21+
22+
// See if we got the right one.
23+
if got.hash != blockNode.hash {
24+
t.Fatalf("expected ancestor at height %d "+
25+
"but got a node at height %d",
26+
blockNode.height, got.height)
27+
}
28+
29+
// Gensis doesn't have ancestors so skip the check below.
30+
if blockNode.height == 0 {
31+
continue
32+
}
33+
34+
// The ancestors are deterministic so check that this node's
35+
// ancestor is the correct one.
36+
if blockNode.ancestor.height != getAncestorHeight(blockNode.height) {
37+
t.Fatalf("expected anestor at height %d, but it was at %d",
38+
getAncestorHeight(blockNode.height),
39+
blockNode.ancestor.height)
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)