fix: transactions populated from RPC requests retain original account key order (#23720)
* fix: transaction populate * chore: web3: fix tx serialization test * chore: web3: run prettier * fix: web3: transaction populate * fix: web3: handle nonce info * add hash calc config.use_write_cache (#24005) * restore existing overlapping overflow (#24010) * Stringify populated transaction fields * fix: web3: compare stringified JSON * chore: web3: remove eslint indent rule that conflicts with prettier * fix: web3: explicitly call toJSON * fix: web3: add test for compileMessage * fix: web3: make JSON internal * fix: web3: connection simulation from message relies on mutating transaction Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com> Co-authored-by: Jack May <jack@solana.com> Co-authored-by: Justin Starry <justin@solana.com>
This commit is contained in:
parent
6c5a3ca4a0
commit
21dacefbee
|
@ -31,14 +31,6 @@ module.exports = {
|
|||
'newlines-between': 'always',
|
||||
},
|
||||
],
|
||||
indent: [
|
||||
'error',
|
||||
2,
|
||||
{
|
||||
MemberExpression: 1,
|
||||
SwitchCase: 1,
|
||||
},
|
||||
],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
'no-console': [0],
|
||||
'no-trailing-spaces': ['error'],
|
||||
|
|
|
@ -3895,6 +3895,8 @@ export class Connection {
|
|||
transaction.instructions = transactionOrMessage.instructions;
|
||||
} else {
|
||||
transaction = Transaction.populate(transactionOrMessage);
|
||||
// HACK: this function relies on mutating the populated transaction
|
||||
transaction._message = transaction._json = undefined;
|
||||
}
|
||||
|
||||
if (transaction.nonceInfo && signers) {
|
||||
|
|
|
@ -66,6 +66,19 @@ export type SerializeConfig = {
|
|||
verifySignatures?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface TransactionInstructionJSON {
|
||||
keys: {
|
||||
pubkey: string;
|
||||
isSigner: boolean;
|
||||
isWritable: boolean;
|
||||
}[];
|
||||
programId: string;
|
||||
data: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction Instruction class
|
||||
*/
|
||||
|
@ -93,6 +106,21 @@ export class TransactionInstruction {
|
|||
this.data = opts.data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
toJSON(): TransactionInstructionJSON {
|
||||
return {
|
||||
keys: this.keys.map(({pubkey, isSigner, isWritable}) => ({
|
||||
pubkey: pubkey.toJSON(),
|
||||
isSigner,
|
||||
isWritable,
|
||||
})),
|
||||
programId: this.programId.toJSON(),
|
||||
data: [...this.data],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,6 +156,20 @@ export type NonceInformation = {
|
|||
nonceInstruction: TransactionInstruction;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface TransactionJSON {
|
||||
recentBlockhash: string | null;
|
||||
feePayer: string | null;
|
||||
nonceInfo: {
|
||||
nonce: string;
|
||||
nonceInstruction: TransactionInstructionJSON;
|
||||
} | null;
|
||||
instructions: TransactionInstructionJSON[];
|
||||
signatures: {publicKey: string; signature: number[] | null}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction class
|
||||
*/
|
||||
|
@ -169,6 +211,16 @@ export class Transaction {
|
|||
*/
|
||||
nonceInfo?: NonceInformation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_message?: Message;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_json?: TransactionJSON;
|
||||
|
||||
/**
|
||||
* Construct an empty Transaction
|
||||
*/
|
||||
|
@ -176,6 +228,27 @@ export class Transaction {
|
|||
opts && Object.assign(this, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
toJSON(): TransactionJSON {
|
||||
return {
|
||||
recentBlockhash: this.recentBlockhash || null,
|
||||
feePayer: this.feePayer ? this.feePayer.toJSON() : null,
|
||||
nonceInfo: this.nonceInfo
|
||||
? {
|
||||
nonce: this.nonceInfo.nonce,
|
||||
nonceInstruction: this.nonceInfo.nonceInstruction.toJSON(),
|
||||
}
|
||||
: null,
|
||||
instructions: this.instructions.map(instruction => instruction.toJSON()),
|
||||
signatures: this.signatures.map(({publicKey, signature}) => ({
|
||||
publicKey: publicKey.toJSON(),
|
||||
signature: signature ? [...signature] : null,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more instructions to this Transaction
|
||||
*/
|
||||
|
@ -204,6 +277,15 @@ export class Transaction {
|
|||
* Compile transaction data
|
||||
*/
|
||||
compileMessage(): Message {
|
||||
if (this._message) {
|
||||
if (JSON.stringify(this.toJSON()) !== JSON.stringify(this._json)) {
|
||||
throw new Error(
|
||||
'Transaction mutated after being populated from Message',
|
||||
);
|
||||
}
|
||||
return this._message;
|
||||
}
|
||||
|
||||
const {nonceInfo} = this;
|
||||
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) {
|
||||
this.recentBlockhash = nonceInfo.nonce;
|
||||
|
@ -719,6 +801,9 @@ export class Transaction {
|
|||
);
|
||||
});
|
||||
|
||||
transaction._message = message;
|
||||
transaction._json = transaction.toJSON();
|
||||
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -365,14 +365,14 @@ describe('Transaction', () => {
|
|||
}).add(transfer);
|
||||
expectedTransaction.sign(sender);
|
||||
|
||||
const wireTransaction = Buffer.from(
|
||||
const serializedTransaction = Buffer.from(
|
||||
'AVuErQHaXv0SG0/PchunfxHKt8wMRfMZzqV0tkC5qO6owYxWU2v871AoWywGoFQr4z+q/7mE8lIufNl/kxj+nQ0BAAEDE5j2LG0aRXxRumpLXz29L2n8qTIWIY3ImX5Ba9F9k8r9Q5/Mtmcn8onFxt47xKj+XdXXd3C8j/FcPu7csUrz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxJrndgN4IFTxep3s6kO0ROug7bEsbx0xxuDkqEvwUusBAgIAAQwCAAAAMQAAAAAAAAA=',
|
||||
'base64',
|
||||
);
|
||||
const tx = Transaction.from(wireTransaction);
|
||||
const deserializedTransaction = Transaction.from(serializedTransaction);
|
||||
|
||||
expect(tx).to.eql(expectedTransaction);
|
||||
expect(wireTransaction).to.eql(expectedTransaction.serialize());
|
||||
expect(expectedTransaction.serialize()).to.eql(serializedTransaction);
|
||||
expect(deserializedTransaction.serialize()).to.eql(serializedTransaction);
|
||||
});
|
||||
|
||||
it('populate transaction', () => {
|
||||
|
@ -409,6 +409,11 @@ describe('Transaction', () => {
|
|||
expect(transaction.instructions).to.have.length(1);
|
||||
expect(transaction.signatures).to.have.length(2);
|
||||
expect(transaction.recentBlockhash).to.eq(recentBlockhash);
|
||||
|
||||
transaction.feePayer = new PublicKey(6);
|
||||
expect(() => transaction.compileMessage()).to.throw(
|
||||
'Transaction mutated after being populated from Message',
|
||||
);
|
||||
});
|
||||
|
||||
it('serialize unsigned transaction', () => {
|
||||
|
|
Loading…
Reference in New Issue