From efd039270698ef0ada7b491d84f52ff4cd0e25dd Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Wed, 10 Apr 2019 12:31:50 -0700 Subject: [PATCH] BREAKING CHANGE: requires Solana version v0.13.0 or greater feat: Update to current solana tx format --- web3.js/module.flow.js | 6 +- web3.js/package-lock.json | 8 +- web3.js/src/budget-program.js | 32 ++++- web3.js/src/loader.js | 4 +- web3.js/src/system-program.js | 9 +- web3.js/src/token-program.js | 35 +++-- web3.js/src/transaction.js | 57 ++++---- web3.js/test/bpf-loader.test.js | 2 +- web3.js/test/connection.test.js | 4 - web3.js/test/native-loader.test.js | 2 +- web3.js/test/token-program.test.js | 1 - web3.js/test/transaction.test.js | 203 ++++++++++++++--------------- 12 files changed, 196 insertions(+), 167 deletions(-) diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 0629b35c85..311bbdb42d 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -110,13 +110,13 @@ declare module '@solana/web3.js' { declare export type TransactionSignature = string; declare type TransactionInstructionCtorFields = {| - keys: ?Array, + keys: ?Array<{pubkey: PublicKey, isSigner: boolean}>, programId?: PublicKey, data?: Buffer, |}; declare export class TransactionInstruction { - keys: Array; + keys: Array<{pubkey: PublicKey, isSigner: boolean}>; programId: PublicKey; data: Buffer; } @@ -127,7 +127,6 @@ declare module '@solana/web3.js' { |}; declare type TransactionCtorFields = {| - fee?: number, recentBlockhash?: Blockhash, signatures?: Array, |}; @@ -137,7 +136,6 @@ declare module '@solana/web3.js' { signature: ?Buffer; instructions: Array; recentBlockhash: ?Blockhash; - fee: number; constructor(opts?: TransactionCtorFields): Transaction; add( diff --git a/web3.js/package-lock.json b/web3.js/package-lock.json index 70c71d268e..03809c8c0c 100644 --- a/web3.js/package-lock.json +++ b/web3.js/package-lock.json @@ -5805,7 +5805,7 @@ "dependencies": { "marked": { "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, @@ -6069,7 +6069,7 @@ }, "marked": { "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, @@ -12269,7 +12269,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -16742,7 +16742,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/web3.js/src/budget-program.js b/web3.js/src/budget-program.js index 99961bfec9..1febf84ddf 100644 --- a/web3.js/src/budget-program.js +++ b/web3.js/src/budget-program.js @@ -203,7 +203,7 @@ export class BudgetProgram { } return new Transaction().add({ - keys: [from, to], + keys: [{pubkey: from, isSigner: true}, {pubkey: to, isSigner: false}], programId: this.programId, data: data.slice(0, pos), }); @@ -223,7 +223,11 @@ export class BudgetProgram { } return new Transaction().add({ - keys: [from, program, to], + keys: [ + {pubkey: from, isSigner: true}, + {pubkey: program, isSigner: false}, + {pubkey: to, isSigner: false}, + ], programId: this.programId, data: data.slice(0, pos), }); @@ -243,7 +247,11 @@ export class BudgetProgram { } return new Transaction().add({ - keys: [from, program, to], + keys: [ + {pubkey: from, isSigner: true}, + {pubkey: program, isSigner: false}, + {pubkey: to, isSigner: false}, + ], programId: this.programId, data: data.slice(0, pos), }); @@ -287,7 +295,11 @@ export class BudgetProgram { pos += paymentData.length; return new Transaction().add({ - keys: [from, program, to], + keys: [ + {pubkey: from, isSigner: true}, + {pubkey: program, isSigner: false}, + {pubkey: to, isSigner: false}, + ], programId: this.programId, data: data.slice(0, pos), }); @@ -310,7 +322,11 @@ export class BudgetProgram { whenData.copy(data, 4); return new Transaction().add({ - keys: [from, program, to], + keys: [ + {pubkey: from, isSigner: true}, + {pubkey: program, isSigner: false}, + {pubkey: to, isSigner: false}, + ], programId: this.programId, data, }); @@ -336,7 +352,11 @@ export class BudgetProgram { ); return new Transaction().add({ - keys: [from, program, to], + keys: [ + {pubkey: from, isSigner: true}, + {pubkey: program, isSigner: false}, + {pubkey: to, isSigner: false}, + ], programId: this.programId, data, }); diff --git a/web3.js/src/loader.js b/web3.js/src/loader.js index d53d968fae..eeda155fa8 100644 --- a/web3.js/src/loader.js +++ b/web3.js/src/loader.js @@ -75,7 +75,7 @@ export class Loader { ); const transaction = new Transaction().add({ - keys: [program.publicKey], + keys: [{pubkey: program.publicKey, isSigner: true}], programId: this.programId, data, }); @@ -119,7 +119,7 @@ export class Loader { ); const transaction = new Transaction().add({ - keys: [program.publicKey], + keys: [{pubkey: program.publicKey, isSigner: true}], programId: this.programId, data, }); diff --git a/web3.js/src/system-program.js b/web3.js/src/system-program.js index 478352c852..2f082fec03 100644 --- a/web3.js/src/system-program.js +++ b/web3.js/src/system-program.js @@ -48,7 +48,10 @@ export class SystemProgram { ); return new Transaction().add({ - keys: [from, newAccount], + keys: [ + {pubkey: from, isSigner: true}, + {pubkey: newAccount, isSigner: false}, + ], programId: SystemProgram.programId, data, }); @@ -73,7 +76,7 @@ export class SystemProgram { ); return new Transaction().add({ - keys: [from, to], + keys: [{pubkey: from, isSigner: true}, {pubkey: to, isSigner: false}], programId: SystemProgram.programId, data, }); @@ -98,7 +101,7 @@ export class SystemProgram { ); return new Transaction().add({ - keys: [from], + keys: [{pubkey: from, isSigner: true}], programId: SystemProgram.programId, data, }); diff --git a/web3.js/src/token-program.js b/web3.js/src/token-program.js index 14954029f0..434cbaf79a 100644 --- a/web3.js/src/token-program.js +++ b/web3.js/src/token-program.js @@ -236,11 +236,13 @@ export class Token { await sendAndConfirmTransaction(connection, transaction, owner); transaction = new Transaction().add({ - keys: [tokenAccount.publicKey, initialAccountPublicKey], + keys: [ + {pubkey: tokenAccount.publicKey, isSigner: true}, + {pubkey: initialAccountPublicKey, isSigner: false}, + ], programId, data, }); - transaction.fee = 0; // TODO: Batch with the `SystemProgram.createAccount` and remove this line await sendAndConfirmTransaction(connection, transaction, tokenAccount); return [token, initialAccountPublicKey]; @@ -284,16 +286,19 @@ export class Token { await sendAndConfirmTransaction(this.connection, transaction, owner); // Initialize the token account - const keys = [tokenAccount.publicKey, owner.publicKey, this.token]; + const keys = [ + {pubkey: tokenAccount.publicKey, isSigner: true}, + {pubkey: owner.publicKey, isSigner: false}, + {pubkey: this.token, isSigner: false}, + ]; if (source) { - keys.push(source); + keys.push({pubkey: source, isSigner: false}); } transaction = new Transaction().add({ keys, programId: this.programId, data, }); - transaction.fee = 0; // TODO: Batch with the `SystemProgram.createAccount` and remove this line await sendAndConfirmTransaction(this.connection, transaction, tokenAccount); return tokenAccount.publicKey; @@ -480,9 +485,13 @@ export class Token { data, ); - const keys = [owner, source, destination]; + const keys = [ + {pubkey: owner, isSigner: true}, + {pubkey: source, isSigner: false}, + {pubkey: destination, isSigner: false}, + ]; if (accountInfo.source) { - keys.push(accountInfo.source); + keys.push({pubkey: accountInfo.source, isSigner: false}); } return new TransactionInstruction({ keys, @@ -520,7 +529,11 @@ export class Token { ); return new TransactionInstruction({ - keys: [owner, account, delegate], + keys: [ + {pubkey: owner, isSigner: true}, + {pubkey: account, isSigner: false}, + {pubkey: delegate, isSigner: false}, + ], programId: this.programId, data, }); @@ -564,7 +577,11 @@ export class Token { ); return new TransactionInstruction({ - keys: [owner, account, newOwner], + keys: [ + {pubkey: owner, isSigner: true}, + {pubkey: account, isSigner: false}, + {pubkey: newOwner, isSigner: false}, + ], programId: this.programId, data, }); diff --git a/web3.js/src/transaction.js b/web3.js/src/transaction.js index 33058b7452..53e0265cfa 100644 --- a/web3.js/src/transaction.js +++ b/web3.js/src/transaction.js @@ -30,7 +30,7 @@ export const PACKET_DATA_SIZE = 512; * @property {?Buffer} data */ type TransactionInstructionCtorFields = {| - keys?: Array, + keys?: Array<{pubkey: PublicKey, isSigner: boolean}>, programId?: PublicKey, data?: Buffer, |}; @@ -41,8 +41,9 @@ type TransactionInstructionCtorFields = {| export class TransactionInstruction { /** * Public keys to include in this transaction + * Boolean represents whether this pubkey needs to sign the transaction */ - keys: Array = []; + keys: Array<{pubkey: PublicKey, isSigner: boolean}> = []; /** * Program Id to execute @@ -71,13 +72,11 @@ type SignaturePubkeyPair = {| * List of Transaction object fields that may be initialized at construction * * @typedef {Object} TransactionCtorFields - * @property {?number} fee * @property (?recentBlockhash} A recent block hash * @property (?signatures} One or more signatures * */ type TransactionCtorFields = {| - fee?: number, recentBlockhash?: Blockhash, signatures?: Array, |}; @@ -112,11 +111,6 @@ export class Transaction { */ recentBlockhash: ?Blockhash; - /** - * Fee for this transaction - */ - fee: number = 1; - /** * Construct an empty Transaction */ @@ -158,6 +152,7 @@ export class Transaction { } const keys = this.signatures.map(({publicKey}) => publicKey.toString()); + let numRequiredSignatures = 0; const programIds = []; this.instructions.forEach(instruction => { @@ -166,13 +161,15 @@ export class Transaction { programIds.push(programId); } - instruction.keys - .map(key => key.toString()) - .forEach(key => { - if (!keys.includes(key)) { - keys.push(key); - } - }); + instruction.keys.forEach(keySignerPair => { + const keyStr = keySignerPair.pubkey.toString(); + if (keySignerPair.isSigner) { + numRequiredSignatures += 1; + } + if (!keys.includes(keyStr)) { + keys.push(keyStr); + } + }); }); let keyCount = []; @@ -191,7 +188,9 @@ export class Transaction { programIdIndex: programIds.indexOf(programId.toString()), keyIndicesCount: Buffer.from(keyIndicesCount), keyIndices: Buffer.from( - instruction.keys.map(key => keys.indexOf(key.toString())), + instruction.keys.map(keyObj => + keys.indexOf(keyObj.pubkey.toString()), + ), ), dataLength: Buffer.from(dataCount), data, @@ -239,10 +238,10 @@ export class Transaction { instructionBuffer = instructionBuffer.slice(0, instructionBufferLength); const signDataLayout = BufferLayout.struct([ + BufferLayout.blob(1, 'numRequiredSignatures'), BufferLayout.blob(keyCount.length, 'keyCount'), BufferLayout.seq(Layout.publicKey('key'), keys.length, 'keys'), Layout.publicKey('recentBlockhash'), - BufferLayout.ns64('fee'), BufferLayout.blob(programIdCount.length, 'programIdCount'), BufferLayout.seq( @@ -253,10 +252,10 @@ export class Transaction { ]); const transaction = { + numRequiredSignatures: Buffer.from([numRequiredSignatures]), keyCount: Buffer.from(keyCount), keys: keys.map(key => new PublicKey(key).toBuffer()), recentBlockhash: Buffer.from(bs58.decode(recentBlockhash)), - fee: this.fee, programIdCount: Buffer.from(programIdCount), programIds: programIds.map(programId => new PublicKey(programId).toBuffer(), @@ -389,7 +388,7 @@ export class Transaction { */ get keys(): Array { invariant(this.instructions.length === 1); - return this.instructions[0].keys; + return this.instructions[0].keys.map(keyObj => keyObj.pubkey); } /** @@ -430,6 +429,8 @@ export class Transaction { signatures.push(signature); } + byteArray = byteArray.slice(1); // Skip numRequiredSignatures byte + const accountCount = shortvec.decodeLength(byteArray); let accounts = []; for (let i = 0; i < accountCount; i++) { @@ -441,11 +442,6 @@ export class Transaction { const recentBlockhash = byteArray.slice(0, PUBKEY_LENGTH); byteArray = byteArray.slice(PUBKEY_LENGTH); - let fee = 0; - for (let i = 0; i < 8; i++) { - fee += byteArray.shift() >> (8 * i); - } - const programIdCount = shortvec.decodeLength(byteArray); let programs = []; for (let i = 0; i < programIdCount; i++) { @@ -470,7 +466,6 @@ export class Transaction { // Populate Transaction object transaction.recentBlockhash = new PublicKey(recentBlockhash).toBase58(); - transaction.fee = fee; for (let i = 0; i < signatureCount; i++) { const sigPubkeyPair = { signature: Buffer.from(signatures[i]), @@ -485,9 +480,13 @@ export class Transaction { data: Buffer.from(instructions[i].data), }; for (let j = 0; j < instructions[i].accountIndex.length; j++) { - instructionData.keys.push( - new PublicKey(accounts[instructions[i].accountIndex[j]]), - ); + const pubkey = new PublicKey(accounts[instructions[i].accountIndex[j]]); + instructionData.keys.push({ + pubkey, + isSigner: transaction.signatures.some( + keyObj => keyObj.publicKey.toString() === pubkey.toString(), + ), + }); } let instruction = new TransactionInstruction(instructionData); transaction.instructions.push(instruction); diff --git a/web3.js/test/bpf-loader.test.js b/web3.js/test/bpf-loader.test.js index ac7fea82de..19ea3d86a5 100644 --- a/web3.js/test/bpf-loader.test.js +++ b/web3.js/test/bpf-loader.test.js @@ -28,7 +28,7 @@ test('load BPF program', async () => { const data = await fs.readFile('test/fixtures/noop/noop.so'); const programId = await BpfLoader.load(connection, from, data); const transaction = new Transaction().add({ - keys: [from.publicKey], + keys: [{pubkey: from.publicKey, isSigner: true}], programId, }); await sendAndConfirmTransaction(connection, transaction, from); diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index 25dc5d9cfc..80f2ca2c8d 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -301,7 +301,6 @@ test('transaction', async () => { accountTo.publicKey, 10, ); - transaction.fee = 0; const signature = await connection.sendTransaction(transaction, accountFrom); mockRpc.push([ @@ -396,7 +395,6 @@ test('multi-instruction transaction', async () => { accountTo.publicKey, 10, ).add(SystemProgram.move(accountTo.publicKey, accountFrom.publicKey, 10)); - transaction.fee = 0; const signature = await connection.sendTransaction( transaction, accountFrom, @@ -445,7 +443,6 @@ test('account change notification', async () => { 3, BpfLoader.programId, ); - transaction.fee = 0; await sendAndConfirmTransaction(connection, transaction, owner); const loader = new Loader(connection, BpfLoader.programId); @@ -500,7 +497,6 @@ test('program account change notification', async () => { 3, BpfLoader.programId, ); - transaction.fee = 0; await sendAndConfirmTransaction(connection, transaction, owner); const loader = new Loader(connection, BpfLoader.programId); diff --git a/web3.js/test/native-loader.test.js b/web3.js/test/native-loader.test.js index 208cbee072..643e0a4e86 100644 --- a/web3.js/test/native-loader.test.js +++ b/web3.js/test/native-loader.test.js @@ -25,7 +25,7 @@ test('load native program', async () => { const from = await newAccountWithLamports(connection, 1024); const programId = await NativeLoader.load(connection, from, 'noop'); const transaction = new Transaction().add({ - keys: [from.publicKey], + keys: [{pubkey: from.publicKey, isSigner: true}], programId, }); diff --git a/web3.js/test/token-program.test.js b/web3.js/test/token-program.test.js index 79eb008c8d..5becc905af 100644 --- a/web3.js/test/token-program.test.js +++ b/web3.js/test/token-program.test.js @@ -607,4 +607,3 @@ test('set owner', async () => { await testToken.setOwner(newOwner, account, owner.publicKey); }); - diff --git a/web3.js/test/transaction.test.js b/web3.js/test/transaction.test.js index b025a7d5b8..0d0497b503 100644 --- a/web3.js/test/transaction.test.js +++ b/web3.js/test/transaction.test.js @@ -1,4 +1,6 @@ // @flow +import nacl from 'tweetnacl'; + import {Account} from '../src/account'; import {PublicKey} from '../src/publickey'; import {Transaction} from '../src/transaction'; @@ -33,7 +35,6 @@ test('transfer signatures', () => { const newTransaction = new Transaction({ recentBlockhash: orgTransaction.recentBlockhash, - fee: orgTransaction.fee, signatures: orgTransaction.signatures, }).add(move1, move2); @@ -41,8 +42,11 @@ test('transfer signatures', () => { }); test('parse wire format and serialize', () => { + const keypair = nacl.sign.keyPair.fromSeed( + Uint8Array.from(Array(32).fill(8)), + ); + const sender = new Account(Buffer.from(keypair.secretKey)); // Arbitrary known account const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash - const sender = new Account(Buffer.alloc(64, 8)); // Arbitrary known account const recipient = new PublicKey( 'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99', ); // Arbitrary known public key @@ -52,103 +56,104 @@ test('parse wire format and serialize', () => { const wireTransaction = Buffer.from([ 1, - 50, - 238, - 193, - 5, - 227, - 31, - 95, - 69, - 85, - 3, 132, - 143, - 216, - 77, - 235, - 129, - 3, - 109, - 89, - 222, - 127, - 137, - 228, - 15, - 113, - 207, - 169, - 93, - 167, - 249, - 71, + 50, + 204, + 17, + 25, + 230, 33, - 185, - 182, - 83, - 116, - 203, - 102, - 64, - 245, - 68, - 34, - 100, + 52, + 8, + 149, + 124, + 56, + 114, + 17, + 236, + 92, + 93, + 53, + 234, + 122, + 120, + 219, 193, - 156, - 109, - 35, - 104, - 119, - 101, - 197, - 43, - 141, - 174, - 228, - 154, - 146, - 78, - 216, - 202, - 18, - 177, - 179, - 5, + 255, 2, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, - 8, + 14, + 87, + 12, + 207, + 99, + 241, + 32, + 151, + 102, + 70, + 60, + 218, + 73, + 232, + 68, + 33, + 94, + 134, + 117, + 138, + 182, + 179, + 118, + 249, + 132, + 52, + 41, + 162, + 44, + 0, + 43, + 193, + 242, + 120, + 108, + 4, + 163, + 191, + 6, + 1, + 2, + 19, + 152, + 246, + 44, + 109, + 26, + 69, + 124, + 81, + 186, + 106, + 75, + 95, + 61, + 189, + 47, + 105, + 252, + 169, + 50, + 22, + 33, + 141, + 200, + 153, + 126, + 65, + 107, + 209, + 125, + 147, + 202, 253, 67, 159, @@ -221,14 +226,6 @@ test('parse wire format and serialize', () => { 0, 0, 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, 0, 0, 0,