Skip to content

Commit

Permalink
added proof RPC
Browse files Browse the repository at this point in the history
post-merge fixes
  • Loading branch information
BrannonKing committed Aug 14, 2021
1 parent ea7d936 commit 04bdc76
Show file tree
Hide file tree
Showing 10 changed files with 445 additions and 123 deletions.
69 changes: 69 additions & 0 deletions blockchain/claimtrie.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ package blockchain
import (
"bytes"
"fmt"
"strings"

"github.com/pkg/errors"

"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/claimtrie"
"github.com/btcsuite/btcd/claimtrie/change"
"github.com/btcsuite/btcd/claimtrie/merkletrie"
"github.com/btcsuite/btcd/claimtrie/node"
"github.com/btcsuite/btcd/claimtrie/param"
)

func (b *BlockChain) ParseClaimScripts(block *btcutil.Block, bn *blockNode, view *UtxoViewpoint,
Expand Down Expand Up @@ -166,3 +170,68 @@ func (b *BlockChain) GetClaimsForName(height int32, name string) (string, *node.
n.SortClaimsByBid()
return string(normalizedName), n, nil
}

func (b *BlockChain) GetProofForName(name, id string, bid, seq int) (chainhash.Hash, int32, *node.Claim, int32, int32, string, []merkletrie.HashSidePair, error) {
// results: block hash, height, claim, bid, takeover, name, pairs, err

b.chainLock.RLock()
defer b.chainLock.RUnlock()

tip := b.bestChain.Tip()

normalizedName := node.NormalizeIfNecessary([]byte(name), tip.height)

if tip.height < param.ActiveParams.GrandForkHeight {
err := errors.Errorf("Unable to generate proofs for claims before height %d",
param.ActiveParams.GrandForkHeight)
return tip.hash, tip.height, nil, 0, 0, string(normalizedName), nil, err
}

n, err := b.claimTrie.NodeAt(tip.height, normalizedName)
if n == nil && err == nil {
err = errors.Errorf("Unable to locate a claim with name %s at height %d", normalizedName, tip.height)
}
if err != nil {
return tip.hash, tip.height, nil, 0, 0, string(normalizedName), nil, err
}

// now find the desired claim
n.SortClaimsByBid()
var claim *node.Claim
for i, c := range n.Claims {
if c.Status != node.Activated {
continue
}
if bid >= 0 && i == bid {
claim = c
bid = i
break
}
if seq >= 0 && int(c.Sequence) == seq {
claim = c
bid = i
break
}
if len(id) > 0 && strings.HasPrefix(c.ClaimID.String(), id) {
claim = c
bid = i
break
}
}
if claim == nil {
if bid >= 0 {
err = errors.Errorf("Unable to locate a claim named %s with bid %d at height %d", normalizedName, bid, tip.height)
}
if seq >= 0 {
err = errors.Errorf("Unable to locate a claim named %s with sequence %d at height %d", normalizedName, seq, tip.height)
}
if len(id) > 0 {
err = errors.Errorf("Unable to locate a claim named %s with ID %s at height %d", normalizedName, id, tip.height)
}
return tip.hash, tip.height, nil, 0, 0, string(normalizedName), nil, err
}

pairs := b.claimTrie.MerklePath(normalizedName, n, bid)

return tip.hash, tip.height, claim, int32(bid), n.TakenOverAt, string(normalizedName), pairs, nil
}
37 changes: 37 additions & 0 deletions btcjson/claimcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ func init() {
MustRegisterCmd("getclaimsfornamebybid", (*GetClaimsForNameByBidCmd)(nil), flags)
MustRegisterCmd("getclaimsfornamebyseq", (*GetClaimsForNameBySeqCmd)(nil), flags)
MustRegisterCmd("normalize", (*GetNormalizedCmd)(nil), flags)

MustRegisterCmd("getprooffornamebyid", (*GetProofForNameByIDCmd)(nil), flags)
MustRegisterCmd("getprooffornamebybid", (*GetProofForNameByBidCmd)(nil), flags)
MustRegisterCmd("getprooffornamebyseq", (*GetProofForNameBySeqCmd)(nil), flags)
}

// optional inputs are required to be pointers, but they support things like `jsonrpcdefault:"false"`
Expand Down Expand Up @@ -93,3 +97,36 @@ type GetNormalizedCmd struct {
type GetNormalizedResult struct {
NormalizedName string `json:"normalizedname"`
}

type GetProofForNameByIDCmd struct {
Name string `json:"name"`
PartialClaimID string `json:"partialclaimid"`
}

type GetProofForNameByBidCmd struct {
Name string `json:"name"`
Bid int `json:"bid"`
}

type GetProofForNameBySeqCmd struct {
Name string `json:"name"`
Sequence int `json:"sequence"`
}

type ProofPairResult struct {
Right bool `json:"right"`
Hash string `json:"hash"`
}

type ProofResult struct { // should we include the claim trie hash?
BlockHash string `json:"blockhash"`
BlockHeight int32 `json:"blockheight"`
NormalizedName string `json:"normalizedname"`
ClaimID string `json:"claimid"`
TXID string `json:"txid"`
N uint32 `json:"n"`
Bid int32 `json:"bid"`
Sequence int32 `json:"sequence"`
Takeover int32 `json:"takeover"`
Pairs []ProofPairResult `json:"pairs"`
}
23 changes: 22 additions & 1 deletion claimtrie/claimtrie.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func removeDuplicates(names [][]byte) [][]byte { // this might be too expensive;
return names
}

// ResetHeight resets the ClaimTrie to a previous known height..
// ResetHeight resets the ClaimTrie to a previous known height.
func (ct *ClaimTrie) ResetHeight(height int32) error {

names := make([][]byte, 0)
Expand All @@ -316,6 +316,9 @@ func (ct *ClaimTrie) ResetHeight(height int32) error {
}

passedHashFork := ct.height >= param.ActiveParams.AllClaimsInMerkleForkHeight && height < param.ActiveParams.AllClaimsInMerkleForkHeight
if !passedHashFork {
passedHashFork = ct.height >= param.ActiveParams.GrandForkHeight && height < param.ActiveParams.GrandForkHeight
}
ct.height = height
hash, err := ct.blockRepo.Get(height)
if err != nil {
Expand Down Expand Up @@ -463,3 +466,21 @@ func (ct *ClaimTrie) makeNameHashNext(names [][]byte, all bool) chan NameHashNex
}()
return outputs
}

func (ct *ClaimTrie) MerklePath(name []byte, n *node.Node, bid int) []merkletrie.HashSidePair {
pairs := ct.merkleTrie.MerklePath(name)
// TODO: organize this code better
// this is the 2nd half of the above merkle tree computation
// it's done like this so we don't have to create the Node object multiple times
claimHashes := node.ComputeClaimHashes(name, n)
partials := node.ComputeMerklePath(claimHashes, bid)
for i := len(partials) - 1; i >= 0; i-- {
pairs = append(pairs, merkletrie.HashSidePair{Right: ((bid >> i) & 1) > 0, Hash: partials[i]})
}

// reverse the list order:
for i, j := 0, len(pairs)-1; i < j; i, j = i+1, j-1 {
pairs[i], pairs[j] = pairs[j], pairs[i]
}
return pairs
}
52 changes: 52 additions & 0 deletions claimtrie/claimtrie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcd/claimtrie/change"
"github.com/btcsuite/btcd/claimtrie/config"
"github.com/btcsuite/btcd/claimtrie/merkletrie"
"github.com/btcsuite/btcd/claimtrie/node"
"github.com/btcsuite/btcd/claimtrie/param"

"github.com/btcsuite/btcd/chaincfg/chainhash"
Expand Down Expand Up @@ -982,3 +983,54 @@ func TestBlock884431(t *testing.T) {
r.NoError(err)
r.Equal(o11.String(), n.BestClaim.OutPoint.String())
}

func TestMerklePath(t *testing.T) {
r := require.New(t)
setup(t)
param.ActiveParams.ActiveDelayFactor = 1
param.ActiveParams.NormalizedNameForkHeight = 5
param.ActiveParams.AllClaimsInMerkleForkHeight = 6
param.ActiveParams.GrandForkHeight = 7

ct, err := New(cfg)
r.NoError(err)
r.NotNil(ct)
defer ct.Close()

hash := chainhash.HashH([]byte{1, 2, 3})
o1 := wire.OutPoint{Hash: hash, Index: 1}
o2 := wire.OutPoint{Hash: hash, Index: 2}
o3 := wire.OutPoint{Hash: hash, Index: 3}

err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 1)
r.NoError(err)

err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 2)
r.NoError(err)

err = ct.AddClaim([]byte("tester"), o3, change.NewClaimID(o3), 1)
r.NoError(err)

for i := 0; i < 10; i++ {
err = ct.AppendBlock()
r.NoError(err)
}

n, err := ct.NodeAt(ct.height, []byte("test"))
r.NoError(err)
pairs := ct.MerklePath([]byte("test"), n, 0)
claimHash, err := node.ComputeBidSeqNameHash([]byte("test"), n.Claims[0], 0, n.TakenOverAt)
r.NoError(err)
validatePairs(r, pairs, ct.MerkleHash(), claimHash)
}

func validatePairs(r *require.Assertions, pairs []merkletrie.HashSidePair, target *chainhash.Hash, claimHash *chainhash.Hash) {
for i := range pairs {
if pairs[i].Right {
claimHash = node.HashMerkleBranches(pairs[i].Hash, claimHash)
} else {
claimHash = node.HashMerkleBranches(claimHash, pairs[i].Hash)
}
}
r.True(claimHash.IsEqual(target))
}
4 changes: 4 additions & 0 deletions claimtrie/merkletrie/merkletrie.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,7 @@ func (t *PersistentTrie) Dump(s string) {
func (t *PersistentTrie) Flush() error {
return t.repo.Flush()
}

func (t *PersistentTrie) MerklePath(name []byte) []HashSidePair {
panic("MerklePath not implemented in PersistentTrie")
}
70 changes: 61 additions & 9 deletions claimtrie/merkletrie/ramtrie.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type MerkleTrie interface {
MerkleHash() *chainhash.Hash
MerkleHashAllClaims() *chainhash.Hash
Flush() error
MerklePath(name []byte) []HashSidePair
}

type RamTrie struct {
Expand Down Expand Up @@ -117,29 +118,80 @@ func (rt *RamTrie) merkleHashAllClaims(v *collapsedVertex) *chainhash.Hash {
return v.merkleHash
}

childHashes := make([]*chainhash.Hash, 0, len(v.children))
for _, ch := range v.children {
h := rt.merkleHashAllClaims(ch)
childHashes = append(childHashes, h)
}
childHash, hasChildren := rt.computeChildHash(v)

claimHash := NoClaimsHash
if v.claimHash != nil {
claimHash = v.claimHash
} else if len(childHashes) == 0 {
} else if !hasChildren {
return nil
}

v.merkleHash = node.HashMerkleBranches(childHash, claimHash)
return v.merkleHash
}

func (rt *RamTrie) computeChildHash(v *collapsedVertex) (*chainhash.Hash, bool) {
childHashes := make([]*chainhash.Hash, 0, len(v.children))
for _, ch := range v.children {
h := rt.merkleHashAllClaims(ch)
childHashes = append(childHashes, h)
}
childHash := NoChildrenHash
if len(childHashes) > 0 {
// this shouldn't be referencing node; where else can we put this merkle root func?
childHash = node.ComputeMerkleRoot(childHashes)
}

v.merkleHash = node.HashMerkleBranches(childHash, claimHash)
return v.merkleHash
return childHash, len(childHashes) > 0
}

func (rt *RamTrie) Flush() error {
return nil
}

type HashSidePair struct {
Right bool
Hash *chainhash.Hash
}

func (rt *RamTrie) MerklePath(name []byte) []HashSidePair {

// algorithm:
// for each node in the path to key:
// get all the childHashes for that node and the index of our path
// get all the claimHashes for that node as well
// if we're at the end of the path:
// push(true, root(childHashes))
// push all of merklePath(claimHashes, bid)
// else
// push(false, root(claimHashes)
// push all of merklePath(childHashes, child index)

var results []HashSidePair

indexes, path := rt.FindPath(name)
for i := 0; i < len(indexes); i++ {
if i == len(indexes)-1 {
childHash, _ := rt.computeChildHash(path[i])
results = append(results, HashSidePair{Right: true, Hash: childHash})
// letting the caller append the claim hashes at present (needs better code organization)
} else {
ch := path[i].claimHash
if ch == nil {
ch = NoClaimsHash
}
results = append(results, HashSidePair{Right: false, Hash: ch})
childHashes := make([]*chainhash.Hash, 0, len(path[i].children))
for j := range path[i].children {
childHashes = append(childHashes, path[i].children[j].merkleHash)
}
if len(childHashes) > 0 {
partials := node.ComputeMerklePath(childHashes, indexes[i+1])
for i := len(partials) - 1; i >= 0; i-- {
results = append(results, HashSidePair{Right: ((indexes[i+1] >> i) & 1) > 0, Hash: partials[i]})
}
}
}
}
return results
}
Loading

0 comments on commit 04bdc76

Please sign in to comment.