2020-11-26 14:05:05 -08:00
package solana
import (
2021-07-27 14:53:48 -07:00
"errors"
2020-11-26 14:05:05 -08:00
"fmt"
"sort"
2021-08-09 08:21:29 -07:00
"github.com/davecgh/go-spew/spew"
2021-09-04 05:03:55 -07:00
bin "github.com/gagliardetto/binary"
2021-08-09 06:44:34 -07:00
"github.com/gagliardetto/solana-go/text"
"github.com/gagliardetto/treeout"
2020-11-26 14:05:05 -08:00
"go.uber.org/zap"
)
2020-12-16 12:34:07 -08:00
type Instruction interface {
2020-11-26 14:05:05 -08:00
ProgramID ( ) PublicKey // the programID the instruction acts on
2021-07-29 08:19:13 -07:00
Accounts ( ) [ ] * AccountMeta // returns the list of accounts the instructions requires
2020-11-26 14:05:05 -08:00
Data ( ) ( [ ] byte , error ) // the binary encoded instructions
}
2020-12-16 12:34:07 -08:00
type TransactionOption interface {
apply ( opts * transactionOptions )
2020-11-26 14:05:05 -08:00
}
2020-12-16 12:34:07 -08:00
type transactionOptions struct {
payer PublicKey
}
type transactionOptionFunc func ( opts * transactionOptions )
func ( f transactionOptionFunc ) apply ( opts * transactionOptions ) {
f ( opts )
}
func TransactionPayer ( payer PublicKey ) TransactionOption {
return transactionOptionFunc ( func ( opts * transactionOptions ) { opts . payer = payer } )
}
2021-07-20 04:37:52 -07:00
var debugNewTransaction = false
2021-07-30 10:23:34 -07:00
type TransactionBuilder struct {
instructions [ ] Instruction
recentBlockHash Hash
opts [ ] TransactionOption
}
// NewTransactionBuilder creates a new instruction builder.
func NewTransactionBuilder ( ) * TransactionBuilder {
return & TransactionBuilder { }
}
2021-08-02 07:56:12 -07:00
// AddInstruction adds the provided instruction to the builder.
func ( builder * TransactionBuilder ) AddInstruction ( instruction Instruction ) * TransactionBuilder {
2021-07-30 10:23:34 -07:00
builder . instructions = append ( builder . instructions , instruction )
return builder
}
// SetRecentBlockHash sets the recent blockhash for the instruction builder.
func ( builder * TransactionBuilder ) SetRecentBlockHash ( recentBlockHash Hash ) * TransactionBuilder {
builder . recentBlockHash = recentBlockHash
return builder
}
// WithOpt adds a TransactionOption.
func ( builder * TransactionBuilder ) WithOpt ( opt TransactionOption ) * TransactionBuilder {
builder . opts = append ( builder . opts , opt )
return builder
}
// Set transaction fee payer.
// If not set, defaults to first signer account of the first instruction.
func ( builder * TransactionBuilder ) SetFeePayer ( feePayer PublicKey ) * TransactionBuilder {
builder . opts = append ( builder . opts , TransactionPayer ( feePayer ) )
return builder
}
// Build builds and returns a *Transaction.
func ( builder * TransactionBuilder ) Build ( ) ( * Transaction , error ) {
return NewTransaction (
builder . instructions ,
builder . recentBlockHash ,
builder . opts ... ,
)
}
func NewTransaction ( instructions [ ] Instruction , recentBlockHash Hash , opts ... TransactionOption ) ( * Transaction , error ) {
2020-11-26 14:05:05 -08:00
if len ( instructions ) == 0 {
return nil , fmt . Errorf ( "requires at-least one instruction to create a transaction" )
}
2020-12-16 12:34:07 -08:00
options := transactionOptions { }
for _ , opt := range opts {
opt . apply ( & options )
}
feePayer := options . payer
if feePayer . IsZero ( ) {
2020-11-26 14:15:40 -08:00
found := false
2020-11-26 14:05:05 -08:00
for _ , act := range instructions [ 0 ] . Accounts ( ) {
if act . IsSigner {
2020-11-26 14:15:40 -08:00
feePayer = act . PublicKey
found = true
2020-11-26 14:05:05 -08:00
break
}
}
2020-11-26 14:15:40 -08:00
if ! found {
2021-07-30 10:23:34 -07:00
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" )
2020-11-26 14:15:40 -08:00
}
2020-11-26 14:05:05 -08:00
}
2021-08-31 13:23:43 -07:00
programIDs := make ( PublicKeySlice , 0 )
2020-11-26 14:05:05 -08:00
accounts := [ ] * AccountMeta { }
for _ , instruction := range instructions {
for _ , key := range instruction . Accounts ( ) {
accounts = append ( accounts , key )
}
2021-08-31 13:23:43 -07:00
programIDs . UniqueAppend ( instruction . ProgramID ( ) )
2020-11-26 14:05:05 -08:00
}
// Add programID to the account list
2021-07-19 13:10:58 -07:00
for _ , programID := range programIDs {
2020-11-26 14:05:05 -08:00
accounts = append ( accounts , & AccountMeta {
2020-12-16 12:34:07 -08:00
PublicKey : programID ,
2020-11-26 14:05:05 -08:00
IsSigner : false ,
IsWritable : false ,
} )
}
// Sort. Prioritizing first by signer, then by writable
2021-09-21 08:33:48 -07:00
sort . SliceStable ( accounts , func ( i , j int ) bool {
2020-11-26 14:05:05 -08:00
return accounts [ i ] . less ( accounts [ j ] )
} )
2020-12-16 12:34:07 -08:00
uniqAccountsMap := map [ PublicKey ] uint64 { }
2020-11-26 14:05:05 -08:00
uniqAccounts := [ ] * AccountMeta { }
for _ , acc := range accounts {
2020-12-16 12:34:07 -08:00
if index , found := uniqAccountsMap [ acc . PublicKey ] ; found {
2020-11-26 14:05:05 -08:00
uniqAccounts [ index ] . IsWritable = uniqAccounts [ index ] . IsWritable || acc . IsWritable
continue
}
uniqAccounts = append ( uniqAccounts , acc )
2020-12-16 12:34:07 -08:00
uniqAccountsMap [ acc . PublicKey ] = uint64 ( len ( uniqAccounts ) - 1 )
2020-11-26 14:05:05 -08:00
}
2021-07-20 04:37:52 -07:00
if debugNewTransaction {
zlog . Debug ( "unique account sorted" , zap . Int ( "account_count" , len ( uniqAccounts ) ) )
}
2020-11-26 14:05:05 -08:00
// Move fee payer to the front
feePayerIndex := - 1
for idx , acc := range uniqAccounts {
2020-11-26 14:15:40 -08:00
if acc . PublicKey . Equals ( feePayer ) {
2020-11-26 14:05:05 -08:00
feePayerIndex = idx
}
}
2021-07-20 04:37:52 -07:00
if debugNewTransaction {
zlog . Debug ( "current fee payer index" , zap . Int ( "fee_payer_index" , feePayerIndex ) )
}
2020-11-26 14:05:05 -08:00
accountCount := len ( uniqAccounts )
if feePayerIndex < 0 {
// fee payer is not part of accounts we want to add it
accountCount ++
}
finalAccounts := make ( [ ] * AccountMeta , accountCount )
itr := 1
for idx , uniqAccount := range uniqAccounts {
if idx == feePayerIndex {
uniqAccount . IsSigner = true
uniqAccount . IsWritable = true
finalAccounts [ 0 ] = uniqAccount
continue
}
finalAccounts [ itr ] = uniqAccount
itr ++
}
2021-08-22 09:45:55 -07:00
if feePayerIndex < 0 {
// fee payer is not part of accounts we want to add it
feePayerAccount := & AccountMeta {
PublicKey : feePayer ,
IsSigner : true ,
IsWritable : true ,
}
finalAccounts [ 0 ] = feePayerAccount
}
2020-11-26 14:05:05 -08:00
message := Message {
2021-07-30 10:23:34 -07:00
RecentBlockhash : recentBlockHash ,
2020-11-26 14:05:05 -08:00
}
2021-07-19 02:28:55 -07:00
accountKeyIndex := map [ string ] uint16 { }
2020-11-26 14:05:05 -08:00
for idx , acc := range finalAccounts {
2020-11-26 17:06:13 -08:00
2021-07-20 04:37:52 -07:00
if debugNewTransaction {
zlog . Debug ( "transaction account" ,
zap . Int ( "account_index" , idx ) ,
zap . Stringer ( "account_pub_key" , acc . PublicKey ) ,
)
}
2020-11-26 17:06:13 -08:00
2020-11-26 14:05:05 -08:00
message . AccountKeys = append ( message . AccountKeys , acc . PublicKey )
2021-07-19 02:28:55 -07:00
accountKeyIndex [ acc . PublicKey . String ( ) ] = uint16 ( idx )
2020-11-26 14:05:05 -08:00
if acc . IsSigner {
message . Header . NumRequiredSignatures ++
if ! acc . IsWritable {
message . Header . NumReadonlySignedAccounts ++
}
continue
}
if ! acc . IsWritable {
message . Header . NumReadonlyUnsignedAccounts ++
}
}
2021-07-20 04:37:52 -07:00
if debugNewTransaction {
zlog . Debug ( "message header compiled" ,
zap . Uint8 ( "num_required_signatures" , message . Header . NumRequiredSignatures ) ,
zap . Uint8 ( "num_readonly_signed_accounts" , message . Header . NumReadonlySignedAccounts ) ,
zap . Uint8 ( "num_readonly_unsigned_accounts" , message . Header . NumReadonlyUnsignedAccounts ) ,
)
}
2020-11-26 14:05:05 -08:00
2021-07-29 08:19:13 -07:00
for txIdx , instruction := range instructions {
2020-11-26 14:05:05 -08:00
accounts = instruction . Accounts ( )
2021-07-19 02:28:55 -07:00
accountIndex := make ( [ ] uint16 , len ( accounts ) )
2020-11-26 14:05:05 -08:00
for idx , acc := range accounts {
accountIndex [ idx ] = accountKeyIndex [ acc . PublicKey . String ( ) ]
}
data , err := instruction . Data ( )
if err != nil {
2021-07-29 08:19:13 -07:00
return nil , fmt . Errorf ( "unable to encode instructions [%d]: %w" , txIdx , err )
2020-11-26 14:05:05 -08:00
}
message . Instructions = append ( message . Instructions , CompiledInstruction {
ProgramIDIndex : accountKeyIndex [ instruction . ProgramID ( ) . String ( ) ] ,
AccountCount : bin . Varuint16 ( uint16 ( len ( accountIndex ) ) ) ,
Accounts : accountIndex ,
DataLength : bin . Varuint16 ( uint16 ( len ( data ) ) ) ,
Data : data ,
} )
}
return & Transaction {
Message : message ,
} , nil
}
type privateKeyGetter func ( key PublicKey ) * PrivateKey
2021-07-27 14:53:48 -07:00
func ( tx * Transaction ) MarshalBinary ( ) ( [ ] byte , error ) {
if len ( tx . Signatures ) == 0 || len ( tx . Signatures ) != int ( tx . Message . Header . NumRequiredSignatures ) {
return nil , errors . New ( "signature verification failed" )
}
messageContent , err := tx . Message . MarshalBinary ( )
if err != nil {
return nil , fmt . Errorf ( "failed to encode tx.Message to binary: %w" , err )
}
2021-08-08 08:53:42 -07:00
var signatureCount [ ] byte
2021-08-21 08:53:13 -07:00
bin . EncodeCompactU16Length ( & signatureCount , len ( tx . Signatures ) )
2021-07-27 14:53:48 -07:00
output := make ( [ ] byte , 0 , len ( signatureCount ) + len ( signatureCount ) * 64 + len ( messageContent ) )
output = append ( output , signatureCount ... )
for _ , sig := range tx . Signatures {
output = append ( output , sig [ : ] ... )
}
output = append ( output , messageContent ... )
return output , nil
}
2021-08-21 08:53:13 -07:00
func ( tx * Transaction ) MarshalWithEncoder ( encoder * bin . Encoder ) error {
out , err := tx . MarshalBinary ( )
if err != nil {
return err
}
return encoder . WriteBytes ( out , false )
}
func ( tx * Transaction ) UnmarshalWithDecoder ( decoder * bin . Decoder ) ( err error ) {
2021-08-08 08:53:42 -07:00
{
2021-08-21 08:53:13 -07:00
numSignatures , err := bin . DecodeCompactU16LengthFromByteReader ( decoder )
2021-08-08 08:53:42 -07:00
if err != nil {
return err
}
for i := 0 ; i < numSignatures ; i ++ {
sigBytes , err := decoder . ReadNBytes ( 64 )
if err != nil {
return err
}
var sig Signature
copy ( sig [ : ] , sigBytes )
tx . Signatures = append ( tx . Signatures , sig )
}
}
{
2021-08-21 08:53:13 -07:00
err := tx . Message . UnmarshalWithDecoder ( decoder )
2021-08-08 08:53:42 -07:00
if err != nil {
return err
}
}
return nil
}
2021-07-29 08:19:13 -07:00
func ( tx * Transaction ) Sign ( getter privateKeyGetter ) ( out [ ] Signature , err error ) {
messageContent , err := tx . Message . MarshalBinary ( )
2021-07-27 14:53:48 -07:00
if err != nil {
2020-11-26 14:05:05 -08:00
return nil , fmt . Errorf ( "unable to encode message for signing: %w" , err )
}
2021-07-29 08:19:13 -07:00
signerKeys := tx . Message . signerKeys ( )
2020-11-26 14:05:05 -08:00
for _ , key := range signerKeys {
privateKey := getter ( key )
if privateKey == nil {
return nil , fmt . Errorf ( "signer key %q not found. Ensure all the signer keys are in the vault" , key . String ( ) )
}
2021-07-27 14:53:48 -07:00
s , err := privateKey . Sign ( messageContent )
2020-11-26 14:05:05 -08:00
if err != nil {
return nil , fmt . Errorf ( "failed to signed with key %q: %w" , key . String ( ) , err )
}
2021-07-29 08:19:13 -07:00
tx . Signatures = append ( tx . Signatures , s )
2020-11-26 14:05:05 -08:00
}
2021-07-29 08:19:13 -07:00
return tx . Signatures , nil
2020-11-26 14:05:05 -08:00
}
2021-08-09 06:44:34 -07:00
2021-08-09 08:39:09 -07:00
func ( tx * Transaction ) EncodeTree ( encoder * text . TreeEncoder ) ( int , error ) {
2021-09-06 06:45:03 -07:00
if len ( encoder . Doc ) == 0 {
encoder . Doc = "Transaction"
2021-08-09 06:44:34 -07:00
}
tx . EncodeToTree ( encoder )
return encoder . WriteString ( encoder . Tree . String ( ) )
}
func ( tx * Transaction ) EncodeToTree ( parent treeout . Branches ) {
parent . ParentFunc ( func ( txTree treeout . Branches ) {
2021-09-06 04:24:02 -07:00
txTree . Child ( fmt . Sprintf ( "Signatures[len=%v]" , len ( tx . Signatures ) ) ) . ParentFunc ( func ( signaturesBranch treeout . Branches ) {
2021-08-09 06:44:34 -07:00
for _ , sig := range tx . Signatures {
signaturesBranch . Child ( sig . String ( ) )
}
} )
txTree . Child ( "Message" ) . ParentFunc ( func ( messageBranch treeout . Branches ) {
tx . Message . EncodeToTree ( messageBranch )
} )
} )
2021-09-06 04:24:02 -07:00
parent . Child ( fmt . Sprintf ( "Instructions[len=%v]" , len ( tx . Message . Instructions ) ) ) . ParentFunc ( func ( message treeout . Branches ) {
2021-08-09 06:44:34 -07:00
for _ , inst := range tx . Message . Instructions {
progKey , err := tx . ResolveProgramIDIndex ( inst . ProgramIDIndex )
2021-09-04 16:13:45 -07:00
if err == nil {
decodedInstruction , err := DecodeInstruction ( progKey , inst . ResolveInstructionAccounts ( & tx . Message ) , inst . Data )
if err == nil {
if enToTree , ok := decodedInstruction . ( text . EncodableToTree ) ; ok {
enToTree . EncodeToTree ( message )
} else {
message . Child ( spew . Sdump ( decodedInstruction ) )
}
} else {
// TODO: log error?
message . Child ( fmt . Sprintf ( text . RedBG ( "cannot decode instruction for %s program: %s" ) , progKey , err ) )
}
2021-08-09 08:21:29 -07:00
} else {
2021-09-04 16:13:45 -07:00
message . Child ( fmt . Sprintf ( text . RedBG ( "cannot ResolveProgramIDIndex: %s" ) , err ) )
2021-08-09 06:44:34 -07:00
}
}
} )
}