fix: multiple transaction instructions are now supported
This commit is contained in:
parent
e50b705de3
commit
f168cdfd70
|
@ -6,8 +6,8 @@ import nacl from 'tweetnacl';
|
|||
import bs58 from 'bs58';
|
||||
|
||||
import * as Layout from './layout';
|
||||
import {PublicKey} from './publickey';
|
||||
import type {Account} from './account';
|
||||
import type {PublicKey} from './publickey';
|
||||
|
||||
/**
|
||||
* @typedef {string} TransactionSignature
|
||||
|
@ -100,16 +100,22 @@ export class Transaction {
|
|||
*/
|
||||
fee: number = 0;
|
||||
|
||||
/**
|
||||
* Construct an empty Transaction
|
||||
*/
|
||||
constructor(opts?: TransactionCtorFields) {
|
||||
opts && Object.assign(this, opts);
|
||||
}
|
||||
|
||||
add(instruction: TransactionInstructionCtorFields): Transaction {
|
||||
if (this.instructions.length !== 0) {
|
||||
throw new Error('Multiple instructions not supported yet');
|
||||
/**
|
||||
* Add instructions to this Transaction
|
||||
*/
|
||||
add(item: Transaction | TransactionInstructionCtorFields): Transaction {
|
||||
if (item instanceof Transaction) {
|
||||
this.instructions = this.instructions.concat(item.instructions);
|
||||
} else {
|
||||
this.instructions.push(new TransactionInstruction(item));
|
||||
}
|
||||
|
||||
this.instructions.push(new TransactionInstruction(instruction));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -122,43 +128,67 @@ export class Transaction {
|
|||
throw new Error('Transaction lastId required');
|
||||
}
|
||||
|
||||
if (this.instructions.length !== 1) {
|
||||
throw new Error('No instruction provided');
|
||||
if (this.instructions.length < 1) {
|
||||
throw new Error('No instructions provided');
|
||||
}
|
||||
|
||||
const {keys, programId, userdata} = this.instructions[0];
|
||||
const programIds = [programId];
|
||||
const instructions = [
|
||||
{
|
||||
programId: 0,
|
||||
accountsLength: keys.length,
|
||||
accounts: [...keys.keys()],
|
||||
userdataLength: userdata.length,
|
||||
const keys = [];
|
||||
const programIds = [];
|
||||
this.instructions.forEach(instruction => {
|
||||
const programId = instruction.programId.toString();
|
||||
if (!programIds.includes(programId)) {
|
||||
programIds.push(programId);
|
||||
}
|
||||
|
||||
instruction.keys
|
||||
.map(key => key.toString())
|
||||
.forEach(key => {
|
||||
if (!keys.includes(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const instructions = this.instructions.map(instruction => {
|
||||
const {userdata, programId} = instruction;
|
||||
return {
|
||||
programIdIndex: programIds.indexOf(programId.toString()),
|
||||
keyIndices: instruction.keys.map(key => keys.indexOf(key.toString())),
|
||||
userdata,
|
||||
},
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
instructions.forEach(instruction => {
|
||||
assert(instruction.programIdIndex >= 0);
|
||||
instruction.keyIndices.forEach(keyIndex => assert(keyIndex >= 0));
|
||||
});
|
||||
|
||||
const instructionLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('programId'),
|
||||
BufferLayout.u8('programIdIndex'),
|
||||
|
||||
BufferLayout.u32('accountsLength'),
|
||||
BufferLayout.u32('accountsLengthPadding'),
|
||||
BufferLayout.u32('keyIndicesLength'),
|
||||
BufferLayout.u32('keyIndicesLengthPadding'),
|
||||
BufferLayout.seq(
|
||||
BufferLayout.u8('account'),
|
||||
BufferLayout.u8('keyIndex'),
|
||||
BufferLayout.offset(BufferLayout.u32(), -8),
|
||||
'accounts'
|
||||
'keyIndices'
|
||||
),
|
||||
BufferLayout.u32('userdataLength'),
|
||||
BufferLayout.u32('userdataLengthPadding'),
|
||||
BufferLayout.seq(
|
||||
BufferLayout.u8('userdatum'),
|
||||
BufferLayout.offset(BufferLayout.u32(), -8),
|
||||
'userdata'
|
||||
),
|
||||
BufferLayout.ns64('userdataLength'),
|
||||
BufferLayout.blob(userdata.length, 'userdata'),
|
||||
]);
|
||||
|
||||
const signDataLayout = BufferLayout.struct([
|
||||
BufferLayout.u32('accountKeysLength'),
|
||||
BufferLayout.u32('accountKeysLengthPadding'),
|
||||
BufferLayout.u32('keysLength'),
|
||||
BufferLayout.u32('keysLengthPadding'),
|
||||
BufferLayout.seq(
|
||||
Layout.publicKey('accountKey'),
|
||||
Layout.publicKey('key'),
|
||||
BufferLayout.offset(BufferLayout.u32(), -8),
|
||||
'accountKeys'
|
||||
'keys'
|
||||
),
|
||||
Layout.publicKey('lastId'),
|
||||
BufferLayout.ns64('fee'),
|
||||
|
@ -181,10 +211,10 @@ export class Transaction {
|
|||
]);
|
||||
|
||||
const transaction = {
|
||||
accountKeys: keys.map((key) => key.toBuffer()),
|
||||
keys: keys.map((key) => (new PublicKey(key)).toBuffer()),
|
||||
lastId: Buffer.from(bs58.decode(lastId)),
|
||||
fee: 0,
|
||||
programIds: programIds.map((programId) => programId.toBuffer()),
|
||||
fee: this.fee,
|
||||
programIds: programIds.map(programId => (new PublicKey(programId)).toBuffer()),
|
||||
instructions,
|
||||
};
|
||||
|
||||
|
|
|
@ -362,3 +362,51 @@ test('transaction', async () => {
|
|||
]);
|
||||
expect(await connection.getBalance(accountTo.publicKey)).toBe(31);
|
||||
});
|
||||
|
||||
|
||||
test('multi-instruction transaction', async () => {
|
||||
if (mockRpcEnabled) {
|
||||
console.log('non-live test skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
const accountFrom = new Account();
|
||||
const accountTo = new Account();
|
||||
const connection = new Connection(url);
|
||||
|
||||
await connection.requestAirdrop(accountFrom.publicKey, 12);
|
||||
expect(await connection.getBalance(accountFrom.publicKey)).toBe(12);
|
||||
|
||||
await connection.requestAirdrop(accountTo.publicKey, 21);
|
||||
expect(await connection.getBalance(accountTo.publicKey)).toBe(21);
|
||||
|
||||
// 1. Move(accountFrom, accountTo)
|
||||
// 2. Move(accountTo, accountFrom)
|
||||
const transaction = SystemProgram.move(
|
||||
accountFrom.publicKey,
|
||||
accountTo.publicKey,
|
||||
10
|
||||
)
|
||||
.add(SystemProgram.move(
|
||||
accountTo.publicKey,
|
||||
accountFrom.publicKey,
|
||||
10
|
||||
));
|
||||
|
||||
const signature = await connection.sendTransaction(accountFrom, transaction);
|
||||
let i = 0;
|
||||
for (;;) {
|
||||
if (await connection.confirmTransaction(signature)) {
|
||||
break;
|
||||
}
|
||||
|
||||
expect(mockRpcEnabled).toBe(false);
|
||||
expect(++i).toBeLessThan(10);
|
||||
await sleep(500);
|
||||
}
|
||||
await expect(connection.getSignatureStatus(signature)).resolves.toBe('Confirmed');
|
||||
|
||||
expect(await connection.getBalance(accountFrom.publicKey)).toBe(12);
|
||||
expect(await connection.getBalance(accountTo.publicKey)).toBe(21);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue