feat: make Transaction.populate method public and tweak MessageArgs

This commit is contained in:
Justin Starry
2020-06-11 13:15:14 +08:00
committed by Michael Vines
parent 22a63fe93c
commit ad0e71d357
6 changed files with 64 additions and 91 deletions

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

@ -423,7 +423,7 @@ declare module '@solana/web3.js' {
export type MessageArgs = { export type MessageArgs = {
header: MessageHeader; header: MessageHeader;
accountKeys: PublicKey[]; accountKeys: string[];
recentBlockhash: Blockhash; recentBlockhash: Blockhash;
instructions: CompiledInstruction[]; instructions: CompiledInstruction[];
}; };
@ -487,6 +487,7 @@ declare module '@solana/web3.js' {
constructor(opts?: TransactionCtorFields); constructor(opts?: TransactionCtorFields);
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction; static from(buffer: Buffer | Uint8Array | Array<number>): Transaction;
static populate(message: Message, signatures: Array<string>): Transaction;
add( add(
...items: Array< ...items: Array<
Transaction | TransactionInstruction | TransactionInstructionCtorFields Transaction | TransactionInstruction | TransactionInstructionCtorFields

View File

@ -433,7 +433,7 @@ declare module '@solana/web3.js' {
declare export type MessageArgs = { declare export type MessageArgs = {
header: MessageHeader, header: MessageHeader,
accountKeys: PublicKey[], accountKeys: string[],
recentBlockhash: Blockhash, recentBlockhash: Blockhash,
instructions: CompiledInstruction[], instructions: CompiledInstruction[],
}; };
@ -499,6 +499,7 @@ declare module '@solana/web3.js' {
constructor(opts?: TransactionCtorFields): Transaction; constructor(opts?: TransactionCtorFields): Transaction;
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction; static from(buffer: Buffer | Uint8Array | Array<number>): Transaction;
static populate(message: Message, signatures: Array<string>): Transaction;
add( add(
...items: Array< ...items: Array<
Transaction | TransactionInstruction | TransactionInstructionCtorFields, Transaction | TransactionInstruction | TransactionInstructionCtorFields,

View File

@ -12,6 +12,7 @@ import {NonceAccount} from './nonce-account';
import {PublicKey} from './publickey'; import {PublicKey} from './publickey';
import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from './timing'; import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from './timing';
import {Transaction} from './transaction'; import {Transaction} from './transaction';
import {Message} from './message';
import {sleep} from './util/sleep'; import {sleep} from './util/sleep';
import {toBuffer} from './util/to-buffer'; import {toBuffer} from './util/to-buffer';
import type {Blockhash} from './blockhash'; import type {Blockhash} from './blockhash';
@ -1580,27 +1581,26 @@ export class Connection {
*/ */
async getConfirmedBlock(slot: number): Promise<ConfirmedBlock> { async getConfirmedBlock(slot: number): Promise<ConfirmedBlock> {
const unsafeRes = await this._rpcRequest('getConfirmedBlock', [slot]); const unsafeRes = await this._rpcRequest('getConfirmedBlock', [slot]);
const result = GetConfirmedBlockRpcResult(unsafeRes); const {result, error} = GetConfirmedBlockRpcResult(unsafeRes);
if (result.error) { if (error) {
throw new Error('failed to get confirmed block: ' + result.error.message); throw new Error('failed to get confirmed block: ' + result.error.message);
} }
assert(typeof result.result !== 'undefined'); assert(typeof result !== 'undefined');
if (!result.result) { if (!result) {
throw new Error('Confirmed block ' + slot + ' not found'); throw new Error('Confirmed block ' + slot + ' not found');
} }
return { return {
blockhash: new PublicKey(result.result.blockhash).toString(), blockhash: new PublicKey(result.blockhash).toString(),
previousBlockhash: new PublicKey( previousBlockhash: new PublicKey(result.previousBlockhash).toString(),
result.result.previousBlockhash, parentSlot: result.parentSlot,
).toString(), transactions: result.transactions.map(result => {
parentSlot: result.result.parentSlot, const {message, signatures} = result.transaction;
transactions: result.result.transactions.map(result => {
return { return {
transaction: Transaction.fromRpcResult(result.transaction), transaction: Transaction.populate(new Message(message), signatures),
meta: result.meta, meta: result.meta,
}; };
}), }),
rewards: result.result.rewards || [], rewards: result.rewards || [],
}; };
} }
@ -1622,9 +1622,10 @@ export class Connection {
return result; return result;
} }
const {message, signatures} = result.transaction;
return { return {
slot: result.slot, slot: result.slot,
transaction: Transaction.fromRpcResult(result.transaction), transaction: Transaction.populate(new Message(message), signatures),
meta: result.meta, meta: result.meta,
}; };
} }

View File

@ -42,13 +42,13 @@ export type CompiledInstruction = {
* *
* @typedef {Object} MessageArgs * @typedef {Object} MessageArgs
* @property {MessageHeader} header The message header, identifying signed and read-only `accountKeys` * @property {MessageHeader} header The message header, identifying signed and read-only `accountKeys`
* @property {PublicKey[]} accounts All the account keys used by this transaction * @property {string[]} accounts All the account keys used by this transaction
* @property {Blockhash} recentBlockhash The hash of a recent ledger block * @property {Blockhash} recentBlockhash The hash of a recent ledger block
* @property {CompiledInstruction[]} instructions Instructions that will be executed in sequence and committed in one atomic transaction if all succeed. * @property {CompiledInstruction[]} instructions Instructions that will be executed in sequence and committed in one atomic transaction if all succeed.
*/ */
type MessageArgs = { type MessageArgs = {
header: MessageHeader, header: MessageHeader,
accountKeys: PublicKey[], accountKeys: string[],
recentBlockhash: Blockhash, recentBlockhash: Blockhash,
instructions: CompiledInstruction[], instructions: CompiledInstruction[],
}; };
@ -64,7 +64,7 @@ export class Message {
constructor(args: MessageArgs) { constructor(args: MessageArgs) {
this.header = args.header; this.header = args.header;
this.accountKeys = args.accountKeys; this.accountKeys = args.accountKeys.map(account => new PublicKey(account));
this.recentBlockhash = args.recentBlockhash; this.recentBlockhash = args.recentBlockhash;
this.instructions = args.instructions; this.instructions = args.instructions;
} }

View File

@ -210,7 +210,7 @@ export class Transaction {
let numReadonlySignedAccounts = 0; let numReadonlySignedAccounts = 0;
let numReadonlyUnsignedAccounts = 0; let numReadonlyUnsignedAccounts = 0;
const keys = this.signatures.map(({publicKey}) => publicKey.toString()); const accountKeys = this.signatures.map(({publicKey}) => publicKey.toString());
const programIds: string[] = []; const programIds: string[] = [];
const accountMetas: AccountMeta[] = []; const accountMetas: AccountMeta[] = [];
this.instructions.forEach(instruction => { this.instructions.forEach(instruction => {
@ -233,8 +233,8 @@ export class Transaction {
accountMetas.forEach(({pubkey, isSigner, isWritable}) => { accountMetas.forEach(({pubkey, isSigner, isWritable}) => {
const keyStr = pubkey.toString(); const keyStr = pubkey.toString();
if (!keys.includes(keyStr)) { if (!accountKeys.includes(keyStr)) {
keys.push(keyStr); accountKeys.push(keyStr);
if (isSigner) { if (isSigner) {
this.signatures.push({ this.signatures.push({
signature: null, signature: null,
@ -250,8 +250,8 @@ export class Transaction {
}); });
programIds.forEach(programId => { programIds.forEach(programId => {
if (!keys.includes(programId)) { if (!accountKeys.includes(programId)) {
keys.push(programId); accountKeys.push(programId);
numReadonlyUnsignedAccounts += 1; numReadonlyUnsignedAccounts += 1;
} }
}); });
@ -260,9 +260,9 @@ export class Transaction {
instruction => { instruction => {
const {data, programId} = instruction; const {data, programId} = instruction;
return { return {
programIdIndex: keys.indexOf(programId.toString()), programIdIndex: accountKeys.indexOf(programId.toString()),
accounts: instruction.keys.map(keyObj => accounts: instruction.keys.map(keyObj =>
keys.indexOf(keyObj.pubkey.toString()), accountKeys.indexOf(keyObj.pubkey.toString()),
), ),
data: bs58.encode(data), data: bs58.encode(data),
}; };
@ -280,7 +280,7 @@ export class Transaction {
numReadonlySignedAccounts, numReadonlySignedAccounts,
numReadonlyUnsignedAccounts, numReadonlyUnsignedAccounts,
}, },
accountKeys: keys.map(k => new PublicKey(k)), accountKeys,
recentBlockhash, recentBlockhash,
instructions, instructions,
}); });
@ -477,11 +477,11 @@ export class Transaction {
const numReadonlyUnsignedAccounts = byteArray.shift(); const numReadonlyUnsignedAccounts = byteArray.shift();
const accountCount = shortvec.decodeLength(byteArray); const accountCount = shortvec.decodeLength(byteArray);
let accounts = []; let accountKeys = [];
for (let i = 0; i < accountCount; i++) { for (let i = 0; i < accountCount; i++) {
const account = byteArray.slice(0, PUBKEY_LENGTH); const account = byteArray.slice(0, PUBKEY_LENGTH);
byteArray = byteArray.slice(PUBKEY_LENGTH); byteArray = byteArray.slice(PUBKEY_LENGTH);
accounts.push(bs58.encode(Buffer.from(account))); accountKeys.push(bs58.encode(Buffer.from(account)));
} }
const recentBlockhash = byteArray.slice(0, PUBKEY_LENGTH); const recentBlockhash = byteArray.slice(0, PUBKEY_LENGTH);
@ -502,54 +502,24 @@ export class Transaction {
instructions.push(instruction); instructions.push(instruction);
} }
const message = { const messageArgs = {
header: { header: {
numRequiredSignatures, numRequiredSignatures,
numReadonlySignedAccounts, numReadonlySignedAccounts,
numReadonlyUnsignedAccounts, numReadonlyUnsignedAccounts,
}, },
recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)), recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)),
accountKeys: accounts.map(account => new PublicKey(account)), accountKeys,
instructions, instructions,
}; };
return Transaction._populate(signatures, new Message(message)); return Transaction.populate(new Message(messageArgs), signatures);
} }
/** /**
* Parse an RPC result into a Transaction object. * Populate Transaction object from message and signatures
*/ */
static fromRpcResult(rpcResult: any): Transaction { static populate(message: Message, signatures: Array<string>): Transaction {
const signatures = rpcResult.signatures;
const accounts = rpcResult.message.accountKeys;
const instructions = rpcResult.message.instructions;
const recentBlockhash = rpcResult.message.recentBlockhash;
const numRequiredSignatures =
rpcResult.message.header.numRequiredSignatures;
const numReadonlySignedAccounts =
rpcResult.message.header.numReadonlySignedAccounts;
const numReadonlyUnsignedAccounts =
rpcResult.message.header.numReadonlyUnsignedAccounts;
const message = {
header: {
numRequiredSignatures,
numReadonlySignedAccounts,
numReadonlyUnsignedAccounts,
},
recentBlockhash,
accountKeys: accounts.map(account => new PublicKey(account)),
instructions,
};
return Transaction._populate(signatures, new Message(message));
}
/**
* Populate Transaction object
* @private
*/
static _populate(signatures: Array<string>, message: Message): Transaction {
const transaction = new Transaction(); const transaction = new Transaction();
transaction.recentBlockhash = message.recentBlockhash; transaction.recentBlockhash = message.recentBlockhash;
signatures.forEach((signature, index) => { signatures.forEach((signature, index) => {

View File

@ -7,6 +7,7 @@ import {PublicKey} from '../src/publickey';
import {Transaction} from '../src/transaction'; import {Transaction} from '../src/transaction';
import {StakeProgram} from '../src/stake-program'; import {StakeProgram} from '../src/stake-program';
import {SystemProgram} from '../src/system-program'; import {SystemProgram} from '../src/system-program';
import {Message} from '../src/message';
test('signPartial', () => { test('signPartial', () => {
const account1 = new Account(); const account1 = new Account();
@ -159,10 +160,9 @@ test('parse wire format and serialize', () => {
expect(wireTransaction).toEqual(expectedTransaction.serialize()); expect(wireTransaction).toEqual(expectedTransaction.serialize());
}); });
test('transaction from rpc result', () => { test('populate transaction', () => {
const recentBlockhash = new PublicKey(1).toString(); const recentBlockhash = new PublicKey(1).toString();
const rpcResult = { const message = {
message: {
accountKeys: [ accountKeys: [
new PublicKey(1).toString(), new PublicKey(1).toString(),
new PublicKey(2).toString(), new PublicKey(2).toString(),
@ -171,7 +171,7 @@ test('transaction from rpc result', () => {
new PublicKey(5).toString(), new PublicKey(5).toString(),
], ],
header: { header: {
num_ReadonlySignedAccounts: 0, numReadonlySignedAccounts: 0,
numReadonlyUnsignedAccounts: 3, numReadonlyUnsignedAccounts: 3,
numRequiredSignatures: 2, numRequiredSignatures: 2,
}, },
@ -183,14 +183,14 @@ test('transaction from rpc result', () => {
}, },
], ],
recentBlockhash, recentBlockhash,
},
signatures: [
bs58.encode(Buffer.alloc(64).fill(1)),
bs58.encode(Buffer.alloc(64).fill(2)),
],
}; };
const transaction = Transaction.fromRpcResult(rpcResult); const signatures = [
bs58.encode(Buffer.alloc(64).fill(1)),
bs58.encode(Buffer.alloc(64).fill(2)),
];
const transaction = Transaction.populate(new Message(message), signatures);
expect(transaction.instructions.length).toEqual(1); expect(transaction.instructions.length).toEqual(1);
expect(transaction.signatures.length).toEqual(2); expect(transaction.signatures.length).toEqual(2);
expect(transaction.recentBlockhash).toEqual(recentBlockhash); expect(transaction.recentBlockhash).toEqual(recentBlockhash);