Skip to content

Commit ca6e5c1

Browse files
author
Simon Goldberg
committed
feedback incorp'd
1 parent 2e5662d commit ca6e5c1

14 files changed

+427
-187
lines changed

lib/bitcoin-core/README.md

Lines changed: 113 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,46 @@ This guide walks you through deploying a Bitcoin Core mainnet node in a **Virtua
1010

1111
---
1212

13+
## Well-Architected
14+
15+
<details>
16+
<summary>Review pros and cons of this solution.</summary>
17+
18+
### Well-Architected Checklist
19+
20+
This is the Well-Architected checklist for **Bitcoin Core node 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.
21+
22+
| Pillar | Control | Question/Check | Remarks |
23+
|:------------------------|:----------------------------------|:---------------------------------------------------------------------------------|:-----------------|
24+
| Security | Network protection | Are there unnecessary open ports in security groups? | Port 8332 (RPC) is restricted to the VPC. |
25+
| | | Traffic inspection | Optional: VPC Flow Logs or traffic mirroring can be enabled for deeper inspection. |
26+
| | Compute protection | Reduce attack surface | This solution uses Amazon Linux 2 AMI. No SSH access is enabled; SSM is used. |
27+
| | | Enable people to perform actions at a distance | This solution uses AWS Systems Manager Session Manager. |
28+
| | Data protection at rest | Use encrypted Amazon Elastic Block Store (Amazon EBS) volumes | Encrypted Amazon EBS volumes are used. |
29+
| | Data protection in transit | Use TLS | The AWS Application Load balancer currently uses HTTP listener. Create HTTPS listener with self signed certificate if TLS is desired. |
30+
| | Authorization and access control | Use instance profile with Amazon Elastic Compute Cloud (Amazon EC2) instances | AWS IAM role is attached to the EC2 instance. |
31+
| | | Following principle of least privilege access | IAM privileges are scoped down to what is necessary. |
32+
| | Application security | Security focused development practices | `cdk-nag` is used with appropriate suppressions. |
33+
| Cost optimization | Service selection | Use cost effective resources | Cost efficient T3 instances 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. |
34+
| Reliability | Resiliency implementation | Withstand component failures | Single node deployment. Can be extended with backup nodes and monitoring. |
35+
| | Resource monitoring | How are workload resources monitored? | Amazon CloudWatch Dashboards track CPU, memory, disk, network, and block height. |
36+
| Performance efficiency | Compute selection | How is compute solution selected? | Compute solution is selected based on performance needs and budget. |
37+
| | Storage selection | How is storage solution selected? | EBS volumes (e.g. gp3 or io2) are selected for consistent throughput and IOPS. |
38+
| Operational excellence | Workload health | How is health of workload determined? | Health is tracked using CloudWatch custom metrics including block height. |
39+
| Sustainability | Hardware & services | Select most efficient hardware for your workload | T3A instances offer efficient memory utilization, reducing power and cost. |
40+
41+
</details>
42+
1343
### Getting Started
1444

45+
#### Open AWS CloudShell
46+
47+
To begin, ensure you login to your AWS account with permissions to create and modify resources in IAM, EC2, EBS, VPC, S3, KMS, and Secrets Manager.
48+
49+
From the AWS Management Console, open the [AWS CloudShell](https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html), a web-based shell environment. If unfamiliar, review the [2-minute YouTube video](https://youtu.be/fz4rbjRaiQM) for an overview and check out [CloudShell with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) that we'll use to test nodes API from internal IP address space.
50+
51+
Once ready, you can run the commands to deploy and test blueprints in the CloudShell.
52+
1553
#### Cloning the Repository
1654

1755
First, clone the repository and install the dependencies:
@@ -24,7 +62,32 @@ npm install
2462

2563
Before proceeding, ensure you have the AWS CLI installed and configured.
2664

27-
### Configuration Management - Generating RPC Authentication
65+
### Configuration
66+
67+
1. Make sure you are in the root directory of the cloned repository.
68+
69+
2. If you have deleted or don't have the default VPC, create default VPC
70+
71+
```
72+
aws ec2 create-default-vpc
73+
```
74+
> **NOTE:** *You may see the following error if the default VPC already exists: `An error occurred (DefaultVpcAlreadyExists) when calling the CreateDefaultVpc operation: A Default VPC already exists for this account in this region.`. That means you can just continue with the following steps.*
75+
76+
3. Create your own copy of `.env` file and edit it to update with your AWS Account ID and Region:
77+
78+
```bash
79+
cd lib/bitcoin
80+
cp ./sample-configs/.env-sample-bitcoin-mainnet .env
81+
vim .env
82+
```
83+
84+
4. Deploy common components such as IAM role:
85+
86+
```bash
87+
npx cdk deploy BitcoinCommonStack
88+
```
89+
90+
### Generating RPC Authentication
2891

2992
To interact with the Bitcoin Core RPC endpoint within your isolated VPC environment, run the following command before deploying the Bitcoin Node via CDK:
3093

@@ -41,53 +104,57 @@ For a deeper dive and an overview of credential rotation, see [RPC Authenticatio
41104
To deploy a single node setup, use the following command:
42105

43106
```
44-
npx cdk deploy SingleNodeBitcoinCoreStack
107+
npx cdk deploy SingleNodeBitcoinCoreStack --outputs-file single-node-outputs.json
45108
```
46109

47110
For High Availability (HA) node deployment, use:
48111

49112
```
50-
npx cdk deploy HABitcoinCoreNodeStack
113+
npx cdk deploy HABitcoinCoreNodeStack --outputs-file ha-nodes-outputs.json
51114
```
52115

53116
### Deployment Architectures for Bitcoin Nodes
54117

55118
#### Single Node Setup
119+
![Single Node Deployment](./doc/assets/Bitcoin-Single-Node-Arch.png)
56120

57-
- A **Bitcoin node** deployed in a **private subnet** continuously synchronizes with the Bitcoin network using outbound connections through a **NAT Gateway**.
58-
- Outbound communication flows through an **Internet Gateway (IGW)**, but the node itself does not have a **public IP address** or **Elastic IP (EIP)**.
59-
- The **NAT Gateway** translates the node's private IP into a public IP for outbound connections, but inbound connections are blocked. This ensures that the node functions as an **outbound-only node** (i.e., it does not accept inbound peer connections), increasing security and reducing data transfer costs.
121+
- A **Bitcoin node** deployed in a **public subnet** continuously synchronizes with the Bitcoin network.
122+
- Outbound peer-to-peer (P2P) communication flows through an **Internet Gateway (IGW)**.
123+
- The node's security group permits incoming P2P connections on port 8333.
124+
- The node's RPC methods can be accessed from within the VPC.
125+
- The Solana node sends various monitoring metrics for both EC2 and Solana nodes to Amazon CloudWatch.
60126

61127
#### High Availability (HA) Setup
128+
![HA Node Deployment](./doc/assets/Bitcoin-HA-Nodes-Arch.png)
62129

63130
- Deploying **multiple Bitcoin nodes** in an **Auto Scaling Group** enhances fault tolerance and availability.
64-
- The nodes' RPC endpoints are exposed through an **Application Load Balancer (ALB)**. The ALB implements session persistence using a "stickiness cookie". This ensures that subsequent requests from the same client are consistently routed to the same node, maintaining session continuity. The stickiness duration is set to 90 minutes but can be configured for up to 7 days.
65-
Note: The Bitcoin Core nodes in the HA setup do not share state (e.g., wallet, mempool)
66-
- HA nodes maintain synchronization through the **NAT Gateway** without exposing the RPC endpoint to the public internet.
131+
- The nodes' RPC endpoints are exposed through an **Application Load Balancer (ALB)**. The ALB implements session persistence using a "stickiness cookie". This ensures that subsequent requests from the same client are consistently routed to the same node, maintaining session continuity. The stickiness duration is set to 90 minutes but can be configured for up to 7 days. Note: The Bitcoin Core nodes in the HA setup do not share state (e.g., wallet, mempool)
132+
- HA nodes do not expose the RPC endpoint to the public internet. This endpoint can be accessed from within the VPC.
67133

68134
---
69135

70-
### Optimizing Data Transfer Costs
71-
72-
By deploying as an **outbound-only node**, data transfer costs are significantly reduced since the node does not serve blockchain data to external peers. With its outbound connections, the node(s) are able to maintain full blockchain synchronization.
73-
74-
---
75136
### Accessing and Using bitcoin-cli on a Bitcoin Core Instance
76137

77138
To interact with your Bitcoin Core instance, you'll need to use AWS Systems Manager, as direct SSH access is not available.
78139

79140
Bitcoin Core supports cookie-based authentication by default, so interacting with the `bitcoin-cli` from the node itself does not require credentials.
80141

81-
Follow these steps to make an RPC call:
142+
From your CloudShell terminal, run the following command to connect to your node via Systems Manager:
143+
144+
```
145+
export INSTANCE_ID=$(jq -r '.SingleNodeBitcoinCoreStack.BitcoinNodeInstanceId' single-node-outputs.json)
146+
echo "INSTANCE_ID="$INSTANCE_ID
147+
aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION
148+
```
82149

83-
1. **Access the Instance:**
84-
- Open the AWS Console and navigate to EC2 Instances.
150+
**Note**: You can alternatively connect to your node via Systems Manager in the AWS Console with the following steps:
151+
- Open the AWS Console and navigate to EC2 Instances.
85152
- Locate and select the instance named `SingleNodeBitcoinCoreStack/BitcoinSingleNode`.
86153
- Click the "Connect" button.
87154
- Choose "Session Manager" from the connection options.
88155
- Select "Connect" to establish a session.
89156

90-
2. **Execute an RPC Call:**
157+
**Execute an RPC Call:**
91158
Once connected, you can interact with the Bitcoin Core node using Docker commands.
92159

93160
To test the RPC interface, use the following command:
@@ -98,7 +165,7 @@ sudo docker exec -it bitcoind bitcoin-cli getblockchaininfo
98165

99166
This command executes the `getblockchaininfo` RPC method, which returns current state information about the blockchain.
100167

101-
3. **Interpreting Results:**
168+
**Interpreting Results:**
102169
- The output will provide detailed information about the current state of the blockchain, including the current block height, difficulty, and other relevant data.
103170
- You can use similar commands to execute other RPC methods supported by Bitcoin Core.
104171

@@ -117,23 +184,37 @@ export BTC_RPC_AUTH=$(aws secretsmanager get-secret-value --secret-id bitcoin_rp
117184
```
118185

119186
#### Single node RPC Call using credentials
120-
To make an RPC call to a single Bitcoin node, use the following command. Replace `[Bitcoin-Node-Private-IP]` with the actual private IP address of your Bitcoin node:
187+
To make an RPC call to a single Bitcoin node, run the following command to retrieve the private IP address of your Bitcoin node:
188+
189+
```
190+
export BITCOIN_NODE_IP=$(jq -r '.SingleNodeBitcoinCoreStack.BitcoinNodePrivateIP' single-node-outputs.json)
191+
echo "BITCOIN_NODE_IP=$BITCOIN_NODE_IP"
192+
```
193+
Copy output from the last `echo` command with `BITCOIN_NODE_IP=<internal_IP>` and open [CloudShell tab with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) to access internal IP address space. Paste `BITCOIN_NODE_IP=<internal_IP>` into the new CloudShell tab. Then query the node:
121194

122195
```
123196
curl --user "$BTC_RPC_AUTH" \
124197
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' \
125-
-H 'content-type: text/plain;' http://[Bitcoin-Node-Private-IP]:8332/
198+
-H 'content-type: text/plain;' http://$BITCOIN_NODE_IP:8332/
126199
```
127200

128201
#### High Availability (HA) RPC Call using credentials
129202

130-
For high availability setups utilizing an Application Load Balancer (ALB), use the following command. Replace `[Load-Balancer-DNS-Name]` with your ALB's DNS name:
203+
Use the following command to retrieve your load balancer's DNS name:
204+
205+
```
206+
export LOAD_BALANCER_DNS=$(jq -r '.HABitcoinCoreNodeStack.LoadBalancerDNS' ha-nodes-outputs.json)
207+
echo LOAD_BALANCER_DNS=$LOAD_BALANCER_DNS
208+
```
209+
Copy output from the last `echo` command with `RPC_ABL_URL=<internal_IP>` and open [CloudShell tab with VPC environment](https://docs.aws.amazon.com/cloudshell/latest/userguide/creating-vpc-environment.html) to access internal IP address space. Paste `RPC_ABL_URL=<internal_IP>` into the new CloudShell tab.
210+
211+
Execute the following command to make an RPC request to your HA node setup:
131212

132213
```
133214
curl --user "$BTC_RPC_AUTH" \
134215
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' \
135216
-H 'content-type: text/plain;' \
136-
[Load Balancer DNS Name]
217+
$LOAD_BALANCER_DNS
137218
```
138219

139220
---
@@ -293,7 +374,15 @@ curl --user "$BTC_RPC_AUTH" \
293374

294375
### Monitoring and Troubleshooting
295376

296-
Keep your node healthy by monitoring logs and configurations:
377+
Keep your node healthy by monitoring logs and configurations.
378+
379+
These can be run after accessing the node via Systems Manager:
380+
381+
```
382+
export INSTANCE_ID=$(jq -r '.SingleNodeBitcoinCoreStack.BitcoinNodeInstanceId' single-node-outputs.json)
383+
echo "INSTANCE_ID="$INSTANCE_ID
384+
aws ssm start-session --target $INSTANCE_ID --region $AWS_REGION
385+
```
297386

298387
- Check recent Bitcoin logs:
299388
```

lib/bitcoin-core/app.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
11
#!/usr/bin/env node
2+
require('dotenv').config(); // Load .env first
23
const cdk = require('aws-cdk-lib');
4+
const { Aspects } = require('aws-cdk-lib');
5+
const { AwsSolutionsChecks } = require('cdk-nag');
6+
const { BitcoinCommonStack } = require('./lib/common-infra');
37
const { SingleNodeBitcoinCoreStack } = require('./lib/single-node-stack');
48
const { HABitcoinCoreNodeStack } = require('./lib/ha-node-stack');
59

610
const app = new cdk.App();
7-
new SingleNodeBitcoinCoreStack(app, 'SingleNodeBitcoinCoreStack');
8-
new HABitcoinCoreNodeStack(app, 'HABitcoinCoreNodeStack');
11+
12+
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
13+
14+
const env = {
15+
account: process.env.AWS_ACCOUNT_ID,
16+
region: process.env.AWS_REGION,
17+
};
18+
19+
const commonStack = new BitcoinCommonStack(app, 'BitcoinCommonStack', { env });
20+
new SingleNodeBitcoinCoreStack(app, 'SingleNodeBitcoinCoreStack', {
21+
env,
22+
instanceRole: commonStack.instanceRole,
23+
});
24+
25+
new HABitcoinCoreNodeStack(app, 'HABitcoinCoreNodeStack', {
26+
env,
27+
instanceRole: commonStack.instanceRole,
28+
});
123 KB
Loading
88.6 KB
Loading
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
# This script is used to set up a mainnet Bitcoin Core node on an Amazon Linux 2 instance.
3+
yum update -y
4+
amazon-linux-extras install docker -y
5+
service docker start
6+
mkdir -p /home/bitcoin/.bitcoin
7+
echo "${BITCOIN_CONF}" > /home/bitcoin/.bitcoin/bitcoin.conf
8+
docker run -d --name bitcoind -v /home/bitcoin/.bitcoin:/root/.bitcoin -p 8333:8333 -p 8332:8332 bitcoin/bitcoin:latest bash -c "chown -R bitcoin:bitcoin /root/.bitcoin && bitcoind"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
# This script is used to set up a cron job to send the Bitcoin block height to Amazon CloudWatch every 5 minutes.
3+
REGION=${AWS_REGION}
4+
(crontab -l 2>/dev/null; echo "*/5 * * * * sudo /usr/bin/docker exec bitcoind bitcoin-cli getblockcount | xargs -I {} sudo /usr/bin/aws cloudwatch put-metric-data --metric-name BlockHeight --namespace Bitcoin --unit Count --value {} --region $REGION") | crontab -
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
# This script is used to set up the Amazon CloudWatch agent on an Amazon Linux 2 instance.
3+
4+
yum install -y amazon-cloudwatch-agent
5+
mkdir -p /opt/aws/amazon-cloudwatch-agent/etc
6+
cat <<EOF > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
7+
{
8+
"metrics": {
9+
"metrics_collected": {
10+
"disk": {
11+
"measurement": ["used_percent", "inodes_free"],
12+
"resources": ["*"],
13+
"ignore_file_system_types": ["sysfs", "devtmpfs"]
14+
},
15+
"mem": {
16+
"measurement": ["mem_used_percent"]
17+
},
18+
"cpu": {
19+
"measurement": ["cpu_usage_idle", "cpu_usage_user", "cpu_usage_system"]
20+
},
21+
"net": {
22+
"measurement": ["net_bytes_sent", "net_bytes_recv"],
23+
"resources": ["eth0"]
24+
}
25+
}
26+
}
27+
}
28+
EOF
29+
30+
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s

lib/bitcoin-core/lib/common-infra.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const cdk = require("aws-cdk-lib");
2+
const iam = require("aws-cdk-lib/aws-iam");
3+
const nag = require("cdk-nag");
4+
5+
class BitcoinCommonStack extends cdk.Stack {
6+
constructor(scope, id, props) {
7+
super(scope, id, props);
8+
9+
this.instanceRole = new iam.Role(this, "BitcoinNodeRole", {
10+
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
11+
managedPolicies: [
12+
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
13+
iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"),
14+
],
15+
});
16+
17+
new cdk.CfnOutput(this, "InstanceRoleArn", {
18+
value: this.instanceRole.roleArn,
19+
exportName: "BitcoinNodeInstanceRoleArn",
20+
});
21+
22+
// cdk-nag suppressions
23+
nag.NagSuppressions.addResourceSuppressions(
24+
this.instanceRole,
25+
[
26+
{
27+
id: "AwsSolutions-IAM4",
28+
reason: "AmazonSSMManagedInstanceCore and CloudWatchAgentServerPolicy are sufficient for this use case.",
29+
},
30+
{
31+
id: "AwsSolutions-IAM5",
32+
reason: "Managed policies and wildcard usage are acceptable for this limited-scope Bitcoin node role.",
33+
},
34+
]
35+
);
36+
}
37+
}
38+
39+
module.exports = { BitcoinCommonStack };
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const { Construct } = require('constructs');
2+
const ec2 = require('aws-cdk-lib/aws-ec2');
3+
4+
class BitcoinSecurityGroup extends Construct {
5+
constructor(scope, id, vpc) {
6+
super(scope, id);
7+
8+
const sg = new ec2.SecurityGroup(this, 'BitcoinSG', {
9+
vpc,
10+
allowAllOutbound: true,
11+
});
12+
13+
sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8333), 'Bitcoin P2P');
14+
sg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(8332), 'Bitcoin RPC from VPC');
15+
16+
this.securityGroup = sg;
17+
}
18+
}
19+
20+
module.exports = { BitcoinSecurityGroup };

0 commit comments

Comments
 (0)