lbcd/rpcclaimtrie.go

344 lines
9.3 KiB
Go
Raw Normal View History

package main
import (
"bytes"
"encoding/hex"
"strconv"
"strings"
"github.com/lbryio/lbcd/btcjson"
"github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/lbryio/lbcd/claimtrie/node"
"github.com/lbryio/lbcd/claimtrie/normalization"
"github.com/lbryio/lbcd/database"
"github.com/lbryio/lbcd/txscript"
"github.com/lbryio/lbcd/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(normalization.Normalize([]byte(c.Name))),
}
return r, nil
}