diff --git a/.prettierignore b/.prettierignore similarity index 100% rename from .prettierignore rename to .prettierignore diff --git a/lib/allora/.gitignore b/lib/allora/.gitignore new file mode 100644 index 00000000..f60797b6 --- /dev/null +++ b/lib/allora/.gitignore @@ -0,0 +1,8 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/lib/allora/.npmignore b/lib/allora/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/lib/allora/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/lib/allora/README.md b/lib/allora/README.md new file mode 100644 index 00000000..229e7c92 --- /dev/null +++ b/lib/allora/README.md @@ -0,0 +1,233 @@ +# Sample AWS Blockchain Node Runner app for Allora Worker Nodes + +| Contributed by | +|:--------------------:| +| [@clementupshot](https://github.com/clementupshot), [@allora-rc](https://github.com/allora-rc), [@Madisonw](https://github.com/Madisonw)| + +[Allora](https://www.allora.network/) is a self-improving decentralized Artificial Intelligence (AI) network. The primary goal of the network is to be the marketplace for intelligence. In other words, Allora aims to incentivize data scientists (workers) to provide high-quality inferences as requested by consumers. Inferences include predictions of arbitrary future events or difficult computations requiring specialized knowledge. + +The Allora Network brings together: + + - [Consumers](https://docs.allora.network/devs) who pay for and acquire inferences or expertise to be revealed + - [Workers](https://v2.docs.allora.network/datasci) who reveal inferences + - [Reputers](https://docs.allora.network/nops) who determine how accurate workers are after a ground truth is revealed + - [Validators](https://docs.allora.network/nops) who secure protocol state, history, and reward distributions + +With these ingredients, the Allora Network is able to continuously learn and improve itself over time producing inferences that are more accurate than the most accurate participant. + +Allora Worker nodes are the interfaces between data scientists' models and the Allora Network. A worker node is a machine-intelligent application registered on the Allora chain that provides inference/prediction on a particular topic it's subscribed to and gets rewarded based on the inference quality. + +This blueprint is designed to assist in deploying a single Allora [Worker Node](https://v2.docs.allora.network/datasci) on AWS. It is intended for use in development, testing, or Proof of Concept (PoC) environments. + +## Overview of Deployment Architecture + +### Single Worker Node Setup +![Single Worker Node Deployment](./doc/assets/Architecture-Single-Allora-Worker-Node.png) + +The AWS Cloud Development Kit (CDK) is used to deploy a single Allora Worker Node. The CDK application deploys the following infrastructure: + + - Virtual Private Cloud (VPC) + - Internet Gateway (IGW) to allow inbound requests for inferences from consumers and outbound responses from the worker node revealing inferences + - Public subnet that has a direct route to the IGW + - Security Group (SG) with TCP Port 9010 open inbound allowing requests for inferences to be routed to the Allora Worker Node + - Single Amazon Elastic Compute Cloud (EC2) instance (the Allora Worker Node) assigned to the public subnet + +The Allora Worker Node is accessed by the user internally and is not exposed to the Internet to protect the node from unauthorized access. A user can gain access to the EC2 Instance using AWS Session Manager. + +Multiple processes run on the Allora Worker Node (EC2 instance): + + - Docker container with the worker node logic that handles communication between the worker and the public head nodes + - Docker container running the model server that reveals inferences to consumers + +Allora Public Head Nodes publish the Allora chain requests (requests for inferences from consumers) to Allora worker nodes. When a worker node is initialized, it starts with an environment variable called BOOT_NODES, which helps handle the connection and communications between worker nodes and the head nodes. + +The worker node (docker container) will call the function that invokes custom logic that handles the actual inference. The request-response is a bidirectional flow from the Allora chain (inference requests from consumers) to the public head nodes to the worker node and finally to the model server that reveals inferences. + +## Additional materials + +
+ +Well-Architected Checklist + +This is the Well-Architected checklist for the Allora worker nodes implementation of the AWS Blockchain Node Runner app. This checklist takes into account questions from the [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/) which are relevant to this workload. Please feel free to add more checks from the framework if required for your workload. + +| Pillar | Control | Question/Check | Remarks | +|:------------------------|:----------------------------------|:---------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Security | Network protection | Are there unnecessary open ports in security groups? | Please note that port 9010 (TCP) is open inbound to support requests for inferences from the Allora Network public head nodes. | +| | | Traffic inspection | Traffic protection is not used in the solution. [AWS Web Applications Firewall (WAF)](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) could be implemented for traffic over HTTP(S), [AWS Shield](https://docs.aws.amazon.com/waf/latest/developerguide/shield-chapter.html) provides Distributed Denial of Service (DDoS) protection. Additional charges will apply. | +| | Compute protection | Reduce attack surface | This solution uses Amazon Linux AMI. You may choose to run hardening scripts on it. | +| | | Enable people to perform actions at a distance | This solution uses [AWS Systems Manager for terminal session](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html#start-sys-console). SSH Port 22 is not open inbound. | +| | Data protection at rest | Use encrypted Amazon Elastic Block Store (Amazon EBS) volumes | This solution uses encrypted Amazon EBS volumes. | +| | Authorization and access control | Use instance profile with Amazon Elastic Compute Cloud (Amazon EC2) instances | This solution uses AWS Identity and Access Management (AWS IAM) role instead of IAM user. | +| | | Following principle of least privilege access | Root user is not used (using special user "ec2-user" instead). | +| | Application security | Security focused development practices | cdk-nag is being used with appropriate suppressions. | +| Cost optimization | Service selection | Use cost effective resources | We use a T3 instance as T3 instances are a low cost burstable general purpose instance type that provide a baseline level of CPU performance with the ability to burst CPU usage at any time for as long as required. T3 instances are designed for applications with moderate CPU usage that experience temporary spikes in use. This profile aligns closely with the load profile of Allora Network worker nodes. | +| Reliability | Resiliency implementation | Withstand component failures | This solution does not use an [AWS EC2 Auto Scaling Group](https://aws.amazon.com/ec2/autoscaling/) but one can be implemented. | +| | Data backup | How is data backed up? | Considering blockchain data is replicated by Allora Cosmos AppChain Validator nodes, we don't use additional mechanisms to backup the data. | +| | Resource monitoring | How are workload resources monitored? | Resources are not being monitored using Amazon CloudWatch dashboards. Amazon CloudWatch custom metrics are being pushed via CloudWatch Agent. | +| Performance efficiency | Compute selection | How is compute solution selected? | Compute solution is selected based on best price-performance, i.e. AWS EC2 T3 Medium instance suitable for bursty workloads. | +| | Storage selection | How is storage solution selected? | Storage solution is selected based on best price-performance, i.e. Amazon EBS volumes with optimal IOPS and throughput. | +| | Architecture selection | How is the best performance architecture selected? | A combination of recommendations from the Allora Network community and Allora Labs testing. | +| Sustainability | Hardware & services | Select most efficient hardware for your workload | The solution uses AMD-powered instances. There is a potential to use AWS Graviton-based Amazon EC2 instances which offer the best performance per watt of energy use in Amazon EC2. +
+ +## Worker Node System Requirements + +- Operating System: Any modern Linux operating system +- CPU: Minimum of 2 cores +- Memory: Minimum of 4GB +- Storage: SSD or NVMe with minimum of 20GB of space + +## Setup Instructions + +### Setup Cloud9 + +We will use AWS Cloud9 to execute the subsequent commands. Follow the instructions in [Cloud9 Setup](../../docs/setup-cloud9.md). + +### Clone this repository and install dependencies + +```bash + git clone https://github.com/aws-samples/aws-blockchain-node-runners.git + cd aws-blockchain-node-runners + npm install +``` + +### Deploy single worker node + +1. Make sure you are in the root directory of the cloned repository + +2. Configure your setup + + Create your own copy of `.env` file and edit it to update with your AWS Account ID and Region: + ```bash + # Make sure you are in aws-blockchain-node-runners/lib/allora + cd lib/allora + npm install + pwd + cp ./sample-configs/.env-sample-full .env + nano .env + ``` + > NOTE: + > Example configuration parameters are set in the local `.env-sample` file. You can find more examples inside `sample-configs` directory. + + > IMPORTANT: + > All AWS CDK v2 deployments use dedicated AWS resources to hold data during deployment. Therefore, your AWS account and Region must be [bootstrapped](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html) to create these resources before you can deploy. If you haven't already bootstrapped, issue the following command: + > ```bash + > cdk bootstrap aws://ACCOUNT-NUMBER/REGION + > ``` + +3. Deploy Common Stack + + ```bash + pwd + # Make sure you are in aws-blockchain-node-runners/lib/allora + npx cdk deploy allora-edge-common --json --outputs-file allora-edge-common-deploy.json + ``` + +5. Deploy Allora Worker Node + + ```bash + pwd + # Make sure you are in aws-blockchain-node-runners/lib/allora + npx cdk deploy allora-single-node --json --outputs-file single-node-deploy.json + ``` + +## Clear up and undeploy everything + +1. Undeploy worker node and common components + + ```bash + # Setting the AWS account id and region in case local .env file is lost + export AWS_ACCOUNT_ID= + export AWS_REGION= + + pwd + # Make sure you are in aws-blockchain-node-runners/lib/allora + + # Undeploy Single Node + npx cdk destroy allora-single-node + + # Undeploy Common Stack + npx cdk destroy allora-edge-nodes-common + ``` + +2. Follow these steps to delete the Cloud9 instance in [Cloud9 Setup](../../docs/setup-cloud9.md) + + Navigate to the AWS Cloud9 service in your Management Console, then select the environment you have created. On the top right, click **Delete** button and follow the instructions. + +3. Delete the instance profile and IAM role + +```bash +aws iam delete-instance-profile --instance-profile-name Cloud9-Developer-Access +aws iam delete-role --role-name Cloud9-Developer-Access +``` + +### FAQ + +1. How to check the logs from the EC2 user-data script? + +Please enter the [AWS Management Console - EC2 Instances](https://us-east-2.console.aws.amazon.com/ec2/home?region=us-east-2#Instances:instanceState=running), choose the correct region, copy the instance ID you need to query. + +```bash +pwd +# Make sure you are in aws-blockchain-node-runners/lib/allora + +export INSTANCE_ID="i-**************" +echo "INSTANCE_ID=" $INSTANCE_ID +aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION +sudo cat /var/log/cloud-init-output.log +``` +2. How to check the worker node connectivity to the Allora Network? + +Please enter the [AWS Management Console - EC2 Instances](https://us-east-2.console.aws.amazon.com/ec2/home?region=us-east-2#Instances:instanceState=running), choose the correct region, copy the instance ID you need to query. + +```bash +pwd +# Make sure you are in aws-blockchain-node-runners/lib/allora + +export INSTANCE_ID="i-**************" +echo "INSTANCE_ID=" $INSTANCE_ID +aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION +``` + +You should be able to query Topic 1 on the Allora Network and see similar output below +```bash +$ allorad q emissions topic 1 --node https://allora-rpc.testnet-1.testnet.allora.network +effective_revenue: "0" +topic: + allow_negative: true + alpha_regret: "0.1" + creator: allo1lzf3xp0zqg4239mrswd0cclsgt3y8fl7l84hxu + default_arg: ETH + epoch_last_ended: "183177" + epoch_length: "120" + ground_truth_lag: "120" + id: "1" + inference_logic: bafybeifqs2c7ghellof657rygvrh6ht73scto3oznw4i747sqk3ihy7s5m + inference_method: allora-inference-function.wasm + loss_logic: bafybeid7mmrv5qr4w5un6c64a6kt2y4vce2vylsmfvnjt7z2wodngknway + loss_method: loss-calculation-eth.wasm + metadata: ETH 10min Prediction + p_norm: "3" + tolerance: "0.001" +weight: "0" +``` +3. How to check the Allora worker containers are running? + +Please enter the [AWS Management Console - EC2 Instances](https://us-east-2.console.aws.amazon.com/ec2/home?region=us-east-2#Instances:instanceState=running), choose the correct region, copy the instance ID you need to query. + +```bash +pwd +# Make sure you are in aws-blockchain-node-runners/lib/allora + +export INSTANCE_ID="i-**************" +echo "INSTANCE_ID=" $INSTANCE_ID +aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION +``` + +```bash +[ec2-user@ip-192-168-0-224 ~]$ docker ps -a +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +b10c12c51f32 worker-worker "allora-node allora-…" 18 hours ago Exited (2) 18 hours ago worker +05273577ce7a alloranetwork/allora-inference-base-head:latest "allora-node allora-…" 18 hours ago Exited (2) 18 hours ago head +``` \ No newline at end of file diff --git a/lib/allora/allora.ts b/lib/allora/allora.ts new file mode 100644 index 00000000..0f139376 --- /dev/null +++ b/lib/allora/allora.ts @@ -0,0 +1,92 @@ +#!/usr/bin/env node +import 'dotenv/config'; +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import * as ec2 from "aws-cdk-lib/aws-ec2"; +import * as constants from "../constructs/constants"; +import { EdgeCommonStack } from "./lib/common-stack"; +import { AlloraStack } from './lib/allora-stack'; + +const parseDataVolumeType = (dataVolumeType: string) => { + switch (dataVolumeType) { + case "gp3": + return ec2.EbsDeviceVolumeType.GP3; + case "io2": + return ec2.EbsDeviceVolumeType.IO2; + case "io1": + return ec2.EbsDeviceVolumeType.IO1; + case "instance-store": + return constants.InstanceStoreageDeviceVolumeType; + default: + return ec2.EbsDeviceVolumeType.GP3; + } +}; + +const app = new cdk.App(); + +new EdgeCommonStack(app, "allora-edge-common", { + stackName: `allora-edge-nodes-common`, + env: { account: process.env.AWS_ACCOUNT_ID || "xxxxxxxxxxx", region: process.env.AWS_REGION || 'us-east-1' } +}); + +new AlloraStack(app, 'allora-single-node', { + stackName: 'allora-single-node', + env: { + account: process.env.AWS_ACCOUNT_ID || "xxxxxxxxxxx", + region: process.env.AWS_REGION || 'us-east-1', + }, + instanceType: process.env.AWS_INSTANCE_TYPE || 't3.medium', + vpcMaxAzs: Number(process.env.AWS_VPC_MAX_AZS || 1), + vpcNatGateways: Number(process.env.AWS_VPC_NAT_GATEWAYS || 0), + vpcSubnetCidrMask: Number(process.env.AWS_VPC_CIDR_MASK), + resourceNamePrefix: process.env.AWS_RESOURCE_NAME_PREFIX || 'AlloraWorkerx', + dataVolume: { + sizeGiB: process.env.EDGE_DATA_VOL_SIZE ? parseInt(process.env.EDGE_DATA_VOL_SIZE) : 256, + type: parseDataVolumeType(process.env.EDGE_DATA_VOL_TYPE?.toLowerCase() ? process.env.EDGE_DATA_VOL_TYPE?.toLowerCase() : "gp3"), + iops: process.env.EDGE_DATA_VOL_IOPS ? parseInt(process.env.EDGE_DATA_VOL_IOPS) : 10000, + throughput: process.env.EDGE_DATA_VOL_THROUGHPUT ? parseInt(process.env.EDGE_DATA_VOL_THROUGHPUT) : 700 + }, + alloraWorkerName: process.env.ALLORA_WORKER_NAME || 'aws', + alloraEnv: process.env.ALLORA_ENV || 'dev', + modelRepo: process.env.MODEL_REPO || 'https://github.com/allora-network/basic-coin-prediction-node', + modelEnvVars: process.env.MODEL_ENV_VARS || '', + + //Wallet config + alloraWalletAddressKeyName: process.env.ALLORA_WALLET_ADDRESS_KEY_NAME || 'secret', + alloraWalletAddressRestoreMnemonic: process.env.ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC || 'secret', + alloraWalletHomeDir: process.env.ALLORA_WALLET_HOME_DIR || '', + alloraWalletGas: process.env.ALLORA_WALLET_GAS || '1000000', + alloraWalletGasAdjustment: process.env.ALLORA_WALLET_GAS_ADJUSTMENT || '1.0', + + alloraWalletGasPrices: process.env.ALLORA_WALLET_GAS_PRICES || 'auto', + alloraWalletGasPriceInterval: process.env.ALLORA_WALLET_GAS_PRICE_INTERVAL || '60', + alloraWalletRetryDelay: process.env.ALLORA_WALLET_RETRY_DELAY || '3', + alloraWalletBlockDurationEstimated: process.env.ALLORA_WALLET_BLOCK_DURATION_ESTIMATED || '10', + alloraWalletWindowCorrectionFactor: process.env.ALLORA_WALLET_WINDOW_CORRECTION_FACTOR || '0.8', + alloraWalletAccountSequenceRetryDelay: process.env.ALLORA_WALLET_ACCOUNT_SEQUENCE_RETRY_DELAY || '5', + + alloraWalletNodeRpc: process.env.ALLORA_WALLET_NODE_RPC || 'https://localhost:26657', + alloraWalletMaxRetries: process.env.ALLORA_WALLET_MAX_RETRIES || '1', + alloraWalletDelay: process.env.ALLORA_WALLET_DELAY || '1', + alloraWalletSubmitTx: process.env.ALLORA_WALLET_SUBMIT_TX || 'false', + alloraWalletMaxFees: process.env.ALLORA_WALLET_MAX_FEES || '500000', + + //Worker Properties + alloraWorkerTopicId: process.env.ALLORA_WORKER_TOPIC_ID || '1', + alloraWorkerInferenceEntrypointName: process.env.ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME || 'api-worker-reputer', + alloraWorkerInferenceEndpoint: process.env.ALLORA_WORKER_INFERENCE_ENDPOINT || 'http://source:8000/inference/{Token}', + alloraWorkerLoopSeconds: process.env.ALLORA_WORKER_LOOP_SECONDS || '30', + alloraWorkerToken: process.env.ALLORA_WORKER_TOKEN || 'ethereum', + + //Reputer Properties + alloraReputerTopicId: process.env.ALLORA_REPUTER_TOPIC_ID || '1', + alloraReputerEntrypointName: process.env.ALLORA_REPUTER_ENTRYPOINT_NAME || 'api-worker-reputer', + alloraReputerSourceOfTruthEndpoint: process.env.ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT || 'http://source:8888/truth/{Token}/{BlockHeight}', + + alloraReputerLossFunctionService: process.env.ALLORA_REPUTER_LOSS_FUNCTION_SERVICE || 'http://localhost:5000', + alloraReputerLossMethodOptionsLossMethod: process.env.ALLORA_REPUTER_LOSS_METHOD_OPTIONS_LOSS_METHOD || 'sqe', + + alloraReputerLoopSeconds: process.env.ALLORA_REPUTER_LOOP_SECONDS || '30', + alloraReputerToken: process.env.ALLORA_REPUTER_TOKEN || 'ethereum', + alloraReputerMinStake: process.env.ALLORA_REPUTER_MIN_STAKE || '100000', +}); diff --git a/lib/allora/cdk.json b/lib/allora/cdk.json new file mode 100644 index 00000000..78dacb3a --- /dev/null +++ b/lib/allora/cdk.json @@ -0,0 +1,72 @@ +{ + "app": "npx ts-node --prefer-ts-exts allora.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false + } +} diff --git a/lib/allora/doc/assets/Architecture-Single-Allora-Worker-Node-Source.drawio b/lib/allora/doc/assets/Architecture-Single-Allora-Worker-Node-Source.drawio new file mode 100644 index 00000000..a2f2f859 --- /dev/null +++ b/lib/allora/doc/assets/Architecture-Single-Allora-Worker-Node-Source.drawio @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/allora/doc/assets/Architecture-Single-Allora-Worker-Node.png b/lib/allora/doc/assets/Architecture-Single-Allora-Worker-Node.png new file mode 100644 index 00000000..197544d8 Binary files /dev/null and b/lib/allora/doc/assets/Architecture-Single-Allora-Worker-Node.png differ diff --git a/lib/allora/jest.config.js b/lib/allora/jest.config.js new file mode 100644 index 00000000..08263b89 --- /dev/null +++ b/lib/allora/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/lib/allora/lib/allora-stack.ts b/lib/allora/lib/allora-stack.ts new file mode 100644 index 00000000..6981ccab --- /dev/null +++ b/lib/allora/lib/allora-stack.ts @@ -0,0 +1,318 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; +import { SingleNodeConstruct, SingleNodeConstructCustomProps } from "../../constructs/single-node" +import * as fs from 'fs'; +import * as path from 'path'; +import * as nag from "cdk-nag"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as configTypes from "../../constructs/config.interface"; +import * as nodeCwDashboard from "./assets/node-cw-dashboard" +import * as cw from 'aws-cdk-lib/aws-cloudwatch'; + +interface AlloraStackEnvironment extends cdk.Environment { + account: string; + region: string; +} + +export interface AlloraStackProps extends cdk.StackProps { + instanceType: string; + vpcMaxAzs: number; + vpcNatGateways: number + vpcSubnetCidrMask: number; + resourceNamePrefix: string; + dataVolume: configTypes.DataVolumeConfig; + env: AlloraStackEnvironment + alloraWorkerName: string; + alloraEnv: string; + modelRepo: string; + modelEnvVars: string; + + alloraWalletAddressKeyName: string; + alloraWalletAddressRestoreMnemonic: string; + alloraWalletHomeDir: string; + alloraWalletGas: string, + alloraWalletGasAdjustment: string; + + alloraWalletGasPrices: string; + alloraWalletGasPriceInterval: string; + alloraWalletRetryDelay: string; + alloraWalletBlockDurationEstimated: string; + alloraWalletWindowCorrectionFactor: string; + alloraWalletMaxFees: string; + alloraWalletAccountSequenceRetryDelay: string; + + alloraWalletNodeRpc: string; + alloraWalletMaxRetries: string; + alloraWalletDelay: string; + alloraWalletSubmitTx: string; + + alloraWorkerTopicId: string; + alloraWorkerInferenceEntrypointName: string; + alloraWorkerInferenceEndpoint: string; + alloraWorkerLoopSeconds: string; + alloraWorkerToken: string; + + alloraReputerTopicId: string; + alloraReputerEntrypointName: string; + alloraReputerSourceOfTruthEndpoint: string; + alloraReputerLoopSeconds: string; + alloraReputerToken: string; + alloraReputerMinStake: string; + + alloraReputerLossFunctionService: string; + alloraReputerLossMethodOptionsLossMethod: string; +} + + +export class AlloraStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AlloraStackProps) { + super(scope, id, props); + + const { + env, + instanceType, + resourceNamePrefix, + dataVolume, + alloraWorkerName, + alloraEnv, + modelRepo, + modelEnvVars, + + //wallet props + alloraWalletAddressKeyName, + alloraWalletAddressRestoreMnemonic, + alloraWalletHomeDir, + alloraWalletGas, + alloraWalletGasAdjustment, + + alloraWalletGasPrices, + alloraWalletGasPriceInterval, + alloraWalletRetryDelay, + alloraWalletBlockDurationEstimated, + alloraWalletWindowCorrectionFactor, + alloraWalletMaxFees, + alloraWalletAccountSequenceRetryDelay, + + alloraWalletNodeRpc, + alloraWalletMaxRetries, + alloraWalletDelay, + alloraWalletSubmitTx, + + //worker props + alloraWorkerTopicId, + alloraWorkerInferenceEntrypointName, + alloraWorkerInferenceEndpoint, + alloraWorkerLoopSeconds, + alloraWorkerToken, + + //reputer props + alloraReputerTopicId, + alloraReputerEntrypointName, + alloraReputerSourceOfTruthEndpoint, + + alloraReputerLossFunctionService, + alloraReputerLossMethodOptionsLossMethod, + + alloraReputerLoopSeconds, + alloraReputerToken, + alloraReputerMinStake, + } = props; + const { region } = env; + + const STACK_NAME = cdk.Stack.of(this).stackName; + const STACK_ID = cdk.Stack.of(this).stackId; + + + + // Create S3 Bucket + const bucket = new s3.Bucket(this, `${resourceNamePrefix}Bucket`, { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + + // Upload node.sh to S3 + new s3deploy.BucketDeployment(this, `${resourceNamePrefix}ScriptDeployment`, { + sources: [s3deploy.Source.asset(path.join(__dirname, 'assets', 'user-data'))], + destinationBucket: bucket, + destinationKeyPrefix: 'user-data', // optional prefix in destination bucket + }); + + // Create VPC + const vpc = new ec2.Vpc(this, `${resourceNamePrefix}Vpc`, { + maxAzs: props.vpcMaxAzs, + natGateways: props.vpcNatGateways, + subnetConfiguration: [{ + cidrMask: props.vpcSubnetCidrMask, + name:`${resourceNamePrefix}PublicSubnet`, + subnetType: ec2.SubnetType.PUBLIC, + }] + }); + + // Security Group with inbound TCP port 9010 open + const securityGroup = new ec2.SecurityGroup(this, `${resourceNamePrefix}SecurityGroup`, { + vpc, + allowAllOutbound: true, + description: 'Allow inbound TCP 9010', + }); + securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(9010), 'Allow inbound TCP 9010'); + + + + + // Getting the snapshot bucket name and IAM role ARN from the common stack + const importedInstanceRoleArn = cdk.Fn.importValue("EdgeNodeInstanceRoleArn"); + + const instanceRole = iam.Role.fromRoleArn(this, "iam-role", importedInstanceRoleArn); + + // Making sure our instance will be able to read the assets + bucket.grantRead(instanceRole); + + + // Define SingleNodeConstructCustomProps + const singleNodeProps: SingleNodeConstructCustomProps = { + instanceName: `${resourceNamePrefix}Instance`, + instanceType: new ec2.InstanceType(instanceType), + dataVolumes: [ dataVolume ], // Define your data volumes here + machineImage:new ec2.AmazonLinuxImage({ + generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023, + kernel:ec2.AmazonLinuxKernel.KERNEL5_X, + cpuType: ec2.AmazonLinuxCpuType.X86_64, + }), + role: instanceRole, + vpc: vpc, + securityGroup: securityGroup, + availabilityZone: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }).availabilityZones[0], + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + }; + + // Instantiate SingleNodeConstruct + const singleNode = new SingleNodeConstruct(this, `${resourceNamePrefix}SingleNode`, singleNodeProps); + + const instance = singleNode.instance; + + // Read user data script and inject variables + const userData = fs.readFileSync(path.join(__dirname, 'assets', 'user-data', 'node.sh')).toString(); + const modifiedUserData = cdk.Fn.sub(userData, { + _AWS_REGION_: region, + _ASSETS_S3_PATH_: `s3://${bucket.bucketName}/user-data/node.sh`, + _NODE_CF_LOGICAL_ID_: singleNode.nodeCFLogicalId, + _STACK_NAME_: STACK_NAME, + _STACK_ID_: STACK_ID, + _ALLORA_WORKER_NAME_: alloraWorkerName, + _ALLORA_ENV_: alloraEnv, + _MODEL_REPO_: modelRepo, + _MODEL_ENV_VARS_: modelEnvVars, + + //wallet config + _ALLORA_WALLET_ADDRESS_KEY_NAME_ : alloraWalletAddressKeyName, + _ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC_ : alloraWalletAddressRestoreMnemonic, + _ALLORA_WALLET_HOME_DIR_: alloraWalletHomeDir, + _ALLORA_WALLET_GAS_ADJUSTMENT_: alloraWalletGasAdjustment, + _ALLORA_WALLET_GAS_: alloraWalletGas, + + _ALLORA_WALLET_GAS_PRICES_: alloraWalletGasPrices, + _ALLORA_WALLET_GAS_PRICE_INTERVAL_: alloraWalletGasPriceInterval, + _ALLORA_WALLET_RETRY_DELAY_: alloraWalletRetryDelay, + _ALLORA_WALLET_BLOCK_DURATION_ESTIMATED_: alloraWalletBlockDurationEstimated, + _ALLORA_WALLET_WINDOW_CORRECTION_FACTOR_: alloraWalletWindowCorrectionFactor, + _ALLORA_WALLET_MAX_FEES_: alloraWalletMaxFees, + _ALLORA_WALLET_ACCOUNT_SEQUENCE_RETRY_DELAY_: alloraWalletAccountSequenceRetryDelay, + + _ALLORA_WALLET_NODE_RPC_: alloraWalletNodeRpc, + _ALLORA_WALLET_MAX_RETRIES_: alloraWalletMaxRetries, + _ALLORA_WALLET_DELAY_: alloraWalletDelay, + _ALLORA_WALLET_SUBMIT_TX_: alloraWalletSubmitTx, + + //worker config + _ALLORA_WORKER_TOPIC_ID_: alloraWorkerTopicId, + _ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME_: alloraWorkerInferenceEntrypointName, + _ALLORA_WORKER_INFERENCE_ENDPOINT_: alloraWorkerInferenceEndpoint, + _ALLORA_WORKER_LOOP_SECONDS_: alloraWorkerLoopSeconds, + _ALLORA_WORKER_TOKEN_: alloraWorkerToken, + + //reputer config + _ALLORA_REPUTER_TOPIC_ID_: alloraReputerTopicId, + _ALLORA_REPUTER_ENTRYPOINT_NAME_: alloraReputerEntrypointName, + _ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT_: alloraReputerSourceOfTruthEndpoint, + + _ALLORA_REPUTER_LOSS_FUNCTION_SERVICE_: alloraReputerLossFunctionService, + _ALLORA_REPUTER_LOSS_METHOD_OPTIONS_LOSS_METHOD_: alloraReputerLossMethodOptionsLossMethod, + + _ALLORA_REPUTER_LOOP_SECONDS_: alloraReputerLoopSeconds, + _ALLORA_REPUTER_TOKEN_: alloraReputerToken, + _ALLORA_REPUTER_MIN_STAKE_: alloraReputerMinStake, + + + }); + + // Create UserData for EC2 instance + const ec2UserData = ec2.UserData.forLinux(); + ec2UserData.addCommands(modifiedUserData); + + instance.addUserData(ec2UserData.render()) + + const dashboardString = cdk.Fn.sub(JSON.stringify(nodeCwDashboard.SyncNodeCWDashboardJSON()), { + INSTANCE_ID: singleNode.instanceId, + INSTANCE_NAME: `${resourceNamePrefix}Instance`, + REGION: region, + }); + + new cw.CfnDashboard(this, 'single-cw-dashboard', { + dashboardName: `AlloraStack-${singleNode.instanceId}`, + dashboardBody: dashboardString, + }); + + new cdk.CfnOutput(this, "node-instance-id", { + value: singleNode.instanceId, + }); + + // Elastic IP + const eip = new ec2.CfnEIP(this, `${resourceNamePrefix}EIP`); + new ec2.CfnEIPAssociation(this, `${resourceNamePrefix}EIPAssociation`, { + eip: eip.ref, + instanceId: singleNode.instanceId, + }); + + nag.NagSuppressions.addResourceSuppressions( + this, + [ + { + id: "AwsSolutions-EC23", + reason: "Inbound access from any IP is required for this application.", + }, + { + id: "AwsSolutions-IAM4", + reason: "This IAM role requires broad permissions to function correctly.", + }, + { + id: "AwsSolutions-IAM5", + reason: "Full access is needed for administrative tasks.", + }, + { + id: "AwsSolutions-S1", + reason: "Server-side encryption is not required for this bucket.", + }, + { + id: "AwsSolutions-EC2", + reason: "Unrestricted access is required for the instance to operate correctly.", + }, + { + id: "AwsSolutions-AS3", + reason: "No notifications needed for this specific application.", + }, + { + id: "AwsSolutions-S2", + reason: "Access logging is not necessary for this bucket.", + }, + { + id: "AwsSolutions-S10", + reason: "HTTPS requirement is not needed for this bucket.", + }, + ], + true + ); + } +} diff --git a/lib/allora/lib/assets/node-cw-dashboard.ts b/lib/allora/lib/assets/node-cw-dashboard.ts new file mode 100644 index 00000000..7115919a --- /dev/null +++ b/lib/allora/lib/assets/node-cw-dashboard.ts @@ -0,0 +1,579 @@ +export const SyncNodeCWDashboardJSON = () => { + + let disk_name: string = "sda1"; + + return { + "widgets": [ + { + "height": 5, + "width": 6, + "y": 0, + "x": 0, + "type": "metric", + "properties": { + "view": "timeSeries", + "stat": "Average", + "period": 300, + "stacked": false, + "yAxis": { + "left": { + "min": 0 + } + }, + "region": "${REGION}", + "metrics": [ + [ "AWS/EC2", "CPUUtilization", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] + ], + "title": "CPU utilization (%)" + } + }, + { + "height": 5, + "width": 6, + "y": 5, + "x": 18, + "type": "metric", + "properties": { + "view": "timeSeries", + "stat": "Average", + "period": 300, + "stacked": false, + "yAxis": { + "left": { + "min": 0 + } + }, + "region": "${REGION}", + "metrics": [ + [ "AWS/EC2", "NetworkIn", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] + ], + "title": "Network in (bytes)" + } + }, + { + "height": 5, + "width": 6, + "y": 0, + "x": 18, + "type": "metric", + "properties": { + "view": "timeSeries", + "stat": "Average", + "period": 300, + "stacked": false, + "yAxis": { + "left": { + "min": 0 + } + }, + "region": "${REGION}", + "metrics": [ + [ "AWS/EC2", "NetworkOut", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] + ], + "title": "Network out (bytes)" + } + }, + { + "height": 5, + "width": 6, + "y": 10, + "x": 0, + "type": "metric", + "properties": { + "view": "timeSeries", + "stacked": false, + "region": "${REGION}", + "stat": "Average", + "period": 300, + "metrics": [ + [ "CWAgent", "mem_used_percent", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] + ], + "title": "Mem Used (%)" + } + }, + { + "height": 5, + "width": 6, + "y": 5, + "x": 0, + "type": "metric", + "properties": { + "view": "timeSeries", + "stacked": false, + "region": "${REGION}", + "stat": "Average", + "period": 300, + "metrics": [ + [ "CWAgent", "cpu_usage_iowait", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] + ], + "title": "CPU Usage IO wait (%)" + } + }, + { + "height": 5, + "width": 6, + "y": 0, + "x": 6, + "type": "metric", + "properties": { + "metrics": [ + [ { "expression": "m7/PERIOD(m7)", "label": "Read", "id": "e7" } ], + [ "CWAgent", "diskio_reads", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m7", "visible": false, "stat": "Sum", "period": 60 } ], + [ { "expression": "m8/PERIOD(m8)", "label": "Write", "id": "e8" } ], + [ "CWAgent", "diskio_writes", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m8", "visible": false, "stat": "Sum", "period": 60 } ] + ], + "view": "timeSeries", + "stacked": false, + "region": "${REGION}", + "stat": "Sum", + "period": 60, + "title": `${disk_name} Volume Read/Write (IO/sec)` + } + }, + { + "height": 4, + "width": 6, + "y": 0, + "x": 12, + "type": "metric", + "properties": { + "metrics": [ + [ "CWAgent", "allora_current_block_height", "InstanceId", "${INSTANCE_ID}", { "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] + ], + "sparkline": true, + "view": "timeSeries", + "stacked": false, + "region": "${REGION}", + "stat": "Maximum", + "period": 60, + "title": "Allora Client Block Height" + } + }, + { + "height": 4, + "width": 6, + "y": 4, + "x": 12, + "type": "metric", + "properties": { + "sparkline": true, + "view": "timeSeries", + "stacked": false, + "region": "${REGION}", + "stat": "Maximum", + "period": 60, + "metrics": [ + [ { "expression": "SELECT COUNT(edge_peer) FROM CWAgent GROUP BY InstanceId", "label": "${INSTANCE_ID}-${INSTANCE_NAME}" } ] + ], + "title": "Allora Client Peer Count" + } + }, + { + "height": 5, + "width": 6, + "y": 5, + "x": 6, + "type": "metric", + "properties": { + "view": "timeSeries", + "stat": "Sum", + "period": 60, + "stacked": false, + "yAxis": { + "left": { + "min": 0 + } + }, + "region": "${REGION}", + "metrics": [ + [ { "expression": "IF(m7_2 !=0, (m7_1 / m7_2), 0)", "label": "Read", "id": "e7" } ], + [ "CWAgent", "diskio_read_time", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m7_1", "visible": false, "stat": "Sum", "period": 60 } ], + [ "CWAgent", "diskio_reads", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m7_2", "visible": false, "stat": "Sum", "period": 60 } ], + [ { "expression": "IF(m7_4 !=0, (m7_3 / m7_4), 0)", "label": "Write", "id": "e8" } ], + [ "CWAgent", "diskio_write_time", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m7_3", "visible": false, "stat": "Sum", "period": 60 } ], + [ "CWAgent", "diskio_writes", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m7_4", "visible": false, "stat": "Sum", "period": 60 } ] + ], + "title": `${disk_name} Volume Read/Write latency (ms/op)` + } + }, + { + "height": 5, + "width": 6, + "y": 10, + "x": 6, + "type": "metric", + "properties": { + "metrics": [ + [ { "expression": "(m2/1048576)/PERIOD(m2)", "label": "Read", "id": "e2", "period": 60, "region": "${REGION}" } ], + [ "CWAgent", "diskio_read_bytes", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m2", "stat": "Sum", "visible": false, "period": 60 } ], + [ { "expression": "(m3/1048576)/PERIOD(m3)", "label": "Write", "id": "e3", "period": 60, "region": "${REGION}" } ], + [ "CWAgent", "diskio_write_bytes", "InstanceId", "${INSTANCE_ID}", "name", disk_name, { "id": "m3", "stat": "Sum", "visible": false, "period": 60 } ] + ], + "view": "timeSeries", + "stacked": false, + "region": "${REGION}", + "stat": "Average", + "period": 60, + "title": `${disk_name} Volume Read/Write throughput (MiB/sec)` + } + }, + { + "height": 3, + "width": 6, + "y": 15, + "x": 6, + "type": "metric", + "properties": { + "metrics": [ + [ "CWAgent", "disk_used_percent", "path", "/data", "InstanceId", "${INSTANCE_ID}", "device", disk_name, "fstype", "ext4", { "region": "${REGION}", "label": "/data" } ] + ], + "sparkline": true, + "view": "singleValue", + "region": "${REGION}", + "title": `${disk_name} Disk Used (%)`, + "period": 60, + "stat": "Average" + } + } + ] + } + +} + +const instance_storage_types = new Set(["d3en.12xlarge", + "d3en.8xlarge", + "d3en.6xlarge", + "d3en.4xlarge", + "g6.48xlarge", + "i3en.24xlarge", + "i3en.metal", + "d3en.2xlarge", + "d2.8xlarge", + "d3.8xlarge", + "p5.48xlarge", + "i3en.12xlarge", + "i4i.metal", + "i4i.32xlarge", + "im4gn.16xlarge", + "is4gen.8xlarge", + "d3en.xlarge", + "d2.4xlarge", + "d3.4xlarge", + "i4i.24xlarge", + "h1.16xlarge", + "i3.16xlarge", + "g6.24xlarge", + "i3.metal", + "g6.12xlarge", + "i3en.6xlarge", + "i4i.16xlarge", + "is4gen.4xlarge", + "i4g.16xlarge", + "im4gn.8xlarge", + "d2.2xlarge", + "d3.2xlarge", + "i4i.12xlarge", + "p4d.24xlarge", + "h1.8xlarge", + "r6idn.metal", + "m6id.32xlarge", + "c6id.32xlarge", + "r6id.32xlarge", + "c6id.metal", + "r6id.metal", + "m6idn.metal", + "m6id.metal", + "g5.48xlarge", + "r6idn.32xlarge", + "i3.8xlarge", + "m6idn.32xlarge", + "trn1.32xlarge", + "trn1n.32xlarge", + "im4gn.4xlarge", + "i3en.3xlarge", + "i4i.8xlarge", + "i4g.8xlarge", + "is4gen.2xlarge", + "i2.8xlarge", + "d2.xlarge", + "d3.xlarge", + "r6id.24xlarge", + "m6id.24xlarge", + "c6id.24xlarge", + "r6idn.24xlarge", + "m6idn.24xlarge", + "i3en.2xlarge", + "h1.4xlarge", + "dl1.24xlarge", + "x1e.32xlarge", + "x1.32xlarge", + "x2iedn.32xlarge", + "x2idn.metal", + "r7gd.metal", + "r6id.16xlarge", + "m6id.16xlarge", + "m7gd.16xlarge", + "x2iedn.metal", + "g5.12xlarge", + "r6idn.16xlarge", + "x2gd.metal", + "m6gd.16xlarge", + "c5ad.24xlarge", + "c6id.16xlarge", + "m7gd.metal", + "m6gd.metal", + "c7gd.metal", + "r7gd.16xlarge", + "m6idn.16xlarge", + "c7gd.16xlarge", + "c6gd.16xlarge", + "x2idn.32xlarge", + "g5.24xlarge", + "i3.4xlarge", + "x2gd.16xlarge", + "r6gd.16xlarge", + "g6.16xlarge", + "r6gd.metal", + "c6gd.metal", + "f1.16xlarge", + "i4i.4xlarge", + "i4g.4xlarge", + "im4gn.2xlarge", + "is4gen.xlarge", + "c5d.24xlarge", + "c5d.metal", + "r5d.24xlarge", + "m5d.24xlarge", + "m5dn.24xlarge", + "m5d.metal", + "r5ad.24xlarge", + "r5dn.24xlarge", + "m5ad.24xlarge", + "r5dn.metal", + "m5dn.metal", + "r5d.metal", + "i2.4xlarge", + "r6idn.12xlarge", + "c6id.12xlarge", + "m6id.12xlarge", + "x2iedn.24xlarge", + "m6gd.12xlarge", + "m6idn.12xlarge", + "c7gd.12xlarge", + "x2idn.24xlarge", + "r6id.12xlarge", + "r6gd.12xlarge", + "r7gd.12xlarge", + "c6gd.12xlarge", + "m7gd.12xlarge", + "x2gd.12xlarge", + "i3en.xlarge", + "m5ad.16xlarge", + "r5d.16xlarge", + "m5dn.16xlarge", + "r5ad.16xlarge", + "m5d.16xlarge", + "r5dn.16xlarge", + "c5ad.16xlarge", + "g4ad.16xlarge", + "h1.2xlarge", + "x1e.16xlarge", + "x1.16xlarge", + "r6id.8xlarge", + "r6idn.8xlarge", + "x2iedn.16xlarge", + "m6id.8xlarge", + "i3.2xlarge", + "x2idn.16xlarge", + "c6id.8xlarge", + "g5.16xlarge", + "r6gd.8xlarge", + "m6gd.8xlarge", + "c6gd.8xlarge", + "m6idn.8xlarge", + "x2gd.8xlarge", + "r7gd.8xlarge", + "m7gd.8xlarge", + "c7gd.8xlarge", + "i4i.2xlarge", + "im4gn.xlarge", + "i4g.2xlarge", + "is4gen.large", + "m5dn.12xlarge", + "r5d.12xlarge", + "z1d.12xlarge", + "c5d.18xlarge", + "r5dn.12xlarge", + "m5d.12xlarge", + "c5d.12xlarge", + "c5ad.12xlarge", + "g4dn.metal", + "m5ad.12xlarge", + "r5ad.12xlarge", + "z1d.metal", + "p3dn.24xlarge", + "m1.xlarge", + "m2.4xlarge", + "c1.xlarge", + "i2.2xlarge", + "i3en.large", + "m5d.8xlarge", + "r5dn.8xlarge", + "m5dn.8xlarge", + "c5ad.8xlarge", + "r5d.8xlarge", + "m5ad.8xlarge", + "r5ad.8xlarge", + "g4ad.8xlarge", + "x1e.8xlarge", + "r6idn.4xlarge", + "c6id.4xlarge", + "r6gd.4xlarge", + "r6id.4xlarge", + "c7gd.4xlarge", + "m6id.4xlarge", + "m6gd.4xlarge", + "m6idn.4xlarge", + "i3.xlarge", + "r7gd.4xlarge", + "x2iedn.8xlarge", + "c6gd.4xlarge", + "m7gd.4xlarge", + "x2gd.4xlarge", + "f1.4xlarge", + "i4i.xlarge", + "im4gn.large", + "i4g.xlarge", + "is4gen.medium", + "c5d.9xlarge", + "g5.8xlarge", + "g4dn.8xlarge", + "g4dn.12xlarge", + "z1d.6xlarge", + "gr6.8xlarge", + "g4dn.16xlarge", + "g6.8xlarge", + "m2.2xlarge", + "m1.large", + "i2.xlarge", + "r3.8xlarge", + "c3.8xlarge", + "r5ad.4xlarge", + "r5d.4xlarge", + "c5ad.4xlarge", + "g5.4xlarge", + "g6.4xlarge", + "m5dn.4xlarge", + "m5d.4xlarge", + "m5ad.4xlarge", + "r5dn.4xlarge", + "gr6.4xlarge", + "g4ad.4xlarge", + "x1e.4xlarge", + "x2iedn.4xlarge", + "x2gd.2xlarge", + "i3.large", + "m6id.2xlarge", + "r6id.2xlarge", + "c6id.2xlarge", + "m6idn.2xlarge", + "m7gd.2xlarge", + "c7gd.2xlarge", + "r7gd.2xlarge", + "r6gd.2xlarge", + "m6gd.2xlarge", + "c6gd.2xlarge", + "r6idn.2xlarge", + "trn1.2xlarge", + "f1.2xlarge", + "i4g.large", + "i4i.large", + "z1d.3xlarge", + "g6.2xlarge", + "g5.2xlarge", + "m2.xlarge", + "m1.medium", + "c5d.4xlarge", + "c1.medium", + "r3.4xlarge", + "c3.4xlarge", + "m5d.2xlarge", + "z1d.2xlarge", + "r5ad.2xlarge", + "m5dn.2xlarge", + "c5ad.2xlarge", + "m5ad.2xlarge", + "r5dn.2xlarge", + "r5d.2xlarge", + "g4ad.2xlarge", + "g6.xlarge", + "g5.xlarge", + "x1e.2xlarge", + "m6gd.xlarge", + "m6id.xlarge", + "m6idn.xlarge", + "c7gd.xlarge", + "x2iedn.2xlarge", + "c6id.xlarge", + "r6id.xlarge", + "c6gd.xlarge", + "r7gd.xlarge", + "m7gd.xlarge", + "r6gd.xlarge", + "r6idn.xlarge", + "x2gd.xlarge", + "g4dn.2xlarge", + "g4dn.4xlarge", + "c5d.2xlarge", + "r3.2xlarge", + "m3.2xlarge", + "c3.2xlarge", + "m1.small", + "r5ad.xlarge", + "r5dn.xlarge", + "m5dn.xlarge", + "m5ad.xlarge", + "g4ad.xlarge", + "c5ad.xlarge", + "m5d.xlarge", + "r5d.xlarge", + "z1d.xlarge", + "g4dn.xlarge", + "x1e.xlarge", + "r7gd.large", + "x2iedn.xlarge", + "c6id.large", + "c7gd.large", + "r6id.large", + "m6id.large", + "r6idn.large", + "m7gd.large", + "m6idn.large", + "c6gd.large", + "x2gd.large", + "m6gd.large", + "r6gd.large", + "c5d.xlarge", + "r3.xlarge", + "c3.xlarge", + "m3.xlarge", + "z1d.large", + "c5ad.large", + "r5dn.large", + "m5ad.large", + "m5d.large", + "r5d.large", + "r5ad.large", + "m5dn.large", + "m7gd.medium", + "m6gd.medium", + "r7gd.medium", + "c6gd.medium", + "x2gd.medium", + "r6gd.medium", + "c7gd.medium", + "c5d.large", + "r3.large", + "c3.large", + "m3.large", + "m3.medium"]) \ No newline at end of file diff --git a/lib/allora/lib/assets/user-data/node.sh b/lib/allora/lib/assets/user-data/node.sh new file mode 100644 index 00000000..5bec6c94 --- /dev/null +++ b/lib/allora/lib/assets/user-data/node.sh @@ -0,0 +1,182 @@ +#!/bin/bash +echo "----------------------------------------------" +echo "[user-data] STARTING ALLORA USER DATA SCRIPT" +echo "----------------------------------------------" + +echo "AWS_REGION=${_AWS_REGION_}" >> /etc/environment +echo "ASSETS_S3_PATH=${_ASSETS_S3_PATH_}" >> /etc/environment +echo "RESOURCE_ID=${_NODE_CF_LOGICAL_ID_}" >> /etc/environment +echo "STACK_NAME=${_STACK_NAME_}" >> /etc/environment +echo "STACK_ID=${_STACK_ID_}" >> /etc/environment + +echo "ALLORA_WORKER_NAME=${_ALLORA_WORKER_NAME_}" >> /etc/environment +echo "ALLORA_ENV=${_ALLORA_ENV_}" >> /etc/environment +echo "MODEL_REPO=${_MODEL_REPO_}" >> /etc/environment +echo -e "MODEL_ENV_VARS='${_MODEL_ENV_VARS_}'" >> /etc/environment + + +echo "ALLORA_WALLET_ADDRESS_KEY_NAME=${_ALLORA_WALLET_ADDRESS_KEY_NAME_}" >> /etc/environment +echo "ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC=${_ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC_}" >> /etc/environment +echo "ALLORA_WALLET_HOME_DIR=${_ALLORA_WALLET_HOME_DIR_}" >> /etc/environment +echo "ALLORA_WALLET_GAS_ADJUSTMENT=${_ALLORA_WALLET_GAS_ADJUSTMENT_}" >> /etc/environment +echo "ALLORA_WALLET_GAS=${_ALLORA_WALLET_GAS_}" >> /etc/environment + +#new props +echo "ALLORA_WALLET_GAS_PRICES=${_ALLORA_WALLET_GAS_PRICES_}" >> /etc/environment +echo "ALLORA_WALLET_GAS_PRICE_INTERVAL=${_ALLORA_WALLET_GAS_PRICE_INTERVAL_}" >> /etc/environment +echo "ALLORA_WALLET_RETRY_DELAY=${_ALLORA_WALLET_RETRY_DELAY_}" >> /etc/environment +echo "ALLORA_WALLET_BLOCK_DURATION_ESTIMATED=${_ALLORA_WALLET_BLOCK_DURATION_ESTIMATED_}" >> /etc/environment +echo "ALLORA_WALLET_WINDOW_CORRECTION_FACTOR=${_ALLORA_WALLET_WINDOW_CORRECTION_FACTOR_}" >> /etc/environment +echo "ALLORA_WALLET_MAX_FEES=${_ALLORA_WALLET_MAX_FEES_}" >> /etc/environment +echo "ALLORA_WALLET_ACCOUNT_SEQUENCE_RETRY_DELAY=${_ALLORA_WALLET_ACCOUNT_SEQUENCE_RETRY_DELAY_}" >> /etc/environment +#/new props + + +echo "ALLORA_WALLET_NODE_RPC=${_ALLORA_WALLET_NODE_RPC_}" >> /etc/environment +echo "ALLORA_WALLET_MAX_RETRIES=${_ALLORA_WALLET_MAX_RETRIES_}" >> /etc/environment +echo "ALLORA_WALLET_DELAY=${_ALLORA_WALLET_DELAY_}" >> /etc/environment +echo "ALLORA_WALLET_SUBMIT_TX=${_ALLORA_WALLET_SUBMIT_TX_}" >> /etc/environment + +echo "ALLORA_WORKER_TOPIC_ID=${_ALLORA_WORKER_TOPIC_ID_}" >> /etc/environment +echo "ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME=${_ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME_}" >> /etc/environment +echo "ALLORA_WORKER_INFERENCE_ENDPOINT=${_ALLORA_WORKER_INFERENCE_ENDPOINT_}" >> /etc/environment +echo "ALLORA_WORKER_LOOP_SECONDS=${_ALLORA_WORKER_LOOP_SECONDS_}" >> /etc/environment +echo "ALLORA_WORKER_TOKEN=${_ALLORA_WORKER_TOKEN_}" >> /etc/environment + +echo "ALLORA_REPUTER_TOPIC_ID=${_ALLORA_REPUTER_TOPIC_ID_}" >> /etc/environment +echo "ALLORA_REPUTER_ENTRYPOINT_NAME=${_ALLORA_REPUTER_ENTRYPOINT_NAME_}" >> /etc/environment +echo "ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT=${_ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT_}" >> /etc/environment + +#new props +echo "ALLORA_REPUTER_LOSS_FUNCTION_SERVICE=${_ALLORA_REPUTER_LOSS_FUNCTION_SERVICE_}" >> /etc/environment +echo "ALLORA_REPUTER_LOSS_METHOD_OPTIONS_LOSS_METHOD=${_ALLORA_REPUTER_LOSS_METHOD_OPTIONS_LOSS_METHOD_}" >> /etc/environment +#/new props + +echo "ALLORA_REPUTER_LOOP_SECONDS=${_ALLORA_REPUTER_LOOP_SECONDS_}" >> /etc/environment +echo "ALLORA_REPUTER_TOKEN=${_ALLORA_REPUTER_TOKEN_}" >> /etc/environment +echo "ALLORA_REPUTER_MIN_STAKE=${_ALLORA_REPUTER_MIN_STAKE_}" >> /etc/environment + + +source /etc/environment + +echo "Updating and installing required system packages" +sudo yum update -y +amazon-linux-extras install epel -y +sudo yum groupinstall "Development Tools" -y +sudo yum -y install python3-pip amazon-cloudwatch-agent collectd jq gcc10-10.5.0-1.amzn2.0.2 ncurses-devel telnet aws-cfn-bootstrap + +#install Allora CLI tool with pip +sudo pip3 install allocmd --upgrade + +# Install Git +sudo yum install git -y + +# Install docker +sudo yum install docker -y + +# Add the current user to the docker permissions group +sudo usermod -aG docker ec2-user + +# Enable docker service at AMI boot time +sudo systemctl enable docker.service + +# Start the docker service +sudo systemctl start docker.service + +# Install docker-compose +sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose +docker-compose version + + +#install AWS CLI +echo 'Installing AWS CLI v2' +curl https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o "awscliv2.zip" +unzip -q awscliv2.zip +./aws/install +rm /usr/bin/aws +ln /usr/local/bin/aws /usr/bin/aws + +cfn-signal -e $? --stack $STACK_NAME --resource $RESOURCE_ID --region $AWS_REGION + +# clone node repo +cd ~ +git clone https://github.com/allora-network/allora-offchain-node.git node-repo +cd node-repo +git checkout $ALLORA_ENV + +cp config.cdk.json.template config.json +cd +#wallet config str replace +sed -i "s/_ALLORA_WALLET_ADDRESS_KEY_NAME_/$ALLORA_WALLET_ADDRESS_KEY_NAME/" config.json +sed -i "s/_ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC_/$ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC/" config.json +sed -i "s/_ALLORA_WALLET_HOME_DIR_/$ALLORA_WALLET_HOME_DIR/" config.json +sed -i "s/_ALLORA_WALLET_GAS_ADJUSTMENT_/$ALLORA_WALLET_GAS_ADJUSTMENT/" config.json #must go before + +#new props +sed -i "s/_ALLORA_WALLET_GAS_PRICE_INTERVAL_/$ALLORA_WALLET_GAS_PRICE_INTERVAL/" config.json #must go first +sed -i "s/_ALLORA_WALLET_GAS_PRICES_/$ALLORA_WALLET_GAS_PRICES/" config.json +sed -i "s/_ALLORA_WALLET_GAS_/$ALLORA_WALLET_GAS/" config.json #has to go last of the gas +sed -i "s/_ALLORA_WALLET_MAX_FEES_/$ALLORA_WALLET_MAX_FEES/" config.json +sed -i "s/_ALLORA_WALLET_RETRY_DELAY_/$ALLORA_WALLET_RETRY_DELAY/" config.json +sed -i "s/_ALLORA_WALLET_BLOCK_DURATION_ESTIMATED_/$ALLORA_WALLET_BLOCK_DURATION_ESTIMATED/" config.json +sed -i "s/_ALLORA_WALLET_WINDOW_CORRECTION_FACTOR_/$ALLORA_WALLET_WINDOW_CORRECTION_FACTOR/" config.json +sed -i "s/_ALLORA_WALLET_ACCOUNT_SEQUENCE_RETRY_DELAY_/$ALLORA_WALLET_ACCOUNT_SEQUENCE_RETRY_DELAY/" config.json + +#/new props + +sed -i "s#_ALLORA_WALLET_NODE_RPC_#$ALLORA_WALLET_NODE_RPC#" config.json +sed -i "s/_ALLORA_WALLET_MAX_RETRIES_/$ALLORA_WALLET_MAX_RETRIES/" config.json +sed -i "s/_ALLORA_WALLET_DELAY_/$ALLORA_WALLET_DELAY/" config.json #@deprecated +sed -i "s/_ALLORA_WALLET_SUBMIT_TX_/$ALLORA_WALLET_SUBMIT_TX/" config.json #@deprecated + +#worker config str replace +sed -i "s/_ALLORA_WORKER_TOPIC_ID_/$ALLORA_WORKER_TOPIC_ID/" config.json +sed -i "s/_ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME_/$ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME/" config.json +sed -i "s#_ALLORA_WORKER_INFERENCE_ENDPOINT_#$ALLORA_WORKER_INFERENCE_ENDPOINT#" config.json +sed -i "s/_ALLORA_WORKER_LOOP_SECONDS_/$ALLORA_WORKER_LOOP_SECONDS/" config.json #@deprecated +sed -i "s/_ALLORA_WORKER_TOKEN_/$ALLORA_WORKER_TOKEN/" config.json + +#reputer config str replace +sed -i "s/_ALLORA_REPUTER_TOPIC_ID_/$ALLORA_REPUTER_TOPIC_ID/" config.json +sed -i "s/_ALLORA_REPUTER_ENTRYPOINT_NAME_/$ALLORA_REPUTER_ENTRYPOINT_NAME/" config.json +sed -i "s#_ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT_#$ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT#" config.json + +#new props +sed -i "s#_ALLORA_REPUTER_LOSS_FUNCTION_SERVICE_#$ALLORA_REPUTER_LOSS_FUNCTION_SERVICE#" config.json +sed -i "s/_ALLORA_REPUTER_LOSS_METHOD_OPTIONS_LOSS_METHOD_/$ALLORA_REPUTER_LOSS_METHOD_OPTIONS_LOSS_METHOD/" config.json +#/new props + +sed -i "s/_ALLORA_REPUTER_LOOP_SECONDS_/$ALLORA_REPUTER_LOOP_SECONDS/" config.json #@deprecated +sed -i "s/_ALLORA_REPUTER_TOKEN_/$ALLORA_REPUTER_TOKEN/" config.json +sed -i "s/_ALLORA_REPUTER_MIN_STAKE_/$ALLORA_REPUTER_MIN_STAKE/" config.json + +#pull in model repo +echo 'Pulling in the model repo ' +echo $MODEL_REPO +cd ~/node-repo/adapter/api +rm -rf source +git clone $MODEL_REPO source + +#build node +echo 'Building inner node' +cd source + +cp ~/node-repo/config.json config.json + +echo -e "$MODEL_ENV_VARS" >> .env + + +#build basic worker +echo 'building basic worker' +chmod +x init.config +./init.config +docker-compose up --build + +echo "----------------------------------------------" +echo "[user-data] Allora user-data script successful" +echo "----------------------------------------------" + + +#ping the server for an inference response to $ALLORA_WORKER_INFERENCE_ENDPOINT/inference/$ALLORA_WORKER_TOKEN +curl "$ALLORA_WORKER_INFERENCE_ENDPOINT/inference/$ALLORA_WORKER_TOKEN" \ No newline at end of file diff --git a/lib/allora/lib/common-stack.ts b/lib/allora/lib/common-stack.ts new file mode 100644 index 00000000..dc687ef4 --- /dev/null +++ b/lib/allora/lib/common-stack.ts @@ -0,0 +1,54 @@ +import * as cdk from "aws-cdk-lib"; +import * as cdkConstructs from "constructs"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as secrets from "aws-cdk-lib/aws-secretsmanager"; +import * as nag from "cdk-nag"; + +export interface EdgeCommonStackProps extends cdk.StackProps { + +} + +export class EdgeCommonStack extends cdk.Stack { + AWS_STACK_NAME = cdk.Stack.of(this).stackName; + AWS_ACCOUNT_ID = cdk.Stack.of(this).account; + + constructor(scope: cdkConstructs.Construct, id: string, props: EdgeCommonStackProps) { + super(scope, id, props); + + const instanceRole = new iam.Role(this, "node-role", { + assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"), + iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy") + + ] + }); + + instanceRole.addToPolicy(new iam.PolicyStatement({ + resources: ["*"], + actions: ["cloudformation:SignalResource"] + })); + + + new cdk.CfnOutput(this, "Instance Role ARN", { + value: instanceRole.roleArn, + exportName: "EdgeNodeInstanceRoleArn" + }); + + // cdk-nag suppressions + nag.NagSuppressions.addResourceSuppressions( + this, + [ + { + id: "AwsSolutions-IAM4", + reason: "AmazonSSMManagedInstanceCore and CloudWatchAgentServerPolicy are restrictive enough" + }, + { + id: "AwsSolutions-IAM5", + reason: "Can't target specific stack: https://github.com/aws/aws-cdk/issues/22657" + } + ], + true + ); + } +} diff --git a/lib/allora/package.json b/lib/allora/package.json new file mode 100644 index 00000000..7e548d40 --- /dev/null +++ b/lib/allora/package.json @@ -0,0 +1,13 @@ +{ + "name": "allora", + "version": "0.1.0", + "bin": { + "allora": "allora.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + } +} \ No newline at end of file diff --git a/lib/allora/sample-configs/.env-sample-full b/lib/allora/sample-configs/.env-sample-full new file mode 100644 index 00000000..386b4161 --- /dev/null +++ b/lib/allora/sample-configs/.env-sample-full @@ -0,0 +1,71 @@ +#allora node run config + +#Must fill all these in +ALLORA_WALLET_ADDRESS_KEY_NAME="xxxxxxxxxxx" +ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC="xxxxxxxxxxx" #if you don't have this, generate with allocmd +AWS_ACCOUNT_ID="xxxxxxxxxxx" +MODEL_REPO="https://github.com/allora-network/basic-coin-prediction-node" +MODEL_ENV_VARS=' +TOKEN="ETH" +TRAINING_DAYS="1" +TIMEFRAME="4h" +MODEL="LinearRegression" +REGION="US" +DATA_PROVIDER="coingecko" +CG_API_KEY="secret" +' +############################### + +AWS_RESOURCE_NAME_PREFIX="AlloraWorkerx" +AWS_REGION="us-east-1" +AWS_INSTANCE_TYPE="t3.medium" +AWS_VPC_MAX_AZS="1" +AWS_VPC_NAT_GATEWAYS="0" +AWS_VPC_CIDR_MASK="24" + +# Data volume configuration +EDGE_DATA_VOL_TYPE="gp3" # Other options: "io1" | "io2" | "gp3" | "instance-store" . IMPORTANT: "instance-store" NOT recommended as it is ephermal and will be reset after stopping the instance. Use "instance-store" option only with instance types that support that feature, like popular for node g4dn, d3, i3en, and i4i instance families +EDGE_DATA_VOL_SIZE="256" # Current required data size to keep both snapshot archive and unarchived version of it (not applicable for "instance-store") +EDGE_DATA_VOL_IOPS="3000" # Max IOPS for EBS volumes (not applicable for "instance-store") +EDGE_DATA_VOL_THROUGHPUT="125" # Max throughput for EBS gp3 volumes (not applicable for "io1" | "io2" | "instance-store") + +#Allora Node Worker Generation Config +ALLORA_WORKER_NAME="aws" +ALLORA_ENV="dev" + +#wallet config +ALLORA_WALLET_HOME_DIR="" +ALLORA_WALLET_GAS="1000000" +ALLORA_WALLET_GAS_ADJUSTMENT="1.0" + +ALLORA_WALLET_GAS_PRICES="auto" +ALLORA_WALLET_GAS_PRICE_INTERVAL="60" +ALLORA_WALLET_RETRY_DELAY="3" +ALLORA_WALLET_BLOCK_DURATION_ESTIMATED="10" +ALLORA_WALLET_WINDOW_CORRECTION_FACTOR="0.8" +ALLORA_WALLET_MAX_FEES="500000" +ALLORA_WALLET_ACCOUNT_SEQUENCE_RETRY_DELAY="5" + +ALLORA_WALLET_NODE_RPC="https://allora-rpc.testnet.allora.network" +ALLORA_WALLET_MAX_RETRIES="1" +ALLORA_WALLET_DELAY="1" +ALLORA_WALLET_SUBMIT_TX="false" + +#worker config +ALLORA_WORKER_TOPIC_ID="1" +ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME="api-worker-reputer" +ALLORA_WORKER_INFERENCE_ENDPOINT="http://source:8000/inference/{Token}" + +ALLORA_REPUTER_LOSS_FUNCTION_SERVICE="http://localhost:5000" +ALLORA_REPUTER_LOSS_METHOD_OPTIONS_LOSS_METHOD="sqe" + +ALLORA_WORKER_LOOP_SECONDS="30" +ALLORA_WORKER_TOKEN="ethereum" + +#reputer config +ALLORA_REPUTER_TOPIC_ID="1" +ALLORA_REPUTER_ENTRYPOINT_NAME="api-worker-reputer" +ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT="http://source:8888/truth/{Token}/{BlockHeight}" +ALLORA_REPUTER_LOOP_SECONDS="30" +ALLORA_REPUTER_TOKEN="ethereum" +ALLORA_REPUTER_MIN_STAKE="100000" diff --git a/lib/allora/test/.env-test b/lib/allora/test/.env-test new file mode 100644 index 00000000..53acdab5 --- /dev/null +++ b/lib/allora/test/.env-test @@ -0,0 +1,49 @@ +#allora node run config + +#Must fill all these in +ALLORA_WALLET_ADDRESS_KEY_NAME="xxxxxxxxxxx" +ALLORA_WALLET_ADDRESS_RESTORE_MNEMONIC="xxxxxxxxxxx" +AWS_ACCOUNT_ID="xxxxxxxxxxx" +MODEL_REPO="https://github.com/allora-network/basic-coin-prediction-node" +############################### + +AWS_RESOURCE_NAME_PREFIX="AlloraWorkerx" +AWS_REGION="us-east-1" +AWS_INSTANCE_TYPE="t3.medium" +AWS_VPC_MAX_AZS="1" +AWS_VPC_NAT_GATEWAYS="0" +AWS_VPC_CIDR_MASK="24" + +# Data volume configuration +EDGE_DATA_VOL_TYPE="gp3" # Other options: "io1" | "io2" | "gp3" | "instance-store" . IMPORTANT: "instance-store" NOT recommended as it is ephermal and will be reset after stopping the instance. Use "instance-store" option only with instance types that support that feature, like popular for node g4dn, d3, i3en, and i4i instance families +EDGE_DATA_VOL_SIZE="256" # Current required data size to keep both snapshot archive and unarchived version of it (not applicable for "instance-store") +EDGE_DATA_VOL_IOPS="3000" # Max IOPS for EBS volumes (not applicable for "instance-store") +EDGE_DATA_VOL_THROUGHPUT="125" # Max throughput for EBS gp3 volumes (not applicable for "io1" | "io2" | "instance-store") + +#Allora Node Worker Generation Config +ALLORA_WORKER_NAME="aws" +ALLORA_ENV="dev" + +#wallet config +ALLORA_WALLET_HOME_DIR="" +ALLORA_WALLET_GAS="1000000" +ALLORA_WALLET_GAS_ADJUSTMENT="1.0" +ALLORA_WALLET_NODE_RPC="https://localhost:26657" +ALLORA_WALLET_MAX_RETRIES="1" +ALLORA_WALLET_DELAY="1" +ALLORA_WALLET_SUBMIT_TX="false" + +#worker config +ALLORA_WORKER_TOPIC_ID="1" +ALLORA_WORKER_INFERENCE_ENTRYPOINT_NAME="api-worker-reputer" +ALLORA_WORKER_INFERENCE_ENDPOINT="http://source:8000/inference/{Token}" +ALLORA_WORKER_LOOP_SECONDS="30" +ALLORA_WORKER_TOKEN="ethereum" + +#reputer config +ALLORA_REPUTER_TOPIC_ID="1" +ALLORA_REPUTER_ENTRYPOINT_NAME="api-worker-reputer" +ALLORA_REPUTER_SOURCE_OF_TRUTH_ENDPOINT="http://source:8888/truth/{Token}/{BlockHeight}" +ALLORA_REPUTER_LOOP_SECONDS="30" +ALLORA_REPUTER_TOKEN="ethereum" +ALLORA_REPUTER_MIN_STAKE="100000" diff --git a/lib/allora/test/allora.test.ts b/lib/allora/test/allora.test.ts new file mode 100644 index 00000000..c6e247dd --- /dev/null +++ b/lib/allora/test/allora.test.ts @@ -0,0 +1,152 @@ +import * as cdk from 'aws-cdk-lib'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import { AlloraStack } from '../lib/allora-stack'; +import * as ec2 from "aws-cdk-lib/aws-ec2"; + +describe("AlloranodeStack", () => { + test('Stack has correct resources', () => { + const app = new cdk.App(); + const stack = new AlloraStack(app, 'TestStack', { + env: { account: 'xxxxxxxxxxx', region: 'us-east-1' }, + instanceType: 't3.medium', + vpcMaxAzs: 1, + vpcNatGateways: 0, + vpcSubnetCidrMask: 24, + resourceNamePrefix: 'AlloraWorkerTest', + dataVolume: { + sizeGiB: 256, + type: ec2.EbsDeviceVolumeType.GP3, + iops: 10000, + throughput: 700 + } + }); + + const template = Template.fromStack(stack); + + // Check for VPC + template.hasResourceProperties('AWS::EC2::VPC', { + CidrBlock: Match.stringLikeRegexp('10.0.0.0/16'), + }); + + // Check for Security Group with inbound TCP 9010 rule + template.hasResourceProperties('AWS::EC2::SecurityGroup', { + GroupDescription: 'Allow inbound TCP 9010', + SecurityGroupIngress: [ + { + IpProtocol: 'tcp', + FromPort: 9010, + ToPort: 9010, + CidrIp: '0.0.0.0/0', + }, + ], + }); + + // Check for EC2 Instance + template.hasResourceProperties('AWS::EC2::Instance', { + InstanceType: 't3.medium', + ImageId: { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amikernel510hvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + BlockDeviceMappings: [ + { + DeviceName: '/dev/sda1', + Ebs: { + VolumeSize: 46, + VolumeType: 'gp3', + }, + }, + ], + }); + + // Check for EIP + template.resourceCountIs('AWS::EC2::EIP', 1); + + // Check for EIP Association + template.hasResourceProperties('AWS::EC2::EIPAssociation', { + InstanceId: Match.anyValue(), + }); + + // Check for S3 Bucket + template.resourceCountIs('AWS::S3::Bucket', 1); + + // Check for Bucket Deployment + template.hasResourceProperties('Custom::CDKBucketDeployment', { + DestinationBucketName: { + 'Ref': Match.anyValue(), + }, + DestinationBucketKeyPrefix: 'user-data', + Prune: true, + ServiceToken: { + 'Fn::GetAtt': [ + Match.anyValue(), + 'Arn' + ] + }, + SourceBucketNames: [ + Match.anyValue(), + ], + SourceObjectKeys: [ + Match.stringLikeRegexp('.*\\.zip') + ] + }); + + // Check for S3 bucket policy to allow read access to the EC2 instance + template.hasResourceProperties('AWS::S3::BucketPolicy', { + Bucket: { + 'Ref': Match.anyValue(), + }, + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: [ + "s3:PutBucketPolicy", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*" + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': [ + Match.anyValue(), + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + Match.anyValue(), + 'Arn' + ] + }, + '/*' + ] + ], + } + ], + Principal: { + AWS: { + 'Fn::GetAtt': [ + Match.anyValue(), + 'Arn', + ], + }, + }, + }), + ]), + Version: '2012-10-17' + }, + }); + + // Has CloudWatch dashboard. + template.hasResourceProperties("AWS::CloudWatch::Dashboard", { + DashboardBody: Match.anyValue(), + DashboardName: { + "Fn::Join": ["", ["AlloraStack-",{ "Ref": Match.anyValue() }]] + } + }) + }); +}); \ No newline at end of file diff --git a/lib/allora/tsconfig.json b/lib/allora/tsconfig.json new file mode 100644 index 00000000..8e1979f3 --- /dev/null +++ b/lib/allora/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "../../node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/website/docs/Blueprints/Allora.md b/website/docs/Blueprints/Allora.md new file mode 100644 index 00000000..4945273f --- /dev/null +++ b/website/docs/Blueprints/Allora.md @@ -0,0 +1,9 @@ +--- +sidebar_position: 10 +sidebar_label: Allora +--- +# + +import Readme from '../../../lib/allora/README.md'; + +