github.com/gagliardetto/solana-go@v1.11.0/transaction.go (about) 1 // Copyright 2021 github.com/gagliardetto 2 // This file has been modified by github.com/gagliardetto 3 // 4 // Copyright 2020 dfuse Platform Inc. 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package solana 19 20 import ( 21 "bytes" 22 "encoding/base64" 23 "fmt" 24 "sort" 25 26 "github.com/davecgh/go-spew/spew" 27 bin "github.com/gagliardetto/binary" 28 "github.com/gagliardetto/treeout" 29 "github.com/mr-tron/base58" 30 "go.uber.org/zap" 31 32 "github.com/gagliardetto/solana-go/text" 33 ) 34 35 type Transaction struct { 36 // A list of base-58 encoded signatures applied to the transaction. 37 // The list is always of length `message.header.numRequiredSignatures` and not empty. 38 // The signature at index `i` corresponds to the public key at index 39 // `i` in `message.account_keys`. The first one is used as the transaction id. 40 Signatures []Signature `json:"signatures"` 41 42 // Defines the content of the transaction. 43 Message Message `json:"message"` 44 } 45 46 // UnmarshalBase64 decodes a base64 encoded transaction. 47 func (tx *Transaction) UnmarshalBase64(b64 string) error { 48 b, err := base64.StdEncoding.DecodeString(b64) 49 if err != nil { 50 return err 51 } 52 return tx.UnmarshalWithDecoder(bin.NewBinDecoder(b)) 53 } 54 55 var _ bin.EncoderDecoder = &Transaction{} 56 57 func (t *Transaction) HasAccount(account PublicKey) (bool, error) { 58 return t.Message.HasAccount(account) 59 } 60 61 func (t *Transaction) IsSigner(account PublicKey) bool { 62 return t.Message.IsSigner(account) 63 } 64 65 func (t *Transaction) IsWritable(account PublicKey) (bool, error) { 66 return t.Message.IsWritable(account) 67 } 68 69 func (t *Transaction) AccountMetaList() ([]*AccountMeta, error) { 70 return t.Message.AccountMetaList() 71 } 72 73 func (t *Transaction) ResolveProgramIDIndex(programIDIndex uint16) (PublicKey, error) { 74 return t.Message.ResolveProgramIDIndex(programIDIndex) 75 } 76 77 func (t *Transaction) GetAccountIndex(account PublicKey) (uint16, error) { 78 return t.Message.GetAccountIndex(account) 79 } 80 81 // TransactionFromDecoder decodes a transaction from a decoder. 82 func TransactionFromDecoder(decoder *bin.Decoder) (*Transaction, error) { 83 var out *Transaction 84 err := decoder.Decode(&out) 85 if err != nil { 86 return nil, err 87 } 88 return out, nil 89 } 90 91 func TransactionFromBytes(data []byte) (*Transaction, error) { 92 decoder := bin.NewBinDecoder(data) 93 return TransactionFromDecoder(decoder) 94 } 95 96 func TransactionFromBase64(b64 string) (*Transaction, error) { 97 data, err := base64.StdEncoding.DecodeString(b64) 98 if err != nil { 99 return nil, err 100 } 101 return TransactionFromBytes(data) 102 } 103 104 func TransactionFromBase58(b58 string) (*Transaction, error) { 105 data, err := base58.Decode(b58) 106 if err != nil { 107 return nil, err 108 } 109 return TransactionFromBytes(data) 110 } 111 112 // MustTransactionFromDecoder decodes a transaction from a decoder. 113 // Panics on error. 114 func MustTransactionFromDecoder(decoder *bin.Decoder) *Transaction { 115 out, err := TransactionFromDecoder(decoder) 116 if err != nil { 117 panic(err) 118 } 119 return out 120 } 121 122 type CompiledInstruction struct { 123 // Index into the message.accountKeys array indicating the program account that executes this instruction. 124 // NOTE: it is actually a uint8, but using a uint16 because uint8 is treated as a byte everywhere, 125 // and that can be an issue. 126 ProgramIDIndex uint16 `json:"programIdIndex"` 127 128 // List of ordered indices into the message.accountKeys array indicating which accounts to pass to the program. 129 // NOTE: it is actually a []uint8, but using a uint16 because []uint8 is treated as a []byte everywhere, 130 // and that can be an issue. 131 Accounts []uint16 `json:"accounts"` 132 133 // The program input data encoded in a base-58 string. 134 Data Base58 `json:"data"` 135 } 136 137 func (ci *CompiledInstruction) ResolveInstructionAccounts(message *Message) ([]*AccountMeta, error) { 138 out := make([]*AccountMeta, len(ci.Accounts)) 139 metas, err := message.AccountMetaList() 140 if err != nil { 141 return nil, err 142 } 143 for i, acct := range ci.Accounts { 144 out[i] = metas[acct] 145 } 146 147 return out, nil 148 } 149 150 type Instruction interface { 151 ProgramID() PublicKey // the programID the instruction acts on 152 Accounts() []*AccountMeta // returns the list of accounts the instructions requires 153 Data() ([]byte, error) // the binary encoded instructions 154 } 155 156 type TransactionOption interface { 157 apply(opts *transactionOptions) 158 } 159 160 type transactionOptions struct { 161 payer PublicKey 162 addressTables map[PublicKey]PublicKeySlice // [tablePubkey]addresses 163 } 164 165 type transactionOptionFunc func(opts *transactionOptions) 166 167 func (f transactionOptionFunc) apply(opts *transactionOptions) { 168 f(opts) 169 } 170 171 func TransactionPayer(payer PublicKey) TransactionOption { 172 return transactionOptionFunc(func(opts *transactionOptions) { opts.payer = payer }) 173 } 174 175 func TransactionAddressTables(tables map[PublicKey]PublicKeySlice) TransactionOption { 176 return transactionOptionFunc(func(opts *transactionOptions) { opts.addressTables = tables }) 177 } 178 179 var debugNewTransaction = false 180 181 type TransactionBuilder struct { 182 instructions []Instruction 183 recentBlockHash Hash 184 opts []TransactionOption 185 } 186 187 // NewTransactionBuilder creates a new instruction builder. 188 func NewTransactionBuilder() *TransactionBuilder { 189 return &TransactionBuilder{} 190 } 191 192 // AddInstruction adds the provided instruction to the builder. 193 func (builder *TransactionBuilder) AddInstruction(instruction Instruction) *TransactionBuilder { 194 builder.instructions = append(builder.instructions, instruction) 195 return builder 196 } 197 198 // SetRecentBlockHash sets the recent blockhash for the instruction builder. 199 func (builder *TransactionBuilder) SetRecentBlockHash(recentBlockHash Hash) *TransactionBuilder { 200 builder.recentBlockHash = recentBlockHash 201 return builder 202 } 203 204 // WithOpt adds a TransactionOption. 205 func (builder *TransactionBuilder) WithOpt(opt TransactionOption) *TransactionBuilder { 206 builder.opts = append(builder.opts, opt) 207 return builder 208 } 209 210 // Set transaction fee payer. 211 // If not set, defaults to first signer account of the first instruction. 212 func (builder *TransactionBuilder) SetFeePayer(feePayer PublicKey) *TransactionBuilder { 213 builder.opts = append(builder.opts, TransactionPayer(feePayer)) 214 return builder 215 } 216 217 // Build builds and returns a *Transaction. 218 func (builder *TransactionBuilder) Build() (*Transaction, error) { 219 return NewTransaction( 220 builder.instructions, 221 builder.recentBlockHash, 222 builder.opts..., 223 ) 224 } 225 226 type addressTablePubkeyWithIndex struct { 227 addressTable PublicKey 228 index uint8 229 } 230 231 func NewTransaction(instructions []Instruction, recentBlockHash Hash, opts ...TransactionOption) (*Transaction, error) { 232 if len(instructions) == 0 { 233 return nil, fmt.Errorf("requires at-least one instruction to create a transaction") 234 } 235 236 options := transactionOptions{} 237 for _, opt := range opts { 238 opt.apply(&options) 239 } 240 241 feePayer := options.payer 242 if feePayer.IsZero() { 243 found := false 244 for _, act := range instructions[0].Accounts() { 245 if act.IsSigner { 246 feePayer = act.PublicKey 247 found = true 248 break 249 } 250 } 251 if !found { 252 return nil, fmt.Errorf("cannot determine fee payer. You can ether pass the fee payer via the 'TransactionWithInstructions' option parameter or it falls back to the first instruction's first signer") 253 } 254 } 255 256 addressLookupKeysMap := make(map[PublicKey]addressTablePubkeyWithIndex) // all accounts from tables as map 257 for addressTablePubKey, addressTable := range options.addressTables { 258 if len(addressTable) > 256 { 259 return nil, fmt.Errorf("max lookup table index exceeded for %s table", addressTablePubKey) 260 } 261 262 for i, address := range addressTable { 263 _, ok := addressLookupKeysMap[address] 264 if ok { 265 continue 266 } 267 268 addressLookupKeysMap[address] = addressTablePubkeyWithIndex{ 269 addressTable: addressTablePubKey, 270 index: uint8(i), 271 } 272 } 273 } 274 275 programIDs := make(PublicKeySlice, 0) 276 accounts := []*AccountMeta{} 277 for _, instruction := range instructions { 278 accounts = append(accounts, instruction.Accounts()...) 279 programIDs.UniqueAppend(instruction.ProgramID()) 280 } 281 282 // for IsInvoked check 283 programIDsMap := make(map[PublicKey]struct{}, len(programIDs)) 284 // Add programID to the account list 285 for _, programID := range programIDs { 286 accounts = append(accounts, &AccountMeta{ 287 PublicKey: programID, 288 IsSigner: false, 289 IsWritable: false, 290 }) 291 292 programIDsMap[programID] = struct{}{} 293 } 294 295 // Sort. Prioritizing first by signer, then by writable 296 sort.SliceStable(accounts, func(i, j int) bool { 297 return accounts[i].less(accounts[j]) 298 }) 299 300 uniqAccountsMap := map[PublicKey]uint64{} 301 uniqAccounts := []*AccountMeta{} 302 for _, acc := range accounts { 303 if index, found := uniqAccountsMap[acc.PublicKey]; found { 304 uniqAccounts[index].IsWritable = uniqAccounts[index].IsWritable || acc.IsWritable 305 continue 306 } 307 uniqAccounts = append(uniqAccounts, acc) 308 uniqAccountsMap[acc.PublicKey] = uint64(len(uniqAccounts) - 1) 309 } 310 311 if debugNewTransaction { 312 zlog.Debug("unique account sorted", zap.Int("account_count", len(uniqAccounts))) 313 } 314 // Move fee payer to the front 315 feePayerIndex := -1 316 for idx, acc := range uniqAccounts { 317 if acc.PublicKey.Equals(feePayer) { 318 feePayerIndex = idx 319 } 320 } 321 if debugNewTransaction { 322 zlog.Debug("current fee payer index", zap.Int("fee_payer_index", feePayerIndex)) 323 } 324 325 accountCount := len(uniqAccounts) 326 if feePayerIndex < 0 { 327 // fee payer is not part of accounts we want to add it 328 accountCount++ 329 } 330 allKeys := make([]*AccountMeta, accountCount) 331 332 itr := 1 333 for idx, uniqAccount := range uniqAccounts { 334 if idx == feePayerIndex { 335 uniqAccount.IsSigner = true 336 uniqAccount.IsWritable = true 337 allKeys[0] = uniqAccount 338 continue 339 } 340 allKeys[itr] = uniqAccount 341 itr++ 342 } 343 344 if feePayerIndex < 0 { 345 // fee payer is not part of accounts we want to add it 346 feePayerAccount := &AccountMeta{ 347 PublicKey: feePayer, 348 IsSigner: true, 349 IsWritable: true, 350 } 351 allKeys[0] = feePayerAccount 352 } 353 354 message := Message{ 355 RecentBlockhash: recentBlockHash, 356 } 357 lookupsMap := make(map[PublicKey]struct { // extended MessageAddressTableLookup 358 AccountKey PublicKey // The account key of the address table. 359 WritableIndexes []uint8 360 Writable []PublicKey 361 ReadonlyIndexes []uint8 362 Readonly []PublicKey 363 }) 364 for idx, acc := range allKeys { 365 366 if debugNewTransaction { 367 zlog.Debug("transaction account", 368 zap.Int("account_index", idx), 369 zap.Stringer("account_pub_key", acc.PublicKey), 370 ) 371 } 372 373 addressLookupKeyEntry, isPresentedInTables := addressLookupKeysMap[acc.PublicKey] 374 _, isInvoked := programIDsMap[acc.PublicKey] 375 // skip fee payer 376 if isPresentedInTables && idx != 0 && !acc.IsSigner && !isInvoked { 377 lookup := lookupsMap[addressLookupKeyEntry.addressTable] 378 if acc.IsWritable { 379 lookup.WritableIndexes = append(lookup.WritableIndexes, addressLookupKeyEntry.index) 380 lookup.Writable = append(lookup.Writable, acc.PublicKey) 381 } else { 382 lookup.ReadonlyIndexes = append(lookup.ReadonlyIndexes, addressLookupKeyEntry.index) 383 lookup.Readonly = append(lookup.Readonly, acc.PublicKey) 384 } 385 386 lookupsMap[addressLookupKeyEntry.addressTable] = lookup 387 continue // prevent changing message.Header properties 388 } 389 390 message.AccountKeys = append(message.AccountKeys, acc.PublicKey) 391 392 if acc.IsSigner { 393 message.Header.NumRequiredSignatures++ 394 if !acc.IsWritable { 395 message.Header.NumReadonlySignedAccounts++ 396 } 397 continue 398 } 399 400 if !acc.IsWritable { 401 message.Header.NumReadonlyUnsignedAccounts++ 402 } 403 } 404 405 var lookupsWritableKeys []PublicKey 406 var lookupsReadOnlyKeys []PublicKey 407 if len(lookupsMap) > 0 { 408 lookups := make([]MessageAddressTableLookup, 0, len(lookupsMap)) 409 410 for tablePubKey, l := range lookupsMap { 411 lookupsWritableKeys = append(lookupsWritableKeys, l.Writable...) 412 lookupsReadOnlyKeys = append(lookupsReadOnlyKeys, l.Readonly...) 413 414 lookups = append(lookups, MessageAddressTableLookup{ 415 AccountKey: tablePubKey, 416 WritableIndexes: l.WritableIndexes, 417 ReadonlyIndexes: l.ReadonlyIndexes, 418 }) 419 } 420 421 // prevent error created in ResolveLookups 422 err := message.SetAddressTables(options.addressTables) 423 if err != nil { 424 return nil, fmt.Errorf("SetAddressTables: %s", err) 425 } 426 message.SetAddressTableLookups(lookups) 427 } 428 429 var idx uint16 430 accountKeyIndex := make(map[string]uint16, len(message.AccountKeys)+len(lookupsWritableKeys)+len(lookupsReadOnlyKeys)) 431 for _, acc := range message.AccountKeys { 432 accountKeyIndex[acc.String()] = idx 433 idx++ 434 } 435 for _, acc := range lookupsWritableKeys { 436 accountKeyIndex[acc.String()] = idx 437 idx++ 438 } 439 for _, acc := range lookupsReadOnlyKeys { 440 accountKeyIndex[acc.String()] = idx 441 idx++ 442 } 443 444 if debugNewTransaction { 445 zlog.Debug("message header compiled", 446 zap.Uint8("num_required_signatures", message.Header.NumRequiredSignatures), 447 zap.Uint8("num_readonly_signed_accounts", message.Header.NumReadonlySignedAccounts), 448 zap.Uint8("num_readonly_unsigned_accounts", message.Header.NumReadonlyUnsignedAccounts), 449 ) 450 } 451 452 for txIdx, instruction := range instructions { 453 accounts = instruction.Accounts() 454 accountIndex := make([]uint16, len(accounts)) 455 for idx, acc := range accounts { 456 accountIndex[idx] = accountKeyIndex[acc.PublicKey.String()] 457 } 458 data, err := instruction.Data() 459 if err != nil { 460 return nil, fmt.Errorf("unable to encode instructions [%d]: %w", txIdx, err) 461 } 462 message.Instructions = append(message.Instructions, CompiledInstruction{ 463 ProgramIDIndex: accountKeyIndex[instruction.ProgramID().String()], 464 Accounts: accountIndex, 465 Data: data, 466 }) 467 } 468 469 return &Transaction{ 470 Message: message, 471 }, nil 472 } 473 474 type privateKeyGetter func(key PublicKey) *PrivateKey 475 476 func (tx *Transaction) MarshalBinary() ([]byte, error) { 477 messageContent, err := tx.Message.MarshalBinary() 478 if err != nil { 479 return nil, fmt.Errorf("failed to encode tx.Message to binary: %w", err) 480 } 481 482 var signatureCount []byte 483 bin.EncodeCompactU16Length(&signatureCount, len(tx.Signatures)) 484 output := make([]byte, 0, len(signatureCount)+len(signatureCount)*64+len(messageContent)) 485 output = append(output, signatureCount...) 486 for _, sig := range tx.Signatures { 487 output = append(output, sig[:]...) 488 } 489 output = append(output, messageContent...) 490 491 return output, nil 492 } 493 494 func (tx Transaction) MarshalWithEncoder(encoder *bin.Encoder) error { 495 out, err := tx.MarshalBinary() 496 if err != nil { 497 return err 498 } 499 return encoder.WriteBytes(out, false) 500 } 501 502 func (tx *Transaction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { 503 { 504 numSignatures, err := decoder.ReadCompactU16() 505 if err != nil { 506 return fmt.Errorf("unable to read numSignatures: %w", err) 507 } 508 if numSignatures < 0 { 509 return fmt.Errorf("numSignatures is negative") 510 } 511 if numSignatures > decoder.Remaining()/64 { 512 return fmt.Errorf("numSignatures %d is too large for remaining bytes %d", numSignatures, decoder.Remaining()) 513 } 514 515 tx.Signatures = make([]Signature, numSignatures) 516 for i := 0; i < numSignatures; i++ { 517 _, err := decoder.Read(tx.Signatures[i][:]) 518 if err != nil { 519 return fmt.Errorf("unable to read tx.Signatures[%d]: %w", i, err) 520 } 521 } 522 } 523 { 524 err := tx.Message.UnmarshalWithDecoder(decoder) 525 if err != nil { 526 return fmt.Errorf("unable to decode tx.Message: %w", err) 527 } 528 } 529 return nil 530 } 531 532 func (tx *Transaction) PartialSign(getter privateKeyGetter) (out []Signature, err error) { 533 messageContent, err := tx.Message.MarshalBinary() 534 if err != nil { 535 return nil, fmt.Errorf("unable to encode message for signing: %w", err) 536 } 537 signerKeys := tx.Message.signerKeys() 538 539 // Ensure that the transaction has the correct number of signatures initialized 540 if len(tx.Signatures) == 0 { 541 // Initialize the Signatures slice to the correct length if it's empty 542 tx.Signatures = make([]Signature, len(signerKeys)) 543 } else if len(tx.Signatures) != len(signerKeys) { 544 // Return an error if the current length of the Signatures slice doesn't match the expected number 545 return nil, fmt.Errorf("invalid signatures length, expected %d, actual %d", len(signerKeys), len(tx.Signatures)) 546 } 547 548 for i, key := range signerKeys { 549 privateKey := getter(key) 550 if privateKey != nil { 551 s, err := privateKey.Sign(messageContent) 552 if err != nil { 553 return nil, fmt.Errorf("failed to signed with key %q: %w", key.String(), err) 554 } 555 // Directly assign the signature to the corresponding position in the transaction's signature slice 556 tx.Signatures[i] = s 557 } 558 } 559 return tx.Signatures, nil 560 } 561 562 func (tx *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error) { 563 signerKeys := tx.Message.signerKeys() 564 for _, key := range signerKeys { 565 if getter(key) == nil { 566 return nil, fmt.Errorf("signer key %q not found. Ensure all the signer keys are in the vault", key.String()) 567 } 568 } 569 return tx.PartialSign(getter) 570 } 571 572 func (tx *Transaction) EncodeTree(encoder *text.TreeEncoder) (int, error) { 573 tx.EncodeToTree(encoder) 574 return encoder.WriteString(encoder.Tree.String()) 575 } 576 577 // String returns a human-readable string representation of the transaction data. 578 // To disable colors, set "github.com/gagliardetto/solana-go/text".DisableColors = true 579 func (tx *Transaction) String() string { 580 buf := new(bytes.Buffer) 581 _, err := tx.EncodeTree(text.NewTreeEncoder(buf, "")) 582 if err != nil { 583 panic(err) 584 } 585 return buf.String() 586 } 587 588 func (tx Transaction) ToBase64() (string, error) { 589 out, err := tx.MarshalBinary() 590 if err != nil { 591 return "", err 592 } 593 return base64.StdEncoding.EncodeToString(out), nil 594 } 595 596 func (tx Transaction) MustToBase64() string { 597 out, err := tx.ToBase64() 598 if err != nil { 599 panic(err) 600 } 601 return out 602 } 603 604 func (tx *Transaction) EncodeToTree(parent treeout.Branches) { 605 parent.ParentFunc(func(txTree treeout.Branches) { 606 txTree.Child(fmt.Sprintf("Signatures[len=%v]", len(tx.Signatures))).ParentFunc(func(signaturesBranch treeout.Branches) { 607 for _, sig := range tx.Signatures { 608 signaturesBranch.Child(sig.String()) 609 } 610 }) 611 612 txTree.Child("Message").ParentFunc(func(messageBranch treeout.Branches) { 613 tx.Message.EncodeToTree(messageBranch) 614 }) 615 }) 616 617 parent.Child(fmt.Sprintf("Instructions[len=%v]", len(tx.Message.Instructions))).ParentFunc(func(message treeout.Branches) { 618 for _, inst := range tx.Message.Instructions { 619 620 progKey, err := tx.ResolveProgramIDIndex(inst.ProgramIDIndex) 621 if err == nil { 622 accounts, err := inst.ResolveInstructionAccounts(&tx.Message) 623 if err != nil { 624 message.Child(fmt.Sprintf(text.RedBG("cannot ResolveInstructionAccounts: %s"), err)) 625 return 626 } 627 decodedInstruction, err := DecodeInstruction(progKey, accounts, inst.Data) 628 if err == nil { 629 if enToTree, ok := decodedInstruction.(text.EncodableToTree); ok { 630 enToTree.EncodeToTree(message) 631 } else { 632 message.Child(spew.Sdump(decodedInstruction)) 633 } 634 } else { 635 // TODO: log error? 636 message.Child(fmt.Sprintf(text.RedBG("cannot decode instruction for %s program: %s"), progKey, err)). 637 Child(text.IndigoBG("Program") + ": " + text.Bold("<unknown>") + " " + text.ColorizeBG(progKey.String())). 638 // 639 ParentFunc(func(programBranch treeout.Branches) { 640 programBranch.Child(text.Purple(text.Bold("Instruction")) + ": " + text.Bold("<unknown>")). 641 // 642 ParentFunc(func(instructionBranch treeout.Branches) { 643 // Data of the instruction call: 644 instructionBranch.Child(text.Sf("data[len=%v bytes]", len(inst.Data))).ParentFunc(func(paramsBranch treeout.Branches) { 645 paramsBranch.Child(bin.FormatByteSlice(inst.Data)) 646 }) 647 648 // Accounts of the instruction call: 649 instructionBranch.Child(text.Sf("accounts[len=%v]", len(accounts))).ParentFunc(func(accountsBranch treeout.Branches) { 650 for i := range accounts { 651 accountsBranch.Child(formatMeta(text.Sf("accounts[%v]", i), accounts[i])) 652 } 653 }) 654 }) 655 }) 656 } 657 } else { 658 message.Child(fmt.Sprintf(text.RedBG("cannot ResolveProgramIDIndex: %s"), err)) 659 } 660 } 661 }) 662 } 663 664 func formatMeta(name string, meta *AccountMeta) string { 665 if meta == nil { 666 return text.Shakespeare(name) + ": " + "<nil>" 667 } 668 out := text.Shakespeare(name) + ": " + text.ColorizeBG(meta.PublicKey.String()) 669 out += " [" 670 if meta.IsWritable { 671 out += "WRITE" 672 } 673 if meta.IsSigner { 674 if meta.IsWritable { 675 out += ", " 676 } 677 out += "SIGN" 678 } 679 out += "] " 680 return out 681 } 682 683 // VerifySignatures verifies all the signatures in the transaction 684 // against the pubkeys of the signers. 685 func (tx *Transaction) VerifySignatures() error { 686 msg, err := tx.Message.MarshalBinary() 687 if err != nil { 688 return err 689 } 690 691 signers := tx.Message.Signers() 692 693 if len(signers) != len(tx.Signatures) { 694 return fmt.Errorf( 695 "got %v signers, but %v signatures", 696 len(signers), 697 len(tx.Signatures), 698 ) 699 } 700 701 for i, sig := range tx.Signatures { 702 if !sig.Verify(signers[i], msg) { 703 return fmt.Errorf("invalid signature by %s", signers[i].String()) 704 } 705 } 706 707 return nil 708 } 709 710 func (tx *Transaction) GetProgramIDs() (PublicKeySlice, error) { 711 programIDs := make(PublicKeySlice, 0) 712 for ixi, inst := range tx.Message.Instructions { 713 progKey, err := tx.ResolveProgramIDIndex(inst.ProgramIDIndex) 714 if err == nil { 715 programIDs = append(programIDs, progKey) 716 } else { 717 return nil, fmt.Errorf("cannot resolve program ID for instruction %d: %w", ixi, err) 718 } 719 } 720 return programIDs, nil 721 } 722 723 func (tx *Transaction) NumWriteableAccounts() int { 724 return countWriteableAccounts(tx) 725 } 726 727 func (tx *Transaction) NumSigners() int { 728 return countSigners(tx) 729 } 730 731 func countSigners(tx *Transaction) (count int) { 732 if tx == nil { 733 return -1 734 } 735 return tx.Message.Signers().Len() 736 } 737 738 func (tx *Transaction) NumReadonlyAccounts() int { 739 return countReadonlyAccounts(tx) 740 } 741 742 func countReadonlyAccounts(tx *Transaction) (count int) { 743 if tx == nil { 744 return -1 745 } 746 return int(tx.Message.Header.NumReadonlyUnsignedAccounts) + int(tx.Message.Header.NumReadonlySignedAccounts) 747 } 748 749 func countWriteableAccounts(tx *Transaction) (count int) { 750 if tx == nil { 751 return -1 752 } 753 if !tx.Message.IsVersioned() { 754 metas, err := tx.Message.AccountMetaList() 755 if err != nil { 756 return -1 757 } 758 for _, meta := range metas { 759 if meta.IsWritable { 760 count++ 761 } 762 } 763 return count 764 } 765 numStatisKeys := len(tx.Message.AccountKeys) 766 statisKeys := tx.Message.AccountKeys 767 h := tx.Message.Header 768 for _, key := range statisKeys { 769 accIndex, ok := getStaticAccountIndex(tx, key) 770 if !ok { 771 continue 772 } 773 index := int(accIndex) 774 is := false 775 if index >= int(h.NumRequiredSignatures) { 776 // unsignedAccountIndex < numWritableUnsignedAccounts 777 is = index-int(h.NumRequiredSignatures) < (numStatisKeys-int(h.NumRequiredSignatures))-int(h.NumReadonlyUnsignedAccounts) 778 } else { 779 is = index < int(h.NumRequiredSignatures-h.NumReadonlySignedAccounts) 780 } 781 if is { 782 count++ 783 } 784 } 785 if tx.Message.IsResolved() { 786 return count 787 } 788 count += tx.Message.NumWritableLookups() 789 return count 790 } 791 792 func getStaticAccountIndex(tx *Transaction, key PublicKey) (int, bool) { 793 for idx, a := range tx.Message.AccountKeys { 794 if a.Equals(key) { 795 return (idx), true 796 } 797 } 798 return -1, false 799 }