mirror of
https://github.com/EvernodeXRPL/hp-devkit.git
synced 2026-04-29 15:37:58 +00:00
The bind_mesh function is hardcoding contract.roundtime=2000 (2 seconds) into the configuration incorrectly causing a duplicate roundtime setting in the default of 1000 irrespective of what contract.roundtime=2000 or what the hp.cfg.override is set to.
It should be setting this to contract.consensus.roundtime
```
cat /var/lib/docker/volumes/hpdevkit_default_vol/_data/node1/cfg/hp.cfg | grep -A10 -B5 roundtime
"bin_args": "index.js",
"environment": {},
"max_input_ledger_offset": 10,
"consensus": {
"mode": "public",
"roundtime": 1000, <--------
"stage_slice": 25,
"threshold": 80
},
"npl": {
"mode": "private"
},
"round_limits": {
"user_input_bytes": 0,
"user_output_bytes": 0,
"npl_output_bytes": 0,
"proc_cpu_seconds": 0,
"proc_mem_bytes": 0,
"proc_ofd_count": 0,
"exec_timeout": 300000
},
"roundtime": 2000 <-------
},
"mesh": {
"port": 22861,
"listen": true,
"idle_timeout": 120000,
"known_peers": [
"node2:22862",
"node3:22863"
],
"msg_forwarding": true,
```
342 lines
11 KiB
Bash
342 lines
11 KiB
Bash
#!/bin/bash
|
|
command=$1
|
|
|
|
cluster=$CLUSTER
|
|
cluster_size=$CLUSTER_SIZE
|
|
volume=$VOLUME
|
|
network=$NETWORK
|
|
container_prefix=$CONTAINER_PREFIX
|
|
volume_mount=$VOLUME_MOUNT
|
|
bundle_mount=$BUNDLE_MOUNT
|
|
disparate_dir=$DISPARATE_DIR
|
|
hotpocket_image=$HOTPOCKET_IMAGE
|
|
config_overrides_file=$CONFIG_OVERRIDES_FILE
|
|
user_port_begin=$HP_USER_PORT_BEGIN
|
|
peer_port_begin=$HP_PEER_PORT_BEGIN
|
|
|
|
if [ "$command" = "create" ] || [ "$command" = "bindmesh" ] || [ "$command" = "destroy" ] || \
|
|
[ "$command" = "start" ] || [ "$command" = "stop" ] || [ "$command" = "spawn" ] || \
|
|
[ "$command" = "logs" ] || [ "$command" = "sync" ] || [ "$command" = "status" ] ; then
|
|
echo "sub-command: $command"
|
|
else
|
|
echo "Invalid sub-command."
|
|
echo "Expected: create | destroy | start | stop | spawn | logs | sync | status"
|
|
exit 1
|
|
fi
|
|
|
|
[ -z $cluster ] && echo "Cluster name not specified." && exit 1
|
|
|
|
function validate_node_num_arg {
|
|
! [ "$1" -eq "$1" ] 2> /dev/null && echo "Arg must be a number." && return 1
|
|
[ $1 -le 0 ] && echo "Arg must be 1 or higher." && return 1
|
|
return 0
|
|
}
|
|
|
|
function validate_port {
|
|
! [ "$1" -eq "$1" ] 2> /dev/null && echo "Port must be a number." && return 1
|
|
([ $1 -lt 1 ] || [ $1 -gt 65535 ]) && echo "Port number must be 1 through 65535." && return 1
|
|
return 0
|
|
}
|
|
|
|
function contract_dir_mount_path {
|
|
echo "$volume_mount/node$1"
|
|
}
|
|
|
|
function exists {
|
|
local output=$(docker $1 ls | grep $2)
|
|
if [ -z "$output" ] ; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
function cluster_exists {
|
|
if exists "volume" $volume || exists "network" $network || exists "container" $container_prefix ; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function ensure_cluser_exists {
|
|
! cluster_exists && echo "Cluster '$cluster' does not exist." && exit 1
|
|
}
|
|
|
|
function ensure_cluser_not_exists {
|
|
cluster_exists && echo "Cluster '$cluster' already exists." && exit 1
|
|
}
|
|
|
|
function ensure_cluster_not_running {
|
|
local running_containers=$(docker ps -a --filter "status=running" | grep $container_prefix | wc -l)
|
|
[ "$running_containers" != "0" ] && echo "Cluster '$cluster' needs to be stopped." && exit 1
|
|
}
|
|
|
|
function get_container_count {
|
|
docker ps -a | grep $container_prefix | wc -l
|
|
}
|
|
|
|
function create_instance {
|
|
local node=$1
|
|
# Create contract instance directory.
|
|
docker run --rm --mount type=volume,src=$volume,dst=$volume_mount --rm $hotpocket_image new $volume_mount/node$node
|
|
|
|
let peer_port=$(($peer_port_begin + $node - 1))
|
|
let user_port=$(($user_port_begin + $node - 1))
|
|
|
|
# Create container for hotpocket instance.
|
|
local container_name="${container_prefix}_$node"
|
|
docker container create --name $container_name --privileged \
|
|
-p $user_port:$user_port --network $network --network-alias node$node --restart unless-stopped \
|
|
--mount type=volume,src=$volume,dst=$volume_mount $hotpocket_image run $(contract_dir_mount_path $node)
|
|
}
|
|
|
|
function change_instance_status {
|
|
local action=$1
|
|
local node=$2
|
|
local container_name="${container_prefix}_$node"
|
|
docker $action $container_name
|
|
}
|
|
|
|
|
|
# Function to generate JSON array string while skiping a given index.
|
|
function joinarr {
|
|
local arrname=$1[@]
|
|
local arr=("${!arrname}")
|
|
local ncount=$2
|
|
local skip=$3
|
|
|
|
let prevlast=$ncount-2
|
|
# Resetting prevlast if nothing is given to skip.
|
|
[ $skip -lt 0 ] && let prevlast=prevlast+1
|
|
|
|
local j=0
|
|
local str="["
|
|
for (( i=0; i<$ncount; i++ ))
|
|
do
|
|
if [ "$i" != "$skip" ]
|
|
then
|
|
str="$str\"${arr[i]}\""
|
|
[ $j -lt $prevlast ] && str="$str,"
|
|
let j=j+1
|
|
fi
|
|
done
|
|
str="$str]"
|
|
echo $str
|
|
}
|
|
|
|
# Update all instances hotpocket configs so they connect to each other as a cluster.
|
|
function bind_mesh {
|
|
local instance_count=$(get_container_count)
|
|
|
|
# Collect pubkeys and peers of all nodes.
|
|
local all_pubkeys
|
|
local all_peers
|
|
local contract_id
|
|
for ((i=1; i<=$instance_count; i++));
|
|
do
|
|
local contract_dir=$(contract_dir_mount_path $i)
|
|
local cfg_file=$contract_dir/cfg/hp.cfg
|
|
|
|
# Use first instance contract id for all instances.
|
|
[ $i -eq 1 ] && contract_id=$(jq ".contract.id" $cfg_file)
|
|
|
|
# Assign user and peer ports in incrementing order.
|
|
let peer_port=$(($peer_port_begin + $i - 1))
|
|
let user_port=$(($user_port_begin + $i - 1))
|
|
|
|
jq ".contract.id=$contract_id | .contract.consensus.roundtime=2000 | .mesh.port=$peer_port | .user.port=$user_port" $cfg_file > $cfg_file.tmp \
|
|
&& mv $cfg_file.tmp $cfg_file
|
|
|
|
all_pubkeys[i]=$(jq --raw-output ".node.public_key" $cfg_file)
|
|
all_peers[i]="node$i:${peer_port}"
|
|
done
|
|
|
|
# Update unl and peer list for all instances.
|
|
local unl=$(joinarr all_pubkeys $instance_count -1)
|
|
for ((i=0; i<$instance_count; i++));
|
|
do
|
|
let node=$i+1
|
|
local contract_dir=$(contract_dir_mount_path $node)
|
|
local cfg_file=$contract_dir/cfg/hp.cfg
|
|
local peers=$(joinarr all_peers $instance_count $i)
|
|
jq ".contract.unl=$unl | .mesh.known_peers=$peers | .contract.consensus.mode=\"public\"" $cfg_file > $cfg_file.tmp && mv $cfg_file.tmp $cfg_file
|
|
done
|
|
}
|
|
|
|
function create_cluster {
|
|
ensure_cluser_not_exists
|
|
|
|
local size=$1
|
|
echo "Creating '$cluster' cluster of size $size"
|
|
docker volume create $volume
|
|
docker network create $network
|
|
|
|
# Pull the docker image if not exists.
|
|
if ! docker image inspect $hotpocket_image &>/dev/null;
|
|
then
|
|
echo "Pulling the docker image $hotpocket_image"
|
|
docker pull $hotpocket_image
|
|
fi
|
|
|
|
for ((i=1; i<=$size; i++));
|
|
do
|
|
create_instance $i &
|
|
done
|
|
wait
|
|
}
|
|
|
|
function destroy_cluster {
|
|
ensure_cluser_exists
|
|
|
|
# Delete top-most matching container N times.
|
|
# (This is just in case there are gaps in container numbering due to any tampering by user)
|
|
local container_count=$(get_container_count)
|
|
for ((i=1; i<=$container_count; i++));
|
|
do
|
|
local container_name="$(docker ps -a --format "{{.Names}}" | grep $container_prefix | head -1)"
|
|
docker container rm $container_name
|
|
done
|
|
wait
|
|
|
|
exists "volume" $volume && docker volume rm $volume
|
|
exists "network" $network && docker network rm $network
|
|
}
|
|
|
|
function spawn_node {
|
|
ensure_cluser_exists
|
|
|
|
local old_count=$(get_container_count)
|
|
local new_id=$((old_count+1))
|
|
create_instance $new_id
|
|
|
|
local tmp_dir=$(mktemp -d /tmp/hpdevkit.spawn.XXXXXX)
|
|
local node1_cfg=$(contract_dir_mount_path 1)/cfg/hp.cfg
|
|
local new_cfg=$(contract_dir_mount_path $new_id)/cfg/hp.cfg
|
|
|
|
# Override new node's contract id and unl from the information from node1.
|
|
|
|
# Copy node1 cfg to temp file and remove the information we don't need.
|
|
jq 'del(.contract,.node.public_key,.node.private_key)' $node1_cfg > $tmp_dir/a.json
|
|
jq '{contract:{id:.contract.id,execute:.contract.execute,unl:.contract.unl,log}}' $node1_cfg > $tmp_dir/b.json
|
|
jq -s '.[0] * .[1]' $tmp_dir/a.json $tmp_dir/b.json > $tmp_dir/from-node1.json
|
|
|
|
# Copy new cfg to temp file and remove the information we don't need.
|
|
jq 'del(.contract.unl,.contract.id)' $new_cfg > $tmp_dir/from-newnode.json
|
|
|
|
jq -s '.[0] * .[1]' $tmp_dir/from-node1.json $tmp_dir/from-newnode.json > $tmp_dir/merged.json
|
|
|
|
# Construct the list of all known peers.
|
|
local all_peers
|
|
for ((i=1; i<=$old_count; i++));
|
|
do
|
|
# Assign peer ports in incrementing order.
|
|
let peer_port=$(($peer_port_begin + $i - 1))
|
|
all_peers[i]="node$i:${peer_port}"
|
|
done
|
|
|
|
# Inject the new node configuration to the cfg.
|
|
local peers_json=$(joinarr all_peers $old_count -1)
|
|
let peer_port=$(($peer_port_begin + $new_id - 1))
|
|
let user_port=$(($user_port_begin + $new_id - 1))
|
|
jq ".mesh.port=$peer_port | .user.port=$user_port | .mesh.known_peers=$peers_json" $tmp_dir/merged.json > $new_cfg
|
|
|
|
# Cleanup temp dir.
|
|
rm -r $tmp_dir
|
|
|
|
# Start the new node.
|
|
change_cluster_status start $new_id
|
|
}
|
|
|
|
function change_cluster_status {
|
|
ensure_cluser_exists
|
|
|
|
local action=$1
|
|
local node=$2
|
|
local container_count=$(get_container_count)
|
|
for ((i=1; i<=$container_count; i++));
|
|
do
|
|
# If valid node no. has been specified, target that node. Otherwise target all nodes.
|
|
if ! [ "$node" -eq "$node" ] 2> /dev/null || [ $node -le 0 ] || [ $i -eq $node ] ; then
|
|
change_instance_status $action $i &
|
|
fi
|
|
done
|
|
wait
|
|
}
|
|
|
|
function attach_logs {
|
|
! cluster_exists && echo "Cluster '$cluster' does not exist." && exit 1
|
|
local node=$1
|
|
[ "$1" -eq "999999" ] && node=$(get_container_count)
|
|
local container_name="${container_prefix}_$node"
|
|
docker logs -f --tail=5 $container_name
|
|
}
|
|
|
|
function sync_instance {
|
|
local node=$1
|
|
local contract_dir=$(contract_dir_mount_path $node)
|
|
rm -rf $contract_dir/ledger_fs/* $contract_dir/contract_fs/*
|
|
mkdir -p $contract_dir/contract_fs/seed
|
|
cp -r $bundle_mount $contract_dir/contract_fs/seed/state
|
|
[ -d $contract_dir/contract_fs/seed/state/$disparate_dir ] && rm -r $contract_dir/contract_fs/seed/state/$disparate_dir
|
|
|
|
# Copy non-syncable files if exist.
|
|
[ -d "$bundle_mount/$disparate_dir/$i" ] && cp -r "$bundle_mount/$disparate_dir/$i/." "$contract_dir/contract_fs/seed/"
|
|
|
|
# Merge contract config overrides.
|
|
local cfg_file=$contract_dir/cfg/hp.cfg
|
|
local override_file=$contract_dir/contract_fs/seed/state/$config_overrides_file
|
|
if [ -f $override_file ] ; then
|
|
echo "Applying hp.cfg overrides"
|
|
jq -s '.[0] * .[1]' $cfg_file $override_file > $cfg_file.merged
|
|
mv $cfg_file.merged $cfg_file
|
|
rm $override_file
|
|
fi
|
|
}
|
|
|
|
function sync_contract_bundle {
|
|
ensure_cluster_not_running
|
|
local container_count=$(get_container_count)
|
|
for ((i=1; i<=$container_count; i++));
|
|
do
|
|
sync_instance $i &
|
|
done
|
|
wait
|
|
|
|
# Cleanup the original contract bundle dir which is used to sync from.
|
|
rm -r $bundle_mount/*
|
|
}
|
|
|
|
function validate_port_begin {
|
|
! validate_port $user_port_begin && echo "Invalid user port begin." && exit 1
|
|
! validate_port $peer_port_begin && echo "Invalid peer port begin." && exit 1
|
|
}
|
|
|
|
function show_status {
|
|
docker ps -a --format "table {{.Names}}\\t{{.State}}" | grep $container_prefix | sort
|
|
}
|
|
|
|
if [ $command = "create" ]; then
|
|
! validate_node_num_arg $cluster_size && echo "Invalid cluster size." && exit 1
|
|
validate_port_begin
|
|
create_cluster $cluster_size
|
|
elif [ $command = "bindmesh" ]; then
|
|
validate_port_begin
|
|
bind_mesh
|
|
elif [ $command = "destroy" ]; then
|
|
destroy_cluster
|
|
elif [ $command = "start" ]; then
|
|
change_cluster_status start $2
|
|
elif [ $command = "stop" ]; then
|
|
change_cluster_status stop $2
|
|
elif [ $command = "spawn" ]; then
|
|
spawn_node
|
|
elif [ $command = "logs" ]; then
|
|
! validate_node_num_arg $2 && echo "Usage: logs <node id>" && exit 1
|
|
attach_logs $2
|
|
elif [ $command = "sync" ]; then
|
|
sync_contract_bundle
|
|
elif [ $command = "status" ]; then
|
|
show_status
|
|
fi
|