-
Notifications
You must be signed in to change notification settings - Fork 299
feat(sol): add transaction size benchmark tool #7785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+314
−0
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Solana SDK Scripts | ||
|
|
||
| ## Transaction Size Benchmark | ||
|
|
||
| Determines safe transaction limits for Solana legacy transactions (1232 byte limit). | ||
|
|
||
| **Run:** | ||
| ```bash | ||
| npx tsx modules/sdk-coin-sol/scripts/transaction-size-benchmark.ts | ||
| ``` | ||
|
|
||
| **Output:** | ||
| - Console: Test results and recommended limits | ||
| - File: `transaction-size-benchmark-results.json` | ||
|
|
||
| **Tests:** | ||
| - Token transfers with ATA creation (new recipients) | ||
| - Token transfers without ATA creation (existing accounts) | ||
|
|
295 changes: 295 additions & 0 deletions
295
modules/sdk-coin-sol/scripts/transaction-size-benchmark.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,295 @@ | ||
| /** | ||
| * Transaction Size Benchmark Script | ||
| * | ||
| * This script empirically tests Solana transaction size limits by building | ||
| * transactions with varying numbers of recipients and measuring the serialized | ||
| * payload size. This helps determine safe, conservative limits for production use. | ||
| * | ||
| * Solana Legacy Transaction Constraints: | ||
| * - Maximum transaction size: 1232 bytes | ||
| * - Each instruction includes program ID, accounts, and instruction data | ||
| * - Account metadata and signatures add to the total size | ||
| * | ||
| * Run: npx tsx modules/sdk-coin-sol/scripts/transaction-size-benchmark.ts | ||
| */ | ||
|
|
||
| import { TransactionBuilderFactory, KeyPair } from '../src'; | ||
| import { coins } from '@bitgo/statics'; | ||
|
|
||
| const SOLANA_LEGACY_TX_SIZE_LIMIT = 1232; | ||
| const TEST_BLOCKHASH = 'GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi'; | ||
| const TEST_PRIVATE_KEY = '5jtsd9SmUH5mFZL7ywNNmqmxxdXw4FQ5GJQprXmrdX4LCuUwMBivCfUX2ar8hGdnLHDGrVKkshW1Ke21vZhPiLyr'; | ||
| const TEST_TOKEN = 'tsol:usdt'; | ||
| const TEST_AMOUNT = '1000000000'; | ||
|
|
||
| interface BenchmarkResult { | ||
| recipientCount: number; | ||
| withAtaCreation: boolean; | ||
| instructionCount: number; | ||
| payloadSize: number; | ||
| isVersioned: boolean; | ||
| success: boolean; | ||
| error?: string; | ||
| errorStack?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Generates an array of unique test recipient addresses | ||
| */ | ||
| function generateRecipients(count: number): string[] { | ||
| const baseAddresses = [ | ||
| 'GYFtQoFCRvGS3WgJe32nRi8LarbSH81rp32SeTmd8mqT', | ||
| '9AKiRaA3NusuW9WcFz5NQ7K1vqf9FMLtb1hFXqc7imkx', | ||
| 'FiLHbfatU4keyGzZ1KjC57L3iYmVUi2azJ1ozSZsZWD6', | ||
| '6XKXKPfk5bHmHV4eL2ZFZ5ubcMu6YLed86niBfx7P4Pg', | ||
| 'CfhjtJ7W1HHmwXPo9HAkG25gDJPav64iN9Z3nx8eCWV8', | ||
| 'DNNSTu2fkj45u6oQuH1fBc3tyDjnnbvhAESrZGhH3uxa', | ||
| 'EHPBg7n93e1TWW7oXDwpivQTjZmpavzN9T6HEqAN4bz1', | ||
| 'EUZfVoaJgApESLe3Bx8d7xyqg1pucgeydBLQPiRhi6nE', | ||
| 'EqE7fJ89HZTAE8MxmJtxCqLhW1ozfhaqZigM5cYxRdHx', | ||
| 'CDFk1LXXbWqkd8q9cEsnc3nX44uKgrkDb48b9keBrt8H', | ||
| 'EFDfayERUomCbAKurccrAbEYmkyZ1ytazgkiBMBGM851', | ||
| 'EPPE9i37rnwWFzagoEHQtBM644p52q54UUGqemJPkAQu', | ||
| '3UKwF59ZmRPXq8ooQDjMmzYUiH861KwhJPzjcYfPXDTG', | ||
| 'Cr2yAAa7zEkSLJGebkscXQKNTpmgg5ECC4tCkZHHpNvG', | ||
| '8wQ2VzVEtYDzKaJ5XqGskkQAJZT5YJwHiUTPa7juCF4w', | ||
| '3URWW579b4ugLecHca69feGTM7LKPZ3jDrdTegosVE24', | ||
| '3pL2GxpS8QJoNXhXicnEQ2nvXxFbSy7CoJCRxiWtAuQs', | ||
| 'Cei1xdDuZmQxU6FNAcrrmM7aR7amF72osySgS8afpv5m', | ||
| '5Dd1qGYbbdudLi6uCdDodZw1CJ15j2zWnb7ZxQ68em5a', | ||
| '4fviuPUgg3uJp7J2eqLCmeuiHh6zUbn8iXm6JcECNH7T', | ||
| '4g9w2CsTR5h6urpuvm5R5w7UGPKUuCmrSSbj5BWAygq2', | ||
| '3PRwJ1PjrwF7rBCSsNjAH2s8Pr2XdbSm9koGcVLmrnaz', | ||
| '3p9ViY8efykqjixyZGVY2hPLVpHcfsUDKrrHqCUWyYPb', | ||
| '3oANXoBY6b5qgwRf1yES6kx1hf7rDGbEWkopcmXxau1u', | ||
| '3NqKx8uf3V1bTBtkniMzxVsuqW7SZmTnhJpRFuV6aAFz', | ||
| '3NVNrcyJjZmH7xytX2ayPZRucS1YBywwZ5DMzjGRtJvu', | ||
| '4iLLJqwHSdZPBbDPFpG8XiDazX8jMEam8fPQWwrG3qGP', | ||
| '3wbfKiB6uZuZ3KhjVcU9qc5M4yaWyx6mAUJA56tCD1vJ', | ||
| '4QheUEWRi8nesT94mRxiizZnAtRQTnk45jb4oaKMoo1f', | ||
| '4PowojicaKCxgupfPCZK5DT7QB1U3N7xh8Wq7N7VoCvw', | ||
| 'F3rTU6cFx9Aqa9jmuq5rSZN7p8bu1pmU9EbNBPeKHz7t', | ||
| '6XREPthmB7mTwLTHv54DZM14rgAuMa1NgXsmPeFgrqwu', | ||
| '5LTQgjYbbBsZj3V5rPmkcAkPcJcbCT8PQTskz2rWL3kW', | ||
| '7aG7LiNQ4hMNWwXTZRrpYVpPZDxY1Ea9YcbHuEjXXKVT', | ||
| 'DNyKPeaGfoPYzPmNG4osMZJdyJBZbeMUfNKAAt5RSw23', | ||
| 'DpFPcPk454MhoGv71DoaoHSJ5qWnKcVAo9euVoPWPprZ', | ||
| 'E4T6wq3aF4LivPfgr5sBKFUJLTZz7sND8duJDYK2EbXG', | ||
| 'EeM3aXzgt3Qf7tNV4RBe4vg83gtyGZqUQrJumzwtTUvN', | ||
| 'EqBF851o6HknqDy5Ji8PCtyxo48ACjBVPhC1NSpAKbEx', | ||
| 'EQVV74ZC332uKpZXrCVU7xzqTBBTdom3DERsEnRNqmAF', | ||
| 'EvnEyrSG5qSnxsCSqxdhbbKrcg2oiikKiTtYhwbAMnKT', | ||
| 'EBxE1QUeYuKsCCYuDsf6ngUV4ApmGUdc913b1oCLCwus', | ||
| '5GjFQdcCpB9Wj9CJFbMfKTfB7X9h6FSdqrxG6HaJAmUF', | ||
| 'EY1wKHGmTUK89aKjZ8qmycyx5gQjBDgeWPfKcwQf2Hvd', | ||
| 'EzLYbB2JcSPf9FwiWonD2XfEd3S89ghuMUH27UPvQzgJ', | ||
| '2n2xqWM9Z18LqxfJzkNrMMFWiDUFYA2k6WSgSnf6EnJs', | ||
| 'DesU7XscZjng8yj5VX6AZsk3hWSW4sQ3rTG2LuyQ2P4H', | ||
| 'Azz9EmNuhtjoYrhWvidWx1Hfd14SNBsYyzXhA9Tnoca8', | ||
| '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', | ||
| ]; | ||
|
|
||
| return baseAddresses.slice(0, Math.min(count, baseAddresses.length)); | ||
| } | ||
|
|
||
| /** | ||
| * Tests building a transaction with a specific number of recipients | ||
| */ | ||
| async function testTransactionSize(recipientCount: number, withAtaCreation: boolean): Promise<BenchmarkResult> { | ||
| const coinConfig = coins.get('tsol'); | ||
| const factory = new TransactionBuilderFactory(coinConfig); | ||
| const authAccount = new KeyPair({ prv: TEST_PRIVATE_KEY }); | ||
| const sender = authAccount.getKeys().pub; | ||
| const recipients = generateRecipients(recipientCount); | ||
|
|
||
| try { | ||
| const txBuilder = factory.getTokenTransferBuilder(); | ||
| txBuilder.nonce(TEST_BLOCKHASH); | ||
| txBuilder.sender(sender); | ||
|
|
||
| for (const recipientAddress of recipients) { | ||
| txBuilder.send({ | ||
| address: recipientAddress, | ||
| amount: TEST_AMOUNT, | ||
| tokenName: TEST_TOKEN, | ||
| }); | ||
|
|
||
| if (withAtaCreation) { | ||
| txBuilder.createAssociatedTokenAccount({ | ||
| ownerAddress: recipientAddress, | ||
| tokenName: TEST_TOKEN, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| txBuilder.memo('Benchmark test transaction'); | ||
| txBuilder.sign({ key: authAccount.getKeys().prv }); | ||
|
|
||
| const tx = await txBuilder.build(); | ||
| const payload = tx.signablePayload; | ||
| const instructionCount = tx.toJson().instructionsData?.length || 0; | ||
| const isVersioned = tx.isVersionedTransaction(); | ||
|
|
||
| return { | ||
| recipientCount, | ||
| withAtaCreation, | ||
| instructionCount, | ||
| payloadSize: payload.length, | ||
| isVersioned, | ||
| success: true, | ||
| }; | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : String(error); | ||
| const errorStack = error instanceof Error ? error.stack : undefined; | ||
|
|
||
| return { | ||
| recipientCount, | ||
| withAtaCreation, | ||
| instructionCount: 0, | ||
| payloadSize: 0, | ||
| isVersioned: false, | ||
| success: false, | ||
| error: errorMessage, | ||
| errorStack, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Performs binary search to find the maximum safe recipient count for LEGACY transactions | ||
| */ | ||
| async function findMaxRecipients(withAtaCreation: boolean): Promise<number> { | ||
| let left = 1; | ||
| let right = 50; | ||
| let maxSafe = 0; | ||
|
|
||
| while (left <= right) { | ||
| const mid = Math.floor((left + right) / 2); | ||
| const result = await testTransactionSize(mid, withAtaCreation); | ||
|
|
||
| // Only consider legacy transactions within the size limit as safe | ||
| if (result.success && !result.isVersioned && result.payloadSize <= SOLANA_LEGACY_TX_SIZE_LIMIT) { | ||
| maxSafe = mid; | ||
| left = mid + 1; | ||
| } else { | ||
| right = mid - 1; | ||
| } | ||
| } | ||
|
|
||
| return maxSafe; | ||
| } | ||
|
|
||
| /** | ||
| * Main benchmark execution | ||
| */ | ||
| async function runBenchmark(): Promise<void> { | ||
| console.log('Solana Transaction Size Benchmark'); | ||
| console.log('Limit: 1232 bytes for legacy transactions\n'); | ||
|
|
||
| // Test WITH ATA creation | ||
| console.log('[1/2] Testing WITH ATA Creation:'); | ||
| const withAtaResults: BenchmarkResult[] = []; | ||
| const testCountsWithAta = [1, 5, 10, 12, 13, 14, 15, 16, 17, 18, 20, 25, 30]; | ||
|
|
||
| for (const count of testCountsWithAta) { | ||
| const result = await testTransactionSize(count, true); | ||
| withAtaResults.push(result); | ||
|
|
||
| if (result.success && result.payloadSize <= SOLANA_LEGACY_TX_SIZE_LIMIT) { | ||
| const txType = result.isVersioned ? 'versioned' : 'legacy'; | ||
| console.log( | ||
| ` ${count} recipients: ${result.payloadSize} bytes, ${result.instructionCount} instructions (${txType})` | ||
| ); | ||
| } else { | ||
| console.log(` ${count} recipients: FAILED`); | ||
| if (result.success && result.payloadSize > SOLANA_LEGACY_TX_SIZE_LIMIT) { | ||
| console.log( | ||
| ` Error: Transaction size ${result.payloadSize} bytes exceeds ${SOLANA_LEGACY_TX_SIZE_LIMIT} byte limit` | ||
| ); | ||
| } else { | ||
| console.log(` Error: ${result.error}`); | ||
| if (result.errorStack) { | ||
| const stackLines = result.errorStack.split('\n').slice(0, 4); | ||
| console.log(` Stack: ${stackLines.join('\n ')}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const maxWithAta = await findMaxRecipients(true); | ||
| const conservativeWithAta = Math.floor(maxWithAta * 0.9); | ||
| console.log(`\n Maximum: ${maxWithAta} recipients`); | ||
| console.log(` Conservative (10% buffer): ${conservativeWithAta} recipients\n`); | ||
|
|
||
| // Test WITHOUT ATA creation | ||
| console.log('[2/2] Testing WITHOUT ATA Creation:'); | ||
| const withoutAtaResults: BenchmarkResult[] = []; | ||
| const testCountsWithoutAta = [1, 10, 20, 30, 35, 38, 39, 40, 41, 42, 45, 50]; | ||
|
|
||
| for (const count of testCountsWithoutAta) { | ||
| const result = await testTransactionSize(count, false); | ||
| withoutAtaResults.push(result); | ||
|
|
||
| if (result.success && result.payloadSize <= SOLANA_LEGACY_TX_SIZE_LIMIT) { | ||
| const txType = result.isVersioned ? 'versioned' : 'legacy'; | ||
| console.log( | ||
| ` ${count} recipients: ${result.payloadSize} bytes, ${result.instructionCount} instructions (${txType})` | ||
| ); | ||
| } else { | ||
| console.log(` ${count} recipients: FAILED`); | ||
| if (result.success && result.payloadSize > SOLANA_LEGACY_TX_SIZE_LIMIT) { | ||
| console.log( | ||
| ` Error: Transaction size ${result.payloadSize} bytes exceeds ${SOLANA_LEGACY_TX_SIZE_LIMIT} byte limit` | ||
| ); | ||
| } else { | ||
| console.log(` Error: ${result.error}`); | ||
| if (result.errorStack) { | ||
| const stackLines = result.errorStack.split('\n').slice(0, 4); | ||
| console.log(` Stack: ${stackLines.join('\n ')}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const maxWithoutAta = await findMaxRecipients(false); | ||
| const conservativeWithoutAta = Math.floor(maxWithoutAta * 0.9); | ||
| console.log(`\n Maximum: ${maxWithoutAta} recipients`); | ||
| console.log(` Conservative (10% buffer): ${conservativeWithoutAta} recipients\n`); | ||
|
|
||
| // Summary | ||
| console.log('RECOMMENDED LIMITS (for legacy transactions):'); | ||
| console.log(` With ATA creation: ${conservativeWithAta} recipients`); | ||
| console.log(` Without ATA creation: ${conservativeWithoutAta} recipients`); | ||
| console.log('\nNote: Transactions exceeding limits may build as versioned transactions automatically.'); | ||
|
|
||
| // Export results | ||
| const exportData = { | ||
| timestamp: new Date().toISOString(), | ||
| solanaLegacyTxSizeLimit: SOLANA_LEGACY_TX_SIZE_LIMIT, | ||
| withAtaCreation: { | ||
| maxSafe: maxWithAta, | ||
| conservative: conservativeWithAta, | ||
| testResults: withAtaResults, | ||
| }, | ||
| withoutAtaCreation: { | ||
| maxSafe: maxWithoutAta, | ||
| conservative: conservativeWithoutAta, | ||
| testResults: withoutAtaResults, | ||
| }, | ||
| }; | ||
|
|
||
| const fs = await import('fs'); | ||
| fs.writeFileSync('transaction-size-benchmark-results.json', JSON.stringify(exportData, null, 2)); | ||
| console.log('\nResults saved to: transaction-size-benchmark-results.json'); | ||
| } | ||
|
|
||
| // Execute benchmark | ||
| runBenchmark() | ||
| .then(() => { | ||
| console.log('Benchmark completed successfully'); | ||
| process.exit(0); | ||
| }) | ||
| .catch((error) => { | ||
| console.error('Benchmark failed:', error); | ||
| process.exit(1); | ||
| }); | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the unit of this limit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bytes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ref: https://solana.com/docs/core/transactions#transaction-size