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  }