github.com/gagliardetto/solana-go@v1.11.0/transaction_test.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  	"encoding/base64"
    22  	"testing"
    23  
    24  	bin "github.com/gagliardetto/binary"
    25  	"github.com/mr-tron/base58"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  type testTransactionInstructions struct {
    31  	accounts  []*AccountMeta
    32  	data      []byte
    33  	programID PublicKey
    34  }
    35  
    36  func (t *testTransactionInstructions) Accounts() []*AccountMeta {
    37  	return t.accounts
    38  }
    39  
    40  func (t *testTransactionInstructions) ProgramID() PublicKey {
    41  	return t.programID
    42  }
    43  
    44  func (t *testTransactionInstructions) Data() ([]byte, error) {
    45  	return t.data, nil
    46  }
    47  
    48  func TestNewTransaction(t *testing.T) {
    49  	debugNewTransaction = true
    50  
    51  	instructions := []Instruction{
    52  		&testTransactionInstructions{
    53  			accounts: []*AccountMeta{
    54  				{PublicKey: MustPublicKeyFromBase58("A9QnpgfhCkmiBSjgBuWk76Wo3HxzxvDopUq9x6UUMmjn"), IsSigner: true, IsWritable: false},
    55  				{PublicKey: MustPublicKeyFromBase58("9hFtYBYmBJCVguRYs9pBTWKYAFoKfjYR7zBPpEkVsmD"), IsSigner: true, IsWritable: true},
    56  			},
    57  			data:      []byte{0xaa, 0xbb},
    58  			programID: MustPublicKeyFromBase58("11111111111111111111111111111111"),
    59  		},
    60  		&testTransactionInstructions{
    61  			accounts: []*AccountMeta{
    62  				{PublicKey: MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111"), IsSigner: false, IsWritable: false},
    63  				{PublicKey: MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111"), IsSigner: false, IsWritable: true},
    64  				{PublicKey: MustPublicKeyFromBase58("9hFtYBYmBJCVguRYs9pBTWKYAFoKfjYR7zBPpEkVsmD"), IsSigner: false, IsWritable: true},
    65  				{PublicKey: MustPublicKeyFromBase58("6FzXPEhCJoBx7Zw3SN9qhekHemd6E2b8kVguitmVAngW"), IsSigner: true, IsWritable: false},
    66  			},
    67  			data:      []byte{0xcc, 0xdd},
    68  			programID: MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111"),
    69  		},
    70  	}
    71  
    72  	blockhash, err := HashFromBase58("A9QnpgfhCkmiBSjgBuWk76Wo3HxzxvDopUq9x6UUMmjn")
    73  	require.NoError(t, err)
    74  
    75  	trx, err := NewTransaction(instructions, blockhash)
    76  	require.NoError(t, err)
    77  
    78  	assert.Equal(t, trx.Message.Header, MessageHeader{
    79  		NumRequiredSignatures:       3,
    80  		NumReadonlySignedAccounts:   1,
    81  		NumReadonlyUnsignedAccounts: 3,
    82  	})
    83  
    84  	assert.Equal(t, trx.Message.RecentBlockhash, blockhash)
    85  
    86  	assert.Equal(t, trx.Message.AccountKeys, PublicKeySlice{
    87  		MustPublicKeyFromBase58("A9QnpgfhCkmiBSjgBuWk76Wo3HxzxvDopUq9x6UUMmjn"),
    88  		MustPublicKeyFromBase58("9hFtYBYmBJCVguRYs9pBTWKYAFoKfjYR7zBPpEkVsmD"),
    89  		MustPublicKeyFromBase58("6FzXPEhCJoBx7Zw3SN9qhekHemd6E2b8kVguitmVAngW"),
    90  		MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111"),
    91  		MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111"),
    92  		MustPublicKeyFromBase58("11111111111111111111111111111111"),
    93  		MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111"),
    94  	})
    95  
    96  	assert.Equal(t, trx.Message.Instructions, []CompiledInstruction{
    97  		{
    98  			ProgramIDIndex: 5,
    99  			Accounts:       []uint16{0, 0o1},
   100  			Data:           []byte{0xaa, 0xbb},
   101  		},
   102  		{
   103  			ProgramIDIndex: 6,
   104  			Accounts:       []uint16{4, 3, 1, 2},
   105  			Data:           []byte{0xcc, 0xdd},
   106  		},
   107  	})
   108  }
   109  
   110  func TestPartialSignTransaction(t *testing.T) {
   111  	signers := []PrivateKey{
   112  		NewWallet().PrivateKey,
   113  		NewWallet().PrivateKey,
   114  		NewWallet().PrivateKey,
   115  	}
   116  	instructions := []Instruction{
   117  		&testTransactionInstructions{
   118  			accounts: []*AccountMeta{
   119  				{PublicKey: signers[0].PublicKey(), IsSigner: true, IsWritable: false},
   120  				{PublicKey: signers[1].PublicKey(), IsSigner: true, IsWritable: true},
   121  				{PublicKey: signers[2].PublicKey(), IsSigner: true, IsWritable: false},
   122  			},
   123  			data:      []byte{0xaa, 0xbb},
   124  			programID: MustPublicKeyFromBase58("11111111111111111111111111111111"),
   125  		},
   126  	}
   127  
   128  	blockhash, err := HashFromBase58("A9QnpgfhCkmiBSjgBuWk76Wo3HxzxvDopUq9x6UUMmjn")
   129  	require.NoError(t, err)
   130  
   131  	trx, err := NewTransaction(instructions, blockhash)
   132  	require.NoError(t, err)
   133  
   134  	assert.Equal(t, trx.Message.Header.NumRequiredSignatures, uint8(3))
   135  
   136  	// Test various signing orders
   137  	signingOrders := [][]int{
   138  		{0, 1, 2}, // ABC
   139  		{0, 2, 1}, // ACB
   140  		{1, 0, 2}, // BAC
   141  		{1, 2, 0}, // BCA
   142  		{2, 0, 1}, // CAB
   143  		{2, 1, 0}, // CBA
   144  	}
   145  
   146  	for _, order := range signingOrders {
   147  		// Reset the transaction signatures before each test
   148  		trx.Signatures = make([]Signature, len(signers))
   149  
   150  		// Sign the transaction in the specified order
   151  		for _, idx := range order {
   152  			signer := signers[idx]
   153  			signatures, err := trx.PartialSign(func(key PublicKey) *PrivateKey {
   154  				if key.Equals(signer.PublicKey()) {
   155  					return &signer
   156  				}
   157  				return nil
   158  			})
   159  			require.NoError(t, err)
   160  			assert.Equal(t, len(signatures), 3)
   161  		}
   162  		// Verify Signatures
   163  		require.NoError(t, trx.VerifySignatures())
   164  	}
   165  }
   166  
   167  func TestSignTransaction(t *testing.T) {
   168  	signers := []PrivateKey{
   169  		NewWallet().PrivateKey,
   170  		NewWallet().PrivateKey,
   171  	}
   172  	instructions := []Instruction{
   173  		&testTransactionInstructions{
   174  			accounts: []*AccountMeta{
   175  				{PublicKey: signers[0].PublicKey(), IsSigner: true, IsWritable: false},
   176  				{PublicKey: signers[1].PublicKey(), IsSigner: true, IsWritable: true},
   177  			},
   178  			data:      []byte{0xaa, 0xbb},
   179  			programID: MustPublicKeyFromBase58("11111111111111111111111111111111"),
   180  		},
   181  	}
   182  
   183  	blockhash, err := HashFromBase58("A9QnpgfhCkmiBSjgBuWk76Wo3HxzxvDopUq9x6UUMmjn")
   184  	require.NoError(t, err)
   185  
   186  	trx, err := NewTransaction(instructions, blockhash)
   187  	require.NoError(t, err)
   188  
   189  	assert.Equal(t, trx.Message.Header.NumRequiredSignatures, uint8(2))
   190  
   191  	t.Run("should reject missing signer(s)", func(t *testing.T) {
   192  		_, err := trx.Sign(func(key PublicKey) *PrivateKey {
   193  			if key.Equals(signers[0].PublicKey()) {
   194  				return &signers[0]
   195  			}
   196  			return nil
   197  		})
   198  		require.Error(t, err)
   199  	})
   200  
   201  	t.Run("should sign with signer(s)", func(t *testing.T) {
   202  		signatures, err := trx.Sign(func(key PublicKey) *PrivateKey {
   203  			for _, signer := range signers {
   204  				if key.Equals(signer.PublicKey()) {
   205  					return &signer
   206  				}
   207  			}
   208  			return nil
   209  		})
   210  		require.NoError(t, err)
   211  		assert.Equal(t, len(signatures), 2)
   212  	})
   213  }
   214  
   215  func FuzzTransaction(f *testing.F) {
   216  	encoded := "AfjEs3XhTc3hrxEvlnMPkm/cocvAUbFNbCl00qKnrFue6J53AhEqIFmcJJlJW3EDP5RmcMz+cNTTcZHW/WJYwAcBAAEDO8hh4VddzfcO5jbCt95jryl6y8ff65UcgukHNLWH+UQGgxCGGpgyfQVQV02EQYqm4QwzUt2qf9f1gVLM7rI4hwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6ANIF55zOZWROWRkeh+lExxZBnKFqbvIxZDLE7EijjoBAgIAAQwCAAAAOTAAAAAAAAA="
   217  	data, err := base64.StdEncoding.DecodeString(encoded)
   218  	require.NoError(f, err)
   219  	f.Add(data)
   220  
   221  	f.Fuzz(func(t *testing.T, data []byte) {
   222  		require.NotPanics(t, func() {
   223  			TransactionFromDecoder(bin.NewBinDecoder(data))
   224  		})
   225  	})
   226  }
   227  
   228  func TestTransactionDecode(t *testing.T) {
   229  	encoded := "AfjEs3XhTc3hrxEvlnMPkm/cocvAUbFNbCl00qKnrFue6J53AhEqIFmcJJlJW3EDP5RmcMz+cNTTcZHW/WJYwAcBAAEDO8hh4VddzfcO5jbCt95jryl6y8ff65UcgukHNLWH+UQGgxCGGpgyfQVQV02EQYqm4QwzUt2qf9f1gVLM7rI4hwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6ANIF55zOZWROWRkeh+lExxZBnKFqbvIxZDLE7EijjoBAgIAAQwCAAAAOTAAAAAAAAA="
   230  	data, err := base64.StdEncoding.DecodeString(encoded)
   231  	require.NoError(t, err)
   232  
   233  	tx, err := TransactionFromDecoder(bin.NewBinDecoder(data))
   234  	require.NoError(t, err)
   235  	require.NotNil(t, tx)
   236  
   237  	require.Len(t, tx.Signatures, 1)
   238  	require.Equal(t,
   239  		MustSignatureFromBase58("5yUSwqQqeZLEEYKxnG4JC4XhaaBpV3RS4nQbK8bQTyjLX5btVq9A1Ja5nuJzV7Z3Zq8G6EVKFvN4DKUL6PSAxmTk"),
   240  		tx.Signatures[0],
   241  	)
   242  
   243  	require.Equal(t,
   244  		PublicKeySlice{
   245  			MustPublicKeyFromBase58("52NGrUqh6tSGhr59ajGxsH3VnAaoRdSdTbAaV9G3UW35"),
   246  			MustPublicKeyFromBase58("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"),
   247  			MustPublicKeyFromBase58("11111111111111111111111111111111"),
   248  		},
   249  		tx.Message.AccountKeys,
   250  	)
   251  
   252  	require.Equal(t,
   253  		MessageHeader{
   254  			NumRequiredSignatures:       1,
   255  			NumReadonlySignedAccounts:   0,
   256  			NumReadonlyUnsignedAccounts: 1,
   257  		},
   258  		tx.Message.Header,
   259  	)
   260  
   261  	require.Equal(t,
   262  		MustHashFromBase58("GcgVK9buRA7YepZh3zXuS399GJAESCisLnLDBCmR5Aoj"),
   263  		tx.Message.RecentBlockhash,
   264  	)
   265  
   266  	decodedData, err := base58.Decode("3Bxs4ART6LMJ13T5")
   267  	require.NoError(t, err)
   268  	require.Equal(t, 12, len(decodedData))
   269  	require.Equal(t, []byte{2, 0, 0, 0, 57, 48, 0, 0, 0, 0, 0, 0}, decodedData)
   270  	require.Equal(t,
   271  		[]CompiledInstruction{
   272  			{
   273  				ProgramIDIndex: 2,
   274  				Accounts: []uint16{
   275  					0,
   276  					1,
   277  				},
   278  				Data: Base58(decodedData),
   279  			},
   280  		},
   281  		tx.Message.Instructions,
   282  	)
   283  }
   284  
   285  func TestTransactionVerifySignatures(t *testing.T) {
   286  	type testCase struct {
   287  		Transaction string
   288  	}
   289  
   290  	testCases := []testCase{
   291  		{
   292  			Transaction: "AVBFwRrn4wroV9+NVQfgg/GbjFtQFodLnNI5oTpDMQiQ4HfZNyFzcFamHSSFW4p5wc3efeEKvykbmk8jzf2LCQwBAAIGjYddInd/DSl2KJCP18GhEDlaJyPKVrgBGGsr3TF6jSYPgr3AdITNKr2UQVQ5I+Wh5StQv/a5XdLr6VN4Y21My1M/Y1FNK5wQLKJa1LYfN/HAudufFVtc0fRPR6AMUJ9UrkRI7sjY/PnpcXLF7A7SBvJrWu+o8+7QIaD8sL9aXkGFDy1uAqR6+CTQmradxC1wyyjL+iSft+5XudJWwSdi7wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi+i1vCST+HNO0DEchpEJImMHhZ1BReuf7poRqmXpeA8CBAUBAgMCAgcAAwAAAAEABQIAAAwCAAAA6w0AAAAAAAA=",
   293  		},
   294  		{
   295  			Transaction: "AWwhMTxKhl9yZOlidY0u3gYmy+J/6V3kFSXU7GgK5zwN+SwljR2dOlHgKtUDRX8uee2HtfeyL3t4lB3n749L4QQBAAIEFg+6wTr33dgF0xcKPeDGvZcSah4CwNJZ0Khu+CHW5cehpkZfTC6/JEwx2AvJXCc0WjQk5CjC3vM+ztnpDT9wGwan1RcYx3TJKFZjmGkdXraLXrijm0ttXHNVWyEAAAAA3OXr4eScO58RTLVUTFCpnsDWktY/Vnla4Cmsg9nqi+Jr/+AAgahV8wmBK4mnz9WwJSryq8x2Ic0asytADGhLZAEDAwABAigCAAAABwAAAAEAAAAAAAAAz+dyuQIAAAAIn18BAAAAAPsVKAcAAAAA",
   296  		},
   297  		{
   298  			Transaction: "ARZsk8+AvvT9onUT8FU1VRaiC8Sp+FKveOwhdPoigWHA+MGNcIOqbow6mwSILEYvvyOB/fi3UQ/xKQCjEtxBRgIBAAIFKIX92BRrkgEfrLEXAvXtw7OgPPhHU+62C8DB5QPoMgNSbKXgdub0sr7Yp3Nvdrsp6SDoJ4gdoyRad2AV+Japj0dRtYW4OxE78FvRZTeqHFy2My/m12/afGIPS8iUnMGlBqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAC/jt8clGtWu0PSX5i4e2vlERcwCmEmGvn5+U7telqAiK4hdAN78GteFjqtJrxLXxpVNKsu1lfdcFPXa/Kcg4e5AQQEAQADAicmMiQQAiGujz0xoTQSQCgAMPOroDk5F0hQ/BgzEkBBvVKWIY41EkA=",
   299  		},
   300  		{
   301  			Transaction: "Ad7TPpYTvSpO//KNA5YTZVojVwz4NlH4gH9ktl+rTObJcgo8QkqmHK4t6DQr9dD58B/A/5/N7v9K+0j6y1TVCAsBAAMFA9maY4S727Z/lOSb08nHehVFsC32kTKMMPjPJp111bKM0Fl1Dg04vV2x9nL2TCqSHmjT8xg6wUAzjZa1+6YCBQan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAJDQfslK1yQFkGqDXWu6cthRNuYGlajYMOmtoSJB6hmPAQQEAQIDAE0CAAAAAwAAAAAAAAD5FSgHAAAAAPoVKAcAAAAA+xUoBwAAAADECMJOPX7e7fOF5Hrq9xhdch2Uqhg8vQOYyZM/6V983gHQ0gNiAAAAAA==",
   302  		},
   303  		{
   304  			Transaction: "Ak8jvC3ch5hq3lhOHPkACoFepIUON2zEN4KRcw4lDS6GBsQfnSdzNGPETm/yi0hPKk75/i2VXFj0FLUWnGR64ADyUbqnirFjFtaSNgcGi02+Tm7siT4CPpcaTq0jxfYQK/h9FdxXXPnLry74J+RE8yji/BtJ/Cjxbx+TIHigeIYJAgEBBByE1Y6EqCJKsr7iEupU6lsBHtBdtI4SK3yWMCFA0iEKeFPgnGmtp+1SIX1Ak+sN65iBaR7v4Iim5m1OEuFQTgi9N57UnhNpCNuUePaTt7HJaFBmyeZB3deXeKWVudpY3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWVECK/n3a7QR6OKWYR4DuAVjS6FXgZj82W0dJpSIPnEBAwQAAgEDDAIAAABAQg8AAAAAAA==",
   305  		},
   306  	}
   307  
   308  	for _, tc := range testCases {
   309  		txBin, err := base64.StdEncoding.DecodeString(tc.Transaction)
   310  		require.NoError(t, err)
   311  		tx, err := TransactionFromDecoder(bin.NewBinDecoder(txBin))
   312  		require.NoError(t, err)
   313  		require.NoError(t, tx.VerifySignatures())
   314  		require.Equal(t, len(tx.Signatures), len(tx.Message.Signers()))
   315  	}
   316  }
   317  
   318  func BenchmarkTransactionFromDecoder(b *testing.B) {
   319  	txString := "Ak8jvC3ch5hq3lhOHPkACoFepIUON2zEN4KRcw4lDS6GBsQfnSdzNGPETm/yi0hPKk75/i2VXFj0FLUWnGR64ADyUbqnirFjFtaSNgcGi02+Tm7siT4CPpcaTq0jxfYQK/h9FdxXXPnLry74J+RE8yji/BtJ/Cjxbx+TIHigeIYJAgEBBByE1Y6EqCJKsr7iEupU6lsBHtBdtI4SK3yWMCFA0iEKeFPgnGmtp+1SIX1Ak+sN65iBaR7v4Iim5m1OEuFQTgi9N57UnhNpCNuUePaTt7HJaFBmyeZB3deXeKWVudpY3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWVECK/n3a7QR6OKWYR4DuAVjS6FXgZj82W0dJpSIPnEBAwQAAgEDDAIAAABAQg8AAAAAAA=="
   320  	txBin, err := base64.StdEncoding.DecodeString(txString)
   321  	if err != nil {
   322  		b.Error(err)
   323  	}
   324  	b.ReportAllocs()
   325  	b.ResetTimer()
   326  
   327  	for i := 0; i < b.N; i++ {
   328  		_, err := TransactionFromDecoder(bin.NewBinDecoder(txBin))
   329  		if err != nil {
   330  			b.Error(err)
   331  		}
   332  	}
   333  }
   334  
   335  func BenchmarkTransactionVerifySignatures(b *testing.B) {
   336  	txString := "Ak8jvC3ch5hq3lhOHPkACoFepIUON2zEN4KRcw4lDS6GBsQfnSdzNGPETm/yi0hPKk75/i2VXFj0FLUWnGR64ADyUbqnirFjFtaSNgcGi02+Tm7siT4CPpcaTq0jxfYQK/h9FdxXXPnLry74J+RE8yji/BtJ/Cjxbx+TIHigeIYJAgEBBByE1Y6EqCJKsr7iEupU6lsBHtBdtI4SK3yWMCFA0iEKeFPgnGmtp+1SIX1Ak+sN65iBaR7v4Iim5m1OEuFQTgi9N57UnhNpCNuUePaTt7HJaFBmyeZB3deXeKWVudpY3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWVECK/n3a7QR6OKWYR4DuAVjS6FXgZj82W0dJpSIPnEBAwQAAgEDDAIAAABAQg8AAAAAAA=="
   337  	txBin, err := base64.StdEncoding.DecodeString(txString)
   338  	if err != nil {
   339  		b.Error(err)
   340  	}
   341  
   342  	tx, err := TransactionFromDecoder(bin.NewBinDecoder(txBin))
   343  	if err != nil {
   344  		b.Error(err)
   345  	}
   346  	b.ReportAllocs()
   347  	b.ResetTimer()
   348  
   349  	for i := 0; i < b.N; i++ {
   350  		tx.VerifySignatures()
   351  	}
   352  }