fix: multiple transaction instructions are now supported
This commit is contained in:
@ -6,8 +6,8 @@ import nacl from 'tweetnacl';
|
|||||||
import bs58 from 'bs58';
|
import bs58 from 'bs58';
|
||||||
|
|
||||||
import * as Layout from './layout';
|
import * as Layout from './layout';
|
||||||
|
import {PublicKey} from './publickey';
|
||||||
import type {Account} from './account';
|
import type {Account} from './account';
|
||||||
import type {PublicKey} from './publickey';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {string} TransactionSignature
|
* @typedef {string} TransactionSignature
|
||||||
@ -100,16 +100,22 @@ export class Transaction {
|
|||||||
*/
|
*/
|
||||||
fee: number = 0;
|
fee: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an empty Transaction
|
||||||
|
*/
|
||||||
constructor(opts?: TransactionCtorFields) {
|
constructor(opts?: TransactionCtorFields) {
|
||||||
opts && Object.assign(this, opts);
|
opts && Object.assign(this, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(instruction: TransactionInstructionCtorFields): Transaction {
|
/**
|
||||||
if (this.instructions.length !== 0) {
|
* Add instructions to this Transaction
|
||||||
throw new Error('Multiple instructions not supported yet');
|
*/
|
||||||
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,43 +128,67 @@ export class Transaction {
|
|||||||
throw new Error('Transaction lastId required');
|
throw new Error('Transaction lastId required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.instructions.length !== 1) {
|
if (this.instructions.length < 1) {
|
||||||
throw new Error('No instruction provided');
|
throw new Error('No instructions provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const {keys, programId, userdata} = this.instructions[0];
|
const keys = [];
|
||||||
const programIds = [programId];
|
const programIds = [];
|
||||||
const instructions = [
|
this.instructions.forEach(instruction => {
|
||||||
{
|
const programId = instruction.programId.toString();
|
||||||
programId: 0,
|
if (!programIds.includes(programId)) {
|
||||||
accountsLength: keys.length,
|
programIds.push(programId);
|
||||||
accounts: [...keys.keys()],
|
}
|
||||||
userdataLength: userdata.length,
|
|
||||||
|
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,
|
userdata,
|
||||||
},
|
};
|
||||||
];
|
});
|
||||||
|
|
||||||
|
instructions.forEach(instruction => {
|
||||||
|
assert(instruction.programIdIndex >= 0);
|
||||||
|
instruction.keyIndices.forEach(keyIndex => assert(keyIndex >= 0));
|
||||||
|
});
|
||||||
|
|
||||||
const instructionLayout = BufferLayout.struct([
|
const instructionLayout = BufferLayout.struct([
|
||||||
BufferLayout.u8('programId'),
|
BufferLayout.u8('programIdIndex'),
|
||||||
|
|
||||||
BufferLayout.u32('accountsLength'),
|
BufferLayout.u32('keyIndicesLength'),
|
||||||
BufferLayout.u32('accountsLengthPadding'),
|
BufferLayout.u32('keyIndicesLengthPadding'),
|
||||||
BufferLayout.seq(
|
BufferLayout.seq(
|
||||||
BufferLayout.u8('account'),
|
BufferLayout.u8('keyIndex'),
|
||||||
BufferLayout.offset(BufferLayout.u32(), -8),
|
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([
|
const signDataLayout = BufferLayout.struct([
|
||||||
BufferLayout.u32('accountKeysLength'),
|
BufferLayout.u32('keysLength'),
|
||||||
BufferLayout.u32('accountKeysLengthPadding'),
|
BufferLayout.u32('keysLengthPadding'),
|
||||||
BufferLayout.seq(
|
BufferLayout.seq(
|
||||||
Layout.publicKey('accountKey'),
|
Layout.publicKey('key'),
|
||||||
BufferLayout.offset(BufferLayout.u32(), -8),
|
BufferLayout.offset(BufferLayout.u32(), -8),
|
||||||
'accountKeys'
|
'keys'
|
||||||
),
|
),
|
||||||
Layout.publicKey('lastId'),
|
Layout.publicKey('lastId'),
|
||||||
BufferLayout.ns64('fee'),
|
BufferLayout.ns64('fee'),
|
||||||
@ -181,10 +211,10 @@ export class Transaction {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const transaction = {
|
const transaction = {
|
||||||
accountKeys: keys.map((key) => key.toBuffer()),
|
keys: keys.map((key) => (new PublicKey(key)).toBuffer()),
|
||||||
lastId: Buffer.from(bs58.decode(lastId)),
|
lastId: Buffer.from(bs58.decode(lastId)),
|
||||||
fee: 0,
|
fee: this.fee,
|
||||||
programIds: programIds.map((programId) => programId.toBuffer()),
|
programIds: programIds.map(programId => (new PublicKey(programId)).toBuffer()),
|
||||||
instructions,
|
instructions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -362,3 +362,51 @@ test('transaction', async () => {
|
|||||||
]);
|
]);
|
||||||
expect(await connection.getBalance(accountTo.publicKey)).toBe(31);
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user