Skip to content

Commit 2484005

Browse files
author
Simon Goldberg
committed
refactor to typescript along with several other changes to better align with other bluepritns
1 parent 053576f commit 2484005

22 files changed

+1468
-0
lines changed

lib/bitcoin-core/README.md

Lines changed: 444 additions & 0 deletions
Large diffs are not rendered by default.

lib/bitcoin-core/app.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env node
2+
import 'dotenv/config';
3+
import { App, Aspects } from 'aws-cdk-lib';
4+
import { AwsSolutionsChecks } from 'cdk-nag';
5+
import { BitcoinCommonStack } from './lib/common-infra';
6+
import { SingleNodeBitcoinCoreStack } from './lib/single-node-stack';
7+
import { HABitcoinCoreNodeStack } from './lib/ha-node-stack';
8+
import * as config from './lib/config/bitcoinConfig';
9+
10+
const app = new App();
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+
...config.baseNodeConfig,
24+
});
25+
26+
new HABitcoinCoreNodeStack(app, 'HABitcoinCoreNodeStack', {
27+
env,
28+
instanceRole: commonStack.instanceRole,
29+
...config.baseNodeConfig,
30+
...config.haNodeConfig,
31+
});

lib/bitcoin-core/cdk.json

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts app.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
38+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
39+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
40+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
41+
"@aws-cdk/aws-route53-patters:useCertificate": true,
42+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
43+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
44+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
45+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
46+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
47+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
48+
"@aws-cdk/aws-redshift:columnId": true,
49+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
50+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
51+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
52+
"@aws-cdk/aws-kms:aliasNameRef": true,
53+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
54+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
55+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true
56+
}
57+
}
123 KB
Loading
88.6 KB
Loading

lib/bitcoin-core/jest.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/bin/bash
2+
# This script is used to set up a mainnet Bitcoin Core node on an Amazon Linux 2 instance.
3+
set -euo pipefail
4+
5+
# The stack passes lifecycle hook information when used in an Auto
6+
# Scaling Group. If no lifecycle hook name is supplied (single node
7+
# deployment), default to "none" and use CloudFormation signaling.
8+
LIFECYCLE_HOOK_NAME=${LIFECYCLE_HOOK_NAME:-none}
9+
AUTOSCALING_GROUP_NAME=${AUTOSCALING_GROUP_NAME:-none}
10+
11+
# Ensure the data volume is mounted before proceeding
12+
until mountpoint -q /home/bitcoin; do
13+
echo "Waiting for /home/bitcoin to be mounted..."
14+
sleep 2
15+
done
16+
17+
yum update -y
18+
amazon-linux-extras install docker -y
19+
service docker start
20+
systemctl enable docker
21+
22+
# Create bitcoin user with specific UID:GID to match the container's expected values
23+
if id -u bitcoin > /dev/null 2>&1; then
24+
# User exists, update to correct UID:GID
25+
usermod -u 101 bitcoin
26+
groupmod -g 101 bitcoin
27+
else
28+
# Create user with specific UID:GID
29+
groupadd -g 101 bitcoin
30+
useradd -u 101 -g 101 -m -s /bin/bash bitcoin
31+
fi
32+
33+
# Create the bitcoin data directory structure on the mounted EBS volume
34+
mkdir -p /home/bitcoin/.bitcoin
35+
echo "${BITCOIN_CONF}" > /home/bitcoin/.bitcoin/bitcoin.conf
36+
37+
# Set proper permissions for the Bitcoin configuration
38+
chown -R bitcoin:bitcoin /home/bitcoin
39+
chmod -R 755 /home/bitcoin
40+
41+
# Run Bitcoin Core in Docker with proper volume mapping
42+
# Modified to ensure data is stored on the EBS volume
43+
docker run -d --name bitcoind \
44+
-v /home/bitcoin/.bitcoin:/home/bitcoin/.bitcoin \
45+
-p 8333:8333 \
46+
-p 8332:8332 \
47+
--restart unless-stopped \
48+
bitcoin/bitcoin:latest \
49+
bitcoind -datadir=/home/bitcoin/.bitcoin
50+
51+
# Signal completion depending on deployment type
52+
if [[ "$LIFECYCLE_HOOK_NAME" != "none" ]]; then
53+
echo "Signaling ASG lifecycle hook to complete"
54+
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
55+
INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id)
56+
aws autoscaling complete-lifecycle-action --lifecycle-action-result CONTINUE --instance-id "$INSTANCE_ID" --lifecycle-hook-name "$LIFECYCLE_HOOK_NAME" --auto-scaling-group-name "$AUTOSCALING_GROUP_NAME" --region "$AWS_REGION"
57+
else
58+
if ! command -v cfn-signal &> /dev/null; then
59+
yum install -y aws-cfn-bootstrap
60+
fi
61+
cfn-signal --stack "$STACK_NAME" --resource "$RESOURCE_ID" --region "$AWS_REGION"
62+
fi
63+
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
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
make_fs () {
5+
# If file system = to ext4 use mkfs.ext4, if xfs use mkfs.xfs
6+
if [ -z "$1" ]; then
7+
echo "Error: No file system type provided."
8+
echo "Usage: make_fs <file system type [ xfs | ext4 ]> <target_volume_id>"
9+
exit 1
10+
fi
11+
12+
if [ -z "$2" ]; then
13+
echo "Error: No target volume ID provided."
14+
echo "Usage: make_fs <file system type [ xfs | ext4 ]> <target_volume_id>"
15+
exit 1
16+
fi
17+
18+
local file_system=$1
19+
local volume_id=$2
20+
if [ "$file_system" == "ext4" ]; then
21+
mkfs -t ext4 "$volume_id"
22+
return "$?"
23+
else
24+
mkfs.xfs -f "$volume_id"
25+
return "$?"
26+
fi
27+
}
28+
29+
# We need an nvme disk that is not mounted and not partitioned
30+
get_all_empty_nvme_disks () {
31+
local all_not_mounted_nvme_disks
32+
local all_mounted_nvme_partitions
33+
local unmounted_nvme_disks=()
34+
local sorted_unmounted_nvme_disks
35+
36+
#The disk will only be mounted when the nvme disk is larger than 100GB to avoid storing blockchain node data directly on the root EBS disk (which is 46GB by default)
37+
all_not_mounted_nvme_disks=$(lsblk -lnb | awk '{if ($7 == "" && $4 > 107374182400) {print $1}}' | grep nvme)
38+
all_mounted_nvme_partitions=$(mount | awk '{print $1}' | grep /dev/nvme)
39+
for disk in ${all_not_mounted_nvme_disks[*]}; do
40+
if [[ ! "${all_mounted_nvme_partitions[*]}" =~ $disk ]]; then
41+
unmounted_nvme_disks+=("$disk")
42+
fi
43+
done
44+
# Sort the array
45+
sorted_unmounted_nvme_disks=($(printf '%s\n' "${unmounted_nvme_disks[*]}" | sort))
46+
echo "${sorted_unmounted_nvme_disks[*]}"
47+
}
48+
49+
get_next_empty_nvme_disk () {
50+
local sorted_unmounted_nvme_disks
51+
sorted_unmounted_nvme_disks=($(get_all_empty_nvme_disks))
52+
# Return the first unmounted nvme disk
53+
echo "/dev/${sorted_unmounted_nvme_disks[0]}"
54+
}
55+
56+
# Add input as command line parameters for name of the directory to mount
57+
if [ -n "$1" ]; then
58+
DIR_NAME=$1
59+
else
60+
echo "Error: No data file system mount path is provided."
61+
echo "Usage: instance/storage/setup.sh <file_system_mount_path> <file_system_type [ xfs | ext4 ]> <target_volume_size_in_bytes> "
62+
echo "Default file system type is ext4"
63+
echo "If you skip <target_volume_size_in_bytes>, script will try to use the first unformatted volume ID."
64+
echo "Usage example: instance/storage/setup.sh /data ext4 300000000000000"
65+
exit 1
66+
fi
67+
68+
# Create the directory if it doesn't exist
69+
if [ ! -d "$DIR_NAME" ]; then
70+
echo "Creating directory $DIR_NAME"
71+
mkdir -p "$DIR_NAME"
72+
fi
73+
74+
# Case input for $2 between ext4 and xfs, use ext4 as default
75+
case $2 in
76+
ext4)
77+
echo "File system set to ext4"
78+
FILE_SYSTEM="ext4"
79+
FS_CONFIG="defaults"
80+
;;
81+
xfs)
82+
echo "File system set to xfs"
83+
FILE_SYSTEM="xfs"
84+
FS_CONFIG="noatime,nodiratime,nodiscard" # See more: https://cdrdv2-public.intel.com/686417/rocksdb-benchmark-tuning-guide-on-xeon.pdf
85+
;;
86+
*)
87+
echo "File system set to ext4"
88+
FILE_SYSTEM="ext4"
89+
FS_CONFIG="defaults"
90+
;;
91+
esac
92+
93+
if [ -n "$3" ]; then
94+
VOLUME_SIZE=$3
95+
else
96+
echo "The size of volume for $DIR_NAME is not specified. Will try to guess volume ID."
97+
fi
98+
99+
echo "Checking if $DIR_NAME is mounted, and dont do anything if it is"
100+
if [ $(df --output=target | grep -c "$DIR_NAME") -lt 1 ]; then
101+
102+
if [ -n "$VOLUME_SIZE" ]; then
103+
VOLUME_ID=/dev/$(lsblk -lnb | awk -v VOLUME_SIZE_BYTES="$VOLUME_SIZE" '{if ($4== VOLUME_SIZE_BYTES) {print $1}}')
104+
echo "Data volume size defined, use respective volume id: $VOLUME_ID"
105+
else
106+
VOLUME_ID=$(get_next_empty_nvme_disk)
107+
echo "Data volume size undefined, trying volume id: $VOLUME_ID"
108+
fi
109+
110+
attempts=0
111+
until [ -n "$VOLUME_ID" ] || [ $attempts -ge 30 ]; do
112+
echo "Waiting for data volume to appear..."
113+
sleep 5
114+
if [ -n "$VOLUME_SIZE" ]; then
115+
VOLUME_ID=/dev/$(lsblk -lnb | awk -v VOLUME_SIZE_BYTES="$VOLUME_SIZE" '{if ($4== VOLUME_SIZE_BYTES) {print $1}}')
116+
else
117+
VOLUME_ID=$(get_next_empty_nvme_disk)
118+
fi
119+
attempts=$((attempts+1))
120+
done
121+
122+
if [ -z "$VOLUME_ID" ]; then
123+
echo "Error: Could not find a suitable volume to mount. Listing available disks:"
124+
lsblk -lnb
125+
exit 1
126+
fi
127+
128+
echo "Formatting volume $VOLUME_ID with $FILE_SYSTEM"
129+
make_fs $FILE_SYSTEM "$VOLUME_ID"
130+
if [ $? -ne 0 ]; then
131+
echo "Error: Failed to format the volume. Exiting."
132+
exit 1
133+
fi
134+
135+
sleep 10
136+
VOLUME_UUID=$(lsblk -fn -o UUID "$VOLUME_ID")
137+
if [ -z "$VOLUME_UUID" ]; then
138+
echo "Error: Could not get UUID for volume $VOLUME_ID. Exiting."
139+
exit 1
140+
fi
141+
142+
VOLUME_FSTAB_CONF="UUID=$VOLUME_UUID $DIR_NAME $FILE_SYSTEM $FS_CONFIG 0 2"
143+
echo "VOLUME_ID=$VOLUME_ID"
144+
echo "VOLUME_UUID=$VOLUME_UUID"
145+
echo "VOLUME_FSTAB_CONF=$VOLUME_FSTAB_CONF"
146+
147+
# Check if data disc is already in fstab and replace the line if it is with the new disc UUID
148+
echo "Checking fstab for volume $DIR_NAME"
149+
if [ $(grep -c "$DIR_NAME" /etc/fstab) -gt 0 ]; then
150+
SED_REPLACEMENT_STRING="$(grep -n "$DIR_NAME" /etc/fstab | cut -d: -f1)s#.*#$VOLUME_FSTAB_CONF#"
151+
# if file exists, delete it
152+
if [ -f /etc/fstab.bak ]; then
153+
rm /etc/fstab.bak
154+
fi
155+
cp /etc/fstab /etc/fstab.bak
156+
sed -i "$SED_REPLACEMENT_STRING" /etc/fstab
157+
else
158+
echo "$VOLUME_FSTAB_CONF" | tee -a /etc/fstab
159+
fi
160+
161+
echo "Mounting volume with mount -a"
162+
mount -a
163+
if [ $? -ne 0 ]; then
164+
echo "Error: Failed to mount the volume. Exiting."
165+
exit 1
166+
fi
167+
168+
echo "Volume mounted successfully at $DIR_NAME"
169+
else
170+
echo "$DIR_NAME volume is mounted, nothing changed"
171+
fi

0 commit comments

Comments
 (0)