Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

aws lambda - relayer observer #1483

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions aws_lambda/relayers_observer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.swp
package-lock.json
__pycache__
.pytest_cache
.venv
*.egg-info

# CDK asset staging directory
.cdk.staging
cdk.out
31 changes: 31 additions & 0 deletions aws_lambda/relayers_observer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# trunk-ignore-all(trivy/DS002)
# trunk-ignore-all(trivy/DS026)
# trunk-ignore-all(checkov/CKV_DOCKER_2)
# trunk-ignore-all(checkov/CKV_DOCKER_3)
# trunk-ignore-all(hadolint/DL3013)
# trunk-ignore-all(hadolint/DL3033)
FROM amazon/aws-lambda-python:3.10

RUN yum update -y && \
yum install -y gcc gmp-devel && \
yum clean all && \
rm -rf /var/cache/yum

WORKDIR /var/task

RUN pip install --no-cache-dir uv && uv venv

COPY pyproject.toml ./

RUN pip install --no-cache-dir -e '.[lambda-dependencies]'

COPY build ./build
COPY deployments ./deployments
COPY relayers.json ./relayers.json
COPY .env ./
COPY relayers_observer.py ./
COPY constants.py ./kakarot_scripts/constants.py
COPY starknet.py ./kakarot_scripts/utils/starknet.py
COPY data ./kakarot_scripts/data

CMD ["relayers_observer.lambda_handler"]
9 changes: 9 additions & 0 deletions aws_lambda/relayers_observer/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env python3

import aws_cdk as cdk
from relayers_observer_lambda_stack import RelayerObserverLambdaStack

app = cdk.App()
RelayerObserverLambdaStack(app, "RelayerObserverLambdaStack")

app.synth()
71 changes: 71 additions & 0 deletions aws_lambda/relayers_observer/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"app": "python3 app.py",
"watch": {
"include": ["**"],
"exclude": [
"README.md",
"cdk*.json",
"requirements*.txt",
"source.bat",
"**/__init__.py",
"**/__pycache__",
"tests"
]
},
"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-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,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true
}
}
30 changes: 30 additions & 0 deletions aws_lambda/relayers_observer/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[project]
name = "relayers_observer"
version = "0.1.0"
description = "Relayers Observer Lambda function"
requires-python = ">=3.10,<3.11"

[project.optional-dependencies]
lambda-dependencies = [
"starknet-py==0.23.0",
"python-dotenv==0.21.0",
"web3==6",
"async_lru==2.0.4",
"cairo-lang==0.13.1",
"requests==2.32.3",
"eth_keys==0.5.1",
"boto3==1.35.36",
]

cdk-dependencies = ["aws-cdk-lib==2.161.1", "constructs>=10.0.0,<11.0.0"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build]
include = [
"relayers_observer.py",
"relayers_observer_lambda_stack.py",
"app.py",
]
92 changes: 92 additions & 0 deletions aws_lambda/relayers_observer/relayers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
[
{
"address": 950135147883551002471284612127692562371997031991686973344811456814871551703
},
{
"address": 784553024261379269037061621041983249972520011546534893666375886810285652022
},
{
"address": 2016715077620147159803572307858325398159758228128803672826672562207191315116
},
{
"address": 1662920711526725350032789559696699552222118181660561466963176525053308263625
},
{
"address": 3153637514465957585881265164805986785841191541018593438889626498255327177411
},
{
"address": 2766997136495195637890235109180516317074792508593436805931502677369166475499
},
{
"address": 2093590491756438567971438593464470359400203878063146755934968664136245299457
},
{
"address": 1156309487454888498236027337307355210771977936939547064564822895690729329261
},
{
"address": 2375508979001195339092705976533981949661341069936882436634727685307875319193
},
{
"address": 2794561348319831960405086417566258806031170000859851643881352304467811247177
},
{
"address": 3196938585199069588552778059786084432204759834046564491402522960867285987487
},
{
"address": 3249849696581527057698578089564361163387585517848746353802934812359434293529
},
{
"address": 1707748983747411678822551525712719303335815970545789299210384474650319917052
},
{
"address": 871964783514378596548163446256746702153855963543252144050276816814875674558
},
{
"address": 447594387324528764199053850392655034838752348231600003608193346877723837852
},
{
"address": 249732155503971925348549527968576754852836046059112668466387462006740968212
},
{
"address": 3446662058793631682698091747697837875337565532441852226591023978462097914409
},
{
"address": 2431027466263453465882296491914298235363836672287741575367285923951389377303
},
{
"address": 2667345079579245645829382805366852599258219482721527050573445324850349248720
},
{
"address": 1912701013260142734206290515668606917117159045812693977111553625230013542785
},
{
"address": 2448460999699100678054857534135769814992359258605688440449958764415834140201
},
{
"address": 2506017580705275985007775372164438810134467385003111950692305383782396861109
},
{
"address": 2467262930860607918431477448588678974558152179771189301003734902904667082699
},
{
"address": 238052413982080525062062834169368364942253420244711858791548911875984508405
},
{
"address": 1377987273889472543754659361551378469821977441767344463704291075020954302330
},
{
"address": 2096011801106804190494834046291183630742307755777720881520190404439626060392
},
{
"address": 2526397583797409088576194203532356382647146866713022053321340510362096170426
},
{
"address": 408885905545087652195361774298355147012912093936939629684581725287151251217
},
{
"address": 2036100759087474495109507324801202976882523277765123480505872484555089939206
},
{
"address": 1646975286751587104385756489415815854141667265954694456915839184663717755926
}
]
93 changes: 93 additions & 0 deletions aws_lambda/relayers_observer/relayers_observer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import asyncio
import json
import logging

import boto3
import requests

from kakarot_scripts.constants import SLACK_WEBHOOK_URL
from kakarot_scripts.utils.starknet import (
fund_address,
get_balance,
get_eth_contract,
get_starknet_account,
)

client = boto3.client("secretsmanager")
logger = logging.getLogger()
logger.setLevel("INFO")


def lambda_handler(event, context):
return asyncio.get_event_loop().run_until_complete(check_and_fund_relayers())


async def check_and_fund_relayers():
"""
Check the balance of relayer accounts and fund them if necessary.

This function performs the following steps:
1. Loads relayer information from a JSON file.
2. Retrieves the funding account details from AWS Secrets Manager.
3. Checks the balance of the main relayer account.
4. Iterates through all relayers, checking their balances and funding if needed.
"""
# Constants for balance thresholds and funding amount
funding_account_lower_limit = 10 # ETH
relayers_lower_limit = 0.05 # ETH
amount_to_fund = 0.1 # ETH

# Load relayers information from JSON file
with open("relayers.json", "r") as f:
relayers = json.load(f)

# Retrieve secret from AWS Secrets Manager
response = client.get_secret_value(SecretId="relayers_fund_account")
secret_dict = json.loads(response["SecretString"])

address, private_key = next(iter(secret_dict.items()))
account = await get_starknet_account(address, private_key)

# Get ETH contract and check main relayer account balance
eth_contract = await get_eth_contract(account)
balance = await get_balance(account.address, eth_contract)

# Alert if main relayer account balance is lower than the funding_account_lower_limit
if balance / 1e18 < funding_account_lower_limit:
message = f"Fund the relayer account 0x{account.address:064x}. Current balance: {balance / 1e18} ETH"
send_message_to_slack(message)

# Check and fund individual relayer accounts
for relayer in relayers:
relayer_balance = await get_balance(relayer["address"])
if relayer_balance / 1e18 < relayers_lower_limit:
try:
await fund_address(address, amount_to_fund, account)
message = f"Funded address {address} with {amount_to_fund} ETH from {account.address}"
except Exception:
message = f"Failed to fund address {address}"
send_message_to_slack(message)
return {
"statusCode": 500,
"body": json.dumps({"Failed to fund address"}),
}
else:
logger.info(
f"Address {address} has enough balance: {relayer_balance / 1e18} ETH"
)

return {
"statusCode": 200,
}


def send_message_to_slack(message):
msg = {
"channel": "",
"username": "WEBHOOK_USERNAME",
"text": message,
}
resp = requests.post(SLACK_WEBHOOK_URL, json=msg)
logger.info(
{"message": "test", "status_code": resp.status_code, "response": resp.text}
)
Loading
Loading