diff --git a/.github/workflows/devnet-deploys.yml b/.github/workflows/devnet-deploys.yml index 14347cddb15..2775752bcd1 100644 --- a/.github/workflows/devnet-deploys.yml +++ b/.github/workflows/devnet-deploys.yml @@ -27,6 +27,8 @@ env: TF_VAR_FORK_MNEMONIC: ${{ secrets.FORK_MNEMONIC }} TF_VAR_INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} TF_VAR_FAUCET_ACCOUNT_INDEX: 5 + TF_VAR_BOT_API_KEY: ${{ secrets.BOT_API_KEY }} + TF_VAR_BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }} CONTRACT_S3_BUCKET: s3://aztec-devnet-deployments jobs: @@ -258,3 +260,29 @@ jobs: run: | terraform init -input=false -backend-config="key=${{ env.DEPLOY_TAG }}/aztec-faucet" terraform apply -input=false -auto-approve + + deploy-bot: + runs-on: ubuntu-latest + needs: [terraform_deploy, bootstrap] + steps: + - uses: actions/checkout@v4 + with: + ref: "${{ env.GIT_COMMIT }}" + fetch-depth: 0 + - uses: ./.github/ci-setup-action + - uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.7.5 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-2 + + - name: Deploy transactions bot + working-directory: ./yarn-project/aztec/terraform/bot + run: | + terraform init -input=false -backend-config="key=${{ env.DEPLOY_TAG }}/bot" + terraform apply -input=false -auto-approve diff --git a/yarn-project/aztec/src/cli/cmds/start_bot.ts b/yarn-project/aztec/src/cli/cmds/start_bot.ts index 68bc49bff17..3d31392634b 100644 --- a/yarn-project/aztec/src/cli/cmds/start_bot.ts +++ b/yarn-project/aztec/src/cli/cmds/start_bot.ts @@ -25,7 +25,7 @@ export async function startBot( return services; } -export async function addBot( +export function addBot( options: any, services: ServerList, signalHandlers: (() => Promise)[], @@ -37,7 +37,10 @@ export async function addBot( const botRunner = new BotRunner(config, { pxe: deps.pxe }); const botServer = createBotRunnerRpcServer(botRunner); - await botRunner.start(); + if (!config.noStart) { + void botRunner.start(); // Do not block since bot setup takes time + } services.push({ bot: botServer }); signalHandlers.push(botRunner.stop); + return Promise.resolve(); } diff --git a/yarn-project/aztec/terraform/bot/main.tf b/yarn-project/aztec/terraform/bot/main.tf new file mode 100644 index 00000000000..f3e5bc0000c --- /dev/null +++ b/yarn-project/aztec/terraform/bot/main.tf @@ -0,0 +1,186 @@ +terraform { + backend "s3" { + bucket = "aztec-terraform" + region = "eu-west-2" + } + required_providers { + aws = { + source = "hashicorp/aws" + version = "3.74.2" + } + } +} + +# Define provider and region +provider "aws" { + region = "eu-west-2" +} + +data "terraform_remote_state" "aztec2_iac" { + backend = "s3" + config = { + bucket = "aztec-terraform" + key = "aztec2/iac" + region = "eu-west-2" + } +} + +data "terraform_remote_state" "setup_iac" { + backend = "s3" + config = { + bucket = "aztec-terraform" + key = "setup/setup-iac" + region = "eu-west-2" + } +} + +resource "aws_cloudwatch_log_group" "aztec-bot-log-group" { + name = "/fargate/service/${var.DEPLOY_TAG}/aztec-bot" + retention_in_days = 14 +} + +resource "aws_service_discovery_service" "aztec-bot" { + name = "${var.DEPLOY_TAG}-aztec-bot" + + health_check_custom_config { + failure_threshold = 1 + } + + dns_config { + namespace_id = data.terraform_remote_state.setup_iac.outputs.local_service_discovery_id + + dns_records { + ttl = 60 + type = "A" + } + + dns_records { + ttl = 60 + type = "SRV" + } + + routing_policy = "MULTIVALUE" + } + + # Terraform just fails if this resource changes and you have registered instances. + provisioner "local-exec" { + when = destroy + command = "${path.module}/../servicediscovery-drain.sh ${self.id}" + } +} + +locals { + api_prefix = "/${var.DEPLOY_TAG}/aztec-bot/${var.BOT_API_KEY}" +} + +resource "aws_ecs_task_definition" "aztec-bot" { + family = "${var.DEPLOY_TAG}-aztec-bot" + network_mode = "awsvpc" + cpu = 2048 + memory = 4096 + requires_compatibilities = ["FARGATE"] + execution_role_arn = data.terraform_remote_state.setup_iac.outputs.ecs_task_execution_role_arn + task_role_arn = data.terraform_remote_state.aztec2_iac.outputs.cloudwatch_logging_ecs_role_arn + + container_definitions = jsonencode([ + { + name = "${var.DEPLOY_TAG}-aztec-bot" + image = "${var.DOCKERHUB_ACCOUNT}/aztec:${var.DEPLOY_TAG}" + command = ["start", "--bot"] + essential = true + portMappings = [ + { + containerPort = 80 + hostPort = 80 + } + ] + environment = [ + { name = "BOT_PRIVATE_KEY", value = var.BOT_PRIVATE_KEY }, + { name = "BOT_NO_START", value = "true" }, + { name = "BOT_PXE_URL", value = "http://${var.DEPLOY_TAG}-aztec-pxe-1.local/${var.DEPLOY_TAG}/aztec-pxe-1/${var.API_KEY}" }, + { name = "BOT_TX_INTERVAL_SECONDS", value = 300 }, + { name = "AZTEC_PORT", value = "80" }, + { name = "API_PREFIX", value = local.api_prefix }, + ] + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.aztec-bot-log-group.name + "awslogs-region" = "eu-west-2" + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) +} + +resource "aws_ecs_service" "aztec-bot" { + name = "${var.DEPLOY_TAG}-aztec-bot" + cluster = data.terraform_remote_state.setup_iac.outputs.ecs_cluster_id + launch_type = "FARGATE" + desired_count = 1 + deployment_maximum_percent = 100 + deployment_minimum_healthy_percent = 0 + platform_version = "1.4.0" + force_new_deployment = true + + network_configuration { + subnets = [ + data.terraform_remote_state.setup_iac.outputs.subnet_az1_private_id, + data.terraform_remote_state.setup_iac.outputs.subnet_az2_private_id + ] + security_groups = [data.terraform_remote_state.setup_iac.outputs.security_group_private_id] + } + + load_balancer { + target_group_arn = aws_alb_target_group.bot_http.arn + container_name = "${var.DEPLOY_TAG}-aztec-bot" + container_port = 80 + } + + service_registries { + registry_arn = aws_service_discovery_service.aztec-bot.arn + container_name = "${var.DEPLOY_TAG}-aztec-bot" + container_port = 80 + } + + task_definition = aws_ecs_task_definition.aztec-bot.family +} + +resource "aws_alb_target_group" "bot_http" { + name = "${var.DEPLOY_TAG}-bot-http" + port = 80 + protocol = "HTTP" + target_type = "ip" + vpc_id = data.terraform_remote_state.setup_iac.outputs.vpc_id + deregistration_delay = 5 + + health_check { + path = "${local.api_prefix}/status" + matcher = 200 + interval = 10 + healthy_threshold = 2 + unhealthy_threshold = 5 + timeout = 5 + } + + tags = { + name = "${var.DEPLOY_TAG}-bot-http" + } +} + +resource "aws_lb_listener_rule" "bot_api" { + listener_arn = data.terraform_remote_state.aztec2_iac.outputs.alb_listener_arn + priority = 700 + + action { + type = "forward" + target_group_arn = aws_alb_target_group.bot_http.arn + } + + condition { + path_pattern { + values = ["${local.api_prefix}*"] + } + } +} diff --git a/yarn-project/aztec/terraform/bot/variables.tf b/yarn-project/aztec/terraform/bot/variables.tf new file mode 100644 index 00000000000..c724241819a --- /dev/null +++ b/yarn-project/aztec/terraform/bot/variables.tf @@ -0,0 +1,19 @@ +variable "DEPLOY_TAG" { + type = string +} + +variable "DOCKERHUB_ACCOUNT" { + type = string +} + +variable "API_KEY" { + type = string +} + +variable "BOT_API_KEY" { + type = string +} + +variable "BOT_PRIVATE_KEY" { + type = string +} diff --git a/yarn-project/bot/src/config.ts b/yarn-project/bot/src/config.ts index 0d30bf345cf..844ffd9df40 100644 --- a/yarn-project/bot/src/config.ts +++ b/yarn-project/bot/src/config.ts @@ -18,6 +18,8 @@ export type BotConfig = { publicTransfersPerTx: number; /** How to handle fee payments. */ feePaymentMethod: 'native' | 'none'; + /** True to not automatically setup or start the bot on initialization. */ + noStart: boolean; }; export function getBotConfigFromEnv(): BotConfig { @@ -29,6 +31,7 @@ export function getBotConfigFromEnv(): BotConfig { BOT_TX_INTERVAL_SECONDS, BOT_PRIVATE_TRANSFERS_PER_TX, BOT_PUBLIC_TRANSFERS_PER_TX, + BOT_NO_START, } = process.env; if (BOT_FEE_PAYMENT_METHOD && !['native', 'none'].includes(BOT_FEE_PAYMENT_METHOD)) { throw new Error(`Invalid bot fee payment method: ${BOT_FEE_PAYMENT_METHOD}`); @@ -45,6 +48,7 @@ export function getBotConfigFromEnv(): BotConfig { privateTransfersPerTx: BOT_PRIVATE_TRANSFERS_PER_TX ? parseInt(BOT_PRIVATE_TRANSFERS_PER_TX) : undefined, publicTransfersPerTx: BOT_PUBLIC_TRANSFERS_PER_TX ? parseInt(BOT_PUBLIC_TRANSFERS_PER_TX) : undefined, feePaymentMethod: BOT_FEE_PAYMENT_METHOD ? (BOT_FEE_PAYMENT_METHOD as 'native' | 'none') : undefined, + noStart: BOT_NO_START ? ['1', 'true'].includes(BOT_NO_START) : undefined, }); } @@ -58,6 +62,7 @@ export function getBotDefaultConfig(overrides: Partial = {}): BotConf privateTransfersPerTx: 1, publicTransfersPerTx: 1, feePaymentMethod: 'none', + noStart: false, ...compact(overrides), }; }