Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wallet tx management #8246

Merged
merged 15 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions noir-projects/aztec-nr/authwit/src/account.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::aztec::{
context::PrivateContext, protocol_types::constants::GENERATOR_INDEX__COMBINED_PAYLOAD,
context::PrivateContext,
protocol_types::constants::{GENERATOR_INDEX__COMBINED_PAYLOAD, GENERATOR_INDEX__TX_NULLIFIER},
hash::poseidon2_hash_with_separator
};

Expand Down Expand Up @@ -34,7 +35,7 @@ impl AccountActions<&mut PrivateContext> {
* @param fee_payload The payload that contains the calls to be executed in the setup phase.
*/
// docs:start:entrypoint
pub fn entrypoint(self, app_payload: AppPayload, fee_payload: FeePayload) {
pub fn entrypoint(self, app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) {
let valid_fn = self.is_valid_impl;

let combined_payload_hash = poseidon2_hash_with_separator(
Expand All @@ -46,6 +47,10 @@ impl AccountActions<&mut PrivateContext> {
fee_payload.execute_calls(self.context);
self.context.end_setup();
app_payload.execute_calls(self.context);
if cancellable {
let tx_nullifier = poseidon2_hash_with_separator([app_payload.nonce], GENERATOR_INDEX__TX_NULLIFIER);
self.context.push_nullifier(tx_nullifier);
}
}
// docs:end:entrypoint

Expand Down
1 change: 0 additions & 1 deletion noir-projects/noir-contracts/aztec

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ contract EcdsaKAccount {

// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts
#[aztec(private)]
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) {
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) {
let actions = AccountActions::init(&mut context, is_valid_impl);
actions.entrypoint(app_payload, fee_payload);
actions.entrypoint(app_payload, fee_payload, cancellable);
}

#[aztec(private)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ contract EcdsaRAccount {

// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts
#[aztec(private)]
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) {
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) {
let actions = AccountActions::init(&mut context, is_valid_impl);
actions.entrypoint(app_payload, fee_payload);
actions.entrypoint(app_payload, fee_payload, cancellable);
}

#[aztec(private)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ contract SchnorrAccount {
// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts file
#[aztec(private)]
#[aztec(noinitcheck)]
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) {
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) {
let actions = AccountActions::init(&mut context, is_valid_impl);
actions.entrypoint(app_payload, fee_payload);
actions.entrypoint(app_payload, fee_payload, cancellable);
}

#[aztec(private)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ contract SchnorrHardcodedAccount {

// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts
#[aztec(private)]
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) {
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) {
let actions = AccountActions::init(&mut context, is_valid_impl);
actions.entrypoint(app_payload, fee_payload);
actions.entrypoint(app_payload, fee_payload, cancellable);
}

#[aztec(private)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ contract SchnorrSingleKeyAccount {

// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts
sklppy88 marked this conversation as resolved.
Show resolved Hide resolved
#[aztec(private)]
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) {
fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) {
let actions = AccountActions::init(&mut context, is_valid_impl);
actions.entrypoint(app_payload, fee_payload);
actions.entrypoint(app_payload, fee_payload, cancellable);
}

#[aztec(private)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ global GENERATOR_INDEX__BLOCK_HASH: u32 = 28;
global GENERATOR_INDEX__SIDE_EFFECT: u32 = 29;
global GENERATOR_INDEX__FEE_PAYLOAD: u32 = 30;
global GENERATOR_INDEX__COMBINED_PAYLOAD: u32 = 31;
global GENERATOR_INDEX__TX_NULLIFIER: u32 = 32;
// Indices with size ≤ 16
global GENERATOR_INDEX__TX_REQUEST: u32 = 33;
global GENERATOR_INDEX__SIGNATURE_PAYLOAD: u32 = 34;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class DeployAccountMethod extends DeployMethod {
exec.calls.push({
name: this.#feePaymentArtifact.name,
to: address,
args: encodeArguments(this.#feePaymentArtifact, [emptyAppPayload, feePayload]),
args: encodeArguments(this.#feePaymentArtifact, [emptyAppPayload, feePayload, false]),
selector: FunctionSelector.fromNameAndParameters(
this.#feePaymentArtifact.name,
this.#feePaymentArtifact.parameters,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Tx, type TxExecutionRequest } from '@aztec/circuit-types';
import { GasSettings } from '@aztec/circuits.js';
import { type Fr, GasSettings } from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';

import { type Wallet } from '../account/wallet.js';
Expand All @@ -18,6 +18,10 @@ export type SendMethodOptions = {
fee?: FeeOptions;
/** Whether to run an initial simulation of the tx with high gas limit to figure out actual gas settings (will default to true later down the road). */
estimateGas?: boolean;
/** Custom nonce to inject into the app payload of the transaction. Useful when trying to cancel an ongoing transaction by creating a new one with a higher fee */
nonce?: Fr;
/** Whether the transaction can be cancelled. If true, an extra nullifier will be emitted: H(nonce, GENERATOR_INDEX__TX_NULLIFIER) */
cancellable?: boolean;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ export class ContractFunctionInteraction extends BaseContractInteraction {
if (!this.txRequest) {
const calls = [this.request()];
const fee = opts?.estimateGas ? await this.getFeeOptionsFromEstimatedGas({ calls, fee: opts?.fee }) : opts?.fee;
this.txRequest = await this.wallet.createTxExecutionRequest({ calls, fee });
this.txRequest = await this.wallet.createTxExecutionRequest({
calls,
fee,
nonce: opts?.nonce,
cancellable: opts?.cancellable,
});
}
return this.txRequest;
}
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/aztec.js/src/entrypoint/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type AuthWitness, type FunctionCall, type PackedValues, type TxExecutionRequest } from '@aztec/circuit-types';
import { type Fr } from '@aztec/circuits.js';

import { EntrypointPayload, type FeeOptions, computeCombinedPayloadHash } from './payload.js';

Expand All @@ -17,6 +18,10 @@ export type ExecutionRequestInit = {
packedArguments?: PackedValues[];
/** How the fee is going to be payed */
fee?: FeeOptions;
/** An optional nonce. Used to repeat a previous tx with a higher fee so that the first one is cancelled */
nonce?: Fr;
/** Whether the transaction can be cancelled. If true, an extra nullifier will be emitted: H(nonce, GENERATOR_INDEX__TX_NULLIFIER) */
cancellable?: boolean;
};

/** Creates transaction execution requests out of a set of function calls. */
Expand Down
10 changes: 6 additions & 4 deletions yarn-project/aztec.js/src/entrypoint/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ type EncodedFunctionCall = {
export abstract class EntrypointPayload {
#packedArguments: PackedValues[] = [];
#functionCalls: EncodedFunctionCall[] = [];
#nonce = Fr.random();
#nonce: Fr;
#generatorIndex: number;

protected constructor(functionCalls: FunctionCall[], generatorIndex: number) {
protected constructor(functionCalls: FunctionCall[], generatorIndex: number, nonce = Fr.random()) {
for (const call of functionCalls) {
this.#packedArguments.push(PackedValues.fromValues(call.args));
}
Expand All @@ -62,6 +62,7 @@ export abstract class EntrypointPayload {
/* eslint-enable camelcase */

this.#generatorIndex = generatorIndex;
this.#nonce = nonce;
}

/* eslint-disable camelcase */
Expand Down Expand Up @@ -126,14 +127,15 @@ export abstract class EntrypointPayload {
/**
* Creates an execution payload for the app-portion of a transaction from a set of function calls
* @param functionCalls - The function calls to execute
* @param nonce - The nonce for the payload, used to emit a nullifier identifying the call
* @returns The execution payload
*/
static fromAppExecution(functionCalls: FunctionCall[] | Tuple<FunctionCall, 4>) {
static fromAppExecution(functionCalls: FunctionCall[] | Tuple<FunctionCall, 4>, nonce = Fr.random()) {
if (functionCalls.length > APP_MAX_CALLS) {
throw new Error(`Expected at most ${APP_MAX_CALLS} function calls, got ${functionCalls.length}`);
}
const paddedCalls = padArrayEnd(functionCalls, FunctionCall.empty(), APP_MAX_CALLS);
return new AppEntrypointPayload(paddedCalls, GeneratorIndex.SIGNATURE_PAYLOAD);
return new AppEntrypointPayload(paddedCalls, GeneratorIndex.SIGNATURE_PAYLOAD, nonce);
}

/**
Expand Down
1 change: 1 addition & 0 deletions yarn-project/cli-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"@aztec/ethereum": "workspace:^",
"@aztec/foundation": "workspace:^",
"@aztec/kv-store": "workspace:^",
"@aztec/noir-contracts.js": "workspace:^",
"commander": "^12.1.0",
"inquirer": "^10.1.8",
"source-map-support": "^0.5.21",
Expand Down
52 changes: 52 additions & 0 deletions yarn-project/cli-wallet/src/cmds/cancel_tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { type AccountWalletWithSecretKey, type FeePaymentMethod, SentTx, type TxHash, TxStatus } from '@aztec/aztec.js';
import { type FeeOptions } from '@aztec/aztec.js/entrypoint';
import { Fr, type GasSettings } from '@aztec/circuits.js';
import { type LogFn } from '@aztec/foundation/log';

export async function cancelTx(
wallet: AccountWalletWithSecretKey,
{
txHash,
gasSettings,
nonce,
cancellable,
}: { txHash: TxHash; gasSettings: GasSettings; nonce: Fr; cancellable: boolean },
paymentMethod: FeePaymentMethod,
log: LogFn,
) {
const receipt = await wallet.getTxReceipt(txHash);
if (receipt.status !== TxStatus.PENDING || !cancellable) {
log(`Transaction is in status ${receipt.status} and cannot be cancelled`);
return;
}

const fee: FeeOptions = {
paymentMethod,
gasSettings,
};

gasSettings.inclusionFee.mul(new Fr(2));

const txRequest = await wallet.createTxExecutionRequest({
calls: [],
fee,
nonce,
cancellable: true,
});

const txPromise = await wallet.proveTx(txRequest, true);
const tx = new SentTx(wallet, wallet.sendTx(txPromise));
try {
await tx.wait();

log('Transaction has been cancelled');

const cancelReceipt = await tx.getReceipt();
log(` Tx fee: ${cancelReceipt.transactionFee}`);
log(` Status: ${cancelReceipt.status}`);
log(` Block number: ${cancelReceipt.blockNumber}`);
log(` Block hash: ${cancelReceipt.blockHash?.toString('hex')}`);
} catch (err: any) {
log(`Could not cancel transaction\n ${err.message}`);
}
}
12 changes: 12 additions & 0 deletions yarn-project/cli-wallet/src/cmds/check_tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type PXE, type TxHash } from '@aztec/aztec.js';
import { inspectTx } from '@aztec/cli/utils';
import { type LogFn } from '@aztec/foundation/log';

export async function checkTx(client: PXE, txHash: TxHash, statusOnly: boolean, log: LogFn) {
if (statusOnly) {
const receipt = await client.getTxReceipt(txHash);
return receipt.status;
} else {
await inspectTx(client, txHash, log, { includeBlockInfo: true });
}
}
Loading
Loading