|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# This file implements a test for bootstrapping a network while having <2/3rds of private keys available for Collector nodes. |
| 4 | +# In particular, it tests decentralized voting by Collection nodes on root cluster blocks as part of the bootstrapping process. |
| 5 | +# The test can be run using either local or GCP bucket vote transport. To test GCP bucket transport, set the `bucket` and `token` variables below. |
| 6 | +# To run this test, you must have `jq` and `gsutil` installed. |
| 7 | + |
| 8 | +bucket= |
| 9 | +token= |
| 10 | + |
| 11 | +bootstrapcmd="go run .." |
| 12 | +transitcmd="go run ../transit" |
| 13 | + |
| 14 | +clusteringpath="public-root-information/root-clustering.json" |
| 15 | +clustervotespath="public-root-information/root-block-votes" |
| 16 | +# partner dir must end with `public-root-information`, otherwise partner nodeinfo will not be read from it |
| 17 | +partner_dir="./public-root-information" |
| 18 | +# keygen dir must not exist yet, or be empty |
| 19 | +keygen_dir="./keygen" |
| 20 | + |
| 21 | +clusterCount=2 |
| 22 | + |
| 23 | +# exit early if anything fails |
| 24 | +set -e |
| 25 | +# avoid overwriting existing files or using data from a previous run |
| 26 | +if [ "$(ls | wc -l)" -gt 1 ] |
| 27 | +then |
| 28 | + echo "Found files in $(pwd), please clean up after previous runs:" |
| 29 | + echo "rm -rf permissionless public-root-information private-root-information execution-state $keygen_dir $partner_dir node-config.json partner-weights.json" |
| 30 | + exit 1 |
| 31 | +fi |
| 32 | + |
| 33 | +$bootstrapcmd genconfig --address-format "%s%d.example.com:3569" \ |
| 34 | + --access 2 --collection 7 --consensus 3 --execution 2 --verification 1 --weight 100 \ |
| 35 | + -o ./ --config ./node-config.json |
| 36 | +$bootstrapcmd keygen --config ./node-config.json -o "$keygen_dir" |
| 37 | + |
| 38 | + |
| 39 | +echo "simulating permissionless nodes" |
| 40 | +# mark >33% of collectors permissionless / non-internal |
| 41 | +permissionless_collectors=$(jq -r 'map(select(.["Role"]=="collection")) | .[:(length/3|floor)+1] | .[] | .["NodeID"]' \ |
| 42 | + -- "$keygen_dir/public-root-information/node-internal-infos.pub.json") |
| 43 | + |
| 44 | +# generate partner-weights file |
| 45 | +jq 'map({(.["NodeID"]):.["Weight"]}) | add' \ |
| 46 | + -- "$keygen_dir/public-root-information/node-internal-infos.pub.json" > ./partner-weights.json |
| 47 | + |
| 48 | +# generate partner-node-infos (for non-internal nodes only) |
| 49 | +mkdir -p "$partner_dir" |
| 50 | +for node in $permissionless_collectors |
| 51 | +do |
| 52 | + jq --arg jq_node_id "$node" '.[] | select(.["NodeID"]==$jq_node_id)' \ |
| 53 | + -- "$keygen_dir/public-root-information/node-internal-infos.pub.json" \ |
| 54 | + > "$partner_dir/node-info.pub.$node.json" |
| 55 | +done |
| 56 | + |
| 57 | +# create a directory for each permissionless node to store its private information |
| 58 | +for node in $permissionless_collectors |
| 59 | +do |
| 60 | + mkdir -p "./permissionless/$node/private-root-information" |
| 61 | + mkdir -p "./permissionless/$node/public-root-information" |
| 62 | + echo "$node" >> "./permissionless/$node/public-root-information/node-id" |
| 63 | + mv "$keygen_dir/private-root-information/private-node-info_$node" "./permissionless/$node/private-root-information/" |
| 64 | +done |
| 65 | + |
| 66 | + |
| 67 | +$bootstrapcmd cluster-assignment \ |
| 68 | + --epoch-counter 0 \ |
| 69 | + --collection-clusters $clusterCount \ |
| 70 | + --clustering-random-seed 00000000000000000000000000000000000000000000000000000000deadbeef \ |
| 71 | + --config ./node-config.json \ |
| 72 | + -o ./ \ |
| 73 | + --partner-dir "$partner_dir" \ |
| 74 | + --partner-weights ./partner-weights.json \ |
| 75 | + --internal-priv-dir "$keygen_dir/private-root-information" |
| 76 | + |
| 77 | + |
| 78 | +#confirm that the bootstrapping process cannot continue yet (not enough votes for cluster QCs) |
| 79 | +echo "Expecting FTL (not enough votes for Cluster QCs)..." |
| 80 | +$bootstrapcmd rootblock \ |
| 81 | + --root-chain bench \ |
| 82 | + --root-height 0 \ |
| 83 | + --root-parent 0000000000000000000000000000000000000000000000000000000000000000 \ |
| 84 | + --root-view 0 \ |
| 85 | + --epoch-counter 0 \ |
| 86 | + --epoch-length 30000 \ |
| 87 | + --epoch-staking-phase-length 20000 \ |
| 88 | + --epoch-dkg-phase-length 2000 \ |
| 89 | + --random-seed 00000000000000000000000000000000000000000000000000000000deadbeef \ |
| 90 | + --collection-clusters $clusterCount \ |
| 91 | + --use-default-epoch-timing \ |
| 92 | + --kvstore-finalization-safety-threshold=1000 \ |
| 93 | + --kvstore-epoch-extension-view-count=2000 \ |
| 94 | + --config ./node-config.json \ |
| 95 | + -o ./ \ |
| 96 | + --partner-dir "$partner_dir" \ |
| 97 | + --partner-weights ./partner-weights.json \ |
| 98 | + --internal-priv-dir "$keygen_dir/private-root-information" \ |
| 99 | + --intermediary-clustering-data "./$clusteringpath" \ |
| 100 | + --cluster-votes-dir "./$clustervotespath" \ |
| 101 | + | grep "not enough votes to create qc" |
| 102 | + |
| 103 | +echo "Collecting votes for Cluster QCs..." |
| 104 | +# permissionless collectors retrieve the clustering data |
| 105 | +if [ -n "$bucket" ] |
| 106 | +then |
| 107 | + # upload to cloud bucket; collectors pull using transit script |
| 108 | + echo "uploading to cloud bucket..." |
| 109 | + gsutil cp "./$clusteringpath" "gs://$bucket/$token/$clusteringpath" |
| 110 | + for node in $permissionless_collectors |
| 111 | + do |
| 112 | + $transitcmd pull-clustering -g "$bucket" -t "$token" -b "./permissionless/$node" |
| 113 | + done |
| 114 | +else |
| 115 | + # copy locally |
| 116 | + for node in $permissionless_collectors |
| 117 | + do |
| 118 | + cp "./$clusteringpath" "./permissionless/$node/$clusteringpath" |
| 119 | + done |
| 120 | +fi |
| 121 | + |
| 122 | +# cluster voting |
| 123 | +for node in $permissionless_collectors |
| 124 | +do |
| 125 | + $transitcmd generate-cluster-block-vote -b "./permissionless/$node" |
| 126 | +done |
| 127 | + |
| 128 | +# collectors push votes, and bootstrapping machine retrieves them |
| 129 | +if [ -n "$bucket" ] |
| 130 | +then |
| 131 | + # collectors push using transit script |
| 132 | + for node in $permissionless_collectors |
| 133 | + do |
| 134 | + $transitcmd push-cluster-block-vote -g "$bucket" -t "$token" -b "./permissionless/$node" |
| 135 | + done |
| 136 | + gsutil cp "gs://$bucket/$token/root-cluster-block-vote.*" "./$clustervotespath/" |
| 137 | +else |
| 138 | + # copy locally |
| 139 | + for node in $permissionless_collectors |
| 140 | + do |
| 141 | + cp "./permissionless/$node/private-root-information/private-node-info_$node/root-cluster-block-vote.json" "./$clustervotespath/root-cluster-block-vote.$node.json" |
| 142 | + done |
| 143 | +fi |
| 144 | + |
| 145 | + |
| 146 | +# root block creation should succeed now that we have enough votes |
| 147 | +$bootstrapcmd rootblock \ |
| 148 | + --root-chain bench \ |
| 149 | + --root-height 0 \ |
| 150 | + --root-parent 0000000000000000000000000000000000000000000000000000000000000000 \ |
| 151 | + --root-view 0 \ |
| 152 | + --epoch-counter 0 \ |
| 153 | + --epoch-length 30000 \ |
| 154 | + --epoch-staking-phase-length 20000 \ |
| 155 | + --epoch-dkg-phase-length 2000 \ |
| 156 | + --random-seed 00000000000000000000000000000000000000000000000000000000deadbeef \ |
| 157 | + --collection-clusters $clusterCount \ |
| 158 | + --use-default-epoch-timing \ |
| 159 | + --kvstore-finalization-safety-threshold=1000 \ |
| 160 | + --kvstore-epoch-extension-view-count=2000 \ |
| 161 | + --config ./node-config.json \ |
| 162 | + -o ./ \ |
| 163 | + --partner-dir "$partner_dir" \ |
| 164 | + --partner-weights ./partner-weights.json \ |
| 165 | + --internal-priv-dir "$keygen_dir/private-root-information" \ |
| 166 | + --intermediary-clustering-data "./$clusteringpath" \ |
| 167 | + --cluster-votes-dir "./$clustervotespath" |
| 168 | + |
| 169 | + |
| 170 | +# root block finalization should succeed with enough consensus votes |
| 171 | +$bootstrapcmd finalize \ |
| 172 | + --config ./node-config.json \ |
| 173 | + --partner-dir "./$partner_dir" \ |
| 174 | + --partner-weights ./partner-weights.json \ |
| 175 | + --internal-priv-dir "$keygen_dir/private-root-information" \ |
| 176 | + --dkg-data ./private-root-information/root-dkg-data.priv.json \ |
| 177 | + --root-block ./public-root-information/root-block.json \ |
| 178 | + --intermediary-bootstrapping-data ./public-root-information/intermediary-bootstrapping-data.json \ |
| 179 | + --root-block-votes-dir ./public-root-information/root-block-votes/ \ |
| 180 | + --root-commit 0000000000000000000000000000000000000000000000000000000000000000 \ |
| 181 | + --genesis-token-supply="1000000000.0" \ |
| 182 | + --service-account-public-key-json "{\"PublicKey\":\"R7MTEDdLclRLrj2MI1hcp4ucgRTpR15PCHAWLM5nks6Y3H7+PGkfZTP2di2jbITooWO4DD1yqaBSAVK8iQ6i0A==\",\"SignAlgo\":2,\"HashAlgo\":1,\"SeqNumber\":0,\"Weight\":1000}" \ |
| 183 | + -o ./ |
| 184 | + |
| 185 | +# allow output to be inspected if necessary |
| 186 | +echo "To clean up results, run:" |
| 187 | +echo "rm -rf permissionless public-root-information private-root-information execution-state $keygen_dir $partner_dir node-config.json partner-weights.json" |
0 commit comments