Skip to content

Commit

Permalink
✨ Add erpc service
Browse files Browse the repository at this point in the history
  • Loading branch information
KONFeature committed Jul 24, 2024
1 parent 40080fa commit fa0ff61
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 58 deletions.
15 changes: 14 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: "🔨 Prebuild docker dependencies"
- name: "🔨 Build Ponder docker dependencies"
uses: docker/build-push-action@v6
with:
context: ./packages/ponder
Expand All @@ -81,6 +81,19 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max

- name: "🔨 Build ERPC docker dependencies"
uses: docker/build-push-action@v6
with:
context: ./packages/erpc
platforms: linux/arm64
push: true
tags: |
262732185023.dkr.ecr.eu-west-1.amazonaws.com/erpc:latest
262732185023.dkr.ecr.eu-west-1.amazonaws.com/erpc:${{ github.sha }}
# Github actions cache
cache-from: type=gha
cache-to: type=gha,mode=max

- name: "🚀 SST Deploy"
run: |
echo "Deploying with stage: prod"
Expand Down
27 changes: 27 additions & 0 deletions iac/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Config, type StackContext } from "sst/constructs";

/**
* Simple stack for the config
* @param stack
* @constructor
*/
export function ConfigStack({ stack }: StackContext) {
// RPCs
const rpcSecrets = [
// BlockPi rpcs
new Config.Secret(stack, "BLOCKPI_API_KEY_ARB_SEPOLIA"),
// Alchemy RPC
new Config.Secret(stack, "ALCHEMY_API_KEY"),
];

// Databases
const ponderDb = new Config.Secret(stack, "DATABASE_URL");
const erpcDb = new Config.Secret(stack, "ERPC_DATABASE_URL");

// Return all of that
return {
rpcSecrets,
ponderDb,
erpcDb,
};
}
90 changes: 90 additions & 0 deletions iac/Erpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Repository } from "aws-cdk-lib/aws-ecr";
import { ContainerImage } from "aws-cdk-lib/aws-ecs";
import { Service, type StackContext, use } from "sst/constructs";
import { ConfigStack } from "./Config";
import { buildSecretsMap } from "./utils";

/**
* The CDK stack that will deploy the erpc service
* @param stack
* @constructor
*/
export function ErpcStack({ app, stack }: StackContext) {
// All the secrets env variable we will be using (in local you can just use a .env file)
const { rpcSecrets, erpcDb } = use(ConfigStack);
const secrets = [...rpcSecrets, erpcDb];

// Get our CDK secrets map
const cdkSecretsMap = buildSecretsMap(stack, secrets);

// Get the container props of our prebuilt binaries
const containerRegistry = Repository.fromRepositoryAttributes(
stack,
"ErpcEcr",
{
repositoryArn: `arn:aws:ecr:eu-west-1:${app.account}:repository/erpc`,
repositoryName: "erpc",
}
);

const imageTag = process.env.COMMIT_SHA ?? "latest";
console.log(`Will use the image ${imageTag}`);
const erpcImage = ContainerImage.fromEcrRepository(
containerRegistry,
imageTag
);

// The service itself
const erpcService = new Service(stack, "ErpcService", {
path: "packages/erpc",
port: 4000,
// Domain mapping
customDomain: {
domainName: "erpc.frak.id",
hostedZone: "frak.id",
},
// Setup some capacity options
scaling: {
minContainers: 1,
maxContainers: 1,
cpuUtilization: 90,
memoryUtilization: 90,
},
// Bind the secret we will be using
bind: secrets,
// Arm architecture (lower cost)
architecture: "arm64",
// Hardware config
cpu: "1 vCPU",
memory: "4 GB",
storage: "30 GB",
// Log retention
logRetention: "one_week",
// Set the right environment variables
environment: {
ERPC_LOG_LEVEL: "warn",
},
cdk: {
// Customise fargate service to enable circuit breaker (if the new deployment is failing)
fargateService: {
circuitBreaker: {
enable: true,
},
// Disable rolling update
desiredCount: 1,
minHealthyPercent: 0,
maxHealthyPercent: 100,
},
// Directly specify the image position in the registry here
container: {
containerName: "erpc",
image: erpcImage,
secrets: cdkSecretsMap,
},
},
});

stack.addOutputs({
erpcServiceId: erpcService.id,
});
}
58 changes: 6 additions & 52 deletions iac/Indexer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Repository } from "aws-cdk-lib/aws-ecr";
import { ContainerImage, Secret } from "aws-cdk-lib/aws-ecs";
import { StringParameter } from "aws-cdk-lib/aws-ssm";
import { Config, Service, type Stack, type StackContext } from "sst/constructs";
import { ContainerImage } from "aws-cdk-lib/aws-ecs";
import { Service, type StackContext, use } from "sst/constructs";
import { ConfigStack } from "./Config";
import { buildSecretsMap } from "./utils";

/**
* The CDK stack that will deploy the indexer service
Expand All @@ -10,14 +11,8 @@ import { Config, Service, type Stack, type StackContext } from "sst/constructs";
*/
export function IndexerStack({ app, stack }: StackContext) {
// All the secrets env variable we will be using (in local you can just use a .env file)
const secrets = [
// Db url
new Config.Secret(stack, "DATABASE_URL"),
// BlockPi rpcs
new Config.Secret(stack, "BLOCKPI_API_KEY_ARB_SEPOLIA"),
// Alchemy RPC
new Config.Secret(stack, "ALCHEMY_API_KEY"),
];
const { rpcSecrets, ponderDb } = use(ConfigStack);
const secrets = [...rpcSecrets, ponderDb];

// Get our CDK secrets map
const cdkSecretsMap = buildSecretsMap(stack, secrets);
Expand Down Expand Up @@ -96,45 +91,4 @@ export function IndexerStack({ app, stack }: StackContext) {
stack.addOutputs({
indexerServiceId: indexerService.id,
});

// Set up connections to database via the security group
/*const cluster = indexerService.cdk?.cluster;
if (cluster) {
// Get the security group for the database and link to it
const databaseSecurityGroup = SecurityGroup.fromLookupById(
stack,
"indexer-db-sg",
"sg-0cbbb98322234113f"
);
databaseSecurityGroup.connections.allowFrom(cluster, Port.tcp(5432));
}*/
}

/**
* Build a list of secret name to CDK secret, for direct binding
* @param stack
* @param secrets
*/
function buildSecretsMap(stack: Stack, secrets: Config.Secret[]) {
return secrets.reduce(
(acc, secret) => {
const isSpecificSecret = secret.name === "DATABASE_URL";
const ssmPath = isSpecificSecret
? `/indexer/sst/Secret/${secret.name}/value`
: `/sst/frak-indexer/.fallback/Secret/${secret.name}/value`;

// Add the secret
const stringParameter =
StringParameter.fromSecureStringParameterAttributes(
stack,
`Secret${secret.name}`,
{
parameterName: ssmPath,
}
);
acc[secret.name] = Secret.fromSsmParameter(stringParameter);
return acc;
},
{} as Record<string, Secret>
);
}
34 changes: 34 additions & 0 deletions iac/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Secret } from "aws-cdk-lib/aws-ecs";
import { StringParameter } from "aws-cdk-lib/aws-ssm";
import type { Config, Stack } from "sst/constructs";

const specificSecretsList = ["ERPC_DATABASE_URL", "DATABASE_URL"];

/**
* Build a list of secret name to CDK secret, for direct binding
* @param stack
* @param secrets
*/
export function buildSecretsMap(stack: Stack, secrets: Config.Secret[]) {
return secrets.reduce(
(acc, secret) => {
const isSpecificSecret = specificSecretsList.includes(secret.name);
const ssmPath = isSpecificSecret
? `/indexer/sst/Secret/${secret.name}/value`
: `/sst/frak-indexer/.fallback/Secret/${secret.name}/value`;

// Add the secret
const stringParameter =
StringParameter.fromSecureStringParameterAttributes(
stack,
`Secret${secret.name}`,
{
parameterName: ssmPath,
}
);
acc[secret.name] = Secret.fromSsmParameter(stringParameter);
return acc;
},
{} as Record<string, Secret>
);
}
10 changes: 5 additions & 5 deletions packages/erpc/erpc.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
logLevel: warn
logLevel: ${ERPC_LOG_LEVEL}
database:
# todo: Should use the same postgres as the indexer but on a different schema
evmJsonRpcCache:
Expand Down Expand Up @@ -51,7 +51,7 @@ projects:
upstreams:
- id: alchemy-multi-chain
endpoint: alchemy://${ALCHEMY_API_KEY}
rateLimitBudget: global
rateLimitBudget: alchemy
healthCheckGroup: default-hcg
allowMethods:
- "alchemy_*"
Expand All @@ -68,15 +68,15 @@ projects:
jitter: 500ms
rateLimiters:
budgets:
- id: default-budget
- id: alchemy
rules:
- method: "*"
maxCount: 200
period: 1s
healthChecks:
groups:
- id: default-hcg
checkInterval: 300s
- id: slow-hcg
checkInterval: 600s
maxErrorRatePercent: 10
maxP90LatencyMs: 5s
maxBlocksBehind: 5
4 changes: 4 additions & 0 deletions packages/ponder/src/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export async function increaseCampaignsInteractions({
// Perform the increments
// todo: Should use an `updateMany` if we are sure that campaign stats are created
for (const campaign of campaigns.items) {
if (!campaign.id) {
console.error("Campaign id not found", campaign);
continue;
}
// Create the stats if not found
await PressCampaignStats.upsert({
id: campaign.id,
Expand Down
4 changes: 4 additions & 0 deletions sst.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { SSTConfig } from "sst";
import { ConfigStack } from "./iac/Config";
import { ErpcStack } from "./iac/Erpc";
import { IndexerStack } from "./iac/Indexer";

export default {
Expand Down Expand Up @@ -26,6 +28,8 @@ export default {
tracing: "disabled",
});

app.stack(ConfigStack);
app.stack(ErpcStack);
app.stack(IndexerStack);
},
} satisfies SSTConfig;

0 comments on commit fa0ff61

Please sign in to comment.