Skip to content

Commit 73c2b97

Browse files
Add script to setup two gl nodes with one local CLN node
1 parent 4aac888 commit 73c2b97

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ gltestserver-image: ${REPO_ROOT}/docker/gl-testserver/Dockerfile
207207
.
208208

209209
gltestserver: gltestserver-image
210+
rm -rf .gltestserver/gl-testserver && \
210211
docker run \
211212
--rm \
212213
--user $(shell id -u):$(shell id -g) \
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
const path = require('path');
2+
const axios = require('axios');
3+
const protobuf = require('protobufjs');
4+
5+
async function encodePayload(clnNode, method, payload) {
6+
const methodRequest = clnNode.lookupType(`cln.${method}Request`);
7+
const errMsg = methodRequest.verify(payload);
8+
if (errMsg) throw new Error(errMsg);
9+
const requestPayload = methodRequest.create(payload);
10+
const encodedPayload = methodRequest.encode(requestPayload).finish();
11+
const flags = Buffer.alloc(1);
12+
flags.writeUInt8(0, 0);
13+
const header = Buffer.alloc(4);
14+
header.writeUInt32BE(encodedPayload.length, 0);
15+
return Buffer.concat([flags, header, encodedPayload]);
16+
}
17+
18+
async function sendRequest(port, authPubkey, methodUrl, encodedPayload) {
19+
const buffer = Buffer.alloc(8);
20+
buffer.writeUInt32BE(Math.floor(Date.now() / 1000), 4);
21+
const axiosConfig = {
22+
responseType: 'arraybuffer',
23+
headers: {
24+
'content-type': 'application/grpc',
25+
'accept': 'application/grpc',
26+
'glauthpubkey': Buffer.from(authPubkey, 'hex').toString('base64'),
27+
'glauthsig': 'A'.repeat(64),
28+
'glts': buffer.toString('base64'),
29+
},
30+
};
31+
return await axios.post(`http://localhost:${port}/cln.Node/${methodUrl}`, encodedPayload, axiosConfig);
32+
}
33+
34+
function transformValue(key, value) {
35+
if ((value.type && value.type === "Buffer") || value instanceof Buffer || value instanceof Uint8Array) {
36+
return Buffer.from(value).toString('hex');
37+
}
38+
if (value.msat && !Number.isNaN(parseInt(value.msat))) {
39+
return parseInt(value.msat);
40+
}
41+
return value;
42+
}
43+
44+
function decodeResponse(clnNode, method, response) {
45+
const methodResponse = clnNode.lookupType(`cln.${method}Response`);
46+
const dataBuffer = Buffer.from(response.data);
47+
const resFlag = dataBuffer.readUInt32BE(0, 1);
48+
const resDataLength = dataBuffer.subarray(1, 5);
49+
const responseData = dataBuffer.subarray(5);
50+
const grpcStatus = +response.headers['grpc-status'];
51+
if (grpcStatus !== 0) {
52+
let errorMessage = 'None';
53+
try {
54+
errorMessage = decodeURIComponent(new TextDecoder('utf-8').decode(responseData)).trim();
55+
if (errorMessage == 'None') {
56+
errorMessage = grpcStatus;
57+
}
58+
} catch (decodeError) {
59+
errorMessage = decodeError;
60+
}
61+
throw new Error(errorMessage);
62+
}
63+
const decodedRes = methodResponse.decode(responseData);
64+
const decodedResObject = methodResponse.toObject(decodedRes, {
65+
longs: String,
66+
enums: String,
67+
bytes: Buffer,
68+
defaults: true,
69+
arrays: true,
70+
objects: true,
71+
});
72+
return JSON.parse(JSON.stringify(decodedResObject, transformValue));
73+
}
74+
75+
async function callMethod(port, auth_pubkey, clnNode, method, methodReq, methodRes, methodPayload) {
76+
await new Promise(resolve => setTimeout(resolve, 1000));
77+
const encodedPayload = await encodePayload(clnNode, methodReq, methodPayload);
78+
try {
79+
const response = await sendRequest(port, auth_pubkey, method, encodedPayload);
80+
const responseJSON = decodeResponse(clnNode, methodRes, response);
81+
console.log(JSON.stringify(responseJSON, null, 2));
82+
} catch (error) {
83+
throw error;
84+
}
85+
}
86+
87+
async function main() {
88+
try {
89+
const args = process.argv.slice(2);
90+
const port = args[0];
91+
const auth_pubkey = args[1];
92+
const method = args[2];
93+
const methodReq = args[3];
94+
const methodRes = args[4];
95+
const methodPayload = JSON.parse(args[5]);
96+
const proto_path = [
97+
path.join(process.cwd(), '../../libs/gl-client/.resources/proto/node.proto'),
98+
path.join(process.cwd(), '../../libs/gl-client/.resources/proto/primitives.proto')
99+
];
100+
const clnNode = new protobuf.Root().loadSync(proto_path, { keepCase: true });
101+
await callMethod(port, auth_pubkey, clnNode, method, methodReq, methodRes, methodPayload);
102+
} catch (error) {
103+
console.error(error);
104+
}
105+
}
106+
107+
main();

setup-nodes.sh

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/bin/bash
2+
3+
# Read environment variables from metadata.json
4+
export GL_SERVER_DATA_PATH="$HOME/greenlight/.gltestserver"
5+
export GL_CA_CRT=$(jq -r '.ca_crt_path' ./metadata.json)
6+
export GL_NOBODY_CRT=$(jq -r '.nobody_crt_path' ./metadata.json)
7+
export GL_NOBODY_KEY=$(jq -r '.nobody_key_path' ./metadata.json)
8+
export GL_SCHEDULER_GRPC_URI=$(jq -r '.scheduler_grpc_uri' ./metadata.json)
9+
export GL_BITCOIN_RPC_URI=$(jq -r '.bitcoind_rpc_uri' ./metadata.json)
10+
export GL_GRPC_WEB_PROXY_URI=$(jq -r '.grpc_web_proxy_uri' ./metadata.json)
11+
export GL_GRPC_PORT=$(echo "$GL_GRPC_WEB_PROXY_URI" | sed -E 's/.*:([0-9]+)$/\1/')
12+
export LSP_LIGHTNING_DIR=/tmp/.lightning
13+
14+
# Extract values using parameter expansion and regular expressions
15+
RPC_USER=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\1|')
16+
RPC_PASS=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\2|')
17+
BITCOIN_HOST=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\3|')
18+
BITCOIN_PORT=$(echo $GL_BITCOIN_RPC_URI | sed -E 's|^http://([^:]+):([^@]+)@([^:]+):([0-9]+)$|\4|')
19+
WALLET_NAME="testwallet"
20+
NODE_PUBKEY_1="0279da9a93e50b008a7ba6bd25355fb7132f5015b790a05ee9f41bc9fbdeb30d19"
21+
NODE_PUBKEY_2="036fc50c7183d47baf1b80f312f1ea53b25c117183456b788c05f91b3b4507c8d3"
22+
LSP_NODE_PUBKEY="0208d70da220f05b859512fd805365af6a6b8583fa3ff10fbd00b6b7fbb29acd32"
23+
24+
echo "GL_SERVER_DATA_PATH="$GL_SERVER_DATA_PATH""
25+
echo "GL_CA_CRT="$GL_CA_CRT""
26+
echo "GL_NOBODY_CRT="$GL_NOBODY_CRT""
27+
echo "GL_NOBODY_KEY="$GL_NOBODY_KEY""
28+
echo "GL_SCHEDULER_GRPC_URI="$GL_SCHEDULER_GRPC_URI""
29+
echo "GL_BITCOIN_RPC_URI="$GL_BITCOIN_RPC_URI""
30+
echo "GL_GRPC_WEB_PROXY_URI="$GL_GRPC_WEB_PROXY_URI""
31+
echo "GL_GRPC_PORT="$GL_GRPC_PORT""
32+
echo "BITCOIN_PORT="$BITCOIN_PORT""
33+
echo "WALLET_NAME=testwallet"
34+
echo "NODE_PUBKEY_1=0279da9a93e50b008a7ba6bd25355fb7132f5015b790a05ee9f41bc9fbdeb30d19"
35+
echo "NODE_PUBKEY_2=036fc50c7183d47baf1b80f312f1ea53b25c117183456b788c05f91b3b4507c8d3"
36+
echo "LSP_NODE_PUBKEY=0208d70da220f05b859512fd805365af6a6b8583fa3ff10fbd00b6b7fbb29acd32"
37+
38+
39+
# Function to handle Ctrl+C
40+
trap 'echo "Stopping terminals"; pkill -f "gnome-terminal"; exit' SIGINT
41+
42+
# Function to register and schedule a node
43+
run_scheduler_for_node() {
44+
local node=$1
45+
mkdir -p $GL_SERVER_DATA_PATH/$node && printf $2 > $GL_SERVER_DATA_PATH/$node/hsm_secret
46+
gnome-terminal --title="Scheduler $node" -- bash -c "cargo run --bin glcli scheduler register --network=regtest --data-dir=$GL_SERVER_DATA_PATH/$node;
47+
cargo run --bin glcli scheduler schedule --verbose --network=regtest --data-dir=$GL_SERVER_DATA_PATH/$node;
48+
echo 'Closing terminal...'; exit"
49+
}
50+
51+
# Function to start the signer for a node
52+
run_signer_for_node() {
53+
local node=$1
54+
gnome-terminal --title="Signer $node" -- bash -c "cargo run --bin glcli signer run --verbose --network=regtest --data-dir=$GL_SERVER_DATA_PATH/$node;
55+
echo 'Closing terminal...'; exit"
56+
}
57+
58+
# Function to start the local LSP node
59+
run_local_lsp_node() {
60+
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
61+
gnome-terminal --title="Local LSP node" -- bash -c "
62+
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;
63+
echo 'Closing terminal...'; exit"
64+
}
65+
66+
wait_for_local_funds_confirmation() {
67+
IS_CONFIRMED=false
68+
while ! $IS_CONFIRMED; do
69+
if lightning-cli --network=regtest --lightning-dir="$LSP_LIGHTNING_DIR" listfunds | jq -e '.outputs[] | select(.status == "confirmed" and .reserved == false)' > /dev/null; then
70+
IS_CONFIRMED=true
71+
else
72+
echo "Waiting for funds to confirm on LSP..."
73+
sleep 1
74+
fi
75+
done
76+
}
77+
78+
wait_for_gl_funds_confirmation() {
79+
cd "./examples/javascript"
80+
while ! node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "ListFunds" "Listfunds" "Listfunds" "{}" | grep -q "outputs"; do
81+
echo "Waiting for funds to confirm on gl node 1..."
82+
sleep 1
83+
done
84+
while ! node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "ListFunds" "Listfunds" "Listfunds" "{}" | grep -q "outputs"; do
85+
echo "Waiting for funds to confirm on gl node 2..."
86+
sleep 1
87+
done
88+
cd "../.."
89+
}
90+
91+
run_gl_node_operations() {
92+
cd "./examples/javascript"
93+
CONNECT_PAYLOAD='{"id": "'"$LSP_NODE_PUBKEY"'", "host": "127.0.0.1", "port": 9735}'
94+
echo "Node-1 + LSP"
95+
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "ConnectPeer" "Connect" "Connect" "$CONNECT_PAYLOAD"
96+
echo "Node-2 + LSP"
97+
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "ConnectPeer" "Connect" "Connect" "$CONNECT_PAYLOAD"
98+
BUFFER_ID=$(echo -n "$LSP_NODE_PUBKEY" | xxd -r -p | base64)
99+
FUND_PAYLOAD='{"id": "'"$BUFFER_ID"'", "amount": {"amount": {"msat": 11000000000}}}'
100+
echo "Node-1 -> LSP"
101+
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "FundChannel" "Fundchannel" "Fundchannel" "$FUND_PAYLOAD"
102+
echo "Node-2 -> LSP"
103+
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "FundChannel" "Fundchannel" "Fundchannel" "$FUND_PAYLOAD"
104+
cd "../.."
105+
}
106+
107+
run_node_transactions() {
108+
cd "./examples/javascript"
109+
echo "Create invoice 1"
110+
INVOICE_1_PAYLOAD=$(jq -nc --arg msat "120000000" --arg desc "Learning Bitcoin Book" --arg label "bookinvat$(date +%s)" \
111+
'{"amount_msat": {"amount": {"msat": ($msat | tonumber)}}, "description": $desc, "label": $label}')
112+
INVOICE_1_RESPONSE=$(node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "Invoice" "Invoice" "Invoice" "$INVOICE_1_PAYLOAD")
113+
BOLT11_1=$(echo "$INVOICE_1_RESPONSE" | jq -r '.bolt11')
114+
echo "Invoice 1: $BOLT11_1"
115+
echo "Create invoice 2"
116+
INVOICE_2_PAYLOAD=$(jq -nc --arg msat "5000000" --arg desc "My coffee" --arg label "coffeeinvat$(date +%s)" \
117+
'{"amount_msat": {"amount": {"msat": ($msat | tonumber)}}, "description": $desc, "label": $label}')
118+
INVOICE_2_RESPONSE=$(node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "Invoice" "Invoice" "Invoice" "$INVOICE_2_PAYLOAD")
119+
BOLT11_2=$(echo "$INVOICE_2_RESPONSE" | jq -r '.bolt11')
120+
echo "Invoice 2: $BOLT11_2"
121+
echo "Pay invoice 1"
122+
PAY_PAYLOAD_1='{"bolt11": "'"$BOLT11_1"'"}'
123+
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_2" "Pay" "Pay" "Pay" "$PAY_PAYLOAD_1"
124+
echo "Pay invoice 2"
125+
PAY_PAYLOAD_2='{"bolt11": "'"$BOLT11_2"'"}'
126+
node node-operations.js "$GL_GRPC_PORT" "$NODE_PUBKEY_1" "Pay" "Pay" "Pay" "$PAY_PAYLOAD_2"
127+
cd "../.."
128+
}
129+
130+
start() {
131+
rm -rf $GL_SERVER_DATA_PATH/gl-testserver/regtest/$WALLET_NAME && rm -rf $LSP_LIGHTNING_DIR
132+
133+
# Setup node-1
134+
echo "Scheduler for node-1"
135+
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'
136+
echo "Signer for node-1"
137+
run_signer_for_node "node-1"
138+
139+
# Setup node-2
140+
echo "Scheduler for node-2"
141+
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'
142+
echo "Signer for node-2"
143+
run_signer_for_node "node-2"
144+
145+
echo "Running local LSP node"
146+
run_local_lsp_node
147+
148+
# Send Bitcoin to Node-1, Node-2 and LSP
149+
echo "Creating bitcoin wallet"
150+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS createwallet $WALLET_NAME > /dev/null
151+
echo "Generating 101 blocks"
152+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 101 > /dev/null
153+
echo "Sending bitcoin to LSP node"
154+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME sendtoaddress "bcrt1qaew9v5m5q8cjjuh9mdruuujykzxrcufma28x82" 1 > /dev/null
155+
echo "Sending bitcoin to node-1"
156+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME sendtoaddress "bcrt1q0mlp2u676wv9rgz5e6nrrq2vc76rllxcazcldy" 1 > /dev/null
157+
echo "Sending bitcoin to node-2"
158+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME sendtoaddress "bcrt1q68n5mqlkf0l877chhpa2w2zxlug343kn0p8c6r" 1 > /dev/null
159+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 1 > /dev/null
160+
wait_for_gl_funds_confirmation
161+
wait_for_local_funds_confirmation
162+
163+
# Connect and fund channels Node-1 -> LSP & Node-2 -> LSP
164+
run_gl_node_operations
165+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 6 > /dev/null
166+
wait_for_gl_funds_confirmation
167+
168+
echo "LSP -> Node-1"
169+
lightning-cli --network=regtest --lightning-dir=$LSP_LIGHTNING_DIR fundchannel $NODE_PUBKEY_1 12000000
170+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 6 > /dev/null
171+
wait_for_local_funds_confirmation
172+
173+
echo "LSP -> Node-2"
174+
lightning-cli --network=regtest --lightning-dir=$LSP_LIGHTNING_DIR fundchannel $NODE_PUBKEY_2 12000000
175+
bitcoin-cli -rpcconnect=$BITCOIN_HOST -rpcport=$BITCOIN_PORT -rpcuser=$RPC_USER -rpcpassword=$RPC_PASS -rpcwallet=$WALLET_NAME -generate 6 > /dev/null
176+
wait_for_local_funds_confirmation
177+
178+
# Print LSP node status with channels and outputs before testing transactions
179+
lightning-cli --network=regtest --lightning-dir=$LSP_LIGHTNING_DIR listfunds
180+
181+
# Create invoice and pay
182+
run_node_transactions
183+
}
184+
185+
# Keep the script running to listen for Ctrl+C
186+
wait

0 commit comments

Comments
 (0)