added proof RPC

post-merge fixes

other part of test
post-merge fix
This commit is contained in:
Brannon King 2021-08-23 13:07:58 -04:00
parent 494a39c1ab
commit 9477313d74
10 changed files with 455 additions and 126 deletions

View file

@ -3,6 +3,7 @@ package blockchain
import (
"bytes"
"fmt"
"strings"
"github.com/pkg/errors"
@ -181,3 +182,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 := normalization.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
}

View file

@ -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"`
@ -95,3 +99,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"`
}

View file

@ -303,7 +303,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)
@ -320,6 +320,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
}
hash, err := ct.blockRepo.Get(height)
if err != nil {
return err
@ -486,3 +489,21 @@ func (ct *ClaimTrie) makeNameHashNext(names [][]byte, all bool, interrupt <-chan
}()
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
}

View file

@ -1025,3 +1025,66 @@ 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)
pairs = ct.MerklePath([]byte("test"), n, 1)
claimHash, err = node.ComputeBidSeqNameHash([]byte("test"), n.Claims[1], 1, n.TakenOverAt)
r.NoError(err)
validatePairs(r, pairs, ct.MerkleHash(), claimHash)
n, err = ct.NodeAt(ct.height, []byte("tester"))
r.NoError(err)
pairs = ct.MerklePath([]byte("tester"), n, 0)
claimHash, err = node.ComputeBidSeqNameHash([]byte("tester"), 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))
}

View file

@ -253,3 +253,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")
}

View file

@ -16,6 +16,7 @@ type MerkleTrie interface {
MerkleHash() *chainhash.Hash
MerkleHashAllClaims() *chainhash.Hash
Flush() error
MerklePath(name []byte) []HashSidePair
}
type RamTrie struct {
@ -111,29 +112,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
}
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
}
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)
}
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
}

View file

@ -1,111 +0,0 @@
package node
import (
"crypto/sha256"
"encoding/binary"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/claimtrie/change"
"github.com/btcsuite/btcd/claimtrie/param"
)
type HashV2Manager struct {
Manager
}
type HashV3Manager struct {
Manager
}
func (nm *HashV2Manager) claimHashes(name []byte) (*chainhash.Hash, int32) {
n, err := nm.NodeAt(nm.Height(), name)
if err != nil || n == nil {
return nil, 0
}
n.SortClaimsByBid()
claimHashes := make([]*chainhash.Hash, 0, len(n.Claims))
for _, c := range n.Claims {
if c.Status == Activated { // TODO: unit test this line
claimHashes = append(claimHashes, calculateNodeHash(c.OutPoint, n.TakenOverAt))
}
}
if len(claimHashes) > 0 {
return ComputeMerkleRoot(claimHashes), n.NextUpdate(nm.Height())
}
return nil, n.NextUpdate(nm.Height())
}
func (nm *HashV2Manager) Hash(name []byte) (*chainhash.Hash, int32) {
if nm.Height() >= param.ActiveParams.AllClaimsInMerkleForkHeight {
return nm.claimHashes(name)
}
return nm.Manager.Hash(name)
}
func (nm *HashV3Manager) AppendChange(chg change.Change) {
if nm.Height() >= param.ActiveParams.GrandForkHeight && len(chg.Name) == 0 {
return
}
nm.Manager.AppendChange(chg)
}
func calculateBidSeqNameHash(name []byte, c *Claim, bid, takeover int32) (*chainhash.Hash, error) {
s := sha256.New()
s.Write(c.OutPoint.Hash[:])
var temp [4]byte
binary.BigEndian.PutUint32(temp[:], c.OutPoint.Index)
s.Write(temp[:])
binary.BigEndian.PutUint32(temp[:], uint32(bid))
s.Write(temp[:])
binary.BigEndian.PutUint32(temp[:], uint32(c.Sequence))
s.Write(temp[:])
binary.BigEndian.PutUint32(temp[:], uint32(takeover))
s.Write(temp[:])
s.Write(name)
var m [sha256.Size]byte
return chainhash.NewHash(s.Sum(m[:0]))
}
func (nm *HashV3Manager) bidSeqNameHash(name []byte) (*chainhash.Hash, int32) {
n, err := nm.NodeAt(nm.Height(), name)
if err != nil || n == nil {
return nil, 0
}
n.SortClaimsByBid()
claimHashes := make([]*chainhash.Hash, 0, len(n.Claims))
for i, c := range n.Claims {
if c.Status == Activated {
h, _ := calculateBidSeqNameHash(name, c, int32(i), n.TakenOverAt)
claimHashes = append(claimHashes, h)
}
}
if len(claimHashes) > 0 {
return ComputeMerkleRoot(claimHashes), n.NextUpdate(nm.Height())
}
return nil, n.NextUpdate(nm.Height())
}
func (nm *HashV3Manager) Hash(name []byte) (*chainhash.Hash, int32) {
if nm.Height() >= param.ActiveParams.GrandForkHeight {
if len(name) == 0 {
return nil, 0 // empty name's claims are not included in the hash
}
return nm.bidSeqNameHash(name)
}
return nm.Manager.Hash(name)
}

View file

@ -1,6 +1,9 @@
package node
import (
"crypto/sha256"
"encoding/binary"
"github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/lbryio/lbcd/claimtrie/param"
)
@ -9,6 +12,10 @@ type HashV2Manager struct {
Manager
}
type HashV3Manager struct {
Manager
}
func (nm *HashV2Manager) computeClaimHashes(name []byte) (*chainhash.Hash, int32) {
n, err := nm.NodeAt(nm.Height(), name)
@ -24,9 +31,9 @@ func (nm *HashV2Manager) computeClaimHashes(name []byte) (*chainhash.Hash, int32
}
}
if len(claimHashes) > 0 {
return ComputeMerkleRoot(claimHashes), n.NextUpdate()
return ComputeMerkleRoot(claimHashes), n.NextUpdate(nm.Height())
}
return nil, n.NextUpdate()
return nil, n.NextUpdate(nm.Height())
}
func (nm *HashV2Manager) Hash(name []byte) (*chainhash.Hash, int32) {
@ -37,3 +44,72 @@ func (nm *HashV2Manager) Hash(name []byte) (*chainhash.Hash, int32) {
return nm.Manager.Hash(name)
}
func (nm *HashV3Manager) AppendChange(chg change.Change) {
if nm.Height() >= param.ActiveParams.GrandForkHeight && len(chg.Name) == 0 {
return
}
nm.Manager.AppendChange(chg)
}
func ComputeBidSeqNameHash(name []byte, c *Claim, bid, takeover int32) (*chainhash.Hash, error) {
s := sha256.New()
s.Write(c.OutPoint.Hash[:])
var temp [4]byte
binary.BigEndian.PutUint32(temp[:], c.OutPoint.Index)
s.Write(temp[:])
binary.BigEndian.PutUint32(temp[:], uint32(bid))
s.Write(temp[:])
binary.BigEndian.PutUint32(temp[:], uint32(c.Sequence))
s.Write(temp[:])
binary.BigEndian.PutUint32(temp[:], uint32(takeover))
s.Write(temp[:])
s.Write(name)
var m [sha256.Size]byte
return chainhash.NewHash(s.Sum(m[:0]))
}
func (nm *HashV3Manager) bidSeqNameHash(name []byte) (*chainhash.Hash, int32) {
n, err := nm.NodeAt(nm.Height(), name)
if err != nil || n == nil {
return nil, 0
}
claimHashes := ComputeClaimHashes(name, n)
if len(claimHashes) > 0 {
return ComputeMerkleRoot(claimHashes), n.NextUpdate(nm.Height())
}
return nil, n.NextUpdate(nm.Height())
}
func ComputeClaimHashes(name []byte, n *Node) []*chainhash.Hash {
n.SortClaimsByBid()
claimHashes := make([]*chainhash.Hash, 0, len(n.Claims))
for i, c := range n.Claims {
if c.Status == Activated {
h, _ := ComputeBidSeqNameHash(name, c, int32(i), n.TakenOverAt)
claimHashes = append(claimHashes, h)
}
}
return claimHashes
}
func (nm *HashV3Manager) Hash(name []byte) (*chainhash.Hash, int32) {
if nm.Height() >= param.ActiveParams.GrandForkHeight {
if len(name) == 0 {
return nil, 0 // empty name's claims are not included in the hash
}
return nm.bidSeqNameHash(name)
}
return nm.Manager.Hash(name)
}

View file

@ -55,3 +55,59 @@ func calculateNodeHash(op wire.OutPoint, takeover int32) *chainhash.Hash {
return &hh
}
func ComputeMerklePath(hashes []*chainhash.Hash, idx int) []*chainhash.Hash {
count := 0
matchlevel := -1
matchh := false
var h *chainhash.Hash
var res []*chainhash.Hash
var inner [32]*chainhash.Hash // old code had 32; dunno if it's big enough for all scenarios
iterateInner := func(level int) int {
for ; (count & (1 << level)) == 0; level++ {
ihash := inner[level]
if matchh {
res = append(res, ihash)
} else if matchlevel == level {
res = append(res, h)
matchh = true
}
h = HashMerkleBranches(ihash, h)
}
return level
}
for count < len(hashes) {
h = hashes[count]
matchh = count == idx
count++
level := iterateInner(0)
// Store the resulting hash at inner position level.
inner[level] = h
if matchh {
matchlevel = level
}
}
level := 0
for (count & (1 << level)) == 0 {
level++
}
h = inner[level]
matchh = matchlevel == level
for count != (1 << level) {
// If we reach this point, h is an inner value that is not the top.
if matchh {
res = append(res, h)
}
h = HashMerkleBranches(h, h)
// Increment count to the value it would have if two entries at this
count += 1 << level
level++
level = iterateInner(level)
}
return res
}

View file

@ -22,6 +22,10 @@ var claimtrieHandlers = map[string]commandHandler{
"getclaimsfornamebybid": handleGetClaimsForNameByBid,
"getclaimsfornamebyseq": handleGetClaimsForNameBySeq,
"normalize": handleGetNormalized,
"getprooffornamebyid": handleGetProofForNameByID,
"getprooffornamebybid": handleGetProofForNameByBid,
"getprooffornamebyseq": handleGetProofForNameBySeq,
}
func handleGetChangesInBlock(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
@ -350,3 +354,64 @@ func handleGetNormalized(_ *rpcServer, cmd interface{}, _ <-chan struct{}) (inte
}
return r, nil
}
func handleGetProofForNameByID(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.GetProofForNameByIDCmd)
return getProof(s, c.Name, c.PartialClaimID, -1, -1)
}
func handleGetProofForNameByBid(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.GetProofForNameByBidCmd)
return getProof(s, c.Name, "", c.Bid, -1)
}
func handleGetProofForNameBySeq(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.GetProofForNameBySeqCmd)
return getProof(s, c.Name, "", -1, c.Sequence)
}
func getProof(s *rpcServer, name, id string, bid, seq int) (interface{}, error) {
if !s.cfg.Chain.IsCurrent() {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCClientInInitialDownload,
Message: "Unable to query the chain tip during initial download",
}
}
if len(name) == 0 {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: "name is required and cannot be empty",
}
}
bh, h, c, b, t, n, pairs, err := s.cfg.Chain.GetProofForName(name, id, bid, seq)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCMisc,
Message: "Message: " + err.Error(),
}
}
results := make([]btcjson.ProofPairResult, 0, len(pairs))
for i := 0; i < len(pairs); i++ {
results = append(results, btcjson.ProofPairResult{Right: pairs[i].Right, Hash: pairs[i].Hash.String()})
}
return btcjson.ProofResult{
BlockHash: bh.String(),
BlockHeight: h,
NormalizedName: n,
ClaimID: c.ClaimID.String(),
TXID: c.OutPoint.Hash.String(),
N: c.OutPoint.Index,
Bid: b,
Sequence: c.Sequence,
Takeover: t,
Pairs: results,
}, nil
}