github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/tx.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"errors"
     7  	"fmt"
     8  	"sort"
     9  
    10  	abci "github.com/ari-anchor/sei-tendermint/abci/types"
    11  	"github.com/ari-anchor/sei-tendermint/crypto"
    12  	"github.com/ari-anchor/sei-tendermint/crypto/merkle"
    13  	tmbytes "github.com/ari-anchor/sei-tendermint/libs/bytes"
    14  	tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types"
    15  )
    16  
    17  // Tx is an arbitrary byte array.
    18  // NOTE: Tx has no types at this level, so when wire encoded it's just length-prefixed.
    19  // Might we want types here ?
    20  type Tx []byte
    21  
    22  // Key produces a fixed-length key for use in indexing.
    23  func (tx Tx) Key() TxKey { return sha256.Sum256(tx) }
    24  
    25  // Hash computes the TMHASH hash of the wire encoded transaction.
    26  func (tx Tx) Hash() []byte { return crypto.Checksum(tx) }
    27  
    28  // String returns the hex-encoded transaction as a string.
    29  func (tx Tx) String() string { return fmt.Sprintf("Tx{%X}", []byte(tx)) }
    30  
    31  // Txs is a slice of Tx.
    32  type Txs []Tx
    33  
    34  // Hash returns the Merkle root hash of the transaction hashes.
    35  // i.e. the leaves of the tree are the hashes of the txs.
    36  func (txs Txs) Hash() []byte {
    37  	hl := txs.hashList()
    38  	return merkle.HashFromByteSlices(hl)
    39  }
    40  
    41  // Index returns the index of this transaction in the list, or -1 if not found
    42  func (txs Txs) Index(tx Tx) int {
    43  	for i := range txs {
    44  		if bytes.Equal(txs[i], tx) {
    45  			return i
    46  		}
    47  	}
    48  	return -1
    49  }
    50  
    51  // IndexByHash returns the index of this transaction hash in the list, or -1 if not found
    52  func (txs Txs) IndexByHash(hash []byte) int {
    53  	for i := range txs {
    54  		if bytes.Equal(txs[i].Hash(), hash) {
    55  			return i
    56  		}
    57  	}
    58  	return -1
    59  }
    60  
    61  func (txs Txs) Proof(i int) TxProof {
    62  	hl := txs.hashList()
    63  	root, proofs := merkle.ProofsFromByteSlices(hl)
    64  
    65  	return TxProof{
    66  		RootHash: root,
    67  		Data:     txs[i],
    68  		Proof:    *proofs[i],
    69  	}
    70  }
    71  
    72  func (txs Txs) hashList() [][]byte {
    73  	hl := make([][]byte, len(txs))
    74  	for i := 0; i < len(txs); i++ {
    75  		hl[i] = txs[i].Hash()
    76  	}
    77  	return hl
    78  }
    79  
    80  // Txs is a slice of transactions. Sorting a Txs value orders the transactions
    81  // lexicographically.
    82  func (txs Txs) Len() int      { return len(txs) }
    83  func (txs Txs) Swap(i, j int) { txs[i], txs[j] = txs[j], txs[i] }
    84  func (txs Txs) Less(i, j int) bool {
    85  	return bytes.Compare(txs[i], txs[j]) == -1
    86  }
    87  
    88  // ToSliceOfBytes converts a Txs to slice of byte slices.
    89  func (txs Txs) ToSliceOfBytes() [][]byte {
    90  	txBzs := make([][]byte, len(txs))
    91  	for i := 0; i < len(txs); i++ {
    92  		txBzs[i] = txs[i]
    93  	}
    94  	return txBzs
    95  }
    96  
    97  // TxRecordSet contains indexes into an underlying set of transactions.
    98  // These indexes are useful for validating and working with a list of TxRecords
    99  // from the PrepareProposal response.
   100  //
   101  // Only one copy of the original data is referenced by all of the indexes but a
   102  // transaction may appear in multiple indexes.
   103  type TxRecordSet struct {
   104  	// all holds the complete list of all transactions from the original list of
   105  	// TxRecords.
   106  	all Txs
   107  
   108  	// included is an index of the transactions that will be included in the block
   109  	// and is constructed from the list of both added and unmodified transactions.
   110  	// included maintains the original order that the transactions were present
   111  	// in the list of TxRecords.
   112  	included Txs
   113  
   114  	// added, unmodified, removed, and unknown are indexes for each of the actions
   115  	// that may be supplied with a transaction.
   116  	//
   117  	// Because each transaction only has one action, it can be referenced by
   118  	// at most 3 indexes in this data structure: the action-specific index, the
   119  	// included index, and the all index.
   120  	added      Txs
   121  	unmodified Txs
   122  	removed    Txs
   123  	unknown    Txs
   124  }
   125  
   126  // NewTxRecordSet constructs a new set from the given transaction records.
   127  // The contents of the input transactions are shared by the set, and must not
   128  // be modified during the lifetime of the set.
   129  func NewTxRecordSet(trs []*abci.TxRecord) TxRecordSet {
   130  	txrSet := TxRecordSet{
   131  		all: make([]Tx, len(trs)),
   132  	}
   133  	for i, tr := range trs {
   134  
   135  		txrSet.all[i] = Tx(tr.Tx)
   136  
   137  		// The following set of assignments do not allocate new []byte, they create
   138  		// pointers to the already allocated slice.
   139  		switch tr.GetAction() {
   140  		case abci.TxRecord_UNKNOWN:
   141  			txrSet.unknown = append(txrSet.unknown, txrSet.all[i])
   142  		case abci.TxRecord_UNMODIFIED:
   143  			txrSet.unmodified = append(txrSet.unmodified, txrSet.all[i])
   144  			txrSet.included = append(txrSet.included, txrSet.all[i])
   145  		case abci.TxRecord_ADDED:
   146  			txrSet.added = append(txrSet.added, txrSet.all[i])
   147  			txrSet.included = append(txrSet.included, txrSet.all[i])
   148  		case abci.TxRecord_REMOVED:
   149  			txrSet.removed = append(txrSet.removed, txrSet.all[i])
   150  		}
   151  	}
   152  	return txrSet
   153  }
   154  
   155  // IncludedTxs returns the transactions marked for inclusion in a block. This
   156  // list maintains the order that the transactions were included in the list of
   157  // TxRecords that were used to construct the TxRecordSet.
   158  func (t TxRecordSet) IncludedTxs() []Tx {
   159  	return t.included
   160  }
   161  
   162  // RemovedTxs returns the transactions marked for removal by the application.
   163  func (t TxRecordSet) RemovedTxs() []Tx {
   164  	return t.removed
   165  }
   166  
   167  // Validate checks that the record set was correctly constructed from the original
   168  // list of transactions.
   169  func (t TxRecordSet) Validate(maxSizeBytes int64, otxs Txs) error {
   170  	if len(t.unknown) > 0 {
   171  		return fmt.Errorf("%d transactions marked unknown (first unknown hash: %x)", len(t.unknown), t.unknown[0].Hash())
   172  	}
   173  
   174  	// The following validation logic performs a set of sorts on the data in the TxRecordSet indexes.
   175  	// It sorts the original transaction list, otxs, once.
   176  	// It sorts the new transaction list twice: once when sorting 'all', the total list,
   177  	// and once by sorting the set of the added, removed, and unmodified transactions indexes,
   178  	// which, when combined, comprise the complete list of modified transactions.
   179  	//
   180  	// Each of the added, removed, and unmodified indices is then iterated and once
   181  	// and each value index is checked against the sorted original list for containment.
   182  	// Asymptotically, this yields a total runtime of O(N*log(N) + 2*M*log(M) + M*log(N)).
   183  	// in the input size of the original list, N, and the input size of the new list, M, respectively.
   184  	// Performance gains are likely possible, but this was preferred for readability and maintainability.
   185  
   186  	// Sort a copy of the complete transaction slice so we can check for
   187  	// duplication. The copy is so we do not change the original ordering.
   188  	// Only the slices are copied, the transaction contents are shared.
   189  	allCopy := sortedCopy(t.all)
   190  
   191  	for i, cur := range allCopy {
   192  		// allCopy is sorted, so any duplicated data will be adjacent.
   193  		if i+1 < len(allCopy) && bytes.Equal(cur, allCopy[i+1]) {
   194  			return fmt.Errorf("found duplicate transaction with hash: %x", cur.Hash())
   195  		}
   196  	}
   197  
   198  	// create copies of each of the action-specific indexes so that order of the original
   199  	// indexes can be preserved.
   200  	addedCopy := sortedCopy(t.added)
   201  	removedCopy := sortedCopy(t.removed)
   202  	unmodifiedCopy := sortedCopy(t.unmodified)
   203  
   204  	var size int64
   205  	for _, cur := range append(unmodifiedCopy, addedCopy...) {
   206  		size += int64(len(cur))
   207  		if size > maxSizeBytes {
   208  			return fmt.Errorf("transaction data size exceeds maximum %d", maxSizeBytes)
   209  		}
   210  	}
   211  
   212  	// make a defensive copy of otxs so that the order of
   213  	// the caller's data is not altered.
   214  	otxsCopy := sortedCopy(otxs)
   215  
   216  	if ix, ok := containsAll(otxsCopy, unmodifiedCopy); !ok {
   217  		return fmt.Errorf("new transaction incorrectly marked as removed, transaction hash: %x", unmodifiedCopy[ix].Hash())
   218  	}
   219  
   220  	if ix, ok := containsAll(otxsCopy, removedCopy); !ok {
   221  		return fmt.Errorf("new transaction incorrectly marked as removed, transaction hash: %x", removedCopy[ix].Hash())
   222  	}
   223  	if ix, ok := containsAny(otxsCopy, addedCopy); ok {
   224  		return fmt.Errorf("existing transaction incorrectly marked as added, transaction hash: %x", addedCopy[ix].Hash())
   225  	}
   226  	return nil
   227  }
   228  
   229  func sortedCopy(txs Txs) Txs {
   230  	cp := make(Txs, len(txs))
   231  	copy(cp, txs)
   232  	sort.Sort(cp)
   233  	return cp
   234  }
   235  
   236  // containsAny checks that list a contains one of the transactions in list
   237  // b. If a match is found, the index in b of the matching transaction is returned.
   238  // Both lists must be sorted.
   239  func containsAny(a, b []Tx) (int, bool) {
   240  	for i, cur := range b {
   241  		if _, ok := contains(a, cur); ok {
   242  			return i, true
   243  		}
   244  	}
   245  	return -1, false
   246  }
   247  
   248  // containsAll checks that super contains all of the transactions in the sub
   249  // list. If not all values in sub are present in super, the index in sub of the
   250  // first Tx absent from super is returned.
   251  func containsAll(super, sub Txs) (int, bool) {
   252  	for i, cur := range sub {
   253  		if _, ok := contains(super, cur); !ok {
   254  			return i, false
   255  		}
   256  	}
   257  	return -1, true
   258  }
   259  
   260  // contains checks that the sorted list, set contains elem. If set does contain elem, then the
   261  // index in set of elem is returned.
   262  func contains(set []Tx, elem Tx) (int, bool) {
   263  	n := sort.Search(len(set), func(i int) bool {
   264  		return bytes.Compare(elem, set[i]) <= 0
   265  	})
   266  	if n == len(set) || !bytes.Equal(elem, set[n]) {
   267  		return -1, false
   268  	}
   269  	return n, true
   270  }
   271  
   272  // TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree.
   273  type TxProof struct {
   274  	RootHash tmbytes.HexBytes `json:"root_hash"`
   275  	Data     Tx               `json:"data"`
   276  	Proof    merkle.Proof     `json:"proof"`
   277  }
   278  
   279  // Leaf returns the hash(tx), which is the leaf in the merkle tree which this proof refers to.
   280  func (tp TxProof) Leaf() []byte {
   281  	return tp.Data.Hash()
   282  }
   283  
   284  // Validate verifies the proof. It returns nil if the RootHash matches the dataHash argument,
   285  // and if the proof is internally consistent. Otherwise, it returns a sensible error.
   286  func (tp TxProof) Validate(dataHash []byte) error {
   287  	if !bytes.Equal(dataHash, tp.RootHash) {
   288  		return errors.New("proof matches different data hash")
   289  	}
   290  	if tp.Proof.Index < 0 {
   291  		return errors.New("proof index cannot be negative")
   292  	}
   293  	if tp.Proof.Total <= 0 {
   294  		return errors.New("proof total must be positive")
   295  	}
   296  	valid := tp.Proof.Verify(tp.RootHash, tp.Leaf())
   297  	if valid != nil {
   298  		return errors.New("proof is not internally consistent")
   299  	}
   300  	return nil
   301  }
   302  
   303  func (tp TxProof) ToProto() tmproto.TxProof {
   304  
   305  	pbProof := tp.Proof.ToProto()
   306  
   307  	pbtp := tmproto.TxProof{
   308  		RootHash: tp.RootHash,
   309  		Data:     tp.Data,
   310  		Proof:    pbProof,
   311  	}
   312  
   313  	return pbtp
   314  }
   315  func TxProofFromProto(pb tmproto.TxProof) (TxProof, error) {
   316  
   317  	pbProof, err := merkle.ProofFromProto(pb.Proof)
   318  	if err != nil {
   319  		return TxProof{}, err
   320  	}
   321  
   322  	pbtp := TxProof{
   323  		RootHash: pb.RootHash,
   324  		Data:     pb.Data,
   325  		Proof:    *pbProof,
   326  	}
   327  
   328  	return pbtp, nil
   329  }
   330  
   331  // ComputeProtoSizeForTxs wraps the transactions in tmproto.Data{} and calculates the size.
   332  // https://developers.google.com/protocol-buffers/docs/encoding
   333  func ComputeProtoSizeForTxs(txs []Tx) int64 {
   334  	data := Data{Txs: txs}
   335  	pdData := data.ToProto()
   336  	return int64(pdData.Size())
   337  }