github.com/lbryio/lbcd@v0.22.119/blockchain/claimtrie.go (about) 1 package blockchain 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "github.com/pkg/errors" 8 9 "github.com/lbryio/lbcd/txscript" 10 "github.com/lbryio/lbcd/wire" 11 btcutil "github.com/lbryio/lbcutil" 12 13 "github.com/lbryio/lbcd/claimtrie" 14 "github.com/lbryio/lbcd/claimtrie/change" 15 "github.com/lbryio/lbcd/claimtrie/node" 16 "github.com/lbryio/lbcd/claimtrie/normalization" 17 ) 18 19 func (b *BlockChain) SetClaimtrieHeader(block *btcutil.Block, view *UtxoViewpoint) error { 20 b.chainLock.Lock() 21 defer b.chainLock.Unlock() 22 23 err := b.ParseClaimScripts(block, nil, view, false) 24 if err != nil { 25 return errors.Wrapf(err, "in parse claim scripts") 26 } 27 28 block.MsgBlock().Header.ClaimTrie = *b.claimTrie.MerkleHash() 29 err = b.claimTrie.ResetHeight(b.claimTrie.Height() - 1) 30 31 return errors.Wrapf(err, "in reset height") 32 } 33 34 func (b *BlockChain) ParseClaimScripts(block *btcutil.Block, bn *blockNode, view *UtxoViewpoint, shouldFlush bool) error { 35 ht := block.Height() 36 37 for _, tx := range block.Transactions() { 38 h := handler{ht, tx, view, map[string][]byte{}} 39 if err := h.handleTxIns(b.claimTrie); err != nil { 40 return err 41 } 42 if err := h.handleTxOuts(b.claimTrie); err != nil { 43 return err 44 } 45 } 46 47 err := b.claimTrie.AppendBlock(bn == nil) 48 if err != nil { 49 return errors.Wrapf(err, "in append block") 50 } 51 52 if shouldFlush { 53 b.claimTrie.FlushToDisk() 54 } 55 56 hash := b.claimTrie.MerkleHash() 57 if bn != nil && bn.claimTrie != *hash { 58 // undo our AppendBlock call as we've decided that our interpretation of the block data is incorrect, 59 // or that the person who made the block assembled the pieces incorrectly. 60 _ = b.claimTrie.ResetHeight(b.claimTrie.Height() - 1) 61 return errors.Errorf("height: %d, computed hash: %s != header's ClaimTrie: %s", ht, *hash, bn.claimTrie) 62 } 63 return nil 64 } 65 66 type handler struct { 67 ht int32 68 tx *btcutil.Tx 69 view *UtxoViewpoint 70 spent map[string][]byte 71 } 72 73 func (h *handler) handleTxIns(ct *claimtrie.ClaimTrie) error { 74 if IsCoinBase(h.tx) { 75 return nil 76 } 77 for _, txIn := range h.tx.MsgTx().TxIn { 78 op := txIn.PreviousOutPoint 79 e := h.view.LookupEntry(op) 80 if e == nil { 81 return errors.Errorf("missing input in view for %s", op.String()) 82 } 83 cs, err := txscript.ExtractClaimScript(e.pkScript) 84 if txscript.IsErrorCode(err, txscript.ErrNotClaimScript) { 85 continue 86 } 87 if err != nil { 88 return err 89 } 90 91 var id change.ClaimID 92 name := cs.Name // name of the previous one (that we're now spending) 93 94 switch cs.Opcode { 95 case txscript.OP_CLAIMNAME: // OP code from previous transaction 96 id = change.NewClaimID(op) // claimID of the previous item now being spent 97 h.spent[id.Key()] = normalization.NormalizeIfNecessary(name, ct.Height()) 98 err = ct.SpendClaim(name, op, id) 99 case txscript.OP_UPDATECLAIM: 100 copy(id[:], cs.ClaimID) 101 h.spent[id.Key()] = normalization.NormalizeIfNecessary(name, ct.Height()) 102 err = ct.SpendClaim(name, op, id) 103 case txscript.OP_SUPPORTCLAIM: 104 copy(id[:], cs.ClaimID) 105 err = ct.SpendSupport(name, op, id) 106 } 107 if err != nil { 108 return errors.Wrapf(err, "handleTxIns") 109 } 110 } 111 return nil 112 } 113 114 func (h *handler) handleTxOuts(ct *claimtrie.ClaimTrie) error { 115 for i, txOut := range h.tx.MsgTx().TxOut { 116 op := *wire.NewOutPoint(h.tx.Hash(), uint32(i)) 117 cs, err := txscript.ExtractClaimScript(txOut.PkScript) 118 if txscript.IsErrorCode(err, txscript.ErrNotClaimScript) { 119 continue 120 } 121 if err != nil { 122 return err 123 } 124 125 var id change.ClaimID 126 name := cs.Name 127 amt := txOut.Value 128 129 switch cs.Opcode { 130 case txscript.OP_CLAIMNAME: 131 id = change.NewClaimID(op) 132 err = ct.AddClaim(name, op, id, amt) 133 case txscript.OP_SUPPORTCLAIM: 134 copy(id[:], cs.ClaimID) 135 err = ct.AddSupport(name, op, amt, id) 136 case txscript.OP_UPDATECLAIM: 137 // old code wouldn't run the update if name or claimID didn't match existing data 138 // that was a safety feature, but it should have rejected the transaction instead 139 // TODO: reject transactions with invalid update commands 140 copy(id[:], cs.ClaimID) 141 normName := normalization.NormalizeIfNecessary(name, ct.Height()) 142 if !bytes.Equal(h.spent[id.Key()], normName) { 143 node.LogOnce(fmt.Sprintf("Invalid update operation: name or ID mismatch at %d for: %s, %s", 144 ct.Height(), normName, id.String())) 145 continue 146 } 147 148 delete(h.spent, id.Key()) 149 err = ct.UpdateClaim(name, op, amt, id) 150 } 151 if err != nil { 152 return errors.Wrapf(err, "handleTxOuts") 153 } 154 } 155 return nil 156 } 157 158 func (b *BlockChain) GetNamesChangedInBlock(height int32) ([]string, error) { 159 b.chainLock.RLock() 160 defer b.chainLock.RUnlock() 161 162 return b.claimTrie.NamesChangedInBlock(height) 163 } 164 165 func (b *BlockChain) GetClaimsForName(height int32, name string) (string, *node.Node, error) { 166 167 normalizedName := normalization.NormalizeIfNecessary([]byte(name), height) 168 169 b.chainLock.RLock() 170 defer b.chainLock.RUnlock() 171 172 n, err := b.claimTrie.NodeAt(height, normalizedName) 173 if err != nil { 174 return string(normalizedName), nil, err 175 } 176 177 if n == nil { 178 return string(normalizedName), nil, fmt.Errorf("name does not exist at height %d: %s", height, name) 179 } 180 181 n.SortClaimsByBid() 182 return string(normalizedName), n, nil 183 }