diff --git a/.github/workflows/trigger-devnet-upgrade.yml b/.github/workflows/trigger-devnet-upgrade.yml new file mode 100644 index 0000000000..99caad064c --- /dev/null +++ b/.github/workflows/trigger-devnet-upgrade.yml @@ -0,0 +1,51 @@ +--- +name: trigger-devnet-upgrade +permissions: + contents: read + id-token: write + +on: + schedule: + - cron: '0 */2 * * *' # Runs every 2 hours +jobs: + push_dev_net_protocol_upgrade_if_needed: + runs-on: ubuntu-latest + + steps: + - name: Install casper-client + run: | + sudo mkdir -m 0755 -p /etc/apt/keyrings/ + sudo curl https://repo.casper.network/casper-repo-pubkey.gpg --output /etc/apt/keyrings/casper-repo-pubkey.gpg + sudo echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/casper-repo-pubkey.gpg] https://repo.casper.network/releases jammy main" | sudo tee /etc/apt/sources.list.d/casper.list + sudo apt-get update + sudo apt-get install casper-client -y + + # Get script without checkout for use on any repo + - name: Get script + run: | + mkdir ci + cd ci + curl -JLO https://raw.githubusercontent.com/casper-network/casper-node/refs/heads/dev/ci/dev_net_protocol_generate.sh + chmod +x dev_net_protocol_generate.sh + curl -JLO https://raw.githubusercontent.com/casper-network/casper-node/refs/heads/dev/ci/next_upgrade_era_with_buffer.sh + chmod +x next_upgrade_era_with_buffer.sh + + # Assign AWS PROD role to get access to production cloudfronts and S3 buckets + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ACCESS_ROLE_GENESIS }} + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ secrets.AWS_ACCESS_REGION_GENESIS }} + + - name: Build protocol update + run: ./ci/dev_net_protocol_generate.sh + + - name: Upload artifacts to S3 + run: aws s3 sync ./target/genesis/ s3://${{ secrets.AWS_BUCKET_GENESIS }}/devnet/ + + # Required in case of overwrite + - name: Invalidate CloudFront cache + run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_GENESIS }} --paths "/devnet/*" + + diff --git a/ci/approx_next_era_starts.sh b/ci/approx_next_era_starts.sh new file mode 100644 index 0000000000..1d58a97fbd --- /dev/null +++ b/ci/approx_next_era_starts.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +if [ "$#" -lt 1 ]; then + echo "Usage: $0 [number of future eras]" + exit 1 +fi + +if [[ $1 == http* ]]; then + # Starts with http, so assume good full url + NODE_ADDRESS="--node-address $1" +else + NODE_ADDRESS="--node-address http://$NODE_IP:7777" +fi + +if ! command -v "casper-client" &> /dev/null ; then + echo "casper-client is not installed and required. Exiting..." + exit 1 +fi + +if [ "$#" -lt 2 ]; then + echo "No number of future eras given, using default." + FUTURE_ERAS=10 +else + FUTURE_ERAS=$2 +fi + +LAST_SWITCH_BLOCK=$(casper-client get-era-summary $NODE_ADDRESS | jq -r .result.era_summary.block_hash | tr -d "/n") + +# Getting Timestamp and Era with one call using `@` delimiter +SB_TIMESTAMP_AND_ERA=$(casper-client get-block -b $LAST_SWITCH_BLOCK $NODE_ADDRESS | jq -r '.result.block_with_signatures.block.Version2.header | [.timestamp,.era_id] | join("@")' | tr -d "/n") + +# Parsing this back into seperate variables +IFS=@ read -r SB_TIMESTAMP LAST_ERA_ID <<< "$SB_TIMESTAMP_AND_ERA" + +SB_EPOCH=$(date -d "$SB_TIMESTAMP" +%s) + +START_ERA_ID=$(( LAST_ERA_ID + 1 )) +NEXT_ERA_ID=$(( START_ERA_ID + 1 )) + +FINAL_ERA_ID=$(( START_ERA_ID + FUTURE_ERAS )) + +ERA_TIME_SECONDS=$(( 120*60+9 )) + +#echo "current_era:$START_ERA_ID started_utc:$SB_TIMESTAMP" + +while (( NEXT_ERA_ID <= FINAL_ERA_ID )); do + TIMESTAMP_FROM_Z=$(date -u -d "@$SB_EPOCH" +"%Y-%m-%dT%H:%M:%SZ") + TIMESTAMP_FROM_L=$(date -d "@$SB_EPOCH" +"%Y-%m-%dT%H:%M:%S%z") + echo "era:$NEXT_ERA_ID utc:$TIMESTAMP_FROM_Z local:$TIMESTAMP_FROM_L" + let NEXT_ERA_ID++ + SB_EPOCH=$(( SB_EPOCH + ERA_TIME_SECONDS )) +done + diff --git a/ci/dev_net_protocol_generate.sh b/ci/dev_net_protocol_generate.sh new file mode 100644 index 0000000000..18fc608c83 --- /dev/null +++ b/ci/dev_net_protocol_generate.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash + +if ! command -v "casper-client" &> /dev/null ; then + echo "casper-client is not installed and required. Exiting..." + exit 1 +fi + +# RPC for getting current era for activation point planning +NODE_RPC_URL="https://node-1.dev.casper.network/rpc" +# Delay in minutes till next era start for upgrade +NEXT_ERA_MIN_DELAY=20 + + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 2>&1 && pwd)" +CI_SCRIPT_DIR="$ROOT_DIR/ci" +TARGET_DIR="$ROOT_DIR/target" +GENESIS_DIR="$TARGET_DIR/genesis" +CONFIG_DIR="$TARGET_DIR/config" + +# pull latest dev hash from artifacts +CURRENT_HASH=$(curl -s https://genesis.casper.network/artifacts/casper-node/dev.latest) +echo "Checked out Github hash $CURRENT_HASH" + +LATEST_HASH=$(curl -s https://genesis.casper.network/devnet/latest_git_hash | tr -d '\n') +echo "Latest Hash from devnet protocol is $LATEST_HASH" +if [ ${#LATEST_HASH} -ne 40 ]; then + echo "Latest Hash Length is bad. Probably had retrieval error: $LATEST_HASH" + exit 1 +fi + +echo + +if [ "$CURRENT_HASH" == "$LATEST_HASH" ]; then + echo "Last published devnet protocol has same hash, erroring out." + exit 1 # This fails job and stops workflow +fi + +LATEST_PROTOCOL_VERSION="$(curl -s https://genesis.casper.network/devnet/protocol_versions | tail -n 1 | tr -d '\n')" +echo "Latest devnet protocol version: $LATEST_PROTOCOL_VERSION" + +IFS="_" +# Read latest protocol parts into array +read -ra LPVA <<< "$LATEST_PROTOCOL_VERSION" + +# Incrementing one to patch +NEW_PROTOCOL_VERSION=${LPVA[0]}_${LPVA[1]}_$((LPVA[2] + 1)) +echo "New devnet protocol version: $NEW_PROTOCOL_VERSION" +echo + +PROTOCOL_DIR="$GENESIS_DIR/$NEW_PROTOCOL_VERSION" +echo "## Creating $PROTOCOL_DIR" +mkdir -p "$PROTOCOL_DIR" +echo + +echo "$NEW_PROTOCOL_VERSION" > "$GENESIS_DIR/protocol_versions" +echo "## protocol_versions file contents:" +echo "---" +cat "$GENESIS_DIR/protocol_versions" +echo "---" +echo + +echo "$CURRENT_HASH" > "$GENESIS_DIR/latest_git_hash" +echo "## latest_git_hash file contents:" +echo "---" +cat "$GENESIS_DIR/latest_git_hash" +echo "---" +echo + +mkdir -p "$CONFIG_DIR" +cd "$TARGET_DIR" || exit 1 +DOWNLOAD_PATH="https://genesis.casper.network/artifacts/casper-node/$CURRENT_HASH" +echo "## Downloading: $DOWNLOAD_PATH/bin.tar.gz" +curl -JLO "$DOWNLOAD_PATH/bin.tar.gz" || exit 1 + +echo "## Downloading: $DOWNLOAD_PATH/config-dev.tar.gz" +curl -JLO "$DOWNLOAD_PATH/config-dev.tar.gz" || exit 1 + +cd "$CONFIG_DIR" || exit 1 +# This will validate that files retrieved were good. Should error if just curl output + +echo "## Decompressing config" +tar -xzvf ../config-dev.tar.gz . || exit 1 + +ACTIVATION_POINT=$("$CI_SCRIPT_DIR/next_upgrade_era_with_buffer.sh" "$NODE_RPC_URL" "$NEXT_ERA_MIN_DELAY") + +echo "## Replacing activation_point in chainspec.toml with $ACTIVATION_POINT" +# chainspec.toml replacement +sed -i '/^activation_point = /c\activation_point = '"$ACTIVATION_POINT" chainspec.toml + +# config_example.toml replacement +echo "## Retrieving statuses to make known_addresses" +KNOWN_ADDRESSES="[$( (curl -s https://node-1.dev.casper.network/status | jq -r '.peers[] | .address'; + curl -s https://node-2.dev.casper.network/status | jq -r '.peers[] | .address'; + curl -s https://node-3.dev.casper.network/status | jq -r '.peers[] | .address'; + curl -s https://node-4.dev.casper.network/status | jq -r '.peers[] | .address';) | + sort | uniq | xargs -d '\n' printf "'%s'," | sed 's/, $//' )]" +echo "## Generated: $KNOWN_ADDRESSES" +# If KNOWN_ADDRESSES is very short, calls above failed. IPs should be 20 per min. -> 80 +MIN_KNOWN_ADDRESSES_LEN=80 +if [[ ${#KNOWN_ADDRESSES} -le $MIN_KNOWN_ADDRESSES_LEN ]]; then + echo "Generated known addresses is to short. Expected min of $MIN_KNOWN_ADDRESSES_LEN. Calls probably failed." + exit 1 +fi +echo "## Replacing known_addresses in config-example.toml" +sed -i '/^known_addresses = /c\known_addresses = '"$KNOWN_ADDRESSES" config-example.toml + +CS_PROTOCOL=$(echo -n "$NEW_PROTOCOL_VERSION" | tr '_' '.') +echo "## Replacing protocol.version with $CS_PROTOCOL" +sed -i '/^version = /c\version = '\'"$CS_PROTOCOL"\' chainspec.toml + +echo "## Compressing new config.tar.gz" +tar -czvf ../config.tar.gz . + +cd .. +pwd + +echo "## Moving files bin and config into $PROTOCOL_DIR" +mv config.tar.gz "$PROTOCOL_DIR/" +mv bin.tar.gz "$PROTOCOL_DIR/" + +echo "## Listing contents of $GENESIS_DIR" +ls -alrR "$GENESIS_DIR" diff --git a/ci/mins_to_switch_block.sh b/ci/mins_to_switch_block.sh new file mode 100644 index 0000000000..e05226776d --- /dev/null +++ b/ci/mins_to_switch_block.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +if ! command -v "casper-client" &> /dev/null ; then + echo "casper-client is not installed and required. Exiting..." + exit 1 +fi + +if [[ $1 == http* ]]; then + # Starts with http, so assume good full url + NODE_ADDRESS="--node-address $1" +else + NODE_ADDRESS="--node-address http://$NODE_IP:7777" +fi + +LAST_SWITCH_BLOCK=$(casper-client get-era-summary $NODE_ADDRESS | jq -r .result.era_summary.block_hash | tr -d "/n") + +# Getting Timestamp and Era with one call using `@` delimiter +SB_TIMESTAMP_AND_ERA=$(casper-client get-block -b $LAST_SWITCH_BLOCK $NODE_ADDRESS | jq -r '.result.block_with_signatures.block.Version2.header | [.timestamp,.era_id] | join("@")' | tr -d "/n") + +# Parsing this back into seperate variables +IFS=@ read -r SB_TIMESTAMP LAST_ERA_ID <<< "$SB_TIMESTAMP_AND_ERA" + +# Converting timestamp into Unix second based Epoch +SB_EPOCH=$(date -d "$SB_TIMESTAMP" +%s) +NOW_EPOCH=$(date +%s) + +# Assuming Era length 120 minutes, doing math till next Era +MINS_TILL_SB=$(( 120 - ((NOW_EPOCH - $SB_EPOCH) / 60) )) +NEXT_ERA=$(( LAST_ERA_ID + 1 )) +echo "$MINS_TILL_SB mins till era $NEXT_ERA" diff --git a/ci/next_upgrade_era_with_buffer.sh b/ci/next_upgrade_era_with_buffer.sh new file mode 100644 index 0000000000..99b27ddbbe --- /dev/null +++ b/ci/next_upgrade_era_with_buffer.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +if ! command -v "casper-client" &> /dev/null ; then + echo "casper-client is not installed and required. Exiting..." + exit 1 +fi + +if [[ $1 == http* ]]; then + # Starts with http, so assume good full url + NODE_ADDRESS="--node-address $1" +else + NODE_ADDRESS="--node-address http://$NODE_IP:7777" +fi + +BUFFER_MINS=$2 + +LAST_SWITCH_BLOCK=$(casper-client get-era-summary $NODE_ADDRESS | jq -r .result.era_summary.block_hash | tr -d "/n") + +# Getting Timestamp and Era with one call using `@` delimiter +SB_TIMESTAMP_AND_ERA=$(casper-client get-block -b $LAST_SWITCH_BLOCK $NODE_ADDRESS | jq -r '.result.block_with_signatures.block.Version2.header | [.timestamp,.era_id] | join("@")' | tr -d "/n") + +# Parsing this back into seperate variables +IFS=@ read -r SB_TIMESTAMP LAST_ERA_ID <<< "$SB_TIMESTAMP_AND_ERA" + +SB_EPOCH=$(date -d "$SB_TIMESTAMP" +%s) +NOW_EPOCH=$(date +%s) + +MINS_TILL_SB=$(( 120 - ((NOW_EPOCH - $SB_EPOCH) / 60) )) + +if [ "$MINS_TILL_SB" -gt "$BUFFER_MINS" ]; then + NEXT_BUF_ERA=$(( LAST_ERA_ID + 2 )); +else + NEXT_BUF_ERA=$(( LAST_ERA_ID + 3 )); +fi + +echo "$NEXT_BUF_ERA"