Skip to content

Commit

Permalink
feat: Transaction sender bot (#7586)
Browse files Browse the repository at this point in the history
Adds a new package `@aztec/bot` that sends txs at regular intervals. The
bot idempotently deploys a schnorr account contract and a token contract
and mints funds to its own account, and sends a tx with a configurable
number of private and public transfers.

Bot can be started via `aztec start --bot`, either connected to a remote
pxe or using a local one, and optionally run within a node. The bot
exposes an http control interface when started, so it can be managed
like:

```bash
curl -XPOST -d'{"method": "bot_getConfig"}' http://localhost:8090
curl -XPOST -d'{"method": "bot_update", "params": [{"txIntervalSeconds": 20}]}' http://localhost:8090
curl -XPOST -d'{"method": "bot_stop"}' http://localhost:8090
```

A known issue is that running the bot within a non-sandbox node fails
with #7537.

Fixes #7562
  • Loading branch information
spalladino authored Jul 25, 2024
1 parent 26408c1 commit 176fd08
Show file tree
Hide file tree
Showing 36 changed files with 766 additions and 44 deletions.
9 changes: 9 additions & 0 deletions yarn-project/aztec.js/src/account_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ export class AccountManager {
return this.completeAddress;
}

/**
* Gets the address for this given account.
* Does not require the account to be deployed or registered.
* @returns The address.
*/
public getAddress() {
return this.getCompleteAddress().address;
}

/**
* Returns the contract instance definition associated with this account.
* Does not require the account to be deployed or registered.
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/contract/contract_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class ContractBase {
/** The Application Binary Interface for the contract. */
public readonly artifact: ContractArtifact,
/** The wallet used for interacting with this contract. */
protected wallet: Wallet,
public wallet: Wallet,
) {
artifact.functions.forEach((f: FunctionArtifact) => {
const interactionFunction = (...args: any[]) => {
Expand Down
10 changes: 10 additions & 0 deletions yarn-project/aztec.js/src/contract/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ export class DeployMethod<TContract extends ContractBase = Contract> extends Bas
return this.functionCalls;
}

/**
* Register this contract in the PXE and returns the Contract object.
* @param options - Deployment options.
*/
public async register(options: DeployOptions = {}): Promise<TContract> {
const instance = this.getInstance(options);
await this.wallet.registerContract({ artifact: this.artifact, instance });
return this.postDeployCtor(instance.address, this.wallet);
}

/**
* Returns calls for registration of the class and deployment of the instance, depending on the provided options.
* @param options - Deployment options.
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ export abstract class BaseWallet implements Wallet {
isContractPubliclyDeployed(address: AztecAddress): Promise<boolean> {
return this.pxe.isContractPubliclyDeployed(address);
}
isContractInitialized(address: AztecAddress): Promise<boolean> {
return this.pxe.isContractInitialized(address);
}
getPXEInfo(): Promise<PXEInfo> {
return this.pxe.getPXEInfo();
}
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@aztec/aztec-node": "workspace:^",
"@aztec/aztec.js": "workspace:^",
"@aztec/bb-prover": "workspace:^",
"@aztec/bot": "workspace:^",
"@aztec/builder": "workspace:^",
"@aztec/circuit-types": "workspace:^",
"@aztec/circuits.js": "workspace:^",
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/aztec/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function injectAztecCommands(program: Command, userLog: LogFn, debugLogge
.option('-o, --prover-node [options]', cliTexts.proverNode)
.option('-p2p, --p2p-bootstrap [options]', cliTexts.p2pBootstrap)
.option('-t, --txe [options]', cliTexts.txe)
.option('--bot [options]', cliTexts.bot)
.action(async options => {
// list of 'stop' functions to call when process ends
const signalHandlers: Array<() => Promise<void>> = [];
Expand Down Expand Up @@ -66,10 +67,12 @@ export function injectAztecCommands(program: Command, userLog: LogFn, debugLogge
signalHandlers.push(stop);
services = [{ node: nodeServer }, { pxe: pxeServer }];
} else {
// Start Aztec Node
if (options.node) {
const { startNode } = await import('./cmds/start_node.js');
services = await startNode(options, signalHandlers, userLog);
} else if (options.bot) {
const { startBot } = await import('./cmds/start_bot.js');
services = await startBot(options, signalHandlers, userLog);
} else if (options.proverNode) {
const { startProverNode } = await import('./cmds/start_prover_node.js');
services = await startProverNode(options, signalHandlers, userLog);
Expand Down
43 changes: 43 additions & 0 deletions yarn-project/aztec/src/cli/cmds/start_bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { type BotConfig, BotRunner, createBotRunnerRpcServer, getBotConfigFromEnv } from '@aztec/bot';
import { type PXE } from '@aztec/circuit-types';
import { type ServerList } from '@aztec/foundation/json-rpc/server';
import { type LogFn } from '@aztec/foundation/log';

import { mergeEnvVarsAndCliOptions, parseModuleOptions } from '../util.js';

export async function startBot(
options: any,
signalHandlers: (() => Promise<void>)[],
userLog: LogFn,
): Promise<ServerList> {
// Services that will be started in a single multi-rpc server
const services: ServerList = [];

const { proverNode, archiver, sequencer, p2pBootstrap, txe, prover } = options;
if (proverNode || archiver || sequencer || p2pBootstrap || txe || prover) {
userLog(
`Starting a bot with --prover-node, --prover, --archiver, --sequencer, --p2p-bootstrap, or --txe is not supported.`,
);
process.exit(1);
}

await addBot(options, services, signalHandlers);
return services;
}

export async function addBot(
options: any,
services: ServerList,
signalHandlers: (() => Promise<void>)[],
deps: { pxe?: PXE } = {},
) {
const envVars = getBotConfigFromEnv();
const cliOptions = parseModuleOptions(options.bot);
const config = mergeEnvVarsAndCliOptions<BotConfig>(envVars, cliOptions);

const botRunner = new BotRunner(config, { pxe: deps.pxe });
const botServer = createBotRunnerRpcServer(botRunner);
await botRunner.start();
services.push({ bot: botServer });
signalHandlers.push(botRunner.stop);
}
23 changes: 11 additions & 12 deletions yarn-project/aztec/src/cli/cmds/start_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import {
createAztecNodeRpcServer,
getConfigEnvVars as getNodeConfigEnvVars,
} from '@aztec/aztec-node';
import { type PXE } from '@aztec/circuit-types';
import { NULL_KEY } from '@aztec/ethereum';
import { type ServerList } from '@aztec/foundation/json-rpc/server';
import { type LogFn } from '@aztec/foundation/log';
import { createProvingJobSourceServer } from '@aztec/prover-client/prover-agent';
import { type PXEServiceConfig, createPXERpcServer, getPXEServiceConfig } from '@aztec/pxe';
import {
createAndStartTelemetryClient,
getConfigEnvVars as getTelemetryClientConfig,
} from '@aztec/telemetry-client/start';

import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';

import { MNEMONIC, createAztecNode, createAztecPXE, deployContractsToL1 } from '../../sandbox.js';
import { MNEMONIC, createAztecNode, deployContractsToL1 } from '../../sandbox.js';
import { mergeEnvVarsAndCliOptions, parseModuleOptions } from '../util.js';

const { DEPLOY_AZTEC_CONTRACTS } = process.env;
Expand Down Expand Up @@ -108,18 +108,17 @@ export const startNode = async (
// Add node stop function to signal handlers
signalHandlers.push(node.stop);

// Create a PXE client that connects to the node.
// Add a PXE client that connects to this node if requested
let pxe: PXE | undefined;
if (options.pxe) {
const pxeCliOptions = parseModuleOptions(options.pxe);
const pxeConfig = mergeEnvVarsAndCliOptions<PXEServiceConfig>(getPXEServiceConfig(), pxeCliOptions);
const pxe = await createAztecPXE(node, pxeConfig);
const pxeServer = createPXERpcServer(pxe);

// Add PXE to services list
services.push({ pxe: pxeServer });
const { addPXE } = await import('./start_pxe.js');
pxe = await addPXE(options, services, signalHandlers, userLog, { node });
}

// Add PXE stop function to signal handlers
signalHandlers.push(pxe.stop);
// Add a txs bot if requested
if (options.bot) {
const { addBot } = await import('./start_bot.js');
await addBot(options, services, signalHandlers, { pxe });
}

return services;
Expand Down
48 changes: 25 additions & 23 deletions yarn-project/aztec/src/cli/cmds/start_pxe.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
import { createAztecNodeClient } from '@aztec/circuit-types';
import { type AztecNode, createAztecNodeClient } from '@aztec/circuit-types';
import { type ServerList } from '@aztec/foundation/json-rpc/server';
import { type LogFn } from '@aztec/foundation/log';
import { type PXEServiceConfig, createPXERpcServer, createPXEService, getPXEServiceConfig } from '@aztec/pxe';

import { mergeEnvVarsAndCliOptions, parseModuleOptions } from '../util.js';

const { AZTEC_NODE_URL } = process.env;

export const startPXE = async (options: any, signalHandlers: (() => Promise<void>)[], userLog: LogFn) => {
// Services that will be started in a single multi-rpc server
export async function startPXE(options: any, signalHandlers: (() => Promise<void>)[], userLog: LogFn) {
const services: ServerList = [];
// Starting a PXE with a remote node.
// get env vars first
const pxeConfigEnvVars = getPXEServiceConfig();
// get config from options
const pxeCliOptions = parseModuleOptions(options.pxe);
await addPXE(options, services, signalHandlers, userLog, {});
return services;
}

// Determine node url from options or env vars
const nodeUrl = pxeCliOptions.nodeUrl || AZTEC_NODE_URL;
// throw if no Aztec Node URL is provided
if (!nodeUrl) {
export async function addPXE(
options: any,
services: ServerList,
signalHandlers: (() => Promise<void>)[],
userLog: LogFn,
deps: { node?: AztecNode } = {},
) {
const pxeCliOptions = parseModuleOptions(options.pxe);
const pxeConfig = mergeEnvVarsAndCliOptions<PXEServiceConfig>(getPXEServiceConfig(), pxeCliOptions);
const nodeUrl = pxeCliOptions.nodeUrl ?? process.env.AZTEC_NODE_URL;
if (!nodeUrl && !deps.node) {
userLog('Aztec Node URL (nodeUrl | AZTEC_NODE_URL) option is required to start PXE without --node option');
throw new Error('Aztec Node URL (nodeUrl | AZTEC_NODE_URL) option is required to start PXE without --node option');
process.exit(1);
}

// merge env vars and cli options
const pxeConfig = mergeEnvVarsAndCliOptions<PXEServiceConfig>(pxeConfigEnvVars, pxeCliOptions);

// create a node client
const node = createAztecNodeClient(nodeUrl);

const node = deps.node ?? createAztecNodeClient(nodeUrl);
const pxe = await createPXEService(node, pxeConfig);
const pxeServer = createPXERpcServer(pxe);

// Add PXE to services list
services.push({ pxe: pxeServer });

// Add PXE stop function to signal handlers
signalHandlers.push(pxe.stop);
return services;
};

return pxe;
}
10 changes: 10 additions & 0 deletions yarn-project/aztec/src/cli/texts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,14 @@ export const cliTexts = {
'Starts a TXE with options\n' +
'Available options are listed below as cliProperty:ENV_VARIABLE_NAME.\n' +
'txePort:TXE_PORT - number - The port on which the TXE should listen for connections. Default: 8081\n',
bot:
'Starts a bot that sends token transfer txs at regular intervals, using a local or remote PXE\n' +
'Available options are listed below as cliProperty:ENV_VARIABLE_NAME.\n' +
'feePaymentMethod:BOT_FEE_PAYMENT_METHOD - native | none - How to pay for fees for each tx.\n' +
'senderPrivateKey:BOT_PRIVATE_KEY - hex - Private key for sending txs.\n' +
'tokenSalt:BOT_TOKEN_SALT - hex - Deployment salt for the token contract.\n' +
'recipientEncryptionSecret:BOT_RECIPIENT_ENCRYPTION_SECRET - hex - Encryption secret key for the recipient account.\n' +
'txIntervalSeconds:BOT_TX_INTERVAL_SECONDS - number - Interval between txs are started. Too low a value may result in multiple txs in flight at a time. \n' +
'privateTransfersPerTx:BOT_PRIVATE_TRANSFERS_PER_TX - number - How many private transfers to execute per tx. \n' +
'publicTransfersPerTx:BOT_PUBLIC_TRANSFERS_PER_TX - number - How many public transfers to execute per tx.\n',
};
8 changes: 6 additions & 2 deletions yarn-project/aztec/src/cli/util.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { type ArchiverConfig } from '@aztec/archiver';
import { type AztecNodeConfig } from '@aztec/aztec-node';
import { type AccountManager, type Fr } from '@aztec/aztec.js';
import { type BotConfig } from '@aztec/bot';
import { type L1ContractAddresses, l1ContractsNames } from '@aztec/ethereum';
import { EthAddress } from '@aztec/foundation/eth-address';
import { type ServerList } from '@aztec/foundation/json-rpc/server';
import { type LogFn, createConsoleLogger } from '@aztec/foundation/log';
import { type P2PConfig } from '@aztec/p2p';
import { type ProverNodeConfig } from '@aztec/prover-node';
import { type PXEService, type PXEServiceConfig } from '@aztec/pxe';

export interface ServiceStarter<T = any> {
Expand Down Expand Up @@ -66,8 +68,10 @@ export const parseModuleOptions = (options: string): Record<string, string> => {
}, {});
};

export const mergeEnvVarsAndCliOptions = <T extends AztecNodeConfig | PXEServiceConfig | P2PConfig | ArchiverConfig>(
envVars: AztecNodeConfig | PXEServiceConfig | P2PConfig | ArchiverConfig,
export const mergeEnvVarsAndCliOptions = <
T extends AztecNodeConfig | PXEServiceConfig | P2PConfig | ArchiverConfig | BotConfig | ProverNodeConfig,
>(
envVars: AztecNodeConfig | PXEServiceConfig | P2PConfig | ArchiverConfig | BotConfig | ProverNodeConfig,
cliOptions: Record<string, string>,
contractsRequired = false,
userLog = createConsoleLogger(),
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/aztec/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
{
"path": "../bb-prover"
},
{
"path": "../bot"
},
{
"path": "../builder"
},
Expand Down
1 change: 1 addition & 0 deletions yarn-project/bot/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@aztec/foundation/eslint');
3 changes: 3 additions & 0 deletions yarn-project/bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Transactions Bot

Simple bot that connects to a PXE to send txs on a recurring basis.
84 changes: 84 additions & 0 deletions yarn-project/bot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"name": "@aztec/bot",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./dest/index.js"
},
"inherits": [
"../package.common.json"
],
"scripts": {
"build": "yarn clean && tsc -b",
"build:dev": "tsc -b --watch",
"clean": "rm -rf ./dest .tsbuildinfo",
"formatting": "run -T prettier --check ./src && run -T eslint ./src",
"formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
"bb": "node --no-warnings ./dest/bb/index.js",
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests"
},
"jest": {
"moduleNameMapper": {
"^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
},
"testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
"rootDir": "./src",
"transform": {
"^.+\\.tsx?$": [
"@swc/jest",
{
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
}
}
}
]
},
"extensionsToTreatAsEsm": [
".ts"
],
"reporters": [
[
"default",
{
"summaryThreshold": 9999
}
]
]
},
"dependencies": {
"@aztec/accounts": "workspace:^",
"@aztec/aztec.js": "workspace:^",
"@aztec/circuit-types": "workspace:^",
"@aztec/circuits.js": "workspace:^",
"@aztec/entrypoints": "workspace:^",
"@aztec/foundation": "workspace:^",
"@aztec/noir-contracts.js": "workspace:^",
"@aztec/protocol-contracts": "workspace:^",
"@aztec/types": "workspace:^",
"source-map-support": "^0.5.21",
"tslib": "^2.4.0"
},
"devDependencies": {
"@jest/globals": "^29.5.0",
"@types/jest": "^29.5.0",
"@types/memdown": "^3.0.0",
"@types/node": "^18.7.23",
"@types/source-map-support": "^0.5.10",
"jest": "^29.5.0",
"jest-mock-extended": "^3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"files": [
"dest",
"src",
"!*.test.*"
],
"types": "./dest/index.d.ts",
"engines": {
"node": ">=18"
}
}
Loading

0 comments on commit 176fd08

Please sign in to comment.