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 }