diff --git a/.github/workflows/v2-apply-scoring.yml b/.github/workflows/v2-apply-scoring.yml new file mode 100644 index 00000000..136836d0 --- /dev/null +++ b/.github/workflows/v2-apply-scoring.yml @@ -0,0 +1,221 @@ +name: Apply Scoring V2 + +on: + workflow_dispatch: + pull_request: + types: [closed] + branches: + - scoring-v2 + +jobs: + apply_scoring_v2: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'ds-scoring/')) + + steps: + - name: Checkout the repository + uses: actions/checkout@master + with: + ref: scoring-v2 + fetch-depth: 20 + + - name: Load scoring details + run: | + set -ex + git fetch --all + scores_csv=$(git log -1 origin/scoring-v2 --name-only --pretty=format: --grep 'scoring run' | grep scores.csv) + params_json=$(git log -1 origin/scoring-v2 --name-only --pretty=format: --grep 'scoring run' | grep params.json) + + scoring_run_ui_id=$(git log -1 origin/scoring-v2 --name-only --pretty=format: --grep 'scoring run' | head -6 | tail -1 | awk -F / '{print $2}') + epoch=$(<<<"$scoring_run_ui_id" awk -F . '{print $1}') + + weightVoteCredits=$(jq '.weightVoteCredits' $params_json) + weightInflationCommission=$(jq '.weightInflationCommission' $params_json) + weightMEVCommission=$(jq '.weightMEVCommission' $params_json) + weightStakeConcentrationCity=$(jq '.weightStakeConcentrationCity' $params_json) + weightStakeConcentrationASO=$(jq '.weightStakeConcentrationASO' $params_json) + weightStakeConcentrationNode=$(jq '.weightStakeConcentrationNode' $params_json) + weightStakeConcentrationCountry=$(jq '.weightStakeConcentrationCountry' $params_json) + weightBlockProduction=$(jq '.weightBlockProduction' $params_json) + + if [[ -z $scores_csv ]] + then + echo "Failed to find CSV with scores in the PR!" + exit 1 + fi + + if [[ -z $scoring_run_ui_id ]] + then + echo "Failed to detect scoring run UI ID from the PR!" + exit 1 + fi + + if [[ -z $epoch ]] + then + echo "Failed to detect epoch from the UI ID!" + exit 1 + fi + + echo "scores_csv=$scores_csv" >> $GITHUB_ENV + echo "scoring_run_ui_id=$scoring_run_ui_id" >> $GITHUB_ENV + echo "epoch=$epoch" >> $GITHUB_ENV + echo "components=COMMISSION_ADJUSTED_CREDITS,BLOCK_PRODUCTION,INFLATION_COMMISSION,MEV_COMMISSION,DC_COUNTRY_CONCENTRATION,DC_CITY_CONCENTRATION,DC_ASO_CONCENTRATION,DC_NODE_CONCENTRATION" >> $GITHUB_ENV + echo "components_weights=$weightVoteCredits,$weightBlockProduction,$weightInflationCommission,$weightMEVCommission,$weightStakeConcentrationCountry,$weightStakeConcentrationCity,$weightStakeConcentrationASO,$weightStakeConcentrationNode" >> $GITHUB_ENV + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Prepare solana config + run: | + cat < /tmp/solana-config.yml + json_rpc_url: "$RPC_URL" + websocket_url: "" + keypair_path: /.config/solana/id.json + address_labels: + "11111111111111111111111111111111": System Program + commitment: confirmed + EOF + echo "$KEYPAIR" > /tmp/id.json + + env: + RPC_URL: ${{ secrets.RPC_URL }} + KEYPAIR: ${{ secrets.VALIDATOR_MANAGEMENT_KEYPAIR }} + + - name: Configure image + run: | + images=$(aws ecr describe-images --repository-name marinade.finance/validator-manager) + latest=$(<<<"$images" jq '.imageDetails[] | .imagePushedAt + " " + .imageTags[0]' -r | sort | tail -1 | cut -d' ' -f2) + echo "latest=$latest" >> $GITHUB_ENV + + - name: Run scoring - simulation + run: | + docker run --rm --user "$(id -u):$(id -g)" \ + -v /tmp/solana-config.yml:/.config/solana/cli/config.yml \ + -v /tmp/id.json:/.config/solana/id.json \ + -v "$(realpath "$SCORES_CSV"):/scores.csv" \ + "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ + ./validator-manager -s --print-only update-scores2 --scores-file /scores.csv + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: marinade.finance/validator-manager + SCORES_CSV: ${{ env.scores_csv }} + IMAGE_TAG: ${{ env.latest }} + + - name: Publish scoring results + run: | + curl -sLfS "https://scoring-dev.marinade.finance/api/v1/scores/upload?epoch=${{ env.epoch }}&components=${{ env.components }}&component_weights=${{ env.components_weights }}&ui_id=${{ env.scoring_run_ui_id }}" -X POST \ + -H "Authorization: Bearer ${{ secrets.VALIDATORS_API_ADMIN_TOKEN }}" \ + -F "scores=@${{ env.scores_csv }}" + + early_unstake: + needs: apply_scoring_v2 + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@master + with: + ref: scoring-v2 + fetch-depth: 20 + + - name: Load scoring details + run: | + set -ex + unstake_hints_json=$(git log -1 --name-only --pretty=format: --grep 'scoring run' | grep unstake-hints.json) + scoring_run_ui_id=$(git log -1 origin/scoring-v2 --name-only --pretty=format: --grep 'scoring run' | head -6 | tail -1 | awk -F / '{print $2}') + epoch=$(<<<"$scoring_run_ui_id" awk -F . '{print $1}') + + if [[ -z $unstake_hints_json ]] + then + echo "Failed to find JSON with unstake hints in the PR!" + exit 1 + fi + + if [[ -z $epoch ]] + then + echo "Failed to detect epoch from the UI ID!" + exit 1 + fi + + echo "unstake_hints_json=$unstake_hints_json" >> $GITHUB_ENV + echo "epoch=$epoch" >> $GITHUB_ENV + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Prepare solana config + run: | + cat < /tmp/solana-config.yml + json_rpc_url: "$RPC_URL" + websocket_url: "" + keypair_path: /.config/solana/id.json + address_labels: + "11111111111111111111111111111111": System Program + commitment: confirmed + EOF + echo "$KEYPAIR" > /tmp/id.json + + env: + RPC_URL: ${{ secrets.RPC_URL }} + KEYPAIR: ${{ secrets.VALIDATOR_MANAGEMENT_KEYPAIR }} + + - name: Configure image + run: | + images=$(aws ecr describe-images --repository-name marinade.finance/validator-manager) + latest=$(<<<"$images" jq '.imageDetails[] | .imagePushedAt + " " + .imageTags[0]' -r | sort | tail -1 | cut -d' ' -f2) + echo "latest=$latest" >> $GITHUB_ENV + + - name: Early unstake V2 - simulation + run: | + <"$UNSTAKE_HINTS_JSON" jq '.unstake_hints[].vote_account' -r | xargs -I{} \ + docker run --rm --user "$(id -u):$(id -g)" \ + -v /tmp/solana-config.yml:/.config/solana/cli/config.yml \ + -v /tmp/id.json:/.config/solana/id.json \ + "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ + ./validator-manager -s --print-only emergency-unstake {} + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: marinade.finance/validator-manager + UNSTAKE_HINTS_JSON: ${{ env.unstake_hints_json }} + IMAGE_TAG: ${{ env.latest }} + + - name: Send Discord Notification + run: | + validators_count=$(<"$UNSTAKE_HINTS_JSON" jq '[.unstake_hints[].marinade_stake] | length' -r) + unstaked_sol=$(<"$UNSTAKE_HINTS_JSON" jq '[.unstake_hints[].marinade_stake] | add' -r) + + message="Early unstake successfully applied. ($validators_count validators, $unstaked_sol SOL)" + + if (( $validators_count == 0 )) + then + message="No emergency unstakes needed this time." + fi + + curl "$DISCORD_WEBHOOK" -H "Content-Type: application/json" -d '{ + "username": "Delegation Strategy - Early Unstake V2", + "avatar_url": "https://public.marinade.finance/ds-emergency-bot.png", + "embeds": [ + { + "title": "'"$message"'", + "color": "52224" + } + ] + }' + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + UNSTAKE_HINTS_JSON: ${{ env.unstake_hints_json }} diff --git a/.github/workflows/v2-apply-unstakes.yml b/.github/workflows/v2-apply-unstakes.yml new file mode 100644 index 00000000..7c575013 --- /dev/null +++ b/.github/workflows/v2-apply-unstakes.yml @@ -0,0 +1,115 @@ + +name: Apply Unstakes V2 + +on: + workflow_dispatch: + pull_request: + types: [closed] + branches: + - scoring-v2 + +jobs: + late_unstake: + if: startsWith(github.event.pull_request.head.ref, 'ds-unstakes') && github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@master + with: + ref: scoring-v2 + fetch-depth: 20 + + - name: Load scoring details + run: | + set -ex + unstake_hints_json=$(git log -1 --name-only --pretty=format: --grep 'emergency unstakes' | grep unstake-hints.json) + scoring_run_ui_id=$(git log -1 origin/scoring-v2 --name-only --pretty=format: --grep 'emergency unstakes' | head -3 | tail -1 | awk -F / '{print $2}') + epoch=$(<<<"$scoring_run_ui_id" awk -F . '{print $1}') + + if [[ -z $unstake_hints_json ]] + then + echo "Failed to find JSON with unstake hints in the PR!" + exit 1 + fi + + if [[ -z $epoch ]] + then + echo "Failed to detect epoch from the UI ID!" + exit 1 + fi + + echo "unstake_hints_json=$unstake_hints_json" >> $GITHUB_ENV + echo "epoch=$epoch" >> $GITHUB_ENV + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Prepare solana config + run: | + cat < /tmp/solana-config.yml + json_rpc_url: "$RPC_URL" + websocket_url: "" + keypair_path: /.config/solana/id.json + address_labels: + "11111111111111111111111111111111": System Program + commitment: confirmed + EOF + echo "$KEYPAIR" > /tmp/id.json + + env: + RPC_URL: ${{ secrets.RPC_URL }} + KEYPAIR: ${{ secrets.VALIDATOR_MANAGEMENT_KEYPAIR }} + + - name: Configure image + run: | + images=$(aws ecr describe-images --repository-name marinade.finance/validator-manager) + latest=$(<<<"$images" jq '.imageDetails[] | .imagePushedAt + " " + .imageTags[0]' -r | sort | tail -1 | cut -d' ' -f2) + echo "latest=$latest" >> $GITHUB_ENV + + - name: Early unstake V2 - simulation + run: | + <"$UNSTAKE_HINTS_JSON" jq '.unstake_hints[].vote_account' -r | xargs -I{} \ + docker run --rm --user "$(id -u):$(id -g)" \ + -v /tmp/solana-config.yml:/.config/solana/cli/config.yml \ + -v /tmp/id.json:/.config/solana/id.json \ + "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ + ./validator-manager -s --print-only emergency-unstake {} + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: marinade.finance/validator-manager + UNSTAKE_HINTS_JSON: ${{ env.unstake_hints_json }} + IMAGE_TAG: ${{ env.latest }} + + - name: Send Discord Notification + run: | + validators_count=$(<"$UNSTAKE_HINTS_JSON" jq '[.unstake_hints[].marinade_stake] | length' -r) + unstaked_sol=$(<"$UNSTAKE_HINTS_JSON" jq '[.unstake_hints[].marinade_stake] | add' -r) + + message="Late unstake successfully applied. ($validators_count validators, $unstaked_sol SOL)" + + if (( $validators_count == 0 )) + then + message="No emergency unstakes needed this time." + fi + + curl "$DISCORD_WEBHOOK" -H "Content-Type: application/json" -d '{ + "username": "Delegation Strategy - Early Unstake V2", + "avatar_url": "https://public.marinade.finance/ds-emergency-bot.png", + "embeds": [ + { + "title": "'"$message"'", + "color": "52224" + } + ] + }' + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + UNSTAKE_HINTS_JSON: ${{ env.unstake_hints_json }} diff --git a/.github/workflows/v2-prepare-scoring.yml b/.github/workflows/v2-prepare-scoring.yml new file mode 100644 index 00000000..6a926d12 --- /dev/null +++ b/.github/workflows/v2-prepare-scoring.yml @@ -0,0 +1,105 @@ +name: Prepare Scoring V2 + +on: + workflow_dispatch: + repository_dispatch: + types: [trigger_scoring_v2] + +jobs: + scoring: + runs-on: ubuntu-latest + + steps: + - name: Set start timestamp + run: | + start_timestamp=$(date +%s) + echo "start_timestamp=$start_timestamp" >> $GITHUB_ENV + + - uses: actions/checkout@master + - name: Prepare target directory + run: | + epoch_info=$(curl -sfLS "http://api.mainnet-beta.solana.com" -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}') + current_epoch=$(<<<"$epoch_info" jq '.result.epoch' -r) + slot_index=$(<<<"$epoch_info" jq '.result.slotIndex' -r) + + scoring_id="$current_epoch.$slot_index" + echo "scoring_id=$scoring_id" >> $GITHUB_ENV + + scoring_path="scoring-v2/$scoring_id" + echo "scoring_path=$scoring_path" >> $GITHUB_ENV + + mkdir -p "$scoring_path" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Configure image + run: | + images=$(aws ecr describe-images --repository-name marinade.finance/ds-scoring) + latest=$(<<<"$images" jq '.imageDetails[] | .imagePushedAt + " " + .imageTags[0]' -r | sort | tail -1 | cut -d' ' -f2) + echo "latest=$latest" >> $GITHUB_ENV + + - name: Run compute scoring + run: | + docker run --rm \ + -e "DATA_PROVIDER_MODE=api" \ + -e "VALIDATORS_URL=https://validators-api.marinade.finance/validators" \ + -e "BLACKLIST_URL=https://raw.githubusercontent.com/marinade-finance/delegation-strategy-2/master/blacklist.csv" \ + -e "REWARDS_API=https://validators-api.marinade.finance/rewards" \ + -e "VEMNDE_VOTES_URL=https://snapshots-api.marinade.finance/v1/votes/vemnde/latest" \ + -e "MSOL_VOTES_URL=https://snapshots-api.marinade.finance/v1/votes/msol/latest" \ + -e "REWARDS_URL=https://validators-api.marinade.finance/rewards" \ + -e "JITO_MEV_URL=https://kobe.mainnet.jito.network/api/v1/validators" \ + -e "BONDS_URL=https://validator-bonds-api.marinade.finance/bonds" \ + -e "MARINADE_TVL_URL=http://api.marinade.finance/tlv" \ + -v "/tmp/snapshot:/usr/src/app/snapshot" \ + "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ + pnpm cli -- computeScoring + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: marinade.finance/ds-scoring + IMAGE_TAG: ${{ env.latest }} + + - name: Make a Pull Request + run: | + scoring_id="${{ env.scoring_id }}" + scoring_branch="ds-scoring/$scoring_id" + cp /tmp/snapshot/* "${{ env.scoring_path }}/" + gzip -c "${{ env.scoring_path }}/validators.json" > "${{ env.scoring_path }}/validators.json.gz" + rm "${{ env.scoring_path }}/validators.json" + git config --global user.name 'Autonomous Scoring Pipeline' + git config --global user.email 'bot@noreply.marinade.finance' + git checkout -b "$scoring_branch" + git add scoring-v2 + [[ -n "$(git status --porcelain)" ]] && git commit -m "scoring run $scoring_id" && git push -u origin "$scoring_branch" || echo "No changes to be committed" + + pr_url=$(gh pr create -B scoring-v2 -H "$scoring_branch" --title "Publish V2 Scoring Results ($scoring_id)" --body-file "${{ env.scoring_path }}/summary.md") + echo "pr_url=$pr_url" >> $GITHUB_ENV + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Send Discord Notification + run: | + scoring_id="${{ env.scoring_id }}" + pr_url="${{ env.pr_url }}" + curl "$DISCORD_WEBHOOK" -H "Content-Type: application/json" -d '{ + "username": "DS Scoring", + "avatar_url": "https://public.marinade.finance/ds-scoring-bot.png", + "embeds": [ + { + "title": "Scoring PR ('"$scoring_id"') is prepared.", + "url": "'"$pr_url"'", + "color": "5661687" + } + ] + }' + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} \ No newline at end of file diff --git a/.github/workflows/v2-prepare-unstakes.yml b/.github/workflows/v2-prepare-unstakes.yml new file mode 100644 index 00000000..e287108c --- /dev/null +++ b/.github/workflows/v2-prepare-unstakes.yml @@ -0,0 +1,152 @@ +name: Prepare Late Unstakes V2 + +on: + workflow_dispatch: + repository_dispatch: + types: [trigger_unstakes_v2] +jobs: + unstakes: + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@master + with: + ref: scoring-v2 + fetch-depth: 20 + + - name: Load scoring unstakes details + run: | + set -ex + unstake_hints_json=$(git log -1 origin/scoring-v2 --name-only --pretty=format: --grep 'scoring run' | grep unstake-hints.json) + scoring_run_ui_id=$(git log -1 origin/scoring-v2 --name-only --pretty=format: --grep 'scoring run' | head -6 | tail -1 | awk -F / '{print $2}') + epoch=$(<<<"$scoring_run_ui_id" awk -F . '{print $1}') + + if [[ -z $unstake_hints_json ]] + then + echo "Failed to find JSON with unstake hints in the PR!" + exit 1 + fi + + if [[ -z $epoch ]] + then + echo "Failed to detect epoch from the UI ID!" + exit 1 + fi + + echo "unstake_hints_json=$unstake_hints_json" >> $GITHUB_ENV + echo "epoch=$epoch" >> $GITHUB_ENV + echo "scoring_id=$scoring_run_ui_id" >> $GITHUB_ENV + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Configure image + run: | + images=$(aws ecr describe-images --repository-name marinade.finance/ds-scoring) + latest=$(<<<"$images" jq '.imageDetails[] | .imagePushedAt + " " + .imageTags[0]' -r | sort | tail -1 | cut -d' ' -f2) + echo "latest=$latest" >> $GITHUB_ENV + + - name: Run compute unstakes + run: | + docker run --rm \ + -e "DATA_PROVIDER_MODE=api" \ + -e "VALIDATORS_URL=https://validators-api.marinade.finance/validators" \ + -e "BLACKLIST_URL=https://raw.githubusercontent.com/marinade-finance/delegation-strategy-2/master/blacklist.csv" \ + -e "REWARDS_API=https://validators-api.marinade.finance/rewards" \ + -e "VEMNDE_VOTES_URL=https://snapshots-api.marinade.finance/v1/votes/vemnde/latest" \ + -e "MSOL_VOTES_URL=https://snapshots-api.marinade.finance/v1/votes/msol/latest" \ + -e "REWARDS_URL=https://validators-api.marinade.finance/rewards" \ + -e "JITO_MEV_URL=https://kobe.mainnet.jito.network/api/v1/validators" \ + -e "BONDS_URL=https://validator-bonds-api.marinade.finance/bonds" \ + -e "MARINADE_TVL_URL=http://api.marinade.finance/tlv" \ + -v "/tmp/snapshot:/usr/src/app/snapshot" \ + "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \ + pnpm cli -- computeUnstakes + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: marinade.finance/ds-scoring + IMAGE_TAG: ${{ env.latest }} + + - name: Merge unstakes + run: | + file1="/tmp/snapshot/unstake-hints.json" + file2="${{ env.unstake_hints_json }}" + output="merged.json" + temp=$(mktemp) + jq '.unstake_hints = []' "$file1" > "$output" + accounts=$(jq -r '.unstake_hints[].vote_account' "$file1" "$file2" | sort -u) + echo "$accounts" | while read -r account; do + [[ -z "$account" ]] && continue + hints1=$(jq -r --arg account "$account" '.unstake_hints[] | select(.vote_account == $account) | .hints[]' "$file1") + hints2=$(jq -r --arg account "$account" '.unstake_hints[] | select(.vote_account == $account) | .hints[]' "$file2") + hints_merged=$(echo -e "$hints1\n$hints2" | grep -v '^$' | sort | uniq | jq -R . | jq -s .) + base_entry=$(jq --arg account "$account" '.unstake_hints[] | select(.vote_account == $account)' "$file1") + if [[ -z "$base_entry" ]]; then + base_entry=$(jq --arg account "$account" '.unstake_hints[] | select(.vote_account == $account)' "$file2") + fi + updated_entry=$(echo "$base_entry" | jq --argjson hints "$hints_merged" '.hints = $hints') + jq --argjson entry "$updated_entry" '.unstake_hints += [$entry]' "$output" > "$temp" && mv "$temp" "$output" + done + total_marinade_stake=$(jq '[.unstake_hints[].marinade_stake | tonumber] | add' merged.json) + validators_count=$(jq '.unstake_hints | length' merged.json) + echo "total_marinade_stake=$total_marinade_stake" >> $GITHUB_ENV + echo "validators_count=$validators_count" >> $GITHUB_ENV + mv merged.json "${{ env.unstake_hints_json }}" + + - name: Generate Unstakes Summary + run: | + total_marinade_stake="${{ env.total_marinade_stake }}" + validators_count="${{ env.validators_count }}" + + cat > unstakes.md << EOF + # Unstakes results $scoring_id + + | | Stake (SOL) | Validators | + |:------------------------|---------------:|---------------:| + | **Total unstakes** | $total_marinade_stake | $validators_count | + + EOF + + - name: Make a Pull Request + run: | + scoring_id="${{ env.scoring_id }}" + scoring_branch="ds-unstakes/$scoring_id" + total_marinade_stake="${{ env.total_marinade_stake }}" + validators_count="${{ env.validators_count }}" + git config --global user.name 'Autonomous Scoring Pipeline' + git config --global user.email 'bot@noreply.marinade.finance' + git checkout -b "$scoring_branch" + git add scoring-v2 + [[ -n "$(git status --porcelain)" ]] && git commit -m "emergency unstakes for scoring run $scoring_id" && git push -u origin "$scoring_branch" || echo "No changes to be committed" + pr_body=$(cat unstakes.md) + pr_url=$(gh pr create -B scoring-v2 -H "$scoring_branch" --title "Publish V2 Emergency Unstakes ($scoring_id)" --body "$pr_body") + echo "pr_url=$pr_url" >> $GITHUB_ENV + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Send Discord Notification + run: | + scoring_id="${{ env.scoring_id }}" + pr_url="${{ env.pr_url }}" + curl "$DISCORD_WEBHOOK" -H "Content-Type: application/json" -d '{ + "username": "DS Scoring", + "avatar_url": "https://public.marinade.finance/ds-scoring-bot.png", + "embeds": [ + { + "title": "Late Unstakes PR ('"$scoring_id"') is prepared.", + "url": "'"$pr_url"'", + "color": "5661687" + } + ] + }' + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} \ No newline at end of file diff --git a/.github/workflows/v2-schedule-scoring.yml b/.github/workflows/v2-schedule-scoring.yml new file mode 100644 index 00000000..2e4a43f9 --- /dev/null +++ b/.github/workflows/v2-schedule-scoring.yml @@ -0,0 +1,49 @@ +name: Schedule Scoring V2 + +on: + workflow_dispatch: + schedule: + - cron: '50 0/1 * * *' +jobs: + trigger_scoring_v2_if_needed: + name: Schedule Scoring V2 + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@master + - name: Check epoch timing + run: | + epoch_info=$(curl -sfLS "http://api.mainnet-beta.solana.com" -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}') + current_epoch=$(<<<"$epoch_info" jq '.result.epoch' -r) + slot_index=$(<<<"$epoch_info" jq '.result.slotIndex' -r) + + last_v2_scoring_epoch_from_dir=$(find scoring-v2 -type d | sort -n | tail -1 | cut -d/ -f2 | cut -d. -f1) + last_v2_scoring_epoch_from_git=$(gh pr list --state open --json headRefName,isCrossRepository --jq '.[] | select(.isCrossRepository == false) | .headRefName' | grep scoring-v2/ | sort -n | tail -1 | cut -d/ -f2| cut -d. -f1) + + echo "Last V2 scoring epoch (git): $last_v2_scoring_epoch_from_git" + echo "Last V2 scoring epoch (dir): $last_v2_scoring_epoch_from_dir" + echo "Current epoch: $current_epoch" + echo "Current slot: $slot_index" + + if (( slot_index < 10000 )) + then + echo "Too early in the epoch!" + exit 0 + fi + + if ! [[ -z $last_v2_scoring_epoch_from_dir ]] && (( last_v2_scoring_epoch_from_dir == current_epoch )) + then + echo "Scoring already run and merged!" + exit 0 + fi + + if ! [[ -z $last_v2_scoring_epoch_from_git ]] && (( last_v2_scoring_epoch_from_git == current_epoch )) + then + echo "Scoring already in PR!" + exit 0 + fi + + echo "Triggering the V2 scoring pipeline..." + gh workflow run v2-prepare-scoring.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/v2-schedule-unstakes.yml b/.github/workflows/v2-schedule-unstakes.yml new file mode 100644 index 00000000..4740c265 --- /dev/null +++ b/.github/workflows/v2-schedule-unstakes.yml @@ -0,0 +1,49 @@ +name: Schedule Unstakes V2 + +on: + workflow_dispatch: + schedule: + - cron: '50 0/1 * * *' +jobs: + trigger_unstakes_v2_if_needed: + name: Schedule Unstakes V2 + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@master + - name: Check epoch timing + run: | + epoch_info=$(curl -sfLS "http://api.mainnet-beta.solana.com" -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}') + current_epoch=$(<<<"$epoch_info" jq '.result.epoch' -r) + slot_index=$(<<<"$epoch_info" jq '.result.slotIndex' -r) + + last_v2_scoring_epoch_from_dir=$(find scoring-v2 -type d | sort -n | tail -1 | cut -d/ -f2 | cut -d. -f1) + last_v2_scoring_epoch_from_git=$(gh pr list --state open --json headRefName,isCrossRepository --jq '.[] | select(.isCrossRepository == false) | .headRefName' | grep scoring-v2/ | sort -n | tail -1 | cut -d/ -f2| cut -d. -f1) + + echo "Last V2 scoring epoch (git): $last_v2_scoring_epoch_from_git" + echo "Last V2 scoring epoch (dir): $last_v2_scoring_epoch_from_dir" + echo "Current epoch: $current_epoch" + echo "Current slot: $slot_index" + + if (( slot_index < 259200 )) + then + echo "Too early in the epoch!" + exit 0 + fi + + if [[ -z $last_v2_scoring_epoch_from_dir ]] && (( last_v2_scoring_epoch_from_dir == current_epoch )) + then + echo "Scoring didn't run or wasn't merged!" + exit 0 + fi + + if ! [[ -z $last_v2_scoring_epoch_from_git ]] && (( last_v2_scoring_epoch_from_git == current_epoch )) + then + echo "Scoring PR is not merged!" + exit 0 + fi + + echo "Triggering the V2 late unstake pipeline..." + gh workflow run v2-prepare-unstakes.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}