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 }