2020-11-26 14:05:05 -08:00
package solana
import (
"bytes"
"fmt"
"sort"
bin "github.com/dfuse-io/binary"
"go.uber.org/zap"
)
2020-12-16 12:34:07 -08:00
type Instruction interface {
2020-11-26 14:05:05 -08:00
Accounts ( ) [ ] * AccountMeta // returns the list of accounts the instructions requires
ProgramID ( ) PublicKey // the programID the instruction acts on
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-06-30 14:35:30 -07:00
func NewTransaction ( instructions [ ] Instruction , blockHash 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 {
return nil , fmt . Errorf ( "cannot determine fee payer. You can ether pass the fee payer vai the 'TransactionWithInstructions' option parameter or it fallback to the first instruction's first signer" )
}
2020-11-26 14:05:05 -08:00
}
2020-12-16 12:34:07 -08:00
programIDs := map [ PublicKey ] bool { }
2020-11-26 14:05:05 -08:00
accounts := [ ] * AccountMeta { }
for _ , instruction := range instructions {
for _ , key := range instruction . Accounts ( ) {
accounts = append ( accounts , key )
}
2020-12-16 12:34:07 -08:00
programIDs [ instruction . ProgramID ( ) ] = true
2020-11-26 14:05:05 -08:00
}
// Add programID to the account list
2020-12-15 15:07:11 -08: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
sort . Slice ( accounts , func ( i , j int ) bool {
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
}
zlog . Debug ( "unique account sorted" , zap . Int ( "account_count" , len ( uniqAccounts ) ) )
// 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
}
}
zlog . Debug ( "current fee payer index" , zap . Int ( "fee_payer_index" , feePayerIndex ) )
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 ++
}
message := Message {
2020-11-26 17:06:13 -08:00
RecentBlockhash : blockHash ,
2020-11-26 14:05:05 -08:00
}
accountKeyIndex := map [ string ] uint8 { }
for idx , acc := range finalAccounts {
2020-11-26 17:06:13 -08:00
zlog . Debug ( "transaction account" ,
zap . Int ( "account_index" , idx ) ,
zap . Stringer ( "account_pub_key" , acc . PublicKey ) ,
)
2020-11-26 14:05:05 -08:00
message . AccountKeys = append ( message . AccountKeys , acc . PublicKey )
accountKeyIndex [ acc . PublicKey . String ( ) ] = uint8 ( idx )
if acc . IsSigner {
message . Header . NumRequiredSignatures ++
if ! acc . IsWritable {
message . Header . NumReadonlySignedAccounts ++
}
continue
}
if ! acc . IsWritable {
message . Header . NumReadonlyUnsignedAccounts ++
}
}
2020-11-26 17:06:13 -08:00
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
for trxIdx , instruction := range instructions {
accounts = instruction . Accounts ( )
accountIndex := make ( [ ] uint8 , len ( accounts ) )
for idx , acc := range accounts {
accountIndex [ idx ] = accountKeyIndex [ acc . PublicKey . String ( ) ]
}
data , err := instruction . Data ( )
if err != nil {
return nil , fmt . Errorf ( "unable to encode instructions [%d]: %w" , trxIdx , err )
}
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
func ( t * Transaction ) Sign ( getter privateKeyGetter ) ( out [ ] Signature , err error ) {
buf := new ( bytes . Buffer )
if err = bin . NewEncoder ( buf ) . Encode ( t . Message ) ; err != nil {
return nil , fmt . Errorf ( "unable to encode message for signing: %w" , err )
}
messageCnt := buf . Bytes ( )
signerKeys := t . Message . signerKeys ( )
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 ( ) )
}
s , err := privateKey . Sign ( messageCnt )
if err != nil {
return nil , fmt . Errorf ( "failed to signed with key %q: %w" , key . String ( ) , err )
}
t . Signatures = append ( t . Signatures , s )
}
return t . Signatures , nil
}