github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/types/tx.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/badrootd/celestia-core/crypto/merkle"
    10  	"github.com/badrootd/celestia-core/crypto/tmhash"
    11  	cmtbytes "github.com/badrootd/celestia-core/libs/bytes"
    12  	"github.com/badrootd/celestia-core/pkg/consts"
    13  	cmtproto "github.com/badrootd/celestia-core/proto/tendermint/types"
    14  	"github.com/gogo/protobuf/proto"
    15  )
    16  
    17  // TxKeySize is the size of the transaction key index
    18  const TxKeySize = sha256.Size
    19  
    20  type (
    21  	// Tx is an arbitrary byte array.
    22  	// NOTE: Tx has no types at this level, so when wire encoded it's just length-prefixed.
    23  	// Might we want types here ?
    24  	Tx []byte
    25  
    26  	// TxKey is the fixed length array key used as an index.
    27  	TxKey [TxKeySize]byte
    28  )
    29  
    30  // Hash computes the TMHASH hash of the wire encoded transaction. It attempts to
    31  // unwrap the transaction if it is a IndexWrapper or a BlobTx.
    32  func (tx Tx) Hash() []byte {
    33  	if indexWrapper, isIndexWrapper := UnmarshalIndexWrapper(tx); isIndexWrapper {
    34  		return tmhash.Sum(indexWrapper.Tx)
    35  	}
    36  	if blobTx, isBlobTx := UnmarshalBlobTx(tx); isBlobTx {
    37  		return tmhash.Sum(blobTx.Tx)
    38  	}
    39  	return tmhash.Sum(tx)
    40  }
    41  
    42  // Key returns the sha256 hash of the wire encoded transaction. It attempts to
    43  // unwrap the transaction if it is a BlobTx or a IndexWrapper.
    44  func (tx Tx) Key() TxKey {
    45  	if blobTx, isBlobTx := UnmarshalBlobTx(tx); isBlobTx {
    46  		return sha256.Sum256(blobTx.Tx)
    47  	}
    48  	if indexWrapper, isIndexWrapper := UnmarshalIndexWrapper(tx); isIndexWrapper {
    49  		return sha256.Sum256(indexWrapper.Tx)
    50  	}
    51  	return sha256.Sum256(tx)
    52  }
    53  
    54  // String returns the hex-encoded transaction as a string.
    55  func (tx Tx) String() string {
    56  	return fmt.Sprintf("Tx{%X}", []byte(tx))
    57  }
    58  
    59  func (key TxKey) String() string {
    60  	return fmt.Sprintf("TxKey{%X}", key[:])
    61  }
    62  
    63  func TxKeyFromBytes(bytes []byte) (TxKey, error) {
    64  	if len(bytes) != TxKeySize {
    65  		return TxKey{}, fmt.Errorf("incorrect tx key size. Expected %d bytes, got %d", TxKeySize, len(bytes))
    66  	}
    67  	var key TxKey
    68  	copy(key[:], bytes)
    69  	return key, nil
    70  }
    71  
    72  // Txs is a slice of Tx.
    73  type Txs []Tx
    74  
    75  // Hash returns the Merkle root hash of the transaction hashes.
    76  // i.e. the leaves of the tree are the hashes of the txs.
    77  func (txs Txs) Hash() []byte {
    78  	// These allocations will be removed once Txs is switched to [][]byte,
    79  	// ref #2603. This is because golang does not allow type casting slices without unsafe
    80  	txBzs := make([][]byte, len(txs))
    81  	for i := 0; i < len(txs); i++ {
    82  		txBzs[i] = txs[i].Hash()
    83  	}
    84  	return merkle.HashFromByteSlices(txBzs)
    85  }
    86  
    87  // Index returns the index of this transaction in the list, or -1 if not found
    88  func (txs Txs) Index(tx Tx) int {
    89  	for i := range txs {
    90  		if bytes.Equal(txs[i], tx) {
    91  			return i
    92  		}
    93  	}
    94  	return -1
    95  }
    96  
    97  // IndexByHash returns the index of this transaction hash in the list, or -1 if not found
    98  func (txs Txs) IndexByHash(hash []byte) int {
    99  	for i := range txs {
   100  		if bytes.Equal(txs[i].Hash(), hash) {
   101  			return i
   102  		}
   103  	}
   104  	return -1
   105  }
   106  
   107  // ToSliceOfBytes converts a Txs to slice of byte slices.
   108  //
   109  // NOTE: This method should become obsolete once Txs is switched to [][]byte.
   110  // ref: #2603 https://github.com/cometbft/cometbft/issues/2603
   111  func (txs Txs) ToSliceOfBytes() [][]byte {
   112  	txBzs := make([][]byte, len(txs))
   113  	for i := 0; i < len(txs); i++ {
   114  		txBzs[i] = txs[i]
   115  	}
   116  	return txBzs
   117  }
   118  
   119  // ToTxs converts a raw slice of byte slices into a Txs type.
   120  func ToTxs(txs [][]byte) Txs {
   121  	txBzs := make(Txs, len(txs))
   122  	for i := 0; i < len(txs); i++ {
   123  		txBzs[i] = txs[i]
   124  	}
   125  	return txBzs
   126  }
   127  
   128  // Proof returns a simple merkle proof for this node.
   129  // Panics if i < 0 or i >= len(txs)
   130  // TODO: optimize this!
   131  func (txs Txs) Proof(i int) TxProof {
   132  	l := len(txs)
   133  	bzs := make([][]byte, l)
   134  	for i := 0; i < l; i++ {
   135  		bzs[i] = txs[i].Hash()
   136  	}
   137  	root, proofs := merkle.ProofsFromByteSlices(bzs)
   138  
   139  	return TxProof{
   140  		RootHash: root,
   141  		Data:     txs[i],
   142  		Proof:    *proofs[i],
   143  	}
   144  }
   145  
   146  // TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree.
   147  type TxProof struct {
   148  	RootHash cmtbytes.HexBytes `json:"root_hash"`
   149  	Data     Tx                `json:"data"`
   150  	Proof    merkle.Proof      `json:"proof"`
   151  }
   152  
   153  // Leaf returns the hash(tx), which is the leaf in the merkle tree which this proof refers to.
   154  func (tp TxProof) Leaf() []byte {
   155  	return tp.Data.Hash()
   156  }
   157  
   158  // Validate verifies the proof. It returns nil if the RootHash matches the dataHash argument,
   159  // and if the proof is internally consistent. Otherwise, it returns a sensible error.
   160  func (tp TxProof) Validate(dataHash []byte) error {
   161  	if !bytes.Equal(dataHash, tp.RootHash) {
   162  		return errors.New("proof matches different data hash")
   163  	}
   164  	if tp.Proof.Index < 0 {
   165  		return errors.New("proof index cannot be negative")
   166  	}
   167  	if tp.Proof.Total <= 0 {
   168  		return errors.New("proof total must be positive")
   169  	}
   170  	valid := tp.Proof.Verify(tp.RootHash, tp.Leaf())
   171  	if valid != nil {
   172  		return errors.New("proof is not internally consistent")
   173  	}
   174  	return nil
   175  }
   176  
   177  func (tp TxProof) ToProto() cmtproto.TxProof {
   178  
   179  	pbProof := tp.Proof.ToProto()
   180  
   181  	pbtp := cmtproto.TxProof{
   182  		RootHash: tp.RootHash,
   183  		Data:     tp.Data,
   184  		Proof:    pbProof,
   185  	}
   186  
   187  	return pbtp
   188  }
   189  func TxProofFromProto(pb cmtproto.TxProof) (TxProof, error) {
   190  
   191  	pbProof, err := merkle.ProofFromProto(pb.Proof)
   192  	if err != nil {
   193  		return TxProof{}, err
   194  	}
   195  
   196  	pbtp := TxProof{
   197  		RootHash: pb.RootHash,
   198  		Data:     pb.Data,
   199  		Proof:    *pbProof,
   200  	}
   201  
   202  	return pbtp, nil
   203  }
   204  
   205  // ComputeProtoSizeForTxs wraps the transactions in cmtproto.Data{} and calculates the size.
   206  // https://developers.google.com/protocol-buffers/docs/encoding
   207  func ComputeProtoSizeForTxs(txs []Tx) int64 {
   208  	data := Data{Txs: txs}
   209  	pdData := data.ToProto()
   210  	return int64(pdData.Size())
   211  }
   212  
   213  // UnmarshalIndexWrapper attempts to unmarshal the provided transaction into an
   214  // IndexWrapper transaction. It returns true if the provided transaction is an
   215  // IndexWrapper transaction. An IndexWrapper transaction is a transaction that contains
   216  // a MsgPayForBlob that has been wrapped with a share index.
   217  //
   218  // NOTE: protobuf sometimes does not throw an error if the transaction passed is
   219  // not a tmproto.IndexWrapper, since the protobuf definition for MsgPayForBlob is
   220  // kept in the app, we cannot perform further checks without creating an import
   221  // cycle.
   222  func UnmarshalIndexWrapper(tx Tx) (indexWrapper cmtproto.IndexWrapper, isIndexWrapper bool) {
   223  	// attempt to unmarshal into an IndexWrapper transaction
   224  	err := proto.Unmarshal(tx, &indexWrapper)
   225  	if err != nil {
   226  		return indexWrapper, false
   227  	}
   228  	if indexWrapper.TypeId != consts.ProtoIndexWrapperTypeID {
   229  		return indexWrapper, false
   230  	}
   231  	return indexWrapper, true
   232  }
   233  
   234  // MarshalIndexWrapper creates a wrapped Tx that includes the original transaction
   235  // and the share index of the start of its blob.
   236  //
   237  // NOTE: must be unwrapped to be a viable sdk.Tx
   238  func MarshalIndexWrapper(tx Tx, shareIndexes ...uint32) (Tx, error) {
   239  	wTx := cmtproto.IndexWrapper{
   240  		Tx:           tx,
   241  		ShareIndexes: shareIndexes,
   242  		TypeId:       consts.ProtoIndexWrapperTypeID,
   243  	}
   244  	return proto.Marshal(&wTx)
   245  }
   246  
   247  // UnmarshalBlobTx attempts to unmarshal a transaction into blob transaction. If an
   248  // error is thrown, false is returned.
   249  func UnmarshalBlobTx(tx Tx) (bTx cmtproto.BlobTx, isBlob bool) {
   250  	err := bTx.Unmarshal(tx)
   251  	if err != nil {
   252  		return cmtproto.BlobTx{}, false
   253  	}
   254  	// perform some quick basic checks to prevent false positives
   255  	if bTx.TypeId != consts.ProtoBlobTxTypeID {
   256  		return bTx, false
   257  	}
   258  	if len(bTx.Blobs) == 0 {
   259  		return bTx, false
   260  	}
   261  	for _, b := range bTx.Blobs {
   262  		if len(b.NamespaceId) != consts.NamespaceIDSize {
   263  			return bTx, false
   264  		}
   265  	}
   266  	return bTx, true
   267  }
   268  
   269  // MarshalBlobTx creates a BlobTx using a normal transaction and some number of
   270  // blobs.
   271  //
   272  // NOTE: Any checks on the blobs or the transaction must be performed in the
   273  // application
   274  func MarshalBlobTx(tx []byte, blobs ...*cmtproto.Blob) (Tx, error) {
   275  	bTx := cmtproto.BlobTx{
   276  		Tx:     tx,
   277  		Blobs:  blobs,
   278  		TypeId: consts.ProtoBlobTxTypeID,
   279  	}
   280  	return bTx.Marshal()
   281  }