Skip to content

Commit

Permalink
fix: transactions populated from RPC requests retain original account…
Browse files Browse the repository at this point in the history
… key order (solana-labs#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 (solana-labs#24005)

* restore existing overlapping overflow (solana-labs#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>
  • Loading branch information
4 people committed Jun 30, 2022
1 parent 4b92853 commit 15704a8
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 12 deletions.
8 changes: 0 additions & 8 deletions web3.js/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
2 changes: 2 additions & 0 deletions web3.js/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
85 changes: 85 additions & 0 deletions web3.js/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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],
};
}
}

/**
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -169,13 +211,44 @@ export class Transaction {
*/
nonceInfo?: NonceInformation;

/**
* @internal
*/
_message?: Message;

/**
* @internal
*/
_json?: TransactionJSON;

/**
* Construct an empty Transaction
*/
constructor(opts?: TransactionCtorFields) {
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
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -719,6 +801,9 @@ export class Transaction {
);
});

transaction._message = message;
transaction._json = transaction.toJSON();

return transaction;
}
}
13 changes: 9 additions & 4 deletions web3.js/test/transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 15704a8

Please sign in to comment.