feat: support restarting failed bpf loader deploys (#12163)

* feat: support restarting failed bpf loader deploys

* chore: add error message if program already exists
This commit is contained in:
Justin Starry 2020-09-23 22:54:27 +08:00 committed by GitHub
parent bb72cbe7ae
commit 54775ffedf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 28 deletions

4
web3.js/module.d.ts vendored
View File

@ -962,7 +962,7 @@ declare module '@solana/web3.js' {
program: Account,
programId: PublicKey,
data: Buffer | Uint8Array | Array<number>,
): Promise<PublicKey>;
): Promise<boolean>;
}
// === src/bpf-loader.js ===
@ -975,7 +975,7 @@ declare module '@solana/web3.js' {
program: Account,
elfBytes: Buffer | Uint8Array | Array<number>,
loaderProgramId: PublicKey,
): Promise<PublicKey>;
): Promise<boolean>;
}
// === src/bpf-loader-deprecated.js ===

View File

@ -969,7 +969,7 @@ declare module '@solana/web3.js' {
program: Account,
programId: PublicKey,
data: Buffer | Uint8Array | Array<number>,
): Promise<PublicKey>;
): Promise<boolean>;
}
// === src/bpf-loader.js ===
@ -982,7 +982,7 @@ declare module '@solana/web3.js' {
program: Account,
elfBytes: Buffer | Uint8Array | Array<number>,
loaderProgramId: PublicKey,
): Promise<PublicKey>;
): Promise<boolean>;
}
// === src/bpf-loader-deprecated.js ===

View File

@ -31,6 +31,7 @@ export class BpfLoader {
* @param program Account to load the program into
* @param elf The entire ELF containing the BPF program
* @param loaderProgramId The program id of the BPF loader to use
* @return true if program was loaded successfully, false if program was already loaded
*/
static load(
connection: Connection,
@ -38,7 +39,7 @@ export class BpfLoader {
program: Account,
elf: Buffer | Uint8Array | Array<number>,
loaderProgramId: PublicKey,
): Promise<void> {
): Promise<boolean> {
return Loader.load(connection, payer, program, loaderProgramId, elf);
}
}

View File

@ -45,6 +45,7 @@ export class Loader {
* @param program Account to load the program into
* @param programId Public key that identifies the loader
* @param data Program octets
* @return true if program was loaded successfully, false if program was already loaded
*/
static async load(
connection: Connection,
@ -52,29 +53,80 @@ export class Loader {
program: Account,
programId: PublicKey,
data: Buffer | Uint8Array | Array<number>,
): Promise<void> {
): Promise<boolean> {
{
const balanceNeeded = await connection.getMinimumBalanceForRentExemption(
data.length,
);
const transaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: program.publicKey,
lamports: balanceNeeded > 0 ? balanceNeeded : 1,
space: data.length,
programId,
}),
);
await sendAndConfirmTransaction(
connection,
transaction,
[payer, program],
{
commitment: 'single',
skipPreflight: true,
},
// Fetch program account info to check if it has already been created
const programInfo = await connection.getAccountInfo(
program.publicKey,
'single',
);
let transaction: Transaction | null = null;
if (programInfo !== null) {
if (programInfo.executable) {
console.error('Program load failed, account is already executable');
return false;
}
if (programInfo.data.length !== data.length) {
transaction = transaction || new Transaction();
transaction.add(
SystemProgram.allocate({
accountPubkey: program.publicKey,
space: data.length,
}),
);
}
if (!programInfo.owner.equals(programId)) {
transaction = transaction || new Transaction();
transaction.add(
SystemProgram.assign({
accountPubkey: program.publicKey,
programId,
}),
);
}
if (programInfo.lamports < balanceNeeded) {
transaction = transaction || new Transaction();
transaction.add(
SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: program.publicKey,
lamports: balanceNeeded - programInfo.lamports,
}),
);
}
} else {
transaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: program.publicKey,
lamports: balanceNeeded > 0 ? balanceNeeded : 1,
space: data.length,
programId,
}),
);
}
// If the account is already created correctly, skip this step
// and proceed directly to loading instructions
if (transaction !== null) {
await sendAndConfirmTransaction(
connection,
transaction,
[payer, program],
{
commitment: 'single',
skipPreflight: true,
},
);
}
}
const dataLayout = BufferLayout.struct([
@ -165,5 +217,8 @@ export class Loader {
},
);
}
// success
return true;
}
}

View File

@ -62,18 +62,19 @@ describe('load BPF Rust program', () => {
let program: Account;
let signature: string;
let payerAccount: Account;
let programData: Buffer;
beforeAll(async () => {
const data = await fs.readFile(
programData = await fs.readFile(
'test/fixtures/noop-rust/solana_bpf_rust_noop.so',
);
const {feeCalculator} = await connection.getRecentBlockhash();
const fees =
feeCalculator.lamportsPerSignature *
(BpfLoader.getMinNumSignatures(data.length) + NUM_RETRIES);
(BpfLoader.getMinNumSignatures(programData.length) + NUM_RETRIES);
const balanceNeeded = await connection.getMinimumBalanceForRentExemption(
data.length,
programData.length,
);
payerAccount = await newAccountWithLamports(
@ -81,12 +82,30 @@ describe('load BPF Rust program', () => {
fees + balanceNeeded,
);
program = new Account();
// Create program account with low balance
program = await newAccountWithLamports(connection, balanceNeeded - 1);
// First load will fail part way due to lack of funds
const insufficientPayerAccount = await newAccountWithLamports(
connection,
2 * feeCalculator.lamportsPerSignature * 8,
);
const failedLoad = BpfLoader.load(
connection,
insufficientPayerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
);
await expect(failedLoad).rejects.toThrow('Transaction was not confirmed');
// Second load will succeed
await BpfLoader.load(
connection,
payerAccount,
program,
data,
programData,
BPF_LOADER_PROGRAM_ID,
);
@ -196,4 +215,16 @@ describe('load BPF Rust program', () => {
expect(logs.length).toEqual(0);
});
test('reload program', async () => {
expect(
await BpfLoader.load(
connection,
payerAccount,
program,
programData,
BPF_LOADER_PROGRAM_ID,
),
).toBe(false);
});
});