feat: allow setting explicit fee payer for transaction (#13129)
This commit is contained in:
2
web3.js/module.d.ts
vendored
2
web3.js/module.d.ts
vendored
@ -653,7 +653,7 @@ declare module '@solana/web3.js' {
|
|||||||
instructions: Array<TransactionInstruction>;
|
instructions: Array<TransactionInstruction>;
|
||||||
recentBlockhash?: Blockhash;
|
recentBlockhash?: Blockhash;
|
||||||
nonceInfo?: NonceInformation;
|
nonceInfo?: NonceInformation;
|
||||||
feePayer: PublicKey | null;
|
feePayer?: PublicKey;
|
||||||
|
|
||||||
constructor(opts?: TransactionCtorFields);
|
constructor(opts?: TransactionCtorFields);
|
||||||
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction;
|
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction;
|
||||||
|
@ -657,7 +657,7 @@ declare module '@solana/web3.js' {
|
|||||||
instructions: Array<TransactionInstruction>;
|
instructions: Array<TransactionInstruction>;
|
||||||
recentBlockhash: ?Blockhash;
|
recentBlockhash: ?Blockhash;
|
||||||
nonceInfo: ?NonceInformation;
|
nonceInfo: ?NonceInformation;
|
||||||
feePayer: PublicKey | null;
|
feePayer: ?PublicKey;
|
||||||
|
|
||||||
constructor(opts?: TransactionCtorFields): Transaction;
|
constructor(opts?: TransactionCtorFields): Transaction;
|
||||||
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction;
|
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction;
|
||||||
|
@ -83,16 +83,6 @@ export class Message {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
findSignerIndex(signer: PublicKey): number {
|
|
||||||
const index = this.accountKeys.findIndex(accountKey => {
|
|
||||||
return accountKey.equals(signer);
|
|
||||||
});
|
|
||||||
if (index < 0) {
|
|
||||||
throw new Error(`unknown signer: ${signer.toString()}`);
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(): Buffer {
|
serialize(): Buffer {
|
||||||
const numKeys = this.accountKeys.length;
|
const numKeys = this.accountKeys.length;
|
||||||
|
|
||||||
|
@ -112,12 +112,14 @@ type SignaturePubkeyPair = {|
|
|||||||
*
|
*
|
||||||
* @typedef {Object} TransactionCtorFields
|
* @typedef {Object} TransactionCtorFields
|
||||||
* @property {?Blockhash} recentBlockhash A recent blockhash
|
* @property {?Blockhash} recentBlockhash A recent blockhash
|
||||||
|
* @property {?PublicKey} feePayer The transaction fee payer
|
||||||
* @property {?Array<SignaturePubkeyPair>} signatures One or more signatures
|
* @property {?Array<SignaturePubkeyPair>} signatures One or more signatures
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
type TransactionCtorFields = {|
|
type TransactionCtorFields = {|
|
||||||
recentBlockhash?: Blockhash | null,
|
recentBlockhash?: Blockhash | null,
|
||||||
nonceInfo?: NonceInformation | null,
|
nonceInfo?: NonceInformation | null,
|
||||||
|
feePayer?: PublicKey | null,
|
||||||
signatures?: Array<SignaturePubkeyPair>,
|
signatures?: Array<SignaturePubkeyPair>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
@ -154,14 +156,9 @@ export class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transaction fee payer (first signer)
|
* The transaction fee payer
|
||||||
*/
|
*/
|
||||||
get feePayer(): PublicKey | null {
|
feePayer: ?PublicKey;
|
||||||
if (this.signatures.length > 0) {
|
|
||||||
return this.signatures[0].publicKey;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The instructions to atomically execute
|
* The instructions to atomically execute
|
||||||
@ -171,13 +168,13 @@ export class Transaction {
|
|||||||
/**
|
/**
|
||||||
* A recent transaction id. Must be populated by the caller
|
* A recent transaction id. Must be populated by the caller
|
||||||
*/
|
*/
|
||||||
recentBlockhash: Blockhash | null;
|
recentBlockhash: ?Blockhash;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional Nonce information. If populated, transaction will use a durable
|
* Optional Nonce information. If populated, transaction will use a durable
|
||||||
* Nonce hash instead of a recentBlockhash. Must be populated by the caller
|
* Nonce hash instead of a recentBlockhash. Must be populated by the caller
|
||||||
*/
|
*/
|
||||||
nonceInfo: NonceInformation | null;
|
nonceInfo: ?NonceInformation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an empty Transaction
|
* Construct an empty Transaction
|
||||||
@ -228,8 +225,14 @@ export class Transaction {
|
|||||||
throw new Error('No instructions provided');
|
throw new Error('No instructions provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.feePayer === null) {
|
let feePayer: PublicKey;
|
||||||
throw new Error('Transaction feePayer required');
|
if (this.feePayer) {
|
||||||
|
feePayer = this.feePayer;
|
||||||
|
} else if (this.signatures.length > 0 && this.signatures[0].publicKey) {
|
||||||
|
// Use implicit fee payer
|
||||||
|
feePayer = this.signatures[0].publicKey;
|
||||||
|
} else {
|
||||||
|
throw new Error('Transaction fee payer required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const programIds: string[] = [];
|
const programIds: string[] = [];
|
||||||
@ -277,31 +280,41 @@ export class Transaction {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Move payer to the front and disallow unknown signers
|
// Move fee payer to the front
|
||||||
this.signatures.forEach((signature, signatureIndex) => {
|
const feePayerIndex = uniqueMetas.findIndex(x => {
|
||||||
const isPayer = signatureIndex === 0;
|
return x.pubkey.equals(feePayer);
|
||||||
const uniqueIndex = uniqueMetas.findIndex(x => {
|
|
||||||
return x.pubkey.equals(signature.publicKey);
|
|
||||||
});
|
});
|
||||||
if (uniqueIndex > -1) {
|
if (feePayerIndex > -1) {
|
||||||
if (isPayer) {
|
const [payerMeta] = uniqueMetas.splice(feePayerIndex, 1);
|
||||||
const [payerMeta] = uniqueMetas.splice(uniqueIndex, 1);
|
|
||||||
payerMeta.isSigner = true;
|
payerMeta.isSigner = true;
|
||||||
payerMeta.isWritable = true;
|
payerMeta.isWritable = true;
|
||||||
uniqueMetas.unshift(payerMeta);
|
uniqueMetas.unshift(payerMeta);
|
||||||
} else {
|
} else {
|
||||||
uniqueMetas[uniqueIndex].isSigner = true;
|
|
||||||
}
|
|
||||||
} else if (isPayer) {
|
|
||||||
uniqueMetas.unshift({
|
uniqueMetas.unshift({
|
||||||
pubkey: signature.publicKey,
|
pubkey: feePayer,
|
||||||
isSigner: true,
|
isSigner: true,
|
||||||
isWritable: true,
|
isWritable: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow unknown signers
|
||||||
|
for (const signature of this.signatures) {
|
||||||
|
const uniqueIndex = uniqueMetas.findIndex(x => {
|
||||||
|
return x.pubkey.equals(signature.publicKey);
|
||||||
|
});
|
||||||
|
if (uniqueIndex > -1) {
|
||||||
|
if (!uniqueMetas[uniqueIndex].isSigner) {
|
||||||
|
uniqueMetas[uniqueIndex].isSigner = true;
|
||||||
|
console.warn(
|
||||||
|
'Transaction references a signature that is unnecessary, ' +
|
||||||
|
'only the fee payer and instruction signer accounts should sign a transaction. ' +
|
||||||
|
'This behavior is deprecated and will throw an error in the next major version release.',
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unknown signer: ${signature.publicKey.toString()}`);
|
throw new Error(`unknown signer: ${signature.publicKey.toString()}`);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
let numRequiredSignatures = 0;
|
let numRequiredSignatures = 0;
|
||||||
let numReadonlySignedAccounts = 0;
|
let numReadonlySignedAccounts = 0;
|
||||||
@ -325,18 +338,14 @@ export class Transaction {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (numRequiredSignatures !== this.signatures.length) {
|
|
||||||
throw new Error('missing signer(s)');
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountKeys = signedKeys.concat(unsignedKeys);
|
const accountKeys = signedKeys.concat(unsignedKeys);
|
||||||
const instructions: CompiledInstruction[] = this.instructions.map(
|
const instructions: CompiledInstruction[] = this.instructions.map(
|
||||||
instruction => {
|
instruction => {
|
||||||
const {data, programId} = instruction;
|
const {data, programId} = instruction;
|
||||||
return {
|
return {
|
||||||
programIdIndex: accountKeys.indexOf(programId.toString()),
|
programIdIndex: accountKeys.indexOf(programId.toString()),
|
||||||
accounts: instruction.keys.map(keyObj =>
|
accounts: instruction.keys.map(meta =>
|
||||||
accountKeys.indexOf(keyObj.pubkey.toString()),
|
accountKeys.indexOf(meta.pubkey.toString()),
|
||||||
),
|
),
|
||||||
data: bs58.encode(data),
|
data: bs58.encode(data),
|
||||||
};
|
};
|
||||||
@ -360,11 +369,37 @@ export class Transaction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_compile(): Message {
|
||||||
|
const message = this.compileMessage();
|
||||||
|
const signedKeys = message.accountKeys.slice(
|
||||||
|
0,
|
||||||
|
message.header.numRequiredSignatures,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.signatures.length === signedKeys.length) {
|
||||||
|
const valid = this.signatures.every((pair, index) => {
|
||||||
|
return signedKeys[index].equals(pair.publicKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (valid) return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.signatures = signedKeys.map(publicKey => ({
|
||||||
|
signature: null,
|
||||||
|
publicKey,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a buffer of the Transaction data that need to be covered by signatures
|
* Get a buffer of the Transaction data that need to be covered by signatures
|
||||||
*/
|
*/
|
||||||
serializeMessage(): Buffer {
|
serializeMessage(): Buffer {
|
||||||
return this.compileMessage().serialize();
|
return this._compile().serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -372,6 +407,10 @@ export class Transaction {
|
|||||||
* The first signer will be used as the transaction fee payer account.
|
* The first signer will be used as the transaction fee payer account.
|
||||||
*
|
*
|
||||||
* Signatures can be added with either `partialSign` or `addSignature`
|
* Signatures can be added with either `partialSign` or `addSignature`
|
||||||
|
*
|
||||||
|
* @deprecated Deprecated since v0.84.0. Only the fee payer needs to be
|
||||||
|
* specified and it can be set in the Transaction constructor or with the
|
||||||
|
* `feePayer` property.
|
||||||
*/
|
*/
|
||||||
setSigners(...signers: Array<PublicKey>) {
|
setSigners(...signers: Array<PublicKey>) {
|
||||||
if (signers.length === 0) {
|
if (signers.length === 0) {
|
||||||
@ -395,8 +434,10 @@ export class Transaction {
|
|||||||
/**
|
/**
|
||||||
* Sign the Transaction with the specified accounts. Multiple signatures may
|
* Sign the Transaction with the specified accounts. Multiple signatures may
|
||||||
* be applied to a Transaction. The first signature is considered "primary"
|
* be applied to a Transaction. The first signature is considered "primary"
|
||||||
* and is used when testing for Transaction confirmation. The first signer
|
* and is used identify and confirm transactions.
|
||||||
* will be used as the transaction fee payer account.
|
*
|
||||||
|
* If the Transaction `feePayer` is not set, the first signer will be used
|
||||||
|
* as the transaction fee payer account.
|
||||||
*
|
*
|
||||||
* Transaction fields should not be modified after the first call to `sign`,
|
* Transaction fields should not be modified after the first call to `sign`,
|
||||||
* as doing so may invalidate the signature and cause the Transaction to be
|
* as doing so may invalidate the signature and cause the Transaction to be
|
||||||
@ -409,28 +450,33 @@ export class Transaction {
|
|||||||
throw new Error('No signers');
|
throw new Error('No signers');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dedupe signers
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
this.signatures = signers
|
const uniqueSigners = [];
|
||||||
.filter(signer => {
|
for (const signer of signers) {
|
||||||
const key = signer.publicKey.toString();
|
const key = signer.publicKey.toString();
|
||||||
if (seen.has(key)) {
|
if (seen.has(key)) {
|
||||||
return false;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
return true;
|
uniqueSigners.push(signer);
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.map(signer => ({
|
|
||||||
|
this.signatures = uniqueSigners.map(signer => ({
|
||||||
signature: null,
|
signature: null,
|
||||||
publicKey: signer.publicKey,
|
publicKey: signer.publicKey,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.partialSign(...signers);
|
const message = this._compile();
|
||||||
|
this._partialSign(message, ...uniqueSigners);
|
||||||
|
this._verifySignatures(message.serialize(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partially sign a transaction with the specified accounts. All accounts must
|
* Partially sign a transaction with the specified accounts. All accounts must
|
||||||
* correspond to a public key that was previously provided to `setSigners`.
|
* correspond to either the fee payer or a signer account in the transaction
|
||||||
|
* instructions.
|
||||||
*
|
*
|
||||||
* All the caveats from the `sign` method apply to `partialSign`
|
* All the caveats from the `sign` method apply to `partialSign`
|
||||||
*/
|
*/
|
||||||
@ -439,25 +485,48 @@ export class Transaction {
|
|||||||
throw new Error('No signers');
|
throw new Error('No signers');
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = this.compileMessage();
|
// Dedupe signers
|
||||||
this.signatures.sort(function (x, y) {
|
const seen = new Set();
|
||||||
const xIndex = message.findSignerIndex(x.publicKey);
|
const uniqueSigners = [];
|
||||||
const yIndex = message.findSignerIndex(y.publicKey);
|
for (const signer of signers) {
|
||||||
return xIndex < yIndex ? -1 : 1;
|
const key = signer.publicKey.toString();
|
||||||
});
|
if (seen.has(key)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
seen.add(key);
|
||||||
|
uniqueSigners.push(signer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = this._compile();
|
||||||
|
this._partialSign(message, ...uniqueSigners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_partialSign(message: Message, ...signers: Array<Account>) {
|
||||||
const signData = message.serialize();
|
const signData = message.serialize();
|
||||||
signers.forEach(signer => {
|
signers.forEach(signer => {
|
||||||
const signature = nacl.sign.detached(signData, signer.secretKey);
|
const signature = nacl.sign.detached(signData, signer.secretKey);
|
||||||
this.addSignature(signer.publicKey, signature);
|
this._addSignature(signer.publicKey, signature);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an externally created signature to a transaction. The public key
|
* Add an externally created signature to a transaction. The public key
|
||||||
* must correspond to a public key that was previously provided to `setSigners`.
|
* must correspond to either the fee payer or a signer account in the transaction
|
||||||
|
* instructions.
|
||||||
*/
|
*/
|
||||||
addSignature(pubkey: PublicKey, signature: Buffer) {
|
addSignature(pubkey: PublicKey, signature: Buffer) {
|
||||||
|
this._compile(); // Ensure signatures array is populated
|
||||||
|
this._addSignature(pubkey, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_addSignature(pubkey: PublicKey, signature: Buffer) {
|
||||||
invariant(signature.length === 64);
|
invariant(signature.length === 64);
|
||||||
|
|
||||||
const index = this.signatures.findIndex(sigpair =>
|
const index = this.signatures.findIndex(sigpair =>
|
||||||
@ -600,6 +669,9 @@ export class Transaction {
|
|||||||
static populate(message: Message, signatures: Array<string>): Transaction {
|
static populate(message: Message, signatures: Array<string>): Transaction {
|
||||||
const transaction = new Transaction();
|
const transaction = new Transaction();
|
||||||
transaction.recentBlockhash = message.recentBlockhash;
|
transaction.recentBlockhash = message.recentBlockhash;
|
||||||
|
if (message.header.numRequiredSignatures > 0) {
|
||||||
|
transaction.feePayer = message.accountKeys[0];
|
||||||
|
}
|
||||||
signatures.forEach((signature, index) => {
|
signatures.forEach((signature, index) => {
|
||||||
const sigPubkeyPair = {
|
const sigPubkeyPair = {
|
||||||
signature:
|
signature:
|
||||||
|
@ -176,7 +176,7 @@ describe('load BPF Rust program', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('simulate transaction without signature verification', async () => {
|
test('deprecated - simulate transaction without signature verification', async () => {
|
||||||
const simulatedTransaction = new Transaction().add({
|
const simulatedTransaction = new Transaction().add({
|
||||||
keys: [
|
keys: [
|
||||||
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
|
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
|
||||||
@ -202,6 +202,33 @@ describe('load BPF Rust program', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('simulate transaction without signature verification', async () => {
|
||||||
|
const simulatedTransaction = new Transaction({
|
||||||
|
feePayer: payerAccount.publicKey,
|
||||||
|
}).add({
|
||||||
|
keys: [
|
||||||
|
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
|
||||||
|
],
|
||||||
|
programId: program.publicKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {err, logs} = (
|
||||||
|
await connection.simulateTransaction(simulatedTransaction)
|
||||||
|
).value;
|
||||||
|
expect(err).toBeNull();
|
||||||
|
|
||||||
|
if (logs === null) {
|
||||||
|
expect(logs).not.toBeNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(logs.length).toBeGreaterThanOrEqual(2);
|
||||||
|
expect(logs[0]).toEqual(`Call BPF program ${program.publicKey.toBase58()}`);
|
||||||
|
expect(logs[logs.length - 1]).toEqual(
|
||||||
|
`BPF program ${program.publicKey.toBase58()} success`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('simulate transaction with bad programId', async () => {
|
test('simulate transaction with bad programId', async () => {
|
||||||
const simulatedTransaction = new Transaction().add({
|
const simulatedTransaction = new Transaction().add({
|
||||||
keys: [
|
keys: [
|
||||||
|
@ -198,8 +198,8 @@ test('get program accounts', async () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (transaction.recentBlockhash === null) {
|
if (!transaction.recentBlockhash) {
|
||||||
expect(transaction.recentBlockhash).not.toBeNull();
|
expect(transaction.recentBlockhash).toBeTruthy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,19 +86,22 @@ describe('compileMessage', () => {
|
|||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
transaction.compileMessage();
|
transaction.compileMessage();
|
||||||
}).toThrow('Transaction feePayer required');
|
}).toThrow('Transaction fee payer required');
|
||||||
|
|
||||||
transaction.setSigners(payer.publicKey);
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
transaction.compileMessage();
|
|
||||||
}).toThrow('missing signer');
|
|
||||||
|
|
||||||
transaction.setSigners(payer.publicKey, new Account().publicKey);
|
transaction.setSigners(payer.publicKey, new Account().publicKey);
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
transaction.compileMessage();
|
transaction.compileMessage();
|
||||||
}).toThrow('unknown signer');
|
}).toThrow('unknown signer');
|
||||||
|
|
||||||
|
// Expect compile to succeed with implicit fee payer from signers
|
||||||
|
transaction.setSigners(payer.publicKey);
|
||||||
|
transaction.compileMessage();
|
||||||
|
|
||||||
|
// Expect compile to succeed with fee payer and no signers
|
||||||
|
transaction.signatures = [];
|
||||||
|
transaction.feePayer = payer.publicKey;
|
||||||
|
transaction.compileMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('payer is writable', () => {
|
test('payer is writable', () => {
|
||||||
@ -260,6 +263,7 @@ test('transfer signatures', () => {
|
|||||||
|
|
||||||
const newTransaction = new Transaction({
|
const newTransaction = new Transaction({
|
||||||
recentBlockhash: orgTransaction.recentBlockhash,
|
recentBlockhash: orgTransaction.recentBlockhash,
|
||||||
|
feePayer: orgTransaction.feePayer,
|
||||||
signatures: orgTransaction.signatures,
|
signatures: orgTransaction.signatures,
|
||||||
}).add(transfer1, transfer2);
|
}).add(transfer1, transfer2);
|
||||||
|
|
||||||
@ -354,7 +358,10 @@ test('parse wire format and serialize', () => {
|
|||||||
toPubkey: recipient,
|
toPubkey: recipient,
|
||||||
lamports: 49,
|
lamports: 49,
|
||||||
});
|
});
|
||||||
const expectedTransaction = new Transaction({recentBlockhash}).add(transfer);
|
const expectedTransaction = new Transaction({
|
||||||
|
recentBlockhash,
|
||||||
|
feePayer: sender.publicKey,
|
||||||
|
}).add(transfer);
|
||||||
expectedTransaction.sign(sender);
|
expectedTransaction.sign(sender);
|
||||||
|
|
||||||
const wireTransaction = Buffer.from(
|
const wireTransaction = Buffer.from(
|
||||||
@ -423,21 +430,38 @@ test('serialize unsigned transaction', () => {
|
|||||||
expect(expectedTransaction.signatures.length).toBe(0);
|
expect(expectedTransaction.signatures.length).toBe(0);
|
||||||
expect(() => {
|
expect(() => {
|
||||||
expectedTransaction.serialize();
|
expectedTransaction.serialize();
|
||||||
}).toThrow(Error);
|
}).toThrow('Transaction fee payer required');
|
||||||
expect(() => {
|
expect(() => {
|
||||||
expectedTransaction.serialize({verifySignatures: false});
|
expectedTransaction.serialize({verifySignatures: false});
|
||||||
}).toThrow(Error);
|
}).toThrow('Transaction fee payer required');
|
||||||
expect(() => {
|
expect(() => {
|
||||||
expectedTransaction.serializeMessage();
|
expectedTransaction.serializeMessage();
|
||||||
}).toThrow('Transaction feePayer required');
|
}).toThrow('Transaction fee payer required');
|
||||||
|
|
||||||
|
expectedTransaction.feePayer = sender.publicKey;
|
||||||
|
|
||||||
|
// Transactions with missing signatures will fail sigverify.
|
||||||
|
expect(() => {
|
||||||
|
expectedTransaction.serialize();
|
||||||
|
}).toThrow('Signature verification failed');
|
||||||
|
|
||||||
|
// Serializing without signatures is allowed if sigverify disabled.
|
||||||
|
expectedTransaction.serialize({verifySignatures: false});
|
||||||
|
|
||||||
|
// Serializing the message is allowed when signature array has null signatures
|
||||||
|
expectedTransaction.serializeMessage();
|
||||||
|
|
||||||
|
expectedTransaction.feePayer = null;
|
||||||
expectedTransaction.setSigners(sender.publicKey);
|
expectedTransaction.setSigners(sender.publicKey);
|
||||||
expect(expectedTransaction.signatures.length).toBe(1);
|
expect(expectedTransaction.signatures.length).toBe(1);
|
||||||
|
|
||||||
// Signature array populated with null signatures fails.
|
// Transactions with missing signatures will fail sigverify.
|
||||||
expect(() => {
|
expect(() => {
|
||||||
expectedTransaction.serialize();
|
expectedTransaction.serialize();
|
||||||
}).toThrow(Error);
|
}).toThrow('Signature verification failed');
|
||||||
|
|
||||||
|
// Serializing without signatures is allowed if sigverify disabled.
|
||||||
|
expectedTransaction.serialize({verifySignatures: false});
|
||||||
|
|
||||||
// Serializing the message is allowed when signature array has null signatures
|
// Serializing the message is allowed when signature array has null signatures
|
||||||
expectedTransaction.serializeMessage();
|
expectedTransaction.serializeMessage();
|
||||||
@ -468,7 +492,7 @@ test('serialize unsigned transaction', () => {
|
|||||||
expect(expectedTransaction.signatures.length).toBe(1);
|
expect(expectedTransaction.signatures.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('externally signed stake delegate', () => {
|
test('deprecated - externally signed stake delegate', () => {
|
||||||
const from_keypair = nacl.sign.keyPair.fromSeed(
|
const from_keypair = nacl.sign.keyPair.fromSeed(
|
||||||
Uint8Array.from(Array(32).fill(1)),
|
Uint8Array.from(Array(32).fill(1)),
|
||||||
);
|
);
|
||||||
@ -489,3 +513,25 @@ test('externally signed stake delegate', () => {
|
|||||||
tx.addSignature(from.publicKey, signature);
|
tx.addSignature(from.publicKey, signature);
|
||||||
expect(tx.verifySignatures()).toBe(true);
|
expect(tx.verifySignatures()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('externally signed stake delegate', () => {
|
||||||
|
const from_keypair = nacl.sign.keyPair.fromSeed(
|
||||||
|
Uint8Array.from(Array(32).fill(1)),
|
||||||
|
);
|
||||||
|
const authority = new Account(Buffer.from(from_keypair.secretKey));
|
||||||
|
const stake = new PublicKey(2);
|
||||||
|
const recentBlockhash = new PublicKey(3).toBuffer();
|
||||||
|
const vote = new PublicKey(4);
|
||||||
|
var tx = StakeProgram.delegate({
|
||||||
|
stakePubkey: stake,
|
||||||
|
authorizedPubkey: authority.publicKey,
|
||||||
|
votePubkey: vote,
|
||||||
|
});
|
||||||
|
const from = authority;
|
||||||
|
tx.recentBlockhash = bs58.encode(recentBlockhash);
|
||||||
|
tx.feePayer = from.publicKey;
|
||||||
|
const tx_bytes = tx.serializeMessage();
|
||||||
|
const signature = nacl.sign.detached(tx_bytes, from.secretKey);
|
||||||
|
tx.addSignature(from.publicKey, signature);
|
||||||
|
expect(tx.verifySignatures()).toBe(true);
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user