Skip to content

Commit

Permalink
Add script to setup two gl nodes with one local CLN node
Browse files Browse the repository at this point in the history
  • Loading branch information
ShahanaFarooqui committed Feb 14, 2025
1 parent 4aac888 commit 73c2b97
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ gltestserver-image: ${REPO_ROOT}/docker/gl-testserver/Dockerfile
.

gltestserver: gltestserver-image
rm -rf .gltestserver/gl-testserver && \
docker run \
--rm \
--user $(shell id -u):$(shell id -g) \
Expand Down
107 changes: 107 additions & 0 deletions examples/javascript/node-operations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const path = require('path');
const axios = require('axios');
const protobuf = require('protobufjs');

async function encodePayload(clnNode, method, payload) {
const methodRequest = clnNode.lookupType(`cln.${method}Request`);
const errMsg = methodRequest.verify(payload);
if (errMsg) throw new Error(errMsg);
const requestPayload = methodRequest.create(payload);
const encodedPayload = methodRequest.encode(requestPayload).finish();
const flags = Buffer.alloc(1);
flags.writeUInt8(0, 0);
const header = Buffer.alloc(4);
header.writeUInt32BE(encodedPayload.length, 0);
return Buffer.concat([flags, header, encodedPayload]);
}

async function sendRequest(port, authPubkey, methodUrl, encodedPayload) {
const buffer = Buffer.alloc(8);
buffer.writeUInt32BE(Math.floor(Date.now() / 1000), 4);
const axiosConfig = {
responseType: 'arraybuffer',
headers: {
'content-type': 'application/grpc',
'accept': 'application/grpc',
'glauthpubkey': Buffer.from(authPubkey, 'hex').toString('base64'),
'glauthsig': 'A'.repeat(64),
'glts': buffer.toString('base64'),
},
};
return await axios.post(`http://localhost:${port}/cln.Node/${methodUrl}`, encodedPayload, axiosConfig);
}

function transformValue(key, value) {
if ((value.type && value.type === "Buffer") || value instanceof Buffer || value instanceof Uint8Array) {
return Buffer.from(value).toString('hex');
}
if (value.msat && !Number.isNaN(parseInt(value.msat))) {
return parseInt(value.msat);
}
return value;
}

function decodeResponse(clnNode, method, response) {
const methodResponse = clnNode.lookupType(`cln.${method}Response`);
const dataBuffer = Buffer.from(response.data);
const resFlag = dataBuffer.readUInt32BE(0, 1);
const resDataLength = dataBuffer.subarray(1, 5);
const responseData = dataBuffer.subarray(5);
const grpcStatus = +response.headers['grpc-status'];
if (grpcStatus !== 0) {
let errorMessage = 'None';
try {
errorMessage = decodeURIComponent(new TextDecoder('utf-8').decode(responseData)).trim();
if (errorMessage == 'None') {
errorMessage = grpcStatus;
}
} catch (decodeError) {
errorMessage = decodeError;
}
throw new Error(errorMessage);
}
const decodedRes = methodResponse.decode(responseData);
const decodedResObject = methodResponse.toObject(decodedRes, {
longs: String,
enums: String,
bytes: Buffer,
defaults: true,
arrays: true,
objects: true,
});
return JSON.parse(JSON.stringify(decodedResObject, transformValue));
}

async function callMethod(port, auth_pubkey, clnNode, method, methodReq, methodRes, methodPayload) {
await new Promise(resolve => setTimeout(resolve, 1000));
const encodedPayload = await encodePayload(clnNode, methodReq, methodPayload);
try {
const response = await sendRequest(port, auth_pubkey, method, encodedPayload);
const responseJSON = decodeResponse(clnNode, methodRes, response);
console.log(JSON.stringify(responseJSON, null, 2));
} catch (error) {
throw error;
}
}

async function main() {
try {
const args = process.argv.slice(2);
const port = args[0];
const auth_pubkey = args[1];
const method = args[2];
const methodReq = args[3];
const methodRes = args[4];
const methodPayload = JSON.parse(args[5]);
const proto_path = [
path.join(process.cwd(), '../../libs/gl-client/.resources/proto/node.proto'),
path.join(process.cwd(), '../../libs/gl-client/.resources/proto/primitives.proto')
];
const clnNode = new protobuf.Root().loadSync(proto_path, { keepCase: true });
await callMethod(port, auth_pubkey, clnNode, method, methodReq, methodRes, methodPayload);
} catch (error) {
console.error(error);
}
}

main();
186 changes: 186 additions & 0 deletions setup-nodes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/bin/bash

# Read environment variables from metadata.json
export GL_SERVER_DATA_PATH="$HOME/greenlight/.gltestserver"
export GL_CA_CRT=$(jq -r '.ca_crt_path' ./metadata.json)
export GL_NOBODY_CRT=$(jq -r '.nobody_crt_path' ./metadata.json)
export GL_NOBODY_KEY=$(jq -r '.nobody_key_path' ./metadata.json)
export GL_SCHEDULER_GRPC_URI=$(jq -r '.scheduler_grpc_uri' ./metadata.json)
export GL_BITCOIN_RPC_URI=$(jq -r '.bitcoind_rpc_uri' ./metadata.json)
export GL_GRPC_WEB_PROXY_URI=$(jq -r '.grpc_web_proxy_uri' ./metadata.json)
export GL_GRPC_PORT=$(echo "$GL_GRPC_WEB_PROXY_URI" | sed -E 's/.*:([0-9]+)$/\1/')
export LSP_LIGHTNING_DIR=/tmp/.lightning

# Extract values using parameter expansion and regular expressions
RPC_USER=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\1|')
RPC_PASS=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\2|')
BITCOIN_HOST=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\3|')
BITCOIN_PORT=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\4|')
WALLET_NAME="testwallet"
NODE_PUBKEY_1="0279da9a93e50b008a7ba6bd25355fb7132f5015b790a05ee9f41bc9fbdeb30d19"
NODE_PUBKEY_2="036fc50c7183d47baf1b80f312f1ea53b25c117183456b788c05f91b3b4507c8d3"
LSP_NODE_PUBKEY="0208d70da220f05b859512fd805365af6a6b8583fa3ff10fbd00b6b7fbb29acd32"

echo "GL_SERVER_DATA_PATH="$GL_SERVER_DATA_PATH""
echo "GL_CA_CRT="$GL_CA_CRT""
echo "GL_NOBODY_CRT="$GL_NOBODY_CRT""
echo "GL_NOBODY_KEY="$GL_NOBODY_KEY""
echo "GL_SCHEDULER_GRPC_URI="$GL_SCHEDULER_GRPC_URI""
echo "GL_BITCOIN_RPC_URI="$GL_BITCOIN_RPC_URI""
echo "GL_GRPC_WEB_PROXY_URI="$GL_GRPC_WEB_PROXY_URI""
echo "GL_GRPC_PORT="$GL_GRPC_PORT""
echo "BITCOIN_PORT="$BITCOIN_PORT""
echo "WALLET_NAME=testwallet"
echo "NODE_PUBKEY_1=0279da9a93e50b008a7ba6bd25355fb7132f5015b790a05ee9f41bc9fbdeb30d19"
echo "NODE_PUBKEY_2=036fc50c7183d47baf1b80f312f1ea53b25c117183456b788c05f91b3b4507c8d3"
echo "LSP_NODE_PUBKEY=0208d70da220f05b859512fd805365af6a6b8583fa3ff10fbd00b6b7fbb29acd32"


# Function to handle Ctrl+C
trap 'echo "Stopping terminals"; pkill -f "gnome-terminal"; exit' SIGINT

# Function to register and schedule a node
run_scheduler_for_node() {
local node=$1
mkdir -p $GL_SERVER_DATA_PATH/$node && printf $2 > $GL_SERVER_DATA_PATH/$node/hsm_secret
gnome-terminal --title="Scheduler $node" -- bash -c "cargo run --bin glcli scheduler register --network=regtest --data-dir=$GL_SERVER_DATA_PATH/$node;
cargo run --bin glcli scheduler schedule --verbose --network=regtest --data-dir=$GL_SERVER_DATA_PATH/$node;
echo 'Closing terminal...'; exit"
}

# Function to start the signer for a node
run_signer_for_node() {
local node=$1
gnome-terminal --title="Signer $node" -- bash -c "cargo run --bin glcli signer run --verbose --network=regtest --data-dir=$GL_SERVER_DATA_PATH/$node;
echo 'Closing terminal...'; exit"
}

# Function to start the local LSP node
run_local_lsp_node() {
mkdir -p $LSP_LIGHTNING_DIR/regtest && printf '\x9f\xaa\x67\x08\x67\x05\x8c\x9d\xcc\x1f\xea\xd9\x9c\xe4\x91\xe2\x85\x95\x6d\xdb\x66\xa8\xed\x05\x85\xf3\x2a\x77\x0e\x1d\x14\xa6' > $LSP_LIGHTNING_DIR/regtest/hsm_secret
gnome-terminal --title="Local LSP node" -- bash -c "
lightningd --network=regtest --log-level=debug --lightning-dir=$LSP_LIGHTNING_DIR --bitcoin-rpcconnect=$BITCOIN_HOST --bitcoin-rpcport=$BITCOIN_PORT --bitcoin-datadir=$GL_SERVER_DATA_PATH/gl-testserver --bitcoin-rpcuser=$RPC_USER --bitcoin-rpcpassword=$RPC_PASS --alias=CLNLocalLSPNode --bind-addr=0.0.0.0:9735 --bind-addr=ws:0.0.0.0:5019 --announce-addr=0.0.0.0:9735 --addr=localhost:7171 --grpc-port=9737 --clnrest-port=3010 --clnrest-protocol=http;
echo 'Closing terminal...'; exit"
}

wait_for_local_funds_confirmation() {
IS_CONFIRMED=false
while ! $IS_CONFIRMED; do
if lightning-cli --network=regtest --lightning-dir="$LSP_LIGHTNING_DIR" listfunds | jq -e '.outputs[] | select(.status == "confirmed" and .reserved == false)' > /dev/null; then
IS_CONFIRMED=true
else
echo "Waiting for funds to confirm on LSP..."
sleep 1
fi
done
}

wait_for_gl_funds_confirmation() {
cd "./examples/javascript"
while ! node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "ListFunds" "Listfunds" "Listfunds" "{}" | grep -q "outputs"; do
echo "Waiting for funds to confirm on gl node 1..."
sleep 1
done
while ! node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "ListFunds" "Listfunds" "Listfunds" "{}" | grep -q "outputs"; do
echo "Waiting for funds to confirm on gl node 2..."
sleep 1
done
cd "../.."
}

run_gl_node_operations() {
cd "./examples/javascript"
CONNECT_PAYLOAD='{"id": "'"$LSP_NODE_PUBKEY"'", "host": "127.0.0.1", "port": 9735}'
echo "Node-1 + LSP"
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "ConnectPeer" "Connect" "Connect" "$CONNECT_PAYLOAD"
echo "Node-2 + LSP"
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "ConnectPeer" "Connect" "Connect" "$CONNECT_PAYLOAD"
BUFFER_ID=$(echo -n "$LSP_NODE_PUBKEY" | xxd -r -p | base64)
FUND_PAYLOAD='{"id": "'"$BUFFER_ID"'", "amount": {"amount": {"msat": 11000000000}}}'
echo "Node-1 -> LSP"
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "FundChannel" "Fundchannel" "Fundchannel" "$FUND_PAYLOAD"
echo "Node-2 -> LSP"
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "FundChannel" "Fundchannel" "Fundchannel" "$FUND_PAYLOAD"
cd "../.."
}

run_node_transactions() {
cd "./examples/javascript"
echo "Create invoice 1"
INVOICE_1_PAYLOAD=$(jq -nc --arg msat "120000000" --arg desc "Learning Bitcoin Book" --arg label "bookinvat$(date +%s)" \
'{"amount_msat": {"amount": {"msat": ($msat | tonumber)}}, "description": $desc, "label": $label}')
INVOICE_1_RESPONSE=$(node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "Invoice" "Invoice" "Invoice" "$INVOICE_1_PAYLOAD")
BOLT11_1=$(echo "$INVOICE_1_RESPONSE" | jq -r '.bolt11')
echo "Invoice 1: $BOLT11_1"
echo "Create invoice 2"
INVOICE_2_PAYLOAD=$(jq -nc --arg msat "5000000" --arg desc "My coffee" --arg label "coffeeinvat$(date +%s)" \
'{"amount_msat": {"amount": {"msat": ($msat | tonumber)}}, "description": $desc, "label": $label}')
INVOICE_2_RESPONSE=$(node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "Invoice" "Invoice" "Invoice" "$INVOICE_2_PAYLOAD")
BOLT11_2=$(echo "$INVOICE_2_RESPONSE" | jq -r '.bolt11')
echo "Invoice 2: $BOLT11_2"
echo "Pay invoice 1"
PAY_PAYLOAD_1='{"bolt11": "'"$BOLT11_1"'"}'
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "Pay" "Pay" "Pay" "$PAY_PAYLOAD_1"
echo "Pay invoice 2"
PAY_PAYLOAD_2='{"bolt11": "'"$BOLT11_2"'"}'
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "Pay" "Pay" "Pay" "$PAY_PAYLOAD_2"
cd "../.."
}

start() {
rm -rf $GL_SERVER_DATA_PATH/gl-testserver/regtest/$WALLET_NAME && rm -rf $LSP_LIGHTNING_DIR

# Setup node-1
echo "Scheduler for node-1"
run_scheduler_for_node "node-1" '\x0f\x29\xa7\x60\xea\xa1\xa3\xba\xbb\x7b\xa2\x5b\x7a\x82\xd1\x0b\x38\x55\xb9\xd2\xc6\x39\xb2\x79\xa9\xa0\x8d\x9b\xd1\xe6\x67\x9d'
echo "Signer for node-1"
run_signer_for_node "node-1"

# Setup node-2
echo "Scheduler for node-2"
run_scheduler_for_node "node-2" '\x5d\x74\x25\x7e\x03\xb0\xee\x2f\xf4\x7a\x06\x97\x3a\xd9\x14\xf1\x7c\x61\xec\xac\x20\xb1\xf9\x3b\xb2\x33\x98\xc9\x40\x86\xad\x67'
echo "Signer for node-2"
run_signer_for_node "node-2"

echo "Running local LSP node"
run_local_lsp_node

# Send Bitcoin to Node-1, Node-2 and LSP
echo "Creating bitcoin wallet"
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS createwallet $WALLET_NAME > /dev/null
echo "Generating 101 blocks"
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 101 > /dev/null
echo "Sending bitcoin to LSP node"
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME sendtoaddress "bcrt1qaew9v5m5q8cjjuh9mdruuujykzxrcufma28x82" 1 > /dev/null
echo "Sending bitcoin to node-1"
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME sendtoaddress "bcrt1q0mlp2u676wv9rgz5e6nrrq2vc76rllxcazcldy" 1 > /dev/null
echo "Sending bitcoin to node-2"
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME sendtoaddress "bcrt1q68n5mqlkf0l877chhpa2w2zxlug343kn0p8c6r" 1 > /dev/null
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 1 > /dev/null
wait_for_gl_funds_confirmation
wait_for_local_funds_confirmation

# Connect and fund channels Node-1 -> LSP & Node-2 -> LSP
run_gl_node_operations
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 6 > /dev/null
wait_for_gl_funds_confirmation

echo "LSP -> Node-1"
lightning-cli --network=regtest --lightning-dir=$LSP_LIGHTNING_DIR fundchannel $NODE_PUBKEY_1 12000000
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 6 > /dev/null
wait_for_local_funds_confirmation

echo "LSP -> Node-2"
lightning-cli --network=regtest --lightning-dir=$LSP_LIGHTNING_DIR fundchannel $NODE_PUBKEY_2 12000000
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 6 > /dev/null
wait_for_local_funds_confirmation

# Print LSP node status with channels and outputs before testing transactions
lightning-cli --network=regtest --lightning-dir=$LSP_LIGHTNING_DIR listfunds

# Create invoice and pay
run_node_transactions
}

# Keep the script running to listen for Ctrl+C
wait

0 comments on commit 73c2b97

Please # to comment.