diff --git a/blockchain/claimtrie.go b/blockchain/claimtrie.go index b9913203..1c90665c 100644 --- a/blockchain/claimtrie.go +++ b/blockchain/claimtrie.go @@ -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 +} diff --git a/btcjson/claimcmds.go b/btcjson/claimcmds.go index 8f50fc0c..fd1b0b86 100644 --- a/btcjson/claimcmds.go +++ b/btcjson/claimcmds.go @@ -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"` +} diff --git a/claimtrie/claimtrie.go b/claimtrie/claimtrie.go index 55b75c71..46a2eeff 100644 --- a/claimtrie/claimtrie.go +++ b/claimtrie/claimtrie.go @@ -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 +} diff --git a/claimtrie/claimtrie_test.go b/claimtrie/claimtrie_test.go index 7cd1432b..20ee4eba 100644 --- a/claimtrie/claimtrie_test.go +++ b/claimtrie/claimtrie_test.go @@ -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)) +} diff --git a/claimtrie/merkletrie/merkletrie.go b/claimtrie/merkletrie/merkletrie.go index 3bc525fe..e92e2d43 100644 --- a/claimtrie/merkletrie/merkletrie.go +++ b/claimtrie/merkletrie/merkletrie.go @@ -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") +} diff --git a/claimtrie/merkletrie/ramtrie.go b/claimtrie/merkletrie/ramtrie.go index 7b426655..293661a4 100644 --- a/claimtrie/merkletrie/ramtrie.go +++ b/claimtrie/merkletrie/ramtrie.go @@ -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 +} diff --git a/claimtrie/node/hash_manager.go b/claimtrie/node/hash_manager.go deleted file mode 100644 index 8c4fc213..00000000 --- a/claimtrie/node/hash_manager.go +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/claimtrie/node/hashfork_manager.go b/claimtrie/node/hashfork_manager.go index bbd814ee..840b5202 100644 --- a/claimtrie/node/hashfork_manager.go +++ b/claimtrie/node/hashfork_manager.go @@ -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) +} diff --git a/claimtrie/node/hashfunc.go b/claimtrie/node/hashfunc.go index deec78bb..0fbeaf45 100644 --- a/claimtrie/node/hashfunc.go +++ b/claimtrie/node/hashfunc.go @@ -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 +} diff --git a/rpcclaimtrie.go b/rpcclaimtrie.go index 11706920..92ad7fe8 100644 --- a/rpcclaimtrie.go +++ b/rpcclaimtrie.go @@ -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 +}