solana/web3.js/src/message.js

171 lines
5.6 KiB
JavaScript

// @flow
import bs58 from 'bs58';
import * as BufferLayout from 'buffer-layout';
import {PublicKey} from './publickey';
import type {Blockhash} from './blockhash';
import * as Layout from './layout';
import {PACKET_DATA_SIZE} from './transaction';
import * as shortvec from './util/shortvec-encoding';
/**
* The message header, identifying signed and read-only account
*
* @typedef {Object} MessageHeader
* @property {number} numRequiredSignatures The number of signatures required for this message to be considered valid
* @property {number} numReadonlySignedAccounts: The last `numReadonlySignedAccounts` of the signed keys are read-only accounts
* @property {number} numReadonlyUnsignedAccounts The last `numReadonlySignedAccounts` of the unsigned keys are read-only accounts
*/
export type MessageHeader = {
numRequiredSignatures: number,
numReadonlySignedAccounts: number,
numReadonlyUnsignedAccounts: number,
};
/**
* An instruction to execute by a program
*
* @typedef {Object} CompiledInstruction
* @property {number} programIdIndex Index into the transaction keys array indicating the program account that executes this instruction
* @property {number[]} accounts Ordered indices into the transaction keys array indicating which accounts to pass to the program
* @property {string} data The program input data encoded as base 58
*/
export type CompiledInstruction = {
programIdIndex: number,
accounts: number[],
data: string,
};
/**
* Message constructor arguments
*
* @typedef {Object} MessageArgs
* @property {MessageHeader} header The message header, identifying signed and read-only `accountKeys`
* @property {PublicKey[]} accounts All the account keys used by this transaction
* @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.
*/
type MessageArgs = {
header: MessageHeader,
accountKeys: PublicKey[],
recentBlockhash: Blockhash,
instructions: CompiledInstruction[],
};
/**
* List of instructions to be processed atomically
*/
export class Message {
header: MessageHeader;
accountKeys: PublicKey[];
recentBlockhash: Blockhash;
instructions: CompiledInstruction[];
constructor(args: MessageArgs) {
this.header = args.header;
this.accountKeys = args.accountKeys;
this.recentBlockhash = args.recentBlockhash;
this.instructions = args.instructions;
}
isAccountWritable(index: number): boolean {
return (
index <
this.header.numRequiredSignatures -
this.header.numReadonlySignedAccounts ||
(index >= this.header.numRequiredSignatures &&
index <
this.accountKeys.length - this.header.numReadonlyUnsignedAccounts)
);
}
serialize(): Buffer {
const numKeys = this.accountKeys.length;
let keyCount = [];
shortvec.encodeLength(keyCount, numKeys);
const instructions = this.instructions.map(instruction => {
const {accounts, programIdIndex} = instruction;
const data = bs58.decode(instruction.data);
let keyIndicesCount = [];
shortvec.encodeLength(keyIndicesCount, accounts.length);
let dataCount = [];
shortvec.encodeLength(dataCount, data.length);
return {
programIdIndex,
keyIndicesCount: Buffer.from(keyIndicesCount),
keyIndices: Buffer.from(accounts),
dataLength: Buffer.from(dataCount),
data,
};
});
let instructionCount = [];
shortvec.encodeLength(instructionCount, instructions.length);
let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE);
Buffer.from(instructionCount).copy(instructionBuffer);
let instructionBufferLength = instructionCount.length;
instructions.forEach(instruction => {
const instructionLayout = BufferLayout.struct([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(
instruction.keyIndicesCount.length,
'keyIndicesCount',
),
BufferLayout.seq(
BufferLayout.u8('keyIndex'),
instruction.keyIndices.length,
'keyIndices',
),
BufferLayout.blob(instruction.dataLength.length, 'dataLength'),
BufferLayout.seq(
BufferLayout.u8('userdatum'),
instruction.data.length,
'data',
),
]);
const length = instructionLayout.encode(
instruction,
instructionBuffer,
instructionBufferLength,
);
instructionBufferLength += length;
});
instructionBuffer = instructionBuffer.slice(0, instructionBufferLength);
const signDataLayout = BufferLayout.struct([
BufferLayout.blob(1, 'numRequiredSignatures'),
BufferLayout.blob(1, 'numReadonlySignedAccounts'),
BufferLayout.blob(1, 'numReadonlyUnsignedAccounts'),
BufferLayout.blob(keyCount.length, 'keyCount'),
BufferLayout.seq(Layout.publicKey('key'), numKeys, 'keys'),
Layout.publicKey('recentBlockhash'),
]);
const transaction = {
numRequiredSignatures: Buffer.from([this.header.numRequiredSignatures]),
numReadonlySignedAccounts: Buffer.from([
this.header.numReadonlySignedAccounts,
]),
numReadonlyUnsignedAccounts: Buffer.from([
this.header.numReadonlyUnsignedAccounts,
]),
keyCount: Buffer.from(keyCount),
keys: this.accountKeys.map(key => key.toBuffer()),
recentBlockhash: bs58.decode(this.recentBlockhash),
};
let signData = Buffer.alloc(2048);
const length = signDataLayout.encode(transaction, signData);
instructionBuffer.copy(signData, length);
return signData.slice(0, length + instructionBuffer.length);
}
}