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 }