Skip to content

Commit

Permalink
Merge pull request #88 from kcalvinalvin/2023-11-17-better-ancestor
Browse files Browse the repository at this point in the history
blockchain: better Ancestor with skiplists
  • Loading branch information
kcalvinalvin committed Nov 17, 2023
2 parents 877de3e + 79ef5a3 commit 5d19b98
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 2 deletions.
13 changes: 13 additions & 0 deletions blockchain/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,16 @@ func BenchmarkIsCoinBaseTx(b *testing.B) {
IsCoinBaseTx(tx)
}
}

func BenchmarkAncestor(b *testing.B) {
height := 1 << 19
blockNodes := chainedNodes(nil, height)

b.ResetTimer()
for i := 0; i < b.N; i++ {
blockNodes[len(blockNodes)-1].Ancestor(0)
for j := 0; j <= 19; j++ {
blockNodes[len(blockNodes)-1].Ancestor(1 << j)
}
}
}
41 changes: 39 additions & 2 deletions blockchain/blockindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ type blockNode struct {
// parent is the parent block for this node.
parent *blockNode

// ancestor is a block that is more than one block back from this node.
ancestor *blockNode

// hash is the double sha 256 of the block.
hash chainhash.Hash

Expand Down Expand Up @@ -137,6 +140,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *block
node.parent = parent
node.height = parent.height + 1
node.workSum = node.workSum.Add(parent.workSum, node.workSum)
node.buildAncestor()
}
}

Expand Down Expand Up @@ -168,6 +172,26 @@ func (node *blockNode) Header() wire.BlockHeader {
}
}

// invertLowestOne turns the lowest 1 bit in the binary representation of a number into a 0.
func invertLowestOne(n int32) int32 {
return n & (n - 1)
}

// getAncestorHeight returns a suitable ancestor for the node at the given height.
func getAncestorHeight(height int32) int32 {
// We pop off two 1 bits of the height.
// This results in a maximum of 330 steps to go back to an ancestor
// from height 1<<29.
return invertLowestOne(invertLowestOne(height))
}

// buildAncestor sets an ancestor for the given blocknode.
func (node *blockNode) buildAncestor() {
if node.parent != nil {
node.ancestor = node.parent.Ancestor(getAncestorHeight(node.height))
}
}

// Ancestor returns the ancestor block node at the provided height by following
// the chain backwards from this node. The returned block will be nil when a
// height is requested that is after the height of the passed node or is less
Expand All @@ -179,9 +203,22 @@ func (node *blockNode) Ancestor(height int32) *blockNode {
return nil
}

// Traverse back until we find the desired node.
n := node
for ; n != nil && n.height != height; n = n.parent {
// Intentionally left blank
for n != nil && n.height != height {
// If there's an ancestor available, use it. Otherwise, just
// follow the parent.
if n.ancestor != nil {
// Calculate the height for this ancestor and
// check if we can take the ancestor skip.
if getAncestorHeight(n.height) >= height {
n = n.ancestor
continue
}
}

// We couldn't take the ancestor skip so traverse back to the parent.
n = n.parent
}

return n
Expand Down
42 changes: 42 additions & 0 deletions blockchain/blockindex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2023 The utreexo developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
"math/rand"
"testing"
)

func TestAncestor(t *testing.T) {
height := 500_000
blockNodes := chainedNodes(nil, height)

for i, blockNode := range blockNodes {
// Grab a random node that's a child of this node
// and try to fetch the current blockNode with Ancestor.
randNode := blockNodes[rand.Intn(height-i)+i]
got := randNode.Ancestor(blockNode.height)

// See if we got the right one.
if got.hash != blockNode.hash {
t.Fatalf("expected ancestor at height %d "+
"but got a node at height %d",
blockNode.height, got.height)
}

// Gensis doesn't have ancestors so skip the check below.
if blockNode.height == 0 {
continue
}

// The ancestors are deterministic so check that this node's
// ancestor is the correct one.
if blockNode.ancestor.height != getAncestorHeight(blockNode.height) {
t.Fatalf("expected anestor at height %d, but it was at %d",
getAncestorHeight(blockNode.height),
blockNode.ancestor.height)
}
}
}

0 comments on commit 5d19b98

Please sign in to comment.