diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index cbbe8201bb..9128b6ce36 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -249,9 +249,13 @@ declare module '@solana/web3.js' { // === src/loader.js === declare export class Loader { - constructor(connection: Connection, programId: PublicKey): Loader; - load(program: Account, offset: number, bytes: Array): Promise; - finalize(program: Account): Promise; + static load( + connection: Connection, + payer: Account, + program: Account, + programId: PublicKey, + data: Array, + ): Promise; } // === src/bpf-loader.js === @@ -259,7 +263,7 @@ declare module '@solana/web3.js' { static programId: PublicKey; static load( connection: Connection, - owner: Account, + payer: Account, elfBytes: Array, ): Promise; } @@ -269,7 +273,7 @@ declare module '@solana/web3.js' { static programId: PublicKey; static load( connection: Connection, - owner: Account, + payer: Account, programName: string, ): Promise; } diff --git a/web3.js/src/bpf-loader.js b/web3.js/src/bpf-loader.js index fbfbad12ed..4c79be81d4 100644 --- a/web3.js/src/bpf-loader.js +++ b/web3.js/src/bpf-loader.js @@ -3,8 +3,6 @@ import {Account} from './account'; import {PublicKey} from './publickey'; import {Loader} from './loader'; -import {SystemProgram} from './system-program'; -import {sendAndConfirmTransaction} from './util/send-and-confirm-transaction'; import type {Connection} from './connection'; /** @@ -15,9 +13,7 @@ export class BpfLoader { * Public key that identifies the BpfLoader */ static get programId(): PublicKey { - return new PublicKey( - 'BPFLoader1111111111111111111111111111111111', - ); + return new PublicKey('BPFLoader1111111111111111111111111111111111'); } /** @@ -27,26 +23,12 @@ export class BpfLoader { * @param owner User account to load the program into * @param elfBytes The entire ELF containing the BPF program */ - static async load( + static load( connection: Connection, - owner: Account, + payer: Account, elf: Array, ): Promise { - const programAccount = new Account(); - - const transaction = SystemProgram.createAccount( - owner.publicKey, - programAccount.publicKey, - 1 + Math.ceil(elf.length / Loader.chunkSize) + 1, - elf.length, - BpfLoader.programId, - ); - await sendAndConfirmTransaction(connection, transaction, owner); - - const loader = new Loader(connection, BpfLoader.programId); - await loader.load(programAccount, elf); - await loader.finalize(programAccount); - - return programAccount.publicKey; + const program = new Account(); + return Loader.load(connection, payer, program, BpfLoader.programId, elf); } } diff --git a/web3.js/src/budget-program.js b/web3.js/src/budget-program.js index 401d180aba..e869f71c16 100644 --- a/web3.js/src/budget-program.js +++ b/web3.js/src/budget-program.js @@ -143,9 +143,7 @@ export class BudgetProgram { * Public key that identifies the Budget program */ static get programId(): PublicKey { - return new PublicKey( - 'Budget1111111111111111111111111111111111111', - ); + return new PublicKey('Budget1111111111111111111111111111111111111'); } /** diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index 62544a1ea3..1381f8a7e6 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -626,7 +626,6 @@ export class Connection { subscriptionId, programId, } = this._programAccountChangeSubscriptions[id]; - console.log('program-id: ' + programId); if (subscriptionId === null) { try { this._programAccountChangeSubscriptions[ diff --git a/web3.js/src/loader.js b/web3.js/src/loader.js index eeda155fa8..c482d26ef3 100644 --- a/web3.js/src/loader.js +++ b/web3.js/src/loader.js @@ -9,43 +9,46 @@ import {Transaction} from './transaction'; import {sendAndConfirmTransaction} from './util/send-and-confirm-transaction'; import {sleep} from './util/sleep'; import type {Connection} from './connection'; +import {SystemProgram} from './system-program'; /** * Program loader interface */ export class Loader { - /** - * @private - */ - connection: Connection; - - /** - * @private - */ - programId: PublicKey; - /** * Amount of program data placed in each load Transaction */ static get chunkSize(): number { - return 256; + return 229; // Keep program chunks under PACKET_DATA_SIZE } /** - * @param connection The connection to use - * @param programId Public key that identifies the loader - */ - constructor(connection: Connection, programId: PublicKey) { - Object.assign(this, {connection, programId}); - } - - /** - * Load program data + * Loads a generic program * - * @param program Account to load the program info - * @param data Program data + * @param connection The connection to use + * @param payer System account that pays to load the program + * @param program Account to load the program into + * @param programId Public key that identifies the loader + * @param data Program octets */ - async load(program: Account, data: Array) { + static async load( + connection: Connection, + payer: Account, + program: Account, + programId: PublicKey, + data: Array, + ): Promise { + { + const transaction = SystemProgram.createAccount( + payer.publicKey, + program.publicKey, + 1, + data.length, + programId, + ); + await sendAndConfirmTransaction(connection, transaction, payer); + } + const dataLayout = BufferLayout.struct([ BufferLayout.u32('instruction'), BufferLayout.u32('offset'), @@ -76,11 +79,11 @@ export class Loader { const transaction = new Transaction().add({ keys: [{pubkey: program.publicKey, isSigner: true}], - programId: this.programId, + programId, data, }); transactions.push( - sendAndConfirmTransaction(this.connection, transaction, program), + sendAndConfirmTransaction(connection, transaction, payer, program), ); // Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors @@ -100,29 +103,26 @@ export class Loader { array = array.slice(chunkSize); } await Promise.all(transactions); - } - /** - * Finalize an account loaded with program data for execution - * - * @param program `load()`ed Account - */ - async finalize(program: Account) { - const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction')]); + // Finalize the account loaded with program data for execution + { + const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction')]); - const data = Buffer.alloc(dataLayout.span); - dataLayout.encode( - { - instruction: 1, // Finalize instruction - }, - data, - ); + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: 1, // Finalize instruction + }, + data, + ); - const transaction = new Transaction().add({ - keys: [{pubkey: program.publicKey, isSigner: true}], - programId: this.programId, - data, - }); - await sendAndConfirmTransaction(this.connection, transaction, program); + const transaction = new Transaction().add({ + keys: [{pubkey: program.publicKey, isSigner: true}], + programId, + data, + }); + await sendAndConfirmTransaction(connection, transaction, payer, program); + } + return program.publicKey; } } diff --git a/web3.js/src/native-loader.js b/web3.js/src/native-loader.js index 0c37e5bb94..794de73e2c 100644 --- a/web3.js/src/native-loader.js +++ b/web3.js/src/native-loader.js @@ -3,8 +3,6 @@ import {Account} from './account'; import {PublicKey} from './publickey'; import {Loader} from './loader'; -import {SystemProgram} from './system-program'; -import {sendAndConfirmTransaction} from './util/send-and-confirm-transaction'; import type {Connection} from './connection'; /** @@ -15,41 +13,29 @@ export class NativeLoader { * Public key that identifies the NativeLoader */ static get programId(): PublicKey { - return new PublicKey( - 'NativeLoader1111111111111111111111111111111', - ); + return new PublicKey('NativeLoader1111111111111111111111111111111'); } /** * Loads a native program * * @param connection The connection to use - * @param owner User account to load the program with + * @param payer System account that pays to load the program * @param programName Name of the native program */ - static async load( + static load( connection: Connection, - owner: Account, + payer: Account, programName: string, ): Promise { const bytes = [...Buffer.from(programName)]; - - const programAccount = new Account(); - - // Allocate memory for the program account - const transaction = SystemProgram.createAccount( - owner.publicKey, - programAccount.publicKey, - 1 + 1 + 1, - bytes.length + 1, + const program = new Account(); + return Loader.load( + connection, + payer, + program, NativeLoader.programId, + bytes, ); - await sendAndConfirmTransaction(connection, transaction, owner); - - const loader = new Loader(connection, NativeLoader.programId); - await loader.load(programAccount, bytes); - await loader.finalize(programAccount); - - return programAccount.publicKey; } } diff --git a/web3.js/src/token-program.js b/web3.js/src/token-program.js index 4c4c2da9dc..d0f7a7fddb 100644 --- a/web3.js/src/token-program.js +++ b/web3.js/src/token-program.js @@ -243,7 +243,12 @@ export class Token { programId, data, }); - await sendAndConfirmTransaction(connection, transaction, tokenAccount); + await sendAndConfirmTransaction( + connection, + transaction, + owner, + tokenAccount, + ); return [token, initialAccountPublicKey]; } @@ -299,7 +304,12 @@ export class Token { programId: this.programId, data, }); - await sendAndConfirmTransaction(this.connection, transaction, tokenAccount); + await sendAndConfirmTransaction( + this.connection, + transaction, + owner, + tokenAccount, + ); return tokenAccount.publicKey; } diff --git a/web3.js/src/transaction.js b/web3.js/src/transaction.js index 53e0265cfa..894945d6cf 100644 --- a/web3.js/src/transaction.js +++ b/web3.js/src/transaction.js @@ -92,7 +92,7 @@ export class Transaction { signatures: Array = []; /** - * The first (primary) Transaction signature + * The first (payer) Transaction signature */ get signature(): Buffer | null { if (this.signatures.length > 0) { @@ -172,6 +172,14 @@ export class Transaction { }); }); + if (numRequiredSignatures > this.signatures.length) { + throw new Error( + `Insufficent signatures: expected ${numRequiredSignatures} but got ${ + this.signatures.length + }`, + ); + } + let keyCount = []; shortvec.encodeLength(keyCount, keys.length); @@ -252,7 +260,7 @@ export class Transaction { ]); const transaction = { - numRequiredSignatures: Buffer.from([numRequiredSignatures]), + numRequiredSignatures: Buffer.from([this.signatures.length]), keyCount: Buffer.from(keyCount), keys: keys.map(key => new PublicKey(key).toBuffer()), recentBlockhash: Buffer.from(bs58.decode(recentBlockhash)), diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index bd4a5dd35f..6f01a293ee 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -1,12 +1,5 @@ // @flow -import { - Account, - Connection, - BpfLoader, - Loader, - SystemProgram, - sendAndConfirmTransaction, -} from '../src'; +import {Account, Connection, BpfLoader, Loader, SystemProgram} from '../src'; import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing'; import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch'; import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash'; @@ -436,7 +429,11 @@ test('transaction', async () => { result: 2, }, ]); - expect(await connection.getBalance(accountFrom.publicKey)).toBe(2); + + // accountFrom may have less than 2 due to transaction fees + const balance = await connection.getBalance(accountFrom.publicKey); + expect(balance).toBeGreaterThan(0); + expect(balance).toBeLessThanOrEqual(2); mockRpc.push([ url, @@ -494,7 +491,12 @@ test('multi-instruction transaction', async () => { Ok: null, }); - expect(await connection.getBalance(accountFrom.publicKey)).toBe(12); + // accountFrom may have less than 12 due to transaction fees + expect(await connection.getBalance(accountFrom.publicKey)).toBeGreaterThan(0); + expect( + await connection.getBalance(accountFrom.publicKey), + ).toBeLessThanOrEqual(12); + expect(await connection.getBalance(accountTo.publicKey)).toBe(21); }); @@ -516,28 +518,21 @@ test('account change notification', async () => { ); await connection.requestAirdrop(owner.publicKey, 42); - const transaction = SystemProgram.createAccount( - owner.publicKey, - programAccount.publicKey, - 42, + await Loader.load(connection, owner, programAccount, BpfLoader.programId, [ + 1, + 2, 3, - BpfLoader.programId, - ); - await sendAndConfirmTransaction(connection, transaction, owner); - - const loader = new Loader(connection, BpfLoader.programId); - await loader.load(programAccount, [1, 2, 3]); + ]); // Wait for mockCallback to receive a call let i = 0; for (;;) { - if (mockCallback.mock.calls.length === 1) { + if (mockCallback.mock.calls.length > 0) { break; } - if (++i === 5) { - console.log(JSON.stringify(mockCallback.mock.calls)); - throw new Error('mockCallback should be called twice'); + if (++i === 30) { + throw new Error('Account change notification not observed'); } // Sleep for a 1/4 of a slot, notifications only occur after a block is // processed @@ -546,10 +541,8 @@ test('account change notification', async () => { await connection.removeAccountChangeListener(subscriptionId); - expect(mockCallback.mock.calls[0][0].lamports).toBe(42); + expect(mockCallback.mock.calls[0][0].lamports).toBe(1); expect(mockCallback.mock.calls[0][0].owner).toEqual(BpfLoader.programId); - expect(mockCallback.mock.calls[0][0].executable).toBe(false); - expect(mockCallback.mock.calls[0][0].data).toEqual(Buffer.from([1, 2, 3])); }); test('program account change notification', async () => { @@ -562,36 +555,35 @@ test('program account change notification', async () => { const owner = new Account(); const programAccount = new Account(); - const mockCallback = jest.fn(); + // const mockCallback = jest.fn(); + let notified = false; const subscriptionId = connection.onProgramAccountChange( BpfLoader.programId, - mockCallback, + keyedAccountInfo => { + if (keyedAccountInfo.accountId !== programAccount.publicKey.toString()) { + //console.log('Ignoring another account', keyedAccountInfo); + return; + } + expect(keyedAccountInfo.accountInfo.lamports).toBe(1); + expect(keyedAccountInfo.accountInfo.owner).toEqual(BpfLoader.programId); + notified = true; + }, ); await connection.requestAirdrop(owner.publicKey, 42); - const transaction = SystemProgram.createAccount( - owner.publicKey, - programAccount.publicKey, - 42, + await Loader.load(connection, owner, programAccount, BpfLoader.programId, [ + 1, + 2, 3, - BpfLoader.programId, - ); - await sendAndConfirmTransaction(connection, transaction, owner); - - const loader = new Loader(connection, BpfLoader.programId); - await loader.load(programAccount, [1, 2, 3]); + ]); // Wait for mockCallback to receive a call let i = 0; - for (;;) { - if (mockCallback.mock.calls.length === 1) { - break; - } - - if (++i === 20) { - console.log(JSON.stringify(mockCallback.mock.calls)); - throw new Error('mockCallback should be called twice'); + while (!notified) { + //for (;;) { + if (++i === 30) { + throw new Error('Program change notification not observed'); } // Sleep for a 1/4 of a slot, notifications only occur after a block is // processed @@ -599,16 +591,4 @@ test('program account change notification', async () => { } await connection.removeProgramAccountChangeListener(subscriptionId); - - expect(mockCallback.mock.calls[0][0].accountId).toEqual( - programAccount.publicKey.toString(), - ); - expect(mockCallback.mock.calls[0][0].accountInfo.lamports).toBe(42); - expect(mockCallback.mock.calls[0][0].accountInfo.owner).toEqual( - BpfLoader.programId, - ); - expect(mockCallback.mock.calls[0][0].accountInfo.executable).toBe(false); - expect(mockCallback.mock.calls[0][0].accountInfo.data).toEqual( - Buffer.from([1, 2, 3]), - ); }); diff --git a/web3.js/test/new-account-with-lamports.js b/web3.js/test/new-account-with-lamports.js index 9d4aed7404..babfdf51b1 100644 --- a/web3.js/test/new-account-with-lamports.js +++ b/web3.js/test/new-account-with-lamports.js @@ -6,7 +6,7 @@ import {url} from './url'; export async function newAccountWithLamports( connection: Connection, - lamports: number = 10, + lamports: number = 1000000, ): Promise { const account = new Account();