github.com/lbryio/lbcd@v0.22.119/rpcclaimtrie.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/lbryio/lbcd/btcjson"
    10  	"github.com/lbryio/lbcd/chaincfg/chainhash"
    11  	"github.com/lbryio/lbcd/claimtrie/node"
    12  	"github.com/lbryio/lbcd/claimtrie/normalization"
    13  	"github.com/lbryio/lbcd/database"
    14  	"github.com/lbryio/lbcd/txscript"
    15  	"github.com/lbryio/lbcd/wire"
    16  )
    17  
    18  var claimtrieHandlers = map[string]commandHandler{
    19  	"getchangesinblock":     handleGetChangesInBlock,
    20  	"getclaimsforname":      handleGetClaimsForName,
    21  	"getclaimsfornamebyid":  handleGetClaimsForNameByID,
    22  	"getclaimsfornamebybid": handleGetClaimsForNameByBid,
    23  	"getclaimsfornamebyseq": handleGetClaimsForNameBySeq,
    24  	"normalize":             handleGetNormalized,
    25  }
    26  
    27  func handleGetChangesInBlock(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
    28  
    29  	c := cmd.(*btcjson.GetChangesInBlockCmd)
    30  	hash, height, err := parseHashOrHeight(s, c.HashOrHeight)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	names, err := s.cfg.Chain.GetNamesChangedInBlock(height)
    36  	if err != nil {
    37  		return nil, &btcjson.RPCError{
    38  			Code:    btcjson.ErrRPCMisc,
    39  			Message: "Message: " + err.Error(),
    40  		}
    41  	}
    42  
    43  	return btcjson.GetChangesInBlockResult{
    44  		Hash:   hash,
    45  		Height: height,
    46  		Names:  names,
    47  	}, nil
    48  }
    49  
    50  func parseHashOrHeight(s *rpcServer, hashOrHeight *string) (string, int32, error) {
    51  	if hashOrHeight == nil || len(*hashOrHeight) == 0 {
    52  
    53  		if !s.cfg.Chain.IsCurrent() {
    54  			return "", 0, &btcjson.RPCError{
    55  				Code:    btcjson.ErrRPCClientInInitialDownload,
    56  				Message: "Unable to query the chain tip during initial download",
    57  			}
    58  		}
    59  
    60  		// just give them the latest block if a specific one wasn't requested
    61  		best := s.cfg.Chain.BestSnapshot()
    62  		return best.Hash.String(), best.Height, nil
    63  	}
    64  
    65  	ht, err := strconv.ParseInt(*hashOrHeight, 10, 32)
    66  	if err == nil && len(*hashOrHeight) < 32 {
    67  		hs, err := s.cfg.Chain.BlockHashByHeight(int32(ht))
    68  		if err != nil {
    69  			return "", 0, &btcjson.RPCError{
    70  				Code:    btcjson.ErrRPCBlockNotFound,
    71  				Message: "Unable to locate a block at height " + *hashOrHeight + ": " + err.Error(),
    72  			}
    73  		}
    74  		return hs.String(), int32(ht), nil
    75  	}
    76  
    77  	hs, err := chainhash.NewHashFromStr(*hashOrHeight)
    78  	if err != nil {
    79  		return "", 0, &btcjson.RPCError{
    80  			Code:    btcjson.ErrRPCInvalidParameter,
    81  			Message: "Unable to parse a height or hash from " + *hashOrHeight + ": " + err.Error(),
    82  		}
    83  	}
    84  	h, err := s.cfg.Chain.BlockHeightByHash(hs)
    85  	if err != nil {
    86  		return hs.String(), h, &btcjson.RPCError{
    87  			Code:    btcjson.ErrRPCBlockNotFound,
    88  			Message: "Unable to find a block with hash " + hs.String() + ": " + err.Error(),
    89  		}
    90  	}
    91  	return hs.String(), h, nil
    92  }
    93  
    94  func handleGetClaimsForName(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
    95  
    96  	c := cmd.(*btcjson.GetClaimsForNameCmd)
    97  	hash, height, err := parseHashOrHeight(s, c.HashOrHeight)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name)
   103  	if err != nil {
   104  		return nil, &btcjson.RPCError{
   105  			Code:    btcjson.ErrRPCMisc,
   106  			Message: "Message: " + err.Error(),
   107  		}
   108  	}
   109  
   110  	var results []btcjson.ClaimResult
   111  	for i := range n.Claims {
   112  		cr, err := toClaimResult(s, int32(i), n, c.IncludeValues)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  		results = append(results, cr)
   117  	}
   118  
   119  	return btcjson.GetClaimsForNameResult{
   120  		Hash:               hash,
   121  		Height:             height,
   122  		LastTakeoverHeight: n.TakenOverAt,
   123  		NormalizedName:     name,
   124  		Claims:             results,
   125  	}, nil
   126  }
   127  
   128  func handleGetClaimsForNameByID(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
   129  
   130  	c := cmd.(*btcjson.GetClaimsForNameByIDCmd)
   131  	hash, height, err := parseHashOrHeight(s, c.HashOrHeight)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name)
   137  	if err != nil {
   138  		return nil, &btcjson.RPCError{
   139  			Code:    btcjson.ErrRPCMisc,
   140  			Message: "Message: " + err.Error(),
   141  		}
   142  	}
   143  
   144  	var results []btcjson.ClaimResult
   145  	for i := 0; i < len(n.Claims); i++ {
   146  		for _, id := range c.PartialClaimIDs {
   147  			if strings.HasPrefix(n.Claims[i].ClaimID.String(), id) {
   148  				cr, err := toClaimResult(s, int32(i), n, c.IncludeValues)
   149  				if err != nil {
   150  					return nil, err
   151  				}
   152  				results = append(results, cr)
   153  				break
   154  			}
   155  		}
   156  	}
   157  
   158  	return btcjson.GetClaimsForNameResult{
   159  		Hash:               hash,
   160  		Height:             height,
   161  		LastTakeoverHeight: n.TakenOverAt,
   162  		NormalizedName:     name,
   163  		Claims:             results,
   164  	}, nil
   165  }
   166  
   167  func handleGetClaimsForNameByBid(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
   168  
   169  	c := cmd.(*btcjson.GetClaimsForNameByBidCmd)
   170  	hash, height, err := parseHashOrHeight(s, c.HashOrHeight)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name)
   176  	if err != nil {
   177  		return nil, &btcjson.RPCError{
   178  			Code:    btcjson.ErrRPCMisc,
   179  			Message: "Message: " + err.Error(),
   180  		}
   181  	}
   182  
   183  	var results []btcjson.ClaimResult
   184  	for _, b := range c.Bids { // claims are already sorted in bid order
   185  		if b >= 0 && int(b) < len(n.Claims) {
   186  			cr, err := toClaimResult(s, b, n, c.IncludeValues)
   187  			if err != nil {
   188  				return nil, err
   189  			}
   190  			results = append(results, cr)
   191  		}
   192  	}
   193  
   194  	return btcjson.GetClaimsForNameResult{
   195  		Hash:               hash,
   196  		Height:             height,
   197  		LastTakeoverHeight: n.TakenOverAt,
   198  		NormalizedName:     name,
   199  		Claims:             results,
   200  	}, nil
   201  }
   202  
   203  func handleGetClaimsForNameBySeq(s *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
   204  
   205  	c := cmd.(*btcjson.GetClaimsForNameBySeqCmd)
   206  	hash, height, err := parseHashOrHeight(s, c.HashOrHeight)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	name, n, err := s.cfg.Chain.GetClaimsForName(height, c.Name)
   212  	if err != nil {
   213  		return nil, &btcjson.RPCError{
   214  			Code:    btcjson.ErrRPCMisc,
   215  			Message: "Message: " + err.Error(),
   216  		}
   217  	}
   218  
   219  	sm := map[int32]bool{}
   220  	for _, seq := range c.Sequences {
   221  		sm[seq] = true
   222  	}
   223  
   224  	var results []btcjson.ClaimResult
   225  	for i := 0; i < len(n.Claims); i++ {
   226  		if sm[n.Claims[i].Sequence] {
   227  			cr, err := toClaimResult(s, int32(i), n, c.IncludeValues)
   228  			if err != nil {
   229  				return nil, err
   230  			}
   231  			results = append(results, cr)
   232  		}
   233  	}
   234  
   235  	return btcjson.GetClaimsForNameResult{
   236  		Hash:               hash,
   237  		Height:             height,
   238  		LastTakeoverHeight: n.TakenOverAt,
   239  		NormalizedName:     name,
   240  		Claims:             results,
   241  	}, nil
   242  }
   243  
   244  func toClaimResult(s *rpcServer, i int32, n *node.Node, includeValues *bool) (btcjson.ClaimResult, error) {
   245  	claim := n.Claims[i]
   246  	address, value, err := lookupValue(s, claim.OutPoint, includeValues)
   247  	supports, err := toSupportResults(s, i, n, includeValues)
   248  	effectiveAmount := n.SupportSums[claim.ClaimID.Key()] // should only be active supports
   249  	if claim.Status == node.Activated {
   250  		effectiveAmount += claim.Amount
   251  	}
   252  	return btcjson.ClaimResult{
   253  		ClaimID:         claim.ClaimID.String(),
   254  		Height:          claim.AcceptedAt,
   255  		ValidAtHeight:   claim.ActiveAt,
   256  		TXID:            claim.OutPoint.Hash.String(),
   257  		N:               claim.OutPoint.Index,
   258  		Bid:             i, // assuming sorted by bid
   259  		Amount:          claim.Amount,
   260  		EffectiveAmount: effectiveAmount,
   261  		Sequence:        claim.Sequence,
   262  		Supports:        supports,
   263  		Address:         address,
   264  		Value:           value,
   265  	}, err
   266  }
   267  
   268  func toSupportResults(s *rpcServer, i int32, n *node.Node, includeValues *bool) ([]btcjson.SupportResult, error) {
   269  	var results []btcjson.SupportResult
   270  	c := n.Claims[i]
   271  	for _, sup := range n.Supports {
   272  		if sup.Status == node.Activated && c.ClaimID == sup.ClaimID {
   273  			address, value, err := lookupValue(s, sup.OutPoint, includeValues)
   274  			if err != nil {
   275  				return results, err
   276  			}
   277  			results = append(results, btcjson.SupportResult{
   278  				TXID:          sup.OutPoint.Hash.String(),
   279  				N:             sup.OutPoint.Index,
   280  				Height:        sup.AcceptedAt,
   281  				ValidAtHeight: sup.ActiveAt,
   282  				Amount:        sup.Amount,
   283  				Value:         value,
   284  				Address:       address,
   285  			})
   286  		}
   287  	}
   288  	return results, nil
   289  }
   290  
   291  func lookupValue(s *rpcServer, outpoint wire.OutPoint, includeValues *bool) (string, string, error) {
   292  	if includeValues == nil || !*includeValues {
   293  		return "", "", nil
   294  	}
   295  	// TODO: maybe use addrIndex if the txIndex is not available
   296  
   297  	if s.cfg.TxIndex == nil {
   298  		return "", "", &btcjson.RPCError{
   299  			Code: btcjson.ErrRPCNoTxInfo,
   300  			Message: "The transaction index must be " +
   301  				"enabled to query the blockchain " +
   302  				"(specify --txindex)",
   303  		}
   304  	}
   305  
   306  	txHash := &outpoint.Hash
   307  	blockRegion, err := s.cfg.TxIndex.TxBlockRegion(txHash)
   308  	if err != nil {
   309  		context := "Failed to retrieve transaction location"
   310  		return "", "", internalRPCError(err.Error(), context)
   311  	}
   312  	if blockRegion == nil {
   313  		return "", "", rpcNoTxInfoError(txHash)
   314  	}
   315  
   316  	// Load the raw transaction bytes from the database.
   317  	var txBytes []byte
   318  	err = s.cfg.DB.View(func(dbTx database.Tx) error {
   319  		var err error
   320  		txBytes, err = dbTx.FetchBlockRegion(blockRegion)
   321  		return err
   322  	})
   323  	if err != nil {
   324  		return "", "", rpcNoTxInfoError(txHash)
   325  	}
   326  
   327  	// Deserialize the transaction
   328  	var msgTx wire.MsgTx
   329  	err = msgTx.Deserialize(bytes.NewReader(txBytes))
   330  	if err != nil {
   331  		context := "Failed to deserialize transaction"
   332  		return "", "", internalRPCError(err.Error(), context)
   333  	}
   334  
   335  	txo := msgTx.TxOut[outpoint.Index]
   336  	cs, err := txscript.ExtractClaimScript(txo.PkScript)
   337  	if err != nil {
   338  		context := "Failed to decode the claim script"
   339  		return "", "", internalRPCError(err.Error(), context)
   340  	}
   341  
   342  	_, addresses, _, _ := txscript.ExtractPkScriptAddrs(txo.PkScript[cs.Size:], s.cfg.ChainParams)
   343  	return addresses[0].EncodeAddress(), hex.EncodeToString(cs.Value), nil
   344  }
   345  
   346  func handleGetNormalized(_ *rpcServer, cmd interface{}, _ <-chan struct{}) (interface{}, error) {
   347  	c := cmd.(*btcjson.GetNormalizedCmd)
   348  	r := btcjson.GetNormalizedResult{
   349  		NormalizedName: string(normalization.Normalize([]byte(c.Name))),
   350  	}
   351  	return r, nil
   352  }