Skip to content

Bitcoin Core Implementation #185

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

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4ce981c
Bitcoin Core initial commit
Mar 28, 2025
4686b08
Update README.md
racket2000 Mar 28, 2025
12a990d
Update README.md
racket2000 Mar 28, 2025
99e33ce
Fix formatting
racket2000 Mar 28, 2025
c4b1782
Fix path
racket2000 Mar 28, 2025
bb00a03
Fix command
racket2000 Mar 28, 2025
7cf4620
Update README.md
racket2000 Mar 28, 2025
038e799
Update bitcoin.conf
racket2000 Mar 28, 2025
ec039e6
Update README.md
racket2000 Mar 31, 2025
210d050
Add clean up instructions
racket2000 Apr 1, 2025
e17e766
Add info about ALB cookie
racket2000 Apr 1, 2025
66ffdca
incorporate to website
Apr 1, 2025
fff4884
fix pre-commit errors
Apr 1, 2025
d82ade9
fix test and remove printed password from generateRPCAuth
Apr 1, 2025
f983100
fix pre-commit again
Apr 1, 2025
689a0cb
refresh package.json
Apr 1, 2025
f90c731
trying to fix website build error
Apr 1, 2025
97b375f
fix package-lock
Apr 1, 2025
8fe84a1
fix website build error
Apr 1, 2025
fc9b08a
fix website build error pt3
Apr 1, 2025
892b87d
Update README.md
racket2000 Apr 1, 2025
7322b92
fix website dependecy issues
Apr 1, 2025
a0d6b94
fix build dependecy issue
Apr 1, 2025
7a534ab
fix website build
Apr 1, 2025
a1b814c
fix website build pt2
Apr 1, 2025
689e531
fix website build pt3
Apr 1, 2025
57e092c
implement .env and remove local packages
Apr 3, 2025
8bc79d9
fix pre-commit
Apr 3, 2025
0795d9d
modify local package.json
Apr 3, 2025
79d8e70
fix file path
Apr 3, 2025
a707c89
update readme
Apr 3, 2025
2734a5a
fix precommit
Apr 3, 2025
a9c2482
update readme formatting
Apr 3, 2025
2e5662d
clarify credential rotation
Apr 3, 2025
ca6e5c1
feedback incorp'd
Apr 10, 2025
7c62ebc
fix pre-commmit and add placeholder account ID
Apr 10, 2025
7f55a3e
update HA arch
Apr 10, 2025
ae0e262
fix path in readme
racket2000 Apr 10, 2025
d2cbe9f
update account id
racket2000 Apr 10, 2025
2b6f9b5
Remove .env file from repo
Apr 10, 2025
f83fdef
fix tests
Apr 10, 2025
1593a83
update readme regarding secrets manager
Apr 10, 2025
6a308a5
fix typo in env var
Apr 10, 2025
ba32139
clarify directory in command
Apr 10, 2025
7a5b647
add GP3_THROUGHPUT .env
Apr 10, 2025
f6ddf77
fix pre-commit
Apr 10, 2025
50dc0ac
update package.json
Apr 10, 2025
47d6254
fix precommit
Apr 10, 2025
480d7a6
add note regarding auth creds
Apr 10, 2025
47f188e
fix pre-commit
Apr 10, 2025
f749e51
add SSM note to payments
Apr 10, 2025
c62de10
fix typo
Apr 10, 2025
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
439 changes: 439 additions & 0 deletions lib/bitcoin-core/README.md

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions lib/bitcoin-core/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env node
require('dotenv').config(); // Load .env first
const cdk = require('aws-cdk-lib');
const { Aspects } = require('aws-cdk-lib');
const { AwsSolutionsChecks } = require('cdk-nag');
const { BitcoinCommonStack } = require('./lib/common-infra');
const { SingleNodeBitcoinCoreStack } = require('./lib/single-node-stack');
const { HABitcoinCoreNodeStack } = require('./lib/ha-node-stack');

const app = new cdk.App();

Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));

const env = {
account: process.env.AWS_ACCOUNT_ID,
region: process.env.AWS_REGION,
};

const commonStack = new BitcoinCommonStack(app, 'BitcoinCommonStack', { env });
new SingleNodeBitcoinCoreStack(app, 'SingleNodeBitcoinCoreStack', {
env,
instanceRole: commonStack.instanceRole,
});

new HABitcoinCoreNodeStack(app, 'HABitcoinCoreNodeStack', {
env,
instanceRole: commonStack.instanceRole,
});
3 changes: 3 additions & 0 deletions lib/bitcoin-core/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "node app.js"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions lib/bitcoin-core/generateRPCAuth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const crypto = require('crypto');
const base64url = require('base64url');
const fs = require('fs');
const { SecretsManagerClient, CreateSecretCommand, PutSecretValueCommand } = require('@aws-sdk/client-secrets-manager');

// Set up AWS SDK client
const client = new SecretsManagerClient({ region: 'us-east-1' }); // Change region if needed

// Create size byte hex salt
function genSalt(size = 16) {
const buffer = crypto.randomBytes(size);
return buffer.toString('hex');
}

// Create 32 byte b64 password
function genPass(size = 32) {
const buffer = crypto.randomBytes(size);
return base64url.fromBase64(buffer.toString('base64'));
}

function genUser() {
return 'user_' + Math.round(Math.random() * 1000);
}

function genHash(password, salt) {
const hash = crypto
.createHmac('sha256', salt)
.update(password)
.digest('hex');
return hash;
}

function genRpcAuth(username = genUser(), password = genPass(), salt = genSalt()) {
const hash = genHash(password, salt);
return { username, password, salt, hash };
}

function writeRpcAuthToConf(rpcauthStr) {
const confPath = 'lib/bitcoin.conf';
try {
fs.writeFileSync(confPath, rpcauthStr + '\n', { flag: 'a' });
console.log(`Successfully wrote to ${confPath}`);
} catch (error) {
console.error(`Error writing to ${confPath}:`, error);
}
}

async function storeCredentialsInAWS(username, password) {
const secretName = 'bitcoin_rpc_credentials';
const secretValue = `${username}:${password}`;

try {
const createCommand = new CreateSecretCommand({
Name: secretName,
SecretString: secretValue,
});
await client.send(createCommand);
console.log(`Successfully stored credentials in AWS Secrets Manager: ${secretName}`);
} catch (error) {
if (error.name === 'ResourceExistsException') {
const updateCommand = new PutSecretValueCommand({
SecretId: secretName,
SecretString: secretValue,
});
await client.send(updateCommand);
console.log(`Successfully updated existing secret in AWS Secrets Manager: ${secretName}`);
} else {
console.error(`Error storing credentials in AWS Secrets Manager:`, error);
}
}
}

async function genRpcAuthStr(username, password, salt) {
const rpcauth = genRpcAuth(username, password, salt);
const str = `rpcauth=${rpcauth.username}:${rpcauth.salt}$${rpcauth.hash}`;
const strEscapeCharacter = `${rpcauth.username}:${rpcauth.salt}\\$${rpcauth.hash}`;
console.log(`Username: ${rpcauth.username}`);
console.log("Password generated securely and stored in Secrets Manager");
console.log(`rpcauth string with escape character: ${strEscapeCharacter}`); // Print the rpcauth string

// Write to bitcoin.conf
writeRpcAuthToConf(str);

// Store in AWS Secrets Manager
await storeCredentialsInAWS(rpcauth.username, rpcauth.password);

return str;
}

// Example usage
genRpcAuthStr();

module.exports = {
genSalt,
genPass,
genUser,
genHash,
genRpcAuth,
genRpcAuthStr,
};
8 changes: 8 additions & 0 deletions lib/bitcoin-core/lib/assets/bitcoin-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
# This script is used to set up a mainnet Bitcoin Core node on an Amazon Linux 2 instance.
yum update -y
amazon-linux-extras install docker -y
service docker start
mkdir -p /home/bitcoin/.bitcoin
echo "${BITCOIN_CONF}" > /home/bitcoin/.bitcoin/bitcoin.conf
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"
4 changes: 4 additions & 0 deletions lib/bitcoin-core/lib/assets/blockheight-cron.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
# This script is used to set up a cron job to send the Bitcoin block height to Amazon CloudWatch every 5 minutes.
REGION=${AWS_REGION}
(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 -
30 changes: 30 additions & 0 deletions lib/bitcoin-core/lib/assets/cloudwatch-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
# This script is used to set up the Amazon CloudWatch agent on an Amazon Linux 2 instance.

yum install -y amazon-cloudwatch-agent
mkdir -p /opt/aws/amazon-cloudwatch-agent/etc
cat <<EOF > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
{
"metrics": {
"metrics_collected": {
"disk": {
"measurement": ["used_percent", "inodes_free"],
"resources": ["*"],
"ignore_file_system_types": ["sysfs", "devtmpfs"]
},
"mem": {
"measurement": ["mem_used_percent"]
},
"cpu": {
"measurement": ["cpu_usage_idle", "cpu_usage_user", "cpu_usage_system"]
},
"net": {
"measurement": ["net_bytes_sent", "net_bytes_recv"],
"resources": ["eth0"]
}
}
}
}
EOF

/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
5 changes: 5 additions & 0 deletions lib/bitcoin-core/lib/bitcoin.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
server=1
rpcallowip=0.0.0.0/0
txindex=1
rpcbind=0.0.0.0:8332
datadir=/home/bitcoin/.bitcoin
39 changes: 39 additions & 0 deletions lib/bitcoin-core/lib/common-infra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const cdk = require("aws-cdk-lib");
const iam = require("aws-cdk-lib/aws-iam");
const nag = require("cdk-nag");

class BitcoinCommonStack extends cdk.Stack {
constructor(scope, id, props) {
super(scope, id, props);

this.instanceRole = new iam.Role(this, "BitcoinNodeRole", {
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"),
],
});

new cdk.CfnOutput(this, "InstanceRoleArn", {
value: this.instanceRole.roleArn,
exportName: "BitcoinNodeInstanceRoleArn",
});

// cdk-nag suppressions
nag.NagSuppressions.addResourceSuppressions(
this.instanceRole,
[
{
id: "AwsSolutions-IAM4",
reason: "AmazonSSMManagedInstanceCore and CloudWatchAgentServerPolicy are sufficient for this use case.",
},
{
id: "AwsSolutions-IAM5",
reason: "Managed policies and wildcard usage are acceptable for this limited-scope Bitcoin node role.",
},
]
);
}
}

module.exports = { BitcoinCommonStack };
20 changes: 20 additions & 0 deletions lib/bitcoin-core/lib/constructs/bitcoin-mainnet-security-group.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { Construct } = require('constructs');
const ec2 = require('aws-cdk-lib/aws-ec2');

class BitcoinSecurityGroup extends Construct {
constructor(scope, id, vpc) {
super(scope, id);

const sg = new ec2.SecurityGroup(this, 'BitcoinSG', {
vpc,
allowAllOutbound: true,
});

sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8333), 'Bitcoin P2P');
sg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(8332), 'Bitcoin RPC from VPC');

this.securityGroup = sg;
}
}

module.exports = { BitcoinSecurityGroup };
Loading
Loading