github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/model/flow/transaction.go (about) 1 package flow 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/crypto" 7 "github.com/onflow/crypto/hash" 8 "golang.org/x/exp/slices" 9 10 "github.com/onflow/flow-go/model/fingerprint" 11 ) 12 13 // TransactionBody includes the main contents of a transaction 14 type TransactionBody struct { 15 16 // A reference to a previous block 17 // A transaction is expired after specific number of blocks (defined by network) counting from this block 18 // for example, if block reference is pointing to a block with height of X and network limit is 10, 19 // a block with x+10 height is the last block that is allowed to include this transaction. 20 // user can adjust this reference to older blocks if he/she wants to make tx expire faster 21 ReferenceBlockID Identifier 22 23 // the transaction script as UTF-8 encoded Cadence source code 24 Script []byte 25 26 // arguments passed to the Cadence transaction 27 Arguments [][]byte 28 29 // Max amount of computation which is allowed to be done during this transaction 30 GasLimit uint64 31 32 // Account key used to propose the transaction 33 ProposalKey ProposalKey 34 35 // Account that pays for this transaction fees 36 Payer Address 37 38 // A ordered (ascending) list of addresses that scripts will touch their assets (including payer address) 39 // Accounts listed here all have to provide signatures 40 // Each account might provide multiple signatures (sum of weight should be at least 1) 41 // If code touches accounts that is not listed here, tx fails 42 Authorizers []Address 43 44 // List of account signatures excluding signature of the payer account 45 PayloadSignatures []TransactionSignature 46 47 // payer signature over the envelope (payload + payload signatures) 48 EnvelopeSignatures []TransactionSignature 49 } 50 51 // NewTransactionBody initializes and returns an empty transaction body 52 func NewTransactionBody() *TransactionBody { 53 return &TransactionBody{} 54 } 55 56 func (tb TransactionBody) Fingerprint() []byte { 57 return fingerprint.Fingerprint(struct { 58 Payload interface{} 59 PayloadSignatures interface{} 60 EnvelopeSignatures interface{} 61 }{ 62 Payload: tb.payloadCanonicalForm(), 63 PayloadSignatures: signaturesList(tb.PayloadSignatures).canonicalForm(), 64 EnvelopeSignatures: signaturesList(tb.EnvelopeSignatures).canonicalForm(), 65 }) 66 } 67 68 func (tb TransactionBody) ByteSize() uint { 69 size := 0 70 size += len(tb.ReferenceBlockID) 71 size += len(tb.Script) 72 for _, arg := range tb.Arguments { 73 size += len(arg) 74 } 75 size += 8 // gas size 76 size += tb.ProposalKey.ByteSize() 77 size += AddressLength // payer address 78 size += len(tb.Authorizers) * AddressLength // Authorizers 79 for _, s := range tb.PayloadSignatures { 80 size += s.ByteSize() 81 } 82 for _, s := range tb.EnvelopeSignatures { 83 size += s.ByteSize() 84 } 85 return uint(size) 86 } 87 88 // InclusionEffort returns the inclusion effort of the transaction 89 func (tb TransactionBody) InclusionEffort() uint64 { 90 // Hardcoded inclusion effort (of 1.0 UFix). 91 // Eventually this will be dynamic and will depend on the transaction properties 92 inclusionEffort := uint64(100_000_000) 93 return inclusionEffort 94 } 95 96 func (tb TransactionBody) ID() Identifier { 97 return MakeID(tb) 98 } 99 100 func (tb TransactionBody) Checksum() Identifier { 101 return MakeID(tb) 102 } 103 104 // SetScript sets the Cadence script for this transaction. 105 func (tb *TransactionBody) SetScript(script []byte) *TransactionBody { 106 tb.Script = script 107 return tb 108 } 109 110 // SetArguments sets the Cadence arguments list for this transaction. 111 func (tb *TransactionBody) SetArguments(args [][]byte) *TransactionBody { 112 tb.Arguments = args 113 return tb 114 } 115 116 // AddArgument adds an argument to the Cadence arguments list for this transaction. 117 func (tb *TransactionBody) AddArgument(arg []byte) *TransactionBody { 118 tb.Arguments = append(tb.Arguments, arg) 119 return tb 120 } 121 122 // SetReferenceBlockID sets the reference block ID for this transaction. 123 func (tb *TransactionBody) SetReferenceBlockID(blockID Identifier) *TransactionBody { 124 tb.ReferenceBlockID = blockID 125 return tb 126 } 127 128 // SetComputeLimit sets the gas limit for this transaction. 129 func (tb *TransactionBody) SetComputeLimit(limit uint64) *TransactionBody { 130 tb.GasLimit = limit 131 return tb 132 } 133 134 // SetProposalKey sets the proposal key and sequence number for this transaction. 135 // 136 // The first two arguments specify the account key to be used, and the last argument is the sequence 137 // number being declared. 138 func (tb *TransactionBody) SetProposalKey(address Address, keyID uint64, sequenceNum uint64) *TransactionBody { 139 proposalKey := ProposalKey{ 140 Address: address, 141 KeyIndex: keyID, 142 SequenceNumber: sequenceNum, 143 } 144 tb.ProposalKey = proposalKey 145 return tb 146 } 147 148 // SetPayer sets the payer account for this transaction. 149 func (tb *TransactionBody) SetPayer(address Address) *TransactionBody { 150 tb.Payer = address 151 return tb 152 } 153 154 // AddAuthorizer adds an authorizer account to this transaction. 155 func (tb *TransactionBody) AddAuthorizer(address Address) *TransactionBody { 156 tb.Authorizers = append(tb.Authorizers, address) 157 return tb 158 } 159 160 // Transaction is the smallest unit of task. 161 type Transaction struct { 162 TransactionBody 163 Status TransactionStatus 164 Events []Event 165 ComputationSpent uint64 166 StartState StateCommitment 167 EndState StateCommitment 168 } 169 170 // MissingFields checks if a transaction is missing any required fields and returns those that are missing. 171 func (tb *TransactionBody) MissingFields() []string { 172 // Required fields are Script, ReferenceBlockID, Payer 173 missingFields := make([]string, 0) 174 175 if len(tb.Script) == 0 { 176 missingFields = append(missingFields, TransactionFieldScript.String()) 177 } 178 179 if tb.ReferenceBlockID == ZeroID { 180 missingFields = append(missingFields, TransactionFieldRefBlockID.String()) 181 } 182 183 if tb.Payer == EmptyAddress { 184 missingFields = append(missingFields, TransactionFieldPayer.String()) 185 } 186 187 return missingFields 188 } 189 190 // signerList returns a list of unique accounts required to sign this transaction. 191 // 192 // The list is returned in the following order: 193 // 1. PROPOSER 194 // 2. PAYER 195 // 2. AUTHORIZERS (in insertion order) 196 // 197 // The only exception to the above ordering is for deduplication; if the same account 198 // is used in multiple signing roles, only the first occurrence is included in the list. 199 func (tb *TransactionBody) signerList() []Address { 200 signers := make([]Address, 0) 201 seen := make(map[Address]struct{}) 202 203 var addSigner = func(address Address) { 204 _, ok := seen[address] 205 if ok { 206 return 207 } 208 209 signers = append(signers, address) 210 seen[address] = struct{}{} 211 } 212 213 if tb.ProposalKey.Address != EmptyAddress { 214 addSigner(tb.ProposalKey.Address) 215 } 216 217 if tb.Payer != EmptyAddress { 218 addSigner(tb.Payer) 219 } 220 221 for _, authorizer := range tb.Authorizers { 222 addSigner(authorizer) 223 } 224 225 return signers 226 } 227 228 // signerMap returns a mapping from address to signer index. 229 func (tb *TransactionBody) signerMap() map[Address]int { 230 signers := make(map[Address]int) 231 232 for i, signer := range tb.signerList() { 233 signers[signer] = i 234 } 235 236 return signers 237 } 238 239 // SignPayload signs the transaction payload (TransactionDomainTag + payload) with the specified account key using the default transaction domain tag. 240 // 241 // The resulting signature is combined with the account address and key ID before 242 // being added to the transaction. 243 // 244 // This function returns an error if the signature cannot be generated. 245 func (tb *TransactionBody) SignPayload( 246 address Address, 247 keyID uint64, 248 privateKey crypto.PrivateKey, 249 hasher hash.Hasher, 250 ) error { 251 sig, err := tb.Sign(tb.PayloadMessage(), privateKey, hasher) 252 253 if err != nil { 254 return fmt.Errorf("failed to sign transaction payload with given key: %w", err) 255 } 256 257 tb.AddPayloadSignature(address, keyID, sig) 258 259 return nil 260 } 261 262 // SignEnvelope signs the full transaction (TransactionDomainTag + payload + payload signatures) with the specified account key using the default transaction domain tag. 263 // 264 // The resulting signature is combined with the account address and key ID before 265 // being added to the transaction. 266 // 267 // This function returns an error if the signature cannot be generated. 268 func (tb *TransactionBody) SignEnvelope( 269 address Address, 270 keyID uint64, 271 privateKey crypto.PrivateKey, 272 hasher hash.Hasher, 273 ) error { 274 sig, err := tb.Sign(tb.EnvelopeMessage(), privateKey, hasher) 275 276 if err != nil { 277 return fmt.Errorf("failed to sign transaction envelope with given key: %w", err) 278 } 279 280 tb.AddEnvelopeSignature(address, keyID, sig) 281 282 return nil 283 } 284 285 // Sign signs the data (transaction_tag + message) with the specified private key 286 // and hasher. 287 // 288 // This function returns an error if: 289 // - crypto.InvalidInputsError if the private key cannot sign with the given hasher 290 // - other error if an unexpected error occurs 291 func (tb *TransactionBody) Sign( 292 message []byte, 293 privateKey crypto.PrivateKey, 294 hasher hash.Hasher, 295 ) ([]byte, error) { 296 message = append(TransactionDomainTag[:], message...) 297 sig, err := privateKey.Sign(message, hasher) 298 if err != nil { 299 return nil, fmt.Errorf("failed to sign message with given key: %w", err) 300 } 301 302 return sig, nil 303 } 304 305 // AddPayloadSignature adds a payload signature to the transaction for the given address and key ID. 306 func (tb *TransactionBody) AddPayloadSignature(address Address, keyID uint64, sig []byte) *TransactionBody { 307 s := tb.createSignature(address, keyID, sig) 308 309 tb.PayloadSignatures = append(tb.PayloadSignatures, s) 310 slices.SortFunc(tb.PayloadSignatures, compareSignatures) 311 312 return tb 313 } 314 315 // AddEnvelopeSignature adds an envelope signature to the transaction for the given address and key ID. 316 func (tb *TransactionBody) AddEnvelopeSignature(address Address, keyID uint64, sig []byte) *TransactionBody { 317 s := tb.createSignature(address, keyID, sig) 318 319 tb.EnvelopeSignatures = append(tb.EnvelopeSignatures, s) 320 slices.SortFunc(tb.EnvelopeSignatures, compareSignatures) 321 322 return tb 323 } 324 325 func (tb *TransactionBody) createSignature(address Address, keyID uint64, sig []byte) TransactionSignature { 326 signerIndex, signerExists := tb.signerMap()[address] 327 if !signerExists { 328 signerIndex = -1 329 } 330 331 return TransactionSignature{ 332 Address: address, 333 SignerIndex: signerIndex, 334 KeyIndex: keyID, 335 Signature: sig, 336 } 337 } 338 339 func (tb *TransactionBody) PayloadMessage() []byte { 340 return fingerprint.Fingerprint(tb.payloadCanonicalForm()) 341 } 342 343 func (tb *TransactionBody) payloadCanonicalForm() interface{} { 344 authorizers := make([][]byte, len(tb.Authorizers)) 345 for i, auth := range tb.Authorizers { 346 authorizers[i] = auth.Bytes() 347 } 348 349 return struct { 350 Script []byte 351 Arguments [][]byte 352 ReferenceBlockID []byte 353 GasLimit uint64 354 ProposalKeyAddress []byte 355 ProposalKeyID uint64 356 ProposalKeySequenceNumber uint64 357 Payer []byte 358 Authorizers [][]byte 359 }{ 360 Script: tb.Script, 361 Arguments: tb.Arguments, 362 ReferenceBlockID: tb.ReferenceBlockID[:], 363 GasLimit: tb.GasLimit, 364 ProposalKeyAddress: tb.ProposalKey.Address.Bytes(), 365 ProposalKeyID: tb.ProposalKey.KeyIndex, 366 ProposalKeySequenceNumber: tb.ProposalKey.SequenceNumber, 367 Payer: tb.Payer.Bytes(), 368 Authorizers: authorizers, 369 } 370 } 371 372 // EnvelopeMessage returns the signable message for transaction envelope. 373 // 374 // This message is only signed by the payer account. 375 func (tb *TransactionBody) EnvelopeMessage() []byte { 376 return fingerprint.Fingerprint(tb.envelopeCanonicalForm()) 377 } 378 379 func (tb *TransactionBody) envelopeCanonicalForm() interface{} { 380 return struct { 381 Payload interface{} 382 PayloadSignatures interface{} 383 }{ 384 tb.payloadCanonicalForm(), 385 signaturesList(tb.PayloadSignatures).canonicalForm(), 386 } 387 } 388 389 func (tx *Transaction) PayloadMessage() []byte { 390 return fingerprint.Fingerprint(tx.TransactionBody.payloadCanonicalForm()) 391 } 392 393 // Checksum provides a cryptographic commitment for a chunk content 394 func (tx *Transaction) Checksum() Identifier { 395 return MakeID(tx) 396 } 397 398 func (tx *Transaction) String() string { 399 return fmt.Sprintf("Transaction %v submitted by %v (block %v)", 400 tx.ID(), tx.Payer.Hex(), tx.ReferenceBlockID) 401 } 402 403 // TransactionStatus represents the status of a transaction. 404 type TransactionStatus int 405 406 const ( 407 // TransactionStatusUnknown indicates that the transaction status is not known. 408 TransactionStatusUnknown TransactionStatus = iota 409 // TransactionStatusPending is the status of a pending transaction. 410 TransactionStatusPending 411 // TransactionStatusFinalized is the status of a finalized transaction. 412 TransactionStatusFinalized 413 // TransactionStatusExecuted is the status of an executed transaction. 414 TransactionStatusExecuted 415 // TransactionStatusSealed is the status of a sealed transaction. 416 TransactionStatusSealed 417 // TransactionStatusExpired is the status of an expired transaction. 418 TransactionStatusExpired 419 ) 420 421 // String returns the string representation of a transaction status. 422 func (s TransactionStatus) String() string { 423 return [...]string{"UNKNOWN", "PENDING", "FINALIZED", "EXECUTED", "SEALED", "EXPIRED"}[s] 424 } 425 426 // TransactionField represents a required transaction field. 427 type TransactionField int 428 429 const ( 430 TransactionFieldUnknown TransactionField = iota 431 TransactionFieldScript 432 TransactionFieldRefBlockID 433 TransactionFieldPayer 434 ) 435 436 // String returns the string representation of a transaction field. 437 func (f TransactionField) String() string { 438 return [...]string{"Unknown", "Script", "ReferenceBlockID", "Payer"}[f] 439 } 440 441 // A ProposalKey is the key that specifies the proposal key and sequence number for a transaction. 442 type ProposalKey struct { 443 Address Address 444 KeyIndex uint64 445 SequenceNumber uint64 446 } 447 448 // ByteSize returns the byte size of the proposal key 449 func (p ProposalKey) ByteSize() int { 450 keyIDLen := 8 451 sequenceNumberLen := 8 452 return len(p.Address) + keyIDLen + sequenceNumberLen 453 } 454 455 // A TransactionSignature is a signature associated with a specific account key. 456 type TransactionSignature struct { 457 Address Address 458 SignerIndex int 459 KeyIndex uint64 460 Signature []byte 461 } 462 463 // String returns the string representation of a transaction signature. 464 func (s TransactionSignature) String() string { 465 return fmt.Sprintf("Address: %s. SignerIndex: %d. KeyID: %d. Signature: %s", 466 s.Address, s.SignerIndex, s.KeyIndex, s.Signature) 467 } 468 469 // ByteSize returns the byte size of the transaction signature 470 func (s TransactionSignature) ByteSize() int { 471 signerIndexLen := 8 472 keyIDLen := 8 473 return len(s.Address) + signerIndexLen + keyIDLen + len(s.Signature) 474 } 475 476 func (s TransactionSignature) Fingerprint() []byte { 477 return fingerprint.Fingerprint(s.canonicalForm()) 478 } 479 480 func (s TransactionSignature) canonicalForm() interface{} { 481 return struct { 482 SignerIndex uint 483 KeyID uint 484 Signature []byte 485 }{ 486 SignerIndex: uint(s.SignerIndex), // int is not RLP-serializable 487 KeyID: uint(s.KeyIndex), // int is not RLP-serializable 488 Signature: s.Signature, 489 } 490 } 491 492 func compareSignatures(sigA, sigB TransactionSignature) int { 493 if sigA.SignerIndex == sigB.SignerIndex { 494 return int(sigA.KeyIndex) - int(sigB.KeyIndex) 495 } 496 497 return sigA.SignerIndex - sigB.SignerIndex 498 } 499 500 type signaturesList []TransactionSignature 501 502 func (s signaturesList) canonicalForm() interface{} { 503 signatures := make([]interface{}, len(s)) 504 505 for i, signature := range s { 506 signatures[i] = signature.canonicalForm() 507 } 508 509 return signatures 510 }