package blockchain import ( "bytes" "fmt" "github.com/pkg/errors" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcd/claimtrie" "github.com/btcsuite/btcd/claimtrie/change" "github.com/btcsuite/btcd/claimtrie/node" ) func (b *BlockChain) ParseClaimScripts(block *btcutil.Block, bn *blockNode, view *UtxoViewpoint, failOnHashMiss bool, shouldFlush bool) error { ht := block.Height() for _, tx := range block.Transactions() { h := handler{ht, tx, view, map[string][]byte{}} if err := h.handleTxIns(b.claimTrie); err != nil { return err } if err := h.handleTxOuts(b.claimTrie); err != nil { return err } } err := b.claimTrie.AppendBlock() if err != nil { return errors.Wrapf(err, "in append block") } if shouldFlush { b.claimTrie.FlushToDisk() } hash := b.claimTrie.MerkleHash() if bn.claimTrie != *hash { if failOnHashMiss { return errors.Errorf("height: %d, ct.MerkleHash: %s != node.ClaimTrie: %s", ht, *hash, bn.claimTrie) } node.LogOnce(fmt.Sprintf("\n\nHeight: %d, ct.MerkleHash: %s != node.ClaimTrie: %s, Error: %s", ht, *hash, bn.claimTrie, err)) } return nil } type handler struct { ht int32 tx *btcutil.Tx view *UtxoViewpoint spent map[string][]byte } func (h *handler) handleTxIns(ct *claimtrie.ClaimTrie) error { if IsCoinBase(h.tx) { return nil } for _, txIn := range h.tx.MsgTx().TxIn { op := txIn.PreviousOutPoint e := h.view.LookupEntry(op) if e == nil { return errors.Errorf("missing input in view for %s", op.String()) } cs, closer, err := txscript.DecodeClaimScript(e.pkScript) if err == txscript.ErrNotClaimScript { closer() continue } if err != nil { closer() return err } var id change.ClaimID name := cs.Name() // name of the previous one (that we're now spending) switch cs.Opcode() { case txscript.OP_CLAIMNAME: // OP code from previous transaction id = change.NewClaimID(op) // claimID of the previous item now being spent h.spent[id.Key()] = node.NormalizeIfNecessary(name, ct.Height()) err = ct.SpendClaim(name, op, id) case txscript.OP_UPDATECLAIM: copy(id[:], cs.ClaimID()) h.spent[id.Key()] = node.NormalizeIfNecessary(name, ct.Height()) err = ct.SpendClaim(name, op, id) case txscript.OP_SUPPORTCLAIM: copy(id[:], cs.ClaimID()) err = ct.SpendSupport(name, op, id) } closer() if err != nil { return errors.Wrapf(err, "handleTxIns") } } return nil } func (h *handler) handleTxOuts(ct *claimtrie.ClaimTrie) error { for i, txOut := range h.tx.MsgTx().TxOut { op := *wire.NewOutPoint(h.tx.Hash(), uint32(i)) cs, closer, err := txscript.DecodeClaimScript(txOut.PkScript) if err == txscript.ErrNotClaimScript { closer() continue } if err != nil { closer() return err } var id change.ClaimID name := cs.Name() amt := txOut.Value switch cs.Opcode() { case txscript.OP_CLAIMNAME: id = change.NewClaimID(op) err = ct.AddClaim(name, op, id, amt) case txscript.OP_SUPPORTCLAIM: copy(id[:], cs.ClaimID()) err = ct.AddSupport(name, op, amt, id) case txscript.OP_UPDATECLAIM: // old code wouldn't run the update if name or claimID didn't match existing data // that was a safety feature, but it should have rejected the transaction instead // TODO: reject transactions with invalid update commands copy(id[:], cs.ClaimID()) normName := node.NormalizeIfNecessary(name, ct.Height()) if !bytes.Equal(h.spent[id.Key()], normName) { node.LogOnce(fmt.Sprintf("Invalid update operation: name or ID mismatch at %d for: %s, %s", ct.Height(), normName, id.String())) closer() continue } delete(h.spent, id.Key()) err = ct.UpdateClaim(name, op, amt, id) } closer() if err != nil { return errors.Wrapf(err, "handleTxOuts") } } 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 { if n != nil { n.Close() } 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 }