diff --git a/nix/workbench/backend/nomad/exec.sh b/nix/workbench/backend/nomad/exec.sh index 38a21875f9e..5c746903766 100644 --- a/nix/workbench/backend/nomad/exec.sh +++ b/nix/workbench/backend/nomad/exec.sh @@ -221,6 +221,13 @@ deploy-genesis-nomadexec() { msg "$(blue Reusing) already running local $(yellow "HTTP server")" fi msg "$(blue Creating) $(yellow "\"${nomad_job_name}.tar.zst\"") ..." +################################################################################ +################################################################################ +################################################################################ + cp /tmp/guardrails-script.plutus "$dir"/genesis/guardrails-script.plutus +################################################################################ +################################################################################ +################################################################################ if ! wb_nomad webfs add-genesis-dir "${dir}"/genesis "${nomad_job_name}" then if test "${nomad_agents_were_already_running}" = "false" diff --git a/nix/workbench/backend/supervisor.sh b/nix/workbench/backend/supervisor.sh index cdfe5bc049d..5f121ad822a 100755 --- a/nix/workbench/backend/supervisor.sh +++ b/nix/workbench/backend/supervisor.sh @@ -91,6 +91,13 @@ case "$op" in deploy-genesis ) local usage="USAGE: wb backend $op RUN-DIR" local dir=${1:?$usage}; shift +################################################################################ +################################################################################ +################################################################################ + cp /tmp/guardrails-script.plutus "${dir}"/genesis/guardrails-script.plutus +################################################################################ +################################################################################ +################################################################################ ;; describe-run ) diff --git a/nix/workbench/genesis/guardrails-script.plutus b/nix/workbench/genesis/guardrails-script.plutus new file mode 100644 index 00000000000..46ab9559eb6 --- /dev/null +++ b/nix/workbench/genesis/guardrails-script.plutus @@ -0,0 +1,5 @@ +{ + "type": "PlutusScriptV3", + "description": "*BE CAREFUL* that this is compiled from a release commit of plutus and not from master", + "cborHex": "5908545908510101003232323232323232323232323232323232323232323232323232323232323232323232323232323232259323255333573466e1d20000011180098111bab357426ae88d55cf00104554ccd5cd19b87480100044600422c6aae74004dd51aba1357446ae88d55cf1baa3255333573466e1d200a35573a002226ae84d5d11aab9e00111637546ae84d5d11aba235573c6ea800642b26006003149a2c8a4c301f801c0052000c00e0070018016006901e406cc00e003000c00d20d00fc000c0003003800a4005801c00e003002c00d20c09a0c80d9801c006001801a4101b5881380018000600700148013003801c006005801a410100078001801c006001801a4101001f8001800060070014801b0038018096007001800600690404002600060001801c0052008c00e006025801c006001801a41209d8001800060070014802b003801c006005801a410112f501b3003800c00300348202b7881300030000c00e00290066007003800c00b003482032ad7b806036403060070014803b00380180960003003800a4021801c00e003002c00d20f40380d9801c006001801a41403f800100a0c00e0029009600f0030078040c00e002900a600f003800c00b003301c483403e01a600700180060066038904801e00060001801c0052016c01e00600f801c006001801980ca402900e30000c00e002901060070030128060c00e00290116007003800c00b003483c0ba03660070018006006906432e00040283003800a40498003003800a404d802c00e00f003800c00b003301c480cb0003003800c003003301c4802b00030001801c01e0070018016006603890605c0160006007001800600660389048276000600030000c00e0029014600b003801c00c04b003800c00300348203a2489b00030001801c00e006025801c006001801a4101b11dc2df80018000c0003003800a4055802c00e007003012c00e003000c00d2080b8b872c000c0006007003801809600700180060069040607e4155016000600030000c00e00290166007003012c00e003000c00d2080c001c000c0003003800a405d801c00e003002c00d20c80180d9801c006001801a412007800100a0c00e00290186007003014c0006007001480cb0058018016006007801801e00600300403003800a4069802c00c00b003003c00c00f003803c00e003002c00c03f00333023480692028c0004014c00c007003002c00c00b003002c00e00f003800c00b00300f80590052008003003800a406d801c00e003002c00d2000c00d2006c00060070018006006900a600060001801c0052038c00e007001801600690006006901260003003800c003003483281300020141801c005203ac00e006029801c006001801a403d800180006007001480f3003801804e00700180060069040404af3c4e302600060001801c005203ec00e006013801c006001801a4101416f0fd20b80018000600700148103003801c006005801a403501b3003800c0030034812b00030000c00e0029021600f003800c00a01ac00e003000c00ccc08d20d00f4800b00030000c0000000000803c00c017003800c003003014c00c04b00018000803c00c013003800c00300301380498000803c00c00e004400e00f003800c00b00300bc000802180020070018006006021801808e00030004006005801804e0060158000800c00b00330154805200c400e00300080330004006005801a4001801a410112f58000801c00600901160008019807240118002007001800600690404a75ee01e00060008018026000801803e000300d48010c03520c80130074800a0030028048c011200a800c00b0034800b0000c01d2002300448050c0312008300b48000c029200630094804a00690006000300748008c0192066300a2233335573e00250002801994004d55ce800cd55cf0008d5d08014c00cd5d10011263009222532900389800a4d2219002912c80344c01526910c80148964cc04cdd68010034564cc03801400626601800e0071801226601800e01518010096400a3000910c008600444002600244004a664600200244246466004460044460040064600444600200646a660080080066a00600224446600644b20051800484ccc02600244666ae68cdc3801000c00200500a91199ab9a33710004003000801488ccd5cd19b89002001800400a44666ae68cdc4801000c00a00122333573466e20008006005000912a999ab9a3371200400222002220052255333573466e2400800444008440040026eb400a42660080026eb000a4264666015001229002914801c8954ccd5cd19b8700400211333573466e1c00c006001002118011229002914801c88cc044cdc100200099b82002003245200522900391199ab9a3371066e08010004cdc1001001c002004403245200522900391199ab9a3371266e08010004cdc1001001c00a00048a400a45200722333573466e20cdc100200099b820020038014000912c99807001000c40062004912c99807001000c400a2002001199919ab9a357466ae880048cc028dd69aba1003375a6ae84008d5d1000934000dd60010a40064666ae68d5d1800c0020052225933006003357420031330050023574400318010600a444aa666ae68cdc3a400000222c22aa666ae68cdc4000a4000226600666e05200000233702900000088994004cdc2001800ccdc20010008cc010008004c01088954ccd5cd19b87480000044400844cc00c004cdc300100091119803112c800c60012219002911919806912c800c4c02401a442b26600a004019130040018c008002590028c804c8888888800d1900991111111002a244b267201722222222008001000c600518000001112a999ab9a3370e004002230001155333573466e240080044600823002229002914801c88ccd5cd19b893370400800266e0800800e00100208c8c0040048c0088cc008008005" +} diff --git a/nix/workbench/service/generator.nix b/nix/workbench/service/generator.nix index 3f347c2a643..62fb7dec025 100644 --- a/nix/workbench/service/generator.nix +++ b/nix/workbench/service/generator.nix @@ -129,6 +129,14 @@ let value = '' #!${pkgs.stdenv.shell} + ${import ./voting.nix {inherit pkgs profile nodeSpecs;}} + + ${pkgs.coreutils}/bin/echo "governance_workload_funds" + governance_workload_funds \ + ${if profile.composition.with_explorer then "explorer" else "node-0"} \ + "../genesis/cache-entry/utxo-keys/utxo2.vkey" \ + "../genesis/cache-entry/utxo-keys/utxo2.skey" + ${service.script} ''; JSON = pkgs.writeScript "startup-generator.sh" value; diff --git a/nix/workbench/service/healthcheck.nix b/nix/workbench/service/healthcheck.nix index 76a967f63e1..d2c66493794 100644 --- a/nix/workbench/service/healthcheck.nix +++ b/nix/workbench/service/healthcheck.nix @@ -53,12 +53,14 @@ let active_slots="$(${jq}/bin/jq --null-input -r \ "''${epoch_length} * ''${active_slots_coeff}" \ )" + with_explorer="$(${jq}/bin/jq .composition.with_explorer ../profile.json)" ${coreutils}/bin/echo "profile.json:" ${coreutils}/bin/echo "- network_magic: ''${network_magic}" ${coreutils}/bin/echo "- slot_duration: ''${slot_duration}" ${coreutils}/bin/echo "- epoch_length: ''${epoch_length}" ${coreutils}/bin/echo "- active_slots_coeff: ''${active_slots_coeff}" ${coreutils}/bin/echo "- active_slots: ''${active_slots}" + ${coreutils}/bin/echo "- with_explorer: ''${with_explorer}" # Fetch all defined node names (Including "explorer" nodes) ########################################################### @@ -149,6 +151,27 @@ let ${coreutils}/bin/sleep 1 done msg "Node "\"''${node}\"" is now synced!" + + ###################################################################### + ###################################################################### + if test "''${node}" = "node-0" + then + ${coreutils}/bin/echo "governance_create_constitution" + governance_create_constitution "''${node}" \ + "../genesis/cache-entry/utxo-keys/utxo2.vkey" \ + "../genesis/cache-entry/utxo-keys/utxo2.skey" + ${coreutils}/bin/sleep 60 + governance_vote_all "''${node}" "''${node#node-}" + else + while true + do + governance_vote_all "''${node}" "''${node#node-}" + ${coreutils}/bin/sleep 10 + done + fi + ###################################################################### + ###################################################################### + done # This is an "explorer" node (only one node and generator). @@ -223,7 +246,7 @@ let # If the ping fails the whole script must fail! ${cardano-cli}/bin/cardano-cli ping \ --magic "''${network_magic}" \ - --count 3 \ + --count 1 \ --json \ --host "''${host}" \ --port "''${port}" @@ -935,6 +958,12 @@ let exit 22 } + ###################################################################### + # Conway/governance functions! ####################################### + ###################################################################### + + ${import ./voting.nix {inherit pkgs profile nodeSpecs;}} + if test -n "''${NOMAD_DEBUG:-}" then DEBUG_FILE="$(${coreutils}/bin/dirname "$(${coreutils}/bin/readlink -f "$0")")"/"$0".debug diff --git a/nix/workbench/service/voting.nix b/nix/workbench/service/voting.nix new file mode 100644 index 00000000000..9b6ae294e5a --- /dev/null +++ b/nix/workbench/service/voting.nix @@ -0,0 +1,836 @@ +{ pkgs +, profile +, nodeSpecs +}: + +let + + # Packages + ########## + bashInteractive = pkgs.bashInteractive; + coreutils = pkgs.coreutils; + jq = pkgs.jq; + cardano-cli = pkgs.cardanoNodePackages.cardano-cli; + + # Script params! + ################ + testnetMagic = 42; # TODO: get it from `profile`! + # Construct an "array" with node producers to use in BASH `for` loops. + producers_array = + "(" + + (builtins.concatStringsSep + " " + (builtins.map + (x: "\"" + x.name + "\"") + (builtins.filter + (nodeSpec: nodeSpec.isProducer) + (pkgs.lib.mapAttrsToList + (nodeName: nodeSpec: {inherit (nodeSpec) name isProducer;}) + nodeSpecs + ) + ) + ) + ) + + ")" + ; + # Where to obtain the genesis funds from. + genesis_funds_vkey = "../genesis/cache-entry/utxo-keys/utxo2.vkey"; + genesis_funds_skey = "../genesis/cache-entry/utxo-keys/utxo2.skey"; + # Sleeps. + wait_any_utxos_tries = 20; + wait_any_utxos_sleep = 5; + wait_utxo_id_tries = 10; + wait_utxo_id_sleep = 10; + # How many constitutions to create with the genesis funds. + constitutions_from_genesis = 1; # HACK: "Estimated transaction fee: 182617 Lovelace" + # Initial donation from genesis funds to make "valid" withdrawal proposals. + treasury_donation = 500000; + # When splitting the genesis funds, we first move to a "node address" (called + # DRep 0) for each producer, and then to a "node-drep address" for each + # node-drep combination. + # HACK: 1000 DReps for 52 producers. TODO: Calculate from profile / workload! + dreps_per_producer = 19; + # To calculate how much to leave on each nodes' address (DRep 0) for the nodes + # to create withdrawal proposals (`--governance-action-deposit` argument). + proposals_per_node = 2; + # TODO: Calculate from profile and workload! + votes_per_dreps = 2; +in '' + +################################################################################ +# Must be called by UTxO consumers. +################################################################################ +function get_socket_lock { + + # Function arguments. + local nodeName=''${1}; shift # node name / folder to find the socket. + + local socket_path="../''${nodeName}/node.socket" + local lockfile_path="''${socket_path}".lock + + exec 200>"''${lockfile_path}" + flock 200 + ${coreutils}/bin/echo "''${socket_path}" +} + +################################################################################ +# Release the socket and cache the next, expected, UTxO. +################################################################################ +function release_socket_lock { + + # Function arguments. + local nodeName=$1 # node name / folder to find the socket. + local tx_signed=$2 + local addr=$3 + + local socket_path="../''${nodeName}/node.socket" + local lockfile_path="''${socket_path}".lock + + # Store address next UTxO + calculate_next_utxo \ + "''${tx_signed}" \ + "''${addr}" \ + > ../"''${nodeName}"/"''${addr}".utxo + + # A mystery! + flock -u 200 2>/dev/null || true + exec 200>&- +} + +################################################################################ +# Evenly split the first UTxO of this key to the addresses in the array! +# No waiting, no UTxO "cache", no fancy stuff for the input. +# Stores the future UTxO in a file for later references. +################################################################################ +function funds_from_to { + + # Function arguments. + local nodeName=''${1}; shift # node name / folder to find the socket. + local utxo_vkey=''${1}; shift # In + local utxo_skey=''${1}; shift # In + local reminder=''${1}; shift # Funds to keep in the origin address. + local donation=''${1}; shift # To treasury. + local addrs_array=("$@") # Outs + + # Only defined in functions that use it. + local socket_path + # Lock needed. Creates or destroys UTxOs. + socket_path="$(get_socket_lock "''${nodeName}")" + + # Get the "in" address and its first utxo. + local funds_addr funds_json funds_tx funds_lovelace + funds_addr="$( \ + ${cardano-cli}/bin/cardano-cli address build \ + --testnet-magic ${toString testnetMagic} \ + --payment-verification-key-file "''${utxo_vkey}" \ + )" + funds_json="$( \ + ${cardano-cli}/bin/cardano-cli query utxo \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --address "''${funds_addr}" \ + --output-json \ + )" + funds_tx="$( \ + ${coreutils}/bin/echo "''${funds_json}" \ + | ${jq}/bin/jq -r \ + 'keys[0]' \ + )" + funds_lovelace="$( \ + ${coreutils}/bin/echo "''${funds_json}" \ + | ${jq}/bin/jq -r \ + --arg keyName "''${funds_tx}" \ + '.[$keyName].value.lovelace' \ + )" + + # Calculate how much lovelace for each output address. + local addrs_count per_out_lovelace + addrs_count="''${#addrs_array[@]}" + ### HACK: Fees! Always using 300000!!! + ### With 2 outputs: "Estimated transaction fee: 172233 Lovelace" + ### With 10 outputs: "Estimated transaction fee: 186665 Lovelace" + ### With 53 outputs: "Estimated transaction fee: 264281 Lovelace" + per_out_lovelace="$( \ + ${jq}/bin/jq -r --null-input \ + --argjson numerator "''${funds_lovelace}" \ + --argjson denominator "''${addrs_count}" \ + --argjson reminder "''${reminder}" \ + --argjson donation "''${donation}" \ + '(($numerator - $reminder - $donation - 300000) / $denominator | round)' \ + )" + + # Build the "--tx-out" arguments array. + local txOuts_array=() + for addr in "''${addrs_array[@]}" + do + txOuts_array+=("--tx-out") + txOuts_array+=("''${addr}+''${per_out_lovelace}") + done + + # Some debugging! + ${coreutils}/bin/echo "funds_from_to: ''${utxo_vkey} (''${funds_addr}): --tx-in ''${funds_tx} ''${txOuts_array[*]}" + + # Send to each node! + # Build transaction. + local tx_filename=../"''${nodeName}"/tx."''${funds_addr}" + ${cardano-cli}/bin/cardano-cli conway transaction build \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-in "''${funds_tx}" \ + ''${txOuts_array[@]} \ + --treasury-donation "''${donation}" \ + --change-address "''${funds_addr}" \ + --out-file "''${tx_filename}.raw" + # Sign transaction. + ${cardano-cli}/bin/cardano-cli conway transaction sign \ + --testnet-magic ${toString testnetMagic} \ + --signing-key-file "''${utxo_skey}" \ + --tx-body-file "''${tx_filename}.raw" \ + --out-file "''${tx_filename}.signed" + # Submit transaction. + ${cardano-cli}/bin/cardano-cli conway transaction submit \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-file "''${tx_filename}.signed" + + release_socket_lock \ + "''${nodeName}" \ + "''${tx_filename}.signed" \ + "''${funds_addr}" +} + +################################################################################ +# Given a tx.signed return TxHash#TxIx of the first occurrence of an address. +################################################################################ +function calculate_next_utxo { + + # Function arguments. + local tx_signed=$1 + local addr=$2 + + local tx_id tx_ix + + tx_id="$( \ + ${cardano-cli}/bin/cardano-cli conway transaction txid \ + --tx-file "''${tx_signed}" \ + )" + + tx_ix="$( \ + ${cardano-cli}/bin/cardano-cli debug transaction view \ + --tx-file "''${tx_signed}" \ + | \ + ${jq}/bin/jq -r \ + --argjson addr "\"''${addr}\"" \ + ' + .outputs + | map(.address == $addr) + | index(true) + ' \ + )" + + if test "''${tx_ix}" = "null" + then + error "ERROR" + else + ${coreutils}/bin/echo "''${tx_id}#''${tx_ix}" + fi +} + +################################################################################ +# Get pre-calculated "cached" future UTxO or nothing (an "empty" string). +################################################################################ +function get_address_utxo_expected { + + # Function arguments. + local nodeName=$1 # node name / folder to find the socket. + local addr=$2 + + local utxo_file=../"''${nodeName}"/"''${addr}".utxo + if test -f "''${utxo_file}" + then + ${coreutils}/bin/cat "''${utxo_file}" + fi +} + +################################################################################ +# Get the first UTxO available (Must be non-empty or it fails!) +################################################################################ +function get_address_utxo_0 { + + # Function arguments. + local nodeName=$1 # node name / folder to find the socket. + local addr=$2 + + # Only defined in functions that use it. + local socket_path="../''${nodeName}/node.socket" + + ${cardano-cli}/bin/cardano-cli query utxo \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --address "''${addr}" \ + --output-json \ + | ${jq}/bin/jq -r 'keys[0]' +} + +################################################################################ +# Waits until the UTxOs of this address are not empty (Error on timeout). +################################################################################ +function wait_any_utxos { + + # Function arguments. + local nodeName=$1 # node name / folder to find the socket. + local addr=$2 + + # Only defined in functions that use it. + local socket_path="../''${nodeName}/node.socket" + + local tries=${toString wait_any_utxos_tries} + local utxos_json="{}" + while test "''${utxos_json}" = "{}" + do + if test "''${tries}" = 0 + then + ${coreutils}/bin/echo "wait_any_utxos: Failed waiting for ''${addr}" + fi + utxos_json="$( \ + ${cardano-cli}/bin/cardano-cli query utxo \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --address "''${addr}" \ + --output-json \ + )" + if ! test "''${tries}" = ${toString wait_any_utxos_tries} + then + ${coreutils}/bin/sleep ${toString wait_any_utxos_sleep} + fi + tries="$((tries - 1))" + done + + # Return first tx_id from the "cached" response (not get_address_utxo_0!). + ${coreutils}/bin/echo "''${utxos_json}" \ + | ${jq}/bin/jq -r \ + 'keys[0]' +} + +################################################################################ +# Waits until an specific UTxO of this address appears or returns empty. +################################################################################ +function wait_utxo_id { + + # Function arguments. + local nodeName=$1 # node name / folder to find the socket. + local addr=$2 + local utxo_id=$3 + + # Only defined in functions that use it. + local socket_path="../''${nodeName}/node.socket" + + local contains_addr="false" + local tries=${toString wait_utxo_id_tries} + while test "''${contains_addr}" = "false" + do + if test "''${tries}" = 0 + then + # Time's up! + return; + else + local utxos_json + utxos_json="$( \ + ${cardano-cli}/bin/cardano-cli query utxo \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --address "''${addr}" \ + --output-json \ + )" + contains_addr="$( \ + ${coreutils}/bin/echo "''${utxos_json}" \ + | ${jq}/bin/jq --raw-output \ + --argjson utxo_id "\"''${utxo_id}\"" \ + 'keys | any(. == $utxo_id) // false' \ + )" + if ! test "''${tries}" = ${toString wait_utxo_id_tries} + then + ${coreutils}/bin/sleep ${toString wait_utxo_id_sleep} + fi + tries="$((tries - 1))" + fi + done + + # Return the expected UTxO ID to be able to easily check the response. + ${coreutils}/bin/echo "''${utxo_id}" +} + +################################################################################ +# Waits until the UTxOs of this address are not empty. +################################################################################ +function wait_utxo_expected_or_0 { + + # Function arguments. + local nodeName=$1 # node name / folder to find the socket. + local addr=$2 + + local tx_id_expected="" + tx_id_expected="$(get_address_utxo_expected "''${nodeName}" "''${addr}")" + if test "''${tx_id_expected}" = "" || test -z "''${tx_id_expected}" + then + # No expected tx_id + # Waits for any to appear and returns the first one. + wait_any_utxos "''${nodeName}" "''${addr}" + else + local tx_id_wait + tx_id_wait="$(wait_utxo_id "''${nodeName}" "''${addr}" "''${tx_id_expected}")" + if test "''${tx_id_expected}" = "''${tx_id_wait}" + then + ${coreutils}/bin/echo "''${tx_id_expected}" + else + get_address_utxo_0 + fi + fi +} + +################################################################################ +# Hack: Given a node "i" and a DRep number create always the same address keys. +# Only supports up to 99 nodes and 9999 DReps by adding the missing Hex chars. +# Returns the file path without the extensions (the ".skey" or ".vkey" part). +################################################################################ +function create_node_drep_keys { + + # Function arguments. + local nodeName=$1 + local node_i=$2 # This "i" is part of the node name ("node-i"). + local drep_i=$3 + + local filename=../"''${nodeName}"-drep-"''${drep_i}" + # Now with the extensions. + local skey="''${filename}".skey + local vkey="''${filename}".vkey + + # Only create if not already there! + if ! test -f "''${vkey}" + then + ${jq}/bin/jq --null-input \ + --argjson node_i "''${node_i}" \ + --argjson drep_i "''${drep_i}" \ + ' + {"type": "PaymentSigningKeyShelley_ed25519", + "description": "Payment Signing Key", + "cborHex": ( + "5820b02868d722df021278c78be3b7363759b37f5852b8747b488bab20c3c8" + + (if $node_i <= 9 + then ("0" + ($node_i | tostring)) + elif $node_i >= 10 and $node_i <= 99 + then ( $node_i | tostring) + else (error ("Node ID above 99")) + end + ) + + (if $drep_i <= 9 + then ( "000" + ($drep_i | tostring)) + elif $drep_i >= 10 and $drep_i <= 99 + then ( "00" + ($drep_i | tostring)) + elif $drep_i >= 100 and $drep_i <= 999 + then ( "0" + ($drep_i | tostring)) + elif $drep_i >= 1000 and $drep_i <= 9999 + then ( ($drep_i | tostring)) + else (error ("DRep ID above 9999")) + end + ) + ) + } + ' \ + > "''${skey}" + ${cardano-cli}/bin/cardano-cli conway key verification-key \ + --signing-key-file "''${skey}" \ + --verification-key-file "''${vkey}" + fi + ${coreutils}/bin/echo "''${filename}" +} + +################################################################################ +# Get address of the node-drep combination! +################################################################################ +function build_node_drep_address { + + # Function arguments. + local nodeName=$1 # node name to use. + local node_i=$2 # This "i" is part of the node name ("node-i"). + local drep_i=$3 + + local filename addr + filename="$(create_node_drep_keys "''${nodeName}" "''${node_i}" "''${drep_i}")" + addr="''${filename}.addr" + # Only create if not already there! + if ! test -f "''${addr}" + then + local vkey="''${filename}".vkey + ${cardano-cli}/bin/cardano-cli address build \ + --testnet-magic ${toString testnetMagic} \ + --payment-verification-key-file "''${vkey}" \ + > "''${addr}" + fi + ${coreutils}/bin/cat "''${addr}" +} + +################################################################################ +# Deposit needed to create a proposal (`--governance-action-deposit` argument). +################################################################################ +function get_gov_action_deposit { + + local nodeName=$1 # node name / folder to find the socket. + + # Only defined in functions that use it. + local socket_path="../''${nodeName}/node.socket" + + ${cardano-cli}/bin/cardano-cli conway query gov-state \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + | ${jq}/bin/jq -r '.currentPParams.govActionDeposit' +} + +################################################################################ +# Evenly distribute to all producer nodes the "utxo_key" genesis funds. +# First send all funds evenly to all producers and later to an address for each +# producers DReps (TODO in as many UTxO as the times it intends to vote ???). +################################################################################ +function governance_workload_funds { + + # Function arguments. + local nodeName=$1 # node name / folder to find the socket. + local utxo_vkey=$2 + local utxo_skey=$3 + local producers=${toString producers_array} + + local action_deposit + action_deposit="$(get_gov_action_deposit "''${nodeName}")" + + local constitution_reminder + # HACK: Plus a fee estimate. + constitution_reminder="$((action_deposit * ${toString constitutions_from_genesis} + 500000))" + + # Send funds to each node (using DRep ID as 0). + ${coreutils}/bin/echo "Node splitting phase!" + local node_addrs_array=() + for nodeName in ''${producers[*]} + do + local node_i + local node_addr + node_i="$( \ + ${jq}/bin/jq --raw-output \ + --arg keyName "''${nodeName}" \ + '.[$keyName].i' \ + ../node-specs.json \ + )" + # Drep 0 is No DRep (funds for the node) + node_addr="$(build_node_drep_address "''${nodeName}" "''${node_i}" 0)" + node_addrs_array+=("''${node_addr}") + ${coreutils}/bin/echo "Splitting to: ''${nodeName} - ''${node_i} - 0 - ''${node_addr}" + done + funds_from_to \ + "''${nodeName}" \ + "''${utxo_vkey}" "''${utxo_skey}" \ + "''${constitution_reminder}" \ + ${toString treasury_donation} \ + "''${node_addrs_array[@]}" + + local proposals_reminder + # HACK: Plus a fee estimate. + proposals_reminder="$((action_deposit * ${toString proposals_per_node} + 500000))" + + # Send funds to each node's assigned DReps (DReps N to ???). + ${coreutils}/bin/echo "Nodes' assigned DRep(s) splitting phase!" + local drep_step=0 + for nodeName in ''${producers[*]} + do + local node_i + node_i="$( \ + ${jq}/bin/jq --raw-output \ + --arg keyName "''${nodeName}" \ + '.[$keyName].i' \ + ../node-specs.json \ + )" + local actual_drep + local node_i_addrs_array=() + for i in {1..${toString dreps_per_producer}} + do + local node_addr + actual_drep="$((drep_step + i))" + node_addr="$(build_node_drep_address "''${nodeName}" "''${node_i}" "''${actual_drep}")" + node_i_addrs_array+=("''${node_addr}") + ${coreutils}/bin/echo "Splitting to: ''${nodeName} - ''${node_i} - ''${actual_drep} - ''${node_addr}" + done + local node_i_vkey node_i_skey + node_i_vkey="$(create_node_drep_keys "''${nodeName}" "''${node_i}" 0)".vkey + node_i_skey="$(create_node_drep_keys "''${nodeName}" "''${node_i}" 0)".skey + local node_i_addr + node_i_addr="$(build_node_drep_address "''${nodeName}" "''${node_i}" 0)" + ${coreutils}/bin/echo "Wait for funds: $(${coreutils}/bin/date --rfc-3339=seconds)" + wait_any_utxos "''${nodeName}" "''${node_i_addr}" >/dev/null + ${coreutils}/bin/echo "Funds available: $(${coreutils}/bin/date --rfc-3339=seconds)" + funds_from_to \ + "''${nodeName}" \ + "''${node_i_vkey}" "''${node_i_skey}" \ + "''${proposals_reminder}" \ + 0 \ + "''${node_i_addrs_array[@]}" + drep_step="$((drep_step + ${toString dreps_per_producer}))" + done +} + +################################################################################ +function governance_create_constitution { + + # Function arguments. + local nodeName=$1 + local utxo_vkey=$2 + local utxo_skey=$3 + + ${coreutils}/bin/echo "governance_create_constitution: ''${nodeName} - ''${utxo_vkey}" + + # Only defined in functions that use it. + local socket_path + # Lock needed. Creates or destroys UTxOs. + socket_path="$(get_socket_lock "''${nodeName}")" + + local node_addr + node_addr="$( \ + ${cardano-cli}/bin/cardano-cli address build \ + --testnet-magic ${toString testnetMagic} \ + --payment-verification-key-file "''${utxo_vkey}" + )" + + # Funds needed for this governance action ? + local action_deposit + action_deposit="$(get_gov_action_deposit "''${nodeName}")" + # Funds address. + local funds_tx + funds_tx="$(wait_utxo_expected_or_0 "''${nodeName}" "''${node_addr}")" + + # Show current gov-state. + ${cardano-cli}/bin/cardano-cli conway query gov-state \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + | ${jq}/bin/jq -r \ + '.nextRatifyState.nextEnactState.prevGovActionIds' + + # Create dummy constitution. + ${coreutils}/bin/echo "My Constitution: free mate and asado" \ + > ../"''${nodeName}"/constitution.txt + # Calculate constitution hash. + ${cardano-cli}/bin/cardano-cli hash anchor-data \ + --file-text ../"''${nodeName}"/constitution.txt \ + --out-file ../"''${nodeName}"/constitution.hash + # Copy guardrails-script. + ${coreutils}/bin/cp \ + ../genesis/guardrails-script.plutus \ + ../"''${nodeName}"/guardrails-script.plutus + # Calculate guardrails-script hash. + ${cardano-cli}/bin/cardano-cli hash script \ + --script-file ../"''${nodeName}"/guardrails-script.plutus \ + --out-file ../"''${nodeName}"/guardrails-script.hash + + # Create action. + local tx_filename=../"''${nodeName}"/create-constitution + ${cardano-cli}/bin/cardano-cli conway governance action create-constitution \ + --testnet \ + --anchor-url "https://raw.githubusercontent.com/cardano-foundation/CIPs/master/CIP-0100/cip-0100.common.schema.json" \ + --anchor-data-hash "9d99fbca260b2d77e6d3012204e1a8658f872637ae94cdb1d8a53f4369400aa9" \ + --constitution-url "https://ipfs.io/ipfs/Qmdo2J5vkGKVu2ur43PuTrM7FdaeyfeFav8fhovT6C2tto" \ + --constitution-hash "$(cat ../"''${nodeName}"/constitution.hash)" \ + --constitution-script-hash "$(cat ../"''${nodeName}"/guardrails-script.hash)" \ + --governance-action-deposit "''${action_deposit}" \ + --deposit-return-stake-verification-key-file ../genesis/pools/staking-reward1.vkey \ + --out-file "''${tx_filename}".action + # Build transaction. + ${cardano-cli}/bin/cardano-cli conway transaction build \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-in "''${funds_tx}" \ + --change-address "''${node_addr}" \ + --proposal-file "''${tx_filename}".action \ + --out-file "''${tx_filename}".raw + # Sign transaction. + ${cardano-cli}/bin/cardano-cli conway transaction sign \ + --testnet-magic ${toString testnetMagic} \ + --signing-key-file "''${utxo_skey}" \ + --tx-body-file "''${tx_filename}".raw \ + --out-file "''${tx_filename}".signed + # Submit transaction. + ${cardano-cli}/bin/cardano-cli conway transaction submit \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-file "''${tx_filename}".signed + + release_socket_lock \ + "''${nodeName}" \ + "''${tx_filename}.signed" \ + "''${node_addr}" +} + +################################################################################ +function governance_create_withdrawal { + + # Function arguments. + local nodeName=$1 + local node_i=$2 + local drep_i=$3 + + ${coreutils}/bin/echo "governance_create_withdrawal: ''${nodeName} - ''${node_i} - ''${drep_i}" + + # Only defined in functions that use it. + local socket_path + # Lock needed. Creates or destroys UTxOs. + socket_path="$(get_socket_lock "''${nodeName}")" + + local node_drep_vkey node_drep_skey node_drep_addr + node_drep_vkey="$(create_node_drep_keys "''${nodeName}" "''${node_i}" "''${drep_i}")".vkey + node_drep_skey="$(create_node_drep_keys "''${nodeName}" "''${node_i}" "''${drep_i}")".skey + node_drep_addr="$(build_node_drep_address "''${nodeName}" "''${node_i}" "''${drep_i}")" + + # Funds needed for this governance action ? + local action_deposit + action_deposit="$(get_gov_action_deposit "''${nodeName}")" + # Funds address. + local funds_tx + funds_tx="$(wait_utxo_expected_or_0 "''${nodeName}" "''${node_drep_addr}")" + + local tx_filename=../"''${nodeName}"/create-withdrawal + # Create action. + ${cardano-cli}/bin/cardano-cli conway governance action create-treasury-withdrawal \ + --testnet \ + --anchor-url "https://raw.githubusercontent.com/cardano-foundation/CIPs/master/CIP-0108/examples/treasury-withdrawal.jsonld" \ + --anchor-data-hash "311b148ca792007a3b1fee75a8698165911e306c3bc2afef6cf0145ecc7d03d4" \ + --governance-action-deposit "''${action_deposit}" \ + --transfer 50 \ + --deposit-return-stake-verification-key-file ../genesis/pools/staking-reward1.vkey \ + --funds-receiving-stake-verification-key-file ../genesis/pools/staking-reward2.vkey \ + --out-file "''${tx_filename}".action + # Build transaction. + ${cardano-cli}/bin/cardano-cli conway transaction build \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-in "''${funds_tx}" \ + --change-address "''${node_drep_addr}" \ + --proposal-file "''${tx_filename}".action \ + --out-file "''${tx_filename}".raw + # Sign transaction. + ${cardano-cli}/bin/cardano-cli conway transaction sign \ + --testnet-magic ${toString testnetMagic} \ + --signing-key-file "''${node_drep_skey}" \ + --tx-body-file "''${tx_filename}".raw \ + --out-file "''${tx_filename}".signed + # Submit transaction. + ${cardano-cli}/bin/cardano-cli conway transaction submit \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-file "''${tx_filename}".signed + + release_socket_lock \ + "''${nodeName}" \ + "''${tx_filename}.signed" \ + "''${node_drep_addr}" +} + +################################################################################ +function governance_vote_proposal { + + # Function arguments. + local nodeName=$1 + local node_i=$2 + local proposal_id=$3 + + ${coreutils}/bin/echo "governance_vote_proposal: ''${nodeName} - ''${node_i} - ''${proposal_id}" + + # Only defined in functions that use it. + local socket_path + # Lock needed. Creates or destroys UTxOs. + socket_path="$(get_socket_lock "''${nodeName}")" + + local drep_step actual_drep + drep_step=$((node_i * ${toString dreps_per_producer})) + for i in {1..${toString dreps_per_producer}} # for drepDir in ../genesis/cache-entry/drep-keys/drep* + do + actual_drep="$((drep_step + i))" + + local node_drep_vkey node_drep_skey node_drep_addr + node_drep_vkey="$(create_node_drep_keys "''${nodeName}" "''${node_i}" "''${actual_drep}")".vkey + node_drep_skey="$(create_node_drep_keys "''${nodeName}" "''${node_i}" "''${actual_drep}")".skey + node_drep_addr="$(build_node_drep_address "''${nodeName}" "''${node_i}" "''${actual_drep}")" + + # Funds address. + local funds_tx + funds_tx="$(wait_utxo_expected_or_0 "''${nodeName}" "''${node_drep_addr}")" + + # Voting with DRep keys: + local tx_filename=../"''${nodeName}"/vote + ${cardano-cli}/bin/cardano-cli conway governance vote create \ + --yes \ + --governance-action-tx-id "''${proposal_id}" \ + --governance-action-index "0" \ + --drep-verification-key-file ../genesis/cache-entry/drep-keys/drep"''${actual_drep}"/drep.vkey \ + --out-file "''${tx_filename}".action + # Build the transaction. + ${cardano-cli}/bin/cardano-cli conway transaction build \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-in "''${funds_tx}" \ + --change-address "''${node_drep_addr}" \ + --witness-override 2 \ + --vote-file "''${tx_filename}".action \ + --out-file "''${tx_filename}".raw + # Sign it with the DRep key: + ${cardano-cli}/bin/cardano-cli transaction sign \ + --testnet-magic ${toString testnetMagic} \ + --signing-key-file ../genesis/cache-entry/drep-keys/drep"''${actual_drep}"/drep.skey \ + --signing-key-file "''${node_drep_skey}" \ + --tx-body-file "''${tx_filename}".raw \ + --out-file "''${tx_filename}".signed + # Submit the transaction: + ${cardano-cli}/bin/cardano-cli transaction submit \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + --tx-file "''${tx_filename}".signed + + release_socket_lock \ + "''${nodeName}" \ + "''${tx_filename}.signed" \ + "''${node_drep_addr}" + + done +} + +################################################################################ +function governance_vote_all { + + # Function arguments. + local nodeName=$1 + local node_i=$2 + + ${coreutils}/bin/echo "governance_vote_all: ''${nodeName} - ''${node_i}" + + # Only defined in functions that use it. + local socket_path="../''${nodeName}/node.socket" + + ## Show proposals + # ${cardano-cli}/bin/cardano-cli conway query gov-state \ + # --testnet-magic ${toString testnetMagic} \ + # --socket-path "''${socket_path}" \ + #| ${jq}/bin/jq '.proposals' \ + + # Cycle proposals. + local txIdsJSON txIds + txIdsJSON="$( \ + ${cardano-cli}/bin/cardano-cli conway query gov-state \ + --testnet-magic ${toString testnetMagic} \ + --socket-path "''${socket_path}" \ + | ${jq}/bin/jq '.proposals | map(.actionId.txId)' \ + )" + txIds=$(echo "''${txIdsJSON}" | ${jq}/bin/jq --raw-output '. | join (" ")') + for txId in ''${txIds[*]} + do + governance_vote_proposal "''${nodeName}" "''${node_i}" "''${txId}" + done + + # TODO: TRY! + governance_create_withdrawal "''${nodeName}" "''${node_i}" 0 + +} +''