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 }