diff --git a/blockchain/claimtrie.go b/blockchain/claimtrie.go index a3b01051..9605b398 100644 --- a/blockchain/claimtrie.go +++ b/blockchain/claimtrie.go @@ -139,3 +139,30 @@ func (h *handler) handleTxOuts(ct *claimtrie.ClaimTrie) error { } return nil } + +func (b *BlockChain) GetNamesChangedInBlock(height int32) ([]string, error) { + b.chainLock.RLock() + defer b.chainLock.RUnlock() + + return b.claimTrie.NamesChangedInBlock(height) +} + +func (b *BlockChain) GetClaimsForName(height int32, name string) (string, *node.Node, error) { + + normalizedName := node.NormalizeIfNecessary([]byte(name), height) + + b.chainLock.RLock() + defer b.chainLock.RUnlock() + + n, err := b.claimTrie.NodeAt(height, normalizedName) + if err != nil { + return string(normalizedName), nil, err + } + + if n == nil { + return string(normalizedName), nil, fmt.Errorf("name does not exist at height %d: %s", height, name) + } + + n.SortClaimsByBid() + return string(normalizedName), n, nil +} diff --git a/btcjson/claimcmds.go b/btcjson/claimcmds.go new file mode 100644 index 00000000..cb98fbf8 --- /dev/null +++ b/btcjson/claimcmds.go @@ -0,0 +1,95 @@ +package btcjson + +func init() { + // No special flags for commands in this file. + flags := UsageFlag(0) + + MustRegisterCmd("getchangesinblock", (*GetChangesInBlockCmd)(nil), flags) + MustRegisterCmd("getclaimsforname", (*GetClaimsForNameCmd)(nil), flags) + MustRegisterCmd("getclaimsfornamebyid", (*GetClaimsForNameByIDCmd)(nil), flags) + MustRegisterCmd("getclaimsfornamebybid", (*GetClaimsForNameByBidCmd)(nil), flags) + MustRegisterCmd("getclaimsfornamebyseq", (*GetClaimsForNameBySeqCmd)(nil), flags) + MustRegisterCmd("normalize", (*GetNormalizedCmd)(nil), flags) +} + +// optional inputs are required to be pointers, but they support things like `jsonrpcdefault:"false"` +// optional inputs have to be at the bottom of the struct +// optional outputs require ",omitempty" +// traditional bitcoin fields are all lowercase + +type GetChangesInBlockCmd struct { + HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""` +} + +type GetChangesInBlockResult struct { + Hash string `json:"hash"` + Height int32 `json:"height"` + Names []string `json:"names"` +} + +type GetClaimsForNameCmd struct { + Name string `json:"name"` + HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""` + IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"` +} + +type GetClaimsForNameByIDCmd struct { + Name string `json:"name"` + PartialClaimIDs []string `json:"partialclaimids"` + HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""` + IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"` +} + +type GetClaimsForNameByBidCmd struct { + Name string `json:"name"` + Bids []int32 `json:"bids"` + HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""` + IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"` +} + +type GetClaimsForNameBySeqCmd struct { + Name string `json:"name"` + Sequences []int32 `json:"sequences" jsonrpcusage:"[sequence,...]"` + HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""` + IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"` +} + +type GetClaimsForNameResult struct { + Hash string `json:"hash"` + Height int32 `json:"height"` + NormalizedName string `json:"normalizedname"` + Claims []ClaimResult `json:"claims"` + // UnclaimedSupports []SupportResult `json:"unclaimedSupports"` how would this work with other constraints? +} + +type SupportResult struct { + TXID string `json:"txid"` + N uint32 `json:"n"` + Height int32 `json:"height"` + ValidAtHeight int32 `json:"validatheight"` + Amount int64 `json:"amount"` + Address string `json:"address,omitempty"` + Value string `json:"value,omitempty"` +} + +type ClaimResult struct { + ClaimID string `json:"claimid"` + TXID string `json:"txid"` + N uint32 `json:"n"` + Bid int32 `json:"bid"` + Sequence int32 `json:"sequence"` + Height int32 `json:"height"` + ValidAtHeight int32 `json:"validatheight"` + EffectiveAmount int64 `json:"effectiveamount"` + Supports []SupportResult `json:"supports,omitempty"` + Address string `json:"address,omitempty"` + Value string `json:"value,omitempty"` +} + +type GetNormalizedCmd struct { + Name string `json:"name"` +} + +type GetNormalizedResult struct { + NormalizedName string `json:"normalizedname"` +} diff --git a/claimtrie/claimtrie.go b/claimtrie/claimtrie.go index 83751a53..7bcde646 100644 --- a/claimtrie/claimtrie.go +++ b/claimtrie/claimtrie.go @@ -359,8 +359,17 @@ func (ct *ClaimTrie) forwardNodeChange(chg change.Change) error { return nil } -func (ct *ClaimTrie) Node(name []byte) (*node.Node, error) { - return ct.nodeManager.Node(name) +func (ct *ClaimTrie) NodeAt(height int32, name []byte) (*node.Node, error) { + return ct.nodeManager.NodeAt(height, name) +} + +func (ct *ClaimTrie) NamesChangedInBlock(height int32) ([]string, error) { + hits, err := ct.temporalRepo.NodesAt(height) + r := make([]string, len(hits)) + for i := range hits { + r[i] = string(hits[i]) + } + return r, err } func (ct *ClaimTrie) FlushToDisk() { diff --git a/claimtrie/claimtrie_test.go b/claimtrie/claimtrie_test.go index 455a1ccb..162cbc1b 100644 --- a/claimtrie/claimtrie_test.go +++ b/claimtrie/claimtrie_test.go @@ -110,7 +110,7 @@ func TestNormalizationFork(t *testing.T) { r.NoError(err) r.NotEqual(merkletrie.EmptyTrieHash[:], ct.MerkleHash()[:]) - n, err := ct.nodeManager.Node([]byte("AÑEJO")) + n, err := ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte("AÑEJO")) r.NoError(err) r.NotNil(n.BestClaim) r.Equal(int32(1), n.TakenOverAt) @@ -123,7 +123,7 @@ func TestNormalizationFork(t *testing.T) { r.NoError(err) r.NotEqual(merkletrie.EmptyTrieHash[:], ct.MerkleHash()[:]) - n, err = ct.nodeManager.Node([]byte("añejo")) + n, err = ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte("añejo")) r.NoError(err) r.Equal(3, len(n.Claims)) r.Equal(uint32(1), n.BestClaim.OutPoint.Index) @@ -212,7 +212,7 @@ func verifyBestIndex(t *testing.T, ct *ClaimTrie, name string, idx uint32, claim r := require.New(t) - n, err := ct.nodeManager.Node([]byte(name)) + n, err := ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte(name)) r.NoError(err) r.Equal(claims, len(n.Claims)) if claims > 0 { diff --git a/claimtrie/cmd/cmd/node.go b/claimtrie/cmd/cmd/node.go index 0034221c..bcd1183b 100644 --- a/claimtrie/cmd/cmd/node.go +++ b/claimtrie/cmd/cmd/node.go @@ -90,12 +90,7 @@ var nodeReplayCmd = &cobra.Command{ } nm := node.NewNormalizingManager(bm) - _, err = nm.IncrementHeightTo(int32(height)) - if err != nil { - return fmt.Errorf("increment height: %w", err) - } - - n, err := nm.Node(name) + n, err := nm.NodeAt(int32(height), name) if err != nil || n == nil { return fmt.Errorf("get node: %w", err) } diff --git a/claimtrie/cmd/cmd/ui.go b/claimtrie/cmd/cmd/ui.go index bbfc52ca..28eb6fe8 100644 --- a/claimtrie/cmd/cmd/ui.go +++ b/claimtrie/cmd/cmd/ui.go @@ -54,7 +54,7 @@ func showNode(n *node.Node) { fmt.Printf("%s\n", strings.Repeat("-", 200)) fmt.Printf("Last Node Takeover: %d\n\n", n.TakenOverAt) - n.SortClaims() + n.SortClaimsByBid() for _, c := range n.Claims { showClaim(c, n) for _, s := range n.Supports { diff --git a/claimtrie/merkletrie/collapsedtrie.go b/claimtrie/merkletrie/collapsedtrie.go index 7544aa25..262bc7e6 100644 --- a/claimtrie/merkletrie/collapsedtrie.go +++ b/claimtrie/merkletrie/collapsedtrie.go @@ -181,18 +181,18 @@ func (pt *collapsedTrie) FindPath(value KeyType) ([]int, []*collapsedVertex) { // IterateFrom can be used to find a value and run a function on that value. // If the handler returns true it continues to iterate through the children of value. -func (pt *collapsedTrie) IterateFrom(start KeyType, handler func(value *collapsedVertex) bool) { +func (pt *collapsedTrie) IterateFrom(start KeyType, handler func(name KeyType, value *collapsedVertex) bool) { node := find(start, pt.Root, nil, nil) if node == nil { return } - iterateFrom(node, handler) + iterateFrom(start, node, handler) } -func iterateFrom(node *collapsedVertex, handler func(value *collapsedVertex) bool) { - for handler(node) { +func iterateFrom(name KeyType, node *collapsedVertex, handler func(name KeyType, value *collapsedVertex) bool) { + for handler(name, node) { for _, child := range node.children { - iterateFrom(child, handler) + iterateFrom(append(name, child.key...), child, handler) } } } diff --git a/claimtrie/node/claim.go b/claimtrie/node/claim.go index 8b456f82..2108142c 100644 --- a/claimtrie/node/claim.go +++ b/claimtrie/node/claim.go @@ -21,13 +21,16 @@ const ( // Claim defines a structure of stake, which could be a Claim or Support. type Claim struct { - OutPoint wire.OutPoint - ClaimID change.ClaimID - Amount int64 - AcceptedAt int32 // when arrived (aka, originally landed in block) + OutPoint wire.OutPoint + ClaimID change.ClaimID + Amount int64 + // CreatedAt int32 // the very first block, unused at present + AcceptedAt int32 // the latest update height ActiveAt int32 // AcceptedAt + actual delay - Status Status VisibleAt int32 + + Status Status `msgpack:",omitempty"` + Sequence int32 `msgpack:",omitempty"` } func (c *Claim) setOutPoint(op wire.OutPoint) *Claim { diff --git a/claimtrie/node/manager.go b/claimtrie/node/manager.go index 8995e7c6..06fcf97c 100644 --- a/claimtrie/node/manager.go +++ b/claimtrie/node/manager.go @@ -21,7 +21,7 @@ type Manager interface { DecrementHeightTo(affectedNames [][]byte, height int32) error Height() int32 Close() error - Node(name []byte) (*Node, error) + NodeAt(height int32, name []byte) (*Node, error) NextUpdateHeightOfNode(name []byte) ([]byte, int32) IterateNames(predicate func(name []byte) bool) Hash(name []byte) *chainhash.Hash @@ -97,9 +97,24 @@ func NewBaseManager(repo Repo) (*BaseManager, error) { return nm, nil } +func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) { + + changes, err := nm.repo.LoadChanges(name) + if err != nil { + return nil, errors.Wrap(err, "in load changes") + } + + n, err := nm.newNodeFromChanges(changes, height) + if err != nil { + return nil, errors.Wrap(err, "in new node") + } + + return n, nil +} + // Node returns a node at the current height. // The returned node may have pending changes. -func (nm *BaseManager) Node(name []byte) (*Node, error) { +func (nm *BaseManager) node(name []byte) (*Node, error) { nameStr := string(name) n := nm.cache.Get(nameStr) @@ -107,22 +122,13 @@ func (nm *BaseManager) Node(name []byte) (*Node, error) { return n.AdjustTo(nm.height, -1, name), nil } - changes, err := nm.repo.LoadChanges(name) - if err != nil { - return nil, errors.Wrap(err, "in load changes") + n, err := nm.NodeAt(nm.height, name) + + if n != nil && err == nil { + nm.cache.Put(nameStr, n) } - n, err = nm.newNodeFromChanges(changes, nm.height) - if err != nil { - return nil, errors.Wrap(err, "in new node") - } - - if n == nil { // they've requested a nonexistent or expired name - return nil, nil - } - - nm.cache.Put(nameStr, n) - return n, nil + return n, err } // newNodeFromChanges returns a new Node constructed from the changes. @@ -341,7 +347,7 @@ func calculateDelay(curr, tookOver int32) int32 { func (nm BaseManager) NextUpdateHeightOfNode(name []byte) ([]byte, int32) { - n, err := nm.Node(name) + n, err := nm.node(name) if err != nil || n == nil { return name, 0 } @@ -393,12 +399,12 @@ func (nm *BaseManager) IterateNames(predicate func(name []byte) bool) { func (nm *BaseManager) claimHashes(name []byte) *chainhash.Hash { - n, err := nm.Node(name) + n, err := nm.node(name) if err != nil || n == nil { return nil } - n.SortClaims() + n.SortClaimsByBid() claimHashes := make([]*chainhash.Hash, 0, len(n.Claims)) for _, c := range n.Claims { if c.Status == Activated { // TODO: unit test this line @@ -417,7 +423,7 @@ func (nm *BaseManager) Hash(name []byte) *chainhash.Hash { return nm.claimHashes(name) } - n, err := nm.Node(name) + n, err := nm.node(name) if err != nil { return nil } diff --git a/claimtrie/node/manager_test.go b/claimtrie/node/manager_test.go index 2164c1fe..6a335715 100644 --- a/claimtrie/node/manager_test.go +++ b/claimtrie/node/manager_test.go @@ -69,25 +69,25 @@ func TestSimpleAddClaim(t *testing.T) { _, err = m.IncrementHeightTo(12) r.NoError(err) - n1, err := m.Node(name1) + n1, err := m.node(name1) r.NoError(err) r.Equal(1, len(n1.Claims)) r.NotNil(n1.Claims.find(byOut(*out1))) - n2, err := m.Node(name2) + n2, err := m.node(name2) r.NoError(err) r.Equal(1, len(n2.Claims)) r.NotNil(n2.Claims.find(byOut(*out2))) err = m.DecrementHeightTo([][]byte{name2}, 11) r.NoError(err) - n2, err = m.Node(name2) + n2, err = m.node(name2) r.NoError(err) r.Nil(n2) err = m.DecrementHeightTo([][]byte{name1}, 1) r.NoError(err) - n2, err = m.Node(name1) + n2, err = m.node(name1) r.NoError(err) r.Nil(n2) } @@ -138,7 +138,7 @@ func TestSupportAmounts(t *testing.T) { _, err = m.IncrementHeightTo(20) r.NoError(err) - n1, err := m.Node(name1) + n1, err := m.node(name1) r.NoError(err) r.Equal(2, len(n1.Claims)) r.Equal(int64(5), n1.BestClaim.Amount+n1.SupportSums[n1.BestClaim.ClaimID.Key()]) @@ -178,7 +178,7 @@ func TestClaimSort(t *testing.T) { n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 3, Amount: 2, ClaimID: change.ClaimID{3}}) n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 4, Amount: 2, ClaimID: change.ClaimID{4}}) n.Claims = append(n.Claims, &Claim{OutPoint: *out1, AcceptedAt: 3, Amount: 4, ClaimID: change.ClaimID{1}}) - n.SortClaims() + n.SortClaimsByBid() r.Equal(int64(4), n.Claims[0].Amount) r.Equal(int64(3), n.Claims[1].Amount) diff --git a/claimtrie/node/node.go b/claimtrie/node/node.go index eff5989f..55afd547 100644 --- a/claimtrie/node/node.go +++ b/claimtrie/node/node.go @@ -2,7 +2,6 @@ package node import ( "fmt" - "github.com/pkg/errors" "math" "sort" @@ -37,17 +36,19 @@ func (n *Node) ApplyChange(chg change.Change, delay int32) error { switch chg.Type { case change.AddClaim: c := &Claim{ - OutPoint: chg.OutPoint, - Amount: chg.Amount, - ClaimID: chg.ClaimID, - AcceptedAt: chg.Height, // not tracking original height in this version (but we could) + OutPoint: chg.OutPoint, + Amount: chg.Amount, + ClaimID: chg.ClaimID, + // CreatedAt: chg.Height, + AcceptedAt: chg.Height, ActiveAt: chg.Height + delay, VisibleAt: visibleAt, + Sequence: int32(len(n.Claims)), } - old := n.Claims.find(byOut(chg.OutPoint)) // TODO: remove this after proving ResetHeight works - if old != nil { - return errors.Errorf("CONFLICT WITH EXISTING TXO! Name: %s, Height: %d", chg.Name, chg.Height) - } + // old := n.Claims.find(byOut(chg.OutPoint)) // TODO: remove this after proving ResetHeight works + // if old != nil { + // return errors.Errorf("CONFLICT WITH EXISTING TXO! Name: %s, Height: %d", chg.Name, chg.Height) + // } n.Claims = append(n.Claims, c) case change.SpendClaim: @@ -72,10 +73,10 @@ func (n *Node) ApplyChange(chg change.Change, delay int32) error { c.setStatus(Accepted) // it was Deactivated in the spend (but we only activate at the end of the block) // that's because the old code would put all insertions into the "queue" that was processed at block's end - // It's a bug, but the old code would update these. - // That forces this to be newer, which may in an unintentional takeover if there's an older one. - c.setAccepted(chg.Height) // TODO: Fork this out - c.setActiveAt(chg.Height + delay) // TODO: Fork this out + // This forces us to be newer, which may in an unintentional takeover if there's an older one. + // TODO: reconsider these updates in future hard forks. + c.setAccepted(chg.Height) + c.setActiveAt(chg.Height + delay) } else { LogOnce(fmt.Sprintf("Updating claim but missing existing claim with ID %s", chg.ClaimID)) @@ -291,7 +292,7 @@ func (n *Node) activateAllClaims(height int32) int { return count } -func (n *Node) SortClaims() { +func (n *Node) SortClaimsByBid() { // purposefully sorting by descent sort.Slice(n.Claims, func(j, i int) bool { diff --git a/claimtrie/node/normalizing_manager.go b/claimtrie/node/normalizing_manager.go index 150ef60c..da88d132 100644 --- a/claimtrie/node/normalizing_manager.go +++ b/claimtrie/node/normalizing_manager.go @@ -72,7 +72,7 @@ func (nm *NormalizingManager) addNormalizationForkChangesIfNecessary(height int3 // by loading changes for norm here, you can determine if there will be a conflict - n, err := nm.Manager.Node(clone) + n, err := nm.Manager.NodeAt(nm.Manager.Height(), clone) if err != nil || n == nil { return true } diff --git a/rpcclaimtrie.go b/rpcclaimtrie.go new file mode 100644 index 00000000..0677637b --- /dev/null +++ b/rpcclaimtrie.go @@ -0,0 +1,342 @@ +package main + +import ( + "bytes" + "encoding/hex" + "strconv" + "strings" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/claimtrie/node" + "github.com/btcsuite/btcd/database" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +var claimtrieHandlers = map[string]commandHandler{ + "getchangesinblock": handleGetChangesInBlock, + "getclaimsforname": handleGetClaimsForName, + "getclaimsfornamebyid": handleGetClaimsForNameByID, + "getclaimsfornamebybid": handleGetClaimsForNameByBid, + "getclaimsfornamebyseq": handleGetClaimsForNameBySeq, + "normalize": handleGetNormalized, +} + +func handleGetChangesInBlock(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) { + + c := cmd.(*btcjson.GetChangesInBlockCmd) + hash, height, err := parseHashOrHeight(s, c.HashOrHeight) + if err != nil { + return nil, err + } + + names, err := s.cfg.Chain.GetNamesChangedInBlock(height) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Message: " + err.Error(), + } + } + + return btcjson.GetChangesInBlockResult{ + Hash: hash, + Height: height, + Names: names, + }, nil +} + +func parseHashOrHeight(s *rpcServer, hashOrHeight *string) (string, int32, error) { + if hashOrHeight == nil || len(*hashOrHeight) == 0 { + + if !s.cfg.Chain.IsCurrent() { + return "", 0, &btcjson.RPCError{ + Code: btcjson.ErrRPCClientInInitialDownload, + Message: "Unable to query the chain tip during initial download", + } + } + + // just give them the latest block if a specific one wasn't requested + best := s.cfg.Chain.BestSnapshot() + return best.Hash.String(), best.Height, nil + } + + ht, err := strconv.ParseInt(*hashOrHeight, 10, 32) + if err == nil && len(*hashOrHeight) < 32 { + hs, err := s.cfg.Chain.BlockHashByHeight(int32(ht)) + if err != nil { + return "", 0, &btcjson.RPCError{ + Code: btcjson.ErrRPCBlockNotFound, + Message: "Unable to locate a block at height " + *hashOrHeight + ": " + err.Error(), + } + } + return hs.String(), int32(ht), nil + } + + hs, err := chainhash.NewHashFromStr(*hashOrHeight) + if err != nil { + return "", 0, &btcjson.RPCError{ + Code: btcjson.ErrRPCInvalidParameter, + Message: "Unable to parse a height or hash from " + *hashOrHeight + ": " + err.Error(), + } + } + h, err := s.cfg.Chain.BlockHeightByHash(hs) + if err != nil { + return hs.String(), h, &btcjson.RPCError{ + Code: btcjson.ErrRPCBlockNotFound, + Message: "Unable to find a block with hash " + hs.String() + ": " + err.Error(), + } + } + return hs.String(), h, nil +} + +func handleGetClaimsForName(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) { + + c := cmd.(*btcjson.GetClaimsForNameCmd) + hash, height, err := parseHashOrHeight(s, c.HashOrHeight) + if err != nil { + return nil, err + } + + name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Message: " + err.Error(), + } + } + + var results []btcjson.ClaimResult + for i := range n.Claims { + cr, err := toClaimResult(s, int32(i), n, c.IncludeValues) + if err != nil { + return nil, err + } + results = append(results, cr) + } + + return btcjson.GetClaimsForNameResult{ + Hash: hash, + Height: height, + NormalizedName: name, + Claims: results, + }, nil +} + +func handleGetClaimsForNameByID(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) { + + c := cmd.(*btcjson.GetClaimsForNameByIDCmd) + hash, height, err := parseHashOrHeight(s, c.HashOrHeight) + if err != nil { + return nil, err + } + + name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Message: " + err.Error(), + } + } + + var results []btcjson.ClaimResult + for i := 0; i < len(n.Claims); i++ { + for _, id := range c.PartialClaimIDs { + if strings.HasPrefix(n.Claims[i].ClaimID.String(), id) { + cr, err := toClaimResult(s, int32(i), n, c.IncludeValues) + if err != nil { + return nil, err + } + results = append(results, cr) + break + } + } + } + + return btcjson.GetClaimsForNameResult{ + Hash: hash, + Height: height, + NormalizedName: name, + Claims: results, + }, nil +} + +func handleGetClaimsForNameByBid(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) { + + c := cmd.(*btcjson.GetClaimsForNameByBidCmd) + hash, height, err := parseHashOrHeight(s, c.HashOrHeight) + if err != nil { + return nil, err + } + + name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Message: " + err.Error(), + } + } + + var results []btcjson.ClaimResult + for _, b := range c.Bids { // claims are already sorted in bid order + if b >= 0 && int(b) < len(n.Claims) { + cr, err := toClaimResult(s, b, n, c.IncludeValues) + if err != nil { + return nil, err + } + results = append(results, cr) + } + } + + return btcjson.GetClaimsForNameResult{ + Hash: hash, + Height: height, + NormalizedName: name, + Claims: results, + }, nil +} + +func handleGetClaimsForNameBySeq(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) { + + c := cmd.(*btcjson.GetClaimsForNameBySeqCmd) + hash, height, err := parseHashOrHeight(s, c.HashOrHeight) + if err != nil { + return nil, err + } + + name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCMisc, + Message: "Message: " + err.Error(), + } + } + + sm := map[int32]bool{} + for _, seq := range c.Sequences { + sm[seq] = true + } + + var results []btcjson.ClaimResult + for i := 0; i < len(n.Claims); i++ { + if sm[n.Claims[i].Sequence] { + cr, err := toClaimResult(s, int32(i), n, c.IncludeValues) + if err != nil { + return nil, err + } + results = append(results, cr) + } + } + + return btcjson.GetClaimsForNameResult{ + Hash: hash, + Height: height, + NormalizedName: name, + Claims: results, + }, nil +} + +func toClaimResult(s *rpcServer, i int32, node *node.Node, includeValues *bool) (btcjson.ClaimResult, error) { + claim := node.Claims[i] + address, value, err := lookupValue(s, claim.OutPoint, includeValues) + supports, err := toSupportResults(s, i, node, includeValues) + return btcjson.ClaimResult{ + ClaimID: claim.ClaimID.String(), + Height: claim.AcceptedAt, + ValidAtHeight: claim.ActiveAt, + TXID: claim.OutPoint.Hash.String(), + N: claim.OutPoint.Index, + Bid: i, // assuming sorted by bid + EffectiveAmount: claim.Amount + node.SupportSums[claim.ClaimID.Key()], + Sequence: claim.Sequence, + Supports: supports, + Address: address, + Value: value, + }, err +} + +func toSupportResults(s *rpcServer, i int32, n *node.Node, includeValues *bool) ([]btcjson.SupportResult, error) { + var results []btcjson.SupportResult + c := n.Claims[i] + for _, sup := range n.Supports { + if sup.Status == node.Activated && c.ClaimID == sup.ClaimID { + address, value, err := lookupValue(s, sup.OutPoint, includeValues) + if err != nil { + return results, err + } + results = append(results, btcjson.SupportResult{ + TXID: sup.OutPoint.Hash.String(), + N: sup.OutPoint.Index, + Height: sup.AcceptedAt, + ValidAtHeight: sup.ActiveAt, + Amount: sup.Amount, + Value: value, + Address: address, + }) + } + } + return results, nil +} + +func lookupValue(s *rpcServer, outpoint wire.OutPoint, includeValues *bool) (string, string, error) { + if includeValues == nil || !*includeValues { + return "", "", nil + } + // TODO: maybe use addrIndex if the txIndex is not available + + if s.cfg.TxIndex == nil { + return "", "", &btcjson.RPCError{ + Code: btcjson.ErrRPCNoTxInfo, + Message: "The transaction index must be " + + "enabled to query the blockchain " + + "(specify --txindex)", + } + } + + txHash := &outpoint.Hash + blockRegion, err := s.cfg.TxIndex.TxBlockRegion(txHash) + if err != nil { + context := "Failed to retrieve transaction location" + return "", "", internalRPCError(err.Error(), context) + } + if blockRegion == nil { + return "", "", rpcNoTxInfoError(txHash) + } + + // Load the raw transaction bytes from the database. + var txBytes []byte + err = s.cfg.DB.View(func(dbTx database.Tx) error { + var err error + txBytes, err = dbTx.FetchBlockRegion(blockRegion) + return err + }) + if err != nil { + return "", "", rpcNoTxInfoError(txHash) + } + + // Deserialize the transaction + var msgTx wire.MsgTx + err = msgTx.Deserialize(bytes.NewReader(txBytes)) + if err != nil { + context := "Failed to deserialize transaction" + return "", "", internalRPCError(err.Error(), context) + } + + txo := msgTx.TxOut[outpoint.Index] + cs, err := txscript.DecodeClaimScript(txo.PkScript) + if err != nil { + context := "Failed to decode the claim script" + return "", "", internalRPCError(err.Error(), context) + } + + _, addresses, _, _ := txscript.ExtractPkScriptAddrs(txo.PkScript[cs.Size():], s.cfg.ChainParams) + return addresses[0].EncodeAddress(), hex.EncodeToString(cs.Value()), nil +} + +func handleGetNormalized(_ *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.GetNormalizedCmd) + r := btcjson.GetNormalizedResult{ + NormalizedName: string(node.Normalize([]byte(c.Name))), + } + return r, nil +} diff --git a/rpcserver.go b/rpcserver.go index 3339ca4f..07d4ec91 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4688,5 +4688,8 @@ func (s *rpcServer) handleBlockchainNotification(notification *blockchain.Notifi func init() { rpcHandlers = rpcHandlersBeforeInit + for key := range claimtrieHandlers { + rpcHandlers[key] = claimtrieHandlers[key] + } rand.Seed(time.Now().UnixNano()) }