fix: repair key handling in _getSignData and add Nonce live test
This commit is contained in:
committed by
Michael Vines
parent
bd0a9348f4
commit
aea0e83a40
@ -1224,59 +1224,63 @@ export class Connection {
|
|||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
...signers: Array<Account>
|
...signers: Array<Account>
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
for (;;) {
|
if (transaction.nonceInfo) {
|
||||||
// Attempt to use a recent blockhash for up to 30 seconds
|
transaction.sign(...signers);
|
||||||
const seconds = new Date().getSeconds();
|
} else {
|
||||||
if (
|
|
||||||
this._blockhashInfo.recentBlockhash != null &&
|
|
||||||
this._blockhashInfo.seconds < seconds + 30
|
|
||||||
) {
|
|
||||||
transaction.recentBlockhash = this._blockhashInfo.recentBlockhash;
|
|
||||||
transaction.sign(...signers);
|
|
||||||
if (!transaction.signature) {
|
|
||||||
throw new Error('!signature'); // should never happen
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the signature of this transaction has not been seen before with the
|
|
||||||
// current recentBlockhash, all done.
|
|
||||||
const signature = transaction.signature.toString();
|
|
||||||
if (!this._blockhashInfo.transactionSignatures.includes(signature)) {
|
|
||||||
this._blockhashInfo.transactionSignatures.push(signature);
|
|
||||||
if (this._disableBlockhashCaching) {
|
|
||||||
this._blockhashInfo.seconds = -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch a new blockhash
|
|
||||||
let attempts = 0;
|
|
||||||
const startTime = Date.now();
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const [
|
// Attempt to use a recent blockhash for up to 30 seconds
|
||||||
recentBlockhash,
|
const seconds = new Date().getSeconds();
|
||||||
//feeCalculator,
|
if (
|
||||||
] = await this.getRecentBlockhash();
|
this._blockhashInfo.recentBlockhash != null &&
|
||||||
|
this._blockhashInfo.seconds < seconds + 30
|
||||||
|
) {
|
||||||
|
transaction.recentBlockhash = this._blockhashInfo.recentBlockhash;
|
||||||
|
transaction.sign(...signers);
|
||||||
|
if (!transaction.signature) {
|
||||||
|
throw new Error('!signature'); // should never happen
|
||||||
|
}
|
||||||
|
|
||||||
if (this._blockhashInfo.recentBlockhash != recentBlockhash) {
|
// If the signature of this transaction has not been seen before with the
|
||||||
this._blockhashInfo = {
|
// current recentBlockhash, all done.
|
||||||
|
const signature = transaction.signature.toString();
|
||||||
|
if (!this._blockhashInfo.transactionSignatures.includes(signature)) {
|
||||||
|
this._blockhashInfo.transactionSignatures.push(signature);
|
||||||
|
if (this._disableBlockhashCaching) {
|
||||||
|
this._blockhashInfo.seconds = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch a new blockhash
|
||||||
|
let attempts = 0;
|
||||||
|
const startTime = Date.now();
|
||||||
|
for (;;) {
|
||||||
|
const [
|
||||||
recentBlockhash,
|
recentBlockhash,
|
||||||
seconds: new Date().getSeconds(),
|
//feeCalculator,
|
||||||
transactionSignatures: [],
|
] = await this.getRecentBlockhash();
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (attempts === 50) {
|
|
||||||
throw new Error(
|
|
||||||
`Unable to obtain a new blockhash after ${Date.now() -
|
|
||||||
startTime}ms`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep for approximately half a slot
|
if (this._blockhashInfo.recentBlockhash != recentBlockhash) {
|
||||||
await sleep((500 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND);
|
this._blockhashInfo = {
|
||||||
|
recentBlockhash,
|
||||||
|
seconds: new Date().getSeconds(),
|
||||||
|
transactionSignatures: [],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (attempts === 50) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to obtain a new blockhash after ${Date.now() -
|
||||||
|
startTime}ms`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
++attempts;
|
// Sleep for approximately half a slot
|
||||||
|
await sleep((500 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND);
|
||||||
|
|
||||||
|
++attempts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import {PublicKey} from './publickey';
|
|||||||
const NonceAccountLayout = BufferLayout.struct([
|
const NonceAccountLayout = BufferLayout.struct([
|
||||||
BufferLayout.u32('state'),
|
BufferLayout.u32('state'),
|
||||||
Layout.publicKey('authorizedPubkey'),
|
Layout.publicKey('authorizedPubkey'),
|
||||||
Layout.publicKey('hash'),
|
Layout.publicKey('nonce'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -184,7 +184,7 @@ export class Transaction {
|
|||||||
*/
|
*/
|
||||||
_getSignData(): Buffer {
|
_getSignData(): Buffer {
|
||||||
const {nonceInfo} = this;
|
const {nonceInfo} = this;
|
||||||
if (nonceInfo) {
|
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) {
|
||||||
this.recentBlockhash = nonceInfo.nonce;
|
this.recentBlockhash = nonceInfo.nonce;
|
||||||
this.instructions.unshift(nonceInfo.nonceInstruction);
|
this.instructions.unshift(nonceInfo.nonceInstruction);
|
||||||
}
|
}
|
||||||
@ -203,25 +203,10 @@ export class Transaction {
|
|||||||
|
|
||||||
const programIds = [];
|
const programIds = [];
|
||||||
|
|
||||||
|
const allKeys = [];
|
||||||
this.instructions.forEach(instruction => {
|
this.instructions.forEach(instruction => {
|
||||||
instruction.keys.forEach(keySignerPair => {
|
instruction.keys.forEach(keySignerPair => {
|
||||||
const keyStr = keySignerPair.pubkey.toString();
|
allKeys.push(keySignerPair);
|
||||||
if (!keys.includes(keyStr)) {
|
|
||||||
if (keySignerPair.isSigner) {
|
|
||||||
this.signatures.push({
|
|
||||||
signature: null,
|
|
||||||
publicKey: keySignerPair.pubkey,
|
|
||||||
});
|
|
||||||
if (!keySignerPair.isWritable) {
|
|
||||||
numReadonlySignedAccounts += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!keySignerPair.isWritable) {
|
|
||||||
numReadonlyUnsignedAccounts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keys.push(keyStr);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const programId = instruction.programId.toString();
|
const programId = instruction.programId.toString();
|
||||||
@ -230,6 +215,32 @@ export class Transaction {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
allKeys.sort(function(x, y) {
|
||||||
|
const checkSigner = x.isSigner === y.isSigner ? 0 : x.isSigner ? -1 : 1;
|
||||||
|
const checkWritable = x.isWritable === y.isWritable ? 0 : x.isWritable ? -1 : 1;
|
||||||
|
return checkSigner || checkWritable;
|
||||||
|
});
|
||||||
|
|
||||||
|
allKeys.forEach(keySignerPair => {
|
||||||
|
const keyStr = keySignerPair.pubkey.toString();
|
||||||
|
if (!keys.includes(keyStr)) {
|
||||||
|
if (keySignerPair.isSigner) {
|
||||||
|
this.signatures.push({
|
||||||
|
signature: null,
|
||||||
|
publicKey: keySignerPair.pubkey,
|
||||||
|
});
|
||||||
|
if (!keySignerPair.isWritable) {
|
||||||
|
numReadonlySignedAccounts += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!keySignerPair.isWritable) {
|
||||||
|
numReadonlyUnsignedAccounts += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys.push(keyStr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
programIds.forEach(programId => {
|
programIds.forEach(programId => {
|
||||||
if (!keys.includes(programId)) {
|
if (!keys.includes(programId)) {
|
||||||
keys.push(programId);
|
keys.push(programId);
|
||||||
|
@ -3,10 +3,21 @@
|
|||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
BudgetProgram,
|
BudgetProgram,
|
||||||
|
Connection,
|
||||||
SystemInstruction,
|
SystemInstruction,
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
sendAndConfirmRecentTransaction,
|
||||||
|
LAMPORTS_PER_SOL,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
import {mockRpcEnabled} from './__mocks__/node-fetch';
|
||||||
|
import {sleep} from '../src/util/sleep';
|
||||||
|
import {url} from './url';
|
||||||
|
|
||||||
|
if (!mockRpcEnabled) {
|
||||||
|
// Testing max commitment level takes around 20s to complete
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
}
|
||||||
|
|
||||||
test('createAccount', () => {
|
test('createAccount', () => {
|
||||||
const from = new Account();
|
const from = new Account();
|
||||||
@ -85,7 +96,9 @@ test('createNonceAccount', () => {
|
|||||||
expect(transaction.instructions[0].programId).toEqual(
|
expect(transaction.instructions[0].programId).toEqual(
|
||||||
SystemProgram.programId,
|
SystemProgram.programId,
|
||||||
);
|
);
|
||||||
expect(transaction.instructions[1].programId).toEqual(SystemProgram.programId);
|
expect(transaction.instructions[1].programId).toEqual(
|
||||||
|
SystemProgram.programId,
|
||||||
|
);
|
||||||
// TODO: Validate transaction contents more
|
// TODO: Validate transaction contents more
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -259,3 +272,72 @@ test('non-SystemInstruction error', () => {
|
|||||||
SystemInstruction.from(transaction.instructions[0]);
|
SystemInstruction.from(transaction.instructions[0]);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('live Nonce actions', async () => {
|
||||||
|
if (mockRpcEnabled) {
|
||||||
|
console.log('non-live test skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = new Connection(url, 'recent');
|
||||||
|
const nonceAccount = new Account();
|
||||||
|
const from = new Account();
|
||||||
|
const to = new Account();
|
||||||
|
const authority = new Account();
|
||||||
|
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL);
|
||||||
|
await connection.requestAirdrop(authority.publicKey, LAMPORTS_PER_SOL);
|
||||||
|
|
||||||
|
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
SystemProgram.nonceSpace,
|
||||||
|
'recent',
|
||||||
|
);
|
||||||
|
|
||||||
|
let createNonceAccount = SystemProgram.createNonceAccount(
|
||||||
|
from.publicKey,
|
||||||
|
nonceAccount.publicKey,
|
||||||
|
from.publicKey,
|
||||||
|
minimumAmount,
|
||||||
|
);
|
||||||
|
await sendAndConfirmRecentTransaction(
|
||||||
|
connection,
|
||||||
|
createNonceAccount,
|
||||||
|
from,
|
||||||
|
nonceAccount,
|
||||||
|
);
|
||||||
|
const nonceBalance = await connection.getBalance(nonceAccount.publicKey);
|
||||||
|
expect(nonceBalance).toEqual(minimumAmount);
|
||||||
|
|
||||||
|
const nonceQuery1 = await connection.getNonce(nonceAccount.publicKey);
|
||||||
|
const nonceQuery2 = await connection.getNonce(nonceAccount.publicKey);
|
||||||
|
expect(nonceQuery1.nonce).toEqual(nonceQuery2.nonce);
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const advanceNonce = new Transaction().add(
|
||||||
|
SystemProgram.nonceAdvance(nonceAccount.publicKey, from.publicKey),
|
||||||
|
);
|
||||||
|
await sendAndConfirmRecentTransaction(connection, advanceNonce, from);
|
||||||
|
|
||||||
|
const nonceQuery3 = await connection.getNonce(nonceAccount.publicKey);
|
||||||
|
expect(nonceQuery1.nonce).not.toEqual(nonceQuery3.nonce);
|
||||||
|
const nonce = nonceQuery3.nonce;
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
let transfer = SystemProgram.transfer(
|
||||||
|
from.publicKey,
|
||||||
|
to.publicKey,
|
||||||
|
minimumAmount,
|
||||||
|
);
|
||||||
|
transfer.nonceInfo = {
|
||||||
|
nonce,
|
||||||
|
nonceInstruction: SystemProgram.nonceAdvance(
|
||||||
|
nonceAccount.publicKey,
|
||||||
|
from.publicKey,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
await sendAndConfirmRecentTransaction(connection, transfer, from);
|
||||||
|
const toBalance = await connection.getBalance(to.publicKey);
|
||||||
|
expect(toBalance).toEqual(minimumAmount);
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user