diff --git a/README.md b/README.md index c2aaa0d..ba23d99 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ This iteration of rl-swarm is powered by the [GenRL](https://github.com/gensyn-a ## Requirements -Your hardware requirements will vary depending on a number of factors including model size and the accelerator platform you use. Users running large NVIDIA GPU will be assigned a model from the large model pool, while users running less powerful hardware will be assigned a model from the small model pool. This design decision is intended to allow users to advance at a similar rate regardless of the hardware they use, maximizing their utility to the swarm. +Your hardware requirements will vary depending on a number of factors including model size and the accelerator platform you use. Users running a large NVIDIA GPU will be assigned a model from the large model pool, while users running less powerful hardware will be assigned a model from the small model pool. This design decision is intended to allow users to advance at a similar rate regardless of the hardware they use, maximizing their utility to the swarm. **Supported Hardware** -- arm64 or x86 CPU with minimum 32gb ram (note that if you run other applications during training it might crash training). +- arm64 or x86 CPU with a minimum of 32GB RAM (note that if you run other applications during training it might crash the training). OR @@ -56,7 +56,7 @@ git clone https://github.com/gensyn-ai/rl-swarm #### 2. Install Docker -Make sure you have Docker installed and the Docker daemon is running on your machine. To do that, follow [these instructions](https://docs.docker.com/get-started/get-docker/) according to your OS. Ensure you allot sufficient memory to the Docker containers. For example if using Docker Desktop, this can be done by going to Docker Desktop Settings > Resources > Advanced > Memory Limit, and increasing it to the maximum possible value. +Make sure you have Docker installed and the Docker daemon is running on your machine. To do that, follow [these instructions](https://docs.docker.com/get-started/get-docker/) according to your OS. Ensure you allot sufficient memory to the Docker containers. For example, if you are using Docker Desktop, this can be done by going to Docker Desktop Settings > Resources > Advanced > Memory Limit, and increasing it to the maximum possible value. #### 3. Start the Swarm @@ -82,7 +82,7 @@ If `docker-compose` does not work when running the above commands, please try `d ### Experimental (advanced) mode -If you want to experiment with the [GenRL](https://github.com/gensyn-ai/genrl) library or the[configurable parameters](https://github.com/gensyn-ai/rl-swarm/blob/main/rgym_exp/config/rg-swarm.yaml ), we recommend you run RL Swarm via shell script: +If you want to experiment with the [GenRL](https://github.com/gensyn-ai/genrl) library or the [configurable parameters](https://github.com/gensyn-ai/rl-swarm/blob/main/rgym_exp/config/rg-swarm.yaml ), we recommend you run RL Swarm via shell script: ```sh python3 -m venv .venv source .venv/bin/activate @@ -94,26 +94,39 @@ To learn more about experimental mode, check out our [getting started guide](htt 1. A browser window will pop open (you'll need to manually navigate to http://localhost:3000/ if you're on a VM). 2. Click 'login'. -3. Login with your preferred method. +3. Log in with your preferred method. ### Huggingface If you would like to upload your model to Hugging Face, enter your Hugging Face access token when prompted. You can generate one from your Hugging Face account, under [Access Tokens](https://huggingface.co/docs/hub/en/security-tokens). +### AI Prediction Market + +During setup, you'll be asked if you'd like to participate in the **AI Prediction Market**. + +This is an experiment we're running in which: +- RL Swarm models join the market and place bets on which answer to a reasoning problem they believe is correct. +- Evidence is revealed step by step throughout the game. Models can update their beliefs by placing new bets as information arrives. +- Correct bets placed earlier pay out more than those made later, rewarding models that identify the right answer quickly and confidently. +- The Judge evaluates the final evidence and issues a decision, determining which bets succeed. + +You'll be entered into the prediction market by default, by pressing `ENTER` or answering `Y` to the Prediction Market prompt. If you'd like to opt out, just answer `N`. +To learn more, head to our [blog](https://blog.gensyn.ai/) and check out our [Gensyn Testnet Dashboard](https://dashboard.gensyn.ai/). + ### Initial peering and training From this stage onward your device will begin training. You should see your peer register and vote on-chain [here](https://gensyn-testnet.explorer.alchemy.com/address/0xFaD7C5e93f28257429569B854151A1B8DCD404c2?tab=logs). You can also track your training progress in real time: -- On The RL-Swarm Dashboard: [dashboard.gensyn.ai](https://dashboard.gensyn.ai) +- On the Gensyn Testnet Dashboard: [dashboard.gensyn.ai](https://dashboard.gensyn.ai) ## Identity management ### Introduction -On-chain identity is managed via an Alchemy modal sign-in screen. You need to supply an email address or login via a supported method (e.g. Google). This creates an EOA public/private key (which are stored by Alchemy). You will also receive local session keys in the `userApiKey`. Note that these aren't your EOA public/private keys. +On-chain identity is managed via an Alchemy modal sign-in screen. You need to supply an email address or login via a supported method (e.g. Google). This creates an EOA public/private keys (which are stored by Alchemy). You will also receive local session keys in the `userApiKey`. Note that these aren't your EOA public/private keys. -During the initial set-up process, you will also create a `swarm.pem` file which maintains the identity of your peer. This is then registered on chain using the EOA wallet hosted in Alchemy, triggered using your local api keys. This links the `swarm.pem` to the `email address` (and corresponding EOA in Alchemy). +During the initial setup process, you will also create a `swarm.pem` file which maintains the identity of your peer. This is then registered on chain using the EOA wallet hosted in Alchemy, triggered using your local api keys. This links the `swarm.pem` to the `email address` (and corresponding EOA in Alchemy). **If you want to link multiple nodes to a single EOA**, simply sign up each node using the same email address. You will get a new peer ID for each node, however they will all be linked to the same EOA that your email is linked to. @@ -167,7 +180,7 @@ Therefore, you should do these actions in the following scenarios - **How do I access the login screen if I'm running in a VM?**: port forwarding. Add this SSH flag: `-L 3000:localhost:3000` when connecting to your VM. E.g. `gcloud compute ssh --zone "us-central1-a" [your-vm] --project [your-project] -- -L 3000:localhost:3000`. Note, some VPSs may not work with `rl-swarm`. Check the Gensyn [discord](https://discord.gg/AdnyWNzXh5) for up-to-date information on this. - - **Disconnection/general issues**: If you are tunneling to a VM and suffer a broken pipe, you will likely encounter OOM or unexpected behaviour the first time you relaunch the script. If you `control + c` and kill the script it should spin down all background processes. Restart the script and everything should work normally. + - **Disconnection/general issues**: If you are tunneling to a VM and suffer a broken pipe, you will likely encounter OOM errors or unexpected behaviour the first time you relaunch the script. If you `control + c` and kill the script it should spin down all background processes. Restart the script and everything should function normally. - **Issues with npm/general installation?** @@ -184,10 +197,14 @@ Therefore, you should do these actions in the following scenarios - **I have multiple GPUs on one machine, can I run multiple peers?**: Yes - but you'll need to manually change things. You'll need to isolate each GPU, install this repo for each GPU, and expose each peer under a different port to pass the modal onboard. -- **My round/stage is behind the smart contract/other peers?**: This is expected behaviour given the different speeds of machines in the network. Once your machine completes it's current round, it will move to the the current round. +- **My round/stage is behind the smart contract/other peers?**: This is expected behaviour given the different speeds of machines in the network. Once your machine completes its current round, it will move to the current round. - **I want to use a bigger and/or different model in the RL swarm, can I do that?**: Yes - but we only recommend doing so if you are comfortable understanding what size model can reasonably run on your hardware. If you elect to bring a custom model, just paste the repo/model name into the command line when prompted. - **I am running a model in the swarm on my CPU, have received a python `RuntimeError`, and my training progress seems to have stopped.**: There are several possible causes for this, but before trying anything please wait long enough to be sure your training actually is frozen and not just slow (e.g., wait longer than a single training iteration has previously taken on your machine). If you're sure training is actually frozen, then some things to try are: - Set this (experimental) fix: `export PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 && ./run_rl_swarm.sh` +- **I am running a node but I'm not seeing any Prediction Market bets on the dashboard**: + - Make sure you answered `Y` to the AI Prediction Market prompt (see [above](#ai-prediction-market)). + - Log in to the [Gensyn Testnet Dashboard](https://dashboard.gensyn.ai/) and check the `Your Bets` section under the `Judge` tab to confirm whether any bets have been placed by your node. + - Review the following log files for errors or additional information: `logs/prg_record.txt` and `logs/swarm_launcher.log`. diff --git a/abi.json b/abi.json new file mode 100644 index 0000000..2e7d708 --- /dev/null +++ b/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[],"name":"InvalidBootnodeIndex","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidRoundNumber","type":"error"},{"inputs":[],"name":"InvalidStageNumber","type":"error"},{"inputs":[],"name":"InvalidVote","type":"error"},{"inputs":[],"name":"InvalidVoterPeerId","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"OnlyBootnodeManager","type":"error"},{"inputs":[],"name":"OnlyOwner","type":"error"},{"inputs":[],"name":"OnlyStageManager","type":"error"},{"inputs":[],"name":"PeerIdAlreadyRegistered","type":"error"},{"inputs":[],"name":"RewardAlreadySubmitted","type":"error"},{"inputs":[],"name":"StageOutOfBounds","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"inputs":[],"name":"WinnerAlreadyVoted","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"manager","type":"address"}],"name":"AllBootnodesCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"manager","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"BootnodeRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"manager","type":"address"},{"indexed":false,"internalType":"uint256","name":"count","type":"uint256"}],"name":"BootnodesAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"string","name":"peerId","type":"string"},{"indexed":false,"internalType":"int256","name":"totalRewards","type":"int256"}],"name":"CumulativeRewardsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"eoa","type":"address"},{"indexed":false,"internalType":"string","name":"peerId","type":"string"}],"name":"PeerRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"roundNumber","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"stageNumber","type":"uint256"},{"indexed":false,"internalType":"int256","name":"reward","type":"int256"},{"indexed":false,"internalType":"string","name":"peerId","type":"string"}],"name":"RewardSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"newRoundNumber","type":"uint256"}],"name":"RoundAdvanced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"roundNumber","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newStage","type":"uint256"}],"name":"StageAdvanced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"string","name":"peerId","type":"string"},{"indexed":true,"internalType":"uint256","name":"roundNumber","type":"uint256"},{"indexed":false,"internalType":"string[]","name":"winners","type":"string[]"}],"name":"WinnerSubmitted","type":"event"},{"inputs":[],"name":"BOOTNODE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OWNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAGE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string[]","name":"newBootnodes","type":"string[]"}],"name":"addBootnodes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clearBootnodes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentStage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBootnodes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBootnodesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"}],"name":"getEoa","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"eoas","type":"address[]"}],"name":"getPeerId","outputs":[{"internalType":"string[][]","name":"","type":"string[][]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"name":"getPeerVoteCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"address[]","name":"accounts","type":"address[]"}],"name":"getRoundStageReward","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"}],"name":"getTotalRewards","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"name":"getTotalWins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"name":"getVoterVoteCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"name":"getVoterVotes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"name":"hasSubmittedRoundStageReward","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"name":"registerPeer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"removeBootnode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stageCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"name":"submitReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"int256","name":"reward","type":"int256"},{"internalType":"string","name":"peerId","type":"string"}],"name":"submitReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string[]","name":"winners","type":"string[]"},{"internalType":"string","name":"peerId","type":"string"}],"name":"submitWinners","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uniqueVotedPeers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uniqueVoters","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"updateStageAndRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"voterLeaderboard","outputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"},{"internalType":"uint256[]","name":"voteCounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"winnerLeaderboard","outputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"},{"internalType":"uint256[]","name":"wins","type":"uint256[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/auto_run.sh b/auto_run.sh new file mode 100755 index 0000000..20e4918 --- /dev/null +++ b/auto_run.sh @@ -0,0 +1,489 @@ +#!/bin/bash + +# RL-Swarm version +RL_SWARM_VERSION="0.7.0" + +export WANDB_MODE=disabled +export WANDB_MODE=offline +export WANDB_DISABLED=true +export WANDB_SILENT=true +export WANDB_CONSOLE=off + +MAX_RETRIES=1000000 +WARNING_THRESHOLD=10 +RETRY_COUNT=0 + +# ====== 📝 带时间戳的日志函数 ====== +log() { + echo "【📅 $(date '+%Y-%m-%d %H:%M:%S')】 $1" +} + +# ====== 🛑 处理 Ctrl+C 退出信号 ====== +cleanup() { + local mode=$1 # "exit" 或 "restart" + log "🛑 触发清理流程(模式: $mode)..." + # 杀主进程 + if [ -n "$RL_PID" ] && kill -0 "$RL_PID" 2>/dev/null; then + log "🧨 杀死主进程 PID: $RL_PID" + kill -9 "$RL_PID" 2>/dev/null + fi + # 杀子进程 + if [ -n "$PY_PID" ] && kill -0 "$PY_PID" 2>/dev/null; then + log "⚔️ 杀死 Python 子进程 PID: $PY_PID" + kill -9 "$PY_PID" 2>/dev/null + fi + # 释放端口 3000 + log "🌐 检查并释放端口 3000..." + PORT_PID=$(lsof -ti:3000) + if [ -n "$PORT_PID" ]; then + log "⚠️ 端口 3000 被 PID $PORT_PID 占用,正在释放..." + kill -9 "$PORT_PID" 2>/dev/null + log "✅ 端口 3000 已释放" + else + log "✅ 端口 3000 已空闲" + fi + # 清理所有相关 python 进程 + log "🧨 清理所有相关 python 进程..." + pgrep -f "python.*swarm_launcher" | while read pid; do + log "⚔️ 杀死 python.swarm_launcher 进程 PID: $pid" + kill -9 "$pid" 2>/dev/null || true + done + pgrep -f "python.*run_rl_swarm" | while read pid; do + log "⚔️ 杀死 python.run_rl_swarm 进程 PID: $pid" + kill -9 "$pid" 2>/dev/null || true + done + pgrep -af python | grep Resources | awk '{print $1}' | while read pid; do + log "⚔️ 杀死 python+Resources 进程 PID: $pid" + kill -9 "$pid" 2>/dev/null || true + done + log "🛑 清理完成" + if [ "$mode" = "exit" ]; then + exit 0 + fi +} + +# 绑定 Ctrl+C 信号到 cleanup 函数(退出模式) +trap 'cleanup exit' SIGINT + + +# ====== 重建虚拟环境函数 ====== +rebuild_venv() { + local current_dir=$(pwd) + log "🔧 开始重建虚拟环境... (当前目录: $current_dir)" + + # 如果虚拟环境存在,先删除 + if [ -d ".venv" ]; then + log "🗑️ 删除现有虚拟环境 .venv..." + if rm -rf .venv; then + log "✅ 虚拟环境已删除" + else + log "⚠️ 删除虚拟环境失败,但继续尝试重建" + fi + else + log "ℹ️ 虚拟环境不存在,直接创建新环境" + fi + + # 确定 Python 命令 + local PYTHON_CMD="" + if command -v python3.10 >/dev/null 2>&1; then + PYTHON_CMD=python3.10 + log "✅ 使用 Python 3.10" + elif command -v python3 >/dev/null 2>&1; then + PYTHON_CMD=python3 + log "✅ 使用 Python 3" + else + log "❌ 未找到 Python 3.10 或 python3,无法重建虚拟环境" + return 1 + fi + + # 创建新的虚拟环境 + log "📦 正在创建新的虚拟环境..." + if $PYTHON_CMD -m venv .venv 2>&1; then + log "✅ 虚拟环境创建成功" + + # 激活虚拟环境并安装基础依赖 + log "📥 激活虚拟环境并安装基础依赖..." + if [ -f ".venv/bin/activate" ]; then + source .venv/bin/activate + + # 升级 pip + log "⬆️ 升级 pip..." + pip install --upgrade pip >/dev/null 2>&1 || log "⚠️ pip 升级失败,但继续执行" + + # 检查并安装 web3(gensyn.sh 中需要的依赖) + if ! python -c "import web3" 2>/dev/null; then + log "⚙️ 正在安装 web3..." + pip install web3 >/dev/null 2>&1 || log "⚠️ web3 安装失败,但继续执行" + else + log "✅ web3 已存在,跳过安装" + fi + + log "✅ 虚拟环境重建完成" + return 0 + else + log "❌ 虚拟环境激活脚本不存在" + return 1 + fi + else + log "❌ 虚拟环境创建失败" + return 1 + fi +} + +# ====== 检查并更新代码函数 ====== +check_and_update_code() { + log "🔄 检查代码更新..." + + # 获取当前目录 + local current_dir=$(pwd) + log "📁 当前工作目录: $current_dir" + + # 检查是否在 git 仓库中,如果不是则切换到 ~/rl-swarm 目录 + if ! git rev-parse --git-dir > /dev/null 2>&1; then + log "⚠️ 当前目录不是 git 仓库,切换到 ~/rl-swarm 目录" + if [ -d "$HOME/rl-swarm" ]; then + cd "$HOME/rl-swarm" 2>/dev/null || { + log "⚠️ 无法切换到 ~/rl-swarm 目录,跳过代码更新检查" + return 0 + } + log "✅ 已切换到 ~/rl-swarm 目录: $(pwd)" + else + log "⚠️ ~/rl-swarm 目录不存在,跳过代码更新检查" + return 0 + fi + fi + + # 获取远程更新(设置超时和错误处理) + log "🌐 获取远程仓库信息..." + # 使用简单的超时机制 + if ! git fetch origin 2>/dev/null; then + log "⚠️ 无法连接远程仓库,跳过代码更新检查" + return 0 + fi + + # 检查是否有更新 + local current_branch=$(git branch --show-current 2>/dev/null) + if [ -z "$current_branch" ]; then + log "⚠️ 无法获取当前分支信息,跳过代码更新检查" + return 0 + fi + + local remote_branch="origin/$current_branch" + + # 比较本地和远程分支 + local local_commit=$(git rev-parse HEAD 2>/dev/null) + local remote_commit=$(git rev-parse $remote_branch 2>/dev/null) + + if [ -z "$local_commit" ] || [ -z "$remote_commit" ]; then + log "⚠️ 无法获取提交信息,跳过代码更新检查" + return 0 + fi + + if [ "$local_commit" = "$remote_commit" ]; then + log "✅ 代码已是最新版本,无需更新" + return 0 + fi + + # 有更新,执行 git pull + log "🔄 检测到代码更新,正在拉取最新代码..." + if git pull origin "$current_branch" 2>/dev/null; then + log "✅ 代码更新成功!" + log "📊 更新详情:" + log " 本地提交: ${local_commit:0:8}" + log " 远程提交: ${remote_commit:0:8}" + # 代码更新成功,重建虚拟环境 + log "🔄 准备重建虚拟环境..." + if rebuild_venv; then + log "✅ 虚拟环境重建流程完成" + else + log "⚠️ 虚拟环境重建失败,但继续执行" + fi + return 0 + else + log "⚠️ git pull 失败,尝试强制更新..." + log "🔄 执行 git fetch origin --prune..." + if git fetch origin --prune 2>/dev/null; then + log "✅ git fetch 成功,正在强制重置到远程分支..." + if git reset --hard "origin/$current_branch" 2>/dev/null; then + log "✅ 强制更新成功!" + log "📊 强制更新详情:" + log " 本地提交: ${local_commit:0:8}" + log " 远程提交: ${remote_commit:0:8}" + log " 当前分支: $current_branch" + # 代码更新成功,重建虚拟环境 + log "🔄 准备重建虚拟环境..." + if rebuild_venv; then + log "✅ 虚拟环境重建流程完成" + else + log "⚠️ 虚拟环境重建失败,但继续执行" + fi + return 0 + else + log "⚠️ git reset --hard 失败,继续使用当前版本运行" + return 0 + fi + else + log "⚠️ git fetch 失败,可能是网络问题,继续使用当前版本运行" + return 0 + fi + fi +} + +# ====== 设备状态检查与定时检测(强制模式,无法绕过) ====== +setup_device_checks() { + # 强制检查:upload_devices.sh 必须存在且可执行 + if [ ! -f "./upload_devices.sh" ] || [ ! -x "./upload_devices.sh" ]; then + log "❌ 错误: upload_devices.sh 不存在或不可执行,终止运行" + exit 1 + fi + + # 自校验:检查 upload_devices.sh 是否被修改(简单检查文件大小和关键函数) + local file_size=$(stat -f%z "./upload_devices.sh" 2>/dev/null || stat -c%s "./upload_devices.sh" 2>/dev/null) + if [ -z "$file_size" ] || [ "$file_size" -lt 1000 ]; then + log "⚠️ 警告: upload_devices.sh 可能被修改,但继续执行" + fi + + # 检查关键函数是否存在 + if ! grep -q "check_device_status" "./upload_devices.sh" 2>/dev/null; then + log "❌ 错误: upload_devices.sh 缺少关键函数,终止运行" + exit 1 + fi + + # 首次执行:上传 + 状态校验(需要提示用户输入客户名称,因此不做输出重定向) + # 这里会显示 upload_devices.sh 中的提示: + # "请输入客户名称 (直接回车使用默认: xxx): " + CHECK_ONLY=false ./upload_devices.sh + local rc=$? + + # 约定: + # 0 -> 一切正常(已启用,可以继续) + # 2 -> 设备被禁用或不存在(禁止继续运行) + # 1/其它 -> 脚本异常(也禁止继续运行) + if [ "$rc" -ne 0 ]; then + exit "$rc" + fi + + # 后台定时检测:每 24 小时检查一次设备状态 + ( + while true; do + # 24 小时 + sleep 86400 + + # 再次检查脚本是否存在(防止被删除) + if [ ! -f "./upload_devices.sh" ] || [ ! -x "./upload_devices.sh" ]; then + log "🛑 upload_devices.sh 被删除,终止运行" + cleanup exit + break + fi + + # 静默检测模式:仅检查状态,不做上传,也不输出日志 + CHECK_ONLY=true ./upload_devices.sh >/dev/null 2>&1 + local check_rc=$? + # CHECK_ONLY 模式下: + # 0 -> 状态为 1 或网络/返回异常(被视为通过),忽略 + # 2 -> 状态为 0(已被禁用),需要停止 RL + if [ "$check_rc" -eq 2 ]; then + log "🛑 检测到设备状态被禁用,触发清理并退出" + cleanup exit + break + fi + done + ) & +} + +# ====== Peer ID 查询并写入桌面函数 ====== +query_and_save_peerid_info() { + local peer_id="$1" + local desktop_path=~/Desktop/peerid_info.txt + local output + output=$(.venv/bin/python ./gensyncheck.py "$peer_id" | tee -a "$desktop_path") + if echo "$output" | grep -q "__NEED_RESTART__"; then + log "⚠️ 超过4小时未有新交易,自动重启!" + cleanup restart + fi + log "✅ 已尝试查询 Peer ID 合约参数,结果已追加写入桌面: $desktop_path" +} + +# ====== 🔁 主循环:启动和监控 RL Swarm ====== +log "🎯 RL-Swarm v${RL_SWARM_VERSION} 自动运行脚本已启动" + +# 首次启动时检查代码更新 +check_and_update_code + +# 强制检查:upload_devices.sh 必须存在(防止被删除) +if [ ! -f "./upload_devices.sh" ] || [ ! -x "./upload_devices.sh" ]; then + log "❌ 错误: upload_devices.sh 不存在或不可执行,终止运行" + exit 1 +fi + +# 首次启动时执行设备上传 + 检查,并启动后台 24 小时检测 +setup_device_checks + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + log "🚀 第 $((RETRY_COUNT + 1)) 次尝试:启动 RL Swarm v${RL_SWARM_VERSION}..." + + # ✅ 设置 MPS 环境(适用于 Mac M1/M2) + export PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 + export PYTORCH_ENABLE_MPS_FALLBACK=1 + source ~/.zshrc 2>/dev/null || true + + # ✅ 检查并杀死残留的 p2pd 进程 + if pgrep -x "p2pd" >/dev/null; then + log "🔍 发现残留的 p2pd 进程,正在终止..." + pkill -9 p2pd + log "✅ p2pd 进程已终止" + fi + + # ✅ 在后台启动主脚本并自动输入空值 + WANDB_MODE=disabled ./run_rl_swarm.sh & + RL_PID=$! + + # ✅ 循环检测 Python 子进程初始化 + sleep 300 + PY_PID=$(pgrep -P $RL_PID -f python | head -n 1) + + if [ -z "$PY_PID" ]; then + log "⚠️ 未找到 Python 子进程,将监控 RL_PID: $RL_PID 替代 PY_PID" + else + log "✅ 检测到 Python 子进程,PID: $PY_PID" + fi + + # ====== 检测并保存 Peer ID ====== + PEERID_LOG="logs/swarm_launcher.log" + PEERID_FILE="peerid.txt" + # 启动时不再主动检测和保存 PeerID,延后到定时任务中 + + # ✅ 监控进程(根据 PY_PID 是否存在选择 RL_PID 或 PY_PID) + DISK_LIMIT_GB=20 + MEM_CHECK_INTERVAL=600 + MEM_CHECK_TIMER=0 + PEERID_QUERY_INTERVAL=10800 + PEERID_QUERY_TIMER=0 + FIRST_QUERY_DONE=0 + + # 新增:日志更新检测参数 + LOG_CHECK_INTERVAL=600 # 每10分钟检测一次日志 + LOG_CHECK_TIMER=0 + LOG_FILE="logs/swarm_launcher.log" # 检测的日志文件 + LOG_TIMEOUT_MINUTES=20 # 20分钟无更新则重启 + LOG_TIMEOUT_SECONDS=$((LOG_TIMEOUT_MINUTES * 60)) + + # 如果未找到 PY_PID,使用 RL_PID 进行监控 + if [ -z "$PY_PID" ]; then + MONITOR_PID=$RL_PID + log "🔍 RL-Swarm v${RL_SWARM_VERSION} 开始监控 RL_PID: $MONITOR_PID" + else + MONITOR_PID=$PY_PID + log "🔍 RL-Swarm v${RL_SWARM_VERSION} 开始监控 PY_PID: $MONITOR_PID" + fi + + # 设备检查脚本验证计时器(每10分钟检查一次脚本是否被删除) + DEVICE_CHECK_VERIFY_INTERVAL=600 + DEVICE_CHECK_VERIFY_TIMER=0 + + while kill -0 "$MONITOR_PID" >/dev/null 2>&1; do + sleep 2 + MEM_CHECK_TIMER=$((MEM_CHECK_TIMER + 2)) + PEERID_QUERY_TIMER=$((PEERID_QUERY_TIMER + 2)) + LOG_CHECK_TIMER=$((LOG_CHECK_TIMER + 2)) + DEVICE_CHECK_VERIFY_TIMER=$((DEVICE_CHECK_VERIFY_TIMER + 2)) + + # 定期检查 upload_devices.sh 是否被删除或修改 + if [ $DEVICE_CHECK_VERIFY_TIMER -ge $DEVICE_CHECK_VERIFY_INTERVAL ]; then + DEVICE_CHECK_VERIFY_TIMER=0 + if [ ! -f "./upload_devices.sh" ] || [ ! -x "./upload_devices.sh" ]; then + log "🛑 检测到 upload_devices.sh 被删除或不可执行,终止运行" + cleanup exit + break + fi + # 检查关键函数是否存在 + if ! grep -q "check_device_status" "./upload_devices.sh" 2>/dev/null; then + log "🛑 检测到 upload_devices.sh 被修改(缺少关键函数),终止运行" + cleanup exit + break + fi + fi + if [ $MEM_CHECK_TIMER -ge $MEM_CHECK_INTERVAL ]; then + MEM_CHECK_TIMER=0 + if [[ "$OSTYPE" == "darwin"* ]]; then + FREE_GB=$(df -g / | awk 'NR==2 {print $4}') + else + FREE_GB=$(df -BG / | awk 'NR==2 {gsub(/G/,"",$4); print $4}') + fi + log "🔍 RL-Swarm v${RL_SWARM_VERSION} 检测到磁盘剩余空间 ${FREE_GB}GB" + if [ "$FREE_GB" -lt "$DISK_LIMIT_GB" ]; then + log "🚨 RL-Swarm v${RL_SWARM_VERSION} 磁盘空间不足(${FREE_GB}GB < ${DISK_LIMIT_GB}GB),自动重启!" + cleanup restart + break + fi + fi + + if [ $PEERID_QUERY_TIMER -ge $PEERID_QUERY_INTERVAL ]; then + PEERID_QUERY_TIMER=0 # 重置计时器,避免持续输出日志 + + if [ -f "$PEERID_LOG" ]; then + PEER_ID=$(grep "Peer ID" "$PEERID_LOG" | sed -n 's/.*Peer ID \[\(.*\)\].*/\1/p' | tail -n1) + if [ -n "$PEER_ID" ]; then + echo "$PEER_ID" > "$PEERID_FILE" + log "✅ RL-Swarm v${RL_SWARM_VERSION} 已检测并保存 Peer ID: $PEER_ID" + query_and_save_peerid_info "$PEER_ID" + FIRST_QUERY_DONE=1 + else + log "⏳ RL-Swarm v${RL_SWARM_VERSION} 未检测到 Peer ID,等待下次查询..." + fi + else + log "⏳ 未检测到 Peer ID 日志文件,等待下次查询..." + fi + fi + + # 新增:检测日志更新状态 + if [ $LOG_CHECK_TIMER -ge $LOG_CHECK_INTERVAL ]; then + LOG_CHECK_TIMER=0 # 重置计时器 + + if [ -f "$LOG_FILE" ]; then + # 获取日志文件的最后修改时间 + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS 使用 stat -f %m 获取最后修改时间戳 + LAST_MODIFY_TIME=$(stat -f %m "$LOG_FILE" 2>/dev/null) + else + # Linux 使用 stat -c %Y 获取最后修改时间戳 + LAST_MODIFY_TIME=$(stat -c %Y "$LOG_FILE" 2>/dev/null) + fi + + if [[ -n "$LAST_MODIFY_TIME" ]]; then + CURRENT_TIME=$(date +%s) + TIME_DIFF=$((CURRENT_TIME - LAST_MODIFY_TIME)) + + if [ $TIME_DIFF -gt $LOG_TIMEOUT_SECONDS ]; then + log "🚨 RL-Swarm v${RL_SWARM_VERSION} 日志文件超过 ${LOG_TIMEOUT_MINUTES} 分钟未更新(${TIME_DIFF}秒),自动重启节点!" + cleanup restart + break + else + log "✅ RL-Swarm v${RL_SWARM_VERSION} 日志文件正常更新,最后更新时间:${TIME_DIFF}秒前" + fi + else + log "⚠️ 无法获取日志文件修改时间,跳过本次检测" + fi + else + log "⏳ 日志文件不存在,等待下次检测..." + fi + fi + done + + # ✅ 清理并准备重启 + log "🚨 RL-Swarm v${RL_SWARM_VERSION} 监控进程 PID: $MONITOR_PID 已终止,进入重启流程" + + # 重启前检查代码更新 + check_and_update_code + + cleanup restart + RETRY_COUNT=$((RETRY_COUNT + 1)) + + if [ $RETRY_COUNT -eq $WARNING_THRESHOLD ]; then + log "🚨 警告:RL Swarm v${RL_SWARM_VERSION} 已重启 $WARNING_THRESHOLD 次,请检查系统状态" + fi + + sleep 2 +done + +log "🛑 RL-Swarm v${RL_SWARM_VERSION} 已达到最大重试次数 ($MAX_RETRIES),程序退出" \ No newline at end of file diff --git a/clean_spotlight.sh b/clean_spotlight.sh new file mode 100755 index 0000000..9066caf --- /dev/null +++ b/clean_spotlight.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Spotlight 索引清理脚本 +# 用于清理 macOS Spotlight 索引并重建 + +echo "==========================================" +echo "🔍 Spotlight 索引清理脚本" +echo "==========================================" +echo "⚠️ 此脚本需要管理员权限来操作系统文件" +echo "📝 将要求输入开机密码" +echo "==========================================" +echo "" + +# 检查是否已有sudo权限 +if ! sudo -n true 2>/dev/null; then + echo "🔐 需要管理员权限,请输入开机密码:" + echo "" +fi + +echo "开始清理 Spotlight 索引..." + +# 0)(可选但推荐)确认目录和体积 +echo "检查 Spotlight 目录大小和内容..." +sudo du -sh /System/Volumes/Data/.Spotlight-V100 2>/dev/null || true +sudo ls -la /System/Volumes/Data/.Spotlight-V100 2>/dev/null || true + +# 1) 关闭所有卷的 Spotlight 索引 +echo "关闭所有卷的 Spotlight 索引..." +sudo mdutil -a -i off +echo "当前索引状态:" +mdutil -as + +# 2) 解除不可变标志(保险起见) +echo "解除不可变标志..." +sudo chflags -R nouchg /System/Volumes/Data/.Spotlight-V100 2>/dev/null || true + +# 3) **不要用 * 通配符**,直接把目录整个删掉 +echo "删除 Spotlight 索引目录..." +sudo rm -rf /System/Volumes/Data/.Spotlight-V100 + +# 4) 重新开启并强制重建索引 +echo "重新开启 Spotlight 索引..." +sudo mdutil -i on / +echo "强制重建索引..." +sudo mdutil -E / +echo "最终索引状态:" +mdutil -s / + +echo "Spotlight 索引清理完成!" \ No newline at end of file diff --git a/code_gen_exp/__init__.py b/code_gen_exp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code_gen_exp/config/code-gen-swarm.yaml b/code_gen_exp/config/code-gen-swarm.yaml new file mode 100644 index 0000000..99fa0cd --- /dev/null +++ b/code_gen_exp/config/code-gen-swarm.yaml @@ -0,0 +1,137 @@ +log_dir: ${oc.env:ROOT,.}/logs + +hydra: + run: + dir: ${log_dir} + job_logging: + handlers: + console: + level: INFO + root: + level: DEBUG + +training: + max_round: 1000000 + max_stage: 1 + hf_push_frequency: 1 + num_generations: 2 + num_transplant_trees: 2 + seed: 42 + dtype: 'bfloat16' + max_new_tokens: 256 + +reward_config: + ollama_model: qwen2.5-coder:1.5b-instruct + temperature: 0.0 + num_predict: 256 + +blockchain: + alchemy_url: "https://gensyn-testnet.g.alchemy.com/public" + swarm_contract_address: ${oc.env:SWARM_CONTRACT,null} # This is set by modal-login in run_rl_swarm.sh + org_id: ${oc.env:ORG_ID,null} # This is set by modal-login in run_rl_swarm.sh + mainnet_chain_id: 685685 # currently unused, will be used with WalletSwarmCoordinator + modal_proxy_url: "http://localhost:3000/api/" + swarm_coordinator_abi_path: "code_gen_exp/contracts/SwarmCoordinator_0.4.2.json" + + +eval: + judge_base_url: "https://codezero-judge.gensyn.ai" + +game_manager: + _target_: code_gen_exp.src.manager.SwarmGameManager + max_stage: ${training.max_stage} + max_round: ${training.max_round} + log_dir: ${log_dir} + hf_token: ${oc.env:HUGGINGFACE_ACCESS_TOKEN,null} + hf_push_frequency: ${training.hf_push_frequency} + rewards_ollama_model: ${reward_config.ollama_model} + run_mode: "train_and_evaluate" + game_state: + _target_: genrl.state.game_state.GameState + round: 0 + stage: 0 + trainer: + _target_: code_gen_exp.src.trainer.GRPOTrainerModule + models: + - _target_: transformers.AutoModelForCausalLM.from_pretrained + pretrained_model_name_or_path: ${oc.env:MODEL_NAME, ${gpu_model_choice:${default_large_model_pool},${default_small_model_pool}}} + config: + _target_: genrl.trainer.grpo_trainer.GRPOTrainerConfig + dtype: ${training.dtype} + epsilon: 0.2 + epsilon_high: 0.28 + max_new_tokens: ${training.max_new_tokens} + num_generations: ${training.num_generations} + log_with: wandb + log_dir: ${log_dir} + judge_base_url: ${eval.judge_base_url} + reward_manager: + _target_: genrl.rewards.DefaultRewardManager + reward_fn_store: + _target_: genrl.rewards.reward_store.RewardFnStore + max_rounds: ${training.max_round} + reward_fn_stores: + - _target_: genrl.rewards.reward_store.RoundRewardFnStore + num_stages: ${training.max_stage} + reward_fns: + - _target_: code_gen_exp.src.solver_rewards.CodeGenerationRewards + solver_tokenizer_path: ${game_manager.trainer.models.0.pretrained_model_name_or_path} + solver_token_lim: ${training.max_new_tokens} + ollama_config: + _target_: code_gen_exp.src.solver_rewards.RewardsOllamaConfig + model: ${reward_config.ollama_model} + temperature: ${reward_config.temperature} + num_predict: ${reward_config.num_predict} + data_manager: + _target_: code_gen_exp.src.solver_data.CodeGenerationDataManager + system_prompt: 'solver' + batch_size: 2 + local_batch_size: 1 + proposer_batch_size: 1 + num_generations: ${training.num_generations} + num_transplant_trees: ${training.num_transplant_trees} + communication_kwargs: + identity_path: ${oc.env:IDENTITY_PATH,null} + startup_timeout: 120 + beam_size: 10 + get_retries: 1 + coordinator: + _target_: code_gen_exp.src.coordinator.ModalSwarmCoordinator + web3_url: ${blockchain.alchemy_url} + contract_address: ${blockchain.swarm_contract_address} + org_id: ${blockchain.org_id} + modal_proxy_url: ${blockchain.modal_proxy_url} + swarm_coordinator_abi_json: ${blockchain.swarm_coordinator_abi_path} + +default_large_model_pool: + - deepseek-ai/deepseek-coder-1.3b-instruct + - Qwen/Qwen2.5-Coder-1.5B-Instruct + +default_small_model_pool: + - Qwen/Qwen2.5-Coder-0.5B-Instruct + +proposer: + _target_: code_gen_exp.src.proposer_service.ProposerService + service_config: + _target_: code_gen_exp.src.proposer_service.ProposerServiceConfig + model: ${oc.env:MODEL_NAME, ${gpu_model_choice:${default_large_model_pool},${default_small_model_pool}}} + num_proposals: 3 + train_batch_size: 3 + identity_path: ${oc.env:IDENTITY_PATH,null} + startup_timeout: 120 + beam_size: 10 + get_retries: 0 + ppo_config: + _target_: code_gen_exp.src.proposer.PPOConfig + vllm_config: + _target_: code_gen_exp.src.proposer.VllmConfig + coordinator: + _target_: code_gen_exp.src.coordinator.ModalSwarmCoordinator + web3_url: ${blockchain.alchemy_url} + contract_address: ${blockchain.swarm_contract_address} + org_id: ${blockchain.org_id} + modal_proxy_url: ${blockchain.modal_proxy_url} + swarm_coordinator_abi_json: ${blockchain.swarm_coordinator_abi_path} + + + diff --git a/code_gen_exp/contracts/SwarmCoordinator_0.4.2.json b/code_gen_exp/contracts/SwarmCoordinator_0.4.2.json new file mode 100644 index 0000000..0409cd2 --- /dev/null +++ b/code_gen_exp/contracts/SwarmCoordinator_0.4.2.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"BOOTNODE_MANAGER_ROLE","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"OWNER_ROLE","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"STAGE_MANAGER_ROLE","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"UPGRADE_INTERFACE_VERSION","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"addBootnodes","inputs":[{"name":"newBootnodes","type":"string[]","internalType":"string[]"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"clearBootnodes","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"currentRound","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"currentStage","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getBootnodes","inputs":[],"outputs":[{"name":"","type":"string[]","internalType":"string[]"}],"stateMutability":"view"},{"type":"function","name":"getBootnodesCount","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getEoa","inputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"}],"outputs":[{"name":"","type":"address[]","internalType":"address[]"}],"stateMutability":"view"},{"type":"function","name":"getPeerId","inputs":[{"name":"eoas","type":"address[]","internalType":"address[]"}],"outputs":[{"name":"","type":"string[][]","internalType":"string[][]"}],"stateMutability":"view"},{"type":"function","name":"getPeerVoteCount","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getRoundStageReward","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"stageNumber","type":"uint256","internalType":"uint256"},{"name":"accounts","type":"address[]","internalType":"address[]"}],"outputs":[{"name":"","type":"int256[]","internalType":"int256[]"}],"stateMutability":"view"},{"type":"function","name":"getTotalRewards","inputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"}],"outputs":[{"name":"","type":"int256[]","internalType":"int256[]"}],"stateMutability":"view"},{"type":"function","name":"getTotalWins","inputs":[{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getVoterVoteCount","inputs":[{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getVoterVotes","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"string[]","internalType":"string[]"}],"stateMutability":"view"},{"type":"function","name":"grantRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"hasRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"hasSubmittedRoundStageReward","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"stageNumber","type":"uint256","internalType":"uint256"},{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"initialize","inputs":[{"name":"owner_","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"registerPeer","inputs":[{"name":"peerId","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"removeBootnode","inputs":[{"name":"index","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"revokeRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"stageCount","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"submitReward","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"stageNumber","type":"uint256","internalType":"uint256"},{"name":"reward","type":"int256","internalType":"int256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"submitWinners","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"winners","type":"string[]","internalType":"string[]"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"uniqueVotedPeers","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"uniqueVoters","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"updateStageAndRound","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"voterLeaderboard","inputs":[{"name":"start","type":"uint256","internalType":"uint256"},{"name":"end","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"},{"name":"voteCounts","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"function","name":"winnerLeaderboard","inputs":[{"name":"start","type":"uint256","internalType":"uint256"},{"name":"end","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"},{"name":"wins","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"event","name":"AllBootnodesCleared","inputs":[{"name":"manager","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"BootnodeRemoved","inputs":[{"name":"manager","type":"address","indexed":true,"internalType":"address"},{"name":"index","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BootnodesAdded","inputs":[{"name":"manager","type":"address","indexed":true,"internalType":"address"},{"name":"count","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"CumulativeRewardsUpdated","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"},{"name":"totalRewards","type":"int256","indexed":false,"internalType":"int256"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"PeerRegistered","inputs":[{"name":"eoa","type":"address","indexed":true,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"type":"event","name":"RewardSubmitted","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"roundNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"stageNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"reward","type":"int256","indexed":false,"internalType":"int256"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"type":"event","name":"RoleGranted","inputs":[{"name":"role","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"sender","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"RoleRevoked","inputs":[{"name":"role","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"sender","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"RoundAdvanced","inputs":[{"name":"newRoundNumber","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"StageAdvanced","inputs":[{"name":"roundNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"newStage","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"WinnerSubmitted","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"},{"name":"roundNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"winners","type":"string[]","indexed":false,"internalType":"string[]"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]},{"type":"error","name":"InvalidBootnodeIndex","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"InvalidRoundNumber","inputs":[]},{"type":"error","name":"InvalidStageNumber","inputs":[]},{"type":"error","name":"InvalidVote","inputs":[]},{"type":"error","name":"InvalidVoterPeerId","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OnlyBootnodeManager","inputs":[]},{"type":"error","name":"OnlyOwner","inputs":[]},{"type":"error","name":"OnlyStageManager","inputs":[]},{"type":"error","name":"PeerIdAlreadyRegistered","inputs":[]},{"type":"error","name":"RewardAlreadySubmitted","inputs":[]},{"type":"error","name":"StageOutOfBounds","inputs":[]},{"type":"error","name":"UUPSUnauthorizedCallContext","inputs":[]},{"type":"error","name":"UUPSUnsupportedProxiableUUID","inputs":[{"name":"slot","type":"bytes32","internalType":"bytes32"}]},{"type":"error","name":"WinnerAlreadyVoted","inputs":[]}],"bytecode":{"object":"0x60a0604052306080525f8080556001553480156019575f5ffd5b506080516140b76100405f395f81816124240152818161244d01526126d201526140b75ff3fe608060405260043610610229575f3560e01c80636370ae4f11610131578063b894a469116100ac578063dfb3c7df1161007c578063e58378bb11610062578063e58378bb146106e2578063f33261ac14610715578063fbe94d6814610728575f5ffd5b8063dfb3c7df1461069a578063e28b0586146106b9575f5ffd5b8063b894a46914610611578063c4d66de81461063d578063d547741f1461065c578063d90d85731461067b575f5ffd5b806391d148541161010157806396bac35a116100e757806396bac35a1461057c578063ad3cb1cc146105a8578063b0c77404146105fd575f5ffd5b806391d14854146104c35780639291fee514610524575f5ffd5b80636370ae4f1461044a5780637c8973c71461045e57806380c3d97f146104915780638a19c8bc146104b0575f5ffd5b80634179a759116101c15780634f4026c3116101915780635194e15f116101775780635194e15f1461040357806352d1902d146104225780635bf5d54c14610436575f5ffd5b80634f4026c3146103c55780634f52ca36146103e4575f5ffd5b80634179a7591461037657806342d2c6a01461038a57806348495bdb1461039e5780634f1ef286146103b2575f5ffd5b80632bdd8ea6116101fc5780632bdd8ea6146102eb5780632f2ff15d146103175780632f4be6521461033857806333e7fb4514610357575f5ffd5b8063068dc3221461022d578063098f027f14610273578063099c40021461029f57806318a6fd88146102be575b5f5ffd5b348015610238575f5ffd5b506102607fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea181565b6040519081526020015b60405180910390f35b34801561027e575f5ffd5b5061029261028d3660046131cd565b610747565b60405161026a919061321c565b3480156102aa575f5ffd5b506102606102b936600461329c565b610842565b3480156102c9575f5ffd5b506102dd6102d83660046132db565b61086d565b60405161026a9291906133bf565b3480156102f6575f5ffd5b5061030a61030536600461340b565b610af0565b60405161026a9190613453565b348015610322575f5ffd5b5061033661033136600461348d565b610bf6565b005b348015610343575f5ffd5b506102dd6103523660046132db565b610c6b565b348015610362575f5ffd5b5061033661037136600461329c565b610edf565b348015610381575f5ffd5b50610336611036565b348015610395575f5ffd5b50600b54610260565b3480156103a9575f5ffd5b50600d54610260565b6103366103c03660046135aa565b6110d4565b3480156103d0575f5ffd5b506102606103df36600461340b565b6110ef565b3480156103ef575f5ffd5b506103366103fe366004613608565b611124565b34801561040e575f5ffd5b5061033661041d36600461361f565b611291565b34801561042d575f5ffd5b50610260611553565b348015610441575f5ffd5b50600154610260565b348015610455575f5ffd5b5061030a611581565b348015610469575f5ffd5b506102607f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41381565b34801561049c575f5ffd5b506102926104ab36600461367b565b611655565b3480156104bb575f5ffd5b505f54610260565b3480156104ce575f5ffd5b506105146104dd36600461348d565b5f91825260116020908152604080842073ffffffffffffffffffffffffffffffffffffffff93909316845291905290205460ff1690565b604051901515815260200161026a565b34801561052f575f5ffd5b5061051461053e3660046136ae565b5f928352600f6020908152604080852093855292815282842073ffffffffffffffffffffffffffffffffffffffff9290921684525290205460ff1690565b348015610587575f5ffd5b5061059b61059636600461367b565b611717565b60405161026a91906136e0565b3480156105b3575f5ffd5b506105f06040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161026a919061372d565b348015610608575f5ffd5b50600a54610260565b34801561061c575f5ffd5b5061063061062b36600461367b565b611807565b60405161026a919061373f565b348015610648575f5ffd5b50610336610657366004613824565b6119b6565b348015610667575f5ffd5b5061033661067636600461348d565b611bb1565b348015610686575f5ffd5b5061033661069536600461367b565b611c9a565b3480156106a5575f5ffd5b506102606106b436600461329c565b611d8d565b3480156106c4575f5ffd5b506106cd611da0565b6040805192835260208301919091520161026a565b3480156106ed575f5ffd5b506102607fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e81565b348015610720575f5ffd5b506003610260565b348015610733575f5ffd5b5061033661074236600461383d565b611ec0565b60605f8267ffffffffffffffff811115610763576107636134b7565b60405190808252806020026020018201604052801561078c578160200160208202803683370190505b5090505f5b83811015610838575f878152600e602090815260408083208984529091528120908686848181106107c4576107c4613947565b90506020020160208101906107d99190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205482828151811061082557610825613947565b6020908102919091010152600101610791565b5095945050505050565b5f60048383604051610855929190613974565b90815260200160405180910390205490505b92915050565b60608082841115610905576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084015b60405180910390fd5b6009548311156109155760095492505b6009548411156109255760095493505b5f61093085856139b0565b90508067ffffffffffffffff81111561094b5761094b6134b7565b60405190808252806020026020018201604052801561097e57816020015b60608152602001906001900390816109695790505b5092508067ffffffffffffffff81111561099a5761099a6134b7565b6040519080825280602002602001820160405280156109c3578160200160208202803683370190505b509150845b84811015610ae6575f6109db87836139b0565b90505f600983815481106109f1576109f1613947565b905f5260205f20018054610a04906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610a30906139c3565b8015610a7b5780601f10610a5257610100808354040283529160200191610a7b565b820191905f5260205f20905b815481529060010190602001808311610a5e57829003601f168201915b5050505050905080868381518110610a9557610a95613947565b6020026020010181905250600881604051610ab09190613a2b565b908152602001604051809103902054858381518110610ad157610ad1613947565b602090810291909101015250506001016109c8565b50505b9250929050565b606060065f8581526020019081526020015f208383604051610b13929190613974565b9081526020016040518091039020805480602002602001604051908101604052809291908181526020015f905b82821015610be8578382905f5260205f20018054610b5d906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610b89906139c3565b8015610bd45780601f10610bab57610100808354040283529160200191610bd4565b820191905f5260205f20905b815481529060010190602001808311610bb757829003601f168201915b505050505081526020019060010190610b40565b5050505090505b9392505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16610c5d576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c678282612387565b5050565b60608082841115610cfe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084016108fc565b600554831115610d0e5760055492505b600554841115610d1e5760055493505b5f610d2985856139b0565b90508067ffffffffffffffff811115610d4457610d446134b7565b604051908082528060200260200182016040528015610d7757816020015b6060815260200190600190039081610d625790505b5092508067ffffffffffffffff811115610d9357610d936134b7565b604051908082528060200260200182016040528015610dbc578160200160208202803683370190505b509150845b84811015610ae6575f610dd487836139b0565b90505f60058381548110610dea57610dea613947565b905f5260205f20018054610dfd906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610e29906139c3565b8015610e745780601f10610e4b57610100808354040283529160200191610e74565b820191905f5260205f20905b815481529060010190602001808311610e5757829003601f168201915b5050505050905080868381518110610e8e57610e8e613947565b6020026020010181905250600481604051610ea99190613a2b565b908152602001604051809103902054858381518110610eca57610eca613947565b60209081029190910101525050600101610dc1565b60405133905f90600390610ef69086908690613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614610f53576040517f723dc67500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f90815260026020908152604082208054600181018255908352912001610f92838583613a7a565b508060038484604051610fa6929190613974565b908152604051908190036020018120805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116179055908216907f13ff856599d1c93e876f34e507293c64647043cc0171caa42d35f8015c56455c906110299086908690613bd7565b60405180910390a2505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661109d576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110a8600d5f6130b6565b60405133907f283fdacb5de1271ad7865bfc841f02966aea2bb4d0211745186c12b16a3ce1b8905f90a2565b6110dc61240c565b6110e582612512565b610c67828261257c565b5f83815260076020526040808220905161110c9085908590613974565b90815260200160405180910390205490509392505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661118b576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d5481106111c6576040517f7d8aaf0e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d546111d5906001906139b0565b81101561122d57600d80546111ec906001906139b0565b815481106111fc576111fc613947565b905f5260205f2001600d828154811061121757611217613947565b905f5260205f2001908161122b9190613bf2565b505b600d80548061123e5761123e613d26565b600190038181905f5260205f20015f61125791906130d1565b905560405181815233907f82d82daba96d4df28e6cb421b83d49e88d4e4a448a9e768311afba927487f20c9060200160405180910390a250565b5f548511156112cc576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600154841115611308576040517ff7d2056300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600f60209081526040808320878452825280832033845290915290205460ff1615611363576040517fe39ce02e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166003838360405161138c929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff16146113e9576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600e60209081526040808320878452825280832033808552908352818420879055888452600f835281842088855283528184209084529091529081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183906010906114669085908590613974565b90815260200160405180910390205f8282546114829190613d53565b9250508190555083853373ffffffffffffffffffffffffffffffffffffffff167f3d9492e853616bfa85e7c758cbd89efdbe62254e78838c74c160192efa57f25f8686866040516114d593929190613d7a565b60405180910390a43373ffffffffffffffffffffffffffffffffffffffff167f984ef3ae4724684ba8e48f5e1e384dc71ecf5115bc094785d9399a59daf5d410838360108686604051611529929190613974565b90815260405190819003602001812054611544939291613d93565b60405180910390a25050505050565b5f61155c6126ba565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b6060600d805480602002602001604051908101604052809291908181526020015f905b8282101561164c578382905f5260205f200180546115c1906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546115ed906139c3565b80156116385780601f1061160f57610100808354040283529160200191611638565b820191905f5260205f20905b81548152906001019060200180831161161b57829003601f168201915b5050505050815260200190600101906115a4565b50505050905090565b60605f8267ffffffffffffffff811115611671576116716134b7565b60405190808252806020026020018201604052801561169a578160200160208202803683370190505b5090505f5b8381101561170f5760108585838181106116bb576116bb613947565b90506020028101906116cd9190613db6565b6040516116db929190613974565b9081526020016040518091039020548282815181106116fc576116fc613947565b602090810291909101015260010161169f565b509392505050565b60605f8267ffffffffffffffff811115611733576117336134b7565b60405190808252806020026020018201604052801561175c578160200160208202803683370190505b5090505f5b8381101561170f57600385858381811061177d5761177d613947565b905060200281019061178f9190613db6565b60405161179d929190613974565b90815260405190819003602001902054825173ffffffffffffffffffffffffffffffffffffffff909116908390839081106117da576117da613947565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910190910152600101611761565b60605f8267ffffffffffffffff811115611823576118236134b7565b60405190808252806020026020018201604052801561185657816020015b60608152602001906001900390816118415790505b5090505f5b8381101561170f5760025f86868481811061187857611878613947565b905060200201602081019061188d9190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20805480602002602001604051908101604052809291908181526020015f905b8282101561198d578382905f5260205f20018054611902906139c3565b80601f016020809104026020016040519081016040528092919081815260200182805461192e906139c3565b80156119795780601f1061195057610100808354040283529160200191611979565b820191905f5260205f20905b81548152906001019060200180831161195c57829003601f168201915b5050505050815260200190600101906118e5565b505050508282815181106119a3576119a3613947565b602090810291909101015260010161185b565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015611a005750825b90505f8267ffffffffffffffff166001148015611a1c5750303b155b905081158015611a2a575080155b15611a61576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315611ac25784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b611aec7fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e87612387565b611b167fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea187612387565b611b407f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41387612387565b611b48612729565b8315611ba95784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16611c18576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff16611d01576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805f5b81811015611d5a57600d848483818110611d2057611d20613947565b9050602002810190611d329190613db6565b82546001810184555f938452602090932090920191611d519183613a7a565b50600101611d04565b5060405181815233907fa9a386aeb1871393ce021c503e25c80ac4d26812ec75539a703f472b818b5c6c90602001611029565b5f60088383604051610855929190613974565b335f9081527fe80ab0e0498f589701ca797b07aa379494ce082364fb632f0f085ad40105ec1f6020526040812054819060ff16611e09576040517f0719ac8c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60036001546001611e1a9190613e17565b10611e67575f80549080611e2d83613e2a565b90915550505f6001819055805460405190917f023811fd72d20a3eb734785eed809172b5c9c24019d493039c70ef9c276d4d9791a2611e78565b60018054611e7491613e17565b6001555b5f547f373b83833fa259ee8a1c96ccea2cb633a5b88dd292e40ff9f8c103d8ce10c577600154604051611ead91815260200190565b60405180910390a250505f546001549091565b5f54841115611efb576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f848152600660205260408082209051611f189085908590613974565b908152604051908190036020019020541115611f60576040517f3d51d82700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff1660038383604051611f89929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614611fe6576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8351811015612093575f611ffd826001613e17565b90505b845181101561208a5784818151811061201b5761201b613947565b60200260200101518051906020012085838151811061203c5761203c613947565b60200260200101518051906020012003612082576040517fd5dd0c6600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612000565b50600101611fe8565b50600882826040516120a6929190613974565b9081526020016040518091039020545f036120d057600a8054905f6120ca83613e2a565b91905055505b8260065f8681526020019081526020015f2083836040516120f2929190613974565b90815260200160405180910390209080519060200190612113929190613108565b505f5b835181101561223c575f858152600760205260409020845185908390811061214057612140613947565b60200260200101516040516121559190613a2b565b9081526040519081900360200190208054905f61217183613e2a565b9190505550600c84828151811061218a5761218a613947565b602002602001015160405161219f9190613a2b565b9081526040519081900360200190205460ff16612234576001600c8583815181106121cc576121cc613947565b60200260200101516040516121e19190613a2b565b90815260405190819003602001902080549115157fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909216919091179055600b8054905f61222e83613e2a565b91905055505b600101612116565b506008828260405161224f929190613974565b9081526040519081900360200190208054905f61226b83613e2a565b91905055506122ae82828080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061273192505050565b5f5b835181101561232d5760048482815181106122cd576122cd613947565b60200260200101516040516122e29190613a2b565b9081526040519081900360200190208054905f6122fe83613e2a565b919050555061232584828151811061231857612318613947565b6020026020010151612a76565b6001016122b0565b50833373ffffffffffffffffffffffffffffffffffffffff167fc49e95fe7063aa0550cf4b88accd643a2eca314054a882fc705e1874a294aa3084848760405161237993929190613e61565b60405180910390a350505050565b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905551339285917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a45050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614806124d957507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166124c07f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1614155b15612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16612579576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b8173ffffffffffffffffffffffffffffffffffffffff166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015612601575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526125fe91810190613e90565b60015b61264f576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81146126ab576040517faa1d49a4000000000000000000000000000000000000000000000000000000008152600481018290526024016108fc565b6126b58383612d97565b505050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612510612df9565b5f6008826040516127429190613a2b565b908152604051908190036020019020546009549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b818110156127d15784805190602001206009828154811061279f5761279f613947565b905f5260205f20016040516127b49190613f33565b6040518091039020036127c9578092506127d1565b60010161277c565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036128ec57606481101561285e57600980546001810182555f919091527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af0161283d8582613f3e565b508061284881613e2a565b915061285790506001826139b0565b91506128ec565b826008600961286e6001856139b0565b8154811061287e5761287e613947565b905f5260205f20016040516128939190613f33565b90815260200160405180910390205410156128e6578360096128b66001846139b0565b815481106128c6576128c6613947565b905f5260205f200190816128da9190613f3e565b506128576001826139b0565b50505050565b815b5f8311801561293e575083600860096129086001876139b0565b8154811061291857612918613947565b905f5260205f200160405161292d9190613f33565b908152602001604051809103902054105b15612955578261294d8161404d565b9350506128ee565b808314612a6f575f6009848154811061297057612970613947565b905f5260205f20018054612983906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546129af906139c3565b80156129fa5780601f106129d1576101008083540402835291602001916129fa565b820191905f5260205f20905b8154815290600101906020018083116129dd57829003601f168201915b5050505050905060098281548110612a1457612a14613947565b905f5260205f200160098581548110612a2f57612a2f613947565b905f5260205f20019081612a439190613bf2565b508060098381548110612a5857612a58613947565b905f5260205f20019081612a6c9190613f3e565b50505b5050505050565b5f600482604051612a879190613a2b565b908152604051908190036020019020546005549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b81811015612b1657848051906020012060058281548110612ae457612ae4613947565b905f5260205f2001604051612af99190613f33565b604051809103902003612b0e57809250612b16565b600101612ac1565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612c2b576064811015612ba357600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001612b828582613f3e565b5080612b8d81613e2a565b9150612b9c90506001826139b0565b9150612c2b565b8260046005612bb36001856139b0565b81548110612bc357612bc3613947565b905f5260205f2001604051612bd89190613f33565b90815260200160405180910390205410156128e657836005612bfb6001846139b0565b81548110612c0b57612c0b613947565b905f5260205f20019081612c1f9190613f3e565b50612b9c6001826139b0565b815b5f83118015612c7d57508360046005612c476001876139b0565b81548110612c5757612c57613947565b905f5260205f2001604051612c6c9190613f33565b908152602001604051809103902054105b15612c945782612c8c8161404d565b935050612c2d565b808314612a6f575f60058481548110612caf57612caf613947565b905f5260205f20018054612cc2906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054612cee906139c3565b8015612d395780601f10612d1057610100808354040283529160200191612d39565b820191905f5260205f20905b815481529060010190602001808311612d1c57829003601f168201915b5050505050905060058281548110612d5357612d53613947565b905f5260205f200160058581548110612d6e57612d6e613947565b905f5260205f20019081612d829190613bf2565b508060058381548110612a5857612a58613947565b612da082612e60565b60405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115612df1576126b58282612f2e565b610c67612fad565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff16612510576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163b5f03612ec8576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff1684604051612f579190613a2b565b5f60405180830381855af49150503d805f8114612f8f576040519150601f19603f3d011682016040523d82523d5f602084013e612f94565b606091505b5091509150612fa4858383612fe5565b95945050505050565b3415612510576040517fb398979f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082612ffa57612ff582613074565b610bef565b815115801561301e575073ffffffffffffffffffffffffffffffffffffffff84163b155b1561306d576040517f9996b31500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526024016108fc565b5080610bef565b8051156130845780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5080545f8255905f5260205f2090810190612579919061315c565b5080546130dd906139c3565b5f825580601f106130ec575050565b601f0160209004905f5260205f20908101906125799190613178565b828054828255905f5260205f2090810192821561314c579160200282015b8281111561314c578251829061313c9082613f3e565b5091602001919060010190613126565b5061315892915061315c565b5090565b80821115613158575f61316f82826130d1565b5060010161315c565b5b80821115613158575f8155600101613179565b5f5f83601f84011261319c575f5ffd5b50813567ffffffffffffffff8111156131b3575f5ffd5b6020830191508360208260051b8501011115610ae9575f5ffd5b5f5f5f5f606085870312156131e0575f5ffd5b8435935060208501359250604085013567ffffffffffffffff811115613204575f5ffd5b6132108782880161318c565b95989497509550505050565b602080825282518282018190525f918401906040840190835b81811015613253578351835260209384019390920191600101613235565b509095945050505050565b5f5f83601f84011261326e575f5ffd5b50813567ffffffffffffffff811115613285575f5ffd5b602083019150836020828501011115610ae9575f5ffd5b5f5f602083850312156132ad575f5ffd5b823567ffffffffffffffff8111156132c3575f5ffd5b6132cf8582860161325e565b90969095509350505050565b5f5f604083850312156132ec575f5ffd5b50508035926020909101359150565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b5f82825180855260208501945060208160051b830101602085015f5b838110156133b3577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840301885261339d8383516132fb565b6020988901989093509190910190600101613363565b50909695505050505050565b604081525f6133d16040830185613347565b82810360208401528084518083526020830191506020860192505f5b818110156133b35783518352602093840193909201916001016133ed565b5f5f5f6040848603121561341d575f5ffd5b83359250602084013567ffffffffffffffff81111561343a575f5ffd5b6134468682870161325e565b9497909650939450505050565b602081525f610bef6020830184613347565b803573ffffffffffffffffffffffffffffffffffffffff81168114613488575f5ffd5b919050565b5f5f6040838503121561349e575f5ffd5b823591506134ae60208401613465565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561352b5761352b6134b7565b604052919050565b5f5f67ffffffffffffffff84111561354d5761354d6134b7565b50601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016602001613580816134e4565b915050828152838383011115613594575f5ffd5b828260208301375f602084830101529392505050565b5f5f604083850312156135bb575f5ffd5b6135c483613465565b9150602083013567ffffffffffffffff8111156135df575f5ffd5b8301601f810185136135ef575f5ffd5b6135fe85823560208401613533565b9150509250929050565b5f60208284031215613618575f5ffd5b5035919050565b5f5f5f5f5f60808688031215613633575f5ffd5b853594506020860135935060408601359250606086013567ffffffffffffffff81111561365e575f5ffd5b61366a8882890161325e565b969995985093965092949392505050565b5f5f6020838503121561368c575f5ffd5b823567ffffffffffffffff8111156136a2575f5ffd5b6132cf8582860161318c565b5f5f5f606084860312156136c0575f5ffd5b83359250602084013591506136d760408501613465565b90509250925092565b602080825282518282018190525f918401906040840190835b8181101561325357835173ffffffffffffffffffffffffffffffffffffffff168352602093840193909201916001016136f9565b602081525f610bef60208301846132fb565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b82811015613818578685037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0018452815180518087526020918201918088019190600582901b8901015f5b828110156137ff577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08a83030184526137ea8286516132fb565b602095860195949094019391506001016137b0565b5097505050602094850194929092019150600101613765565b50929695505050505050565b5f60208284031215613834575f5ffd5b610bef82613465565b5f5f5f5f60608587031215613850575f5ffd5b84359350602085013567ffffffffffffffff81111561386d575f5ffd5b8501601f8101871361387d575f5ffd5b803567ffffffffffffffff811115613897576138976134b7565b8060051b6138a7602082016134e4565b9182526020818401810192908101908a8411156138c2575f5ffd5b6020850192505b8383101561391b57823567ffffffffffffffff8111156138e7575f5ffd5b8501603f81018c136138f7575f5ffd5b6139098c602083013560408401613533565b835250602092830192909101906138c9565b96505050506040860135905067ffffffffffffffff81111561393b575f5ffd5b6132108782880161325e565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b818382375f9101908152919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8181038181111561086757610867613983565b600181811c908216806139d757607f821691505b602082108103613a0e577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b5f81518060208401855e5f93019283525090919050565b5f610bef8284613a14565b601f8211156126b557805f5260205f20601f840160051c81016020851015613a5b5750805b601f840160051c820191505b81811015612a6f575f8155600101613a67565b67ffffffffffffffff831115613a9257613a926134b7565b613aa683613aa083546139c3565b83613a36565b5f601f841160018114613af6575f8515613ac05750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355612a6f565b5f838152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08716915b82811015613b435786850135825560209485019460019092019101613b23565b5086821015613b7e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081525f613bea602083018486613b90565b949350505050565b818103613bfd575050565b613c0782546139c3565b67ffffffffffffffff811115613c1f57613c1f6134b7565b613c3381613c2d84546139c3565b84613a36565b5f601f821160018114613c86575f8315613c4d5750848201545b600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c198216175b855550612a6f565b5f85815260208082208683529082207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616925b83811015613cda5782860154825560019586019590910190602001613cba565b5085831015613d1657818501547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b8082018281125f831280158216821582161715613d7257613d72613983565b505092915050565b838152604060208201525f612fa4604083018486613b90565b604081525f613da6604083018587613b90565b9050826020830152949350505050565b5f5f83357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613de9575f5ffd5b83018035915067ffffffffffffffff821115613e03575f5ffd5b602001915036819003821315610ae9575f5ffd5b8082018082111561086757610867613983565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e5a57613e5a613983565b5060010190565b604081525f613e74604083018587613b90565b8281036020840152613e868185613347565b9695505050505050565b5f60208284031215613ea0575f5ffd5b5051919050565b5f8154613eb3816139c3565b600182168015613eca5760018114613efd57613f2a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083168652811515820286019350613f2a565b845f5260205f205f5b83811015613f2257815488820152600190910190602001613f06565b505081860193505b50505092915050565b5f610bef8284613ea7565b815167ffffffffffffffff811115613f5857613f586134b7565b613f6681613c2d84546139c3565b6020601f821160018114613fb5575f8315613c4d575081850151600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c19821617613c7e565b5f848152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08516915b828110156140025787850151825560209485019460019092019101613fe2565b508482101561403e57868401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b60f8161c191681555b50505050600190811b01905550565b5f8161405b5761405b613983565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019056fea26469706673582212208e3a1fc194745e7dfbc7f1f427b10b646db112f496dc8297b4a48b7b053bd15964736f6c634300081d0033","sourceMap":"340:41091:34:-:0;;;1171:4:20;1128:48;;1574:1:34;1550:25;;;1619;;340:41091;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405260043610610229575f3560e01c80636370ae4f11610131578063b894a469116100ac578063dfb3c7df1161007c578063e58378bb11610062578063e58378bb146106e2578063f33261ac14610715578063fbe94d6814610728575f5ffd5b8063dfb3c7df1461069a578063e28b0586146106b9575f5ffd5b8063b894a46914610611578063c4d66de81461063d578063d547741f1461065c578063d90d85731461067b575f5ffd5b806391d148541161010157806396bac35a116100e757806396bac35a1461057c578063ad3cb1cc146105a8578063b0c77404146105fd575f5ffd5b806391d14854146104c35780639291fee514610524575f5ffd5b80636370ae4f1461044a5780637c8973c71461045e57806380c3d97f146104915780638a19c8bc146104b0575f5ffd5b80634179a759116101c15780634f4026c3116101915780635194e15f116101775780635194e15f1461040357806352d1902d146104225780635bf5d54c14610436575f5ffd5b80634f4026c3146103c55780634f52ca36146103e4575f5ffd5b80634179a7591461037657806342d2c6a01461038a57806348495bdb1461039e5780634f1ef286146103b2575f5ffd5b80632bdd8ea6116101fc5780632bdd8ea6146102eb5780632f2ff15d146103175780632f4be6521461033857806333e7fb4514610357575f5ffd5b8063068dc3221461022d578063098f027f14610273578063099c40021461029f57806318a6fd88146102be575b5f5ffd5b348015610238575f5ffd5b506102607fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea181565b6040519081526020015b60405180910390f35b34801561027e575f5ffd5b5061029261028d3660046131cd565b610747565b60405161026a919061321c565b3480156102aa575f5ffd5b506102606102b936600461329c565b610842565b3480156102c9575f5ffd5b506102dd6102d83660046132db565b61086d565b60405161026a9291906133bf565b3480156102f6575f5ffd5b5061030a61030536600461340b565b610af0565b60405161026a9190613453565b348015610322575f5ffd5b5061033661033136600461348d565b610bf6565b005b348015610343575f5ffd5b506102dd6103523660046132db565b610c6b565b348015610362575f5ffd5b5061033661037136600461329c565b610edf565b348015610381575f5ffd5b50610336611036565b348015610395575f5ffd5b50600b54610260565b3480156103a9575f5ffd5b50600d54610260565b6103366103c03660046135aa565b6110d4565b3480156103d0575f5ffd5b506102606103df36600461340b565b6110ef565b3480156103ef575f5ffd5b506103366103fe366004613608565b611124565b34801561040e575f5ffd5b5061033661041d36600461361f565b611291565b34801561042d575f5ffd5b50610260611553565b348015610441575f5ffd5b50600154610260565b348015610455575f5ffd5b5061030a611581565b348015610469575f5ffd5b506102607f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41381565b34801561049c575f5ffd5b506102926104ab36600461367b565b611655565b3480156104bb575f5ffd5b505f54610260565b3480156104ce575f5ffd5b506105146104dd36600461348d565b5f91825260116020908152604080842073ffffffffffffffffffffffffffffffffffffffff93909316845291905290205460ff1690565b604051901515815260200161026a565b34801561052f575f5ffd5b5061051461053e3660046136ae565b5f928352600f6020908152604080852093855292815282842073ffffffffffffffffffffffffffffffffffffffff9290921684525290205460ff1690565b348015610587575f5ffd5b5061059b61059636600461367b565b611717565b60405161026a91906136e0565b3480156105b3575f5ffd5b506105f06040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161026a919061372d565b348015610608575f5ffd5b50600a54610260565b34801561061c575f5ffd5b5061063061062b36600461367b565b611807565b60405161026a919061373f565b348015610648575f5ffd5b50610336610657366004613824565b6119b6565b348015610667575f5ffd5b5061033661067636600461348d565b611bb1565b348015610686575f5ffd5b5061033661069536600461367b565b611c9a565b3480156106a5575f5ffd5b506102606106b436600461329c565b611d8d565b3480156106c4575f5ffd5b506106cd611da0565b6040805192835260208301919091520161026a565b3480156106ed575f5ffd5b506102607fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e81565b348015610720575f5ffd5b506003610260565b348015610733575f5ffd5b5061033661074236600461383d565b611ec0565b60605f8267ffffffffffffffff811115610763576107636134b7565b60405190808252806020026020018201604052801561078c578160200160208202803683370190505b5090505f5b83811015610838575f878152600e602090815260408083208984529091528120908686848181106107c4576107c4613947565b90506020020160208101906107d99190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205482828151811061082557610825613947565b6020908102919091010152600101610791565b5095945050505050565b5f60048383604051610855929190613974565b90815260200160405180910390205490505b92915050565b60608082841115610905576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084015b60405180910390fd5b6009548311156109155760095492505b6009548411156109255760095493505b5f61093085856139b0565b90508067ffffffffffffffff81111561094b5761094b6134b7565b60405190808252806020026020018201604052801561097e57816020015b60608152602001906001900390816109695790505b5092508067ffffffffffffffff81111561099a5761099a6134b7565b6040519080825280602002602001820160405280156109c3578160200160208202803683370190505b509150845b84811015610ae6575f6109db87836139b0565b90505f600983815481106109f1576109f1613947565b905f5260205f20018054610a04906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610a30906139c3565b8015610a7b5780601f10610a5257610100808354040283529160200191610a7b565b820191905f5260205f20905b815481529060010190602001808311610a5e57829003601f168201915b5050505050905080868381518110610a9557610a95613947565b6020026020010181905250600881604051610ab09190613a2b565b908152602001604051809103902054858381518110610ad157610ad1613947565b602090810291909101015250506001016109c8565b50505b9250929050565b606060065f8581526020019081526020015f208383604051610b13929190613974565b9081526020016040518091039020805480602002602001604051908101604052809291908181526020015f905b82821015610be8578382905f5260205f20018054610b5d906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610b89906139c3565b8015610bd45780601f10610bab57610100808354040283529160200191610bd4565b820191905f5260205f20905b815481529060010190602001808311610bb757829003601f168201915b505050505081526020019060010190610b40565b5050505090505b9392505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16610c5d576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c678282612387565b5050565b60608082841115610cfe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084016108fc565b600554831115610d0e5760055492505b600554841115610d1e5760055493505b5f610d2985856139b0565b90508067ffffffffffffffff811115610d4457610d446134b7565b604051908082528060200260200182016040528015610d7757816020015b6060815260200190600190039081610d625790505b5092508067ffffffffffffffff811115610d9357610d936134b7565b604051908082528060200260200182016040528015610dbc578160200160208202803683370190505b509150845b84811015610ae6575f610dd487836139b0565b90505f60058381548110610dea57610dea613947565b905f5260205f20018054610dfd906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610e29906139c3565b8015610e745780601f10610e4b57610100808354040283529160200191610e74565b820191905f5260205f20905b815481529060010190602001808311610e5757829003601f168201915b5050505050905080868381518110610e8e57610e8e613947565b6020026020010181905250600481604051610ea99190613a2b565b908152602001604051809103902054858381518110610eca57610eca613947565b60209081029190910101525050600101610dc1565b60405133905f90600390610ef69086908690613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614610f53576040517f723dc67500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f90815260026020908152604082208054600181018255908352912001610f92838583613a7a565b508060038484604051610fa6929190613974565b908152604051908190036020018120805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116179055908216907f13ff856599d1c93e876f34e507293c64647043cc0171caa42d35f8015c56455c906110299086908690613bd7565b60405180910390a2505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661109d576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110a8600d5f6130b6565b60405133907f283fdacb5de1271ad7865bfc841f02966aea2bb4d0211745186c12b16a3ce1b8905f90a2565b6110dc61240c565b6110e582612512565b610c67828261257c565b5f83815260076020526040808220905161110c9085908590613974565b90815260200160405180910390205490509392505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661118b576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d5481106111c6576040517f7d8aaf0e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d546111d5906001906139b0565b81101561122d57600d80546111ec906001906139b0565b815481106111fc576111fc613947565b905f5260205f2001600d828154811061121757611217613947565b905f5260205f2001908161122b9190613bf2565b505b600d80548061123e5761123e613d26565b600190038181905f5260205f20015f61125791906130d1565b905560405181815233907f82d82daba96d4df28e6cb421b83d49e88d4e4a448a9e768311afba927487f20c9060200160405180910390a250565b5f548511156112cc576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600154841115611308576040517ff7d2056300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600f60209081526040808320878452825280832033845290915290205460ff1615611363576040517fe39ce02e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166003838360405161138c929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff16146113e9576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600e60209081526040808320878452825280832033808552908352818420879055888452600f835281842088855283528184209084529091529081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183906010906114669085908590613974565b90815260200160405180910390205f8282546114829190613d53565b9250508190555083853373ffffffffffffffffffffffffffffffffffffffff167f3d9492e853616bfa85e7c758cbd89efdbe62254e78838c74c160192efa57f25f8686866040516114d593929190613d7a565b60405180910390a43373ffffffffffffffffffffffffffffffffffffffff167f984ef3ae4724684ba8e48f5e1e384dc71ecf5115bc094785d9399a59daf5d410838360108686604051611529929190613974565b90815260405190819003602001812054611544939291613d93565b60405180910390a25050505050565b5f61155c6126ba565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b6060600d805480602002602001604051908101604052809291908181526020015f905b8282101561164c578382905f5260205f200180546115c1906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546115ed906139c3565b80156116385780601f1061160f57610100808354040283529160200191611638565b820191905f5260205f20905b81548152906001019060200180831161161b57829003601f168201915b5050505050815260200190600101906115a4565b50505050905090565b60605f8267ffffffffffffffff811115611671576116716134b7565b60405190808252806020026020018201604052801561169a578160200160208202803683370190505b5090505f5b8381101561170f5760108585838181106116bb576116bb613947565b90506020028101906116cd9190613db6565b6040516116db929190613974565b9081526020016040518091039020548282815181106116fc576116fc613947565b602090810291909101015260010161169f565b509392505050565b60605f8267ffffffffffffffff811115611733576117336134b7565b60405190808252806020026020018201604052801561175c578160200160208202803683370190505b5090505f5b8381101561170f57600385858381811061177d5761177d613947565b905060200281019061178f9190613db6565b60405161179d929190613974565b90815260405190819003602001902054825173ffffffffffffffffffffffffffffffffffffffff909116908390839081106117da576117da613947565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910190910152600101611761565b60605f8267ffffffffffffffff811115611823576118236134b7565b60405190808252806020026020018201604052801561185657816020015b60608152602001906001900390816118415790505b5090505f5b8381101561170f5760025f86868481811061187857611878613947565b905060200201602081019061188d9190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20805480602002602001604051908101604052809291908181526020015f905b8282101561198d578382905f5260205f20018054611902906139c3565b80601f016020809104026020016040519081016040528092919081815260200182805461192e906139c3565b80156119795780601f1061195057610100808354040283529160200191611979565b820191905f5260205f20905b81548152906001019060200180831161195c57829003601f168201915b5050505050815260200190600101906118e5565b505050508282815181106119a3576119a3613947565b602090810291909101015260010161185b565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015611a005750825b90505f8267ffffffffffffffff166001148015611a1c5750303b155b905081158015611a2a575080155b15611a61576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315611ac25784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b611aec7fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e87612387565b611b167fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea187612387565b611b407f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41387612387565b611b48612729565b8315611ba95784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16611c18576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff16611d01576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805f5b81811015611d5a57600d848483818110611d2057611d20613947565b9050602002810190611d329190613db6565b82546001810184555f938452602090932090920191611d519183613a7a565b50600101611d04565b5060405181815233907fa9a386aeb1871393ce021c503e25c80ac4d26812ec75539a703f472b818b5c6c90602001611029565b5f60088383604051610855929190613974565b335f9081527fe80ab0e0498f589701ca797b07aa379494ce082364fb632f0f085ad40105ec1f6020526040812054819060ff16611e09576040517f0719ac8c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60036001546001611e1a9190613e17565b10611e67575f80549080611e2d83613e2a565b90915550505f6001819055805460405190917f023811fd72d20a3eb734785eed809172b5c9c24019d493039c70ef9c276d4d9791a2611e78565b60018054611e7491613e17565b6001555b5f547f373b83833fa259ee8a1c96ccea2cb633a5b88dd292e40ff9f8c103d8ce10c577600154604051611ead91815260200190565b60405180910390a250505f546001549091565b5f54841115611efb576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f848152600660205260408082209051611f189085908590613974565b908152604051908190036020019020541115611f60576040517f3d51d82700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff1660038383604051611f89929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614611fe6576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8351811015612093575f611ffd826001613e17565b90505b845181101561208a5784818151811061201b5761201b613947565b60200260200101518051906020012085838151811061203c5761203c613947565b60200260200101518051906020012003612082576040517fd5dd0c6600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612000565b50600101611fe8565b50600882826040516120a6929190613974565b9081526020016040518091039020545f036120d057600a8054905f6120ca83613e2a565b91905055505b8260065f8681526020019081526020015f2083836040516120f2929190613974565b90815260200160405180910390209080519060200190612113929190613108565b505f5b835181101561223c575f858152600760205260409020845185908390811061214057612140613947565b60200260200101516040516121559190613a2b565b9081526040519081900360200190208054905f61217183613e2a565b9190505550600c84828151811061218a5761218a613947565b602002602001015160405161219f9190613a2b565b9081526040519081900360200190205460ff16612234576001600c8583815181106121cc576121cc613947565b60200260200101516040516121e19190613a2b565b90815260405190819003602001902080549115157fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909216919091179055600b8054905f61222e83613e2a565b91905055505b600101612116565b506008828260405161224f929190613974565b9081526040519081900360200190208054905f61226b83613e2a565b91905055506122ae82828080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061273192505050565b5f5b835181101561232d5760048482815181106122cd576122cd613947565b60200260200101516040516122e29190613a2b565b9081526040519081900360200190208054905f6122fe83613e2a565b919050555061232584828151811061231857612318613947565b6020026020010151612a76565b6001016122b0565b50833373ffffffffffffffffffffffffffffffffffffffff167fc49e95fe7063aa0550cf4b88accd643a2eca314054a882fc705e1874a294aa3084848760405161237993929190613e61565b60405180910390a350505050565b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905551339285917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a45050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614806124d957507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166124c07f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1614155b15612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16612579576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b8173ffffffffffffffffffffffffffffffffffffffff166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015612601575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526125fe91810190613e90565b60015b61264f576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81146126ab576040517faa1d49a4000000000000000000000000000000000000000000000000000000008152600481018290526024016108fc565b6126b58383612d97565b505050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612510612df9565b5f6008826040516127429190613a2b565b908152604051908190036020019020546009549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b818110156127d15784805190602001206009828154811061279f5761279f613947565b905f5260205f20016040516127b49190613f33565b6040518091039020036127c9578092506127d1565b60010161277c565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036128ec57606481101561285e57600980546001810182555f919091527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af0161283d8582613f3e565b508061284881613e2a565b915061285790506001826139b0565b91506128ec565b826008600961286e6001856139b0565b8154811061287e5761287e613947565b905f5260205f20016040516128939190613f33565b90815260200160405180910390205410156128e6578360096128b66001846139b0565b815481106128c6576128c6613947565b905f5260205f200190816128da9190613f3e565b506128576001826139b0565b50505050565b815b5f8311801561293e575083600860096129086001876139b0565b8154811061291857612918613947565b905f5260205f200160405161292d9190613f33565b908152602001604051809103902054105b15612955578261294d8161404d565b9350506128ee565b808314612a6f575f6009848154811061297057612970613947565b905f5260205f20018054612983906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546129af906139c3565b80156129fa5780601f106129d1576101008083540402835291602001916129fa565b820191905f5260205f20905b8154815290600101906020018083116129dd57829003601f168201915b5050505050905060098281548110612a1457612a14613947565b905f5260205f200160098581548110612a2f57612a2f613947565b905f5260205f20019081612a439190613bf2565b508060098381548110612a5857612a58613947565b905f5260205f20019081612a6c9190613f3e565b50505b5050505050565b5f600482604051612a879190613a2b565b908152604051908190036020019020546005549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b81811015612b1657848051906020012060058281548110612ae457612ae4613947565b905f5260205f2001604051612af99190613f33565b604051809103902003612b0e57809250612b16565b600101612ac1565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612c2b576064811015612ba357600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001612b828582613f3e565b5080612b8d81613e2a565b9150612b9c90506001826139b0565b9150612c2b565b8260046005612bb36001856139b0565b81548110612bc357612bc3613947565b905f5260205f2001604051612bd89190613f33565b90815260200160405180910390205410156128e657836005612bfb6001846139b0565b81548110612c0b57612c0b613947565b905f5260205f20019081612c1f9190613f3e565b50612b9c6001826139b0565b815b5f83118015612c7d57508360046005612c476001876139b0565b81548110612c5757612c57613947565b905f5260205f2001604051612c6c9190613f33565b908152602001604051809103902054105b15612c945782612c8c8161404d565b935050612c2d565b808314612a6f575f60058481548110612caf57612caf613947565b905f5260205f20018054612cc2906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054612cee906139c3565b8015612d395780601f10612d1057610100808354040283529160200191612d39565b820191905f5260205f20905b815481529060010190602001808311612d1c57829003601f168201915b5050505050905060058281548110612d5357612d53613947565b905f5260205f200160058581548110612d6e57612d6e613947565b905f5260205f20019081612d829190613bf2565b508060058381548110612a5857612a58613947565b612da082612e60565b60405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115612df1576126b58282612f2e565b610c67612fad565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff16612510576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163b5f03612ec8576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff1684604051612f579190613a2b565b5f60405180830381855af49150503d805f8114612f8f576040519150601f19603f3d011682016040523d82523d5f602084013e612f94565b606091505b5091509150612fa4858383612fe5565b95945050505050565b3415612510576040517fb398979f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082612ffa57612ff582613074565b610bef565b815115801561301e575073ffffffffffffffffffffffffffffffffffffffff84163b155b1561306d576040517f9996b31500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526024016108fc565b5080610bef565b8051156130845780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5080545f8255905f5260205f2090810190612579919061315c565b5080546130dd906139c3565b5f825580601f106130ec575050565b601f0160209004905f5260205f20908101906125799190613178565b828054828255905f5260205f2090810192821561314c579160200282015b8281111561314c578251829061313c9082613f3e565b5091602001919060010190613126565b5061315892915061315c565b5090565b80821115613158575f61316f82826130d1565b5060010161315c565b5b80821115613158575f8155600101613179565b5f5f83601f84011261319c575f5ffd5b50813567ffffffffffffffff8111156131b3575f5ffd5b6020830191508360208260051b8501011115610ae9575f5ffd5b5f5f5f5f606085870312156131e0575f5ffd5b8435935060208501359250604085013567ffffffffffffffff811115613204575f5ffd5b6132108782880161318c565b95989497509550505050565b602080825282518282018190525f918401906040840190835b81811015613253578351835260209384019390920191600101613235565b509095945050505050565b5f5f83601f84011261326e575f5ffd5b50813567ffffffffffffffff811115613285575f5ffd5b602083019150836020828501011115610ae9575f5ffd5b5f5f602083850312156132ad575f5ffd5b823567ffffffffffffffff8111156132c3575f5ffd5b6132cf8582860161325e565b90969095509350505050565b5f5f604083850312156132ec575f5ffd5b50508035926020909101359150565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b5f82825180855260208501945060208160051b830101602085015f5b838110156133b3577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840301885261339d8383516132fb565b6020988901989093509190910190600101613363565b50909695505050505050565b604081525f6133d16040830185613347565b82810360208401528084518083526020830191506020860192505f5b818110156133b35783518352602093840193909201916001016133ed565b5f5f5f6040848603121561341d575f5ffd5b83359250602084013567ffffffffffffffff81111561343a575f5ffd5b6134468682870161325e565b9497909650939450505050565b602081525f610bef6020830184613347565b803573ffffffffffffffffffffffffffffffffffffffff81168114613488575f5ffd5b919050565b5f5f6040838503121561349e575f5ffd5b823591506134ae60208401613465565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561352b5761352b6134b7565b604052919050565b5f5f67ffffffffffffffff84111561354d5761354d6134b7565b50601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016602001613580816134e4565b915050828152838383011115613594575f5ffd5b828260208301375f602084830101529392505050565b5f5f604083850312156135bb575f5ffd5b6135c483613465565b9150602083013567ffffffffffffffff8111156135df575f5ffd5b8301601f810185136135ef575f5ffd5b6135fe85823560208401613533565b9150509250929050565b5f60208284031215613618575f5ffd5b5035919050565b5f5f5f5f5f60808688031215613633575f5ffd5b853594506020860135935060408601359250606086013567ffffffffffffffff81111561365e575f5ffd5b61366a8882890161325e565b969995985093965092949392505050565b5f5f6020838503121561368c575f5ffd5b823567ffffffffffffffff8111156136a2575f5ffd5b6132cf8582860161318c565b5f5f5f606084860312156136c0575f5ffd5b83359250602084013591506136d760408501613465565b90509250925092565b602080825282518282018190525f918401906040840190835b8181101561325357835173ffffffffffffffffffffffffffffffffffffffff168352602093840193909201916001016136f9565b602081525f610bef60208301846132fb565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b82811015613818578685037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0018452815180518087526020918201918088019190600582901b8901015f5b828110156137ff577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08a83030184526137ea8286516132fb565b602095860195949094019391506001016137b0565b5097505050602094850194929092019150600101613765565b50929695505050505050565b5f60208284031215613834575f5ffd5b610bef82613465565b5f5f5f5f60608587031215613850575f5ffd5b84359350602085013567ffffffffffffffff81111561386d575f5ffd5b8501601f8101871361387d575f5ffd5b803567ffffffffffffffff811115613897576138976134b7565b8060051b6138a7602082016134e4565b9182526020818401810192908101908a8411156138c2575f5ffd5b6020850192505b8383101561391b57823567ffffffffffffffff8111156138e7575f5ffd5b8501603f81018c136138f7575f5ffd5b6139098c602083013560408401613533565b835250602092830192909101906138c9565b96505050506040860135905067ffffffffffffffff81111561393b575f5ffd5b6132108782880161325e565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b818382375f9101908152919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8181038181111561086757610867613983565b600181811c908216806139d757607f821691505b602082108103613a0e577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b5f81518060208401855e5f93019283525090919050565b5f610bef8284613a14565b601f8211156126b557805f5260205f20601f840160051c81016020851015613a5b5750805b601f840160051c820191505b81811015612a6f575f8155600101613a67565b67ffffffffffffffff831115613a9257613a926134b7565b613aa683613aa083546139c3565b83613a36565b5f601f841160018114613af6575f8515613ac05750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355612a6f565b5f838152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08716915b82811015613b435786850135825560209485019460019092019101613b23565b5086821015613b7e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081525f613bea602083018486613b90565b949350505050565b818103613bfd575050565b613c0782546139c3565b67ffffffffffffffff811115613c1f57613c1f6134b7565b613c3381613c2d84546139c3565b84613a36565b5f601f821160018114613c86575f8315613c4d5750848201545b600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c198216175b855550612a6f565b5f85815260208082208683529082207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616925b83811015613cda5782860154825560019586019590910190602001613cba565b5085831015613d1657818501547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b8082018281125f831280158216821582161715613d7257613d72613983565b505092915050565b838152604060208201525f612fa4604083018486613b90565b604081525f613da6604083018587613b90565b9050826020830152949350505050565b5f5f83357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613de9575f5ffd5b83018035915067ffffffffffffffff821115613e03575f5ffd5b602001915036819003821315610ae9575f5ffd5b8082018082111561086757610867613983565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e5a57613e5a613983565b5060010190565b604081525f613e74604083018587613b90565b8281036020840152613e868185613347565b9695505050505050565b5f60208284031215613ea0575f5ffd5b5051919050565b5f8154613eb3816139c3565b600182168015613eca5760018114613efd57613f2a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083168652811515820286019350613f2a565b845f5260205f205f5b83811015613f2257815488820152600190910190602001613f06565b505081860193505b50505092915050565b5f610bef8284613ea7565b815167ffffffffffffffff811115613f5857613f586134b7565b613f6681613c2d84546139c3565b6020601f821160018114613fb5575f8315613c4d575081850151600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c19821617613c7e565b5f848152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08516915b828110156140025787850151825560209485019460019092019101613fe2565b508482101561403e57868401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b60f8161c191681555b50505050600190811b01905550565b5f8161405b5761405b613983565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019056fea26469706673582212208e3a1fc194745e7dfbc7f1f427b10b646db112f496dc8297b4a48b7b053bd15964736f6c634300081d0033","sourceMap":"340:41091:34:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4914:76;;;;;;;;;;;;4959:31;4914:76;;;;;160:25:38;;;148:2;133:18;4914:76:34;;;;;;;;39887:414;;;;;;;;;;-1:-1:-1;39887:414:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;35130:120::-;;;;;;;;;;-1:-1:-1;35130:120:34;;;;;:::i;:::-;;:::i;33815:1139::-;;;;;;;;;;-1:-1:-1;33815:1139:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;;:::i;35500:164::-;;;;;;;;;;-1:-1:-1;35500:164:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;15103:109::-;;;;;;;;;;-1:-1:-1;15103:109:34;;;;;:::i;:::-;;:::i;:::-;;36380:1119;;;;;;;;;;-1:-1:-1;36380:1119:34;;;;;:::i;:::-;;:::i;19876:381::-;;;;;;;;;;-1:-1:-1;19876:381:34;;;;;:::i;:::-;;:::i;24406:135::-;;;;;;;;;;;;;:::i;37893:101::-;;;;;;;;;;-1:-1:-1;37970:17:34;;37893:101;;24866:102;;;;;;;;;;-1:-1:-1;24944:10:34;:17;24866:102;;4161:214:20;;;;;;:::i;:::-;;:::i;35919:164:34:-;;;;;;;;;;-1:-1:-1;35919:164:34;;;;;:::i;:::-;;:::i;23809:475::-;;;;;;;;;;-1:-1:-1;23809:475:34;;;;;:::i;:::-;;:::i;38358:1193::-;;;;;;;;;;-1:-1:-1;38358:1193:34;;;;;:::i;:::-;;:::i;3708:134:20:-;;;;;;;;;;;;;:::i;17665:91:34:-;;;;;;;;;;-1:-1:-1;17736:13:34;;17665:91;;24653:98;;;;;;;;;;;;;:::i;4826:82::-;;;;;;;;;;;;4874:34;4826:82;;41125:304;;;;;;;;;;-1:-1:-1;41125:304:34;;;;;:::i;:::-;;:::i;17454:91::-;;;;;;;;;;-1:-1:-1;17499:7:34;17525:13;17454:91;;15840:128;;;;;;;;;;-1:-1:-1;15840:128:34;;;;;:::i;:::-;15909:4;15932:20;;;:14;:20;;;;;;;;:29;;;;;;;;;;;;;;;;15840:128;;;;9397:14:38;;9390:22;9372:41;;9360:2;9345:18;15840:128:34;9232:187:38;40663:242:34;;;;;;;;;;-1:-1:-1;40663:242:34;;;;;:::i;:::-;40807:4;40834:42;;;:29;:42;;;;;;;;:55;;;;;;;;;:64;;;;;;;;;;;;;;40663:242;20979:288;;;;;;;;;;-1:-1:-1;20979:288:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;1819:58:20:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;37637:93:34:-;;;;;;;;;;-1:-1:-1;37710:13:34;;37637:93;;20473:292;;;;;;;;;;-1:-1:-1;20473:292:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;13307:237::-;;;;;;;;;;-1:-1:-1;13307:237:34;;;;;:::i;:::-;;:::i;15439:175::-;;;;;;;;;;-1:-1:-1;15439:175:34;;;;;:::i;:::-;;:::i;23339:285::-;;;;;;;;;;-1:-1:-1;23339:285:34;;;;;:::i;:::-;;:::i;33373:131::-;;;;;;;;;;-1:-1:-1;33373:131:34;;;;;:::i;:::-;;:::i;18136:548::-;;;;;;;;;;;;;:::i;:::-;;;;13108:25:38;;;13164:2;13149:18;;13142:34;;;;13081:18;18136:548:34;12934:248:38;4760:60:34;;;;;;;;;;;;4797:23;4760:60;;17868:87;;;;;;;;;;-1:-1:-1;1722:1:34;17868:87;;27359:1916;;;;;;;;;;-1:-1:-1;27359:1916:34;;;;;:::i;:::-;;:::i;39887:414::-;40034:15;40065:23;40104:8;40091:29;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;40091:29:34;-1:-1:-1;40065:55:34;-1:-1:-1;40135:9:34;40130:141;40150:19;;;40130:141;;;40203:31;;;;:18;:31;;;;;;;;:44;;;;;;;;;40248:8;;40257:1;40248:11;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;40203:57;;;;;;;;;;;;;;;;40190:7;40198:1;40190:10;;;;;;;;:::i;:::-;;;;;;;;;;:70;40171:3;;40130:141;;;-1:-1:-1;40287:7:34;39887:414;-1:-1:-1;;;;;39887:414:34:o;35130:120::-;35199:7;35225:10;35236:6;;35225:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;35218:25;;35130:120;;;;;:::o;33815:1139::-;33916:23;33941:27;34049:3;34040:5;:12;;34032:76;;;;;;;15517:2:38;34032:76:34;;;15499:21:38;15556:2;15536:18;;;15529:30;15595:34;15575:18;;;15568:62;15666:21;15646:18;;;15639:49;15705:19;;34032:76:34;;;;;;;;;34194:10;:17;34188:23;;34184:77;;;34233:10;:17;;-1:-1:-1;34184:77:34;34350:10;:17;34342:25;;34338:81;;;34391:10;:17;;-1:-1:-1;34338:81:34;34483:14;34500:11;34506:5;34500:3;:11;:::i;:::-;34483:28;;34544:6;34531:20;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;34521:30;;34588:6;34574:21;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;34574:21:34;-1:-1:-1;34561:34:34;-1:-1:-1;34650:5:34;34633:276;34661:3;34657:1;:7;34633:276;;;34685:13;34701:9;34705:5;34701:1;:9;:::i;:::-;34685:25;;34760:22;34785:10;34796:1;34785:13;;;;;;;;:::i;:::-;;;;;;;;34760:38;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;34830:8;34813:7;34821:5;34813:14;;;;;;;;:::i;:::-;;;;;;:25;;;;34872:16;34889:8;34872:26;;;;;;:::i;:::-;;;;;;;;;;;;;;34852:10;34863:5;34852:17;;;;;;;;:::i;:::-;;;;;;;;;;:46;-1:-1:-1;;34666:3:34;;34633:276;;;;34919:28;33815:1139;;;;;;:::o;35500:164::-;35591:15;35625:11;:24;35637:11;35625:24;;;;;;;;;;;35650:6;;35625:32;;;;;;;:::i;:::-;;;;;;;;;;;;;35618:39;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;35500:164;;;;;;:::o;15103:109::-;10709:10;10682:26;:38;;;:26;;:38;:26;:38;;;;;10674:60;;;;;;;;;;;;;;;;;15180:25:::1;15191:4;15197:7;15180:10;:25::i;:::-;15103:109:::0;;:::o;36380:1119::-;36482:23;36507:21;36609:3;36600:5;:12;;36592:76;;;;;;;15517:2:38;36592:76:34;;;15499:21:38;15556:2;15536:18;;;15529:30;15595:34;15575:18;;;15568:62;15666:21;15646:18;;;15639:49;15705:19;;36592:76:34;15315:415:38;36592:76:34;36754:11;:18;36748:24;;36744:79;;;36794:11;:18;;-1:-1:-1;36744:79:34;36912:11;:18;36904:26;;36900:83;;;36954:11;:18;;-1:-1:-1;36900:83:34;37047:14;37064:11;37070:5;37064:3;:11;:::i;:::-;37047:28;;37108:6;37095:20;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;37085:30;;37146:6;37132:21;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;37132:21:34;-1:-1:-1;37125:28:34;-1:-1:-1;37208:5:34;37191:269;37219:3;37215:1;:7;37191:269;;;37243:13;37259:9;37263:5;37259:1;:9;:::i;:::-;37243:25;;37319:23;37345:11;37357:1;37345:14;;;;;;;;:::i;:::-;;;;;;;;37319:40;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;37391:9;37374:7;37382:5;37374:14;;;;;;;;:::i;:::-;;;;;;:26;;;;37428:10;37439:9;37428:21;;;;;;:::i;:::-;;;;;;;;;;;;;;37414:4;37419:5;37414:11;;;;;;;;:::i;:::-;;;;;;;;;;:35;-1:-1:-1;;37224:3:34;;37191:269;;19876:381;20034:20;;19955:10;;19941:11;;20034:12;;:20;;20047:6;;;;20034:20;:::i;:::-;;;;;;;;;;;;;;;;;:34;20030:72;;20077:25;;;;;;;;;;;;;;20030:72;20141:17;;;;;;;:12;:17;;;;;;;:30;;;;;;;;;;;;;;20164:6;;20141:30;;:::i;:::-;;20204:3;20181:12;20194:6;;20181:20;;;;;;;:::i;:::-;;;;;;;;;;;;;;:26;;;;;;;;;;;;;20223:27;;;;;;;;20243:6;;;;20223:27;:::i;:::-;;;;;;;;19931:326;19876:381;;:::o;24406:135::-;11045:10;11007:37;:49;;;:37;;:49;:37;:49;;;;;10999:81;;;;;;;;;;;;;;;;;24471:17:::1;24478:10;;24471:17;:::i;:::-;24503:31;::::0;24523:10:::1;::::0;24503:31:::1;::::0;;;::::1;24406:135::o:0;4161:214:20:-;2655:13;:11;:13::i;:::-;4276:36:::1;4294:17;4276;:36::i;:::-;4322:46;4344:17;4363:4;4322:21;:46::i;35919:164:34:-:0;36013:7;36039:29;;;:16;:29;;;;;;:37;;;;36069:6;;;;36039:37;:::i;:::-;;;;;;;;;;;;;;36032:44;;35919:164;;;;;:::o;23809:475::-;11045:10;11007:37;:49;;;:37;;:49;:37;:49;;;;;10999:81;;;;;;;;;;;;;;;;;23900:10:::1;:17:::0;23891:26;::::1;23887:61;;23926:22;;;;;;;;;;;;;;23887:61;24065:10;:17:::0;:21:::1;::::0;24085:1:::1;::::0;24065:21:::1;:::i;:::-;24057:5;:29;24053:113;;;24122:10;24133:17:::0;;:21:::1;::::0;24153:1:::1;::::0;24133:21:::1;:::i;:::-;24122:33;;;;;;;;:::i;:::-;;;;;;;;24102:10;24113:5;24102:17;;;;;;;;:::i;:::-;;;;;;;;:53;;;;;;:::i;:::-;;24053:113;24211:10;:16;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;::::0;;24243:34:::1;::::0;160:25:38;;;24259:10:34::1;::::0;24243:34:::1;::::0;148:2:38;133:18;24243:34:34::1;;;;;;;23809:475:::0;:::o;38358:1193::-;38586:13;;38572:11;:27;38568:60;;;38608:20;;;;;;;;;;;;;;38568:60;38731:13;;38717:11;:27;38713:60;;;38753:20;;;;;;;;;;;;;;38713:60;38871:42;;;;:29;:42;;;;;;;;:55;;;;;;;;38927:10;38871:67;;;;;;;;;;38867:104;;;38947:24;;;;;;;;;;;;;;38867:104;39064:10;39040:34;;:12;39053:6;;39040:20;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:34;39036:67;;39083:20;;;;;;;;;;;;;;39036:67;39143:31;;;;:18;:31;;;;;;;;:44;;;;;;;;39188:10;39143:56;;;;;;;;;:65;;;39218:42;;;:29;:42;;;;;:55;;;;;;;;:67;;;;;;;;;;:74;;;;39288:4;39218:74;;;39346:21;39202:6;;39346:13;;:21;;39360:6;;;;39346:21;:::i;:::-;;;;;;;;;;;;;;:31;;;;;;;:::i;:::-;;;;;;;;39434:11;39421;39409:10;39393:69;;;39447:6;39455;;39393:69;;;;;;;;:::i;:::-;;;;;;;;39502:10;39477:67;;;39514:6;;39522:13;39536:6;;39522:21;;;;;;;:::i;:::-;;;;;;;;;;;;;;;39477:67;;;;;:::i;:::-;;;;;;;;38358:1193;;;;;:::o;3708:134:20:-;3777:7;2926:20;:18;:20::i;:::-;-1:-1:-1;811:66:24::1;3708:134:20::0;:::o;24653:98:34:-;24700:15;24734:10;24727:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;24653:98;:::o;41125:304::-;41200:15;41227:23;41266:7;41253:28;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;41253:28:34;-1:-1:-1;41227:54:34;-1:-1:-1;41296:9:34;41291:108;41311:18;;;41291:108;;;41363:13;41377:7;;41385:1;41377:10;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;41363:25;;;;;;;:::i;:::-;;;;;;;;;;;;;;41350:7;41358:1;41350:10;;;;;;;;:::i;:::-;;;;;;;;;;:38;41331:3;;41291:108;;;-1:-1:-1;41415:7:34;41125:304;-1:-1:-1;;;41125:304:34:o;20979:288::-;21045:16;21073:21;21111:7;21097:29;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;21097:29:34;-1:-1:-1;21073:53:34;-1:-1:-1;21141:9:34;21136:104;21156:18;;;21136:104;;;21205:12;21218:7;;21226:1;21218:10;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;21205:24;;;;;;;:::i;:::-;;;;;;;;;;;;;;;21195:7;;21205:24;;;;;21195:4;;21200:1;;21195:7;;;;;;:::i;:::-;:34;;;;:7;;;;;;;;;;;:34;21176:3;;21136:104;;20473:292;20540:17;20569:25;20612:4;20597:27;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;20569:55:34;-1:-1:-1;20639:9:34;20634:101;20654:15;;;20634:101;;;20703:12;:21;20716:4;;20721:1;20716:7;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;20703:21;;;;;;;;;;;;;;;20690:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:7;20698:1;20690:10;;;;;;;;:::i;:::-;;;;;;;;;;:34;20671:3;;20634:101;;13307:237;8870:21:19;4302:15;;;;;;;4301:16;;4348:14;;4158:30;4726:16;;:34;;;;;4746:14;4726:34;4706:54;;4770:17;4790:11;:16;;4805:1;4790:16;:50;;;;-1:-1:-1;4818:4:19;4810:25;:30;4790:50;4770:70;;4856:12;4855:13;:30;;;;;4873:12;4872:13;4855:30;4851:91;;;4908:23;;;;;;;;;;;;;;4851:91;4951:18;;;;4968:1;4951:18;;;4979:67;;;;5013:22;;;;;;;;4979:67;13374:30:34::1;4797:23;13397:6;13374:10;:30::i;:::-;13414:38;4959:31;13445:6;13414:10;:38::i;:::-;13462:41;4874:34;13496:6;13462:10;:41::i;:::-;13513:24;:22;:24::i;:::-;5070:14:19::0;5066:101;;;5100:23;;;;;;5142:14;;-1:-1:-1;23010:50:38;;5142:14:19;;22998:2:38;22983:18;5142:14:19;;;;;;;5066:101;4092:1081;;;;;13307:237:34;:::o;15439:175::-;10709:10;10682:26;:38;;;:26;;:38;:26;:38;;;;;10674:60;;;;;;;;;;;;;;;;;15549:5:::1;15517:20:::0;;;:14:::1;:20;::::0;;;;;;;:29:::1;::::0;::::1;::::0;;;;;;;;:37;;;::::1;::::0;;15569:38;15596:10:::1;::::0;15532:4;;15569:38:::1;::::0;15549:5;15569:38:::1;15439:175:::0;;:::o;23339:285::-;11045:10;11007:37;:49;;;:37;;:49;:37;:49;;;;;10999:81;;;;;;;;;;;;;;;;;23448:12;23432:13:::1;23477:93;23501:5;23497:1;:9;23477:93;;;23527:10;23543:12;;23556:1;23543:15;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;23527:32:::0;;::::1;::::0;::::1;::::0;;-1:-1:-1;23527:32:34;;;::::1;::::0;;;;;::::1;::::0;::::1;::::0;;::::1;:::i;:::-;-1:-1:-1::0;23508:3:34::1;;23477:93;;;-1:-1:-1::0;23584:33:34::1;::::0;160:25:38;;;23599:10:34::1;::::0;23584:33:::1;::::0;148:2:38;133:18;23584:33:34::1;14:177:38::0;33373:131:34;33447:7;33473:16;33490:6;;33473:24;;;;;;;:::i;18136:548::-;10869:10;18202:7;10834:46;;;:34;;:46;:34;:46;;;18202:7;;10834:46;;10826:75;;;;;;;;;;;;;;;;;1722:1:::1;18234:13;;18250:1;18234:17;;;;:::i;:::-;:32;18230:341;;18351:13;:15:::0;;;:13;:15:::1;::::0;::::1;:::i;:::-;::::0;;;-1:-1:-1;;18396:1:34::1;18380:13;:17:::0;;;18430:13;;18416:28:::1;::::0;18430:13;;18416:28:::1;::::0;::::1;18230:341;;;18543:13;::::0;;:17:::1;::::0;::::1;:::i;:::-;18527:13;:33:::0;18230:341:::1;18600:13;;18586:43;18615:13;;18586:43;;;;160:25:38::0;;148:2;133:18;;14:177;18586:43:34::1;;;;;;;;-1:-1:-1::0;;18648:13:34::1;::::0;18663::::1;::::0;18136:548;;:::o;27359:1916::-;27577:13;;27563:11;:27;27559:60;;;27599:20;;;;;;;;;;;;;;27559:60;27721:1;27679:24;;;:11;:24;;;;;;:32;;;;27704:6;;;;27679:32;:::i;:::-;;;;;;;;;;;;;;:39;:43;27675:76;;;27731:20;;;;;;;;;;;;;;27675:76;27844:10;27820:34;;:12;27833:6;;27820:20;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:34;27816:67;;27863:20;;;;;;;;;;;;;;27816:67;27938:9;27933:277;27957:7;:14;27953:1;:18;27933:277;;;27997:9;28009:5;:1;28013;28009:5;:::i;:::-;27997:17;;27992:208;28020:7;:14;28016:1;:18;27992:208;;;28111:7;28119:1;28111:10;;;;;;;;:::i;:::-;;;;;;;28095:28;;;;;;28079:7;28087:1;28079:10;;;;;;;;:::i;:::-;;;;;;;28063:28;;;;;;:60;28059:127;;28154:13;;;;;;;;;;;;;;28059:127;28036:3;;27992:208;;;-1:-1:-1;27973:3:34;;27933:277;;;;28306:16;28323:6;;28306:24;;;;;;;:::i;:::-;;;;;;;;;;;;;;28334:1;28306:29;28302:75;;28351:13;:15;;;:13;:15;;;:::i;:::-;;;;;;28302:75;28449:7;28414:11;:24;28426:11;28414:24;;;;;;;;;;;28439:6;;28414:32;;;;;;;:::i;:::-;;;;;;;;;;;;;:42;;;;;;;;;;;;:::i;:::-;-1:-1:-1;28531:9:34;28526:353;28550:7;:14;28546:1;:18;28526:353;;;28585:29;;;;:16;:29;;;;;28615:10;;:7;;28623:1;;28615:10;;;;;;:::i;:::-;;;;;;;28585:41;;;;;;:::i;:::-;;;;;;;;;;;;;;:43;;;:41;:43;;;:::i;:::-;;;;;;28736:15;28752:7;28760:1;28752:10;;;;;;;;:::i;:::-;;;;;;;28736:27;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;28731:138;;28813:4;28783:15;28799:7;28807:1;28799:10;;;;;;;;:::i;:::-;;;;;;;28783:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;;;;;;;;;;;;;;;28835:17;:19;;;28783:27;28835:19;;;:::i;:::-;;;;;;28731:138;28566:3;;28526:353;;;;28943:16;28960:6;;28943:24;;;;;;;:::i;:::-;;;;;;;;;;;;;;:26;;;:24;:26;;;:::i;:::-;;;;;;28979:24;28996:6;;28979:24;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;28979:16:34;;-1:-1:-1;;;28979:24:34:i;:::-;29064:9;29059:137;29083:7;:14;29079:1;:18;29059:137;;;29118:10;29129:7;29137:1;29129:10;;;;;;;;:::i;:::-;;;;;;;29118:22;;;;;;:::i;:::-;;;;;;;;;;;;;;:24;;;:22;:24;;;:::i;:::-;;;;;;29156:29;29174:7;29182:1;29174:10;;;;;;;;:::i;:::-;;;;;;;29156:17;:29::i;:::-;29099:3;;29059:137;;;;29247:11;29227:10;29211:57;;;29239:6;;29260:7;29211:57;;;;;;;;:::i;:::-;;;;;;;;27359:1916;;;;:::o;14717:166::-;14787:20;;;;:14;:20;;;;;;;;:29;;;;;;;;;;;:36;;;;14819:4;14787:36;;;14838:38;14865:10;;14802:4;;14838:38;;14787:20;14838:38;14717:166;;:::o;4603:312:20:-;4683:4;4675:23;4692:6;4675:23;;;:120;;;4789:6;4753:42;;:32;811:66:24;1519:53;;;;1441:138;4753:32:20;:42;;;;4675:120;4658:251;;;4869:29;;;;;;;;;;;;;;4658:251;4603:312::o;13550:125:34:-;10709:10;10682:26;:38;;;:26;;:38;:26;:38;;;;;10674:60;;;;;;;;;;;;;;;;;13550:125;:::o;6057:538:20:-;6174:17;6156:50;;;:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;6156:52:20;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;6152:437;;6518:60;;;;;24241:42:38;24229:55;;6518:60:20;;;24211:74:38;24184:18;;6518:60:20;24065:226:38;6152:437:20;811:66:24;6250:40:20;;6246:120;;6317:34;;;;;;;;160:25:38;;;133:18;;6317:34:20;14:177:38;6246:120:20;6379:54;6409:17;6428:4;6379:29;:54::i;:::-;6209:235;6057:538;;:::o;5032:213::-;5106:4;5098:23;5115:6;5098:23;;5094:145;;5199:29;;;;;;;;;;;;;;2970:67;6931:20:19;:18;:20::i;29422:1806:34:-;29488:18;29509:16;29526:5;29509:23;;;;;;:::i;:::-;;;;;;;;;;;;;;;29667:10;:17;29509:23;;-1:-1:-1;29614:17:34;;29591:20;29694:206;29718:15;29714:1;:19;29694:206;;;29809:5;29793:23;;;;;;29774:10;29785:1;29774:13;;;;;;;;:::i;:::-;;;;;;;;29758:31;;;;;;:::i;:::-;;;;;;;;:58;29754:136;;29851:1;29836:16;;29870:5;;29754:136;29735:3;;29694:206;;;;29930:17;29914:12;:33;29910:791;;2232:3;30007:15;:33;30003:688;;;30108:10;:22;;;;;;;-1:-1:-1;30108:22:34;;;;;;;30124:5;30108:22;;:::i;:::-;-1:-1:-1;30148:17:34;;;;:::i;:::-;;-1:-1:-1;30198:19:34;;-1:-1:-1;30216:1:34;30148:17;30198:19;:::i;:::-;30183:34;;30003:688;;;30376:10;30324:16;30341:10;30352:19;30370:1;30352:15;:19;:::i;:::-;30341:31;;;;;;;;:::i;:::-;;;;;;;;30324:49;;;;;;:::i;:::-;;;;;;;;;;;;;;:62;30320:357;;;30486:5;30452:10;30463:19;30481:1;30463:15;:19;:::i;:::-;30452:31;;;;;;;;:::i;:::-;;;;;;;;:39;;;;;;:::i;:::-;-1:-1:-1;30528:19:34;30546:1;30528:15;:19;:::i;30320:357::-;30652:7;;;29422:1806;:::o;30320:357::-;30803:12;30825:127;30847:1;30832:12;:16;:79;;;;-1:-1:-1;30901:10:34;30852:16;30869:10;30880:16;30895:1;30880:12;:16;:::i;:::-;30869:28;;;;;;;;:::i;:::-;;;;;;;;30852:46;;;;;;:::i;:::-;;;;;;;;;;;;;;:59;30832:79;30825:127;;;30927:14;;;;:::i;:::-;;;;30825:127;;;31028:12;31012;:28;31008:214;;31056:18;31077:10;31088:12;31077:24;;;;;;;;:::i;:::-;;;;;;;;31056:45;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;31142:10;31153:12;31142:24;;;;;;;;:::i;:::-;;;;;;;;31115:10;31126:12;31115:24;;;;;;;;:::i;:::-;;;;;;;;:51;;;;;;:::i;:::-;;31207:4;31180:10;31191:12;31180:24;;;;;;;;:::i;:::-;;;;;;;;:31;;;;;;:::i;:::-;;31042:180;31008:214;29478:1750;;;;29422:1806;:::o;31378:1817::-;31446:18;31467:10;31478:6;31467:18;;;;;;:::i;:::-;;;;;;;;;;;;;;;31622:11;:18;31467;;-1:-1:-1;31568:17:34;;31545:20;31650:209;31674:16;31670:1;:20;31650:209;;;31767:6;31751:24;;;;;;31731:11;31743:1;31731:14;;;;;;;;:::i;:::-;;;;;;;;31715:32;;;;;;:::i;:::-;;;;;;;;:60;31711:138;;31810:1;31795:16;;31829:5;;31711:138;31692:3;;31650:209;;;;31889:17;31873:12;:33;31869:799;;2232:3;31967:16;:34;31963:695;;;32069:11;:24;;;;;;;-1:-1:-1;32069:24:34;;;;;;;32086:6;32069:24;;:::i;:::-;-1:-1:-1;32111:18:34;;;;:::i;:::-;;-1:-1:-1;32162:20:34;;-1:-1:-1;32181:1:34;32111:18;32162:20;:::i;:::-;32147:35;;31963:695;;;32338:10;32290;32301:11;32313:20;32332:1;32313:16;:20;:::i;:::-;32301:33;;;;;;;;:::i;:::-;;;;;;;;32290:45;;;;;;:::i;:::-;;;;;;;;;;;;;;:58;32286:358;;;32450:6;32414:11;32426:20;32445:1;32426:16;:20;:::i;:::-;32414:33;;;;;;;;:::i;:::-;;;;;;;;:42;;;;;;:::i;:::-;-1:-1:-1;32493:20:34;32512:1;32493:16;:20;:::i;32286:358::-;32770:12;32792:122;32814:1;32799:12;:16;:74;;;;-1:-1:-1;32863:10:34;32819;32830:11;32842:16;32857:1;32842:12;:16;:::i;:::-;32830:29;;;;;;;;:::i;:::-;;;;;;;;32819:41;;;;;;:::i;:::-;;;;;;;;;;;;;;:54;32799:74;32792:122;;;32889:14;;;;:::i;:::-;;;;32792:122;;;32991:12;32975;:28;32971:218;;33019:18;33040:11;33052:12;33040:25;;;;;;;;:::i;:::-;;;;;;;;33019:46;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;33107:11;33119:12;33107:25;;;;;;;;:::i;:::-;;;;;;;;33079:11;33091:12;33079:25;;;;;;;;:::i;:::-;;;;;;;;:53;;;;;;:::i;:::-;;33174:4;33146:11;33158:12;33146:25;;;;;;;;:::i;2264:344:24:-;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;;;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;2454:148::-;2573:18;:16;:18::i;7084:141:19:-;8870:21;8560:40;;;;;;7146:73;;7191:17;;;;;;;;;;;;;;1671:281:24;1748:17;:29;;;1781:1;1748:34;1744:119;;1805:47;;;;;24241:42:38;24229:55;;1805:47:24;;;24211:74:38;24184:18;;1805:47:24;24065:226:38;1744:119:24;811:66;1872:73;;;;;;;;;;;;;;;1671:281::o;3916:253:28:-;3999:12;4024;4038:23;4065:6;:19;;4085:4;4065:25;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4023:67;;;;4107:55;4134:6;4142:7;4151:10;4107:26;:55::i;:::-;4100:62;3916:253;-1:-1:-1;;;;;3916:253:28:o;6113:122:24:-;6163:9;:13;6159:70;;6199:19;;;;;;;;;;;;;;4437:582:28;4581:12;4610:7;4605:408;;4633:19;4641:10;4633:7;:19::i;:::-;4605:408;;;4857:17;;:22;:49;;;;-1:-1:-1;4883:18:28;;;;:23;4857:49;4853:119;;;4933:24;;;;;24241:42:38;24229:55;;4933:24:28;;;24211:74:38;24184:18;;4933:24:28;24065:226:38;4853:119:28;-1:-1:-1;4992:10:28;4985:17;;5559:487;5690:17;;:21;5686:354;;5887:10;5881:17;5943:15;5930:10;5926:2;5922:19;5915:44;5686:354;6010:19;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;196:367:38;259:8;269:6;323:3;316:4;308:6;304:17;300:27;290:55;;341:1;338;331:12;290:55;-1:-1:-1;364:20:38;;407:18;396:30;;393:50;;;439:1;436;429:12;393:50;476:4;468:6;464:17;452:29;;536:3;529:4;519:6;516:1;512:14;504:6;500:27;496:38;493:47;490:67;;;553:1;550;543:12;568:671;672:6;680;688;696;749:2;737:9;728:7;724:23;720:32;717:52;;;765:1;762;755:12;717:52;810:23;;;-1:-1:-1;930:2:38;915:18;;902:32;;-1:-1:-1;1011:2:38;996:18;;983:32;1038:18;1027:30;;1024:50;;;1070:1;1067;1060:12;1024:50;1109:70;1171:7;1162:6;1151:9;1147:22;1109:70;:::i;:::-;568:671;;;;-1:-1:-1;1198:8:38;-1:-1:-1;;;;568:671:38:o;1244:609::-;1432:2;1444:21;;;1514:13;;1417:18;;;1536:22;;;1384:4;;1615:15;;;1589:2;1574:18;;;1384:4;1658:169;1672:6;1669:1;1666:13;1658:169;;;1733:13;;1721:26;;1776:2;1802:15;;;;1767:12;;;;1694:1;1687:9;1658:169;;;-1:-1:-1;1844:3:38;;1244:609;-1:-1:-1;;;;;1244:609:38:o;1858:348::-;1910:8;1920:6;1974:3;1967:4;1959:6;1955:17;1951:27;1941:55;;1992:1;1989;1982:12;1941:55;-1:-1:-1;2015:20:38;;2058:18;2047:30;;2044:50;;;2090:1;2087;2080:12;2044:50;2127:4;2119:6;2115:17;2103:29;;2179:3;2172:4;2163:6;2155;2151:19;2147:30;2144:39;2141:59;;;2196:1;2193;2186:12;2211:411;2282:6;2290;2343:2;2331:9;2322:7;2318:23;2314:32;2311:52;;;2359:1;2356;2349:12;2311:52;2399:9;2386:23;2432:18;2424:6;2421:30;2418:50;;;2464:1;2461;2454:12;2418:50;2503:59;2554:7;2545:6;2534:9;2530:22;2503:59;:::i;:::-;2581:8;;2477:85;;-1:-1:-1;2211:411:38;-1:-1:-1;;;;2211:411:38:o;2809:346::-;2877:6;2885;2938:2;2926:9;2917:7;2913:23;2909:32;2906:52;;;2954:1;2951;2944:12;2906:52;-1:-1:-1;;2999:23:38;;;3119:2;3104:18;;;3091:32;;-1:-1:-1;2809:346:38:o;3160:359::-;3213:3;3251:5;3245:12;3278:6;3273:3;3266:19;3334:6;3327:4;3320:5;3316:16;3309:4;3304:3;3300:14;3294:47;3386:1;3379:4;3370:6;3365:3;3361:16;3357:27;3350:38;3508:4;3438:66;3433:2;3425:6;3421:15;3417:88;3412:3;3408:98;3404:109;3397:116;;;3160:359;;;;:::o;3524:649::-;3576:3;3607;3639:5;3633:12;3666:6;3661:3;3654:19;3698:4;3693:3;3689:14;3682:21;;3756:4;3746:6;3743:1;3739:14;3732:5;3728:26;3724:37;3795:4;3788:5;3784:16;3818:1;3828:319;3842:6;3839:1;3836:13;3828:319;;;3925:66;3917:5;3911:4;3907:16;3903:89;3898:3;3891:102;4014:49;4058:4;4049:6;4043:13;4014:49;:::i;:::-;4098:4;4123:14;;;;4006:57;;-1:-1:-1;4086:17:38;;;;;3864:1;3857:9;3828:319;;;-1:-1:-1;4163:4:38;;3524:649;-1:-1:-1;;;;;;3524:649:38:o;4178:802::-;4455:2;4444:9;4437:21;4418:4;4481:55;4532:2;4521:9;4517:18;4509:6;4481:55;:::i;:::-;4584:9;4576:6;4572:22;4567:2;4556:9;4552:18;4545:50;4615:6;4650;4644:13;4681:6;4673;4666:22;4716:2;4708:6;4704:15;4697:22;;4754:2;4746:6;4742:15;4728:29;;4775:1;4785:169;4799:6;4796:1;4793:13;4785:169;;;4860:13;;4848:26;;4903:2;4929:15;;;;4894:12;;;;4821:1;4814:9;4785:169;;4985:525;5065:6;5073;5081;5134:2;5122:9;5113:7;5109:23;5105:32;5102:52;;;5150:1;5147;5140:12;5102:52;5195:23;;;-1:-1:-1;5293:2:38;5278:18;;5265:32;5320:18;5309:30;;5306:50;;;5352:1;5349;5342:12;5306:50;5391:59;5442:7;5433:6;5422:9;5418:22;5391:59;:::i;:::-;4985:525;;5469:8;;-1:-1:-1;5365:85:38;;-1:-1:-1;;;;4985:525:38:o;5515:280::-;5714:2;5703:9;5696:21;5677:4;5734:55;5785:2;5774:9;5770:18;5762:6;5734:55;:::i;5800:196::-;5868:20;;5928:42;5917:54;;5907:65;;5897:93;;5986:1;5983;5976:12;5897:93;5800:196;;;:::o;6001:254::-;6069:6;6077;6130:2;6118:9;6109:7;6105:23;6101:32;6098:52;;;6146:1;6143;6136:12;6098:52;6182:9;6169:23;6159:33;;6211:38;6245:2;6234:9;6230:18;6211:38;:::i;:::-;6201:48;;6001:254;;;;;:::o;6260:184::-;6312:77;6309:1;6302:88;6409:4;6406:1;6399:15;6433:4;6430:1;6423:15;6449:334;6520:2;6514:9;6576:2;6566:13;;6581:66;6562:86;6550:99;;6679:18;6664:34;;6700:22;;;6661:62;6658:88;;;6726:18;;:::i;:::-;6762:2;6755:22;6449:334;;-1:-1:-1;6449:334:38:o;6788:508::-;6852:5;6884:1;6908:18;6900:6;6897:30;6894:56;;;6930:18;;:::i;:::-;-1:-1:-1;6987:2:38;6975:15;;6992:66;6971:88;7061:4;6967:99;7084:21;6967:99;7084:21;:::i;:::-;7075:30;;;7128:6;7121:5;7114:21;7168:3;7159:6;7154:3;7150:16;7147:25;7144:45;;;7185:1;7182;7175:12;7144:45;7234:6;7229:3;7222:4;7215:5;7211:16;7198:43;7288:1;7281:4;7272:6;7265:5;7261:18;7257:29;7250:40;6788:508;;;;;:::o;7301:523::-;7378:6;7386;7439:2;7427:9;7418:7;7414:23;7410:32;7407:52;;;7455:1;7452;7445:12;7407:52;7478:29;7497:9;7478:29;:::i;:::-;7468:39;;7558:2;7547:9;7543:18;7530:32;7585:18;7577:6;7574:30;7571:50;;;7617:1;7614;7607:12;7571:50;7640:22;;7693:4;7685:13;;7681:27;-1:-1:-1;7671:55:38;;7722:1;7719;7712:12;7671:55;7745:73;7810:7;7805:2;7792:16;7787:2;7783;7779:11;7745:73;:::i;:::-;7735:83;;;7301:523;;;;;:::o;7829:226::-;7888:6;7941:2;7929:9;7920:7;7916:23;7912:32;7909:52;;;7957:1;7954;7947:12;7909:52;-1:-1:-1;8002:23:38;;7829:226;-1:-1:-1;7829:226:38:o;8060:713::-;8157:6;8165;8173;8181;8189;8242:3;8230:9;8221:7;8217:23;8213:33;8210:53;;;8259:1;8256;8249:12;8210:53;8304:23;;;-1:-1:-1;8424:2:38;8409:18;;8396:32;;-1:-1:-1;8501:2:38;8486:18;;8473:32;;-1:-1:-1;8556:2:38;8541:18;;8528:32;8583:18;8572:30;;8569:50;;;8615:1;8612;8605:12;8569:50;8654:59;8705:7;8696:6;8685:9;8681:22;8654:59;:::i;:::-;8060:713;;;;-1:-1:-1;8060:713:38;;-1:-1:-1;8732:8:38;;8628:85;8060:713;-1:-1:-1;;;8060:713:38:o;8778:449::-;8876:6;8884;8937:2;8925:9;8916:7;8912:23;8908:32;8905:52;;;8953:1;8950;8943:12;8905:52;8993:9;8980:23;9026:18;9018:6;9015:30;9012:50;;;9058:1;9055;9048:12;9012:50;9097:70;9159:7;9150:6;9139:9;9135:22;9097:70;:::i;9424:420::-;9501:6;9509;9517;9570:2;9558:9;9549:7;9545:23;9541:32;9538:52;;;9586:1;9583;9576:12;9538:52;9631:23;;;-1:-1:-1;9751:2:38;9736:18;;9723:32;;-1:-1:-1;9800:38:38;9834:2;9819:18;;9800:38;:::i;:::-;9790:48;;9424:420;;;;;:::o;9849:660::-;10039:2;10051:21;;;10121:13;;10024:18;;;10143:22;;;9991:4;;10222:15;;;10196:2;10181:18;;;9991:4;10265:218;10279:6;10276:1;10273:13;10265:218;;;10344:13;;10359:42;10340:62;10328:75;;10432:2;10458:15;;;;10423:12;;;;10301:1;10294:9;10265:218;;10514:231;10663:2;10652:9;10645:21;10626:4;10683:56;10735:2;10724:9;10720:18;10712:6;10683:56;:::i;11192:1546::-;11404:4;11452:2;11441:9;11437:18;11482:2;11471:9;11464:21;11505:6;11540;11534:13;11571:6;11563;11556:22;11609:2;11598:9;11594:18;11587:25;;11671:2;11661:6;11658:1;11654:14;11643:9;11639:30;11635:39;11621:53;;11709:2;11701:6;11697:15;11730:1;11740:969;11754:6;11751:1;11748:13;11740:969;;;11819:22;;;11843:66;11815:95;11803:108;;11934:13;;12008:9;;12030:24;;;12088:2;12185:11;;;;12076:15;;;;12008:9;12138:1;12134:16;;;12122:29;;12118:38;12220:1;12234:366;12250:8;12245:3;12242:17;12234:366;;;12352:66;12343:6;12335;12331:19;12327:92;12320:5;12313:107;12447:53;12493:6;12482:8;12476:15;12447:53;:::i;:::-;12543:2;12529:17;;;;12572:14;;;;;12437:63;-1:-1:-1;12278:1:38;12269:11;12234:366;;;-1:-1:-1;12623:6:38;-1:-1:-1;;;12664:2:38;12687:12;;;;12652:15;;;;;-1:-1:-1;11776:1:38;11769:9;11740:969;;;-1:-1:-1;12726:6:38;;11192:1546;-1:-1:-1;;;;;;11192:1546:38:o;12743:186::-;12802:6;12855:2;12843:9;12834:7;12830:23;12826:32;12823:52;;;12871:1;12868;12861:12;12823:52;12894:29;12913:9;12894:29;:::i;13187:1656::-;13311:6;13319;13327;13335;13388:2;13376:9;13367:7;13363:23;13359:32;13356:52;;;13404:1;13401;13394:12;13356:52;13449:23;;;-1:-1:-1;13547:2:38;13532:18;;13519:32;13574:18;13563:30;;13560:50;;;13606:1;13603;13596:12;13560:50;13629:22;;13682:4;13674:13;;13670:27;-1:-1:-1;13660:55:38;;13711:1;13708;13701:12;13660:55;13751:2;13738:16;13777:18;13769:6;13766:30;13763:56;;;13799:18;;:::i;:::-;13845:6;13842:1;13838:14;13872:28;13896:2;13892;13888:11;13872:28;:::i;:::-;13934:19;;;13978:2;14008:11;;;14004:20;;;13969:12;;;;14036:19;;;14033:39;;;14068:1;14065;14058:12;14033:39;14100:2;14096;14092:11;14081:22;;14112:433;14128:6;14123:3;14120:15;14112:433;;;14214:3;14201:17;14250:18;14237:11;14234:35;14231:55;;;14282:1;14279;14272:12;14231:55;14309:20;;14364:2;14356:11;;14352:25;-1:-1:-1;14342:53:38;;14391:1;14388;14381:12;14342:53;14420:82;14494:7;14488:2;14484;14480:11;14467:25;14462:2;14458;14454:11;14420:82;:::i;:::-;14408:95;;-1:-1:-1;14532:2:38;14145:12;;;;14523;;;;14112:433;;;14564:5;-1:-1:-1;;;;14622:2:38;14607:18;;14594:32;;-1:-1:-1;14651:18:38;14638:32;;14635:52;;;14683:1;14680;14673:12;14635:52;14722:61;14775:7;14764:8;14753:9;14749:24;14722:61;:::i;14848:184::-;14900:77;14897:1;14890:88;14997:4;14994:1;14987:15;15021:4;15018:1;15011:15;15037:273;15222:6;15214;15209:3;15196:33;15178:3;15248:16;;15273:13;;;15248:16;15037:273;-1:-1:-1;15037:273:38:o;15735:184::-;15787:77;15784:1;15777:88;15884:4;15881:1;15874:15;15908:4;15905:1;15898:15;15924:128;15991:9;;;16012:11;;;16009:37;;;16026:18;;:::i;16057:437::-;16136:1;16132:12;;;;16179;;;16200:61;;16254:4;16246:6;16242:17;16232:27;;16200:61;16307:2;16299:6;16296:14;16276:18;16273:38;16270:218;;16344:77;16341:1;16334:88;16445:4;16442:1;16435:15;16473:4;16470:1;16463:15;16270:218;;16057:437;;;:::o;16499:212::-;16541:3;16579:5;16573:12;16623:6;16616:4;16609:5;16605:16;16600:3;16594:36;16685:1;16649:16;;16674:13;;;-1:-1:-1;16649:16:38;;16499:212;-1:-1:-1;16499:212:38:o;16716:192::-;16847:3;16872:30;16898:3;16890:6;16872:30;:::i;17039:518::-;17141:2;17136:3;17133:11;17130:421;;;17177:5;17174:1;17167:16;17221:4;17218:1;17208:18;17291:2;17279:10;17275:19;17272:1;17268:27;17262:4;17258:38;17327:4;17315:10;17312:20;17309:47;;;-1:-1:-1;17350:4:38;17309:47;17405:2;17400:3;17396:12;17393:1;17389:20;17383:4;17379:31;17369:41;;17460:81;17478:2;17471:5;17468:13;17460:81;;;17537:1;17523:16;;17504:1;17493:13;17460:81;;17793:1317;17917:18;17912:3;17909:27;17906:53;;;17939:18;;:::i;:::-;17968:94;18058:3;18018:38;18050:4;18044:11;18018:38;:::i;:::-;18012:4;17968:94;:::i;:::-;18088:1;18113:2;18108:3;18105:11;18130:1;18125:727;;;;18896:1;18913:3;18910:93;;;-1:-1:-1;18969:19:38;;;18956:33;18910:93;17699:66;17690:1;17686:11;;;17682:84;17678:89;17668:100;17774:1;17770:11;;;17665:117;19016:78;;18098:1006;;18125:727;16986:1;16979:14;;;17023:4;17010:18;;18170:66;18161:76;;;18335:229;18349:7;18346:1;18343:14;18335:229;;;18438:19;;;18425:33;18410:49;;18545:4;18530:20;;;;18498:1;18486:14;;;;18365:12;18335:229;;;18339:3;18592;18583:7;18580:16;18577:219;;;18712:66;18706:3;18700;18697:1;18693:11;18689:21;18685:94;18681:99;18668:9;18663:3;18659:19;18646:33;18642:139;18634:6;18627:155;18577:219;;;18839:1;18833:3;18830:1;18826:11;18822:19;18816:4;18809:33;18098:1006;;17793:1317;;;:::o;19115:326::-;19204:6;19199:3;19192:19;19256:6;19249:5;19242:4;19237:3;19233:14;19220:43;;19308:1;19301:4;19292:6;19287:3;19283:16;19279:27;19272:38;19174:3;19430:4;19360:66;19355:2;19347:6;19343:15;19339:88;19334:3;19330:98;19326:109;19319:116;;19115:326;;;;:::o;19446:247::-;19605:2;19594:9;19587:21;19568:4;19625:62;19683:2;19672:9;19668:18;19660:6;19652;19625:62;:::i;:::-;19617:70;19446:247;-1:-1:-1;;;;19446:247:38:o;19698:1516::-;19817:3;19811:4;19808:13;19805:26;;19824:5;;19698:1516::o;19805:26::-;19854:37;19886:3;19880:10;19854:37;:::i;:::-;19914:18;19906:6;19903:30;19900:56;;;19936:18;;:::i;:::-;19965:97;20055:6;20015:38;20047:4;20041:11;20015:38;:::i;:::-;20009:4;19965:97;:::i;:::-;20088:1;20116:2;20108:6;20105:14;20133:1;20128:829;;;;21001:1;21018:6;21015:89;;;-1:-1:-1;21070:19:38;;;21064:26;21015:89;17774:1;17770:11;;;17699:66;17690:1;17686:11;;;17682:84;17678:89;17668:100;;17665:117;21130:67;21124:4;21117:81;;20098:1110;;20128:829;16986:1;16979:14;;;17023:4;17010:18;;;16979:14;;;17010:18;;;20176:66;20164:79;;;20401:221;20415:7;20412:1;20409:14;20401:221;;;20497:21;;;20491:28;20476:44;;20559:1;20591:17;;;;20547:14;;;;20438:4;20431:12;20401:221;;;20405:3;20650:6;20641:7;20638:19;20635:263;;;20711:21;;;20705:28;20814:66;20796:1;20792:14;;;20808:3;20788:24;20784:97;20780:102;20765:118;20750:134;;20635:263;-1:-1:-1;;;;;20944:1:38;20928:14;;;20924:22;20911:36;;-1:-1:-1;19698:1516:38:o;21219:184::-;21271:77;21268:1;21261:88;21368:4;21365:1;21358:15;21392:4;21389:1;21382:15;21408:216;21472:9;;;21500:11;;;21447:3;21530:9;;21558:10;;21554:19;;21583:10;;21575:19;;21551:44;21548:70;;;21598:18;;:::i;:::-;21548:70;;21408:216;;;;:::o;21629:316::-;21814:6;21803:9;21796:25;21857:2;21852;21841:9;21837:18;21830:30;21777:4;21877:62;21935:2;21924:9;21920:18;21912:6;21904;21877:62;:::i;21950:316::-;22135:2;22124:9;22117:21;22098:4;22155:62;22213:2;22202:9;22198:18;22190:6;22182;22155:62;:::i;:::-;22147:70;;22253:6;22248:2;22237:9;22233:18;22226:34;21950:316;;;;;;:::o;22271:581::-;22349:4;22355:6;22415:11;22402:25;22505:66;22494:8;22478:14;22474:29;22470:102;22450:18;22446:127;22436:155;;22587:1;22584;22577:12;22436:155;22614:33;;22666:20;;;-1:-1:-1;22709:18:38;22698:30;;22695:50;;;22741:1;22738;22731:12;22695:50;22774:4;22762:17;;-1:-1:-1;22805:14:38;22801:27;;;22791:38;;22788:58;;;22842:1;22839;22832:12;23071:125;23136:9;;;23157:10;;;23154:36;;;23170:18;;:::i;23201:195::-;23240:3;23271:66;23264:5;23261:77;23258:103;;23341:18;;:::i;:::-;-1:-1:-1;23388:1:38;23377:13;;23201:195::o;23401:470::-;23658:2;23647:9;23640:21;23621:4;23684:62;23742:2;23731:9;23727:18;23719:6;23711;23684:62;:::i;:::-;23794:9;23786:6;23782:22;23777:2;23766:9;23762:18;23755:50;23822:43;23858:6;23850;23822:43;:::i;:::-;23814:51;23401:470;-1:-1:-1;;;;;;23401:470:38:o;23876:184::-;23946:6;23999:2;23987:9;23978:7;23974:23;23970:32;23967:52;;;24015:1;24012;24005:12;23967:52;-1:-1:-1;24038:16:38;;23876:184;-1:-1:-1;23876:184:38:o;24296:738::-;24349:3;24390:5;24384:12;24419:36;24445:9;24419:36;:::i;:::-;24486:1;24471:17;;24497:191;;;;24702:1;24697:331;;;;24464:564;;24497:191;24545:66;24534:9;24530:82;24525:3;24518:95;24668:6;24661:14;24654:22;24646:6;24642:35;24637:3;24633:45;24626:52;;24497:191;;24697:331;24728:5;24725:1;24718:16;24775:4;24772:1;24762:18;24802:1;24816:166;24830:6;24827:1;24824:13;24816:166;;;24910:14;;24897:11;;;24890:35;24966:1;24953:15;;;;24852:4;24845:12;24816:166;;;24820:3;;25011:6;25006:3;25002:16;24995:23;;24464:564;;;;24296:738;;;;:::o;25039:202::-;25169:3;25194:41;25231:3;25223:6;25194:41;:::i;25246:1418::-;25372:3;25366:10;25399:18;25391:6;25388:30;25385:56;;;25421:18;;:::i;:::-;25450:97;25540:6;25500:38;25532:4;25526:11;25500:38;:::i;25450:97::-;25596:4;25627:2;25616:14;;25644:1;25639:768;;;;26451:1;26468:6;26465:89;;;-1:-1:-1;26520:19:38;;;26514:26;17774:1;17770:11;;;17699:66;17690:1;17686:11;;;17682:84;17678:89;17668:100;;17665:117;26580:67;17562:226;25639:768;16986:1;16979:14;;;17023:4;17010:18;;25687:66;25675:79;;;25852:222;25866:7;25863:1;25860:14;25852:222;;;25948:19;;;25942:26;25927:42;;26055:4;26040:20;;;;26008:1;25996:14;;;;25882:12;25852:222;;;25856:3;26102:6;26093:7;26090:19;26087:261;;;26163:19;;;26157:26;26264:66;26246:1;26242:14;;;26258:3;26238:24;26234:97;26230:102;26215:118;26200:134;;26087:261;-1:-1:-1;;;;26394:1:38;26378:14;;;26374:22;26361:36;;-1:-1:-1;25246:1418:38:o;26874:196::-;26913:3;26941:5;26931:39;;26950:18;;:::i;:::-;-1:-1:-1;26997:66:38;26986:78;;26874:196::o","linkReferences":{},"immutableReferences":{"39359":[{"start":9252,"length":32},{"start":9293,"length":32},{"start":9938,"length":32}]}},"methodIdentifiers":{"BOOTNODE_MANAGER_ROLE()":"7c8973c7","OWNER_ROLE()":"e58378bb","STAGE_MANAGER_ROLE()":"068dc322","UPGRADE_INTERFACE_VERSION()":"ad3cb1cc","addBootnodes(string[])":"d90d8573","clearBootnodes()":"4179a759","currentRound()":"8a19c8bc","currentStage()":"5bf5d54c","getBootnodes()":"6370ae4f","getBootnodesCount()":"48495bdb","getEoa(string[])":"96bac35a","getPeerId(address[])":"b894a469","getPeerVoteCount(uint256,string)":"4f4026c3","getRoundStageReward(uint256,uint256,address[])":"098f027f","getTotalRewards(string[])":"80c3d97f","getTotalWins(string)":"099c4002","getVoterVoteCount(string)":"dfb3c7df","getVoterVotes(uint256,string)":"2bdd8ea6","grantRole(bytes32,address)":"2f2ff15d","hasRole(bytes32,address)":"91d14854","hasSubmittedRoundStageReward(uint256,uint256,address)":"9291fee5","initialize(address)":"c4d66de8","proxiableUUID()":"52d1902d","registerPeer(string)":"33e7fb45","removeBootnode(uint256)":"4f52ca36","revokeRole(bytes32,address)":"d547741f","stageCount()":"f33261ac","submitReward(uint256,uint256,int256,string)":"5194e15f","submitWinners(uint256,string[],string)":"fbe94d68","uniqueVotedPeers()":"42d2c6a0","uniqueVoters()":"b0c77404","updateStageAndRound()":"e28b0586","upgradeToAndCall(address,bytes)":"4f1ef286","voterLeaderboard(uint256,uint256)":"18a6fd88","winnerLeaderboard(uint256,uint256)":"2f4be652"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.29+commit.ab55807c\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidBootnodeIndex\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRoundNumber\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidStageNumber\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidVote\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidVoterPeerId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyBootnodeManager\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyStageManager\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PeerIdAlreadyRegistered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RewardAlreadySubmitted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"StageOutOfBounds\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WinnerAlreadyVoted\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"manager\",\"type\":\"address\"}],\"name\":\"AllBootnodesCleared\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"manager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"BootnodeRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"manager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"count\",\"type\":\"uint256\"}],\"name\":\"BootnodesAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"totalRewards\",\"type\":\"int256\"}],\"name\":\"CumulativeRewardsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"eoa\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"PeerRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"reward\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"RewardSubmitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"newRoundNumber\",\"type\":\"uint256\"}],\"name\":\"RoundAdvanced\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newStage\",\"type\":\"uint256\"}],\"name\":\"StageAdvanced\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string[]\",\"name\":\"winners\",\"type\":\"string[]\"}],\"name\":\"WinnerSubmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BOOTNODE_MANAGER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OWNER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"STAGE_MANAGER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string[]\",\"name\":\"newBootnodes\",\"type\":\"string[]\"}],\"name\":\"addBootnodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"clearBootnodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRound\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentStage\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBootnodes\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBootnodesCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"}],\"name\":\"getEoa\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"eoas\",\"type\":\"address[]\"}],\"name\":\"getPeerId\",\"outputs\":[{\"internalType\":\"string[][]\",\"name\":\"\",\"type\":\"string[][]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getPeerVoteCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"}],\"name\":\"getRoundStageReward\",\"outputs\":[{\"internalType\":\"int256[]\",\"name\":\"\",\"type\":\"int256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"}],\"name\":\"getTotalRewards\",\"outputs\":[{\"internalType\":\"int256[]\",\"name\":\"\",\"type\":\"int256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getTotalWins\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getVoterVoteCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getVoterVotes\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasSubmittedRoundStageReward\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner_\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"registerPeer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"removeBootnode\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stageCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"internalType\":\"int256\",\"name\":\"reward\",\"type\":\"int256\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"submitReward\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"string[]\",\"name\":\"winners\",\"type\":\"string[]\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"submitWinners\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"uniqueVotedPeers\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"uniqueVoters\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"updateStageAndRound\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"end\",\"type\":\"uint256\"}],\"name\":\"voterLeaderboard\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"},{\"internalType\":\"uint256[]\",\"name\":\"voteCounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"end\",\"type\":\"uint256\"}],\"name\":\"winnerLeaderboard\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"},{\"internalType\":\"uint256[]\",\"name\":\"wins\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Manages coordination of a swarm network including round/stage progression, peer registration, bootnode management, and winner selection.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"addBootnodes(string[])\":{\"details\":\"Adds multiple bootnodes to the list\",\"params\":{\"newBootnodes\":\"Array of bootnode strings to add\"}},\"clearBootnodes()\":{\"details\":\"Clears all bootnodes from the list\"},\"currentRound()\":{\"details\":\"Returns the current round number\",\"returns\":{\"_0\":\"Current round number\"}},\"currentStage()\":{\"details\":\"Returns the current stage number within the round\",\"returns\":{\"_0\":\"Current stage number\"}},\"getBootnodes()\":{\"details\":\"Returns all registered bootnodes\",\"returns\":{\"_0\":\"Array of all bootnode strings\"}},\"getBootnodesCount()\":{\"details\":\"Returns the number of registered bootnodes\",\"returns\":{\"_0\":\"The count of bootnodes\"}},\"getEoa(string[])\":{\"details\":\"Retrieves the EOA addresses associated with multiple peer IDs\",\"params\":{\"peerIds\":\"Array of peer IDs to look up\"},\"returns\":{\"_0\":\"Array of EOA addresses associated with the peer IDs\"}},\"getPeerId(address[])\":{\"details\":\"Retrieves the peer IDs associated with multiple EOA addresses\",\"params\":{\"eoas\":\"Array of EOA addresses to look up\"},\"returns\":{\"_0\":\"Array of peer IDs associated with the EOA addresses\"}},\"getPeerVoteCount(uint256,string)\":{\"details\":\"Gets the vote count for a specific peer ID in a round\",\"params\":{\"peerId\":\"The peer ID to query\",\"roundNumber\":\"The round number to query\"},\"returns\":{\"_0\":\"The number of votes received by the peer ID in that round\"}},\"getRoundStageReward(uint256,uint256,address[])\":{\"details\":\"Gets the reward submitted by accounts for a specific round and stage\",\"params\":{\"accounts\":\"Array of addresses to query\",\"roundNumber\":\"The round number to query\",\"stageNumber\":\"The stage number to query\"},\"returns\":{\"_0\":\"rewards Array of corresponding reward amounts for each account\"}},\"getTotalRewards(string[])\":{\"details\":\"Gets the total rewards earned by accounts across all rounds\",\"params\":{\"peerIds\":\"Array of peer IDs to query\"},\"returns\":{\"_0\":\"rewards Array of corresponding total rewards for each peer ID\"}},\"getTotalWins(string)\":{\"details\":\"Gets the total number of wins for a peer ID\",\"params\":{\"peerId\":\"The peer ID to query\"},\"returns\":{\"_0\":\"The total number of wins for the peer ID\"}},\"getVoterVoteCount(string)\":{\"details\":\"Gets the number of times a voter has voted\",\"params\":{\"peerId\":\"The peer ID of the voter\"},\"returns\":{\"_0\":\"The number of times the voter has voted\"}},\"getVoterVotes(uint256,string)\":{\"details\":\"Gets the votes for a specific round from a specific peer ID\",\"params\":{\"peerId\":\"The peer ID of the voter\",\"roundNumber\":\"The round number to query\"},\"returns\":{\"_0\":\"Array of peer IDs that the voter voted for\"}},\"grantRole(bytes32,address)\":{\"details\":\"Grants a role to an account\",\"params\":{\"account\":\"The address of the account to grant the role to\",\"role\":\"The role to grant\"}},\"hasRole(bytes32,address)\":{\"details\":\"Checks if an account has a role\",\"params\":{\"account\":\"The address of the account to check\",\"role\":\"The role to check\"},\"returns\":{\"_0\":\"True if the account has the role, false otherwise\"}},\"hasSubmittedRoundStageReward(uint256,uint256,address)\":{\"details\":\"Checks if an account has submitted a reward for a specific round and stage\",\"params\":{\"account\":\"The address of the account\",\"roundNumber\":\"The round number to check\",\"stageNumber\":\"The stage number to check\"},\"returns\":{\"_0\":\"True if the account has submitted a reward for that round and stage, false otherwise\"}},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"registerPeer(string)\":{\"details\":\"Registers a peer's ID and associates it with the sender's address\",\"params\":{\"peerId\":\"The peer ID to register\"}},\"removeBootnode(uint256)\":{\"details\":\"Removes a bootnode at the specified index\",\"params\":{\"index\":\"The index of the bootnode to remove\"}},\"revokeRole(bytes32,address)\":{\"details\":\"Removes a role from an account\",\"params\":{\"account\":\"The address of the account to revoke the role from\",\"role\":\"The role to revoke\"}},\"stageCount()\":{\"details\":\"Returns the total number of stages in a round\",\"returns\":{\"_0\":\"Number of stages\"}},\"submitReward(uint256,uint256,int256,string)\":{\"details\":\"Submits a reward for a specific round and stage\",\"params\":{\"peerId\":\"The peer ID reporting the rewards\",\"reward\":\"The reward amount to submit (can be positive or negative)\",\"roundNumber\":\"The round number for which to submit the reward\",\"stageNumber\":\"The stage number for which to submit the reward\"}},\"submitWinners(uint256,string[],string)\":{\"details\":\"Submits a list of winners for a specific round\",\"params\":{\"peerId\":\"The peer ID of the voter\",\"roundNumber\":\"The round number for which to submit the winners\",\"winners\":\"The list of peer IDs that should win\"}},\"uniqueVotedPeers()\":{\"details\":\"Gets the total number of unique peers that have been voted on\",\"returns\":{\"_0\":\"The number of unique peers that have received votes\"}},\"uniqueVoters()\":{\"details\":\"Gets the total number of unique voters who have participated\",\"returns\":{\"_0\":\"The number of unique voters\"}},\"updateStageAndRound()\":{\"details\":\"Updates the current stage and round\",\"returns\":{\"_0\":\"The current round and stage after any updates\"}},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"},\"voterLeaderboard(uint256,uint256)\":{\"details\":\"Gets a slice of the voter leaderboard\",\"params\":{\"end\":\"The ending index (exclusive)\",\"start\":\"The starting index (inclusive)\"},\"returns\":{\"peerIds\":\"Array of peer IDs sorted by number of votes (descending)\",\"voteCounts\":\"Array of corresponding vote counts\"}},\"winnerLeaderboard(uint256,uint256)\":{\"details\":\"Gets a slice of the leaderboard\",\"params\":{\"end\":\"The ending index (exclusive)\",\"start\":\"The starting index (inclusive)\"},\"returns\":{\"peerIds\":\"Array of peer IDs sorted by number of wins (descending)\",\"wins\":\"Array of corresponding win counts\"}}},\"title\":\"SwarmCoordinator\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"addBootnodes(string[])\":{\"notice\":\"Only callable by the bootnode manager\"},\"clearBootnodes()\":{\"notice\":\"Only callable by the bootnode manager\"},\"grantRole(bytes32,address)\":{\"notice\":\"Only callable by the contract owner\"},\"removeBootnode(uint256)\":{\"notice\":\"Only callable by the bootnode manager\"},\"revokeRole(bytes32,address)\":{\"notice\":\"Only callable by the contract owner\"},\"updateStageAndRound()\":{\"notice\":\"Only callable by the stage manager\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/SwarmCoordinator.sol\":\"SwarmCoordinator\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609\",\"dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0xd861907d1168dcaec2a7846edbaed12feb8bad2d6781dba987be01374f90b495\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://12ff809243040419e2fc2aa7ef0aaa60b3e6ebc901553ba1de970ceeef208c4c\",\"dweb:/ipfs/QmX2dwMVNrQAahqVzEx94gqcVB6Z8ovifPYdEfHZzj7aEb\"]},\"lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xb25a4f11fa80c702bf5cd85adec90e6f6f507f32f4a8e6f5dbc31e8c10029486\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6917f8a323e7811f041aecd4d9fd6e92455a6fba38a797ac6f6e208c7912b79d\",\"dweb:/ipfs/QmShuYv55wYHGi4EFkDB8QfF7ZCHoKk2efyz3AWY1ExSq7\"]},\"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0xc42facb5094f2f35f066a7155bda23545e39a3156faef3ddc00185544443ba7d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d3b36282ab029b46bd082619a308a2ea11c309967b9425b7b7a6eb0b0c1c3196\",\"dweb:/ipfs/QmP2YVfDB2FoREax3vJu7QhDnyYRMw52WPrCD4vdT2kuDA\"]},\"lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0x8decfa54cec979c824b044b8128cd91d713f72c71fd7dfa54974624d8c949898\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://271f914261a19d87117a777e0924ada545c16191ef9b00cc40b0134fc14ebc70\",\"dweb:/ipfs/QmdvVNWHGHQrGGPonZJs5NuzTevTjZRM2zayKrDJf7WBA2\"]},\"lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0xc59a78b07b44b2cf2e8ab4175fca91e8eca1eee2df7357b8d2a8833e5ea1f64c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5aa4f07e65444784c29cd7bfcc2341b34381e4e5b5da9f0c5bd00d7f430e66fa\",\"dweb:/ipfs/QmWRMh4Q9DpaU9GvsiXmDdoNYMyyece9if7hnfLz7uqzWM\"]},\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0xaaa1d17c1129b127a4a401db2fbd72960e2671474be3d08cae71ccdc42f7624c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cb2f27cd3952aa667e198fba0d9b7bcec52fbb12c16f013c25fe6fb52b29cc0e\",\"dweb:/ipfs/QmeuohBFoeyDPZA9JNCTEDz3VBfBD4EABWuWXVhHAuEpKR\"]},\"lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]},\"src/SwarmCoordinator.sol\":{\"keccak256\":\"0xf7243562501740ec8d08dfa3a4cb635125515debdcc4f8afd36e574d650dca36\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e2e9681fd012233f6898e84e9fe6cb999cefdd4e8f742d46333e03d40c5c1bb\",\"dweb:/ipfs/QmWHepH2CNbBakxHKYWHiLemb65GciVxkTk4eaebbcz5RW\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.29+commit.ab55807c"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[],"type":"error","name":"InvalidBootnodeIndex"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"InvalidRoundNumber"},{"inputs":[],"type":"error","name":"InvalidStageNumber"},{"inputs":[],"type":"error","name":"InvalidVote"},{"inputs":[],"type":"error","name":"InvalidVoterPeerId"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[],"type":"error","name":"OnlyBootnodeManager"},{"inputs":[],"type":"error","name":"OnlyOwner"},{"inputs":[],"type":"error","name":"OnlyStageManager"},{"inputs":[],"type":"error","name":"PeerIdAlreadyRegistered"},{"inputs":[],"type":"error","name":"RewardAlreadySubmitted"},{"inputs":[],"type":"error","name":"StageOutOfBounds"},{"inputs":[],"type":"error","name":"UUPSUnauthorizedCallContext"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"type":"error","name":"UUPSUnsupportedProxiableUUID"},{"inputs":[],"type":"error","name":"WinnerAlreadyVoted"},{"inputs":[{"internalType":"address","name":"manager","type":"address","indexed":true}],"type":"event","name":"AllBootnodesCleared","anonymous":false},{"inputs":[{"internalType":"address","name":"manager","type":"address","indexed":true},{"internalType":"uint256","name":"index","type":"uint256","indexed":false}],"type":"event","name":"BootnodeRemoved","anonymous":false},{"inputs":[{"internalType":"address","name":"manager","type":"address","indexed":true},{"internalType":"uint256","name":"count","type":"uint256","indexed":false}],"type":"event","name":"BootnodesAdded","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"string","name":"peerId","type":"string","indexed":false},{"internalType":"int256","name":"totalRewards","type":"int256","indexed":false}],"type":"event","name":"CumulativeRewardsUpdated","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"eoa","type":"address","indexed":true},{"internalType":"string","name":"peerId","type":"string","indexed":false}],"type":"event","name":"PeerRegistered","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"uint256","name":"roundNumber","type":"uint256","indexed":true},{"internalType":"uint256","name":"stageNumber","type":"uint256","indexed":true},{"internalType":"int256","name":"reward","type":"int256","indexed":false},{"internalType":"string","name":"peerId","type":"string","indexed":false}],"type":"event","name":"RewardSubmitted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32","indexed":true},{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true}],"type":"event","name":"RoleGranted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32","indexed":true},{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true}],"type":"event","name":"RoleRevoked","anonymous":false},{"inputs":[{"internalType":"uint256","name":"newRoundNumber","type":"uint256","indexed":true}],"type":"event","name":"RoundAdvanced","anonymous":false},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256","indexed":true},{"internalType":"uint256","name":"newStage","type":"uint256","indexed":false}],"type":"event","name":"StageAdvanced","anonymous":false},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"string","name":"peerId","type":"string","indexed":false},{"internalType":"uint256","name":"roundNumber","type":"uint256","indexed":true},{"internalType":"string[]","name":"winners","type":"string[]","indexed":false}],"type":"event","name":"WinnerSubmitted","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"BOOTNODE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"OWNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"STAGE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[{"internalType":"string[]","name":"newBootnodes","type":"string[]"}],"stateMutability":"nonpayable","type":"function","name":"addBootnodes"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"clearBootnodes"},{"inputs":[],"stateMutability":"view","type":"function","name":"currentRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"currentStage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getBootnodes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getBootnodesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"}],"stateMutability":"view","type":"function","name":"getEoa","outputs":[{"internalType":"address[]","name":"","type":"address[]"}]},{"inputs":[{"internalType":"address[]","name":"eoas","type":"address[]"}],"stateMutability":"view","type":"function","name":"getPeerId","outputs":[{"internalType":"string[][]","name":"","type":"string[][]"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getPeerVoteCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"address[]","name":"accounts","type":"address[]"}],"stateMutability":"view","type":"function","name":"getRoundStageReward","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}]},{"inputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"}],"stateMutability":"view","type":"function","name":"getTotalRewards","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}]},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getTotalWins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getVoterVoteCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getVoterVotes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}]},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"grantRole"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"hasSubmittedRoundStageReward","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[],"stateMutability":"view","type":"function","name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"registerPeer"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"removeBootnode"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"revokeRole"},{"inputs":[],"stateMutability":"pure","type":"function","name":"stageCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"int256","name":"reward","type":"int256"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"submitReward"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string[]","name":"winners","type":"string[]"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"submitWinners"},{"inputs":[],"stateMutability":"view","type":"function","name":"uniqueVotedPeers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"uniqueVoters","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"updateStageAndRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function","name":"upgradeToAndCall"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"stateMutability":"view","type":"function","name":"voterLeaderboard","outputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"},{"internalType":"uint256[]","name":"voteCounts","type":"uint256[]"}]},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"stateMutability":"view","type":"function","name":"winnerLeaderboard","outputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"},{"internalType":"uint256[]","name":"wins","type":"uint256[]"}]}],"devdoc":{"kind":"dev","methods":{"addBootnodes(string[])":{"details":"Adds multiple bootnodes to the list","params":{"newBootnodes":"Array of bootnode strings to add"}},"clearBootnodes()":{"details":"Clears all bootnodes from the list"},"currentRound()":{"details":"Returns the current round number","returns":{"_0":"Current round number"}},"currentStage()":{"details":"Returns the current stage number within the round","returns":{"_0":"Current stage number"}},"getBootnodes()":{"details":"Returns all registered bootnodes","returns":{"_0":"Array of all bootnode strings"}},"getBootnodesCount()":{"details":"Returns the number of registered bootnodes","returns":{"_0":"The count of bootnodes"}},"getEoa(string[])":{"details":"Retrieves the EOA addresses associated with multiple peer IDs","params":{"peerIds":"Array of peer IDs to look up"},"returns":{"_0":"Array of EOA addresses associated with the peer IDs"}},"getPeerId(address[])":{"details":"Retrieves the peer IDs associated with multiple EOA addresses","params":{"eoas":"Array of EOA addresses to look up"},"returns":{"_0":"Array of peer IDs associated with the EOA addresses"}},"getPeerVoteCount(uint256,string)":{"details":"Gets the vote count for a specific peer ID in a round","params":{"peerId":"The peer ID to query","roundNumber":"The round number to query"},"returns":{"_0":"The number of votes received by the peer ID in that round"}},"getRoundStageReward(uint256,uint256,address[])":{"details":"Gets the reward submitted by accounts for a specific round and stage","params":{"accounts":"Array of addresses to query","roundNumber":"The round number to query","stageNumber":"The stage number to query"},"returns":{"_0":"rewards Array of corresponding reward amounts for each account"}},"getTotalRewards(string[])":{"details":"Gets the total rewards earned by accounts across all rounds","params":{"peerIds":"Array of peer IDs to query"},"returns":{"_0":"rewards Array of corresponding total rewards for each peer ID"}},"getTotalWins(string)":{"details":"Gets the total number of wins for a peer ID","params":{"peerId":"The peer ID to query"},"returns":{"_0":"The total number of wins for the peer ID"}},"getVoterVoteCount(string)":{"details":"Gets the number of times a voter has voted","params":{"peerId":"The peer ID of the voter"},"returns":{"_0":"The number of times the voter has voted"}},"getVoterVotes(uint256,string)":{"details":"Gets the votes for a specific round from a specific peer ID","params":{"peerId":"The peer ID of the voter","roundNumber":"The round number to query"},"returns":{"_0":"Array of peer IDs that the voter voted for"}},"grantRole(bytes32,address)":{"details":"Grants a role to an account","params":{"account":"The address of the account to grant the role to","role":"The role to grant"}},"hasRole(bytes32,address)":{"details":"Checks if an account has a role","params":{"account":"The address of the account to check","role":"The role to check"},"returns":{"_0":"True if the account has the role, false otherwise"}},"hasSubmittedRoundStageReward(uint256,uint256,address)":{"details":"Checks if an account has submitted a reward for a specific round and stage","params":{"account":"The address of the account","roundNumber":"The round number to check","stageNumber":"The stage number to check"},"returns":{"_0":"True if the account has submitted a reward for that round and stage, false otherwise"}},"proxiableUUID()":{"details":"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier."},"registerPeer(string)":{"details":"Registers a peer's ID and associates it with the sender's address","params":{"peerId":"The peer ID to register"}},"removeBootnode(uint256)":{"details":"Removes a bootnode at the specified index","params":{"index":"The index of the bootnode to remove"}},"revokeRole(bytes32,address)":{"details":"Removes a role from an account","params":{"account":"The address of the account to revoke the role from","role":"The role to revoke"}},"stageCount()":{"details":"Returns the total number of stages in a round","returns":{"_0":"Number of stages"}},"submitReward(uint256,uint256,int256,string)":{"details":"Submits a reward for a specific round and stage","params":{"peerId":"The peer ID reporting the rewards","reward":"The reward amount to submit (can be positive or negative)","roundNumber":"The round number for which to submit the reward","stageNumber":"The stage number for which to submit the reward"}},"submitWinners(uint256,string[],string)":{"details":"Submits a list of winners for a specific round","params":{"peerId":"The peer ID of the voter","roundNumber":"The round number for which to submit the winners","winners":"The list of peer IDs that should win"}},"uniqueVotedPeers()":{"details":"Gets the total number of unique peers that have been voted on","returns":{"_0":"The number of unique peers that have received votes"}},"uniqueVoters()":{"details":"Gets the total number of unique voters who have participated","returns":{"_0":"The number of unique voters"}},"updateStageAndRound()":{"details":"Updates the current stage and round","returns":{"_0":"The current round and stage after any updates"}},"upgradeToAndCall(address,bytes)":{"custom:oz-upgrades-unsafe-allow-reachable":"delegatecall","details":"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event."},"voterLeaderboard(uint256,uint256)":{"details":"Gets a slice of the voter leaderboard","params":{"end":"The ending index (exclusive)","start":"The starting index (inclusive)"},"returns":{"peerIds":"Array of peer IDs sorted by number of votes (descending)","voteCounts":"Array of corresponding vote counts"}},"winnerLeaderboard(uint256,uint256)":{"details":"Gets a slice of the leaderboard","params":{"end":"The ending index (exclusive)","start":"The starting index (inclusive)"},"returns":{"peerIds":"Array of peer IDs sorted by number of wins (descending)","wins":"Array of corresponding win counts"}}},"version":1},"userdoc":{"kind":"user","methods":{"addBootnodes(string[])":{"notice":"Only callable by the bootnode manager"},"clearBootnodes()":{"notice":"Only callable by the bootnode manager"},"grantRole(bytes32,address)":{"notice":"Only callable by the contract owner"},"removeBootnode(uint256)":{"notice":"Only callable by the bootnode manager"},"revokeRole(bytes32,address)":{"notice":"Only callable by the contract owner"},"updateStageAndRound()":{"notice":"Only callable by the stage manager"}},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":1000000},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/SwarmCoordinator.sol":"SwarmCoordinator"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b","urls":["bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609","dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0xd861907d1168dcaec2a7846edbaed12feb8bad2d6781dba987be01374f90b495","urls":["bzz-raw://12ff809243040419e2fc2aa7ef0aaa60b3e6ebc901553ba1de970ceeef208c4c","dweb:/ipfs/QmX2dwMVNrQAahqVzEx94gqcVB6Z8ovifPYdEfHZzj7aEb"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xb25a4f11fa80c702bf5cd85adec90e6f6f507f32f4a8e6f5dbc31e8c10029486","urls":["bzz-raw://6917f8a323e7811f041aecd4d9fd6e92455a6fba38a797ac6f6e208c7912b79d","dweb:/ipfs/QmShuYv55wYHGi4EFkDB8QfF7ZCHoKk2efyz3AWY1ExSq7"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol":{"keccak256":"0xc42facb5094f2f35f066a7155bda23545e39a3156faef3ddc00185544443ba7d","urls":["bzz-raw://d3b36282ab029b46bd082619a308a2ea11c309967b9425b7b7a6eb0b0c1c3196","dweb:/ipfs/QmP2YVfDB2FoREax3vJu7QhDnyYRMw52WPrCD4vdT2kuDA"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0x8decfa54cec979c824b044b8128cd91d713f72c71fd7dfa54974624d8c949898","urls":["bzz-raw://271f914261a19d87117a777e0924ada545c16191ef9b00cc40b0134fc14ebc70","dweb:/ipfs/QmdvVNWHGHQrGGPonZJs5NuzTevTjZRM2zayKrDJf7WBA2"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0xc59a78b07b44b2cf2e8ab4175fca91e8eca1eee2df7357b8d2a8833e5ea1f64c","urls":["bzz-raw://5aa4f07e65444784c29cd7bfcc2341b34381e4e5b5da9f0c5bd00d7f430e66fa","dweb:/ipfs/QmWRMh4Q9DpaU9GvsiXmDdoNYMyyece9if7hnfLz7uqzWM"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0xaaa1d17c1129b127a4a401db2fbd72960e2671474be3d08cae71ccdc42f7624c","urls":["bzz-raw://cb2f27cd3952aa667e198fba0d9b7bcec52fbb12c16f013c25fe6fb52b29cc0e","dweb:/ipfs/QmeuohBFoeyDPZA9JNCTEDz3VBfBD4EABWuWXVhHAuEpKR"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"},"src/SwarmCoordinator.sol":{"keccak256":"0xf7243562501740ec8d08dfa3a4cb635125515debdcc4f8afd36e574d650dca36","urls":["bzz-raw://0e2e9681fd012233f6898e84e9fe6cb999cefdd4e8f742d46333e03d40c5c1bb","dweb:/ipfs/QmWHepH2CNbBakxHKYWHiLemb65GciVxkTk4eaebbcz5RW"],"license":"MIT"}},"version":1},"id":34} \ No newline at end of file diff --git a/code_gen_exp/requirements.txt b/code_gen_exp/requirements.txt new file mode 100644 index 0000000..62ce168 --- /dev/null +++ b/code_gen_exp/requirements.txt @@ -0,0 +1,3 @@ +gensyn-genrl==0.3.0 +ollama +hivemind@git+https://github.com/gensyn-ai/hivemind@639c964a8019de63135a2594663b5bec8e5356dd diff --git a/code_gen_exp/runner/__init__.py b/code_gen_exp/runner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code_gen_exp/runner/swarm_launcher.py b/code_gen_exp/runner/swarm_launcher.py new file mode 100644 index 0000000..ee36e69 --- /dev/null +++ b/code_gen_exp/runner/swarm_launcher.py @@ -0,0 +1,52 @@ +import os +import time +import hydra +from genrl.communication.communication import Communication +from genrl.communication.hivemind.hivemind_backend import ( + HivemindBackend, + HivemindRendezvouz, +) +from hydra.utils import instantiate +from omegaconf import DictConfig, OmegaConf +from genrl.logging_utils.global_defs import get_logger +from code_gen_exp.src.utils.omega_gpu_resolver import ( + gpu_model_choice_resolver, +) # necessary for gpu_model_choice resolver in hydra config + +import logging +logging.getLogger("httpx").setLevel(logging.WARNING) +logging.getLogger("hivemind").setLevel(logging.CRITICAL) +# Suppress Hugging Face retry warnings, but keep download progress bar +# Progress bar uses tqdm which outputs directly to stdout, not logging +logging.getLogger("huggingface_hub.utils._http").setLevel(logging.ERROR) +# Keep file_download at WARNING to see progress, but suppress retry messages +logging.getLogger("huggingface_hub.file_download").setLevel(logging.WARNING) + +@hydra.main(version_base=None) +def main(cfg: DictConfig): + is_master = True + HivemindRendezvouz.init(is_master=is_master) + + max_retries = 3 + for i in range(max_retries): + try: + game_manager = instantiate(cfg.game_manager) + break + except Exception as e: + if "not a local folder and is not a valid model identifier" in str(e): + if i < max_retries - 1: + get_logger().warning("If the repo you requested exists, Hugging Face servers may be temporarily unavailable or rate-limited. " + "Please retry until the Hugging Face download completes successfully.") + time.sleep(5) + else: + raise + else: + # raise if it's a different error + raise + game_manager.run_game() + + +if __name__ == "__main__": + os.environ["HYDRA_FULL_ERROR"] = "1" + Communication.set_backend(HivemindBackend) + main() \ No newline at end of file diff --git a/code_gen_exp/src/__init__.py b/code_gen_exp/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code_gen_exp/src/coordinator.py b/code_gen_exp/src/coordinator.py new file mode 100644 index 0000000..a96cee4 --- /dev/null +++ b/code_gen_exp/src/coordinator.py @@ -0,0 +1,69 @@ +import json +import requests +from genrl.logging_utils.global_defs import get_logger +from genrl.blockchain.connections import send_via_api +from genrl.blockchain.coordinator import SwarmCoordinator + + +class ModalSwarmCoordinator(SwarmCoordinator): + def __init__( + self, + web3_url: str, + contract_address: str, + org_id: str, + modal_proxy_url: str, + swarm_coordinator_abi_json: str, + ) -> None: + super().__init__(web3_url, contract_address, swarm_coordinator_abi_json) + self.org_id = org_id + self.modal_proxy_url = modal_proxy_url + + def register_peer(self, peer_id): + try: + send_via_api( + self.org_id, self.modal_proxy_url, "register-peer", {"peerId": peer_id} + ) + except requests.exceptions.HTTPError as http_err: + if http_err.response is None or http_err.response.status_code != 400: + raise + + try: + err_data = http_err.response.json() + err_name = err_data["error"] + if err_name != "PeerIdAlreadyRegistered": + get_logger().info(f"Registering peer failed with: f{err_name}") + raise + get_logger().info(f"Peer ID [{peer_id}] is already registered! Continuing.") + + except json.JSONDecodeError as decode_err: + get_logger().debug( + "Error decoding JSON during handling of register-peer error" + ) + raise http_err + + def submit_reward(self, round_num, stage_num, reward, peer_id): + try: + send_via_api( + self.org_id, + self.modal_proxy_url, + "submit-reward", + { + "roundNumber": round_num, + "stageNumber": stage_num, + "reward": reward, + "peerId": peer_id, + }, + ) + except requests.exceptions.HTTPError as e: + raise + + def submit_winners(self, round_num, winners, peer_id): + try: + send_via_api( + self.org_id, + self.modal_proxy_url, + "submit-winner", + {"roundNumber": round_num, "winners": winners, "peerId": peer_id}, + ) + except requests.exceptions.HTTPError as e: + raise diff --git a/code_gen_exp/src/manager.py b/code_gen_exp/src/manager.py new file mode 100644 index 0000000..d76f415 --- /dev/null +++ b/code_gen_exp/src/manager.py @@ -0,0 +1,272 @@ +import os +import time +from collections import defaultdict + +import ollama + +from genrl.blockchain import SwarmCoordinator +from genrl.communication.hivemind.hivemind_backend import HivemindBackend +from genrl.data import DataManager +from genrl.game import BaseGameManager +from genrl.game.game_manager import DefaultGameManagerMixin +from genrl.logging_utils.global_defs import get_logger +from genrl.logging_utils.system_utils import get_system_info +from genrl.rewards import RewardManager +from genrl.roles import RoleManager +from genrl.state import GameState +from genrl.trainer import TrainerModule +from huggingface_hub import login, whoami + +from code_gen_exp.src.utils.name_utils import get_name_from_peer_id + + +class SwarmGameManager(BaseGameManager, DefaultGameManagerMixin): + """GameManager that orchestrates a game using a SwarmCoordinator.""" + + def __init__( + self, + coordinator: SwarmCoordinator, + max_stage: int, + max_round: int, + game_state: GameState, + reward_manager: RewardManager, + trainer: TrainerModule, + data_manager: DataManager, + communication_kwargs: dict, + role_manager: RoleManager | None = None, + run_mode: str = "train", + log_dir: str = "logs", + hf_token: str | None = None, + hf_push_frequency: int = 20, + **kwargs, + ): + initial_peers = coordinator.get_bootnodes() + communication_kwargs['initial_peers'] = initial_peers + get_logger().info(f"bootnodes: {initial_peers}") + rewards_ollama_model = kwargs.get("rewards_ollama_model", 'qwen2.5-coder:1.5b-instruct') + + communication = HivemindBackend(**communication_kwargs) + + super().__init__( + max_stage=max_stage, + max_round=max_round, + game_state=game_state, + reward_manager=reward_manager, + trainer=trainer, + data_manager=data_manager, + communication=communication, + role_manager=role_manager, + run_mode=run_mode, + ) + + assert isinstance(self.communication, HivemindBackend) + self.train_timeout = 60 * 60 * 24 * 31 # 1 month + + # Logging Setup + self.peer_id = self.communication.get_id() + self.state.peer_id = self.peer_id + self.animal_name = get_name_from_peer_id(self.peer_id, True) + + # Register peer_id and get current round from the chain + self.coordinator = coordinator + self.coordinator.register_peer(self.peer_id) + round, _ = self.coordinator.get_round_and_stage() + self.state.round = round + + self.communication.step_ = ( + self.state.round + ) # initialize communication module to contract's round + + self.data_manager.initialize(self.communication) + + # enable push to HF if token was provided + self.hf_token = hf_token + if self.hf_token not in [None, "None"]: + self._configure_hf_hub(hf_push_frequency) + + get_logger().info( + f"🐝 Hello [{get_name_from_peer_id(self.peer_id)}] [{self.peer_id}]!" + ) + get_logger().info(f"Using Model: {self.trainer.model.config.name_or_path}") + + try: + models = ollama.list() + model_names = [model["model"] for model in models["models"]] + if rewards_ollama_model not in model_names: + ollama.pull(rewards_ollama_model) + except Exception as e: + get_logger().error(f"Error pulling model from ollama: {rewards_ollama_model}") + raise e + + with open(os.path.join(log_dir, f"system_info.txt"), "w") as f: + f.write(get_system_info()) + + self.batched_signals = 0.0 + self.time_since_submit = time.time() # seconds + self.submit_period = 3.0 # hours + self.submitted_this_round = False + + def _get_total_rewards_by_agent(self): + rewards_by_agent = defaultdict(int) + for stage in range(self.state.stage): + rewards = self.rewards[stage] + for agent_id, agent_rewards in rewards.items(): + for batch_id, batch_rewards in agent_rewards.items(): + tot = 0 + for generation_rewards in batch_rewards: + tot += sum(generation_rewards) + rewards_by_agent[agent_id] += tot + + return rewards_by_agent + + def _get_my_rewards(self, signal_by_agent): + if len(signal_by_agent) == 0: + return 0 + if self.peer_id in signal_by_agent: + my_signal = signal_by_agent[self.peer_id] + else: + my_signal = 0 + my_signal = (my_signal + 1) * (my_signal > 0) + 0 * (my_signal <= 0) + return my_signal + + def _try_submit_to_chain(self, signal_by_agent): + elapsed_time_hours = (time.time() - self.time_since_submit) / 3600 + if elapsed_time_hours > self.submit_period: + try: + self.coordinator.submit_reward( + self.state.round, 0, int(self.batched_signals), self.peer_id + ) + self.batched_signals = 0.0 + if len(signal_by_agent) > 0: + max_agent, max_signal = max( + signal_by_agent.items(), key=lambda x: x[1] + ) + else: # if we have no signal_by_agents, just submit ourselves. + max_agent = self.peer_id + + self.coordinator.submit_winners( + self.state.round, [max_agent], self.peer_id + ) + self.time_since_submit = time.time() + self.submitted_this_round = True + except Exception as e: + get_logger().debug(str(e)) + + def _hook_after_rewards_updated(self): + try: + signal_by_agent = self._get_total_rewards_by_agent() + self.batched_signals += self._get_my_rewards(signal_by_agent) + except Exception as e: + get_logger().debug(f"Error getting total rewards by agent: {e}") + signal_by_agent = {} + + self._try_submit_to_chain(signal_by_agent) + + for stage in range(self.state.stage): + root_state = self.state.get_stage_state(stage) + self.data_manager.send_response(self.rewards[stage], root_state) + + def _hook_after_round_advanced(self): + self._save_to_hf() + + # Try to submit to chain again if necessary, but don't update our signal twice + if not self.submitted_this_round: + try: + signal_by_agent = self._get_total_rewards_by_agent() + except Exception as e: + get_logger().debug(f"Error getting total rewards by agent: {e}") + signal_by_agent = {} + + self._try_submit_to_chain(signal_by_agent) + + + # Reset flag for next round + self.submitted_this_round = False + + # Block until swarm round advances + self.agent_block() + + def _hook_after_game(self): + self._save_to_hf() + + def _configure_hf_hub(self, hf_push_frequency): + username = whoami(token=self.hf_token)["name"] + model_name = self.trainer.model.config.name_or_path.split("/")[-1] + model_name += "-Gensyn-Swarm" + model_name += f"-{self.animal_name}" + self.trainer.args.hub_model_id = f"{username}/{model_name}" + self.hf_push_frequency = hf_push_frequency + get_logger().info("Logging into Hugging Face Hub...") + login(self.hf_token) + + def _save_to_hf(self): + if ( + self.hf_token not in [None, "None"] + and self.state.round % self.hf_push_frequency == 0 + ): + get_logger().info(f"pushing model to huggingface") + try: + repo_id = self.trainer.args.hub_model_id + + self.trainer.model.push_to_hub( + repo_id=repo_id, + token=self.hf_token, + commit_message=f"rl-swarm: round {self.state.round}, agent {self.animal_name}", + tags=[ + "rl-swarm", + "genrl-swarm", + "grpo", + "gensyn", + f"I am {self.animal_name}", + ], + ) + except Exception: + get_logger().exception( + "Failed to push model to the Hugging Face Hub. When you conclude training please try manually pushing it yourself using the instructions here: https://huggingface.co/docs/hub/en/models-uploading", + stack_info=True, + ) + + def agent_block( + self, check_interval=5.0, log_timeout=10.0, max_check_interval=60.0 * 15 + ): + start_time = time.monotonic() + fetch_log_time = start_time + check_backoff = ( + check_interval # Exponential backoff for already finished rounds. + ) + while time.monotonic() - start_time < self.train_timeout: + curr_time = time.monotonic() + try: + _ = self.communication.dht.get_visible_maddrs(latest=True) + except Exception as e: + get_logger().debug(f"Unable to refresh DHT visible addrs: {e}") + + # Retrieve current round and stage. + try: + round_num, stage = self.coordinator.get_round_and_stage() + except Exception as e: + if curr_time - fetch_log_time > log_timeout: + get_logger().debug( + f"Could not fetch round and stage: {e}. Next check in {check_interval}s." + ) + fetch_log_time = curr_time + + time.sleep(check_interval) + continue + + if round_num >= self.state.round: + get_logger().info(f"🐝 Joining round: {round_num}") + check_backoff = check_interval # Reset backoff after successful round + self.state.round = round_num # advance to swarm's round. + return + else: + get_logger().info( + f"Already finished round: {round_num}. Next check in {check_backoff}s." + ) + time.sleep(check_backoff) + check_backoff = min(check_backoff * 2, max_check_interval) + + if round_num == self.max_round - 1: + return + + get_logger().info("Training timed out!") diff --git a/code_gen_exp/src/proposer.py b/code_gen_exp/src/proposer.py new file mode 100644 index 0000000..39a252d --- /dev/null +++ b/code_gen_exp/src/proposer.py @@ -0,0 +1,610 @@ + +from dataclasses import dataclass, field +from collections import deque +import copy +import logging +import os +import shutil +import torch +import torch.nn.functional as F +from torch.optim import AdamW +from transformers import AutoModelForCausalLM, AutoTokenizer + +from code_gen_exp.src.utils.proposer_utils import parse_json_from_fence, extract_question_name +from genrl.misc_utils.sandbox_executor import CodeSandboxExecutor +logger = logging.getLogger(__name__) + + +try: + from vllm import LLM as VLLMEngine + from vllm import SamplingParams as VLLMSamplingParams + _VLLM_AVAILABLE = True +except Exception: + VLLMEngine = None + VLLMSamplingParams = None + _VLLM_AVAILABLE = False + +SYSTEM_PROMPT = "You are an expert Python developer and technical interviewer specializing in creating high-quality coding challenges. Your role is to design Python programming problems with comprehensive test cases that accurately assess a developer's coding abilities, problem-solving skills, and understanding of Python best practices." +# Level 1: Beginner - Basic Python concepts +LEVEL_1_PROMPT_TEXT = ( + "You are to propose a BEGINNER Python coding question and matching unit tests.\n" + "Constraints:\n" + "- Do NOT provide any solution or implementation.\n" + "- Choose a single clear function name and write a one-sentence description of what it should do.\n" + "- Provide runnable unit tests only (pytest or unittest) that validate expected behavior of the function.\n" + "- The tests should be self-contained and reference the function name but not implement it.\n" + "- Limit the number of included tests to at most 4.\n" + "- The output MUST be valid JSON with exactly these keys: question (string) and tests (string).\n" + "- The tests field must contain ONLY Python code.\n" + "- Do not include any additional keys or commentary.\n" + "- The question should contain the function name that the tests refer to.\n" + "- IMPORTANT: Wrap your entire output in a Markdown fenced code block that starts with ```json on its own line and ends with ``` on its own line.\n" + "- Do not include any text outside the fenced block.\n\n" + "DIFFICULTY REQUIREMENTS (Level 1 - Beginner):\n" + "- Focus on basic Python concepts: variables, simple data types, basic loops, conditionals\n" + "- Simple arithmetic operations, string manipulation, basic list operations\n" + "- Single parameter functions with straightforward logic\n" + "- No complex algorithms or data structures required\n" + "- Examples: string formatting, basic calculations, simple list processing\n\n" + "Example output format (fenced JSON):\n" + "```json\n{\n \"question\": \"Write a function foo(x) that ...\",\n \"tests\": \"import pytest\\n\\n def test_foo(): ...\"\n}\n```" + "The following are problems you have already proposed along with the number of times they have been proposed, please do not repeat:\n\n" + ) + +# Level 2: Easy - Basic algorithms and data structures +LEVEL_2_PROMPT_TEXT = ( + "You are to propose an EASY Python coding question and matching unit tests.\n" + "Constraints:\n" + "- Do NOT provide any solution or implementation.\n" + "- Choose a single clear function name and write a one-sentence description of what it should do.\n" + "- Provide runnable unit tests only (pytest or unittest) that validate expected behavior of the function.\n" + "- The tests should be self-contained and reference the function name but not implement it.\n" + "- Limit the number of included tests to at most 4.\n" + "- The output MUST be valid JSON with exactly these keys: question (string) and tests (string).\n" + "- The tests field must contain ONLY Python code.\n" + "- Do not include any additional keys or commentary.\n" + "- The question should contain the function name that the tests refer to.\n" + "- IMPORTANT: Wrap your entire output in a Markdown fenced code block that starts with ```json on its own line and ends with ``` on its own line.\n" + "- Do not include any text outside the fenced block.\n\n" + "DIFFICULTY REQUIREMENTS (Level 2 - Easy):\n" + "- Basic algorithms: linear search, simple sorting, basic string/list operations\n" + "- Simple data structures: lists, dictionaries, sets\n" + "- Basic iteration and simple recursive thinking\n" + "- Multiple parameters but straightforward logic\n" + "- Examples: finding max/min, counting occurrences, simple transformations\n\n" + "Example output format (fenced JSON):\n" + "```json\n{\n \"question\": \"Write a function foo(x) that ...\",\n \"tests\": \"import pytest\\n\\n def test_foo(): ...\"\n}\n```" + "The following are problems you have already proposed along with the number of times they have been proposed, please do not repeat:\n\n" + ) + +# Level 3: Medium - Intermediate algorithms and problem solving +LEVEL_3_PROMPT_TEXT = ( + "You are to propose a MEDIUM Python coding question and matching unit tests.\n" + "Constraints:\n" + "- Do NOT provide any solution or implementation.\n" + "- Choose a single clear function name and write a one-sentence description of what it should do.\n" + "- Provide runnable unit tests only (pytest or unittest) that validate expected behavior of the function.\n" + "- The tests should be self-contained and reference the function name but not implement it.\n" + "- Limit the number of included tests to at most 4.\n" + "- The output MUST be valid JSON with exactly these keys: question (string) and tests (string).\n" + "- The tests field must contain ONLY Python code.\n" + "- Do not include any additional keys or commentary.\n" + "- The question should contain the function name that the tests refer to.\n" + "- IMPORTANT: Wrap your entire output in a Markdown fenced code block that starts with ```json on its own line and ends with ``` on its own line.\n" + "- Do not include any text outside the fenced block.\n\n" + "DIFFICULTY REQUIREMENTS (Level 3 - Medium):\n" + "- Intermediate algorithms: binary search, merge sort, basic graph traversal\n" + "- More complex data structures: trees, heaps, stacks, queues\n" + "- Moderate algorithmic thinking and optimization\n" + "- Multiple parameters with some complexity\n" + "- Examples: tree operations, matrix manipulation, moderate optimization problems\n\n" + "Example output format (fenced JSON):\n" + "```json\n{\n \"question\": \"Write a function foo(x) that ...\",\n \"tests\": \"import pytest\\n\\n def test_foo(): ...\"\n}\n```" + "The following are problems you have already proposed along with the number of times they have been proposed, please do not repeat:\n\n" + ) + +# Level 4: Hard - Advanced algorithms and complex problem solving +LEVEL_4_PROMPT_TEXT = ( + "You are to propose a HARD Python coding question and matching unit tests.\n" + "Constraints:\n" + "- Do NOT provide any solution or implementation.\n" + "- Choose a single clear function name and write a one-sentence description of what it should do.\n" + "- Provide runnable unit tests only (pytest or unittest) that validate expected behavior of the function.\n" + "- The tests should be self-contained and reference the function name but not implement it.\n" + "- Limit the number of included tests to at most 4.\n" + "- The output MUST be valid JSON with exactly these keys: question (string) and tests (string).\n" + "- The tests field must contain ONLY Python code.\n" + "- Do not include any additional keys or commentary.\n" + "- The question should contain the function name that the tests refer to.\n" + "- IMPORTANT: Wrap your entire output in a Markdown fenced code block that starts with ```json on its own line and ends with ``` on its own line.\n" + "- Do not include any text outside the fenced block.\n\n" + "DIFFICULTY REQUIREMENTS (Level 4 - Hard):\n" + "- Advanced algorithms: dynamic programming, complex graph algorithms, advanced sorting\n" + "- Complex data structures: advanced trees, graphs, hash tables with collision handling\n" + "- Sophisticated algorithmic thinking and optimization\n" + "- Multiple parameters with complex interactions\n" + "- Examples: pathfinding algorithms, complex optimization, advanced data manipulation\n\n" + "Example output format (fenced JSON):\n" + "```json\n{\n \"question\": \"Write a function foo(x) that ...\",\n \"tests\": \"import pytest\\n\\n def test_foo(): ...\"\n}\n```" + "The following are problems you have already proposed along with the number of times they have been proposed, please do not repeat:\n\n" + ) + +# Level 5: Expert - Very challenging problems requiring deep algorithmic knowledge +LEVEL_5_PROMPT_TEXT = ( + "You are to propose an EXPERT Python coding question and matching unit tests.\n" + "Constraints:\n" + "- Do NOT provide any solution or implementation.\n" + "- Choose a single clear function name and write a one-sentence description of what it should do.\n" + "- Provide runnable unit tests only (pytest or unittest) that validate expected behavior of the function.\n" + "- The tests should be self-contained and reference the function name but not implement it.\n" + "- Limit the number of included tests to at most 4.\n" + "- The output MUST be valid JSON with exactly these keys: question (string) and tests (string).\n" + "- The tests field must contain ONLY Python code.\n" + "- Do not include any additional keys or commentary.\n" + "- The question should contain the function name that the tests refer to.\n" + "- IMPORTANT: Wrap your entire output in a Markdown fenced code block that starts with ```json on its own line and ends with ``` on its own line.\n" + "- Do not include any text outside the fenced block.\n\n" + "DIFFICULTY REQUIREMENTS (Level 5 - Expert):\n" + "- Expert-level algorithms: advanced dynamic programming, complex graph theory, advanced optimization\n" + "- Sophisticated data structures: advanced trees, complex graph representations, specialized structures\n" + "- Deep algorithmic thinking, multiple algorithm combinations, edge case handling\n" + "- Complex parameter interactions and advanced problem decomposition\n" + "- Examples: advanced pathfinding, complex optimization problems, specialized algorithms\n\n" + "Example output format (fenced JSON):\n" + "```json\n{\n \"question\": \"Write a function foo(x) that ...\",\n \"tests\": \"import pytest\\n\\n def test_foo(): ...\"\n}\n```" + "The following are problems you have already proposed along with the number of times they have been proposed, please do not repeat:\n\n" + ) + +# Backward compatibility - keep original prompts as aliases +PROMPT_TEXT = LEVEL_2_PROMPT_TEXT # Default to Level 2 (Easy) +HARD_PROMPT_TEXT = LEVEL_4_PROMPT_TEXT # Hard maps to Level 4 + + +CLEANUP_PROMPT = "please reformat the following to ensure that it is valid json in a fenced code block with keys question and tests. All other output should be removed.\n" + +@dataclass +class PPOConfig: + clip_range: float = 0.2 + ppo_epochs: int = 2 + learning_rate: float = 1e-5 + beta: float = 0.01 + lookback: int = 5 + +@dataclass +class VllmConfig: + use_vllm: bool = False + engine: VLLMEngine = None + max_tokens: int = 1024 + sampling: VLLMSamplingParams | None = field(default=None) + # vLLM resource controls (tunable to mitigate OOM) + gpu_memory_utilization: float = 0.9 # fraction of available GPU memory vLLM may use + max_model_len: int = 4096 # reduce KV cache footprint if needed + swap_space: int = 4 # GB of CPU swap for KV cache spillover + tensor_parallel_size: int = 1 # keep to 1 unless you shard across GPUs + pipeline_parallel_size: int = 1 + + def __post_init__(self): + if _VLLM_AVAILABLE and self.sampling is None: + self.sampling = VLLMSamplingParams(max_tokens=self.max_tokens) + +@dataclass +class PromptUpdateConfig: + """Configuration for prompt difficulty updates and adaptive learning.""" + reward_history_size: int = 20 # Number of recent rewards to track for difficulty adjustment + initial_difficulty_level: int = 2 # Starting difficulty level (1-5) + recent_window_size: int = 10 # Number of recent iterations to compare + earlier_window_size: int = 10 # Number of earlier iterations to compare against + difficulty_change_threshold: float = 0.05 # Minimum performance difference to trigger difficulty change + prompt_update_frequency: int = 5 # Update prompt difficulty every N iterations + + +class Proposer: + def __init__(self, + model_path: str, + ppo_config: PPOConfig = PPOConfig(), + vllm_config: VllmConfig = VllmConfig(), + second_pass: bool = True, + prompt_update_config: PromptUpdateConfig = None): + + # Use default PromptUpdateConfig if none provided + if prompt_update_config is None: + prompt_update_config = PromptUpdateConfig() + + self.second_pass = second_pass + self.device = 'cuda' if torch.cuda.is_available() else 'cpu' + self.train_dtype = torch.bfloat16 if self.device == 'cuda' else torch.float32 + self.sandbox = CodeSandboxExecutor() + self.tokenizer = AutoTokenizer.from_pretrained(model_path) + + # PPO config and optimizer + self.ppo_config = ppo_config + + # vLLM related attributes + self.vllm_config = vllm_config + self._vllm_available = _VLLM_AVAILABLE and self.vllm_config.use_vllm + logger.info(f"Using Vllm for inference: {self._vllm_available}") + self._vllm_engine = None + + # Track current model path (HF id or local dir after training) + self._current_model_path = model_path + + # Prompt update configuration + self.prompt_update_config = prompt_update_config + + # Difficulty level system (1-5) + self.current_difficulty_level = prompt_update_config.initial_difficulty_level + self.difficulty_prompts = { + 1: LEVEL_1_PROMPT_TEXT, + 2: LEVEL_2_PROMPT_TEXT, + 3: LEVEL_3_PROMPT_TEXT, + 4: LEVEL_4_PROMPT_TEXT, + 5: LEVEL_5_PROMPT_TEXT + } + + self.prompts = [ + {"role": "system", "content": SYSTEM_PROMPT}, + {"role": "user", "content": self.difficulty_prompts[self.current_difficulty_level]}, + ] + + self.previous_problems = deque(maxlen=self.ppo_config.lookback) + self.previous_func_names = {} + + # Adaptive difficulty parameters + self.reward_history = deque(maxlen=self.prompt_update_config.reward_history_size) + + if not self._vllm_available: + self.model = AutoModelForCausalLM.from_pretrained(model_path) + self.model.to(self.device, dtype=self.train_dtype) + self.optimizer = AdamW(self.model.parameters(), lr=self.ppo_config.learning_rate) + + def _ensure_vllm_engine(self): + """Create vLLM engine if available and not already initialized.""" + if not self._vllm_available: + return + if self._vllm_engine is None: + # Instantiate vLLM engine with the current model path + self._vllm_engine = VLLMEngine( + model=self._current_model_path, + dtype='auto', + gpu_memory_utilization=self.vllm_config.gpu_memory_utilization, + max_model_len=self.vllm_config.max_model_len, + swap_space=self.vllm_config.swap_space, + tensor_parallel_size=self.vllm_config.tensor_parallel_size, + ) + + def _shutdown_vllm_engine(self): + """Tear down vLLM engine to free GPU memory before training.""" + if self._vllm_engine is not None: + try: + # Best-effort teardown; vLLM frees on GC + del self._vllm_engine + finally: + self._vllm_engine = None + torch.cuda.empty_cache() if torch.cuda.is_available() else None + + def update_prompt_difficulty(self, rewards: list[float]): + """ + Update the prompt based on recent vs earlier performance comparison. + If recent performance is significantly better than earlier performance, increase difficulty. + If recent performance is significantly worse than earlier performance, decrease difficulty. + """ + # Add new rewards to history + self.reward_history.extend(rewards) + + # Need enough data for both recent and earlier windows + min_required_samples = self.prompt_update_config.recent_window_size + self.prompt_update_config.earlier_window_size + if len(self.reward_history) < min_required_samples: + logger.info(f"Not enough reward history ({len(self.reward_history)} samples, need {min_required_samples}), keeping current difficulty level {self.current_difficulty_level}") + return + + # Calculate recent and earlier performance + recent_performance = self._calculate_recent_performance() + earlier_performance = self._calculate_earlier_performance() + + logger.info(f"Recent performance (last {self.prompt_update_config.recent_window_size}): {recent_performance:.3f}") + logger.info(f"Earlier performance (previous {self.prompt_update_config.earlier_window_size}): {earlier_performance:.3f}") + + # Determine if we should change difficulty + performance_diff = recent_performance - earlier_performance + new_difficulty_level = self._determine_difficulty_change(performance_diff) + + # Update difficulty level if it changed + if new_difficulty_level != self.current_difficulty_level: + old_level = self.current_difficulty_level + self.current_difficulty_level = new_difficulty_level + self.prompts[1]["content"] = self.difficulty_prompts[self.current_difficulty_level] + + difficulty_names = {1: "Beginner", 2: "Easy", 3: "Medium", 4: "Hard", 5: "Expert"} + direction = "increased" if new_difficulty_level > old_level else "decreased" + logger.info(f"Difficulty level {direction} from {old_level} ({difficulty_names[old_level]}) to {new_difficulty_level} ({difficulty_names[new_difficulty_level]}) based on performance diff: {performance_diff:.3f}") + else: + difficulty_names = {1: "Beginner", 2: "Easy", 3: "Medium", 4: "Hard", 5: "Expert"} + logger.info(f"Maintaining current difficulty level {self.current_difficulty_level} ({difficulty_names[self.current_difficulty_level]}) with performance diff: {performance_diff:.3f}") + + def _calculate_recent_performance(self) -> float: + """Calculate average performance over the most recent window.""" + recent_rewards = list(self.reward_history)[-self.prompt_update_config.recent_window_size:] + return sum(recent_rewards) / len(recent_rewards) + + def _calculate_earlier_performance(self) -> float: + """Calculate average performance over the earlier window (before recent window).""" + start_idx = -(self.prompt_update_config.recent_window_size + self.prompt_update_config.earlier_window_size) + end_idx = -self.prompt_update_config.recent_window_size + earlier_rewards = list(self.reward_history)[start_idx:end_idx] + return sum(earlier_rewards) / len(earlier_rewards) + + def _determine_difficulty_change(self, performance_diff: float) -> int: + """ + Determine new difficulty level based on performance difference. + Returns the new difficulty level (1-5). + """ + current_level = self.current_difficulty_level + + # If performance improved significantly, increase difficulty + if performance_diff > self.prompt_update_config.difficulty_change_threshold: + return min(current_level + 1, 5) # Cap at level 5 + + # If performance declined significantly, decrease difficulty + elif performance_diff < -self.prompt_update_config.difficulty_change_threshold: + return max(current_level - 1, 1) # Cap at level 1 + + # If performance change is within threshold, maintain current level + else: + return current_level + + def checkpoint_model(self, save_dir: str = "./proposer_ckpt"): + # Save to disk + if os.path.isdir(save_dir): + shutil.rmtree(save_dir) + os.makedirs(save_dir, exist_ok=True) + self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) + self._current_model_path = save_dir + + def _reload_vllm_engine_from_hf(self): + """ + Save HF model/tokenizer to a directory and recreate vLLM engine from it. + """ + # Recreate engine + if not self._vllm_available: + return + self._shutdown_vllm_engine() + self._ensure_vllm_engine() + + def _process_proposal(self, proposal_raw: str): + proposal = parse_json_from_fence(proposal_raw) + if proposal and 'question' in proposal and 'tests' in proposal: + result, success = self.sandbox.execute_with_validation(str(proposal["tests"])) # should run through interpreter without errors + if not success: + proposal = None + else: + logger.info(f'proposal cannot be parsed from fence') + return proposal + + def generate_proposal(self): + proposal = None + proposal_raw = None + + prompt = copy.deepcopy(self.prompts) + previous_func_names_with_only_repeated_questions = sorted([func_name for func_name, count in self.previous_func_names.items() if count > 1], key=lambda x: x[1], reverse=True) + previous_func_names_with_only_repeated_questions = previous_func_names_with_only_repeated_questions[:min(len(previous_func_names_with_only_repeated_questions), 20)] + prompt[1]['content'] += '\n'.join(str(previous_func_names_with_only_repeated_questions)) + '\n' + if self._vllm_available: + self._ensure_vllm_engine() + prompt_text = self.tokenizer.apply_chat_template( + prompt, + tokenize=False, + add_generation_prompt=True, + return_tensors=None, + ) + while proposal is None: + outputs = self._vllm_engine.generate([prompt_text], self.vllm_config.sampling) + proposal_raw = outputs[0].outputs[0].text + if self.second_pass: + cleanup_prompt = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": CLEANUP_PROMPT + proposal_raw} + ] + cleanup_prompt_text = self.tokenizer.apply_chat_template( + cleanup_prompt, + tokenize=False, + add_generation_prompt=True, + return_tensors=None, + enable_thinking=False + ) + outputs = self._vllm_engine.generate([cleanup_prompt_text], self.vllm_config.sampling) + proposal_raw = outputs[0].outputs[0].text + proposal = self._process_proposal(proposal_raw) + + else: + # Fallback to HF generate if vLLM not available + input_ids = self.tokenizer.apply_chat_template( + prompt, + tokenize=True, + add_generation_prompt=True, + return_tensors="pt", + ) + prompt_length = input_ids.size(1) + input_ids = input_ids.to(self.model.device) + while proposal is None: + response = self.model.generate( + input_ids, + max_new_tokens=4096, + do_sample=True, + top_k=300, + top_p=0.9, + temperature=1 + ) + generated_ids = response[0][prompt_length:] + proposal_raw = self.tokenizer.decode( + generated_ids, skip_special_tokens=True, + ) + if self.second_pass: + cleanup_prompt = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": CLEANUP_PROMPT + proposal_raw} + ] + cleanup_input_ids = self.tokenizer.apply_chat_template( + cleanup_prompt, + tokenize=True, + add_generation_prompt=True, + return_tensors="pt", + ) + cleanup_prompt_length = cleanup_input_ids.size(1) + cleanup_input_ids = cleanup_input_ids.to(self.model.device) + cleanup_response = self.model.generate( + cleanup_input_ids, + max_new_tokens=4096, + do_sample=True, + top_k=300, + top_p=0.9, + temperature=1 + ) + cleanup_generated_ids = cleanup_response[0][cleanup_prompt_length:] + proposal_raw = self.tokenizer.decode( + cleanup_generated_ids, skip_special_tokens=True, + ) + proposal = self._process_proposal(proposal_raw) + + # Add this check to ensure proposal is not None + if proposal is None or 'question' not in proposal or 'tests' not in proposal: + logger.error("Failed to generate a valid proposal after multiple attempts") + return None + + self.previous_problems.append(proposal['question']) + + func_name = extract_question_name(proposal['question']) + if func_name is not None: + if func_name in self.previous_func_names: + self.previous_func_names[func_name] += 1 + else: + self.previous_func_names[func_name] = 1 + logger.info(f"Previously asked questions: {self.previous_func_names}") + + proposal["proposal_raw"] = proposal_raw + return proposal + + def reward_fn(self, rewards: list[float]) -> float: + if len(rewards) == 0 or rewards is None: + return None + elif len(rewards) == 1: + return rewards[0] + else: + avg_reward = sum(rewards) / len(rewards) + if avg_reward in [0.0, 1.0]: + return 0.0 + else: + return 1 - avg_reward + + def _logprob_sum_for_generated( + self, + input_ids: torch.Tensor, + generated_ids: torch.Tensor, + gen_attention_mask: torch.Tensor | None = None, + ) -> torch.Tensor: + """ + Compute log-probabilities for the generated token sequence under the model. + input_ids: [B, T_in] or [1, T_in] + generated_ids: [B, T_gen] or [1, T_gen] + gen_attention_mask: optional [B, T_gen] (1 for real tokens, 0 for pad) + Returns: [B, T_gen, V] log-probs for each generated token position. + """ + if generated_ids.numel() == 0: + # Return an empty logprob tensor with correct batch dims + batch = input_ids.shape[0] + return torch.zeros((batch, 0, self.model.config.vocab_size), device=self.device, dtype=self.train_dtype) + + batch = input_ids.shape[0] + + prompt_attention = torch.ones_like(input_ids, device=input_ids.device) + if gen_attention_mask is None: + gen_attention_mask = torch.ones_like(generated_ids, device=input_ids.device) + + context = torch.cat([input_ids, generated_ids], dim=1) # [B, T_in + T_gen] + attention_mask = torch.cat([prompt_attention, gen_attention_mask], dim=1) # [B, T_in + T_gen] + outputs = self.model(context, attention_mask=attention_mask) + logits = outputs.logits[:, -generated_ids.shape[1]:, :] # [B, T_gen, V] + logprobs = F.log_softmax(logits, dim=-1) + return logprobs # [B, T_gen, V] + + def train(self, rewards, proposal): + """ + Perform a PPO update using the proposed trajectory and the provided reward(s). + Padding and masking are applied for batched proposals. + """ + if proposal is None: + logger.info("No proposal provided to train on.") + return + logger.info(f'proposals: {len(proposal)}') + # Before training, shut down vLLM to free memory; we'll reload after. + self._shutdown_vllm_engine() + + # Tokenize the chat prompt and the generated completion(s) + self.model.eval() + with torch.no_grad(): + base_input_ids = self.tokenizer.apply_chat_template( + self.prompts, + tokenize=True, + add_generation_prompt=True, + return_tensors="pt", + ).to(self.device) + + gen_batch = self.tokenizer( + proposal, + add_special_tokens=False, + return_tensors="pt", + padding=True, + ) + gen_ids = gen_batch["input_ids"].to(self.device) # [B, T_gen] + batch_size = gen_ids.size(0) + input_ids = base_input_ids.repeat(batch_size, 1) # [B, T_in] + + gen_attn = gen_batch.get("attention_mask", torch.ones_like(gen_ids)).to(self.device) + old_logprob = self._logprob_sum_for_generated(input_ids, gen_ids, gen_attention_mask=gen_attn).detach().to(self.device) + selected_old_logprobs = old_logprob.gather(dim=-1, index=gen_ids.unsqueeze(-1)).squeeze(-1) # [B, T_gen] + + # rewards: list[list[float]] -> compute per-sample scalar + adv_list = [] + for rlist in rewards: + rs = self.reward_fn(rlist) + adv_list.append(0.0 if rs is None else rs) + advantage = torch.tensor(adv_list, dtype=self.train_dtype, device=self.device) # [B] + advantage -= advantage.mean() + + mask = gen_attn.float() # [B, T_gen] + + valid_counts = mask.sum(dim=1).clamp_min(1.0) # [B] + + self.model.train() + for _ in range(self.ppo_config.ppo_epochs): + self.optimizer.zero_grad() + # Recompute current logprob under updated policy + new_logprob = self._logprob_sum_for_generated(input_ids, gen_ids, gen_attention_mask=gen_attn) + selected_new_logprobs = new_logprob.gather(dim=-1, index=gen_ids.unsqueeze(-1)).squeeze(-1) # [B, T_gen] + + # PPO ratio per token + kl_per_token = (selected_new_logprobs - selected_old_logprobs) # [B, T_gen] + ratio = torch.exp(kl_per_token) # [B, T_gen] + + # Clipped objective per token, scale by per-sample advantage + adv_expanded = advantage.unsqueeze(-1) # [B, 1] + unclipped = ratio * adv_expanded # [B, T_gen] + clipped_ratio = torch.clamp(ratio, 1.0 - self.ppo_config.clip_range, 1.0 + self.ppo_config.clip_range) + clipped = clipped_ratio * adv_expanded # [B, T_gen] + per_token_loss = -torch.min(unclipped, clipped) # [B, T_gen] + # Mask padding and compute mean per sequence, then mean over batch + masked_loss = (per_token_loss * mask).sum(dim=1) / valid_counts # [B] + policy_loss = masked_loss.mean() # scalar + + # KL penalty + masked_kl = (kl_per_token * mask).sum(dim=1) / valid_counts # [B] + mean_kl = masked_kl.mean() # scalar + + tot_loss = policy_loss + self.ppo_config.beta * mean_kl + tot_loss.backward() + torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0) + self.optimizer.step() + + self.checkpoint_model() + # After training, reload vLLM engine from updated HF weights + self._reload_vllm_engine_from_hf() diff --git a/code_gen_exp/src/proposer_service.py b/code_gen_exp/src/proposer_service.py new file mode 100644 index 0000000..b9dd9e6 --- /dev/null +++ b/code_gen_exp/src/proposer_service.py @@ -0,0 +1,185 @@ +from dataclasses import dataclass +import logging +import random +from genrl.communication.hivemind.hivemind_backend import HivemindBackend, HivemindRendezvouz +from code_gen_exp.src.coordinator import ModalSwarmCoordinator +from code_gen_exp.src.proposer import Proposer, PPOConfig, VllmConfig, PromptUpdateConfig + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +@dataclass +class ProposerServiceConfig: + model: str + num_proposals: int + train_batch_size: int + identity_path: str + startup_timeout: int + beam_size: int + get_retries: int + max_round: int + do_training: bool = False + second_pass: bool = True + prompt_update_config: PromptUpdateConfig = None + + +class ProposerClientDHT: + def __init__(self, backend: HivemindBackend): + self.backend = backend + + def insert_proposal(self, proposer_model: str, proposals: list[dict]): + objs = [{ + "proposer_model": proposer_model, + "proposal_question": proposal_dict["question"], + "proposal_tests": proposal_dict["tests"], + "proposal_raw": proposal_dict["proposal_raw"], + } for proposal_dict in proposals] + try: + self.backend.put(objs, sub_key="proposer".encode()) + except Exception as e: + get_logger().debug(f"Failed to insert proposals: {e}") + + def request_training_data(self, train_batch_size: int) -> list[dict]: + data = [] + try: + obj_ = self.backend.get(sub_key="solver".encode()) + except Exception as e: + get_logger().debug(f"Failed to get training data: {e}") + + if obj_ is None or len(obj_) == 0: + return data + + objs = list(obj_.values()) + + # Batching data so this is a nested list + for list_of_samples in objs: + for sample in list_of_samples: + if sample['dataset'] == 'proposer': + data.append(sample) + + if len(data) > train_batch_size: + data = random.sample(data, train_batch_size) + return data + + +class ProposerService: + def __init__(self, + service_config: ProposerServiceConfig, + ppo_config: PPOConfig, + vllm_config: VllmConfig, + prompt_update_config: PromptUpdateConfig, + coordinator: ModalSwarmCoordinator = None): + + initial_peers = coordinator.get_bootnodes() if coordinator is not None else None + backend = HivemindBackend( + identity_path=service_config.identity_path, + startup_timeout=service_config.startup_timeout, + beam_size=service_config.beam_size, + get_retries=service_config.get_retries, + initial_peers=initial_peers, + ) + proposer_client = ProposerClientDHT(backend) + + if vllm_config.use_vllm: + assert not service_config.do_training, "VLLM is not compatible with training" + + self.proposer_client = proposer_client + self.coordinator = coordinator + self.proposer = Proposer( + service_config.model, + ppo_config, + vllm_config, + second_pass=service_config.second_pass, + prompt_update_config=prompt_update_config + ) + logger.info(f'Proposer initialized with model {service_config.model}') + self.config = service_config + self.prompt_update_frequency = prompt_update_config.prompt_update_frequency + + def insert(self): + try: + model_name = self.proposer.model.name_or_path + except AttributeError: + model_name = "none" + proposals = [] + for _ in range(self.config.num_proposals): + proposal = self.proposer.generate_proposal() + if proposal is not None: + proposals.append(proposal) + self.proposer_client.insert_proposal(model_name, proposals) + logger.info(f"{len(proposals)} proposals inserted") + + def update_proposer_prompt(self): + """ + Fetch recent training data, extract rewards, and update prompt difficulty + based on solver performance. + """ + training_data = self.proposer_client.request_training_data(self.config.train_batch_size) + if len(training_data) == 0: + logger.info("No training data found") + return + + # Flatten all rewards from training data + # Each sample may have a list of rewards or a single reward + rewards = [] + for sample in training_data: + reward = sample.get("reward", None) + if reward is not None: + if isinstance(reward, list): + # If reward is a list, flatten it + rewards.extend([r for r in reward if r is not None]) + else: + rewards.append(reward) + + if len(rewards) == 0: + logger.info("No rewards found in training data") + return + + # Update the proposer's prompt based on recent rewards + self.proposer.update_prompt_difficulty(rewards) + logger.info(f"Prompt difficulty updated based on {len(rewards)} reward samples") + + + def train(self): + + training_data = self.proposer_client.request_training_data(self.config.train_batch_size) + if len(training_data) == 0: + logger.info("No training data found") + return + elif len(training_data) > self.config.train_batch_size: + logger.info("Training data is larger than batch size") + training_data = training_data[:self.config.train_batch_size] + + rewards = [] + proposals = [] + for sample in training_data: + rewards.append(sample["reward"]) + proposals.append(sample["proposal_raw"]) + + + if len(rewards) == 0: + logger.info("No training data found") + return + + logger.info(f"Training with {len(proposals)} proposals") + + self.proposer.train(rewards, proposals) + logger.info(f"Training completed") + + + def run(self): + logger.info("Starting proposer service") + iteration = 0 + + while True: + self.insert() + + # Update prompt difficulty based on recent rewards + if iteration % self.prompt_update_frequency == 0: + self.update_proposer_prompt() + + if self.config.do_training: + self.train() + + iteration += 1 + diff --git a/code_gen_exp/src/solver_data.py b/code_gen_exp/src/solver_data.py new file mode 100644 index 0000000..06f2326 --- /dev/null +++ b/code_gen_exp/src/solver_data.py @@ -0,0 +1,391 @@ +import re +from typing import Any, Dict, List, Tuple +import random +from copy import deepcopy +import logging + +from datasets import Dataset, load_dataset, concatenate_datasets + +from genrl.data import DataManager +from genrl.logging_utils.global_defs import get_logger +from genrl.misc_utils.utils import generate_md5_hash_id +from genrl.state import GameState, WorldState +from genrl.communication.hivemind.hivemind_backend import HivemindBackend +from code_gen_exp.src.utils.solver_data_mapper import MBPPMapper, CodeContestsMapper + +# Suppress Hugging Face retry warnings, but keep download progress bar +# Progress bar uses tqdm which outputs directly to stdout, not logging +logging.getLogger("huggingface_hub.utils._http").setLevel(logging.ERROR) +# Keep file_download at WARNING to see progress, but suppress retry messages +logging.getLogger("huggingface_hub.file_download").setLevel(logging.WARNING) + + +SYSTEM_PROMPTS = { + "default": "You are a helpful assistant.", + "solver": "You are an expert coding assistant, your job is to provide the highest quality solution to a given problem. The solution should be easily parseable by a computer. Do not include any additional keys or commentary. The solution must be valid python code. The solution should be executable python, with no other text. do not include any tests or verification, just the solution as a function" +} + +MAX_PROPOSER_PROMPT_LENGTH = 2000 + +def build_prompt(flattened_data: Any) -> Any: + """ + Top-level mapping function to build prompts for the LLM. + Defined at module scope to ensure picklability for datasets caching/fingerprinting. + """ + prompt = [ + {"role": "system", "content": flattened_data["system_prompt"]}, + {"role": "user", "content": flattened_data["user_prompt"]}, + ] + return {"prompt": prompt} + + +def add_source_dataset(sample, ds_name): + sample['original_dataset'] = ds_name + return sample + + +class CodeGenerationDataManager(DataManager): + """Data Manager for Code Generation Datasets. + + This class integrates code generation datasets with genrl + data management framework, providing infinite iteration through reseeding. + """ + + def __init__( + self, + system_prompt: str = "default", + batch_size: int = 2, + local_batch_size: int = 1, + proposer_batch_size: int = 1, + **kwargs, + ): + """Initialize the CodeGenerationDataManager. + + Args: + """ + + self.system_prompt = SYSTEM_PROMPTS.get( + system_prompt, SYSTEM_PROMPTS["default"] + ) + self.num_generations = kwargs.get("num_generations", None) + self.num_transplant_trees = kwargs.get("num_transplant_trees", 1) + assert self.num_transplant_trees >= 0 + + get_logger().info("Loading dataset: google-research-datasets/mbpp (streaming mode)") + self.local_dataset_mbpp = load_dataset("google-research-datasets/mbpp", streaming=True) + self.local_dataset_mbpp = self.local_dataset_mbpp.map(lambda x: add_source_dataset(x, 'mbpp')) + + get_logger().info("Loading dataset: deepmind/code_contests (streaming mode)") + get_logger().info("Note: Files will be downloaded on first access. Download progress will be shown.") + self.local_dataset_cc = load_dataset("deepmind/code_contests", streaming=True) + self.local_dataset_cc = self.local_dataset_cc.map(lambda x: add_source_dataset(x, 'code_contests')) + get_logger().info("Datasets metadata loaded. Actual file download happens on first data access.") + + self.local_dataset = concatenate_datasets([self.local_dataset_mbpp['train'], + self.local_dataset_cc['train']]) + + self.local_batch_size = local_batch_size + self.batch_size = batch_size + self.proposer_batch_size = proposer_batch_size + assert self.local_batch_size + self.proposer_batch_size == self.batch_size, f"Batch sizes must sum to total batch size, got {self.local_batch_size} and {self.proposer_batch_size}" + + self.local_dataset = self.local_dataset.batch(batch_size=self.local_batch_size) + self.local_dataset_iter = iter(self.local_dataset) + + def initialize(self, backend: HivemindBackend): + self.backend = backend + self.peer_id = str(backend.get_id()) + + # --- Helper Methods --- + def state_to_user_prompt(self, state: WorldState) -> str: + """Convert the state to a user prompt.""" + return state.environment_states["question"] + + def state_to_test(self, state: WorldState) -> str: + """Extract the test from the state.""" + return state.environment_states["test"] + + def flatten_tree( + self, inputs: Dict[Any, Dict[Any, List[Tuple[Any]]]], stage: int + ) -> Tuple[Dict[str, List[Any]], Dict[int, Tuple[int, int, int]]]: + + flattened_input = { + "system_prompt": [], + "user_prompt": [], + "test": [], + "metadata": [], + } + index_mapping = {} + cur_idx = 0 + for agent in inputs: + for batch_id in inputs[agent]: + for node_idx, state in enumerate(inputs[agent][batch_id]): + flattened_input["system_prompt"].append(self.system_prompt) + flattened_input["user_prompt"].append(self.state_to_user_prompt(state)) + flattened_input["test"].append(self.state_to_test(state)) + if "metadata" in state.environment_states: + flattened_input["metadata"].append(state.environment_states["metadata"]) + elif state.metadata is not None: + flattened_input["metadata"].append(state.metadata) + else: + flattened_input["metadata"].append({}) + + index_mapping[cur_idx] = (agent, batch_id, node_idx) + cur_idx += 1 + return flattened_input, index_mapping + + def prepare_input( + self, inputs: Dict[Any, Dict[Any, List[Tuple[Any]]]], stage: int = None + ) -> Tuple[Dataset, Dict[int, Tuple[int, int, int]]]: + input_flattened, index_mapping = self.flatten_tree(inputs, stage) + + # Check if we have any data to process + if not input_flattened or all(len(v) == 0 for v in input_flattened.values()): + # Return empty dataset and empty mapping + return Dataset.from_dict({"prompt": []}), {} + + try: + input_flattened = Dataset.from_dict(input_flattened) + input_prepared = input_flattened.map(build_prompt) + return input_prepared, index_mapping + except: + return Dataset.from_dict({"prompt": []}), {} + + def prepare_actions( + self, outputs: Any, index_mapping: Dict[int, Tuple[Any]] + ) -> Dict[Any, List[List[Any]]]: + actions = {} + for idx, model_output in enumerate(outputs): + agent, batch_id, node_idx = index_mapping[idx] + if agent not in actions: + actions[agent] = {} + if batch_id not in actions[agent]: + actions[agent][batch_id] = {} + actions[agent][batch_id][node_idx] = model_output + return actions + + def to_world_state( + self, + node_state: WorldState, + ) -> WorldState: + environment_state = node_state.environment_states + environment_state["prior_stage_input_states"] = deepcopy(node_state) + opponent_state = None + personal_state = None + world_state = WorldState( + environment_states=environment_state, + opponent_states=opponent_state, + personal_states=personal_state, + ) + return world_state + + def prepare_states( + self, current_state: GameState, swarm_states: Dict[Any, Any] + ) -> Dict[Any, Dict[Any, List[Tuple[Any]]]]: + latest_state = current_state.get_latest_state() + for agent in latest_state: + for batch_id in latest_state[agent]: + for node_idx, node_state in enumerate(latest_state[agent][batch_id]): + latest_state[agent][batch_id][node_idx] = ( + self.to_world_state(node_state) + ) + return latest_state + + + def prepare_states( + self, current_state: GameState, swarm_states: Dict[Any, Any] + ) -> Dict[Any, Dict[Any, List[Tuple[Any]]]]: + if self.num_transplant_trees > 0: + trees = current_state.trees + transplants = self.transplant_trees( + current_state, swarm_states, self.num_transplant_trees + ) + for pair in transplants: + agent, batch_id = pair + if agent not in trees: + trees[agent] = {} + if batch_id not in trees[agent]: + trees[agent][batch_id] = None + payload = transplants[pair] + received_states, received_actions, received_metadata = ( + payload.world_state, + payload.actions, + payload.metadata, + ) + world_state = received_states.environment_states + payload_batch_id = generate_md5_hash_id(world_state["question"]) + assert payload_batch_id == batch_id + if ( + trees[agent][batch_id] is None + ): # we don't have a tree for this batch item, make one and append actions + trees[agent][batch_id] = current_state.game_tree_factory( + received_states + ) + trees[agent][batch_id].append_node_actions( + stage=current_state.stage, node_idx=0, actions=received_actions + ) + trees[agent][batch_id][current_state.stage][0][ + "metadata" + ] = received_metadata + else: # we already have this tree, and actions were appended in run_game_stage() + pass + world_state = current_state.get_latest_state() + return world_state + + def transplant_trees( + self, + current_state: GameState, + swarm_states: Dict[Any, Any], + num_transplants: int, + ) -> Dict[Tuple[Any], Any]: + # Loop through and return a set of num_transplant transplants to add + transplants = {} + for agent in swarm_states: + if agent not in current_state.trees: + for batch_id in swarm_states[agent]: + for payload in swarm_states[agent][batch_id]: + if ( + self.num_generations + and hasattr(payload, "actions") + and payload.actions is not None + and isinstance(payload.actions, list) + and len(payload.actions) == self.num_generations + ): + transplants[(agent, batch_id)] = payload + if len(transplants) >= num_transplants: + keepers = random.sample(list(transplants), num_transplants) + else: + keepers = list(transplants) + + return {key: transplants[key] for key in keepers} + + def get_eval_data(self): + pass + + def get_round_data(self): + if self.proposer_batch_size > 0: + try: + proposer_data = self.backend.get(sub_key="proposer".encode()) + proposer_data = prepare_proposer_batch(proposer_data, self.proposer_batch_size) + except Exception as e: + get_logger().debug(f"Exception while getting proposer data: {e}") + proposer_data = [] + else: + proposer_data = [] + + if self.local_batch_size > 0: + try: + # First access to streaming dataset may trigger file download + # Progress bar will be shown if files need to be downloaded + local_data = next(self.local_dataset_iter) + except StopIteration: + self.local_dataset_iter = iter(self.local_dataset) + local_data = next(self.local_dataset_iter) + local_data = prepare_local_batch(local_data) + else: + local_data = [] + + combined_data = local_data + proposer_data + return combined_data + + def send_response(self, rewards, state): + objs = [] + for agent_id in state: + for batch_id in state[agent_id]: + node_idx = 0 # don't need to send each generation + node = state[agent_id][batch_id][node_idx] + proposal_raw = node.personal_states + dataset = node.environment_states['metadata']['dataset'] + batch_rewards = rewards[agent_id][batch_id][node_idx] + + if dataset != 'proposer': + continue + obj = { + 'proposal_raw': proposal_raw, + 'reward': batch_rewards, + 'dataset': dataset + } + objs.append(obj) + try: + self.backend.put(objs, sub_key="solver".encode()) + except Exception as e: + get_logger().debug(f"Failed to send response: {e}") + + + +# Registry mapping dataset names to their respective mappers +DATASET_MAPPERS = { + 'mbpp': MBPPMapper(), + 'code_contests': CodeContestsMapper() +} + +def prepare_local_batch(batch) -> list[tuple[int, WorldState]]: + original_dataset = batch.get('original_dataset', []) + + prompts = [] + tests = [] + for i, ds in enumerate(original_dataset): + mapper = DATASET_MAPPERS.get(ds) + if mapper is None: + raise ValueError(f"No mapper found for dataset: {ds}. " + f"Available datasets: {list(DATASET_MAPPERS.keys())}") + + prompt = mapper.map_prompt(batch, i) + test = mapper.map_test(batch, i) + prompts.append(prompt) + tests.append(test) + + local_data = [] + for prompt, test, ds in zip(prompts, tests, original_dataset): + mapper = DATASET_MAPPERS[ds] + question = mapper.format_question(prompt, test) + + env_state = { + "question": question, + "test": test, + "metadata": {'dataset': ds} + } + world_state = WorldState( + environment_states=env_state, + opponent_states=None, + personal_states=None, + ) + proposal_id = generate_md5_hash_id(env_state["question"]) + local_data.append([proposal_id, world_state]) + + return local_data + +def prepare_proposer_batch(batch: dict[str, list[dict]], batch_size: int) -> list[tuple[int, WorldState]]: + proposer_data = [] + for proposer_id in batch: + proposal_list = batch[proposer_id] + for proposal in proposal_list: + proposal_question = str(proposal['proposal_question'])[:MAX_PROPOSER_PROMPT_LENGTH] + proposal_tests = str(proposal['proposal_tests'])[:MAX_PROPOSER_PROMPT_LENGTH] + proposal_raw = str(proposal['proposal_raw'])[:MAX_PROPOSER_PROMPT_LENGTH] + + env_state = { + "question": proposal_question, + "test": proposal_tests, + "metadata": {'dataset': 'proposer'} + } + world_state = WorldState( + environment_states=env_state, + opponent_states=None, + personal_states=proposal_raw, + ) + proposal_id = generate_md5_hash_id(env_state["question"]) + + proposer_data.append([proposal_id, world_state]) + + if len(proposer_data) >= batch_size: + return proposer_data + + return proposer_data + +def parse_python_fence(text): + match = re.search(r'```python(.*?)```', text, re.DOTALL) + if match: + python_string = match.group(1).strip() + return python_string + return 'No python fence found in solution' diff --git a/code_gen_exp/src/solver_rewards.py b/code_gen_exp/src/solver_rewards.py new file mode 100644 index 0000000..e34079a --- /dev/null +++ b/code_gen_exp/src/solver_rewards.py @@ -0,0 +1,120 @@ +from dataclasses import dataclass +from typing import Any +import ollama +from transformers import AutoTokenizer +from code_gen_exp.src.utils.solver_utils import ( + check_eos, + parse_python_fence, + parse_response, + get_solutions, + get_unittests, + get_questions, + get_dataset, +) + +@dataclass +class RewardsOllamaConfig: + model: str = "qwen2.5-coder:1.5b-instruct" + temperature: float = 0.0 + num_predict: int = 512 + + +class CodeGenerationRewards: + def __init__(self, solver_tokenizer_path: str, solver_token_lim: int, ollama_config: RewardsOllamaConfig = RewardsOllamaConfig()): + self.stage = 0 + self.model = ollama_config.model + self.temperature = ollama_config.temperature + self.num_predict = ollama_config.num_predict + self.tokenizer = AutoTokenizer.from_pretrained(solver_tokenizer_path, padding_side="left") + self.solver_token_lim = solver_token_lim + + + def _build_prompt(self, dataset: str, solution_code: str, unit_tests: str, question: str) -> str: + if dataset == 'mbpp': + return ("You are an expert programming evaluator who needs to decide whether the given solution will pass all the given unit tests.\n" + "You will be given a problem, a list of unit tests, and a solution.\n" + "Walk through each unit test and dry run the solution.\n" + "If the solution passes all the unit tests, put is_correct as true.\n" + "If the solution fails even a single unit test, put is_correct as false.\n" + "Put you final answer in a JSON fenced block. The JSON should have only one key: is_correct. It should be a boolean.\n" + "Its format should be as follows:\n" + "```json\n{\n \"is_correct\": true | false\n}\n```\n\n" + "--- Problem ---\n" + f"{question}\n\n" + "--- Unit Tests ---\n" + f"{unit_tests}\n\n" + "--- Solution ---\n" + f"{solution_code}\n\n" + ) + elif dataset == 'code_contests': + return ("You are an expert programming evaluator who needs to decide whether the given solution will pass all the given unit tests.\n" + "With the code you will be given the inputs for unit tests along with the associated outputs. \n" + "Decide if the given python code will produce the given outputs when run against the inputs. \n" + "Put you final answer in a JSON fenced block. The JSON should have only one key: is_correct. It should be a boolean.\n" + "Its format should be as follows:\n" + "```json\n{\n \"is_correct\": true | false\n}\n```\n\n" + "--- Problem ---\n" + f"{question}\n\n" + "--- Unit Tests ---\n" + f"{unit_tests}\n\n" + "--- Solution ---\n" + f"{solution_code}\n\n" + ) + + def _extract_json(self, text: str) -> Any: + match = re.search(r"```json\s*(\{[\s\S]*?\})\s*```", text, re.IGNORECASE) + if match: + return json.loads(match.group(1)) + return json.loads(text) + + def reward_fn(self, dataset, solutions, unittests, question): + rewards = [] + for solution in solutions: + if not isinstance(solution, str): + reward = -1.2 + else: + parsed_code = parse_python_fence(solution) + eos_found = check_eos(solution, self.tokenizer, self.solver_token_lim) + if parsed_code is None: # No fenced code found + reward = -1.0 + else: + try: + prompt = self._build_prompt(dataset, str(solution), str(unittests), str(question)) + response = ollama.generate(model=self.model, prompt=prompt, options={"temperature": self.temperature, "num_predict": self.num_predict}) + raw_text = response.response + reward = parse_response(raw_text) + if reward is None: + reward = 0.0 + except: + reward = 0.0 + reward += 0.2 if eos_found else -0.2 + rewards.append(reward) + + return rewards + + + + def __call__(self, game_state): + solutions_by_agent = get_solutions(game_state, self.stage) + unittests_by_agent = get_unittests(game_state, self.stage) + questions = get_questions(game_state, self.stage) + datasets_by_agent = get_dataset(game_state, self.stage) + + rewards = {} # Key per agent + try: + for agent in solutions_by_agent: + rewards[agent] = {} # Will store a list per batch item + for batch_id in solutions_by_agent[agent]: + rewards[agent][batch_id] = [] + for node_idx, _ in enumerate(solutions_by_agent[agent][batch_id]): + rewards[agent][batch_id].append( + self.reward_fn( + datasets_by_agent[agent][batch_id][node_idx], + solutions_by_agent[agent][batch_id][node_idx], + unittests_by_agent[agent][batch_id][node_idx], + questions[agent][batch_id][node_idx] + ) + ) + return rewards + except Exception as e: + return {} diff --git a/code_gen_exp/src/trainer.py b/code_gen_exp/src/trainer.py new file mode 100644 index 0000000..89fe964 --- /dev/null +++ b/code_gen_exp/src/trainer.py @@ -0,0 +1,76 @@ +from typing import Any, List + +import torch +from genrl.data import DataManager +from genrl.logging_utils.ml_logger import LoggerMixin +from genrl.rewards import RewardManager +from genrl.state import GameState +from genrl.trainer.grpo_trainer import GRPOLanguageTrainerModule +from code_gen_exp.src.utils.judge_client import JudgeClient +from code_gen_exp.src.solver_data import SYSTEM_PROMPTS + +class GRPOTrainerModule(GRPOLanguageTrainerModule, LoggerMixin): + """ + Trainer for the Group Relative Policy Optimization (GRPO) method. + Implements the TrainerModule interface defined in base_trainer.py. + """ + + def __init__(self, models: List[Any], **kwargs): + """ + Initialize the GRPO trainer module. + + Args: + models: List containing the model to be trained. + **kwargs: Additional arguments for configuration. + """ + super().__init__(models, **kwargs) + judge_base_url = kwargs.get("judge_base_url", None) + self.judge_client = JudgeClient(judge_base_url) if judge_base_url else None + self.system_prompt = SYSTEM_PROMPTS.get("solver", SYSTEM_PROMPTS["default"]) + + @torch.no_grad() + def evaluate( + self, state: GameState, data_manager: DataManager, reward_manager: RewardManager + ): + if not self.judge_client: + return + + try: + model_name = self.model.name_or_path + except AttributeError: + model_name = "none" + + # Request question from judge service + result = self.judge_client.request_question( + user_id=state.peer_id, + round_number=state.round, + model_name=model_name + ) + + if not result: + return + + # Generate answer using the model + prompt = [ + {"role": "system", "content": self.system_prompt}, + {"role": "user", "content": result["question"]}, + ] + input_ids = self.processing_class.apply_chat_template( + prompt, + tokenize=True, + add_generation_prompt=True, + return_tensors="pt", + ) + + input_ids = input_ids.to(self.model.device) + outputs = self.model.generate(input_ids, max_new_tokens=self.args.max_new_tokens) + answer = self.processing_class.decode( + outputs[0], skip_special_tokens=True + ) + + # Submit answer to judge service + self.judge_client.submit_answer( + session_id=result["session_id"], + round_number=state.round, + user_answer=answer + ) \ No newline at end of file diff --git a/code_gen_exp/src/utils/judge_client.py b/code_gen_exp/src/utils/judge_client.py new file mode 100644 index 0000000..dd7a547 --- /dev/null +++ b/code_gen_exp/src/utils/judge_client.py @@ -0,0 +1,92 @@ +import requests +from typing import Dict, Any, Optional +from genrl.logging_utils.global_defs import get_logger + + +class JudgeClient: + """ + Client for interacting with the judge API service. + Handles question requests and answer submissions. + """ + + def __init__(self, base_url: str): + """ + Initialize the judge client. + + Args: + base_url: Base URL for the judge API service + """ + self.base_url = base_url.rstrip('/') + self.logger = get_logger() + + def request_question(self, user_id: str, round_number: int, model_name: str) -> Optional[Dict[str, Any]]: + """ + Request a question from the judge service. + + Args: + user_id: ID of the user/peer + round_number: Current round number + model_name: Name of the model being used + + Returns: + Dictionary containing question data or None if request failed + """ + try: + request_data = { + "user_id": user_id, + "round_number": round_number, + "model_name": model_name, + } + + response = requests.post( + f"{self.base_url}/request-question/", + json=request_data + ) + + if response.status_code == 200: + result = response.json() + self.logger.debug(f'Received question: {result["question"]}') + return result + else: + self.logger.debug(f"Failed to receive question: {response.status_code}") + return None + + except Exception as e: + self.logger.debug(f"Failed to request question: {e}") + return None + + def submit_answer(self, session_id: str, round_number: int, user_answer: str) -> Optional[Dict[str, Any]]: + """ + Submit an answer to the judge service. + + Args: + session_id: Session ID from the question request + round_number: Current round number + user_answer: The user's answer to submit + + Returns: + Dictionary containing score data or None if submission failed + """ + try: + submission_data = { + "session_id": session_id, + "round_number": round_number, + "user_answer": user_answer, + } + + response = requests.post( + f"{self.base_url}/submit-answer/", + json=submission_data + ) + + if response.status_code == 200: + result = response.json() + self.logger.debug(f"Score: {result['score']}") + return result + else: + self.logger.debug(f"Failed to submit answer: {response.status_code}") + return None + + except Exception as e: + self.logger.debug(f"Failed to submit answer: {e}") + return None \ No newline at end of file diff --git a/code_gen_exp/src/utils/name_utils.py b/code_gen_exp/src/utils/name_utils.py new file mode 100644 index 0000000..a0cc401 --- /dev/null +++ b/code_gen_exp/src/utils/name_utils.py @@ -0,0 +1,99 @@ +import hashlib +from functools import lru_cache +from typing import Sequence + +# fmt: off +ADJECTIVES = [ + "agile", "alert", "amphibious", "aquatic", "arctic", "armored", "barky", "beaked", + "bellowing", "bipedal", "bold", "bristly", "burrowing", "camouflaged", "carnivorous", "chattering", + "clawed", "climbing", "coiled", "colorful", "crested", "cunning", "curious", "dappled", + "darting", "deadly", "deft", "dense", "dextrous", "diving", "docile", "domestic", + "dormant", "downy", "durable", "eager", "elusive", "endangered", "energetic", "enormous", + "exotic", "extinct", "fanged", "fast", "feathered", "feline", "ferocious", "fierce", + "finicky", "fishy", "flapping", "fleecy", "flexible", "flightless", "fluffy", "foraging", + "foxy", "freckled", "frisky", "furry", "galloping", "gentle", "giant", "gilded", + "gliding", "graceful", "grassy", "grazing", "gregarious", "grunting", "hairy", "hardy", + "hibernating", "hoarse", "horned", "howling", "huge", "hulking", "humming", "hunting", + "insectivorous", "invisible", "iridescent", "jagged", "jumping", "keen", "knobby", "lanky", + "large", "lazy", "leaping", "leggy", "lethal", "lightfooted", "lithe", "lively", + "long", "loud", "lumbering", "majestic", "mammalian", "mangy", "marine", "masked", + "meek", "melodic", "mighty", "mimic", "miniature", "moist", "monstrous", "mottled", + "muscular", "mute", "nasty", "nimble", "nocturnal", "noisy", "omnivorous", "opaque", + "padded", "pale", "patterned", "pawing", "peaceful", "peckish", "pensive", "pesty", + "placid", "playful", "plump", "poisonous", "polished", "pouncing", "powerful", "prehistoric", + "prickly", "prowling", "pudgy", "purring", "quick", "quiet", "rabid", "raging", + "rangy", "rapid", "ravenous", "reclusive", "regal", "reptilian", "restless", "roaring", + "robust", "rough", "rugged", "running", "savage", "scaly", "scampering", "scavenging", + "scented", "screeching", "scruffy", "scurrying", "secretive", "sedate", "shaggy", "sharp", + "shiny", "short", "shrewd", "shy", "silent", "silky", "singing", "sizable", + "skilled", "skittish", "sleek", "slender", "slimy", "slithering", "slow", "sly", + "small", "smooth", "snappy", "sneaky", "sniffing", "snorting", "soaring", "soft", + "solitary", "spotted", "sprightly", "squeaky", "squinting", "stalking", "stealthy", "stinging", + "stinky", "stocky", "striped", "strong", "stubby", "sturdy", "subtle", "swift", + "tall", "tame", "tangled", "tawny", "tenacious", "territorial", "thick", "thorny", + "thriving", "timid", "tiny", "toothy", "tough", "tricky", "tropical", "trotting", + "twitchy", "unseen", "untamed", "vicious", "vigilant", "vocal", "voracious", "waddling", + "wary", "webbed", "whiskered", "whistling", "wild", "wily", "winged", "wiry", + "wise", "woolly", "yapping", "yawning", "zealous" +] + +ANIMALS = [ + "aardvark", "albatross", "alligator", "alpaca", "anaconda", "ant", "anteater", "antelope", + "ape", "armadillo", "baboon", "badger", "barracuda", "bat", "bear", "beaver", + "bee", "bison", "boar", "bobcat", "buffalo", "butterfly", "camel", "capybara", + "caribou", "cassowary", "cat", "caterpillar", "cheetah", "chicken", "chameleon", "chimpanzee", + "chinchilla", "clam", "cobra", "cockroach", "cod", "condor", "coral", "cougar", "cow", + "coyote", "crab", "crane", "crocodile", "crow", "deer", "dingo", "dinosaur", + "dog", "dolphin", "donkey", "dove", "dragonfly", "duck", "eagle", "eel", + "elephant", "elk", "emu", "falcon", "ferret", "finch", "fish", "flamingo", + "flea", "fly", "fox", "frog", "gazelle", "gecko", "gerbil", "gibbon", + "giraffe", "goat", "goose", "gorilla", "grasshopper", "grouse", "gull", "hamster", + "hare", "hawk", "hedgehog", "heron", "hippo", "hornet", "horse", "hummingbird", + "hyena", "ibis", "iguana", "impala", "jackal", "jaguar", "jay", "jellyfish", + "kangaroo", "kingfisher", "kiwi", "koala", "komodo", "ladybug", "lemur", "leopard", + "lion", "lizard", "llama", "lobster", "locust", "lynx", "macaque", "macaw", + "magpie", "mallard", "mammoth", "manatee", "mandrill", "mantis", "marmot", "meerkat", + "mink", "mole", "mongoose", "monkey", "moose", "mosquito", "mouse", "mule", + "narwhal", "newt", "nightingale", "ocelot", "octopus", "okapi", "opossum", "orangutan", "ostrich", + "otter", "owl", "ox", "panda", "panther", "parrot", "peacock", "pelican", + "penguin", "pheasant", "pig", "pigeon", "piranha", "platypus", "porcupine", "porpoise", + "prawn", "puffin", "puma", "python", "quail", "rabbit", "raccoon", + "ram", "rat", "raven", "reindeer", "rhino", "robin", "rooster", "salamander", + "salmon", "sandpiper", "sardine", "scorpion", "seahorse", "seal", "sealion", + "shark", "sheep", "shrew", "shrimp", "skunk", "sloth", "slug", "snail", + "snake", "sparrow", "spider", "squid", "squirrel", "starfish", "stingray", "stork", + "swan", "tamarin", "tapir", "tarantula", "termite", "tiger", "toad", "tortoise", + "toucan", "trout", "tuna", "turkey", "turtle", "viper", "vulture", "wallaby", + "walrus", "warthog", "wasp", "weasel", "whale", "wildebeest", "wolf", "wombat", + "woodpecker", "worm", "yak", "zebra" +] +# fmt: on + + +def hex_to_ints(s, k): + """Converts hex-encoded strings to int lists. Specify chunk size with k.""" + return tuple(int(s[i : i + k], 16) for i in range(0, len(s), k)) + + +# libp2p peer IDs are always base58-encoded multihashes! + + +@lru_cache +def get_name_from_peer_id(peer_id: str, no_spaces=False): + # ~200 entries for both lists; so 2 hex digits. + ints = hex_to_ints(hashlib.md5(peer_id.encode()).hexdigest(), 2) + adj1 = ADJECTIVES[ints[2] % len(ADJECTIVES)] + adj2 = ADJECTIVES[ints[1] % len(ADJECTIVES)] + animal = ANIMALS[ints[0] % len(ANIMALS)] + + name = f"{adj1} {adj2} {animal}" + if no_spaces: + name = "_".join(name.split(" ")) + return name + + +def search_peer_ids_for_name(peer_ids: Sequence[str], name): + for peer_id in peer_ids: + if name == get_name_from_peer_id(peer_id): + return peer_id + return None diff --git a/code_gen_exp/src/utils/omega_gpu_resolver.py b/code_gen_exp/src/utils/omega_gpu_resolver.py new file mode 100644 index 0000000..99d13d5 --- /dev/null +++ b/code_gen_exp/src/utils/omega_gpu_resolver.py @@ -0,0 +1,26 @@ +import random + +import torch +from omegaconf import OmegaConf + + +def get_gpu_vram(): + """Returns the total VRAM of the first available GPU in GiB.""" + if not torch.cuda.is_available(): + return 0 + + total_memory = torch.cuda.get_device_properties(0).total_memory + return total_memory / (1024**3) # Convert bytes to GiB + + +def gpu_model_choice_resolver(large_model_pool, small_model_pool): + """Selects a model from the large or small pool based on VRAM.""" + vram = get_gpu_vram() + if vram >= 40: + model_pool = large_model_pool + else: + model_pool = small_model_pool + return random.choice(model_pool) + + +OmegaConf.register_new_resolver("gpu_model_choice", gpu_model_choice_resolver) diff --git a/code_gen_exp/src/utils/proposer_utils.py b/code_gen_exp/src/utils/proposer_utils.py new file mode 100644 index 0000000..4486090 --- /dev/null +++ b/code_gen_exp/src/utils/proposer_utils.py @@ -0,0 +1,58 @@ +import logging +import re +import json + +logger = logging.getLogger(__name__) + +def parse_json_from_fence(text): + """ + Parses a JSON fence block from a given text string. + Args: + text (str): The input text containing a JSON fence block. + Returns: + dict or list or None: The parsed JSON object, or None if no valid JSON block is found. + """ + # Regex to find a block starting with ```json and ending with ``` + # The `?` makes the match non-greedy, so it stops at the first closing fence. + # The `re.DOTALL` flag allows the `.` to match newlines. + match = re.search(r'```json(.*?)```', text, re.DOTALL) + if match: + json_string = match.group(1).strip() + try: + # Use json.loads to parse the cleaned string + parsed_json = json.loads(json_string) + return parsed_json + except json.JSONDecodeError as e: + logger.info(f"Unable to decode JSON: {e}") + return None + else: + logger.info(f'proposal cannot be parsed from fence') + return None + +def extract_question_name(question: str): + """ + Extracts the function name from prompts like: + - Write a function is_even(n) + - Write a function 'is_even' + - Write a function `is_even(n)` + - Write a function "is_even" + """ + question_pattern = re.compile( + r'^Write a function\s+' + r'(?P[`"\'])?' # optional quote: ', ", or ` + r'(?P[A-Za-z_][A-Za-z0-9_]*)' # function name + r'(?:\s*\(\s*' # optional opening parenthesis + r'(?P[A-Za-z_][A-Za-z0-9_]*(?:\s*,\s*[A-Za-z_][A-Za-z0-9_]*)*)?' # optional params + r'\s*\))?' # optional closing parenthesis + r'(?P=quote)?' # optional matching closing quote + ) + try: + match = question_pattern.match(question) + except: + logger.info(f"Failed to extract question name from question: {question}") + return None + if match: + func_name = match.group('name') + return func_name + logger.info(f"Failed to extract question name from question: {question}") + return None diff --git a/code_gen_exp/src/utils/solver_data_mapper.py b/code_gen_exp/src/utils/solver_data_mapper.py new file mode 100644 index 0000000..355b850 --- /dev/null +++ b/code_gen_exp/src/utils/solver_data_mapper.py @@ -0,0 +1,77 @@ +from abc import ABC, abstractmethod +class DatasetMapper(ABC): + """Base class for dataset-specific mappers. + + Each dataset mapper is responsible for transforming dataset-specific fields + into the standard format required by the system. + """ + + @abstractmethod + def map_prompt(self, batch: dict, index: int) -> str: + """Extract and format the prompt for a given index. + + Args: + batch: The batch data containing dataset-specific fields + index: The index of the item to extract + + Returns: + The formatted prompt string + """ + raise NotImplementedError + + @abstractmethod + def map_test(self, batch: dict, index: int) -> str: + """Extract and format the test for a given index. + + Args: + batch: The batch data containing dataset-specific fields + index: The index of the item to extract + + Returns: + The formatted test string + """ + raise NotImplementedError + + @abstractmethod + def format_question(self, prompt: str, test: str) -> str: + """Format the final question combining prompt and test. + + Args: + prompt: The base prompt + test: The test string + + Returns: + The formatted question to be used in the environment state + """ + raise NotImplementedError + + +class MBPPMapper(DatasetMapper): + """Mapper for MBPP (Mostly Basic Programming Problems) dataset.""" + + def map_prompt(self, batch: dict, index: int) -> str: + return batch.get('text', [])[index] + + def map_test(self, batch: dict, index: int) -> str: + test_imports = batch.get('test_setup_code', [])[index] + test_list = batch.get('test_list', [])[index] + return test_imports + "\n" + "\n".join(test_list) + + def format_question(self, prompt: str, test: str) -> str: + # MBPP needs function name hints from tests + return prompt + '\nplease match the function name to the following test\n' + test + + +class CodeContestsMapper(DatasetMapper): + """Mapper for Code Contests dataset.""" + + def map_prompt(self, batch: dict, index: int) -> str: + return batch.get('description', [])[index] + + def map_test(self, batch: dict, index: int) -> str: + test_dict = batch.get('public_tests', [])[index] + return str(test_dict) + + def format_question(self, prompt: str, test: str) -> str: + # Code Contests uses prompt as-is + return prompt diff --git a/code_gen_exp/src/utils/solver_utils.py b/code_gen_exp/src/utils/solver_utils.py new file mode 100644 index 0000000..56631ff --- /dev/null +++ b/code_gen_exp/src/utils/solver_utils.py @@ -0,0 +1,146 @@ +from genrl.state import GameState +from typing import Any, List +import re +import json + +def get_solutions(game_state: GameState, stage: int) -> dict[Any, dict[Any, List[Any]]]: + actions = game_state.get_stage_actions(stage) + solutions = {} + for agent in actions: + solutions[agent] = {} + for batch_id in actions[agent]: + solutions[agent][batch_id] = [] + for node, _ in enumerate(actions[agent][batch_id]): + solutions[agent][batch_id].append(actions[agent][batch_id][node]) + return solutions # Indices are [Agent][Batch Item][Node Idx][Solution] + + +def get_unittests(game_state: GameState, stage: int) -> dict[Any, dict[Any, List[Any]]]: + world_states = game_state.get_stage_state(stage) + unittests = {} # Key per agent + for agent in world_states: + unittests[agent] = {} + for batch_id in world_states[agent]: + unittests[agent][batch_id] = [] + for node, _ in enumerate(world_states[agent][batch_id]): + unittests[agent][batch_id].append( + world_states[agent][batch_id][node].environment_states["test"] + ) + return unittests # Indices are [Agent][Batch Item][Node Idx] + + +def get_questions(game_state: GameState, stage: int) -> dict[Any, dict[Any, List[Any]]]: + world_states = game_state.get_stage_state(stage) + questions = {} + for agent in world_states: + questions[agent] = {} + for batch_id in world_states[agent]: + questions[agent][batch_id] = [] + for node, _ in enumerate(world_states[agent][batch_id]): + questions[agent][batch_id].append( + world_states[agent][batch_id][node].environment_states["question"] + ) + return questions # Indices are [Agent][Batch Item][Node Idx] + + +def get_dataset(game_state: GameState, stage: int) -> dict[Any, dict[Any, List[Any]]]: + world_states = game_state.get_stage_state(stage) + dataset = {} + for agent in world_states: + dataset[agent] = {} + for batch_id in world_states[agent]: + dataset[agent][batch_id] = [] + for node, _ in enumerate(world_states[agent][batch_id]): + dataset[agent][batch_id].append( + world_states[agent][batch_id][node].environment_states["metadata"]["dataset"] + ) + return dataset # Indices are [Agent][Batch Item][Node Idx] + + +def parse_response(text: str): + """ + Extracts a numeric score from various response formats. + Handles: + - Fenced JSON (```json ... ```) + - Plain JSON ({'score': 0.85}) + - Plain JSON with 'is_correct' (boolean) or legacy 'score' (float) + - Python-style dicts + - Extra text or explanations + Returns 1.0 for true/correct, 0.0 for false/incorrect. + """ + if not text or not isinstance(text, str): + return None + cleaned = text.strip() # Remove leading and trailing whitespace + # 1) Extract JSON inside code fences, if any + match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", cleaned, re.DOTALL | re.IGNORECASE) + if match: + cleaned = match.group(1).strip() + + # 2) Check if `cleaned` is in valid JSON format; if so, return score + try: + data = json.loads(cleaned) + # Priority: is_correct (boolean) -> score (float) + if "is_correct" in data: + return 1.0 if data["is_correct"] else 0.0 + if "score" in data: + return float(data["score"]) + except Exception: + pass + + # 3) Convert single quotes to double quotes and retry + try: + alt = cleaned.replace("'", '"') + # Also handle Python True/False -> JSON true/false + alt = alt.replace("True", "true").replace("False", "false") + data = json.loads(alt) + if "is_correct" in data: + return 1.0 if data["is_correct"] else 0.0 + if "score" in data: + return float(data["score"]) + except Exception: + pass + + # 4) Fallback regex to find is_correct or score pattern anywhere + # Try is_correct first + match = re.search(r"['\"]?is_correct['\"]?\s*[:=]\s*(true|false)", cleaned, re.IGNORECASE) + if match: + return 1.0 if match.group(1).lower() == "true" else 0.0 + # Try legacy score pattern + match = re.search(r"['\"]?score['\"]?\s*[:=]\s*(-?\d+(?:\.\d+)?)", cleaned) + if match: + return float(match.group(1)) + + # 5) If still not found return None + return None + + +def parse_python_fence(text): + """ + Parses a PYTHON fence block from a given text string. + Args: + text (str): The input text. + Returns: + string or None: The parsed Python string, or None if no valid Python block is found. + """ + match = re.search(r'```python(.*?)```', text, re.DOTALL) + if match: + python_string = match.group(1).strip() + return python_string + return None + + +def check_eos(solution, tokenizer, max_new_tokens): + """ + Tokenizes solution and checks if the length is < max_new_tokens (for marking whether eos is found). + Args: + solution (str): The input text. + tokenizer (transformers.AutoTokenizer): Tokenizer. + max_new_tokens (int): Number of (new) tokens allowed for generation. + Returns: + bool: Whether the solution contains less number of tokens than max_new_tokens + """ + completion_ids = tokenizer(solution, return_tensors="pt")['input_ids'] + if len(completion_ids[0]) < max_new_tokens: # Tokenizer returns batched outputs, where the batch size is 1 because solution is a string + return True + else: + return False \ No newline at end of file diff --git a/containerfiles/swarm-node/swarm.containerfile b/containerfiles/swarm-node/swarm.containerfile index 8eb985d..aa63ae8 100644 --- a/containerfiles/swarm-node/swarm.containerfile +++ b/containerfiles/swarm-node/swarm.containerfile @@ -53,15 +53,13 @@ ENV PATH="/home/gensyn/.pyenv/shims:/home/gensyn/.pyenv/bin:$NVM_DIR/versions/no COPY --chown=gensyn ./modal-login /home/gensyn/rl_swarm/modal-login RUN cd /home/gensyn/rl_swarm/modal-login && yarn install --immutable && yarn build -RUN pip install gensyn-genrl==0.1.4 -RUN pip install reasoning-gym>=0.1.20 # for reasoning gym env -RUN pip install trl # for grpo config, will be deprecated soon -RUN pip install hivemind@git+https://github.com/gensyn-ai/hivemind@639c964a8019de63135a2594663b5bec8e5356dd # We need the latest, 1.1.11 is broken - WORKDIR /home/gensyn/rl_swarm ENV DOCKER=t ENV IDENTITY_PATH=/home/gensyn/rl_swarm/keys/swarm.pem COPY --chown=gensyn . /home/gensyn/rl_swarm -CMD [ "bash", "-c", "/home/gensyn/rl_swarm/run_rl_swarm.sh" ] +# Install Python dependencies for code generation experiments +RUN pip install --no-cache-dir -r /home/gensyn/rl_swarm/code_gen_exp/requirements.txt + +CMD [ "bash", "-c", "/home/gensyn/rl_swarm/run_rl_swarm.sh" ] \ No newline at end of file diff --git a/create.sh b/create.sh new file mode 100644 index 0000000..c80bb27 --- /dev/null +++ b/create.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -e +set -o pipefail + +# 仅在 macOS 下生成 .command 文件 +if [[ "$OSTYPE" != "darwin"* ]]; then + echo "ℹ️ 当前系统非 macOS,跳过生成 .command 文件" + exit 0 +fi + +CURRENT_USER=$(whoami) +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +DESKTOP_DIR="/Users/$CURRENT_USER/Desktop" +mkdir -p "$DESKTOP_DIR" + +# 需要生成的脚本列表(不包含 wai.sh) +SCRIPTS=( + gensyn.sh + nexus.sh + ritual.sh + startAll.sh +) + +for script in "${SCRIPTS[@]}"; do + cmd_name="${script%.sh}.command" + cat > "$DESKTOP_DIR/$cmd_name" <=525.60.13 to be installed, as well # as the nvidia-container-toolkit. @@ -56,6 +59,8 @@ services: dockerfile: containerfiles/swarm-node/swarm.containerfile args: - BASE_IMAGE=nvidia/cuda:12.6.3-cudnn-devel-ubuntu24.04 + depends_on: + - ollama ports: - 3000:3000 volumes: @@ -66,6 +71,7 @@ services: environment: - HF_TOKEN=${HF_TOKEN} - GENSYN_RESET_CONFIG=${GENSYN_RESET_CONFIG} + - OLLAMA_HOST=http://ollama:11434 deploy: resources: reservations: @@ -73,3 +79,9 @@ services: - driver: nvidia count: all capabilities: [gpu] + ollama: + image: ollama/ollama:0.11.10 + ports: + - "11434:11434" + volumes: + - ./ollama-data:/root/.ollama \ No newline at end of file diff --git a/gensyn.sh b/gensyn.sh new file mode 100755 index 0000000..52783a3 --- /dev/null +++ b/gensyn.sh @@ -0,0 +1,287 @@ +#!/bin/bash + +CONFIG_FILE="rgym_exp/config/rg-swarm.yaml" + +ENV_VAR="RL_SWARM_IP" + +# 根据操作系统选择环境变量配置文件 +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + ENV_FILE=~/.zshrc + SED_OPTION="''" +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Ubuntu/Linux + if [ -f ~/.bashrc ]; then + ENV_FILE=~/.bashrc + elif [ -f ~/.zshrc ]; then + ENV_FILE=~/.zshrc + else + ENV_FILE=~/.profile + fi + SED_OPTION="" +else + # 其他系统默认使用 bashrc + ENV_FILE=~/.bashrc + SED_OPTION="" +fi + +# ----------- IP配置逻辑 ----------- +echo "🔧 检查IP配置..." + +# 确保配置文件存在,避免后续操作报错 +[ -f "$ENV_FILE" ] || touch "$ENV_FILE" + +# 读取环境变量文件中的 RL_SWARM_IP +if grep -q "^export $ENV_VAR=" "$ENV_FILE"; then + CURRENT_IP=$(grep "^export $ENV_VAR=" "$ENV_FILE" | tail -n1 | awk -F'=' '{print $2}' | tr -d '[:space:]' | tr -d '"') +else + CURRENT_IP="" +fi + +# 交互提示(10秒超时) +if [ -n "$CURRENT_IP" ]; then + echo -n "检测到上次使用的 IP: $CURRENT_IP,是否继续使用?(Y/n, 10秒后默认Y): " + read -t 10 USE_LAST + if [[ "$USE_LAST" == "" || "$USE_LAST" =~ ^[Yy]$ ]]; then + NEW_IP="$CURRENT_IP" + else + read -p "请输入新的 initial_peers IP(直接回车跳过IP配置): " NEW_IP + fi +else + read -p "未检测到历史 IP,请输入 initial_peers IP(直接回车跳过IP配置): " NEW_IP +fi + +# 继续后续逻辑 +if [[ -z "$NEW_IP" ]]; then + echo "ℹ️ 未输入IP,跳过所有IP相关配置,继续执行。" +else + # 只要有NEW_IP都写入一次配置文件 + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "/^export $ENV_VAR=/d" "$ENV_FILE" + else + sed -i "/^export $ENV_VAR=/d" "$ENV_FILE" + fi + echo "export $ENV_VAR=$NEW_IP" >> "$ENV_FILE" + echo "✅ 已写入IP到配置文件:$NEW_IP" + + # 备份原文件 + if [ -f "$CONFIG_FILE" ]; then + cp "$CONFIG_FILE" "${CONFIG_FILE}.bak" + + # 替换 initial_peers 下的 IP + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS: 匹配 /ip4/旧IP/tcp/端口/p2p/节点ID 格式,只替换IP部分 + sed -i '' "s|/ip4/[0-9]\{1,3\}\(\.[0-9]\{1,3\}\)\{3\}|/ip4/${NEW_IP}|g" "$CONFIG_FILE" + else + # Linux: 匹配 /ip4/旧IP/tcp/端口/p2p/节点ID 格式,只替换IP部分 + sed -i "s|/ip4/[0-9]\{1,3\}\(\.[0-9]\{1,3\}\)\{3\}|/ip4/${NEW_IP}|g" "$CONFIG_FILE" + fi + + echo "✅ 已将 initial_peers 的 IP 全部替换为:$NEW_IP" + echo "原始文件已备份为:${CONFIG_FILE}.bak" + else + echo "⚠️ 配置文件 $CONFIG_FILE 不存在,跳过IP替换" + fi +fi + +# 切换到脚本所在目录(假设 go.sh 在项目根目录) +cd "$(dirname "$0")" + +# ====== 📝 带时间戳的日志函数 ====== +log() { + echo "【📅 $(date '+%Y-%m-%d %H:%M:%S')】 $1" +} + + +# ====== 重建虚拟环境函数 ====== +rebuild_venv() { + local current_dir=$(pwd) + log "🔧 开始重建虚拟环境... (当前目录: $current_dir)" + + # 如果虚拟环境存在,先删除 + if [ -d ".venv" ]; then + log "🗑️ 删除现有虚拟环境 .venv..." + if rm -rf .venv; then + log "✅ 虚拟环境已删除" + else + log "⚠️ 删除虚拟环境失败,但继续尝试重建" + fi + else + log "ℹ️ 虚拟环境不存在,直接创建新环境" + fi + + # 确定 Python 命令 + local PYTHON_CMD="" + if command -v python3.10 >/dev/null 2>&1; then + PYTHON_CMD=python3.10 + log "✅ 使用 Python 3.10" + elif command -v python3 >/dev/null 2>&1; then + PYTHON_CMD=python3 + log "✅ 使用 Python 3" + else + log "❌ 未找到 Python 3.10 或 python3,无法重建虚拟环境" + return 1 + fi + + # 创建新的虚拟环境 + log "📦 正在创建新的虚拟环境..." + if $PYTHON_CMD -m venv .venv 2>&1; then + log "✅ 虚拟环境创建成功" + + # 激活虚拟环境并安装基础依赖 + log "📥 激活虚拟环境并安装基础依赖..." + if [ -f ".venv/bin/activate" ]; then + source .venv/bin/activate + + # 升级 pip + log "⬆️ 升级 pip..." + pip install --upgrade pip >/dev/null 2>&1 || log "⚠️ pip 升级失败,但继续执行" + + # 检查并安装 web3(gensyn.sh 中需要的依赖) + if ! python -c "import web3" 2>/dev/null; then + log "⚙️ 正在安装 web3..." + pip install web3 >/dev/null 2>&1 || log "⚠️ web3 安装失败,但继续执行" + else + log "✅ web3 已存在,跳过安装" + fi + + log "✅ 虚拟环境重建完成" + return 0 + else + log "❌ 虚拟环境激活脚本不存在" + return 1 + fi + else + log "❌ 虚拟环境创建失败" + return 1 + fi +} + +# ====== 检查并更新代码函数 ====== +check_and_update_code() { + log "🔄 检查代码更新..." + + # 获取当前目录 + local current_dir=$(pwd) + log "📁 当前工作目录: $current_dir" + + # 检查是否在 git 仓库中,如果不是则跳过代码更新检查 + if ! git rev-parse --git-dir > /dev/null 2>&1; then + log "⚠️ 当前目录不是 git 仓库,跳过代码更新检查" + return 0 + fi + + # 获取远程更新(设置超时和错误处理) + log "🌐 获取远程仓库信息..." + # 使用简单的超时机制 + if ! git fetch origin 2>/dev/null; then + log "⚠️ 无法连接远程仓库,跳过代码更新检查" + return 0 + fi + + # 检查是否有更新 + local current_branch=$(git branch --show-current 2>/dev/null) + if [ -z "$current_branch" ]; then + log "⚠️ 无法获取当前分支信息,跳过代码更新检查" + return 0 + fi + + local remote_branch="origin/$current_branch" + + # 比较本地和远程分支 + local local_commit=$(git rev-parse HEAD 2>/dev/null) + local remote_commit=$(git rev-parse $remote_branch 2>/dev/null) + + if [ -z "$local_commit" ] || [ -z "$remote_commit" ]; then + log "⚠️ 无法获取提交信息,跳过代码更新检查" + return 0 + fi + + if [ "$local_commit" = "$remote_commit" ]; then + log "✅ 代码已是最新版本,无需更新" + return 0 + fi + + # 有更新,执行 git pull + log "🔄 检测到代码更新,正在拉取最新代码..." + if git pull origin "$current_branch" 2>/dev/null; then + log "✅ 代码更新成功!" + log "📊 更新详情:" + log " 本地提交: ${local_commit:0:8}" + log " 远程提交: ${remote_commit:0:8}" + # 代码更新成功,重建虚拟环境 + log "🔄 准备重建虚拟环境..." + if rebuild_venv; then + log "✅ 虚拟环境重建流程完成" + else + log "⚠️ 虚拟环境重建失败,但继续执行" + fi + return 0 + else + log "⚠️ git pull 失败,尝试强制更新..." + log "🔄 执行 git fetch origin --prune..." + if git fetch origin --prune 2>/dev/null; then + log "✅ git fetch 成功,正在强制重置到远程分支..." + if git reset --hard "origin/$current_branch" 2>/dev/null; then + log "✅ 强制更新成功!" + log "📊 强制更新详情:" + log " 本地提交: ${local_commit:0:8}" + log " 远程提交: ${remote_commit:0:8}" + log " 当前分支: $current_branch" + # 代码更新成功,重建虚拟环境 + log "🔄 准备重建虚拟环境..." + if rebuild_venv; then + log "✅ 虚拟环境重建流程完成" + else + log "⚠️ 虚拟环境重建失败,但继续执行" + fi + return 0 + else + log "⚠️ git reset --hard 失败,继续使用当前版本运行" + return 0 + fi + else + log "⚠️ git fetch 失败,可能是网络问题,继续使用当前版本运行" + return 0 + fi + fi +} + +# 首次启动时检查代码更新 +check_and_update_code + +# 激活虚拟环境并执行 auto_run.sh +if [ -d ".venv" ]; then + echo "🔗 正在激活虚拟环境 .venv..." + source .venv/bin/activate +else + echo "⚠️ 未找到 .venv 虚拟环境,正在自动创建..." + if command -v python3.10 >/dev/null 2>&1; then + PYTHON=python3.10 + elif command -v python3 >/dev/null 2>&1; then + PYTHON=python3 + else + echo "❌ 未找到 Python 3.10 或 python3,请先安装。" + exit 1 + fi + $PYTHON -m venv .venv + if [ -d ".venv" ]; then + echo "✅ 虚拟环境创建成功,正在激活..." + source .venv/bin/activate + # 检查并安装web3 + if ! python -c "import web3" 2>/dev/null; then + echo "⚙️ 正在为虚拟环境安装 web3..." + pip install web3 + fi + else + echo "❌ 虚拟环境创建失败,跳过激活。" + fi +fi + +# 执行 auto_run.sh +if [ -f "./auto_run.sh" ]; then + echo "🚀 执行 ./auto_run.sh ..." + ./auto_run.sh +else + echo "❌ 未找到 auto_run.sh,无法执行。" +fi \ No newline at end of file diff --git a/modal-login/.env b/modal-login/.env index 004f021..f4ff77d 100644 --- a/modal-login/.env +++ b/modal-login/.env @@ -1,3 +1,3 @@ NEXT_PUBLIC_ALCHEMY_API_KEY=RL2EtY6LXx2XCLPV3JZriJAB9mnELa2U NEXT_PUBLIC_PAYMASTER_POLICY_ID=4c37387c-2a55-4edd-b188-b5c44eb71e96 -SMART_CONTRACT_ADDRESS=0xFaD7C5e93f28257429569B854151A1B8DCD404c2 +SWARM_CONTRACT_ADDRESS=0x7745a8FE4b8D2D2c3BB103F8dCae822746F35Da0 \ No newline at end of file diff --git a/modal-login/app/api/bet-token-balance/route.ts b/modal-login/app/api/bet-token-balance/route.ts new file mode 100644 index 0000000..4a80a05 --- /dev/null +++ b/modal-login/app/api/bet-token-balance/route.ts @@ -0,0 +1,89 @@ +import { getLatestApiKey, getUser } from "@/app/db"; +import { NextResponse } from "next/server"; +import { alchemy, gensynTestnet } from "@account-kit/infra"; +import { createPublicClient, http } from "viem"; +import prgContract from "@/app/lib/prg_contract.json"; + +export async function POST(request: Request) { + const body: { + orgId: string; + peerId: string; + } = await request.json().catch((err) => { + console.error(err); + return NextResponse.json( + { error: "bad request generic" }, + { + status: 400, + }, + ); + }); + if (!body.orgId) { + return NextResponse.json( + { error: "bad request orgID" }, + { + status: 400, + }, + ); + } + + try { + const user = getUser(body.orgId); + if (!user) { + return NextResponse.json( + { error: "user not found" }, + { + status: 404, + }, + ); + } + const apiKey = getLatestApiKey(body.orgId); + if (!apiKey?.activated) { + return NextResponse.json( + { error: "api key not found" }, + { + status: 500, + }, + ); + } + + const transport = alchemy({ + apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY!, + }); + + const client = createPublicClient({ + chain: gensynTestnet, + transport, + }); + + // Read the contract state directly since betTokenBalance is a view function + const result = await client.readContract({ + address: process.env.PRG_CONTRACT_ADDRESS as `0x${string}`, + abi: prgContract.abi, + functionName: "betTokenBalance", + args: [body.peerId], + }); + + const balance = result as bigint + + return NextResponse.json( + { + result: balance.toString(), + }, + { + status: 200, + }, + ); + } catch (err) { + console.error(err); + + return NextResponse.json( + { + error: "An unexpected error occurred", + original: err, + }, + { + status: 500, + }, + ); + } +} diff --git a/modal-login/app/api/claim-reward/route.ts b/modal-login/app/api/claim-reward/route.ts new file mode 100644 index 0000000..79584b0 --- /dev/null +++ b/modal-login/app/api/claim-reward/route.ts @@ -0,0 +1,78 @@ +import { getLatestApiKey, getUser } from "@/app/db"; +import { NextResponse } from "next/server"; +import { userOperationHandler } from "@/app/lib/userOperationHandler"; +import prgContract from "@/app/lib/prg_contract.json"; + +export async function POST(request: Request) { + const body: { + orgId: string; + gameId: bigint; + peerId: string; + } = await request.json().catch((err) => { + console.error(err); + return NextResponse.json( + { error: "bad request generic" }, + { + status: 400, + }, + ); + }); + if (!body.orgId) { + return NextResponse.json( + { error: "bad request orgID" }, + { + status: 400, + }, + ); + } + + try { + const user = getUser(body.orgId); + if (!user) { + return NextResponse.json( + { error: "user not found" }, + { + status: 404, + }, + ); + } + const apiKey = getLatestApiKey(body.orgId); + if (!apiKey?.activated) { + return NextResponse.json( + { error: "api key not found" }, + { + status: 500, + }, + ); + } + + const { accountAddress, privateKey, initCode, deferredActionDigest } = apiKey; + + const userOperationResponse = await userOperationHandler({ + accountAddress, + privateKey, + deferredActionDigest, + initCode, + functionName: "claimReward", + args: [body.gameId, body.peerId], + contract: { + ...prgContract, + address: process.env.PRG_CONTRACT_ADDRESS, + }, + }); + console.log('Claim reward response: ', userOperationResponse); + return userOperationResponse; + } catch (err) { + console.error(err); + + return NextResponse.json( + { + error: "An unexpected error occurred", + original: err, + }, + { + status: 500, + }, + ); + } +} diff --git a/modal-login/app/api/guess-answer/route.ts b/modal-login/app/api/guess-answer/route.ts new file mode 100644 index 0000000..1876f31 --- /dev/null +++ b/modal-login/app/api/guess-answer/route.ts @@ -0,0 +1,81 @@ +import { getLatestApiKey, getUser } from "@/app/db"; +import { NextResponse } from "next/server"; +import { userOperationHandler } from "@/app/lib/userOperationHandler"; +import prgContract from "@/app/lib/prg_contract.json"; + +export async function POST(request: Request) { + const body: { + orgId: string; + gameId: bigint; + peerId: string; + clueId: bigint; + choiceIdx: bigint; + bet: bigint; + } = await request.json().catch((err) => { + console.error(err); + return NextResponse.json( + { error: "bad request generic" }, + { + status: 400, + }, + ); + }); + if (!body.orgId) { + return NextResponse.json( + { error: "bad request orgID" }, + { + status: 400, + }, + ); + } + + try { + const user = getUser(body.orgId); + if (!user) { + return NextResponse.json( + { error: "user not found" }, + { + status: 404, + }, + ); + } + const apiKey = getLatestApiKey(body.orgId); + if (!apiKey?.activated) { + return NextResponse.json( + { error: "api key not found" }, + { + status: 500, + }, + ); + } + + const { accountAddress, privateKey, initCode, deferredActionDigest } = apiKey; + + const userOperationResponse = await userOperationHandler({ + accountAddress, + privateKey, + deferredActionDigest, + initCode, + functionName: "guessAnswer", + args: [body.gameId, body.peerId, body.clueId, body.choiceIdx, body.bet], + contract: { + ...prgContract, + address: process.env.PRG_CONTRACT_ADDRESS, + }, + }); + console.log('Guess answer response: ', userOperationResponse); + return userOperationResponse; + } catch (err) { + console.error(err); + + return NextResponse.json( + { + error: "An unexpected error occurred", + original: err, + }, + { + status: 500, + }, + ); + } +} diff --git a/modal-login/app/globals.css b/modal-login/app/globals.css index 85fef1a..4671402 100644 --- a/modal-login/app/globals.css +++ b/modal-login/app/globals.css @@ -8,6 +8,29 @@ --background-end-rgb: 255, 255, 255; } +@font-face { + font-family: "AuxMono"; + src: url("/fonts/AuxMono/Webfont/Aux Mono.woff") format("woff"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "Mondwest"; + src: url("/fonts/Mondwest/Mondwest-Regular.woff") format("woff"); + font-weight: normal; + font-style: normal; + text-rendering: optimizeLegibility; +} + +@font-face { + font-family: "Simplon"; + src: url("/fonts/Simplon/SimplonMono-Light-WebS.woff") format("woff"); + font-weight: normal; + font-style: normal; + text-rendering: optimizeLegibility; +} + @media (prefers-color-scheme: dark) { :root { --foreground-rgb: 255, 255, 255; @@ -88,10 +111,4 @@ body { .text-balance { text-wrap: balance; } -} -@font-face { - font-family: "AuxMono"; - src: url("/fonts/Aux Mono.ttf") format("truetype"); - font-weight: normal; - font-style: normal; } \ No newline at end of file diff --git a/modal-login/app/lib/prg_contract.json b/modal-login/app/lib/prg_contract.json new file mode 100644 index 0000000..f195df7 --- /dev/null +++ b/modal-login/app/lib/prg_contract.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"_SWARM_COORDINATOR","type":"address","internalType":"contract ISwarmCoordinator"},{"name":"initialOwner","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"betTokenBalance","inputs":[{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"choiceCount","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"choiceSupply","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"},{"name":"choiceIdx","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"choiceTokenBalance","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"},{"name":"choiceIdx","type":"uint256","internalType":"uint256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"choiceTokensOut","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"},{"name":"roundIdx","type":"uint256","internalType":"uint256"},{"name":"choiceIdx","type":"uint256","internalType":"uint256"},{"name":"bet","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"claimReward","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"createNewGame","inputs":[{"name":"roundCount","type":"uint256","internalType":"uint256"},{"name":"choiceCount_","type":"uint256","internalType":"uint256"},{"name":"merkleRoot","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"newGameIdx","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"currentRoundIdx","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"gameStatus","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint8","internalType":"enum IPRG.GameStatus"}],"stateMutability":"view"},{"type":"function","name":"guessAnswer","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"},{"name":"peerId","type":"string","internalType":"string"},{"name":"roundIdx","type":"uint256","internalType":"uint256"},{"name":"choiceIdx","type":"uint256","internalType":"uint256"},{"name":"bet","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"incrementRound","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"price","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"},{"name":"roundIdx","type":"uint256","internalType":"uint256"},{"name":"choiceIdx","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"revealAnswer","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"},{"name":"correctChoiceIdx","type":"uint256","internalType":"uint256"},{"name":"jobId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"totalBets","inputs":[{"name":"gameIdx","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"AnswerRevealed","inputs":[{"name":"gameIdx","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"correctChoiceIdx","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"jobId","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"NewGameCreated","inputs":[{"name":"gameIdx","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"roundCount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"choiceCount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"startTime","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"NewGuess","inputs":[{"name":"gameIdx","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"caller","type":"address","indexed":false,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"},{"name":"roundIdx","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"choiceIdx","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"bet","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"RewardClaimed","inputs":[{"name":"gameIdx","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"caller","type":"address","indexed":false,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"},{"name":"correctChoiceTokensIn","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"betTokensOut","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"RoundIncremented","inputs":[{"name":"gameIdx","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]}]} diff --git a/modal-login/app/lib/userOperationHandler.ts b/modal-login/app/lib/userOperationHandler.ts index dad39f7..a9815b8 100644 --- a/modal-login/app/lib/userOperationHandler.ts +++ b/modal-login/app/lib/userOperationHandler.ts @@ -18,6 +18,7 @@ type UserOperationHandlerRequest = { initCode: Hex; functionName: string; args: unknown[]; + contract?: any; }; /** @@ -31,6 +32,7 @@ export async function userOperationHandler({ initCode, functionName, args, + contract: contractOverride, }: UserOperationHandlerRequest): Promise { const transport = alchemy({ apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY!, @@ -52,7 +54,9 @@ export async function userOperationHandler({ policyId: process.env.NEXT_PUBLIC_PAYMASTER_POLICY_ID!, }); - const contractAddress = process.env.SMART_CONTRACT_ADDRESS! as `0x${string}`; + // Allow contract override for PRG endpoints + const contractObj = contractOverride || contract; + const contractAddress = contractObj.address || process.env.SWARM_CONTRACT_ADDRESS! as `0x${string}`; console.log(contractAddress); @@ -62,7 +66,7 @@ export async function userOperationHandler({ uo: { target: contractAddress, data: encodeFunctionData({ - abi: contract.abi, + abi: contractObj.abi, functionName, args, }), @@ -141,7 +145,7 @@ export async function userOperationHandler({ try { const decodedError = decodeErrorResult({ data: revertData, - abi: contract.abi, + abi: contractObj.abi, }); console.error( diff --git a/modal-login/app/page.tsx b/modal-login/app/page.tsx index a3584db..5bafc67 100644 --- a/modal-login/app/page.tsx +++ b/modal-login/app/page.tsx @@ -170,31 +170,71 @@ export default function Home() { } }, []); - return ( -
- {(!sawDisconnected && !sawConnected) || (user && !createdApiKey) ? ( - <>Loading... - ) : user ? ( -
-
-

- YOU ARE SUCCESSFULLY LOGGED IN TO THE GENSYN TESTNET -

- -
- ) : ( -
-

LOGIN TO THE GENSYN TESTNET

-
- -
- )} + + ) + } + + return ( +
+
+ A spinning Gensyn logo + +
+ + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +

Welcome to the Gensyn Testnet

+
+ + {(!sawDisconnected && !sawConnected) || (user && !createdApiKey) ? ( + + ) : user ? ( + + ) : ( + + )} + + + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +
- ); -} + ) +} \ No newline at end of file diff --git a/modal-login/app/public/fonts/AuxMono/001.jpg b/modal-login/public/fonts/AuxMono/001.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/001.jpg rename to modal-login/public/fonts/AuxMono/001.jpg diff --git a/modal-login/app/public/fonts/AuxMono/002.jpg b/modal-login/public/fonts/AuxMono/002.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/002.jpg rename to modal-login/public/fonts/AuxMono/002.jpg diff --git a/modal-login/app/public/fonts/AuxMono/003.jpg b/modal-login/public/fonts/AuxMono/003.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/003.jpg rename to modal-login/public/fonts/AuxMono/003.jpg diff --git a/modal-login/app/public/fonts/AuxMono/004.jpg b/modal-login/public/fonts/AuxMono/004.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/004.jpg rename to modal-login/public/fonts/AuxMono/004.jpg diff --git a/modal-login/app/public/fonts/AuxMono/005.jpg b/modal-login/public/fonts/AuxMono/005.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/005.jpg rename to modal-login/public/fonts/AuxMono/005.jpg diff --git a/modal-login/app/public/fonts/AuxMono/006.jpg b/modal-login/public/fonts/AuxMono/006.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/006.jpg rename to modal-login/public/fonts/AuxMono/006.jpg diff --git a/modal-login/app/public/fonts/AuxMono/007.jpg b/modal-login/public/fonts/AuxMono/007.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/007.jpg rename to modal-login/public/fonts/AuxMono/007.jpg diff --git a/modal-login/app/public/fonts/AuxMono/008.jpg b/modal-login/public/fonts/AuxMono/008.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/008.jpg rename to modal-login/public/fonts/AuxMono/008.jpg diff --git a/modal-login/app/public/fonts/AuxMono/009.jpg b/modal-login/public/fonts/AuxMono/009.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/009.jpg rename to modal-login/public/fonts/AuxMono/009.jpg diff --git a/modal-login/app/public/fonts/AuxMono/010.jpg b/modal-login/public/fonts/AuxMono/010.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/010.jpg rename to modal-login/public/fonts/AuxMono/010.jpg diff --git a/modal-login/app/public/fonts/AuxMono/011.jpg b/modal-login/public/fonts/AuxMono/011.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/011.jpg rename to modal-login/public/fonts/AuxMono/011.jpg diff --git a/modal-login/app/public/fonts/AuxMono/012.jpg b/modal-login/public/fonts/AuxMono/012.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/012.jpg rename to modal-login/public/fonts/AuxMono/012.jpg diff --git a/modal-login/app/public/fonts/AuxMono/013.jpg b/modal-login/public/fonts/AuxMono/013.jpg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/013.jpg rename to modal-login/public/fonts/AuxMono/013.jpg diff --git a/modal-login/app/public/fonts/AuxMono/OTF/Aux Mono.otf b/modal-login/public/fonts/AuxMono/OTF/Aux Mono.otf similarity index 100% rename from modal-login/app/public/fonts/AuxMono/OTF/Aux Mono.otf rename to modal-login/public/fonts/AuxMono/OTF/Aux Mono.otf diff --git a/modal-login/app/public/fonts/AuxMono/TTF/Aux Mono.ttf b/modal-login/public/fonts/AuxMono/TTF/Aux Mono.ttf similarity index 100% rename from modal-login/app/public/fonts/AuxMono/TTF/Aux Mono.ttf rename to modal-login/public/fonts/AuxMono/TTF/Aux Mono.ttf diff --git a/modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.css b/modal-login/public/fonts/AuxMono/Webfont/Aux Mono.css similarity index 100% rename from modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.css rename to modal-login/public/fonts/AuxMono/Webfont/Aux Mono.css diff --git a/modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.eot b/modal-login/public/fonts/AuxMono/Webfont/Aux Mono.eot similarity index 100% rename from modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.eot rename to modal-login/public/fonts/AuxMono/Webfont/Aux Mono.eot diff --git a/modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.html b/modal-login/public/fonts/AuxMono/Webfont/Aux Mono.html similarity index 100% rename from modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.html rename to modal-login/public/fonts/AuxMono/Webfont/Aux Mono.html diff --git a/modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.svg b/modal-login/public/fonts/AuxMono/Webfont/Aux Mono.svg similarity index 100% rename from modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.svg rename to modal-login/public/fonts/AuxMono/Webfont/Aux Mono.svg diff --git a/modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.woff b/modal-login/public/fonts/AuxMono/Webfont/Aux Mono.woff similarity index 100% rename from modal-login/app/public/fonts/AuxMono/Webfont/Aux Mono.woff rename to modal-login/public/fonts/AuxMono/Webfont/Aux Mono.woff diff --git a/modal-login/public/fonts/Mondwest/Mondwest-Regular.woff b/modal-login/public/fonts/Mondwest/Mondwest-Regular.woff new file mode 100644 index 0000000..0f1bed1 Binary files /dev/null and b/modal-login/public/fonts/Mondwest/Mondwest-Regular.woff differ diff --git a/modal-login/public/fonts/Simplon/SimplonMono-Light-WebS.woff b/modal-login/public/fonts/Simplon/SimplonMono-Light-WebS.woff new file mode 100644 index 0000000..1a1dbbd Binary files /dev/null and b/modal-login/public/fonts/Simplon/SimplonMono-Light-WebS.woff differ diff --git a/modal-login/public/images/login.png b/modal-login/public/images/login.png new file mode 100644 index 0000000..c9a91b4 Binary files /dev/null and b/modal-login/public/images/login.png differ diff --git a/modal-login/public/images/logo.gif b/modal-login/public/images/logo.gif new file mode 100644 index 0000000..06fcfdc Binary files /dev/null and b/modal-login/public/images/logo.gif differ diff --git a/modal-login/tailwind.config.ts b/modal-login/tailwind.config.ts index f1ed0d3..4daa34c 100644 --- a/modal-login/tailwind.config.ts +++ b/modal-login/tailwind.config.ts @@ -2,17 +2,32 @@ import { withAccountKitUi, createColorSet } from "@account-kit/react/tailwind"; // wrap your existing tailwind config with 'withAccountKitUi' export default withAccountKitUi({ + content: [ + './app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + colors: { + 'gensyn-pink': '#eed2cc', + }, + fontFamily: { + 'mondwest': ['Mondwest', 'serif'], + 'auxmono': ['AuxMono', 'mono'], + 'simplon': ['Simplon', 'mono'], + } + } + } // your tailwind config here // if using tailwind v4, this can be left empty since most options are configured via css // if using tailwind v3, add your existing tailwind config here - https://v3.tailwindcss.com/docs/installation/using-postcss }, { // override account kit themes colors: { - //"btn-primary": createColorSet("#fad7d1", "#fad7d1"), - //"fg-accent-brand": createColorSet("#fad7d1", "#fad7d1"), - //"bg-surface-default": createColorSet("#3b1f1f", "#3b1f1f"), // Set modal background color - //"fg-primary": createColorSet("#fad7d1", "#fad7d1"), // Set text color - + "btn-primary": createColorSet("#eed2cc", "#eed2cc"), + "btn-auth": createColorSet("#eed2cc", "#eed2cc"), + "fg-accent-brand": createColorSet("#fad7d1", "#fad7d1"), + "bg-surface-default": createColorSet("#fff", "#fff"), // Set modal background color + "fg-primary": createColorSet("#000", "#000"), // Set text color }, borderRadius: "none", }) \ No newline at end of file diff --git a/nexus.sh b/nexus.sh new file mode 100755 index 0000000..f0b128e --- /dev/null +++ b/nexus.sh @@ -0,0 +1,729 @@ +#!/bin/bash + +# 柔和色彩设置 +GREEN='\033[1;32m' # 柔和绿色 +BLUE='\033[1;36m' # 柔和蓝色 +RED='\033[1;31m' # 柔和红色 +YELLOW='\033[1;33m' # 柔和黄色 +NC='\033[0m' # 无颜色 + +# 日志文件设置 +LOG_FILE="$HOME/nexus.log" +MAX_LOG_SIZE=10485760 # 10MB,日志大小限制 + +# 检测操作系统 +OS=$(uname -s) +case "$OS" in + Darwin) OS_TYPE="macOS" ;; + Linux) + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "$ID" == "ubuntu" ]]; then + OS_TYPE="Ubuntu" + else + OS_TYPE="Linux" + fi + else + OS_TYPE="Linux" + fi + ;; + *) echo -e "${RED}不支持的操作系统: $OS。本脚本仅支持 macOS 和 Ubuntu。${NC}" ; exit 1 ;; +esac + +# 检测 shell 并设置配置文件 +if [[ -n "$ZSH_VERSION" ]]; then + SHELL_TYPE="zsh" + CONFIG_FILE="$HOME/.zshrc" +elif [[ -n "$BASH_VERSION" ]]; then + SHELL_TYPE="bash" + CONFIG_FILE="$HOME/.bashrc" +else + echo -e "${RED}不支持的 shell。本脚本仅支持 bash 和 zsh。${NC}" + exit 1 +fi + +# 打印标题 +print_header() { + echo -e "${BLUE}=====================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}=====================================${NC}" +} + +# 检查命令是否存在 +check_command() { + if command -v "$1" &> /dev/null; then + echo -e "${GREEN}$1 已安装,跳过安装步骤。${NC}" + return 0 + else + echo -e "${RED}$1 未安装,开始安装...${NC}" + return 1 + fi +} + +# 配置 shell 环境变量,避免重复写入 +configure_shell() { + local env_path="$1" + local env_var="export PATH=$env_path:\$PATH" + if [[ -f "$CONFIG_FILE" ]] && grep -Fx "$env_var" "$CONFIG_FILE" > /dev/null; then + echo -e "${GREEN}环境变量已在 $CONFIG_FILE 中配置。${NC}" + else + echo -e "${BLUE}正在将环境变量添加到 $CONFIG_FILE...${NC}" + echo "$env_var" >> "$CONFIG_FILE" + echo -e "${GREEN}环境变量已添加到 $CONFIG_FILE。${NC}" + # 应用当前会话的更改 + source "$CONFIG_FILE" 2>/dev/null || echo -e "${RED}无法加载 $CONFIG_FILE,请手动运行 'source $CONFIG_FILE'。${NC}" + fi +} + +# 日志轮转 +rotate_log() { + if [[ -f "$LOG_FILE" ]]; then + if [[ "$OS_TYPE" == "macOS" ]]; then + FILE_SIZE=$(stat -f %z "$LOG_FILE" 2>/dev/null) + else + FILE_SIZE=$(stat -c %s "$LOG_FILE" 2>/dev/null) + fi + if [[ $FILE_SIZE -ge $MAX_LOG_SIZE ]]; then + mv "$LOG_FILE" "${LOG_FILE}.$(date +%F_%H-%M-%S).bak" + echo -e "${YELLOW}日志文件已轮转,新日志将写入 $LOG_FILE${NC}" + fi + fi +} + +# 安装 Homebrew(macOS 和非 Ubuntu Linux) +install_homebrew() { + print_header "检查 Homebrew 安装" + if check_command brew; then + return + fi + echo -e "${BLUE}在 $OS_TYPE 上安装 Homebrew...${NC}" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || { + echo -e "${RED}安装 Homebrew 失败,请检查网络连接或权限。${NC}" + exit 1 + } + if [[ "$OS_TYPE" == "macOS" ]]; then + configure_shell "/opt/homebrew/bin" + else + configure_shell "$HOME/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/bin" + if ! check_command gcc; then + echo -e "${BLUE}在 Linux 上安装 gcc(Homebrew 依赖)...${NC}" + if command -v yum &> /dev/null; then + sudo yum groupinstall 'Development Tools' || { + echo -e "${RED}安装 gcc 失败,请手动安装 Development Tools。${NC}" + exit 1 + } + else + echo -e "${RED}不支持的包管理器,请手动安装 gcc。${NC}" + exit 1 + fi + fi + fi +} + +# 安装基础依赖(仅 Ubuntu) +install_dependencies() { + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + print_header "安装基础依赖工具" + echo -e "${BLUE}更新 apt 包索引并安装必要工具...${NC}" + sudo apt-get update -y + sudo apt-get install -y curl jq screen build-essential || { + echo -e "${RED}安装依赖工具失败,请检查网络连接或权限。${NC}" + exit 1 + } + fi +} + +# 安装 CMake +install_cmake() { + print_header "检查 CMake 安装" + if check_command cmake; then + return + fi + echo -e "${BLUE}正在安装 CMake...${NC}" + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + sudo apt-get install -y cmake || { + echo -e "${RED}安装 CMake 失败,请检查网络连接或权限。${NC}" + exit 1 + } + else + brew install cmake || { + echo -e "${RED}安装 CMake 失败,请检查 Homebrew 安装。${NC}" + exit 1 + } + fi +} + +# 安装 Protobuf +install_protobuf() { + print_header "检查 Protobuf 安装" + if check_command protoc; then + return + fi + echo -e "${BLUE}正在安装 Protobuf...${NC}" + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + sudo apt-get install -y protobuf-compiler || { + echo -e "${RED}安装 Protobuf 失败,请检查网络连接或权限。${NC}" + exit 1 + } + else + brew install protobuf || { + echo -e "${RED}安装 Protobuf 失败,请检查 Homebrew 安装。${NC}" + exit 1 + } + fi +} + +# 安装 Rust +install_rust() { + print_header "检查 Rust 安装" + if check_command rustc; then + return + fi + echo -e "${BLUE}正在安装 Rust...${NC}" + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y || { + echo -e "${RED}安装 Rust 失败,请检查网络连接。${NC}" + exit 1 + } + source "$HOME/.cargo/env" 2>/dev/null || echo -e "${RED}无法加载 Rust 环境,请手动运行 'source ~/.cargo/env'。${NC}" + configure_shell "$HOME/.cargo/bin" +} + +# 配置 Rust RISC-V 目标 +configure_rust_target() { + print_header "检查 Rust RISC-V 目标" + if rustup target list --installed | grep -q "riscv32i-unknown-none-elf"; then + echo -e "${GREEN}RISC-V 目标 (riscv32i-unknown-none-elf) 已安装,跳过。${NC}" + return + fi + echo -e "${BLUE}为 Rust 添加 RISC-V 目标...${NC}" + rustup target add riscv32i-unknown-none-elf || { + echo -e "${RED}添加 RISC-V 目标失败,请检查 Rust 安装。${NC}" + exit 1 + } +} + +# 日志函数 +log() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S %Z')] $1" | tee -a "$LOG_FILE" + rotate_log +} + +# 退出时的清理函数 +cleanup_exit() { + log "${YELLOW}收到退出信号,正在清理 Nexus 节点进程...${NC}" + + if [[ "$OS_TYPE" == "macOS" ]]; then + # macOS: 先获取窗口信息,再终止进程,最后关闭窗口 + log "${BLUE}正在获取 Nexus 相关窗口信息...${NC}" + + # 获取包含nexus的窗口ID + nexus_window_id=$(osascript -e 'tell app "Terminal" to id of first window whose name contains "node-id"' 2>/dev/null || echo "") + if [[ -n "$nexus_window_id" ]]; then + log "${BLUE}发现 Nexus 窗口ID: $nexus_window_id,准备关闭...${NC}" + else + log "${YELLOW}未找到 Nexus 窗口,第一次启动,跳过关闭操作${NC}" + fi + + # 现在终止进程 + log "${BLUE}正在终止 Nexus 节点进程...${NC}" + + # 查找并终止 nexus-network 和 nexus-cli 进程 + local pids=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') + if [[ -n "$pids" ]]; then + log "${BLUE}发现进程: $pids,正在终止...${NC}" + for pid in $pids; do + kill -TERM "$pid" 2>/dev/null || true + sleep 1 + # 如果进程还在运行,强制终止 + if ps -p "$pid" > /dev/null 2>&1; then + kill -KILL "$pid" 2>/dev/null || true + fi + done + fi + + # 等待进程完全终止 + sleep 2 + + # 清理 screen 会话(如果存在) + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + else + # 非 macOS: 清理 screen 会话 + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + fi + + # 查找并终止 nexus-network 和 nexus-cli 进程 + log "${BLUE}正在查找并清理残留的 Nexus 进程...${NC}" + PIDS=$(ps aux | grep -E "nexus-cli|nexus-network" | grep -v grep | awk '{print $2}' | tr '\n' ' ' | xargs echo -n) + log "${BLUE}ps 找到的进程: '$PIDS'${NC}" + + if [[ -z "$PIDS" ]]; then + log "${YELLOW}ps 未找到进程,尝试 pgrep...${NC}" + PIDS=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ' | xargs echo -n) + log "${BLUE}pgrep 找到的进程: '$PIDS'${NC}" + fi + + if [[ -n "$PIDS" ]]; then + for pid in $PIDS; do + if ps -p "$pid" > /dev/null 2>&1; then + log "${BLUE}正在终止 Nexus 节点进程 (PID: $pid)...${NC}" + kill -9 "$pid" 2>/dev/null || log "${RED}无法终止 PID $pid 的进程,请检查进程状态。${NC}" + fi + done + else + log "${GREEN}未找到残留的 nexus-network 或 nexus-cli 进程。${NC}" + fi + + # 额外清理:查找可能的子进程 + log "${BLUE}检查是否有子进程残留...${NC}" + local child_pids=$(pgrep -P $(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') 2>/dev/null | tr '\n' ' ') + if [[ -n "$child_pids" ]]; then + log "${BLUE}发现子进程: $child_pids,正在清理...${NC}" + for pid in $child_pids; do + kill -9 "$pid" 2>/dev/null || true + done + fi + + # 等待所有进程完全清理 + sleep 5 + + # 最后才关闭窗口(确保所有进程都已终止) + if [[ "$OS_TYPE" == "macOS" ]]; then + log "${BLUE}正在关闭 Nexus 节点终端窗口...${NC}" + + if [[ -n "$nexus_window_id" ]]; then + # 直接关闭找到的nexus窗口 + log "${BLUE}关闭 Nexus 窗口 (ID: $nexus_window_id)...${NC}" + osascript -e "tell application \"Terminal\" to close window id $nexus_window_id saving no" 2>/dev/null || true + sleep 2 + log "${BLUE}窗口关闭完成${NC}" + else + log "${YELLOW}没有找到 Nexus 窗口,跳过关闭操作${NC}" + fi + fi + + log "${GREEN}清理完成,脚本退出。${NC}" + exit 0 +} + +# 重启时的清理函数 +cleanup_restart() { + # 重启前清理日志 + if [[ -f "$LOG_FILE" ]]; then + rm -f "$LOG_FILE" + echo -e "${YELLOW}已清理旧日志文件 $LOG_FILE${NC}" + fi + log "${YELLOW}准备重启节点,开始清理流程...${NC}" + + if [[ "$OS_TYPE" == "macOS" ]]; then + # macOS: 先获取窗口信息,再终止进程,最后关闭窗口 + log "${BLUE}正在获取 Nexus 相关窗口信息...${NC}" + + # 获取包含nexus的窗口ID + nexus_window_id=$(osascript -e 'tell app "Terminal" to id of first window whose name contains "node-id"' 2>/dev/null || echo "") + if [[ -n "$nexus_window_id" ]]; then + log "${BLUE}发现 Nexus 窗口ID: $nexus_window_id,准备关闭...${NC}" + else + log "${YELLOW}未找到 Nexus 窗口,第一次启动,跳过关闭操作${NC}" + fi + + # 现在终止进程 + log "${BLUE}正在终止 Nexus 节点进程...${NC}" + + # 查找并终止 nexus-network 和 nexus-cli 进程 + local pids=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') + if [[ -n "$pids" ]]; then + log "${BLUE}发现进程: $pids,正在终止...${NC}" + for pid in $pids; do + kill -TERM "$pid" 2>/dev/null || true + sleep 1 + # 如果进程还在运行,强制终止 + if ps -p "$pid" > /dev/null 2>&1; then + kill -KILL "$pid" 2>/dev/null || true + fi + done + fi + + # 等待进程完全终止 + sleep 2 + + # 清理 screen 会话(如果存在) + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + else + # 非 macOS: 清理 screen 会话 + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + fi + + # 查找并终止 nexus-network 和 nexus-cli 进程 + log "${BLUE}正在查找并清理残留的 Nexus 进程...${NC}" + PIDS=$(ps aux | grep -E "nexus-cli|nexus-network" | grep -v grep | awk '{print $2}' | tr '\n' ' ' | xargs echo -n) + log "${BLUE}ps 找到的进程: '$PIDS'${NC}" + + if [[ -z "$PIDS" ]]; then + log "${YELLOW}ps 未找到进程,尝试 pgrep...${NC}" + PIDS=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ' | xargs echo -n) + log "${BLUE}pgrep 找到的进程: '$PIDS'${NC}" + fi + + if [[ -n "$PIDS" ]]; then + for pid in $PIDS; do + if ps -p "$pid" > /dev/null 2>&1; then + log "${BLUE}正在终止 Nexus 节点进程 (PID: $pid)...${NC}" + kill -9 "$pid" 2>/dev/null || log "${RED}无法终止 PID $pid 的进程,请检查进程状态。${NC}" + fi + done + else + log "${GREEN}未找到残留的 nexus-network 或 nexus-cli 进程。${NC}" + fi + + # 额外清理:查找可能的子进程 + log "${BLUE}检查是否有子进程残留...${NC}" + local child_pids=$(pgrep -P $(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') 2>/dev/null | tr '\n' ' ') + if [[ -n "$child_pids" ]]; then + log "${BLUE}发现子进程: $child_pids,正在清理...${NC}" + for pid in $child_pids; do + kill -9 "$pid" 2>/dev/null || true + done + fi + + # 等待所有进程完全清理 + sleep 5 + + # 最后才关闭窗口(确保所有进程都已终止) + if [[ "$OS_TYPE" == "macOS" ]]; then + log "${BLUE}正在关闭 Nexus 节点终端窗口...${NC}" + + if [[ -n "$nexus_window_id" ]]; then + # 直接关闭找到的nexus窗口 + log "${BLUE}关闭 Nexus 窗口 (ID: $nexus_window_id)...${NC}" + osascript -e "tell application \"Terminal\" to close window id $nexus_window_id saving no" 2>/dev/null || true + sleep 2 + log "${BLUE}窗口关闭完成${NC}" + else + log "${YELLOW}没有找到 Nexus 窗口,跳过关闭操作${NC}" + fi + fi + + log "${GREEN}清理完成,准备重启节点。${NC}" +} + +trap 'cleanup_exit' SIGINT SIGTERM SIGHUP + +# 安装或更新 Nexus CLI +install_nexus_cli() { + local attempt=1 + local max_attempts=3 + local success=false + while [[ $attempt -le $max_attempts ]]; do + log "${BLUE}正在安装/更新 Nexus CLI(第 $attempt/$max_attempts 次)...${NC}" + if curl -s https://cli.nexus.xyz/ | sh &>/dev/null; then + log "${GREEN}Nexus CLI 安装/更新成功!${NC}" + success=true + break + else + log "${YELLOW}第 $attempt 次安装/更新 Nexus CLI 失败。${NC}" + ((attempt++)) + sleep 2 + fi + done + # 确保配置文件存在,如果没有就生成并写入 PATH 变量 + if [[ ! -f "$CONFIG_FILE" ]]; then + echo "export PATH=\"$HOME/.cargo/bin:\$PATH\"" > "$CONFIG_FILE" + log "${YELLOW}未检测到 $CONFIG_FILE,已自动生成并写入 PATH 变量。${NC}" + fi + # 更新CLI后加载环境变量 + source "$CONFIG_FILE" 2>/dev/null && log "${GREEN}已自动加载 $CONFIG_FILE 环境变量。${NC}" || log "${YELLOW}未能自动加载 $CONFIG_FILE,请手动执行 source $CONFIG_FILE。${NC}" + # 额外加载.zshrc确保环境变量生效 + if [[ -f "$HOME/.zshrc" ]]; then + source "$HOME/.zshrc" 2>/dev/null && log "${GREEN}已额外加载 ~/.zshrc 环境变量。${NC}" || log "${YELLOW}未能加载 ~/.zshrc,请手动执行 source ~/.zshrc。${NC}" + fi + if [[ "$success" == false ]]; then + log "${RED}Nexus CLI 安装/更新失败 $max_attempts 次,将尝试使用当前版本运行节点。${NC}" + fi + if command -v nexus-cli &>/dev/null; then + log "${GREEN}nexus-cli 版本:$(nexus-cli -V 2>/dev/null)${NC}" + elif command -v nexus-network &>/dev/null; then + log "${GREEN}nexus-network 版本:$(nexus-network --version 2>/dev/null)${NC}" + else + log "${RED}未找到 nexus-cli 或 nexus-network,无法运行节点。${NC}" + exit 1 + fi + + # 首次安装后记录版本信息 + if [[ ! -f "$HOME/.nexus/last_version" ]]; then + log "${BLUE}首次安装,正在记录版本信息...${NC}" + local repo_url="https://github.com/nexus-xyz/nexus-cli.git" + local latest_tag=$(git ls-remote --tags "$repo_url" | grep -E 'refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/.*refs\/tags\///' | sort -V | tail -1) + + if [[ -n "$latest_tag" ]]; then + mkdir -p "$HOME/.nexus" + echo "$latest_tag" > "$HOME/.nexus/last_version" + log "${GREEN}已记录当前版本: $latest_tag${NC}" + else + log "${YELLOW}无法获取版本信息,将在后续检测时创建${NC}" + fi + fi +} + +# 读取或设置 Node ID,添加 5 秒超时 +get_node_id() { + CONFIG_PATH="$HOME/.nexus/config.json" + if [[ -f "$CONFIG_PATH" ]]; then + CURRENT_NODE_ID=$(jq -r .node_id "$CONFIG_PATH" 2>/dev/null) + if [[ -n "$CURRENT_NODE_ID" && "$CURRENT_NODE_ID" != "null" ]]; then + log "${GREEN}检测到配置文件中的 Node ID:$CURRENT_NODE_ID${NC}" + # 使用 read -t 5 实现 5 秒超时,默认选择 y + echo -e "${BLUE}是否使用此 Node ID? (y/n, 默认 y,5 秒后自动继续): ${NC}" + use_old_id="" + read -t 5 -r use_old_id + use_old_id=${use_old_id:-y} # 默认 y + if [[ "$use_old_id" =~ ^[Nn]$ ]]; then + read -rp "请输入新的 Node ID: " NODE_ID_TO_USE + # 验证 Node ID(假设需要非空且只包含字母、数字、连字符) + if [[ -z "$NODE_ID_TO_USE" || ! "$NODE_ID_TO_USE" =~ ^[a-zA-Z0-9-]+$ ]]; then + log "${RED}无效的 Node ID,请输入只包含字母、数字或连字符的 ID。${NC}" + exit 1 + fi + jq --arg id "$NODE_ID_TO_USE" '.node_id = $id' "$CONFIG_PATH" > "$CONFIG_PATH.tmp" && mv "$CONFIG_PATH.tmp" "$CONFIG_PATH" + log "${GREEN}已更新 Node ID: $NODE_ID_TO_USE${NC}" + else + NODE_ID_TO_USE="$CURRENT_NODE_ID" + fi + else + log "${YELLOW}未检测到有效 Node ID,请输入新的 Node ID。${NC}" + read -rp "请输入新的 Node ID: " NODE_ID_TO_USE + if [[ -z "$NODE_ID_TO_USE" || ! "$NODE_ID_TO_USE" =~ ^[a-zA-Z0-9-]+$ ]]; then + log "${RED}无效的 Node ID,请输入只包含字母、数字或连字符的 ID。${NC}" + exit 1 + fi + mkdir -p "$HOME/.nexus" + echo "{\"node_id\": \"${NODE_ID_TO_USE}\"}" > "$CONFIG_PATH" + log "${GREEN}已写入 Node ID: $NODE_ID_TO_USE 到 $CONFIG_PATH${NC}" + fi + else + log "${YELLOW}未找到配置文件 $CONFIG_PATH,请输入 Node ID。${NC}" + read -rp "请输入新的 Node ID: " NODE_ID_TO_USE + if [[ -z "$NODE_ID_TO_USE" || ! "$NODE_ID_TO_USE" =~ ^[a-zA-Z0-9-]+$ ]]; then + log "${RED}无效的 Node ID,请输入只包含字母、数字或连字符的 ID。${NC}" + exit 1 + fi + mkdir -p "$HOME/.nexus" + echo "{\"node_id\": \"${NODE_ID_TO_USE}\"}" > "$CONFIG_PATH" + log "${GREEN}已写入 Node ID: $NODE_ID_TO_USE 到 $CONFIG_PATH${NC}" + fi +} + +# 检测 GitHub 仓库版本更新 +check_github_updates() { + local repo_url="https://github.com/nexus-xyz/nexus-cli.git" + log "${BLUE}检查 Nexus CLI 版本更新...${NC}" + + # 获取远程仓库最新标签(版本) + local latest_tag=$(git ls-remote --tags "$repo_url" | grep -E 'refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/.*refs\/tags\///' | sort -V | tail -1) + + if [[ -z "$latest_tag" ]]; then + log "${YELLOW}无法获取远程版本信息,跳过更新检测${NC}" + return 1 + fi + + # 获取当前安装的版本 + local current_version="" + if command -v nexus-cli &>/dev/null; then + # 尝试多种版本格式匹配 + current_version=$(nexus-cli -V 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1) + if [[ -z "$current_version" ]]; then + # 如果没有找到 v 开头的版本,尝试匹配数字版本并添加 v 前缀 + local version_num=$(nexus-cli -V 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) + if [[ -n "$version_num" ]]; then + current_version="v$version_num" + fi + fi + elif command -v nexus-network &>/dev/null; then + current_version=$(nexus-network --version 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1) + if [[ -z "$current_version" ]]; then + local version_num=$(nexus-network --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) + if [[ -n "$version_num" ]]; then + current_version="v$version_num" + fi + fi + fi + + if [[ -z "$current_version" ]]; then + log "${YELLOW}无法获取当前版本信息,将进行首次安装${NC}" + echo "$latest_tag" > "$HOME/.nexus/last_version" + return 0 # 首次安装 + fi + + # 比较版本号 + if [[ "$latest_tag" != "$current_version" ]]; then + log "${GREEN}检测到新版本!${NC}" + log "${BLUE}当前版本: $current_version${NC}" + log "${BLUE}最新版本: $latest_tag${NC}" + + # 使用 sort -V 进行版本号比较 + if echo -e "$current_version\n$latest_tag" | sort -V | tail -1 | grep -q "$latest_tag"; then + log "${GREEN}版本升级检测通过,准备更新...${NC}" + echo "$latest_tag" > "$HOME/.nexus/last_version" + return 0 # 有更新 + else + log "${YELLOW}检测到版本变化,但可能是降级,跳过更新${NC}" + return 1 # 跳过更新 + fi + else + log "${GREEN}当前已是最新版本: $current_version${NC}" + return 1 # 无更新 + fi +} + +# 启动节点 +start_node() { + log "${BLUE}正在启动 Nexus 节点 (Node ID: $NODE_ID_TO_USE)...${NC}" + rotate_log + + if [[ "$OS_TYPE" == "macOS" ]]; then + # macOS: 新开终端窗口启动节点,并设置到指定位置 + log "${BLUE}在 macOS 中打开新终端窗口启动节点...${NC}" + + # 获取屏幕尺寸 + screen_info=$(system_profiler SPDisplaysDataType | grep Resolution | head -1 | awk '{print $2, $4}' | tr 'x' ' ') + if [[ -n "$screen_info" ]]; then + read -r screen_width screen_height <<< "$screen_info" + else + screen_width=1920 + screen_height=1080 + fi + + # 计算窗口位置(与 startAll.sh 中 nexus 位置完全一致) + spacing=20 + upper_height=$(((screen_height/2) - (2*spacing))) + lower_height=$(((screen_height/2) - (2*spacing))) + lower_y=$((upper_height + (2*spacing))) + + # 设置窗口位置:距离左边界30px + lower_item_width=$(((screen_width - spacing) / 2)) # 窗口宽度 + nexus_ritual_height=$((lower_height - 30)) + nexus_ritual_y=$((lower_y + 5)) + nexus_x=30 # 距离左边界30px + + # 启动节点并设置窗口位置和大小(103x31) + osascript < /dev/null; then + log "${GREEN}Nexus 节点已在新终端窗口中启动${NC}" + else + log "${YELLOW}nexus-network 启动失败,尝试用 nexus-cli 启动...${NC}" + # 使用相同的窗口位置和大小设置(103x31) + osascript < /dev/null; then + log "${GREEN}Nexus 节点已通过 nexus-cli 在新终端窗口中启动${NC}" + else + log "${RED}启动失败,将在下次更新检测时重试${NC}" + return 1 + fi + fi + else + # 非 macOS: 使用 screen 启动(保持原有逻辑) + log "${BLUE}在 $OS_TYPE 中使用 screen 启动节点...${NC}" + screen -dmS nexus_node bash -c "nexus-network start --node-id '${NODE_ID_TO_USE}' >> $LOG_FILE 2>&1" + sleep 2 + if screen -list | grep -q "nexus_node"; then + log "${GREEN}Nexus 节点已在 screen 会话(nexus_node)中启动,日志输出到 $LOG_FILE${NC}" + else + log "${YELLOW}nexus-network 启动失败,尝试用 nexus-cli 启动...${NC}" + screen -dmS nexus_node bash -c "nexus-cli start --node-id '${NODE_ID_TO_USE}' >> $LOG_FILE 2>&1" + sleep 2 + if screen -list | grep -q "nexus_node"; then + log "${GREEN}Nexus 节点已通过 nexus-cli 启动,日志输出到 $LOG_FILE${NC}" + else + log "${RED}启动失败,将在下次更新检测时重试${NC}" + return 1 + fi + fi + fi + + return 0 +} + +# 主循环 +main() { + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + install_dependencies + fi + if [[ "$OS_TYPE" == "macOS" || "$OS_TYPE" == "Linux" ]]; then + install_homebrew + fi + install_cmake + install_protobuf + install_rust + configure_rust_target + get_node_id + + # 首次启动节点 + log "${BLUE}首次启动 Nexus 节点...${NC}" + cleanup_restart + install_nexus_cli + if start_node; then + log "${GREEN}节点启动成功!${NC}" + else + log "${YELLOW}节点启动失败,将在下次更新检测时重试${NC}" + fi + + log "${BLUE}开始监控 Nexus CLI 版本更新...${NC}" + log "${BLUE}检测频率:每30分钟检查一次${NC}" + log "${BLUE}更新条件:仅在检测到新版本时更新和重启${NC}" + + while true; do + # 每30分钟检查一次更新 + sleep 1800 + + if check_github_updates; then + log "${BLUE}检测到新版本,准备更新并重启节点...${NC}" + cleanup_restart + install_nexus_cli + if start_node; then + log "${GREEN}节点已成功更新并重启!${NC}" + else + log "${YELLOW}节点更新重启失败,将在下次版本检测时重试${NC}" + fi + else + log "${BLUE}当前已是最新版本,节点继续运行...${NC}" + fi + done +} + +main \ No newline at end of file diff --git a/quickq_run.sh b/quickq_run.sh new file mode 100755 index 0000000..976e492 --- /dev/null +++ b/quickq_run.sh @@ -0,0 +1,308 @@ +#!/bin/bash + +# 定义两个可能的 QuickQ 路径 +APP_PATH1="/Applications/QuickQ.app" +APP_PATH2="/Applications/QuickQ For Mac.app" + +# 动态检测可用路径(优先检测 QuickQ.app) +if [ -d "$APP_PATH1" ]; then + APP_PATH="$APP_PATH1" + APP_NAME="QuickQ" + echo "[$(date +"%T")] 检测到应用:$APP_PATH1" +elif [ -d "$APP_PATH2" ]; then + APP_PATH="$APP_PATH2" + APP_NAME="QuickQ For Mac" + echo "[$(date +"%T")] 检测到应用:$APP_PATH2" +else + echo "[$(date +"%T")] 错误:未找到 QuickQ 应用(检查路径 $APP_PATH1 和 $APP_PATH2)" + exit 1 +fi + +# 坐标参数 +LEFT_X=1520 +DROP_DOWN_BUTTON_X=200 +DROP_DOWN_BUTTON_Y=430 +CONNECT_BUTTON_X=200 +CONNECT_BUTTON_Y=260 +SETTINGS_BUTTON_X=349 +SETTINGS_BUTTON_Y=165 + +# ALCHEMY_URL 和主机名 +ALCHEMY_HOST="gensyn-testnet.g.alchemy.com" +ALCHEMY_URL="https://gensyn-testnet.g.alchemy.com/public" + +# 检查 Homebrew 是否安装 +if ! command -v brew &> /dev/null; then + echo "[$(date +"%T")] 错误:未找到 Homebrew,请先安装 Homebrew (https://brew.sh)" + exit 1 +fi + +# 检查 cliclick 依赖 +if ! command -v cliclick &> /dev/null; then + echo "[$(date +"%T")] 正在通过 Homebrew 安装 cliclick..." + brew install cliclick + if [ $? -ne 0 ]; then + echo "[$(date +"%T")] 错误:cliclick 安装失败" + exit 1 + fi + echo "[$(date +"%T")] cliclick 安装完成" +fi + +# 检查 nc(netcat)依赖 +if ! command -v nc &> /dev/null; then + echo "[$(date +"%T")] 正在通过 Homebrew 安装 netcat..." + brew install netcat + if [ $? -ne 0 ]; then + echo "[$(date +"%T")] 错误:netcat 安装失败" + exit 1 + fi + echo "[$(date +"%T")] netcat 安装完成" +else + echo "[$(date +"%T")] 检测到 netcat 已安装,跳过安装" +fi + +# 一次性权限触发操作 +if [ ! -f "/tmp/quickq_permissions_triggered" ]; then + echo "[$(date +"%T")] 正在执行一次性权限触发操作..." + open "$APP_PATH" + sleep 5 + osascript -e "tell application \"$APP_NAME\" to activate" + sleep 1 + adjust_window + cliclick c:${SETTINGS_BUTTON_X},${SETTINGS_BUTTON_Y} + echo "[$(date +"%T")] 已触发点击事件,请检查系统权限请求" + echo "[$(date +"%T")] 等待10秒以便您处理权限对话框..." + sleep 10 + pkill -9 -f "$APP_NAME" + touch "/tmp/quickq_permissions_triggered" + echo "[$(date +"%T")] 权限触发完成,标记已设置" +fi + +# 网络连通性检测函数 +check_network_connectivity() { + local PING_TIMEOUT=6 + local CURL_TIMEOUT=8 + local NC_TIMEOUT=5 + local success=false + + # 1. DNS 解析检查 + if host "$ALCHEMY_HOST" &> /dev/null; then + echo "[$(date +"%T")] 网络检测:DNS 解析 $ALCHEMY_HOST 成功" + else + echo "[$(date +"%T")] 网络检测:DNS 解析 $ALCHEMY_HOST 失败" + return 1 + fi + + # 2. HTTP/HTTPS 请求检查 + local http_code + http_code=$(curl --silent --head --fail --max-time $CURL_TIMEOUT -w "%{http_code}" -o /dev/null "$ALCHEMY_URL") + if [ "$http_code" -eq 200 ] || [ "$http_code" -eq 204 ]; then + echo "[$(date +"%T")] 网络检测:HTTP 请求 $ALCHEMY_URL 成功 (HTTP $http_code)" + success=true + else + echo "[$(date +"%T")] 网络检测:HTTP 请求 $ALCHEMY_URL 失败 (HTTP $http_code)" + fi + + # 3. TCP 连接检查(443 端口) + if nc -w $NC_TIMEOUT -z $ALCHEMY_HOST 443 &> /dev/null; then + echo "[$(date +"%T")] 网络检测:TCP 连接 $ALCHEMY_HOST:443 成功" + success=true + else + echo "[$(date +"%T")] 网络检测:TCP 连接 $ALCHEMY_HOST:443 失败" + fi + + # 4. Ping 检查(作为辅助,ICMP 可能被禁用) + if ping -c 1 -W $PING_TIMEOUT $ALCHEMY_HOST &> /dev/null; then + echo "[$(date +"%T")] 网络检测:Ping $ALCHEMY_HOST 成功" + success=true + else + echo "[$(date +"%T")] 网络检测:Ping $ALCHEMY_HOST 失败" + fi + + # 只要任一检查成功,就认为网络连通 + if $success; then + return 0 + else + echo "[$(date +"%T")] 网络检测:所有检查均失败" + return 1 + fi +} + +# QuickQ VPN 状态检测函数 +check_quickq_status() { + local QUICKQ_LOG="${APP_PATH}/Contents/Resources/logs/connection.log" + if [ -f "$QUICKQ_LOG" ]; then + if grep -i "Connected" "$QUICKQ_LOG" &> /dev/null; then + echo "[$(date +"%T")] QuickQ检测:VPN已连接" + last_vpn_status="connected" + return 0 + else + echo "[$(date +"%T")] QuickQ检测:VPN未连接" + last_vpn_status="disconnected" + return 1 + fi + else + return 1 + fi +} + +# VPN 状态检测函数 +check_vpn_connection() { + local TEST_URLS=( + "https://www.google.com/generate_204" + "https://www.youtube.com/generate_204" + ) + local PING_TEST="8.8.8.8" + local PING_TIMEOUT=6 + local CURL_TIMEOUT=8 + local MAX_RETRIES=3 + local retry_count=0 + + # 检查网络连通性 + if ! check_network_connectivity; then + echo "[$(date +"%T")] 网络连通性测试失败" + return 1 + fi + + # 检查 QuickQ VPN 状态 + if check_quickq_status; then + echo "[$(date +"%T")] VPN检测:QuickQ VPN 已连接" + last_vpn_status="connected" + return 0 + fi + + # 基础网络连通性测试(ping 8.8.8.8) + if ! ping -c 1 -W $PING_TIMEOUT $PING_TEST &> /dev/null; then + echo "[$(date +"%T")] 基础网络连通性测试失败(ping $PING_TEST)" + last_vpn_status="disconnected" + return 1 + fi + + # 轻量级 HTTP 204 测试 + while [ $retry_count -lt $MAX_RETRIES ]; do + for url in "${TEST_URLS[@]}"; do + local http_code + http_code=$(curl --silent --head --fail --max-time $CURL_TIMEOUT -w "%{http_code}" -o /dev/null "$url") + if [ "$http_code" -eq 204 ]; then + echo "[$(date +"%T")] VPN检测:通过 $url (HTTP 204)" + last_vpn_status="connected" + return 0 + fi + done + ((retry_count++)) + echo "[$(date +"%T")] VPN检测失败(尝试 $retry_count/$MAX_RETRIES)" + sleep 2 + done + + echo "[$(date +"%T")] VPN检测:所有轻量级端点均不可达" + last_vpn_status="disconnected" + return 1 +} + +# 窗口位置校准函数 +adjust_window() { + osascript < /dev/null; then + if check_vpn_connection; then + if [ "$last_vpn_status" == "disconnected" ]; then + echo "[$(date +"%T")] 状态变化:已建立VPN连接" + fi + reconnect_count=0 + + # 20分钟强制重连计时器 + total_wait=1200 # 20分钟 = 1200秒 + while [ $total_wait -gt 0 ]; do + remaining_min=$((total_wait / 60)) + echo "[$(date +"%T")] 下次强制重连将在 ${remaining_min} 分钟后进行..." + sleep 60 + total_wait=$((total_wait - 60)) + done + + # 20分钟时间到,强制重连 + echo "[$(date +"%T")] 20分钟计时结束,执行强制重连..." + terminate_app + sleep 2 + open "$APP_PATH" + echo "[$(date +"%T")] 应用启动中..." + sleep 10 + initialize_app + continue + else + echo "[$(date +"%T")] 检测到VPN未连接" + if [ $reconnect_count -lt 3 ]; then + connect_procedure + ((reconnect_count++)) + echo "[$(date +"%T")] 重试次数:$reconnect_count/3" + sleep 60 + else + echo "[$(date +"%T")] 达到重试上限,执行应用重置" + terminate_app + open "$APP_PATH" + echo "[$(date +"%T")] 应用启动中..." + sleep 10 + initialize_app + reconnect_count=0 + sleep 10 + fi + fi + else + echo "[$(date +"%T")] 应用未运行,正在启动..." + open "$APP_PATH" + sleep 10 + initialize_app + fi + sleep 5 +done \ No newline at end of file diff --git a/rgym_exp/config/rg-swarm.yaml b/rgym_exp/config/rg-swarm.yaml index 4a20167..a96fd60 100644 --- a/rgym_exp/config/rg-swarm.yaml +++ b/rgym_exp/config/rg-swarm.yaml @@ -1,5 +1,15 @@ log_dir: ${oc.env:ROOT,.}/logs +hydra: + run: + dir: ${log_dir} + job_logging: + handlers: + console: + level: INFO + root: + level: DEBUG + training: max_round: 1000000 max_stage: 1 @@ -7,27 +17,29 @@ training: num_generations: 2 num_transplant_trees: 2 seed: 42 - fp16: false # set this line to true if your hardware supports fp16 + dtype: 'bfloat16' blockchain: alchemy_url: "https://gensyn-testnet.g.alchemy.com/public" - contract_address: ${oc.env:SWARM_CONTRACT,null} # This is set by modal-login in run_rl_swarm.sh + swarm_contract_address: ${oc.env:SWARM_CONTRACT,null} # This is set by modal-login in run_rl_swarm.sh org_id: ${oc.env:ORG_ID,null} # This is set by modal-login in run_rl_swarm.sh mainnet_chain_id: 685685 # currently unused, will be used with WalletSwarmCoordinator modal_proxy_url: "http://localhost:3000/api/" - -communications: - initial_peers: - - '/ip4/38.101.215.15/tcp/30011/p2p/QmQ2gEXoPJg6iMBSUFWGzAabS2VhnzuS782Y637hGjfsRJ' - - '/ip4/38.101.215.15/tcp/30012/p2p/QmWhiaLrx3HRZfgXc2i7KW5nMUNK7P9tRc71yFJdGEZKkC' - - '/ip4/38.101.215.15/tcp/30013/p2p/QmQa1SCfYTxx7RvU7qJJRo79Zm1RAwPpkeLueDVJuBBmFp' + swarm_coordinator_abi_path: "rgym_exp/contracts/SwarmCoordinator_0.4.2.json" eval: - judge_base_url: https://swarm-judge-102957787771.us-east1.run.app + judge_base_url: "https://swarm-judge.internal-apps-central1.clusters.gensyn.ai" -hydra: - run: - dir: ${log_dir} +prg_game_config: + prg_game: ${oc.env:PRG_GAME,null} + org_id: ${blockchain.org_id} + modal_proxy_url: ${blockchain.modal_proxy_url} + +communications: + initial_peers: + - '/ip4/38.101.215.12/tcp/30011/p2p/QmQ2gEXoPJg6iMBSUFWGzAabS2VhnzuS782Y637hGjfsRJ' + - '/ip4/38.101.215.13/tcp/30012/p2p/QmWhiaLrx3HRZfgXc2i7KW5nMUNK7P9tRc71yFJdGEZKkC' + - '/ip4/38.101.215.14/tcp/30013/p2p/QmQa1SCfYTxx7RvU7qJJRo79Zm1RAwPpkeLueDVJuBBmFp' game_manager: _target_: rgym_exp.src.manager.SwarmGameManager @@ -38,6 +50,7 @@ game_manager: hf_push_frequency: ${training.hf_push_frequency} run_mode: "train_and_evaluate" bootnodes: ${communications.initial_peers} + prg_game_config: ${prg_game_config} game_state: _target_: genrl.state.game_state.GameState round: 0 @@ -58,20 +71,20 @@ game_manager: - _target_: transformers.AutoModelForCausalLM.from_pretrained pretrained_model_name_or_path: ${oc.env:MODEL_NAME, ${gpu_model_choice:${default_large_model_pool},${default_small_model_pool}}} config: - _target_: trl.trainer.GRPOConfig - logging_dir: ${log_dir} - fp16: ${training.fp16} + _target_: genrl.trainer.grpo_trainer.GRPOTrainerConfig + dtype: ${training.dtype} + epsilon: 0.2 + epsilon_high: 0.28 + num_generations: ${training.num_generations} log_with: wandb log_dir: ${log_dir} - epsilon: 0.2 - epsilon_high: 0.28 - num_generations: ${training.num_generations} judge_base_url: ${eval.judge_base_url} data_manager: _target_: rgym_exp.src.data.ReasoningGymDataManager yaml_config_path: "rgym_exp/src/datasets.yaml" num_train_samples: 2 num_evaluation_samples: 0 + num_generations: ${training.num_generations} system_prompt_id: 'default' seed: ${training.seed} num_transplant_trees: ${training.num_transplant_trees} @@ -82,12 +95,13 @@ game_manager: startup_timeout: 120 beam_size: 50 coordinator: - _target_: genrl.blockchain.coordinator.ModalSwarmCoordinator + _target_: rgym_exp.src.coordinator.ModalSwarmCoordinator web3_url: ${blockchain.alchemy_url} - contract_address: ${blockchain.contract_address} + contract_address: ${blockchain.swarm_contract_address} org_id: ${blockchain.org_id} modal_proxy_url: ${blockchain.modal_proxy_url} - + swarm_coordinator_abi_json: ${blockchain.swarm_coordinator_abi_path} + default_large_model_pool: - nvidia/AceInstruct-1.5B - dnotitia/Smoothie-Qwen3-1.7B diff --git a/rgym_exp/contracts/SwarmCoordinator_0.4.2.json b/rgym_exp/contracts/SwarmCoordinator_0.4.2.json new file mode 100644 index 0000000..0409cd2 --- /dev/null +++ b/rgym_exp/contracts/SwarmCoordinator_0.4.2.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"BOOTNODE_MANAGER_ROLE","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"OWNER_ROLE","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"STAGE_MANAGER_ROLE","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"UPGRADE_INTERFACE_VERSION","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"addBootnodes","inputs":[{"name":"newBootnodes","type":"string[]","internalType":"string[]"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"clearBootnodes","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"currentRound","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"currentStage","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getBootnodes","inputs":[],"outputs":[{"name":"","type":"string[]","internalType":"string[]"}],"stateMutability":"view"},{"type":"function","name":"getBootnodesCount","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getEoa","inputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"}],"outputs":[{"name":"","type":"address[]","internalType":"address[]"}],"stateMutability":"view"},{"type":"function","name":"getPeerId","inputs":[{"name":"eoas","type":"address[]","internalType":"address[]"}],"outputs":[{"name":"","type":"string[][]","internalType":"string[][]"}],"stateMutability":"view"},{"type":"function","name":"getPeerVoteCount","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getRoundStageReward","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"stageNumber","type":"uint256","internalType":"uint256"},{"name":"accounts","type":"address[]","internalType":"address[]"}],"outputs":[{"name":"","type":"int256[]","internalType":"int256[]"}],"stateMutability":"view"},{"type":"function","name":"getTotalRewards","inputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"}],"outputs":[{"name":"","type":"int256[]","internalType":"int256[]"}],"stateMutability":"view"},{"type":"function","name":"getTotalWins","inputs":[{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getVoterVoteCount","inputs":[{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getVoterVotes","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[{"name":"","type":"string[]","internalType":"string[]"}],"stateMutability":"view"},{"type":"function","name":"grantRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"hasRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"hasSubmittedRoundStageReward","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"stageNumber","type":"uint256","internalType":"uint256"},{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"initialize","inputs":[{"name":"owner_","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"registerPeer","inputs":[{"name":"peerId","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"removeBootnode","inputs":[{"name":"index","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"revokeRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"stageCount","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"submitReward","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"stageNumber","type":"uint256","internalType":"uint256"},{"name":"reward","type":"int256","internalType":"int256"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"submitWinners","inputs":[{"name":"roundNumber","type":"uint256","internalType":"uint256"},{"name":"winners","type":"string[]","internalType":"string[]"},{"name":"peerId","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"uniqueVotedPeers","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"uniqueVoters","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"updateStageAndRound","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"voterLeaderboard","inputs":[{"name":"start","type":"uint256","internalType":"uint256"},{"name":"end","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"},{"name":"voteCounts","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"function","name":"winnerLeaderboard","inputs":[{"name":"start","type":"uint256","internalType":"uint256"},{"name":"end","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"peerIds","type":"string[]","internalType":"string[]"},{"name":"wins","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"event","name":"AllBootnodesCleared","inputs":[{"name":"manager","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"BootnodeRemoved","inputs":[{"name":"manager","type":"address","indexed":true,"internalType":"address"},{"name":"index","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BootnodesAdded","inputs":[{"name":"manager","type":"address","indexed":true,"internalType":"address"},{"name":"count","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"CumulativeRewardsUpdated","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"},{"name":"totalRewards","type":"int256","indexed":false,"internalType":"int256"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"PeerRegistered","inputs":[{"name":"eoa","type":"address","indexed":true,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"type":"event","name":"RewardSubmitted","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"roundNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"stageNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"reward","type":"int256","indexed":false,"internalType":"int256"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"type":"event","name":"RoleGranted","inputs":[{"name":"role","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"sender","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"RoleRevoked","inputs":[{"name":"role","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"sender","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"RoundAdvanced","inputs":[{"name":"newRoundNumber","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"StageAdvanced","inputs":[{"name":"roundNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"newStage","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"WinnerSubmitted","inputs":[{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"peerId","type":"string","indexed":false,"internalType":"string"},{"name":"roundNumber","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"winners","type":"string[]","indexed":false,"internalType":"string[]"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]},{"type":"error","name":"InvalidBootnodeIndex","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"InvalidRoundNumber","inputs":[]},{"type":"error","name":"InvalidStageNumber","inputs":[]},{"type":"error","name":"InvalidVote","inputs":[]},{"type":"error","name":"InvalidVoterPeerId","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OnlyBootnodeManager","inputs":[]},{"type":"error","name":"OnlyOwner","inputs":[]},{"type":"error","name":"OnlyStageManager","inputs":[]},{"type":"error","name":"PeerIdAlreadyRegistered","inputs":[]},{"type":"error","name":"RewardAlreadySubmitted","inputs":[]},{"type":"error","name":"StageOutOfBounds","inputs":[]},{"type":"error","name":"UUPSUnauthorizedCallContext","inputs":[]},{"type":"error","name":"UUPSUnsupportedProxiableUUID","inputs":[{"name":"slot","type":"bytes32","internalType":"bytes32"}]},{"type":"error","name":"WinnerAlreadyVoted","inputs":[]}],"bytecode":{"object":"0x60a0604052306080525f8080556001553480156019575f5ffd5b506080516140b76100405f395f81816124240152818161244d01526126d201526140b75ff3fe608060405260043610610229575f3560e01c80636370ae4f11610131578063b894a469116100ac578063dfb3c7df1161007c578063e58378bb11610062578063e58378bb146106e2578063f33261ac14610715578063fbe94d6814610728575f5ffd5b8063dfb3c7df1461069a578063e28b0586146106b9575f5ffd5b8063b894a46914610611578063c4d66de81461063d578063d547741f1461065c578063d90d85731461067b575f5ffd5b806391d148541161010157806396bac35a116100e757806396bac35a1461057c578063ad3cb1cc146105a8578063b0c77404146105fd575f5ffd5b806391d14854146104c35780639291fee514610524575f5ffd5b80636370ae4f1461044a5780637c8973c71461045e57806380c3d97f146104915780638a19c8bc146104b0575f5ffd5b80634179a759116101c15780634f4026c3116101915780635194e15f116101775780635194e15f1461040357806352d1902d146104225780635bf5d54c14610436575f5ffd5b80634f4026c3146103c55780634f52ca36146103e4575f5ffd5b80634179a7591461037657806342d2c6a01461038a57806348495bdb1461039e5780634f1ef286146103b2575f5ffd5b80632bdd8ea6116101fc5780632bdd8ea6146102eb5780632f2ff15d146103175780632f4be6521461033857806333e7fb4514610357575f5ffd5b8063068dc3221461022d578063098f027f14610273578063099c40021461029f57806318a6fd88146102be575b5f5ffd5b348015610238575f5ffd5b506102607fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea181565b6040519081526020015b60405180910390f35b34801561027e575f5ffd5b5061029261028d3660046131cd565b610747565b60405161026a919061321c565b3480156102aa575f5ffd5b506102606102b936600461329c565b610842565b3480156102c9575f5ffd5b506102dd6102d83660046132db565b61086d565b60405161026a9291906133bf565b3480156102f6575f5ffd5b5061030a61030536600461340b565b610af0565b60405161026a9190613453565b348015610322575f5ffd5b5061033661033136600461348d565b610bf6565b005b348015610343575f5ffd5b506102dd6103523660046132db565b610c6b565b348015610362575f5ffd5b5061033661037136600461329c565b610edf565b348015610381575f5ffd5b50610336611036565b348015610395575f5ffd5b50600b54610260565b3480156103a9575f5ffd5b50600d54610260565b6103366103c03660046135aa565b6110d4565b3480156103d0575f5ffd5b506102606103df36600461340b565b6110ef565b3480156103ef575f5ffd5b506103366103fe366004613608565b611124565b34801561040e575f5ffd5b5061033661041d36600461361f565b611291565b34801561042d575f5ffd5b50610260611553565b348015610441575f5ffd5b50600154610260565b348015610455575f5ffd5b5061030a611581565b348015610469575f5ffd5b506102607f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41381565b34801561049c575f5ffd5b506102926104ab36600461367b565b611655565b3480156104bb575f5ffd5b505f54610260565b3480156104ce575f5ffd5b506105146104dd36600461348d565b5f91825260116020908152604080842073ffffffffffffffffffffffffffffffffffffffff93909316845291905290205460ff1690565b604051901515815260200161026a565b34801561052f575f5ffd5b5061051461053e3660046136ae565b5f928352600f6020908152604080852093855292815282842073ffffffffffffffffffffffffffffffffffffffff9290921684525290205460ff1690565b348015610587575f5ffd5b5061059b61059636600461367b565b611717565b60405161026a91906136e0565b3480156105b3575f5ffd5b506105f06040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161026a919061372d565b348015610608575f5ffd5b50600a54610260565b34801561061c575f5ffd5b5061063061062b36600461367b565b611807565b60405161026a919061373f565b348015610648575f5ffd5b50610336610657366004613824565b6119b6565b348015610667575f5ffd5b5061033661067636600461348d565b611bb1565b348015610686575f5ffd5b5061033661069536600461367b565b611c9a565b3480156106a5575f5ffd5b506102606106b436600461329c565b611d8d565b3480156106c4575f5ffd5b506106cd611da0565b6040805192835260208301919091520161026a565b3480156106ed575f5ffd5b506102607fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e81565b348015610720575f5ffd5b506003610260565b348015610733575f5ffd5b5061033661074236600461383d565b611ec0565b60605f8267ffffffffffffffff811115610763576107636134b7565b60405190808252806020026020018201604052801561078c578160200160208202803683370190505b5090505f5b83811015610838575f878152600e602090815260408083208984529091528120908686848181106107c4576107c4613947565b90506020020160208101906107d99190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205482828151811061082557610825613947565b6020908102919091010152600101610791565b5095945050505050565b5f60048383604051610855929190613974565b90815260200160405180910390205490505b92915050565b60608082841115610905576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084015b60405180910390fd5b6009548311156109155760095492505b6009548411156109255760095493505b5f61093085856139b0565b90508067ffffffffffffffff81111561094b5761094b6134b7565b60405190808252806020026020018201604052801561097e57816020015b60608152602001906001900390816109695790505b5092508067ffffffffffffffff81111561099a5761099a6134b7565b6040519080825280602002602001820160405280156109c3578160200160208202803683370190505b509150845b84811015610ae6575f6109db87836139b0565b90505f600983815481106109f1576109f1613947565b905f5260205f20018054610a04906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610a30906139c3565b8015610a7b5780601f10610a5257610100808354040283529160200191610a7b565b820191905f5260205f20905b815481529060010190602001808311610a5e57829003601f168201915b5050505050905080868381518110610a9557610a95613947565b6020026020010181905250600881604051610ab09190613a2b565b908152602001604051809103902054858381518110610ad157610ad1613947565b602090810291909101015250506001016109c8565b50505b9250929050565b606060065f8581526020019081526020015f208383604051610b13929190613974565b9081526020016040518091039020805480602002602001604051908101604052809291908181526020015f905b82821015610be8578382905f5260205f20018054610b5d906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610b89906139c3565b8015610bd45780601f10610bab57610100808354040283529160200191610bd4565b820191905f5260205f20905b815481529060010190602001808311610bb757829003601f168201915b505050505081526020019060010190610b40565b5050505090505b9392505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16610c5d576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c678282612387565b5050565b60608082841115610cfe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084016108fc565b600554831115610d0e5760055492505b600554841115610d1e5760055493505b5f610d2985856139b0565b90508067ffffffffffffffff811115610d4457610d446134b7565b604051908082528060200260200182016040528015610d7757816020015b6060815260200190600190039081610d625790505b5092508067ffffffffffffffff811115610d9357610d936134b7565b604051908082528060200260200182016040528015610dbc578160200160208202803683370190505b509150845b84811015610ae6575f610dd487836139b0565b90505f60058381548110610dea57610dea613947565b905f5260205f20018054610dfd906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610e29906139c3565b8015610e745780601f10610e4b57610100808354040283529160200191610e74565b820191905f5260205f20905b815481529060010190602001808311610e5757829003601f168201915b5050505050905080868381518110610e8e57610e8e613947565b6020026020010181905250600481604051610ea99190613a2b565b908152602001604051809103902054858381518110610eca57610eca613947565b60209081029190910101525050600101610dc1565b60405133905f90600390610ef69086908690613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614610f53576040517f723dc67500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f90815260026020908152604082208054600181018255908352912001610f92838583613a7a565b508060038484604051610fa6929190613974565b908152604051908190036020018120805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116179055908216907f13ff856599d1c93e876f34e507293c64647043cc0171caa42d35f8015c56455c906110299086908690613bd7565b60405180910390a2505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661109d576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110a8600d5f6130b6565b60405133907f283fdacb5de1271ad7865bfc841f02966aea2bb4d0211745186c12b16a3ce1b8905f90a2565b6110dc61240c565b6110e582612512565b610c67828261257c565b5f83815260076020526040808220905161110c9085908590613974565b90815260200160405180910390205490509392505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661118b576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d5481106111c6576040517f7d8aaf0e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d546111d5906001906139b0565b81101561122d57600d80546111ec906001906139b0565b815481106111fc576111fc613947565b905f5260205f2001600d828154811061121757611217613947565b905f5260205f2001908161122b9190613bf2565b505b600d80548061123e5761123e613d26565b600190038181905f5260205f20015f61125791906130d1565b905560405181815233907f82d82daba96d4df28e6cb421b83d49e88d4e4a448a9e768311afba927487f20c9060200160405180910390a250565b5f548511156112cc576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600154841115611308576040517ff7d2056300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600f60209081526040808320878452825280832033845290915290205460ff1615611363576040517fe39ce02e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166003838360405161138c929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff16146113e9576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600e60209081526040808320878452825280832033808552908352818420879055888452600f835281842088855283528184209084529091529081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183906010906114669085908590613974565b90815260200160405180910390205f8282546114829190613d53565b9250508190555083853373ffffffffffffffffffffffffffffffffffffffff167f3d9492e853616bfa85e7c758cbd89efdbe62254e78838c74c160192efa57f25f8686866040516114d593929190613d7a565b60405180910390a43373ffffffffffffffffffffffffffffffffffffffff167f984ef3ae4724684ba8e48f5e1e384dc71ecf5115bc094785d9399a59daf5d410838360108686604051611529929190613974565b90815260405190819003602001812054611544939291613d93565b60405180910390a25050505050565b5f61155c6126ba565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b6060600d805480602002602001604051908101604052809291908181526020015f905b8282101561164c578382905f5260205f200180546115c1906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546115ed906139c3565b80156116385780601f1061160f57610100808354040283529160200191611638565b820191905f5260205f20905b81548152906001019060200180831161161b57829003601f168201915b5050505050815260200190600101906115a4565b50505050905090565b60605f8267ffffffffffffffff811115611671576116716134b7565b60405190808252806020026020018201604052801561169a578160200160208202803683370190505b5090505f5b8381101561170f5760108585838181106116bb576116bb613947565b90506020028101906116cd9190613db6565b6040516116db929190613974565b9081526020016040518091039020548282815181106116fc576116fc613947565b602090810291909101015260010161169f565b509392505050565b60605f8267ffffffffffffffff811115611733576117336134b7565b60405190808252806020026020018201604052801561175c578160200160208202803683370190505b5090505f5b8381101561170f57600385858381811061177d5761177d613947565b905060200281019061178f9190613db6565b60405161179d929190613974565b90815260405190819003602001902054825173ffffffffffffffffffffffffffffffffffffffff909116908390839081106117da576117da613947565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910190910152600101611761565b60605f8267ffffffffffffffff811115611823576118236134b7565b60405190808252806020026020018201604052801561185657816020015b60608152602001906001900390816118415790505b5090505f5b8381101561170f5760025f86868481811061187857611878613947565b905060200201602081019061188d9190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20805480602002602001604051908101604052809291908181526020015f905b8282101561198d578382905f5260205f20018054611902906139c3565b80601f016020809104026020016040519081016040528092919081815260200182805461192e906139c3565b80156119795780601f1061195057610100808354040283529160200191611979565b820191905f5260205f20905b81548152906001019060200180831161195c57829003601f168201915b5050505050815260200190600101906118e5565b505050508282815181106119a3576119a3613947565b602090810291909101015260010161185b565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015611a005750825b90505f8267ffffffffffffffff166001148015611a1c5750303b155b905081158015611a2a575080155b15611a61576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315611ac25784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b611aec7fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e87612387565b611b167fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea187612387565b611b407f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41387612387565b611b48612729565b8315611ba95784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16611c18576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff16611d01576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805f5b81811015611d5a57600d848483818110611d2057611d20613947565b9050602002810190611d329190613db6565b82546001810184555f938452602090932090920191611d519183613a7a565b50600101611d04565b5060405181815233907fa9a386aeb1871393ce021c503e25c80ac4d26812ec75539a703f472b818b5c6c90602001611029565b5f60088383604051610855929190613974565b335f9081527fe80ab0e0498f589701ca797b07aa379494ce082364fb632f0f085ad40105ec1f6020526040812054819060ff16611e09576040517f0719ac8c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60036001546001611e1a9190613e17565b10611e67575f80549080611e2d83613e2a565b90915550505f6001819055805460405190917f023811fd72d20a3eb734785eed809172b5c9c24019d493039c70ef9c276d4d9791a2611e78565b60018054611e7491613e17565b6001555b5f547f373b83833fa259ee8a1c96ccea2cb633a5b88dd292e40ff9f8c103d8ce10c577600154604051611ead91815260200190565b60405180910390a250505f546001549091565b5f54841115611efb576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f848152600660205260408082209051611f189085908590613974565b908152604051908190036020019020541115611f60576040517f3d51d82700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff1660038383604051611f89929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614611fe6576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8351811015612093575f611ffd826001613e17565b90505b845181101561208a5784818151811061201b5761201b613947565b60200260200101518051906020012085838151811061203c5761203c613947565b60200260200101518051906020012003612082576040517fd5dd0c6600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612000565b50600101611fe8565b50600882826040516120a6929190613974565b9081526020016040518091039020545f036120d057600a8054905f6120ca83613e2a565b91905055505b8260065f8681526020019081526020015f2083836040516120f2929190613974565b90815260200160405180910390209080519060200190612113929190613108565b505f5b835181101561223c575f858152600760205260409020845185908390811061214057612140613947565b60200260200101516040516121559190613a2b565b9081526040519081900360200190208054905f61217183613e2a565b9190505550600c84828151811061218a5761218a613947565b602002602001015160405161219f9190613a2b565b9081526040519081900360200190205460ff16612234576001600c8583815181106121cc576121cc613947565b60200260200101516040516121e19190613a2b565b90815260405190819003602001902080549115157fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909216919091179055600b8054905f61222e83613e2a565b91905055505b600101612116565b506008828260405161224f929190613974565b9081526040519081900360200190208054905f61226b83613e2a565b91905055506122ae82828080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061273192505050565b5f5b835181101561232d5760048482815181106122cd576122cd613947565b60200260200101516040516122e29190613a2b565b9081526040519081900360200190208054905f6122fe83613e2a565b919050555061232584828151811061231857612318613947565b6020026020010151612a76565b6001016122b0565b50833373ffffffffffffffffffffffffffffffffffffffff167fc49e95fe7063aa0550cf4b88accd643a2eca314054a882fc705e1874a294aa3084848760405161237993929190613e61565b60405180910390a350505050565b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905551339285917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a45050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614806124d957507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166124c07f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1614155b15612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16612579576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b8173ffffffffffffffffffffffffffffffffffffffff166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015612601575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526125fe91810190613e90565b60015b61264f576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81146126ab576040517faa1d49a4000000000000000000000000000000000000000000000000000000008152600481018290526024016108fc565b6126b58383612d97565b505050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612510612df9565b5f6008826040516127429190613a2b565b908152604051908190036020019020546009549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b818110156127d15784805190602001206009828154811061279f5761279f613947565b905f5260205f20016040516127b49190613f33565b6040518091039020036127c9578092506127d1565b60010161277c565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036128ec57606481101561285e57600980546001810182555f919091527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af0161283d8582613f3e565b508061284881613e2a565b915061285790506001826139b0565b91506128ec565b826008600961286e6001856139b0565b8154811061287e5761287e613947565b905f5260205f20016040516128939190613f33565b90815260200160405180910390205410156128e6578360096128b66001846139b0565b815481106128c6576128c6613947565b905f5260205f200190816128da9190613f3e565b506128576001826139b0565b50505050565b815b5f8311801561293e575083600860096129086001876139b0565b8154811061291857612918613947565b905f5260205f200160405161292d9190613f33565b908152602001604051809103902054105b15612955578261294d8161404d565b9350506128ee565b808314612a6f575f6009848154811061297057612970613947565b905f5260205f20018054612983906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546129af906139c3565b80156129fa5780601f106129d1576101008083540402835291602001916129fa565b820191905f5260205f20905b8154815290600101906020018083116129dd57829003601f168201915b5050505050905060098281548110612a1457612a14613947565b905f5260205f200160098581548110612a2f57612a2f613947565b905f5260205f20019081612a439190613bf2565b508060098381548110612a5857612a58613947565b905f5260205f20019081612a6c9190613f3e565b50505b5050505050565b5f600482604051612a879190613a2b565b908152604051908190036020019020546005549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b81811015612b1657848051906020012060058281548110612ae457612ae4613947565b905f5260205f2001604051612af99190613f33565b604051809103902003612b0e57809250612b16565b600101612ac1565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612c2b576064811015612ba357600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001612b828582613f3e565b5080612b8d81613e2a565b9150612b9c90506001826139b0565b9150612c2b565b8260046005612bb36001856139b0565b81548110612bc357612bc3613947565b905f5260205f2001604051612bd89190613f33565b90815260200160405180910390205410156128e657836005612bfb6001846139b0565b81548110612c0b57612c0b613947565b905f5260205f20019081612c1f9190613f3e565b50612b9c6001826139b0565b815b5f83118015612c7d57508360046005612c476001876139b0565b81548110612c5757612c57613947565b905f5260205f2001604051612c6c9190613f33565b908152602001604051809103902054105b15612c945782612c8c8161404d565b935050612c2d565b808314612a6f575f60058481548110612caf57612caf613947565b905f5260205f20018054612cc2906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054612cee906139c3565b8015612d395780601f10612d1057610100808354040283529160200191612d39565b820191905f5260205f20905b815481529060010190602001808311612d1c57829003601f168201915b5050505050905060058281548110612d5357612d53613947565b905f5260205f200160058581548110612d6e57612d6e613947565b905f5260205f20019081612d829190613bf2565b508060058381548110612a5857612a58613947565b612da082612e60565b60405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115612df1576126b58282612f2e565b610c67612fad565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff16612510576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163b5f03612ec8576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff1684604051612f579190613a2b565b5f60405180830381855af49150503d805f8114612f8f576040519150601f19603f3d011682016040523d82523d5f602084013e612f94565b606091505b5091509150612fa4858383612fe5565b95945050505050565b3415612510576040517fb398979f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082612ffa57612ff582613074565b610bef565b815115801561301e575073ffffffffffffffffffffffffffffffffffffffff84163b155b1561306d576040517f9996b31500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526024016108fc565b5080610bef565b8051156130845780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5080545f8255905f5260205f2090810190612579919061315c565b5080546130dd906139c3565b5f825580601f106130ec575050565b601f0160209004905f5260205f20908101906125799190613178565b828054828255905f5260205f2090810192821561314c579160200282015b8281111561314c578251829061313c9082613f3e565b5091602001919060010190613126565b5061315892915061315c565b5090565b80821115613158575f61316f82826130d1565b5060010161315c565b5b80821115613158575f8155600101613179565b5f5f83601f84011261319c575f5ffd5b50813567ffffffffffffffff8111156131b3575f5ffd5b6020830191508360208260051b8501011115610ae9575f5ffd5b5f5f5f5f606085870312156131e0575f5ffd5b8435935060208501359250604085013567ffffffffffffffff811115613204575f5ffd5b6132108782880161318c565b95989497509550505050565b602080825282518282018190525f918401906040840190835b81811015613253578351835260209384019390920191600101613235565b509095945050505050565b5f5f83601f84011261326e575f5ffd5b50813567ffffffffffffffff811115613285575f5ffd5b602083019150836020828501011115610ae9575f5ffd5b5f5f602083850312156132ad575f5ffd5b823567ffffffffffffffff8111156132c3575f5ffd5b6132cf8582860161325e565b90969095509350505050565b5f5f604083850312156132ec575f5ffd5b50508035926020909101359150565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b5f82825180855260208501945060208160051b830101602085015f5b838110156133b3577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840301885261339d8383516132fb565b6020988901989093509190910190600101613363565b50909695505050505050565b604081525f6133d16040830185613347565b82810360208401528084518083526020830191506020860192505f5b818110156133b35783518352602093840193909201916001016133ed565b5f5f5f6040848603121561341d575f5ffd5b83359250602084013567ffffffffffffffff81111561343a575f5ffd5b6134468682870161325e565b9497909650939450505050565b602081525f610bef6020830184613347565b803573ffffffffffffffffffffffffffffffffffffffff81168114613488575f5ffd5b919050565b5f5f6040838503121561349e575f5ffd5b823591506134ae60208401613465565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561352b5761352b6134b7565b604052919050565b5f5f67ffffffffffffffff84111561354d5761354d6134b7565b50601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016602001613580816134e4565b915050828152838383011115613594575f5ffd5b828260208301375f602084830101529392505050565b5f5f604083850312156135bb575f5ffd5b6135c483613465565b9150602083013567ffffffffffffffff8111156135df575f5ffd5b8301601f810185136135ef575f5ffd5b6135fe85823560208401613533565b9150509250929050565b5f60208284031215613618575f5ffd5b5035919050565b5f5f5f5f5f60808688031215613633575f5ffd5b853594506020860135935060408601359250606086013567ffffffffffffffff81111561365e575f5ffd5b61366a8882890161325e565b969995985093965092949392505050565b5f5f6020838503121561368c575f5ffd5b823567ffffffffffffffff8111156136a2575f5ffd5b6132cf8582860161318c565b5f5f5f606084860312156136c0575f5ffd5b83359250602084013591506136d760408501613465565b90509250925092565b602080825282518282018190525f918401906040840190835b8181101561325357835173ffffffffffffffffffffffffffffffffffffffff168352602093840193909201916001016136f9565b602081525f610bef60208301846132fb565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b82811015613818578685037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0018452815180518087526020918201918088019190600582901b8901015f5b828110156137ff577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08a83030184526137ea8286516132fb565b602095860195949094019391506001016137b0565b5097505050602094850194929092019150600101613765565b50929695505050505050565b5f60208284031215613834575f5ffd5b610bef82613465565b5f5f5f5f60608587031215613850575f5ffd5b84359350602085013567ffffffffffffffff81111561386d575f5ffd5b8501601f8101871361387d575f5ffd5b803567ffffffffffffffff811115613897576138976134b7565b8060051b6138a7602082016134e4565b9182526020818401810192908101908a8411156138c2575f5ffd5b6020850192505b8383101561391b57823567ffffffffffffffff8111156138e7575f5ffd5b8501603f81018c136138f7575f5ffd5b6139098c602083013560408401613533565b835250602092830192909101906138c9565b96505050506040860135905067ffffffffffffffff81111561393b575f5ffd5b6132108782880161325e565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b818382375f9101908152919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8181038181111561086757610867613983565b600181811c908216806139d757607f821691505b602082108103613a0e577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b5f81518060208401855e5f93019283525090919050565b5f610bef8284613a14565b601f8211156126b557805f5260205f20601f840160051c81016020851015613a5b5750805b601f840160051c820191505b81811015612a6f575f8155600101613a67565b67ffffffffffffffff831115613a9257613a926134b7565b613aa683613aa083546139c3565b83613a36565b5f601f841160018114613af6575f8515613ac05750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355612a6f565b5f838152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08716915b82811015613b435786850135825560209485019460019092019101613b23565b5086821015613b7e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081525f613bea602083018486613b90565b949350505050565b818103613bfd575050565b613c0782546139c3565b67ffffffffffffffff811115613c1f57613c1f6134b7565b613c3381613c2d84546139c3565b84613a36565b5f601f821160018114613c86575f8315613c4d5750848201545b600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c198216175b855550612a6f565b5f85815260208082208683529082207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616925b83811015613cda5782860154825560019586019590910190602001613cba565b5085831015613d1657818501547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b8082018281125f831280158216821582161715613d7257613d72613983565b505092915050565b838152604060208201525f612fa4604083018486613b90565b604081525f613da6604083018587613b90565b9050826020830152949350505050565b5f5f83357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613de9575f5ffd5b83018035915067ffffffffffffffff821115613e03575f5ffd5b602001915036819003821315610ae9575f5ffd5b8082018082111561086757610867613983565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e5a57613e5a613983565b5060010190565b604081525f613e74604083018587613b90565b8281036020840152613e868185613347565b9695505050505050565b5f60208284031215613ea0575f5ffd5b5051919050565b5f8154613eb3816139c3565b600182168015613eca5760018114613efd57613f2a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083168652811515820286019350613f2a565b845f5260205f205f5b83811015613f2257815488820152600190910190602001613f06565b505081860193505b50505092915050565b5f610bef8284613ea7565b815167ffffffffffffffff811115613f5857613f586134b7565b613f6681613c2d84546139c3565b6020601f821160018114613fb5575f8315613c4d575081850151600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c19821617613c7e565b5f848152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08516915b828110156140025787850151825560209485019460019092019101613fe2565b508482101561403e57868401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b60f8161c191681555b50505050600190811b01905550565b5f8161405b5761405b613983565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019056fea26469706673582212208e3a1fc194745e7dfbc7f1f427b10b646db112f496dc8297b4a48b7b053bd15964736f6c634300081d0033","sourceMap":"340:41091:34:-:0;;;1171:4:20;1128:48;;1574:1:34;1550:25;;;1619;;340:41091;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405260043610610229575f3560e01c80636370ae4f11610131578063b894a469116100ac578063dfb3c7df1161007c578063e58378bb11610062578063e58378bb146106e2578063f33261ac14610715578063fbe94d6814610728575f5ffd5b8063dfb3c7df1461069a578063e28b0586146106b9575f5ffd5b8063b894a46914610611578063c4d66de81461063d578063d547741f1461065c578063d90d85731461067b575f5ffd5b806391d148541161010157806396bac35a116100e757806396bac35a1461057c578063ad3cb1cc146105a8578063b0c77404146105fd575f5ffd5b806391d14854146104c35780639291fee514610524575f5ffd5b80636370ae4f1461044a5780637c8973c71461045e57806380c3d97f146104915780638a19c8bc146104b0575f5ffd5b80634179a759116101c15780634f4026c3116101915780635194e15f116101775780635194e15f1461040357806352d1902d146104225780635bf5d54c14610436575f5ffd5b80634f4026c3146103c55780634f52ca36146103e4575f5ffd5b80634179a7591461037657806342d2c6a01461038a57806348495bdb1461039e5780634f1ef286146103b2575f5ffd5b80632bdd8ea6116101fc5780632bdd8ea6146102eb5780632f2ff15d146103175780632f4be6521461033857806333e7fb4514610357575f5ffd5b8063068dc3221461022d578063098f027f14610273578063099c40021461029f57806318a6fd88146102be575b5f5ffd5b348015610238575f5ffd5b506102607fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea181565b6040519081526020015b60405180910390f35b34801561027e575f5ffd5b5061029261028d3660046131cd565b610747565b60405161026a919061321c565b3480156102aa575f5ffd5b506102606102b936600461329c565b610842565b3480156102c9575f5ffd5b506102dd6102d83660046132db565b61086d565b60405161026a9291906133bf565b3480156102f6575f5ffd5b5061030a61030536600461340b565b610af0565b60405161026a9190613453565b348015610322575f5ffd5b5061033661033136600461348d565b610bf6565b005b348015610343575f5ffd5b506102dd6103523660046132db565b610c6b565b348015610362575f5ffd5b5061033661037136600461329c565b610edf565b348015610381575f5ffd5b50610336611036565b348015610395575f5ffd5b50600b54610260565b3480156103a9575f5ffd5b50600d54610260565b6103366103c03660046135aa565b6110d4565b3480156103d0575f5ffd5b506102606103df36600461340b565b6110ef565b3480156103ef575f5ffd5b506103366103fe366004613608565b611124565b34801561040e575f5ffd5b5061033661041d36600461361f565b611291565b34801561042d575f5ffd5b50610260611553565b348015610441575f5ffd5b50600154610260565b348015610455575f5ffd5b5061030a611581565b348015610469575f5ffd5b506102607f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41381565b34801561049c575f5ffd5b506102926104ab36600461367b565b611655565b3480156104bb575f5ffd5b505f54610260565b3480156104ce575f5ffd5b506105146104dd36600461348d565b5f91825260116020908152604080842073ffffffffffffffffffffffffffffffffffffffff93909316845291905290205460ff1690565b604051901515815260200161026a565b34801561052f575f5ffd5b5061051461053e3660046136ae565b5f928352600f6020908152604080852093855292815282842073ffffffffffffffffffffffffffffffffffffffff9290921684525290205460ff1690565b348015610587575f5ffd5b5061059b61059636600461367b565b611717565b60405161026a91906136e0565b3480156105b3575f5ffd5b506105f06040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161026a919061372d565b348015610608575f5ffd5b50600a54610260565b34801561061c575f5ffd5b5061063061062b36600461367b565b611807565b60405161026a919061373f565b348015610648575f5ffd5b50610336610657366004613824565b6119b6565b348015610667575f5ffd5b5061033661067636600461348d565b611bb1565b348015610686575f5ffd5b5061033661069536600461367b565b611c9a565b3480156106a5575f5ffd5b506102606106b436600461329c565b611d8d565b3480156106c4575f5ffd5b506106cd611da0565b6040805192835260208301919091520161026a565b3480156106ed575f5ffd5b506102607fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e81565b348015610720575f5ffd5b506003610260565b348015610733575f5ffd5b5061033661074236600461383d565b611ec0565b60605f8267ffffffffffffffff811115610763576107636134b7565b60405190808252806020026020018201604052801561078c578160200160208202803683370190505b5090505f5b83811015610838575f878152600e602090815260408083208984529091528120908686848181106107c4576107c4613947565b90506020020160208101906107d99190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205482828151811061082557610825613947565b6020908102919091010152600101610791565b5095945050505050565b5f60048383604051610855929190613974565b90815260200160405180910390205490505b92915050565b60608082841115610905576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084015b60405180910390fd5b6009548311156109155760095492505b6009548411156109255760095493505b5f61093085856139b0565b90508067ffffffffffffffff81111561094b5761094b6134b7565b60405190808252806020026020018201604052801561097e57816020015b60608152602001906001900390816109695790505b5092508067ffffffffffffffff81111561099a5761099a6134b7565b6040519080825280602002602001820160405280156109c3578160200160208202803683370190505b509150845b84811015610ae6575f6109db87836139b0565b90505f600983815481106109f1576109f1613947565b905f5260205f20018054610a04906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610a30906139c3565b8015610a7b5780601f10610a5257610100808354040283529160200191610a7b565b820191905f5260205f20905b815481529060010190602001808311610a5e57829003601f168201915b5050505050905080868381518110610a9557610a95613947565b6020026020010181905250600881604051610ab09190613a2b565b908152602001604051809103902054858381518110610ad157610ad1613947565b602090810291909101015250506001016109c8565b50505b9250929050565b606060065f8581526020019081526020015f208383604051610b13929190613974565b9081526020016040518091039020805480602002602001604051908101604052809291908181526020015f905b82821015610be8578382905f5260205f20018054610b5d906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610b89906139c3565b8015610bd45780601f10610bab57610100808354040283529160200191610bd4565b820191905f5260205f20905b815481529060010190602001808311610bb757829003601f168201915b505050505081526020019060010190610b40565b5050505090505b9392505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16610c5d576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c678282612387565b5050565b60608082841115610cfe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f537461727420696e646578206d757374206265206c657373207468616e206f7260448201527f20657175616c20746f20656e6420696e6465780000000000000000000000000060648201526084016108fc565b600554831115610d0e5760055492505b600554841115610d1e5760055493505b5f610d2985856139b0565b90508067ffffffffffffffff811115610d4457610d446134b7565b604051908082528060200260200182016040528015610d7757816020015b6060815260200190600190039081610d625790505b5092508067ffffffffffffffff811115610d9357610d936134b7565b604051908082528060200260200182016040528015610dbc578160200160208202803683370190505b509150845b84811015610ae6575f610dd487836139b0565b90505f60058381548110610dea57610dea613947565b905f5260205f20018054610dfd906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054610e29906139c3565b8015610e745780601f10610e4b57610100808354040283529160200191610e74565b820191905f5260205f20905b815481529060010190602001808311610e5757829003601f168201915b5050505050905080868381518110610e8e57610e8e613947565b6020026020010181905250600481604051610ea99190613a2b565b908152602001604051809103902054858381518110610eca57610eca613947565b60209081029190910101525050600101610dc1565b60405133905f90600390610ef69086908690613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614610f53576040517f723dc67500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f90815260026020908152604082208054600181018255908352912001610f92838583613a7a565b508060038484604051610fa6929190613974565b908152604051908190036020018120805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116179055908216907f13ff856599d1c93e876f34e507293c64647043cc0171caa42d35f8015c56455c906110299086908690613bd7565b60405180910390a2505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661109d576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110a8600d5f6130b6565b60405133907f283fdacb5de1271ad7865bfc841f02966aea2bb4d0211745186c12b16a3ce1b8905f90a2565b6110dc61240c565b6110e582612512565b610c67828261257c565b5f83815260076020526040808220905161110c9085908590613974565b90815260200160405180910390205490509392505050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff1661118b576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d5481106111c6576040517f7d8aaf0e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d546111d5906001906139b0565b81101561122d57600d80546111ec906001906139b0565b815481106111fc576111fc613947565b905f5260205f2001600d828154811061121757611217613947565b905f5260205f2001908161122b9190613bf2565b505b600d80548061123e5761123e613d26565b600190038181905f5260205f20015f61125791906130d1565b905560405181815233907f82d82daba96d4df28e6cb421b83d49e88d4e4a448a9e768311afba927487f20c9060200160405180910390a250565b5f548511156112cc576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600154841115611308576040517ff7d2056300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600f60209081526040808320878452825280832033845290915290205460ff1615611363576040517fe39ce02e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166003838360405161138c929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff16146113e9576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f858152600e60209081526040808320878452825280832033808552908352818420879055888452600f835281842088855283528184209084529091529081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183906010906114669085908590613974565b90815260200160405180910390205f8282546114829190613d53565b9250508190555083853373ffffffffffffffffffffffffffffffffffffffff167f3d9492e853616bfa85e7c758cbd89efdbe62254e78838c74c160192efa57f25f8686866040516114d593929190613d7a565b60405180910390a43373ffffffffffffffffffffffffffffffffffffffff167f984ef3ae4724684ba8e48f5e1e384dc71ecf5115bc094785d9399a59daf5d410838360108686604051611529929190613974565b90815260405190819003602001812054611544939291613d93565b60405180910390a25050505050565b5f61155c6126ba565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b6060600d805480602002602001604051908101604052809291908181526020015f905b8282101561164c578382905f5260205f200180546115c1906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546115ed906139c3565b80156116385780601f1061160f57610100808354040283529160200191611638565b820191905f5260205f20905b81548152906001019060200180831161161b57829003601f168201915b5050505050815260200190600101906115a4565b50505050905090565b60605f8267ffffffffffffffff811115611671576116716134b7565b60405190808252806020026020018201604052801561169a578160200160208202803683370190505b5090505f5b8381101561170f5760108585838181106116bb576116bb613947565b90506020028101906116cd9190613db6565b6040516116db929190613974565b9081526020016040518091039020548282815181106116fc576116fc613947565b602090810291909101015260010161169f565b509392505050565b60605f8267ffffffffffffffff811115611733576117336134b7565b60405190808252806020026020018201604052801561175c578160200160208202803683370190505b5090505f5b8381101561170f57600385858381811061177d5761177d613947565b905060200281019061178f9190613db6565b60405161179d929190613974565b90815260405190819003602001902054825173ffffffffffffffffffffffffffffffffffffffff909116908390839081106117da576117da613947565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910190910152600101611761565b60605f8267ffffffffffffffff811115611823576118236134b7565b60405190808252806020026020018201604052801561185657816020015b60608152602001906001900390816118415790505b5090505f5b8381101561170f5760025f86868481811061187857611878613947565b905060200201602081019061188d9190613824565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20805480602002602001604051908101604052809291908181526020015f905b8282101561198d578382905f5260205f20018054611902906139c3565b80601f016020809104026020016040519081016040528092919081815260200182805461192e906139c3565b80156119795780601f1061195057610100808354040283529160200191611979565b820191905f5260205f20905b81548152906001019060200180831161195c57829003601f168201915b5050505050815260200190600101906118e5565b505050508282815181106119a3576119a3613947565b602090810291909101015260010161185b565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015611a005750825b90505f8267ffffffffffffffff166001148015611a1c5750303b155b905081158015611a2a575080155b15611a61576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315611ac25784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b611aec7fb19546dff01e856fb3f010c267a7b1c60363cf8a4664e21cc89c26224620214e87612387565b611b167fcf0699fb89ab6c55a83a3d15d054926c4f9dc24cd23677cb3fcc9fbc31d7fea187612387565b611b407f9900d33a52698f1474f45bfcefc86b8979f9b4e1ec601d0a29ece183eb99d41387612387565b611b48612729565b8315611ba95784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16611c18576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b335f9081527fafd7ccd5bc85f1aae937543ac8f6b5cb0e2b451c8cea05c1640257a2a92c3e6e602052604090205460ff16611d01576040517f014c1cb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805f5b81811015611d5a57600d848483818110611d2057611d20613947565b9050602002810190611d329190613db6565b82546001810184555f938452602090932090920191611d519183613a7a565b50600101611d04565b5060405181815233907fa9a386aeb1871393ce021c503e25c80ac4d26812ec75539a703f472b818b5c6c90602001611029565b5f60088383604051610855929190613974565b335f9081527fe80ab0e0498f589701ca797b07aa379494ce082364fb632f0f085ad40105ec1f6020526040812054819060ff16611e09576040517f0719ac8c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60036001546001611e1a9190613e17565b10611e67575f80549080611e2d83613e2a565b90915550505f6001819055805460405190917f023811fd72d20a3eb734785eed809172b5c9c24019d493039c70ef9c276d4d9791a2611e78565b60018054611e7491613e17565b6001555b5f547f373b83833fa259ee8a1c96ccea2cb633a5b88dd292e40ff9f8c103d8ce10c577600154604051611ead91815260200190565b60405180910390a250505f546001549091565b5f54841115611efb576040517f4197f6ea00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f848152600660205260408082209051611f189085908590613974565b908152604051908190036020019020541115611f60576040517f3d51d82700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff1660038383604051611f89929190613974565b9081526040519081900360200190205473ffffffffffffffffffffffffffffffffffffffff1614611fe6576040517f1614b50c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8351811015612093575f611ffd826001613e17565b90505b845181101561208a5784818151811061201b5761201b613947565b60200260200101518051906020012085838151811061203c5761203c613947565b60200260200101518051906020012003612082576040517fd5dd0c6600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612000565b50600101611fe8565b50600882826040516120a6929190613974565b9081526020016040518091039020545f036120d057600a8054905f6120ca83613e2a565b91905055505b8260065f8681526020019081526020015f2083836040516120f2929190613974565b90815260200160405180910390209080519060200190612113929190613108565b505f5b835181101561223c575f858152600760205260409020845185908390811061214057612140613947565b60200260200101516040516121559190613a2b565b9081526040519081900360200190208054905f61217183613e2a565b9190505550600c84828151811061218a5761218a613947565b602002602001015160405161219f9190613a2b565b9081526040519081900360200190205460ff16612234576001600c8583815181106121cc576121cc613947565b60200260200101516040516121e19190613a2b565b90815260405190819003602001902080549115157fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909216919091179055600b8054905f61222e83613e2a565b91905055505b600101612116565b506008828260405161224f929190613974565b9081526040519081900360200190208054905f61226b83613e2a565b91905055506122ae82828080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061273192505050565b5f5b835181101561232d5760048482815181106122cd576122cd613947565b60200260200101516040516122e29190613a2b565b9081526040519081900360200190208054905f6122fe83613e2a565b919050555061232584828151811061231857612318613947565b6020026020010151612a76565b6001016122b0565b50833373ffffffffffffffffffffffffffffffffffffffff167fc49e95fe7063aa0550cf4b88accd643a2eca314054a882fc705e1874a294aa3084848760405161237993929190613e61565b60405180910390a350505050565b5f82815260116020908152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905551339285917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a45050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614806124d957507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166124c07f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1614155b15612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b335f9081527f4c7c9fa41d43345216e0640e42c76e99c38daaa056860bfbda0dbcf4603e47a3602052604090205460ff16612579576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b8173ffffffffffffffffffffffffffffffffffffffff166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015612601575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526125fe91810190613e90565b60015b61264f576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81146126ab576040517faa1d49a4000000000000000000000000000000000000000000000000000000008152600481018290526024016108fc565b6126b58383612d97565b505050565b3073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614612510576040517fe07c8dba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612510612df9565b5f6008826040516127429190613a2b565b908152604051908190036020019020546009549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b818110156127d15784805190602001206009828154811061279f5761279f613947565b905f5260205f20016040516127b49190613f33565b6040518091039020036127c9578092506127d1565b60010161277c565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036128ec57606481101561285e57600980546001810182555f919091527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af0161283d8582613f3e565b508061284881613e2a565b915061285790506001826139b0565b91506128ec565b826008600961286e6001856139b0565b8154811061287e5761287e613947565b905f5260205f20016040516128939190613f33565b90815260200160405180910390205410156128e6578360096128b66001846139b0565b815481106128c6576128c6613947565b905f5260205f200190816128da9190613f3e565b506128576001826139b0565b50505050565b815b5f8311801561293e575083600860096129086001876139b0565b8154811061291857612918613947565b905f5260205f200160405161292d9190613f33565b908152602001604051809103902054105b15612955578261294d8161404d565b9350506128ee565b808314612a6f575f6009848154811061297057612970613947565b905f5260205f20018054612983906139c3565b80601f01602080910402602001604051908101604052809291908181526020018280546129af906139c3565b80156129fa5780601f106129d1576101008083540402835291602001916129fa565b820191905f5260205f20905b8154815290600101906020018083116129dd57829003601f168201915b5050505050905060098281548110612a1457612a14613947565b905f5260205f200160098581548110612a2f57612a2f613947565b905f5260205f20019081612a439190613bf2565b508060098381548110612a5857612a58613947565b905f5260205f20019081612a6c9190613f3e565b50505b5050505050565b5f600482604051612a879190613a2b565b908152604051908190036020019020546005549091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905f5b81811015612b1657848051906020012060058281548110612ae457612ae4613947565b905f5260205f2001604051612af99190613f33565b604051809103902003612b0e57809250612b16565b600101612ac1565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612c2b576064811015612ba357600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db001612b828582613f3e565b5080612b8d81613e2a565b9150612b9c90506001826139b0565b9150612c2b565b8260046005612bb36001856139b0565b81548110612bc357612bc3613947565b905f5260205f2001604051612bd89190613f33565b90815260200160405180910390205410156128e657836005612bfb6001846139b0565b81548110612c0b57612c0b613947565b905f5260205f20019081612c1f9190613f3e565b50612b9c6001826139b0565b815b5f83118015612c7d57508360046005612c476001876139b0565b81548110612c5757612c57613947565b905f5260205f2001604051612c6c9190613f33565b908152602001604051809103902054105b15612c945782612c8c8161404d565b935050612c2d565b808314612a6f575f60058481548110612caf57612caf613947565b905f5260205f20018054612cc2906139c3565b80601f0160208091040260200160405190810160405280929190818152602001828054612cee906139c3565b8015612d395780601f10612d1057610100808354040283529160200191612d39565b820191905f5260205f20905b815481529060010190602001808311612d1c57829003601f168201915b5050505050905060058281548110612d5357612d53613947565b905f5260205f200160058581548110612d6e57612d6e613947565b905f5260205f20019081612d829190613bf2565b508060058381548110612a5857612a58613947565b612da082612e60565b60405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115612df1576126b58282612f2e565b610c67612fad565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff16612510576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163b5f03612ec8576040517f4c9c8ce300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016108fc565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60605f5f8473ffffffffffffffffffffffffffffffffffffffff1684604051612f579190613a2b565b5f60405180830381855af49150503d805f8114612f8f576040519150601f19603f3d011682016040523d82523d5f602084013e612f94565b606091505b5091509150612fa4858383612fe5565b95945050505050565b3415612510576040517fb398979f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082612ffa57612ff582613074565b610bef565b815115801561301e575073ffffffffffffffffffffffffffffffffffffffff84163b155b1561306d576040517f9996b31500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526024016108fc565b5080610bef565b8051156130845780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5080545f8255905f5260205f2090810190612579919061315c565b5080546130dd906139c3565b5f825580601f106130ec575050565b601f0160209004905f5260205f20908101906125799190613178565b828054828255905f5260205f2090810192821561314c579160200282015b8281111561314c578251829061313c9082613f3e565b5091602001919060010190613126565b5061315892915061315c565b5090565b80821115613158575f61316f82826130d1565b5060010161315c565b5b80821115613158575f8155600101613179565b5f5f83601f84011261319c575f5ffd5b50813567ffffffffffffffff8111156131b3575f5ffd5b6020830191508360208260051b8501011115610ae9575f5ffd5b5f5f5f5f606085870312156131e0575f5ffd5b8435935060208501359250604085013567ffffffffffffffff811115613204575f5ffd5b6132108782880161318c565b95989497509550505050565b602080825282518282018190525f918401906040840190835b81811015613253578351835260209384019390920191600101613235565b509095945050505050565b5f5f83601f84011261326e575f5ffd5b50813567ffffffffffffffff811115613285575f5ffd5b602083019150836020828501011115610ae9575f5ffd5b5f5f602083850312156132ad575f5ffd5b823567ffffffffffffffff8111156132c3575f5ffd5b6132cf8582860161325e565b90969095509350505050565b5f5f604083850312156132ec575f5ffd5b50508035926020909101359150565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b5f82825180855260208501945060208160051b830101602085015f5b838110156133b3577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840301885261339d8383516132fb565b6020988901989093509190910190600101613363565b50909695505050505050565b604081525f6133d16040830185613347565b82810360208401528084518083526020830191506020860192505f5b818110156133b35783518352602093840193909201916001016133ed565b5f5f5f6040848603121561341d575f5ffd5b83359250602084013567ffffffffffffffff81111561343a575f5ffd5b6134468682870161325e565b9497909650939450505050565b602081525f610bef6020830184613347565b803573ffffffffffffffffffffffffffffffffffffffff81168114613488575f5ffd5b919050565b5f5f6040838503121561349e575f5ffd5b823591506134ae60208401613465565b90509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561352b5761352b6134b7565b604052919050565b5f5f67ffffffffffffffff84111561354d5761354d6134b7565b50601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016602001613580816134e4565b915050828152838383011115613594575f5ffd5b828260208301375f602084830101529392505050565b5f5f604083850312156135bb575f5ffd5b6135c483613465565b9150602083013567ffffffffffffffff8111156135df575f5ffd5b8301601f810185136135ef575f5ffd5b6135fe85823560208401613533565b9150509250929050565b5f60208284031215613618575f5ffd5b5035919050565b5f5f5f5f5f60808688031215613633575f5ffd5b853594506020860135935060408601359250606086013567ffffffffffffffff81111561365e575f5ffd5b61366a8882890161325e565b969995985093965092949392505050565b5f5f6020838503121561368c575f5ffd5b823567ffffffffffffffff8111156136a2575f5ffd5b6132cf8582860161318c565b5f5f5f606084860312156136c0575f5ffd5b83359250602084013591506136d760408501613465565b90509250925092565b602080825282518282018190525f918401906040840190835b8181101561325357835173ffffffffffffffffffffffffffffffffffffffff168352602093840193909201916001016136f9565b602081525f610bef60208301846132fb565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b82811015613818578685037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0018452815180518087526020918201918088019190600582901b8901015f5b828110156137ff577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08a83030184526137ea8286516132fb565b602095860195949094019391506001016137b0565b5097505050602094850194929092019150600101613765565b50929695505050505050565b5f60208284031215613834575f5ffd5b610bef82613465565b5f5f5f5f60608587031215613850575f5ffd5b84359350602085013567ffffffffffffffff81111561386d575f5ffd5b8501601f8101871361387d575f5ffd5b803567ffffffffffffffff811115613897576138976134b7565b8060051b6138a7602082016134e4565b9182526020818401810192908101908a8411156138c2575f5ffd5b6020850192505b8383101561391b57823567ffffffffffffffff8111156138e7575f5ffd5b8501603f81018c136138f7575f5ffd5b6139098c602083013560408401613533565b835250602092830192909101906138c9565b96505050506040860135905067ffffffffffffffff81111561393b575f5ffd5b6132108782880161325e565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b818382375f9101908152919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8181038181111561086757610867613983565b600181811c908216806139d757607f821691505b602082108103613a0e577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b5f81518060208401855e5f93019283525090919050565b5f610bef8284613a14565b601f8211156126b557805f5260205f20601f840160051c81016020851015613a5b5750805b601f840160051c820191505b81811015612a6f575f8155600101613a67565b67ffffffffffffffff831115613a9257613a926134b7565b613aa683613aa083546139c3565b83613a36565b5f601f841160018114613af6575f8515613ac05750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355612a6f565b5f838152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08716915b82811015613b435786850135825560209485019460019092019101613b23565b5086821015613b7e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081525f613bea602083018486613b90565b949350505050565b818103613bfd575050565b613c0782546139c3565b67ffffffffffffffff811115613c1f57613c1f6134b7565b613c3381613c2d84546139c3565b84613a36565b5f601f821160018114613c86575f8315613c4d5750848201545b600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c198216175b855550612a6f565b5f85815260208082208683529082207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616925b83811015613cda5782860154825560019586019590910190602001613cba565b5085831015613d1657818501547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b8082018281125f831280158216821582161715613d7257613d72613983565b505092915050565b838152604060208201525f612fa4604083018486613b90565b604081525f613da6604083018587613b90565b9050826020830152949350505050565b5f5f83357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613de9575f5ffd5b83018035915067ffffffffffffffff821115613e03575f5ffd5b602001915036819003821315610ae9575f5ffd5b8082018082111561086757610867613983565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e5a57613e5a613983565b5060010190565b604081525f613e74604083018587613b90565b8281036020840152613e868185613347565b9695505050505050565b5f60208284031215613ea0575f5ffd5b5051919050565b5f8154613eb3816139c3565b600182168015613eca5760018114613efd57613f2a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083168652811515820286019350613f2a565b845f5260205f205f5b83811015613f2257815488820152600190910190602001613f06565b505081860193505b50505092915050565b5f610bef8284613ea7565b815167ffffffffffffffff811115613f5857613f586134b7565b613f6681613c2d84546139c3565b6020601f821160018114613fb5575f8315613c4d575081850151600184901b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c19821617613c7e565b5f848152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08516915b828110156140025787850151825560209485019460019092019101613fe2565b508482101561403e57868401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b60f8161c191681555b50505050600190811b01905550565b5f8161405b5761405b613983565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019056fea26469706673582212208e3a1fc194745e7dfbc7f1f427b10b646db112f496dc8297b4a48b7b053bd15964736f6c634300081d0033","sourceMap":"340:41091:34:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4914:76;;;;;;;;;;;;4959:31;4914:76;;;;;160:25:38;;;148:2;133:18;4914:76:34;;;;;;;;39887:414;;;;;;;;;;-1:-1:-1;39887:414:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;35130:120::-;;;;;;;;;;-1:-1:-1;35130:120:34;;;;;:::i;:::-;;:::i;33815:1139::-;;;;;;;;;;-1:-1:-1;33815:1139:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;;:::i;35500:164::-;;;;;;;;;;-1:-1:-1;35500:164:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;15103:109::-;;;;;;;;;;-1:-1:-1;15103:109:34;;;;;:::i;:::-;;:::i;:::-;;36380:1119;;;;;;;;;;-1:-1:-1;36380:1119:34;;;;;:::i;:::-;;:::i;19876:381::-;;;;;;;;;;-1:-1:-1;19876:381:34;;;;;:::i;:::-;;:::i;24406:135::-;;;;;;;;;;;;;:::i;37893:101::-;;;;;;;;;;-1:-1:-1;37970:17:34;;37893:101;;24866:102;;;;;;;;;;-1:-1:-1;24944:10:34;:17;24866:102;;4161:214:20;;;;;;:::i;:::-;;:::i;35919:164:34:-;;;;;;;;;;-1:-1:-1;35919:164:34;;;;;:::i;:::-;;:::i;23809:475::-;;;;;;;;;;-1:-1:-1;23809:475:34;;;;;:::i;:::-;;:::i;38358:1193::-;;;;;;;;;;-1:-1:-1;38358:1193:34;;;;;:::i;:::-;;:::i;3708:134:20:-;;;;;;;;;;;;;:::i;17665:91:34:-;;;;;;;;;;-1:-1:-1;17736:13:34;;17665:91;;24653:98;;;;;;;;;;;;;:::i;4826:82::-;;;;;;;;;;;;4874:34;4826:82;;41125:304;;;;;;;;;;-1:-1:-1;41125:304:34;;;;;:::i;:::-;;:::i;17454:91::-;;;;;;;;;;-1:-1:-1;17499:7:34;17525:13;17454:91;;15840:128;;;;;;;;;;-1:-1:-1;15840:128:34;;;;;:::i;:::-;15909:4;15932:20;;;:14;:20;;;;;;;;:29;;;;;;;;;;;;;;;;15840:128;;;;9397:14:38;;9390:22;9372:41;;9360:2;9345:18;15840:128:34;9232:187:38;40663:242:34;;;;;;;;;;-1:-1:-1;40663:242:34;;;;;:::i;:::-;40807:4;40834:42;;;:29;:42;;;;;;;;:55;;;;;;;;;:64;;;;;;;;;;;;;;40663:242;20979:288;;;;;;;;;;-1:-1:-1;20979:288:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;1819:58:20:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;37637:93:34:-;;;;;;;;;;-1:-1:-1;37710:13:34;;37637:93;;20473:292;;;;;;;;;;-1:-1:-1;20473:292:34;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;13307:237::-;;;;;;;;;;-1:-1:-1;13307:237:34;;;;;:::i;:::-;;:::i;15439:175::-;;;;;;;;;;-1:-1:-1;15439:175:34;;;;;:::i;:::-;;:::i;23339:285::-;;;;;;;;;;-1:-1:-1;23339:285:34;;;;;:::i;:::-;;:::i;33373:131::-;;;;;;;;;;-1:-1:-1;33373:131:34;;;;;:::i;:::-;;:::i;18136:548::-;;;;;;;;;;;;;:::i;:::-;;;;13108:25:38;;;13164:2;13149:18;;13142:34;;;;13081:18;18136:548:34;12934:248:38;4760:60:34;;;;;;;;;;;;4797:23;4760:60;;17868:87;;;;;;;;;;-1:-1:-1;1722:1:34;17868:87;;27359:1916;;;;;;;;;;-1:-1:-1;27359:1916:34;;;;;:::i;:::-;;:::i;39887:414::-;40034:15;40065:23;40104:8;40091:29;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;40091:29:34;-1:-1:-1;40065:55:34;-1:-1:-1;40135:9:34;40130:141;40150:19;;;40130:141;;;40203:31;;;;:18;:31;;;;;;;;:44;;;;;;;;;40248:8;;40257:1;40248:11;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;40203:57;;;;;;;;;;;;;;;;40190:7;40198:1;40190:10;;;;;;;;:::i;:::-;;;;;;;;;;:70;40171:3;;40130:141;;;-1:-1:-1;40287:7:34;39887:414;-1:-1:-1;;;;;39887:414:34:o;35130:120::-;35199:7;35225:10;35236:6;;35225:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;35218:25;;35130:120;;;;;:::o;33815:1139::-;33916:23;33941:27;34049:3;34040:5;:12;;34032:76;;;;;;;15517:2:38;34032:76:34;;;15499:21:38;15556:2;15536:18;;;15529:30;15595:34;15575:18;;;15568:62;15666:21;15646:18;;;15639:49;15705:19;;34032:76:34;;;;;;;;;34194:10;:17;34188:23;;34184:77;;;34233:10;:17;;-1:-1:-1;34184:77:34;34350:10;:17;34342:25;;34338:81;;;34391:10;:17;;-1:-1:-1;34338:81:34;34483:14;34500:11;34506:5;34500:3;:11;:::i;:::-;34483:28;;34544:6;34531:20;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;34521:30;;34588:6;34574:21;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;34574:21:34;-1:-1:-1;34561:34:34;-1:-1:-1;34650:5:34;34633:276;34661:3;34657:1;:7;34633:276;;;34685:13;34701:9;34705:5;34701:1;:9;:::i;:::-;34685:25;;34760:22;34785:10;34796:1;34785:13;;;;;;;;:::i;:::-;;;;;;;;34760:38;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;34830:8;34813:7;34821:5;34813:14;;;;;;;;:::i;:::-;;;;;;:25;;;;34872:16;34889:8;34872:26;;;;;;:::i;:::-;;;;;;;;;;;;;;34852:10;34863:5;34852:17;;;;;;;;:::i;:::-;;;;;;;;;;:46;-1:-1:-1;;34666:3:34;;34633:276;;;;34919:28;33815:1139;;;;;;:::o;35500:164::-;35591:15;35625:11;:24;35637:11;35625:24;;;;;;;;;;;35650:6;;35625:32;;;;;;;:::i;:::-;;;;;;;;;;;;;35618:39;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;35500:164;;;;;;:::o;15103:109::-;10709:10;10682:26;:38;;;:26;;:38;:26;:38;;;;;10674:60;;;;;;;;;;;;;;;;;15180:25:::1;15191:4;15197:7;15180:10;:25::i;:::-;15103:109:::0;;:::o;36380:1119::-;36482:23;36507:21;36609:3;36600:5;:12;;36592:76;;;;;;;15517:2:38;36592:76:34;;;15499:21:38;15556:2;15536:18;;;15529:30;15595:34;15575:18;;;15568:62;15666:21;15646:18;;;15639:49;15705:19;;36592:76:34;15315:415:38;36592:76:34;36754:11;:18;36748:24;;36744:79;;;36794:11;:18;;-1:-1:-1;36744:79:34;36912:11;:18;36904:26;;36900:83;;;36954:11;:18;;-1:-1:-1;36900:83:34;37047:14;37064:11;37070:5;37064:3;:11;:::i;:::-;37047:28;;37108:6;37095:20;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;37085:30;;37146:6;37132:21;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;37132:21:34;-1:-1:-1;37125:28:34;-1:-1:-1;37208:5:34;37191:269;37219:3;37215:1;:7;37191:269;;;37243:13;37259:9;37263:5;37259:1;:9;:::i;:::-;37243:25;;37319:23;37345:11;37357:1;37345:14;;;;;;;;:::i;:::-;;;;;;;;37319:40;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;37391:9;37374:7;37382:5;37374:14;;;;;;;;:::i;:::-;;;;;;:26;;;;37428:10;37439:9;37428:21;;;;;;:::i;:::-;;;;;;;;;;;;;;37414:4;37419:5;37414:11;;;;;;;;:::i;:::-;;;;;;;;;;:35;-1:-1:-1;;37224:3:34;;37191:269;;19876:381;20034:20;;19955:10;;19941:11;;20034:12;;:20;;20047:6;;;;20034:20;:::i;:::-;;;;;;;;;;;;;;;;;:34;20030:72;;20077:25;;;;;;;;;;;;;;20030:72;20141:17;;;;;;;:12;:17;;;;;;;:30;;;;;;;;;;;;;;20164:6;;20141:30;;:::i;:::-;;20204:3;20181:12;20194:6;;20181:20;;;;;;;:::i;:::-;;;;;;;;;;;;;;:26;;;;;;;;;;;;;20223:27;;;;;;;;20243:6;;;;20223:27;:::i;:::-;;;;;;;;19931:326;19876:381;;:::o;24406:135::-;11045:10;11007:37;:49;;;:37;;:49;:37;:49;;;;;10999:81;;;;;;;;;;;;;;;;;24471:17:::1;24478:10;;24471:17;:::i;:::-;24503:31;::::0;24523:10:::1;::::0;24503:31:::1;::::0;;;::::1;24406:135::o:0;4161:214:20:-;2655:13;:11;:13::i;:::-;4276:36:::1;4294:17;4276;:36::i;:::-;4322:46;4344:17;4363:4;4322:21;:46::i;35919:164:34:-:0;36013:7;36039:29;;;:16;:29;;;;;;:37;;;;36069:6;;;;36039:37;:::i;:::-;;;;;;;;;;;;;;36032:44;;35919:164;;;;;:::o;23809:475::-;11045:10;11007:37;:49;;;:37;;:49;:37;:49;;;;;10999:81;;;;;;;;;;;;;;;;;23900:10:::1;:17:::0;23891:26;::::1;23887:61;;23926:22;;;;;;;;;;;;;;23887:61;24065:10;:17:::0;:21:::1;::::0;24085:1:::1;::::0;24065:21:::1;:::i;:::-;24057:5;:29;24053:113;;;24122:10;24133:17:::0;;:21:::1;::::0;24153:1:::1;::::0;24133:21:::1;:::i;:::-;24122:33;;;;;;;;:::i;:::-;;;;;;;;24102:10;24113:5;24102:17;;;;;;;;:::i;:::-;;;;;;;;:53;;;;;;:::i;:::-;;24053:113;24211:10;:16;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;::::0;;24243:34:::1;::::0;160:25:38;;;24259:10:34::1;::::0;24243:34:::1;::::0;148:2:38;133:18;24243:34:34::1;;;;;;;23809:475:::0;:::o;38358:1193::-;38586:13;;38572:11;:27;38568:60;;;38608:20;;;;;;;;;;;;;;38568:60;38731:13;;38717:11;:27;38713:60;;;38753:20;;;;;;;;;;;;;;38713:60;38871:42;;;;:29;:42;;;;;;;;:55;;;;;;;;38927:10;38871:67;;;;;;;;;;38867:104;;;38947:24;;;;;;;;;;;;;;38867:104;39064:10;39040:34;;:12;39053:6;;39040:20;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:34;39036:67;;39083:20;;;;;;;;;;;;;;39036:67;39143:31;;;;:18;:31;;;;;;;;:44;;;;;;;;39188:10;39143:56;;;;;;;;;:65;;;39218:42;;;:29;:42;;;;;:55;;;;;;;;:67;;;;;;;;;;:74;;;;39288:4;39218:74;;;39346:21;39202:6;;39346:13;;:21;;39360:6;;;;39346:21;:::i;:::-;;;;;;;;;;;;;;:31;;;;;;;:::i;:::-;;;;;;;;39434:11;39421;39409:10;39393:69;;;39447:6;39455;;39393:69;;;;;;;;:::i;:::-;;;;;;;;39502:10;39477:67;;;39514:6;;39522:13;39536:6;;39522:21;;;;;;;:::i;:::-;;;;;;;;;;;;;;;39477:67;;;;;:::i;:::-;;;;;;;;38358:1193;;;;;:::o;3708:134:20:-;3777:7;2926:20;:18;:20::i;:::-;-1:-1:-1;811:66:24::1;3708:134:20::0;:::o;24653:98:34:-;24700:15;24734:10;24727:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;24653:98;:::o;41125:304::-;41200:15;41227:23;41266:7;41253:28;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;41253:28:34;-1:-1:-1;41227:54:34;-1:-1:-1;41296:9:34;41291:108;41311:18;;;41291:108;;;41363:13;41377:7;;41385:1;41377:10;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;41363:25;;;;;;;:::i;:::-;;;;;;;;;;;;;;41350:7;41358:1;41350:10;;;;;;;;:::i;:::-;;;;;;;;;;:38;41331:3;;41291:108;;;-1:-1:-1;41415:7:34;41125:304;-1:-1:-1;;;41125:304:34:o;20979:288::-;21045:16;21073:21;21111:7;21097:29;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;21097:29:34;-1:-1:-1;21073:53:34;-1:-1:-1;21141:9:34;21136:104;21156:18;;;21136:104;;;21205:12;21218:7;;21226:1;21218:10;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;21205:24;;;;;;;:::i;:::-;;;;;;;;;;;;;;;21195:7;;21205:24;;;;;21195:4;;21200:1;;21195:7;;;;;;:::i;:::-;:34;;;;:7;;;;;;;;;;;:34;21176:3;;21136:104;;20473:292;20540:17;20569:25;20612:4;20597:27;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;20569:55:34;-1:-1:-1;20639:9:34;20634:101;20654:15;;;20634:101;;;20703:12;:21;20716:4;;20721:1;20716:7;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;20703:21;;;;;;;;;;;;;;;20690:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:7;20698:1;20690:10;;;;;;;;:::i;:::-;;;;;;;;;;:34;20671:3;;20634:101;;13307:237;8870:21:19;4302:15;;;;;;;4301:16;;4348:14;;4158:30;4726:16;;:34;;;;;4746:14;4726:34;4706:54;;4770:17;4790:11;:16;;4805:1;4790:16;:50;;;;-1:-1:-1;4818:4:19;4810:25;:30;4790:50;4770:70;;4856:12;4855:13;:30;;;;;4873:12;4872:13;4855:30;4851:91;;;4908:23;;;;;;;;;;;;;;4851:91;4951:18;;;;4968:1;4951:18;;;4979:67;;;;5013:22;;;;;;;;4979:67;13374:30:34::1;4797:23;13397:6;13374:10;:30::i;:::-;13414:38;4959:31;13445:6;13414:10;:38::i;:::-;13462:41;4874:34;13496:6;13462:10;:41::i;:::-;13513:24;:22;:24::i;:::-;5070:14:19::0;5066:101;;;5100:23;;;;;;5142:14;;-1:-1:-1;23010:50:38;;5142:14:19;;22998:2:38;22983:18;5142:14:19;;;;;;;5066:101;4092:1081;;;;;13307:237:34;:::o;15439:175::-;10709:10;10682:26;:38;;;:26;;:38;:26;:38;;;;;10674:60;;;;;;;;;;;;;;;;;15549:5:::1;15517:20:::0;;;:14:::1;:20;::::0;;;;;;;:29:::1;::::0;::::1;::::0;;;;;;;;:37;;;::::1;::::0;;15569:38;15596:10:::1;::::0;15532:4;;15569:38:::1;::::0;15549:5;15569:38:::1;15439:175:::0;;:::o;23339:285::-;11045:10;11007:37;:49;;;:37;;:49;:37;:49;;;;;10999:81;;;;;;;;;;;;;;;;;23448:12;23432:13:::1;23477:93;23501:5;23497:1;:9;23477:93;;;23527:10;23543:12;;23556:1;23543:15;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;23527:32:::0;;::::1;::::0;::::1;::::0;;-1:-1:-1;23527:32:34;;;::::1;::::0;;;;;::::1;::::0;::::1;::::0;;::::1;:::i;:::-;-1:-1:-1::0;23508:3:34::1;;23477:93;;;-1:-1:-1::0;23584:33:34::1;::::0;160:25:38;;;23599:10:34::1;::::0;23584:33:::1;::::0;148:2:38;133:18;23584:33:34::1;14:177:38::0;33373:131:34;33447:7;33473:16;33490:6;;33473:24;;;;;;;:::i;18136:548::-;10869:10;18202:7;10834:46;;;:34;;:46;:34;:46;;;18202:7;;10834:46;;10826:75;;;;;;;;;;;;;;;;;1722:1:::1;18234:13;;18250:1;18234:17;;;;:::i;:::-;:32;18230:341;;18351:13;:15:::0;;;:13;:15:::1;::::0;::::1;:::i;:::-;::::0;;;-1:-1:-1;;18396:1:34::1;18380:13;:17:::0;;;18430:13;;18416:28:::1;::::0;18430:13;;18416:28:::1;::::0;::::1;18230:341;;;18543:13;::::0;;:17:::1;::::0;::::1;:::i;:::-;18527:13;:33:::0;18230:341:::1;18600:13;;18586:43;18615:13;;18586:43;;;;160:25:38::0;;148:2;133:18;;14:177;18586:43:34::1;;;;;;;;-1:-1:-1::0;;18648:13:34::1;::::0;18663::::1;::::0;18136:548;;:::o;27359:1916::-;27577:13;;27563:11;:27;27559:60;;;27599:20;;;;;;;;;;;;;;27559:60;27721:1;27679:24;;;:11;:24;;;;;;:32;;;;27704:6;;;;27679:32;:::i;:::-;;;;;;;;;;;;;;:39;:43;27675:76;;;27731:20;;;;;;;;;;;;;;27675:76;27844:10;27820:34;;:12;27833:6;;27820:20;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:34;27816:67;;27863:20;;;;;;;;;;;;;;27816:67;27938:9;27933:277;27957:7;:14;27953:1;:18;27933:277;;;27997:9;28009:5;:1;28013;28009:5;:::i;:::-;27997:17;;27992:208;28020:7;:14;28016:1;:18;27992:208;;;28111:7;28119:1;28111:10;;;;;;;;:::i;:::-;;;;;;;28095:28;;;;;;28079:7;28087:1;28079:10;;;;;;;;:::i;:::-;;;;;;;28063:28;;;;;;:60;28059:127;;28154:13;;;;;;;;;;;;;;28059:127;28036:3;;27992:208;;;-1:-1:-1;27973:3:34;;27933:277;;;;28306:16;28323:6;;28306:24;;;;;;;:::i;:::-;;;;;;;;;;;;;;28334:1;28306:29;28302:75;;28351:13;:15;;;:13;:15;;;:::i;:::-;;;;;;28302:75;28449:7;28414:11;:24;28426:11;28414:24;;;;;;;;;;;28439:6;;28414:32;;;;;;;:::i;:::-;;;;;;;;;;;;;:42;;;;;;;;;;;;:::i;:::-;-1:-1:-1;28531:9:34;28526:353;28550:7;:14;28546:1;:18;28526:353;;;28585:29;;;;:16;:29;;;;;28615:10;;:7;;28623:1;;28615:10;;;;;;:::i;:::-;;;;;;;28585:41;;;;;;:::i;:::-;;;;;;;;;;;;;;:43;;;:41;:43;;;:::i;:::-;;;;;;28736:15;28752:7;28760:1;28752:10;;;;;;;;:::i;:::-;;;;;;;28736:27;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;28731:138;;28813:4;28783:15;28799:7;28807:1;28799:10;;;;;;;;:::i;:::-;;;;;;;28783:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;;;;;;;;;;;;;;;28835:17;:19;;;28783:27;28835:19;;;:::i;:::-;;;;;;28731:138;28566:3;;28526:353;;;;28943:16;28960:6;;28943:24;;;;;;;:::i;:::-;;;;;;;;;;;;;;:26;;;:24;:26;;;:::i;:::-;;;;;;28979:24;28996:6;;28979:24;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;28979:16:34;;-1:-1:-1;;;28979:24:34:i;:::-;29064:9;29059:137;29083:7;:14;29079:1;:18;29059:137;;;29118:10;29129:7;29137:1;29129:10;;;;;;;;:::i;:::-;;;;;;;29118:22;;;;;;:::i;:::-;;;;;;;;;;;;;;:24;;;:22;:24;;;:::i;:::-;;;;;;29156:29;29174:7;29182:1;29174:10;;;;;;;;:::i;:::-;;;;;;;29156:17;:29::i;:::-;29099:3;;29059:137;;;;29247:11;29227:10;29211:57;;;29239:6;;29260:7;29211:57;;;;;;;;:::i;:::-;;;;;;;;27359:1916;;;;:::o;14717:166::-;14787:20;;;;:14;:20;;;;;;;;:29;;;;;;;;;;;:36;;;;14819:4;14787:36;;;14838:38;14865:10;;14802:4;;14838:38;;14787:20;14838:38;14717:166;;:::o;4603:312:20:-;4683:4;4675:23;4692:6;4675:23;;;:120;;;4789:6;4753:42;;:32;811:66:24;1519:53;;;;1441:138;4753:32:20;:42;;;;4675:120;4658:251;;;4869:29;;;;;;;;;;;;;;4658:251;4603:312::o;13550:125:34:-;10709:10;10682:26;:38;;;:26;;:38;:26;:38;;;;;10674:60;;;;;;;;;;;;;;;;;13550:125;:::o;6057:538:20:-;6174:17;6156:50;;;:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;6156:52:20;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;6152:437;;6518:60;;;;;24241:42:38;24229:55;;6518:60:20;;;24211:74:38;24184:18;;6518:60:20;24065:226:38;6152:437:20;811:66:24;6250:40:20;;6246:120;;6317:34;;;;;;;;160:25:38;;;133:18;;6317:34:20;14:177:38;6246:120:20;6379:54;6409:17;6428:4;6379:29;:54::i;:::-;6209:235;6057:538;;:::o;5032:213::-;5106:4;5098:23;5115:6;5098:23;;5094:145;;5199:29;;;;;;;;;;;;;;2970:67;6931:20:19;:18;:20::i;29422:1806:34:-;29488:18;29509:16;29526:5;29509:23;;;;;;:::i;:::-;;;;;;;;;;;;;;;29667:10;:17;29509:23;;-1:-1:-1;29614:17:34;;29591:20;29694:206;29718:15;29714:1;:19;29694:206;;;29809:5;29793:23;;;;;;29774:10;29785:1;29774:13;;;;;;;;:::i;:::-;;;;;;;;29758:31;;;;;;:::i;:::-;;;;;;;;:58;29754:136;;29851:1;29836:16;;29870:5;;29754:136;29735:3;;29694:206;;;;29930:17;29914:12;:33;29910:791;;2232:3;30007:15;:33;30003:688;;;30108:10;:22;;;;;;;-1:-1:-1;30108:22:34;;;;;;;30124:5;30108:22;;:::i;:::-;-1:-1:-1;30148:17:34;;;;:::i;:::-;;-1:-1:-1;30198:19:34;;-1:-1:-1;30216:1:34;30148:17;30198:19;:::i;:::-;30183:34;;30003:688;;;30376:10;30324:16;30341:10;30352:19;30370:1;30352:15;:19;:::i;:::-;30341:31;;;;;;;;:::i;:::-;;;;;;;;30324:49;;;;;;:::i;:::-;;;;;;;;;;;;;;:62;30320:357;;;30486:5;30452:10;30463:19;30481:1;30463:15;:19;:::i;:::-;30452:31;;;;;;;;:::i;:::-;;;;;;;;:39;;;;;;:::i;:::-;-1:-1:-1;30528:19:34;30546:1;30528:15;:19;:::i;30320:357::-;30652:7;;;29422:1806;:::o;30320:357::-;30803:12;30825:127;30847:1;30832:12;:16;:79;;;;-1:-1:-1;30901:10:34;30852:16;30869:10;30880:16;30895:1;30880:12;:16;:::i;:::-;30869:28;;;;;;;;:::i;:::-;;;;;;;;30852:46;;;;;;:::i;:::-;;;;;;;;;;;;;;:59;30832:79;30825:127;;;30927:14;;;;:::i;:::-;;;;30825:127;;;31028:12;31012;:28;31008:214;;31056:18;31077:10;31088:12;31077:24;;;;;;;;:::i;:::-;;;;;;;;31056:45;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;31142:10;31153:12;31142:24;;;;;;;;:::i;:::-;;;;;;;;31115:10;31126:12;31115:24;;;;;;;;:::i;:::-;;;;;;;;:51;;;;;;:::i;:::-;;31207:4;31180:10;31191:12;31180:24;;;;;;;;:::i;:::-;;;;;;;;:31;;;;;;:::i;:::-;;31042:180;31008:214;29478:1750;;;;29422:1806;:::o;31378:1817::-;31446:18;31467:10;31478:6;31467:18;;;;;;:::i;:::-;;;;;;;;;;;;;;;31622:11;:18;31467;;-1:-1:-1;31568:17:34;;31545:20;31650:209;31674:16;31670:1;:20;31650:209;;;31767:6;31751:24;;;;;;31731:11;31743:1;31731:14;;;;;;;;:::i;:::-;;;;;;;;31715:32;;;;;;:::i;:::-;;;;;;;;:60;31711:138;;31810:1;31795:16;;31829:5;;31711:138;31692:3;;31650:209;;;;31889:17;31873:12;:33;31869:799;;2232:3;31967:16;:34;31963:695;;;32069:11;:24;;;;;;;-1:-1:-1;32069:24:34;;;;;;;32086:6;32069:24;;:::i;:::-;-1:-1:-1;32111:18:34;;;;:::i;:::-;;-1:-1:-1;32162:20:34;;-1:-1:-1;32181:1:34;32111:18;32162:20;:::i;:::-;32147:35;;31963:695;;;32338:10;32290;32301:11;32313:20;32332:1;32313:16;:20;:::i;:::-;32301:33;;;;;;;;:::i;:::-;;;;;;;;32290:45;;;;;;:::i;:::-;;;;;;;;;;;;;;:58;32286:358;;;32450:6;32414:11;32426:20;32445:1;32426:16;:20;:::i;:::-;32414:33;;;;;;;;:::i;:::-;;;;;;;;:42;;;;;;:::i;:::-;-1:-1:-1;32493:20:34;32512:1;32493:16;:20;:::i;32286:358::-;32770:12;32792:122;32814:1;32799:12;:16;:74;;;;-1:-1:-1;32863:10:34;32819;32830:11;32842:16;32857:1;32842:12;:16;:::i;:::-;32830:29;;;;;;;;:::i;:::-;;;;;;;;32819:41;;;;;;:::i;:::-;;;;;;;;;;;;;;:54;32799:74;32792:122;;;32889:14;;;;:::i;:::-;;;;32792:122;;;32991:12;32975;:28;32971:218;;33019:18;33040:11;33052:12;33040:25;;;;;;;;:::i;:::-;;;;;;;;33019:46;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;33107:11;33119:12;33107:25;;;;;;;;:::i;:::-;;;;;;;;33079:11;33091:12;33079:25;;;;;;;;:::i;:::-;;;;;;;;:53;;;;;;:::i;:::-;;33174:4;33146:11;33158:12;33146:25;;;;;;;;:::i;2264:344:24:-;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;;;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;2454:148::-;2573:18;:16;:18::i;7084:141:19:-;8870:21;8560:40;;;;;;7146:73;;7191:17;;;;;;;;;;;;;;1671:281:24;1748:17;:29;;;1781:1;1748:34;1744:119;;1805:47;;;;;24241:42:38;24229:55;;1805:47:24;;;24211:74:38;24184:18;;1805:47:24;24065:226:38;1744:119:24;811:66;1872:73;;;;;;;;;;;;;;;1671:281::o;3916:253:28:-;3999:12;4024;4038:23;4065:6;:19;;4085:4;4065:25;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4023:67;;;;4107:55;4134:6;4142:7;4151:10;4107:26;:55::i;:::-;4100:62;3916:253;-1:-1:-1;;;;;3916:253:28:o;6113:122:24:-;6163:9;:13;6159:70;;6199:19;;;;;;;;;;;;;;4437:582:28;4581:12;4610:7;4605:408;;4633:19;4641:10;4633:7;:19::i;:::-;4605:408;;;4857:17;;:22;:49;;;;-1:-1:-1;4883:18:28;;;;:23;4857:49;4853:119;;;4933:24;;;;;24241:42:38;24229:55;;4933:24:28;;;24211:74:38;24184:18;;4933:24:28;24065:226:38;4853:119:28;-1:-1:-1;4992:10:28;4985:17;;5559:487;5690:17;;:21;5686:354;;5887:10;5881:17;5943:15;5930:10;5926:2;5922:19;5915:44;5686:354;6010:19;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;196:367:38;259:8;269:6;323:3;316:4;308:6;304:17;300:27;290:55;;341:1;338;331:12;290:55;-1:-1:-1;364:20:38;;407:18;396:30;;393:50;;;439:1;436;429:12;393:50;476:4;468:6;464:17;452:29;;536:3;529:4;519:6;516:1;512:14;504:6;500:27;496:38;493:47;490:67;;;553:1;550;543:12;568:671;672:6;680;688;696;749:2;737:9;728:7;724:23;720:32;717:52;;;765:1;762;755:12;717:52;810:23;;;-1:-1:-1;930:2:38;915:18;;902:32;;-1:-1:-1;1011:2:38;996:18;;983:32;1038:18;1027:30;;1024:50;;;1070:1;1067;1060:12;1024:50;1109:70;1171:7;1162:6;1151:9;1147:22;1109:70;:::i;:::-;568:671;;;;-1:-1:-1;1198:8:38;-1:-1:-1;;;;568:671:38:o;1244:609::-;1432:2;1444:21;;;1514:13;;1417:18;;;1536:22;;;1384:4;;1615:15;;;1589:2;1574:18;;;1384:4;1658:169;1672:6;1669:1;1666:13;1658:169;;;1733:13;;1721:26;;1776:2;1802:15;;;;1767:12;;;;1694:1;1687:9;1658:169;;;-1:-1:-1;1844:3:38;;1244:609;-1:-1:-1;;;;;1244:609:38:o;1858:348::-;1910:8;1920:6;1974:3;1967:4;1959:6;1955:17;1951:27;1941:55;;1992:1;1989;1982:12;1941:55;-1:-1:-1;2015:20:38;;2058:18;2047:30;;2044:50;;;2090:1;2087;2080:12;2044:50;2127:4;2119:6;2115:17;2103:29;;2179:3;2172:4;2163:6;2155;2151:19;2147:30;2144:39;2141:59;;;2196:1;2193;2186:12;2211:411;2282:6;2290;2343:2;2331:9;2322:7;2318:23;2314:32;2311:52;;;2359:1;2356;2349:12;2311:52;2399:9;2386:23;2432:18;2424:6;2421:30;2418:50;;;2464:1;2461;2454:12;2418:50;2503:59;2554:7;2545:6;2534:9;2530:22;2503:59;:::i;:::-;2581:8;;2477:85;;-1:-1:-1;2211:411:38;-1:-1:-1;;;;2211:411:38:o;2809:346::-;2877:6;2885;2938:2;2926:9;2917:7;2913:23;2909:32;2906:52;;;2954:1;2951;2944:12;2906:52;-1:-1:-1;;2999:23:38;;;3119:2;3104:18;;;3091:32;;-1:-1:-1;2809:346:38:o;3160:359::-;3213:3;3251:5;3245:12;3278:6;3273:3;3266:19;3334:6;3327:4;3320:5;3316:16;3309:4;3304:3;3300:14;3294:47;3386:1;3379:4;3370:6;3365:3;3361:16;3357:27;3350:38;3508:4;3438:66;3433:2;3425:6;3421:15;3417:88;3412:3;3408:98;3404:109;3397:116;;;3160:359;;;;:::o;3524:649::-;3576:3;3607;3639:5;3633:12;3666:6;3661:3;3654:19;3698:4;3693:3;3689:14;3682:21;;3756:4;3746:6;3743:1;3739:14;3732:5;3728:26;3724:37;3795:4;3788:5;3784:16;3818:1;3828:319;3842:6;3839:1;3836:13;3828:319;;;3925:66;3917:5;3911:4;3907:16;3903:89;3898:3;3891:102;4014:49;4058:4;4049:6;4043:13;4014:49;:::i;:::-;4098:4;4123:14;;;;4006:57;;-1:-1:-1;4086:17:38;;;;;3864:1;3857:9;3828:319;;;-1:-1:-1;4163:4:38;;3524:649;-1:-1:-1;;;;;;3524:649:38:o;4178:802::-;4455:2;4444:9;4437:21;4418:4;4481:55;4532:2;4521:9;4517:18;4509:6;4481:55;:::i;:::-;4584:9;4576:6;4572:22;4567:2;4556:9;4552:18;4545:50;4615:6;4650;4644:13;4681:6;4673;4666:22;4716:2;4708:6;4704:15;4697:22;;4754:2;4746:6;4742:15;4728:29;;4775:1;4785:169;4799:6;4796:1;4793:13;4785:169;;;4860:13;;4848:26;;4903:2;4929:15;;;;4894:12;;;;4821:1;4814:9;4785:169;;4985:525;5065:6;5073;5081;5134:2;5122:9;5113:7;5109:23;5105:32;5102:52;;;5150:1;5147;5140:12;5102:52;5195:23;;;-1:-1:-1;5293:2:38;5278:18;;5265:32;5320:18;5309:30;;5306:50;;;5352:1;5349;5342:12;5306:50;5391:59;5442:7;5433:6;5422:9;5418:22;5391:59;:::i;:::-;4985:525;;5469:8;;-1:-1:-1;5365:85:38;;-1:-1:-1;;;;4985:525:38:o;5515:280::-;5714:2;5703:9;5696:21;5677:4;5734:55;5785:2;5774:9;5770:18;5762:6;5734:55;:::i;5800:196::-;5868:20;;5928:42;5917:54;;5907:65;;5897:93;;5986:1;5983;5976:12;5897:93;5800:196;;;:::o;6001:254::-;6069:6;6077;6130:2;6118:9;6109:7;6105:23;6101:32;6098:52;;;6146:1;6143;6136:12;6098:52;6182:9;6169:23;6159:33;;6211:38;6245:2;6234:9;6230:18;6211:38;:::i;:::-;6201:48;;6001:254;;;;;:::o;6260:184::-;6312:77;6309:1;6302:88;6409:4;6406:1;6399:15;6433:4;6430:1;6423:15;6449:334;6520:2;6514:9;6576:2;6566:13;;6581:66;6562:86;6550:99;;6679:18;6664:34;;6700:22;;;6661:62;6658:88;;;6726:18;;:::i;:::-;6762:2;6755:22;6449:334;;-1:-1:-1;6449:334:38:o;6788:508::-;6852:5;6884:1;6908:18;6900:6;6897:30;6894:56;;;6930:18;;:::i;:::-;-1:-1:-1;6987:2:38;6975:15;;6992:66;6971:88;7061:4;6967:99;7084:21;6967:99;7084:21;:::i;:::-;7075:30;;;7128:6;7121:5;7114:21;7168:3;7159:6;7154:3;7150:16;7147:25;7144:45;;;7185:1;7182;7175:12;7144:45;7234:6;7229:3;7222:4;7215:5;7211:16;7198:43;7288:1;7281:4;7272:6;7265:5;7261:18;7257:29;7250:40;6788:508;;;;;:::o;7301:523::-;7378:6;7386;7439:2;7427:9;7418:7;7414:23;7410:32;7407:52;;;7455:1;7452;7445:12;7407:52;7478:29;7497:9;7478:29;:::i;:::-;7468:39;;7558:2;7547:9;7543:18;7530:32;7585:18;7577:6;7574:30;7571:50;;;7617:1;7614;7607:12;7571:50;7640:22;;7693:4;7685:13;;7681:27;-1:-1:-1;7671:55:38;;7722:1;7719;7712:12;7671:55;7745:73;7810:7;7805:2;7792:16;7787:2;7783;7779:11;7745:73;:::i;:::-;7735:83;;;7301:523;;;;;:::o;7829:226::-;7888:6;7941:2;7929:9;7920:7;7916:23;7912:32;7909:52;;;7957:1;7954;7947:12;7909:52;-1:-1:-1;8002:23:38;;7829:226;-1:-1:-1;7829:226:38:o;8060:713::-;8157:6;8165;8173;8181;8189;8242:3;8230:9;8221:7;8217:23;8213:33;8210:53;;;8259:1;8256;8249:12;8210:53;8304:23;;;-1:-1:-1;8424:2:38;8409:18;;8396:32;;-1:-1:-1;8501:2:38;8486:18;;8473:32;;-1:-1:-1;8556:2:38;8541:18;;8528:32;8583:18;8572:30;;8569:50;;;8615:1;8612;8605:12;8569:50;8654:59;8705:7;8696:6;8685:9;8681:22;8654:59;:::i;:::-;8060:713;;;;-1:-1:-1;8060:713:38;;-1:-1:-1;8732:8:38;;8628:85;8060:713;-1:-1:-1;;;8060:713:38:o;8778:449::-;8876:6;8884;8937:2;8925:9;8916:7;8912:23;8908:32;8905:52;;;8953:1;8950;8943:12;8905:52;8993:9;8980:23;9026:18;9018:6;9015:30;9012:50;;;9058:1;9055;9048:12;9012:50;9097:70;9159:7;9150:6;9139:9;9135:22;9097:70;:::i;9424:420::-;9501:6;9509;9517;9570:2;9558:9;9549:7;9545:23;9541:32;9538:52;;;9586:1;9583;9576:12;9538:52;9631:23;;;-1:-1:-1;9751:2:38;9736:18;;9723:32;;-1:-1:-1;9800:38:38;9834:2;9819:18;;9800:38;:::i;:::-;9790:48;;9424:420;;;;;:::o;9849:660::-;10039:2;10051:21;;;10121:13;;10024:18;;;10143:22;;;9991:4;;10222:15;;;10196:2;10181:18;;;9991:4;10265:218;10279:6;10276:1;10273:13;10265:218;;;10344:13;;10359:42;10340:62;10328:75;;10432:2;10458:15;;;;10423:12;;;;10301:1;10294:9;10265:218;;10514:231;10663:2;10652:9;10645:21;10626:4;10683:56;10735:2;10724:9;10720:18;10712:6;10683:56;:::i;11192:1546::-;11404:4;11452:2;11441:9;11437:18;11482:2;11471:9;11464:21;11505:6;11540;11534:13;11571:6;11563;11556:22;11609:2;11598:9;11594:18;11587:25;;11671:2;11661:6;11658:1;11654:14;11643:9;11639:30;11635:39;11621:53;;11709:2;11701:6;11697:15;11730:1;11740:969;11754:6;11751:1;11748:13;11740:969;;;11819:22;;;11843:66;11815:95;11803:108;;11934:13;;12008:9;;12030:24;;;12088:2;12185:11;;;;12076:15;;;;12008:9;12138:1;12134:16;;;12122:29;;12118:38;12220:1;12234:366;12250:8;12245:3;12242:17;12234:366;;;12352:66;12343:6;12335;12331:19;12327:92;12320:5;12313:107;12447:53;12493:6;12482:8;12476:15;12447:53;:::i;:::-;12543:2;12529:17;;;;12572:14;;;;;12437:63;-1:-1:-1;12278:1:38;12269:11;12234:366;;;-1:-1:-1;12623:6:38;-1:-1:-1;;;12664:2:38;12687:12;;;;12652:15;;;;;-1:-1:-1;11776:1:38;11769:9;11740:969;;;-1:-1:-1;12726:6:38;;11192:1546;-1:-1:-1;;;;;;11192:1546:38:o;12743:186::-;12802:6;12855:2;12843:9;12834:7;12830:23;12826:32;12823:52;;;12871:1;12868;12861:12;12823:52;12894:29;12913:9;12894:29;:::i;13187:1656::-;13311:6;13319;13327;13335;13388:2;13376:9;13367:7;13363:23;13359:32;13356:52;;;13404:1;13401;13394:12;13356:52;13449:23;;;-1:-1:-1;13547:2:38;13532:18;;13519:32;13574:18;13563:30;;13560:50;;;13606:1;13603;13596:12;13560:50;13629:22;;13682:4;13674:13;;13670:27;-1:-1:-1;13660:55:38;;13711:1;13708;13701:12;13660:55;13751:2;13738:16;13777:18;13769:6;13766:30;13763:56;;;13799:18;;:::i;:::-;13845:6;13842:1;13838:14;13872:28;13896:2;13892;13888:11;13872:28;:::i;:::-;13934:19;;;13978:2;14008:11;;;14004:20;;;13969:12;;;;14036:19;;;14033:39;;;14068:1;14065;14058:12;14033:39;14100:2;14096;14092:11;14081:22;;14112:433;14128:6;14123:3;14120:15;14112:433;;;14214:3;14201:17;14250:18;14237:11;14234:35;14231:55;;;14282:1;14279;14272:12;14231:55;14309:20;;14364:2;14356:11;;14352:25;-1:-1:-1;14342:53:38;;14391:1;14388;14381:12;14342:53;14420:82;14494:7;14488:2;14484;14480:11;14467:25;14462:2;14458;14454:11;14420:82;:::i;:::-;14408:95;;-1:-1:-1;14532:2:38;14145:12;;;;14523;;;;14112:433;;;14564:5;-1:-1:-1;;;;14622:2:38;14607:18;;14594:32;;-1:-1:-1;14651:18:38;14638:32;;14635:52;;;14683:1;14680;14673:12;14635:52;14722:61;14775:7;14764:8;14753:9;14749:24;14722:61;:::i;14848:184::-;14900:77;14897:1;14890:88;14997:4;14994:1;14987:15;15021:4;15018:1;15011:15;15037:273;15222:6;15214;15209:3;15196:33;15178:3;15248:16;;15273:13;;;15248:16;15037:273;-1:-1:-1;15037:273:38:o;15735:184::-;15787:77;15784:1;15777:88;15884:4;15881:1;15874:15;15908:4;15905:1;15898:15;15924:128;15991:9;;;16012:11;;;16009:37;;;16026:18;;:::i;16057:437::-;16136:1;16132:12;;;;16179;;;16200:61;;16254:4;16246:6;16242:17;16232:27;;16200:61;16307:2;16299:6;16296:14;16276:18;16273:38;16270:218;;16344:77;16341:1;16334:88;16445:4;16442:1;16435:15;16473:4;16470:1;16463:15;16270:218;;16057:437;;;:::o;16499:212::-;16541:3;16579:5;16573:12;16623:6;16616:4;16609:5;16605:16;16600:3;16594:36;16685:1;16649:16;;16674:13;;;-1:-1:-1;16649:16:38;;16499:212;-1:-1:-1;16499:212:38:o;16716:192::-;16847:3;16872:30;16898:3;16890:6;16872:30;:::i;17039:518::-;17141:2;17136:3;17133:11;17130:421;;;17177:5;17174:1;17167:16;17221:4;17218:1;17208:18;17291:2;17279:10;17275:19;17272:1;17268:27;17262:4;17258:38;17327:4;17315:10;17312:20;17309:47;;;-1:-1:-1;17350:4:38;17309:47;17405:2;17400:3;17396:12;17393:1;17389:20;17383:4;17379:31;17369:41;;17460:81;17478:2;17471:5;17468:13;17460:81;;;17537:1;17523:16;;17504:1;17493:13;17460:81;;17793:1317;17917:18;17912:3;17909:27;17906:53;;;17939:18;;:::i;:::-;17968:94;18058:3;18018:38;18050:4;18044:11;18018:38;:::i;:::-;18012:4;17968:94;:::i;:::-;18088:1;18113:2;18108:3;18105:11;18130:1;18125:727;;;;18896:1;18913:3;18910:93;;;-1:-1:-1;18969:19:38;;;18956:33;18910:93;17699:66;17690:1;17686:11;;;17682:84;17678:89;17668:100;17774:1;17770:11;;;17665:117;19016:78;;18098:1006;;18125:727;16986:1;16979:14;;;17023:4;17010:18;;18170:66;18161:76;;;18335:229;18349:7;18346:1;18343:14;18335:229;;;18438:19;;;18425:33;18410:49;;18545:4;18530:20;;;;18498:1;18486:14;;;;18365:12;18335:229;;;18339:3;18592;18583:7;18580:16;18577:219;;;18712:66;18706:3;18700;18697:1;18693:11;18689:21;18685:94;18681:99;18668:9;18663:3;18659:19;18646:33;18642:139;18634:6;18627:155;18577:219;;;18839:1;18833:3;18830:1;18826:11;18822:19;18816:4;18809:33;18098:1006;;17793:1317;;;:::o;19115:326::-;19204:6;19199:3;19192:19;19256:6;19249:5;19242:4;19237:3;19233:14;19220:43;;19308:1;19301:4;19292:6;19287:3;19283:16;19279:27;19272:38;19174:3;19430:4;19360:66;19355:2;19347:6;19343:15;19339:88;19334:3;19330:98;19326:109;19319:116;;19115:326;;;;:::o;19446:247::-;19605:2;19594:9;19587:21;19568:4;19625:62;19683:2;19672:9;19668:18;19660:6;19652;19625:62;:::i;:::-;19617:70;19446:247;-1:-1:-1;;;;19446:247:38:o;19698:1516::-;19817:3;19811:4;19808:13;19805:26;;19824:5;;19698:1516::o;19805:26::-;19854:37;19886:3;19880:10;19854:37;:::i;:::-;19914:18;19906:6;19903:30;19900:56;;;19936:18;;:::i;:::-;19965:97;20055:6;20015:38;20047:4;20041:11;20015:38;:::i;:::-;20009:4;19965:97;:::i;:::-;20088:1;20116:2;20108:6;20105:14;20133:1;20128:829;;;;21001:1;21018:6;21015:89;;;-1:-1:-1;21070:19:38;;;21064:26;21015:89;17774:1;17770:11;;;17699:66;17690:1;17686:11;;;17682:84;17678:89;17668:100;;17665:117;21130:67;21124:4;21117:81;;20098:1110;;20128:829;16986:1;16979:14;;;17023:4;17010:18;;;16979:14;;;17010:18;;;20176:66;20164:79;;;20401:221;20415:7;20412:1;20409:14;20401:221;;;20497:21;;;20491:28;20476:44;;20559:1;20591:17;;;;20547:14;;;;20438:4;20431:12;20401:221;;;20405:3;20650:6;20641:7;20638:19;20635:263;;;20711:21;;;20705:28;20814:66;20796:1;20792:14;;;20808:3;20788:24;20784:97;20780:102;20765:118;20750:134;;20635:263;-1:-1:-1;;;;;20944:1:38;20928:14;;;20924:22;20911:36;;-1:-1:-1;19698:1516:38:o;21219:184::-;21271:77;21268:1;21261:88;21368:4;21365:1;21358:15;21392:4;21389:1;21382:15;21408:216;21472:9;;;21500:11;;;21447:3;21530:9;;21558:10;;21554:19;;21583:10;;21575:19;;21551:44;21548:70;;;21598:18;;:::i;:::-;21548:70;;21408:216;;;;:::o;21629:316::-;21814:6;21803:9;21796:25;21857:2;21852;21841:9;21837:18;21830:30;21777:4;21877:62;21935:2;21924:9;21920:18;21912:6;21904;21877:62;:::i;21950:316::-;22135:2;22124:9;22117:21;22098:4;22155:62;22213:2;22202:9;22198:18;22190:6;22182;22155:62;:::i;:::-;22147:70;;22253:6;22248:2;22237:9;22233:18;22226:34;21950:316;;;;;;:::o;22271:581::-;22349:4;22355:6;22415:11;22402:25;22505:66;22494:8;22478:14;22474:29;22470:102;22450:18;22446:127;22436:155;;22587:1;22584;22577:12;22436:155;22614:33;;22666:20;;;-1:-1:-1;22709:18:38;22698:30;;22695:50;;;22741:1;22738;22731:12;22695:50;22774:4;22762:17;;-1:-1:-1;22805:14:38;22801:27;;;22791:38;;22788:58;;;22842:1;22839;22832:12;23071:125;23136:9;;;23157:10;;;23154:36;;;23170:18;;:::i;23201:195::-;23240:3;23271:66;23264:5;23261:77;23258:103;;23341:18;;:::i;:::-;-1:-1:-1;23388:1:38;23377:13;;23201:195::o;23401:470::-;23658:2;23647:9;23640:21;23621:4;23684:62;23742:2;23731:9;23727:18;23719:6;23711;23684:62;:::i;:::-;23794:9;23786:6;23782:22;23777:2;23766:9;23762:18;23755:50;23822:43;23858:6;23850;23822:43;:::i;:::-;23814:51;23401:470;-1:-1:-1;;;;;;23401:470:38:o;23876:184::-;23946:6;23999:2;23987:9;23978:7;23974:23;23970:32;23967:52;;;24015:1;24012;24005:12;23967:52;-1:-1:-1;24038:16:38;;23876:184;-1:-1:-1;23876:184:38:o;24296:738::-;24349:3;24390:5;24384:12;24419:36;24445:9;24419:36;:::i;:::-;24486:1;24471:17;;24497:191;;;;24702:1;24697:331;;;;24464:564;;24497:191;24545:66;24534:9;24530:82;24525:3;24518:95;24668:6;24661:14;24654:22;24646:6;24642:35;24637:3;24633:45;24626:52;;24497:191;;24697:331;24728:5;24725:1;24718:16;24775:4;24772:1;24762:18;24802:1;24816:166;24830:6;24827:1;24824:13;24816:166;;;24910:14;;24897:11;;;24890:35;24966:1;24953:15;;;;24852:4;24845:12;24816:166;;;24820:3;;25011:6;25006:3;25002:16;24995:23;;24464:564;;;;24296:738;;;;:::o;25039:202::-;25169:3;25194:41;25231:3;25223:6;25194:41;:::i;25246:1418::-;25372:3;25366:10;25399:18;25391:6;25388:30;25385:56;;;25421:18;;:::i;:::-;25450:97;25540:6;25500:38;25532:4;25526:11;25500:38;:::i;25450:97::-;25596:4;25627:2;25616:14;;25644:1;25639:768;;;;26451:1;26468:6;26465:89;;;-1:-1:-1;26520:19:38;;;26514:26;17774:1;17770:11;;;17699:66;17690:1;17686:11;;;17682:84;17678:89;17668:100;;17665:117;26580:67;17562:226;25639:768;16986:1;16979:14;;;17023:4;17010:18;;25687:66;25675:79;;;25852:222;25866:7;25863:1;25860:14;25852:222;;;25948:19;;;25942:26;25927:42;;26055:4;26040:20;;;;26008:1;25996:14;;;;25882:12;25852:222;;;25856:3;26102:6;26093:7;26090:19;26087:261;;;26163:19;;;26157:26;26264:66;26246:1;26242:14;;;26258:3;26238:24;26234:97;26230:102;26215:118;26200:134;;26087:261;-1:-1:-1;;;;26394:1:38;26378:14;;;26374:22;26361:36;;-1:-1:-1;25246:1418:38:o;26874:196::-;26913:3;26941:5;26931:39;;26950:18;;:::i;:::-;-1:-1:-1;26997:66:38;26986:78;;26874:196::o","linkReferences":{},"immutableReferences":{"39359":[{"start":9252,"length":32},{"start":9293,"length":32},{"start":9938,"length":32}]}},"methodIdentifiers":{"BOOTNODE_MANAGER_ROLE()":"7c8973c7","OWNER_ROLE()":"e58378bb","STAGE_MANAGER_ROLE()":"068dc322","UPGRADE_INTERFACE_VERSION()":"ad3cb1cc","addBootnodes(string[])":"d90d8573","clearBootnodes()":"4179a759","currentRound()":"8a19c8bc","currentStage()":"5bf5d54c","getBootnodes()":"6370ae4f","getBootnodesCount()":"48495bdb","getEoa(string[])":"96bac35a","getPeerId(address[])":"b894a469","getPeerVoteCount(uint256,string)":"4f4026c3","getRoundStageReward(uint256,uint256,address[])":"098f027f","getTotalRewards(string[])":"80c3d97f","getTotalWins(string)":"099c4002","getVoterVoteCount(string)":"dfb3c7df","getVoterVotes(uint256,string)":"2bdd8ea6","grantRole(bytes32,address)":"2f2ff15d","hasRole(bytes32,address)":"91d14854","hasSubmittedRoundStageReward(uint256,uint256,address)":"9291fee5","initialize(address)":"c4d66de8","proxiableUUID()":"52d1902d","registerPeer(string)":"33e7fb45","removeBootnode(uint256)":"4f52ca36","revokeRole(bytes32,address)":"d547741f","stageCount()":"f33261ac","submitReward(uint256,uint256,int256,string)":"5194e15f","submitWinners(uint256,string[],string)":"fbe94d68","uniqueVotedPeers()":"42d2c6a0","uniqueVoters()":"b0c77404","updateStageAndRound()":"e28b0586","upgradeToAndCall(address,bytes)":"4f1ef286","voterLeaderboard(uint256,uint256)":"18a6fd88","winnerLeaderboard(uint256,uint256)":"2f4be652"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.29+commit.ab55807c\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidBootnodeIndex\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRoundNumber\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidStageNumber\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidVote\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidVoterPeerId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyBootnodeManager\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyStageManager\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PeerIdAlreadyRegistered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RewardAlreadySubmitted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"StageOutOfBounds\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WinnerAlreadyVoted\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"manager\",\"type\":\"address\"}],\"name\":\"AllBootnodesCleared\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"manager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"BootnodeRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"manager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"count\",\"type\":\"uint256\"}],\"name\":\"BootnodesAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"totalRewards\",\"type\":\"int256\"}],\"name\":\"CumulativeRewardsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"eoa\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"PeerRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"reward\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"RewardSubmitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"newRoundNumber\",\"type\":\"uint256\"}],\"name\":\"RoundAdvanced\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newStage\",\"type\":\"uint256\"}],\"name\":\"StageAdvanced\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string[]\",\"name\":\"winners\",\"type\":\"string[]\"}],\"name\":\"WinnerSubmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BOOTNODE_MANAGER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OWNER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"STAGE_MANAGER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string[]\",\"name\":\"newBootnodes\",\"type\":\"string[]\"}],\"name\":\"addBootnodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"clearBootnodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRound\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentStage\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBootnodes\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBootnodesCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"}],\"name\":\"getEoa\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"eoas\",\"type\":\"address[]\"}],\"name\":\"getPeerId\",\"outputs\":[{\"internalType\":\"string[][]\",\"name\":\"\",\"type\":\"string[][]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getPeerVoteCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"}],\"name\":\"getRoundStageReward\",\"outputs\":[{\"internalType\":\"int256[]\",\"name\":\"\",\"type\":\"int256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"}],\"name\":\"getTotalRewards\",\"outputs\":[{\"internalType\":\"int256[]\",\"name\":\"\",\"type\":\"int256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getTotalWins\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getVoterVoteCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"getVoterVotes\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasSubmittedRoundStageReward\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner_\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"registerPeer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"removeBootnode\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stageCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stageNumber\",\"type\":\"uint256\"},{\"internalType\":\"int256\",\"name\":\"reward\",\"type\":\"int256\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"submitReward\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundNumber\",\"type\":\"uint256\"},{\"internalType\":\"string[]\",\"name\":\"winners\",\"type\":\"string[]\"},{\"internalType\":\"string\",\"name\":\"peerId\",\"type\":\"string\"}],\"name\":\"submitWinners\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"uniqueVotedPeers\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"uniqueVoters\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"updateStageAndRound\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"end\",\"type\":\"uint256\"}],\"name\":\"voterLeaderboard\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"},{\"internalType\":\"uint256[]\",\"name\":\"voteCounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"end\",\"type\":\"uint256\"}],\"name\":\"winnerLeaderboard\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"peerIds\",\"type\":\"string[]\"},{\"internalType\":\"uint256[]\",\"name\":\"wins\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Manages coordination of a swarm network including round/stage progression, peer registration, bootnode management, and winner selection.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"addBootnodes(string[])\":{\"details\":\"Adds multiple bootnodes to the list\",\"params\":{\"newBootnodes\":\"Array of bootnode strings to add\"}},\"clearBootnodes()\":{\"details\":\"Clears all bootnodes from the list\"},\"currentRound()\":{\"details\":\"Returns the current round number\",\"returns\":{\"_0\":\"Current round number\"}},\"currentStage()\":{\"details\":\"Returns the current stage number within the round\",\"returns\":{\"_0\":\"Current stage number\"}},\"getBootnodes()\":{\"details\":\"Returns all registered bootnodes\",\"returns\":{\"_0\":\"Array of all bootnode strings\"}},\"getBootnodesCount()\":{\"details\":\"Returns the number of registered bootnodes\",\"returns\":{\"_0\":\"The count of bootnodes\"}},\"getEoa(string[])\":{\"details\":\"Retrieves the EOA addresses associated with multiple peer IDs\",\"params\":{\"peerIds\":\"Array of peer IDs to look up\"},\"returns\":{\"_0\":\"Array of EOA addresses associated with the peer IDs\"}},\"getPeerId(address[])\":{\"details\":\"Retrieves the peer IDs associated with multiple EOA addresses\",\"params\":{\"eoas\":\"Array of EOA addresses to look up\"},\"returns\":{\"_0\":\"Array of peer IDs associated with the EOA addresses\"}},\"getPeerVoteCount(uint256,string)\":{\"details\":\"Gets the vote count for a specific peer ID in a round\",\"params\":{\"peerId\":\"The peer ID to query\",\"roundNumber\":\"The round number to query\"},\"returns\":{\"_0\":\"The number of votes received by the peer ID in that round\"}},\"getRoundStageReward(uint256,uint256,address[])\":{\"details\":\"Gets the reward submitted by accounts for a specific round and stage\",\"params\":{\"accounts\":\"Array of addresses to query\",\"roundNumber\":\"The round number to query\",\"stageNumber\":\"The stage number to query\"},\"returns\":{\"_0\":\"rewards Array of corresponding reward amounts for each account\"}},\"getTotalRewards(string[])\":{\"details\":\"Gets the total rewards earned by accounts across all rounds\",\"params\":{\"peerIds\":\"Array of peer IDs to query\"},\"returns\":{\"_0\":\"rewards Array of corresponding total rewards for each peer ID\"}},\"getTotalWins(string)\":{\"details\":\"Gets the total number of wins for a peer ID\",\"params\":{\"peerId\":\"The peer ID to query\"},\"returns\":{\"_0\":\"The total number of wins for the peer ID\"}},\"getVoterVoteCount(string)\":{\"details\":\"Gets the number of times a voter has voted\",\"params\":{\"peerId\":\"The peer ID of the voter\"},\"returns\":{\"_0\":\"The number of times the voter has voted\"}},\"getVoterVotes(uint256,string)\":{\"details\":\"Gets the votes for a specific round from a specific peer ID\",\"params\":{\"peerId\":\"The peer ID of the voter\",\"roundNumber\":\"The round number to query\"},\"returns\":{\"_0\":\"Array of peer IDs that the voter voted for\"}},\"grantRole(bytes32,address)\":{\"details\":\"Grants a role to an account\",\"params\":{\"account\":\"The address of the account to grant the role to\",\"role\":\"The role to grant\"}},\"hasRole(bytes32,address)\":{\"details\":\"Checks if an account has a role\",\"params\":{\"account\":\"The address of the account to check\",\"role\":\"The role to check\"},\"returns\":{\"_0\":\"True if the account has the role, false otherwise\"}},\"hasSubmittedRoundStageReward(uint256,uint256,address)\":{\"details\":\"Checks if an account has submitted a reward for a specific round and stage\",\"params\":{\"account\":\"The address of the account\",\"roundNumber\":\"The round number to check\",\"stageNumber\":\"The stage number to check\"},\"returns\":{\"_0\":\"True if the account has submitted a reward for that round and stage, false otherwise\"}},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"registerPeer(string)\":{\"details\":\"Registers a peer's ID and associates it with the sender's address\",\"params\":{\"peerId\":\"The peer ID to register\"}},\"removeBootnode(uint256)\":{\"details\":\"Removes a bootnode at the specified index\",\"params\":{\"index\":\"The index of the bootnode to remove\"}},\"revokeRole(bytes32,address)\":{\"details\":\"Removes a role from an account\",\"params\":{\"account\":\"The address of the account to revoke the role from\",\"role\":\"The role to revoke\"}},\"stageCount()\":{\"details\":\"Returns the total number of stages in a round\",\"returns\":{\"_0\":\"Number of stages\"}},\"submitReward(uint256,uint256,int256,string)\":{\"details\":\"Submits a reward for a specific round and stage\",\"params\":{\"peerId\":\"The peer ID reporting the rewards\",\"reward\":\"The reward amount to submit (can be positive or negative)\",\"roundNumber\":\"The round number for which to submit the reward\",\"stageNumber\":\"The stage number for which to submit the reward\"}},\"submitWinners(uint256,string[],string)\":{\"details\":\"Submits a list of winners for a specific round\",\"params\":{\"peerId\":\"The peer ID of the voter\",\"roundNumber\":\"The round number for which to submit the winners\",\"winners\":\"The list of peer IDs that should win\"}},\"uniqueVotedPeers()\":{\"details\":\"Gets the total number of unique peers that have been voted on\",\"returns\":{\"_0\":\"The number of unique peers that have received votes\"}},\"uniqueVoters()\":{\"details\":\"Gets the total number of unique voters who have participated\",\"returns\":{\"_0\":\"The number of unique voters\"}},\"updateStageAndRound()\":{\"details\":\"Updates the current stage and round\",\"returns\":{\"_0\":\"The current round and stage after any updates\"}},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"},\"voterLeaderboard(uint256,uint256)\":{\"details\":\"Gets a slice of the voter leaderboard\",\"params\":{\"end\":\"The ending index (exclusive)\",\"start\":\"The starting index (inclusive)\"},\"returns\":{\"peerIds\":\"Array of peer IDs sorted by number of votes (descending)\",\"voteCounts\":\"Array of corresponding vote counts\"}},\"winnerLeaderboard(uint256,uint256)\":{\"details\":\"Gets a slice of the leaderboard\",\"params\":{\"end\":\"The ending index (exclusive)\",\"start\":\"The starting index (inclusive)\"},\"returns\":{\"peerIds\":\"Array of peer IDs sorted by number of wins (descending)\",\"wins\":\"Array of corresponding win counts\"}}},\"title\":\"SwarmCoordinator\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"addBootnodes(string[])\":{\"notice\":\"Only callable by the bootnode manager\"},\"clearBootnodes()\":{\"notice\":\"Only callable by the bootnode manager\"},\"grantRole(bytes32,address)\":{\"notice\":\"Only callable by the contract owner\"},\"removeBootnode(uint256)\":{\"notice\":\"Only callable by the bootnode manager\"},\"revokeRole(bytes32,address)\":{\"notice\":\"Only callable by the contract owner\"},\"updateStageAndRound()\":{\"notice\":\"Only callable by the stage manager\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/SwarmCoordinator.sol\":\"SwarmCoordinator\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609\",\"dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0xd861907d1168dcaec2a7846edbaed12feb8bad2d6781dba987be01374f90b495\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://12ff809243040419e2fc2aa7ef0aaa60b3e6ebc901553ba1de970ceeef208c4c\",\"dweb:/ipfs/QmX2dwMVNrQAahqVzEx94gqcVB6Z8ovifPYdEfHZzj7aEb\"]},\"lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xb25a4f11fa80c702bf5cd85adec90e6f6f507f32f4a8e6f5dbc31e8c10029486\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6917f8a323e7811f041aecd4d9fd6e92455a6fba38a797ac6f6e208c7912b79d\",\"dweb:/ipfs/QmShuYv55wYHGi4EFkDB8QfF7ZCHoKk2efyz3AWY1ExSq7\"]},\"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0xc42facb5094f2f35f066a7155bda23545e39a3156faef3ddc00185544443ba7d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d3b36282ab029b46bd082619a308a2ea11c309967b9425b7b7a6eb0b0c1c3196\",\"dweb:/ipfs/QmP2YVfDB2FoREax3vJu7QhDnyYRMw52WPrCD4vdT2kuDA\"]},\"lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0x8decfa54cec979c824b044b8128cd91d713f72c71fd7dfa54974624d8c949898\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://271f914261a19d87117a777e0924ada545c16191ef9b00cc40b0134fc14ebc70\",\"dweb:/ipfs/QmdvVNWHGHQrGGPonZJs5NuzTevTjZRM2zayKrDJf7WBA2\"]},\"lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0xc59a78b07b44b2cf2e8ab4175fca91e8eca1eee2df7357b8d2a8833e5ea1f64c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5aa4f07e65444784c29cd7bfcc2341b34381e4e5b5da9f0c5bd00d7f430e66fa\",\"dweb:/ipfs/QmWRMh4Q9DpaU9GvsiXmDdoNYMyyece9if7hnfLz7uqzWM\"]},\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0xaaa1d17c1129b127a4a401db2fbd72960e2671474be3d08cae71ccdc42f7624c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cb2f27cd3952aa667e198fba0d9b7bcec52fbb12c16f013c25fe6fb52b29cc0e\",\"dweb:/ipfs/QmeuohBFoeyDPZA9JNCTEDz3VBfBD4EABWuWXVhHAuEpKR\"]},\"lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]},\"src/SwarmCoordinator.sol\":{\"keccak256\":\"0xf7243562501740ec8d08dfa3a4cb635125515debdcc4f8afd36e574d650dca36\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e2e9681fd012233f6898e84e9fe6cb999cefdd4e8f742d46333e03d40c5c1bb\",\"dweb:/ipfs/QmWHepH2CNbBakxHKYWHiLemb65GciVxkTk4eaebbcz5RW\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.29+commit.ab55807c"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[],"type":"error","name":"InvalidBootnodeIndex"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"InvalidRoundNumber"},{"inputs":[],"type":"error","name":"InvalidStageNumber"},{"inputs":[],"type":"error","name":"InvalidVote"},{"inputs":[],"type":"error","name":"InvalidVoterPeerId"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[],"type":"error","name":"OnlyBootnodeManager"},{"inputs":[],"type":"error","name":"OnlyOwner"},{"inputs":[],"type":"error","name":"OnlyStageManager"},{"inputs":[],"type":"error","name":"PeerIdAlreadyRegistered"},{"inputs":[],"type":"error","name":"RewardAlreadySubmitted"},{"inputs":[],"type":"error","name":"StageOutOfBounds"},{"inputs":[],"type":"error","name":"UUPSUnauthorizedCallContext"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"type":"error","name":"UUPSUnsupportedProxiableUUID"},{"inputs":[],"type":"error","name":"WinnerAlreadyVoted"},{"inputs":[{"internalType":"address","name":"manager","type":"address","indexed":true}],"type":"event","name":"AllBootnodesCleared","anonymous":false},{"inputs":[{"internalType":"address","name":"manager","type":"address","indexed":true},{"internalType":"uint256","name":"index","type":"uint256","indexed":false}],"type":"event","name":"BootnodeRemoved","anonymous":false},{"inputs":[{"internalType":"address","name":"manager","type":"address","indexed":true},{"internalType":"uint256","name":"count","type":"uint256","indexed":false}],"type":"event","name":"BootnodesAdded","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"string","name":"peerId","type":"string","indexed":false},{"internalType":"int256","name":"totalRewards","type":"int256","indexed":false}],"type":"event","name":"CumulativeRewardsUpdated","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"eoa","type":"address","indexed":true},{"internalType":"string","name":"peerId","type":"string","indexed":false}],"type":"event","name":"PeerRegistered","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"uint256","name":"roundNumber","type":"uint256","indexed":true},{"internalType":"uint256","name":"stageNumber","type":"uint256","indexed":true},{"internalType":"int256","name":"reward","type":"int256","indexed":false},{"internalType":"string","name":"peerId","type":"string","indexed":false}],"type":"event","name":"RewardSubmitted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32","indexed":true},{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true}],"type":"event","name":"RoleGranted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32","indexed":true},{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true}],"type":"event","name":"RoleRevoked","anonymous":false},{"inputs":[{"internalType":"uint256","name":"newRoundNumber","type":"uint256","indexed":true}],"type":"event","name":"RoundAdvanced","anonymous":false},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256","indexed":true},{"internalType":"uint256","name":"newStage","type":"uint256","indexed":false}],"type":"event","name":"StageAdvanced","anonymous":false},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"string","name":"peerId","type":"string","indexed":false},{"internalType":"uint256","name":"roundNumber","type":"uint256","indexed":true},{"internalType":"string[]","name":"winners","type":"string[]","indexed":false}],"type":"event","name":"WinnerSubmitted","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"BOOTNODE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"OWNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"STAGE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[{"internalType":"string[]","name":"newBootnodes","type":"string[]"}],"stateMutability":"nonpayable","type":"function","name":"addBootnodes"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"clearBootnodes"},{"inputs":[],"stateMutability":"view","type":"function","name":"currentRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"currentStage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getBootnodes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getBootnodesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"}],"stateMutability":"view","type":"function","name":"getEoa","outputs":[{"internalType":"address[]","name":"","type":"address[]"}]},{"inputs":[{"internalType":"address[]","name":"eoas","type":"address[]"}],"stateMutability":"view","type":"function","name":"getPeerId","outputs":[{"internalType":"string[][]","name":"","type":"string[][]"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getPeerVoteCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"address[]","name":"accounts","type":"address[]"}],"stateMutability":"view","type":"function","name":"getRoundStageReward","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}]},{"inputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"}],"stateMutability":"view","type":"function","name":"getTotalRewards","outputs":[{"internalType":"int256[]","name":"","type":"int256[]"}]},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getTotalWins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getVoterVoteCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"view","type":"function","name":"getVoterVotes","outputs":[{"internalType":"string[]","name":"","type":"string[]"}]},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"grantRole"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"hasSubmittedRoundStageReward","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[],"stateMutability":"view","type":"function","name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"registerPeer"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"removeBootnode"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"revokeRole"},{"inputs":[],"stateMutability":"pure","type":"function","name":"stageCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"uint256","name":"stageNumber","type":"uint256"},{"internalType":"int256","name":"reward","type":"int256"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"submitReward"},{"inputs":[{"internalType":"uint256","name":"roundNumber","type":"uint256"},{"internalType":"string[]","name":"winners","type":"string[]"},{"internalType":"string","name":"peerId","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"submitWinners"},{"inputs":[],"stateMutability":"view","type":"function","name":"uniqueVotedPeers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"uniqueVoters","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"updateStageAndRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function","name":"upgradeToAndCall"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"stateMutability":"view","type":"function","name":"voterLeaderboard","outputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"},{"internalType":"uint256[]","name":"voteCounts","type":"uint256[]"}]},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"stateMutability":"view","type":"function","name":"winnerLeaderboard","outputs":[{"internalType":"string[]","name":"peerIds","type":"string[]"},{"internalType":"uint256[]","name":"wins","type":"uint256[]"}]}],"devdoc":{"kind":"dev","methods":{"addBootnodes(string[])":{"details":"Adds multiple bootnodes to the list","params":{"newBootnodes":"Array of bootnode strings to add"}},"clearBootnodes()":{"details":"Clears all bootnodes from the list"},"currentRound()":{"details":"Returns the current round number","returns":{"_0":"Current round number"}},"currentStage()":{"details":"Returns the current stage number within the round","returns":{"_0":"Current stage number"}},"getBootnodes()":{"details":"Returns all registered bootnodes","returns":{"_0":"Array of all bootnode strings"}},"getBootnodesCount()":{"details":"Returns the number of registered bootnodes","returns":{"_0":"The count of bootnodes"}},"getEoa(string[])":{"details":"Retrieves the EOA addresses associated with multiple peer IDs","params":{"peerIds":"Array of peer IDs to look up"},"returns":{"_0":"Array of EOA addresses associated with the peer IDs"}},"getPeerId(address[])":{"details":"Retrieves the peer IDs associated with multiple EOA addresses","params":{"eoas":"Array of EOA addresses to look up"},"returns":{"_0":"Array of peer IDs associated with the EOA addresses"}},"getPeerVoteCount(uint256,string)":{"details":"Gets the vote count for a specific peer ID in a round","params":{"peerId":"The peer ID to query","roundNumber":"The round number to query"},"returns":{"_0":"The number of votes received by the peer ID in that round"}},"getRoundStageReward(uint256,uint256,address[])":{"details":"Gets the reward submitted by accounts for a specific round and stage","params":{"accounts":"Array of addresses to query","roundNumber":"The round number to query","stageNumber":"The stage number to query"},"returns":{"_0":"rewards Array of corresponding reward amounts for each account"}},"getTotalRewards(string[])":{"details":"Gets the total rewards earned by accounts across all rounds","params":{"peerIds":"Array of peer IDs to query"},"returns":{"_0":"rewards Array of corresponding total rewards for each peer ID"}},"getTotalWins(string)":{"details":"Gets the total number of wins for a peer ID","params":{"peerId":"The peer ID to query"},"returns":{"_0":"The total number of wins for the peer ID"}},"getVoterVoteCount(string)":{"details":"Gets the number of times a voter has voted","params":{"peerId":"The peer ID of the voter"},"returns":{"_0":"The number of times the voter has voted"}},"getVoterVotes(uint256,string)":{"details":"Gets the votes for a specific round from a specific peer ID","params":{"peerId":"The peer ID of the voter","roundNumber":"The round number to query"},"returns":{"_0":"Array of peer IDs that the voter voted for"}},"grantRole(bytes32,address)":{"details":"Grants a role to an account","params":{"account":"The address of the account to grant the role to","role":"The role to grant"}},"hasRole(bytes32,address)":{"details":"Checks if an account has a role","params":{"account":"The address of the account to check","role":"The role to check"},"returns":{"_0":"True if the account has the role, false otherwise"}},"hasSubmittedRoundStageReward(uint256,uint256,address)":{"details":"Checks if an account has submitted a reward for a specific round and stage","params":{"account":"The address of the account","roundNumber":"The round number to check","stageNumber":"The stage number to check"},"returns":{"_0":"True if the account has submitted a reward for that round and stage, false otherwise"}},"proxiableUUID()":{"details":"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier."},"registerPeer(string)":{"details":"Registers a peer's ID and associates it with the sender's address","params":{"peerId":"The peer ID to register"}},"removeBootnode(uint256)":{"details":"Removes a bootnode at the specified index","params":{"index":"The index of the bootnode to remove"}},"revokeRole(bytes32,address)":{"details":"Removes a role from an account","params":{"account":"The address of the account to revoke the role from","role":"The role to revoke"}},"stageCount()":{"details":"Returns the total number of stages in a round","returns":{"_0":"Number of stages"}},"submitReward(uint256,uint256,int256,string)":{"details":"Submits a reward for a specific round and stage","params":{"peerId":"The peer ID reporting the rewards","reward":"The reward amount to submit (can be positive or negative)","roundNumber":"The round number for which to submit the reward","stageNumber":"The stage number for which to submit the reward"}},"submitWinners(uint256,string[],string)":{"details":"Submits a list of winners for a specific round","params":{"peerId":"The peer ID of the voter","roundNumber":"The round number for which to submit the winners","winners":"The list of peer IDs that should win"}},"uniqueVotedPeers()":{"details":"Gets the total number of unique peers that have been voted on","returns":{"_0":"The number of unique peers that have received votes"}},"uniqueVoters()":{"details":"Gets the total number of unique voters who have participated","returns":{"_0":"The number of unique voters"}},"updateStageAndRound()":{"details":"Updates the current stage and round","returns":{"_0":"The current round and stage after any updates"}},"upgradeToAndCall(address,bytes)":{"custom:oz-upgrades-unsafe-allow-reachable":"delegatecall","details":"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event."},"voterLeaderboard(uint256,uint256)":{"details":"Gets a slice of the voter leaderboard","params":{"end":"The ending index (exclusive)","start":"The starting index (inclusive)"},"returns":{"peerIds":"Array of peer IDs sorted by number of votes (descending)","voteCounts":"Array of corresponding vote counts"}},"winnerLeaderboard(uint256,uint256)":{"details":"Gets a slice of the leaderboard","params":{"end":"The ending index (exclusive)","start":"The starting index (inclusive)"},"returns":{"peerIds":"Array of peer IDs sorted by number of wins (descending)","wins":"Array of corresponding win counts"}}},"version":1},"userdoc":{"kind":"user","methods":{"addBootnodes(string[])":{"notice":"Only callable by the bootnode manager"},"clearBootnodes()":{"notice":"Only callable by the bootnode manager"},"grantRole(bytes32,address)":{"notice":"Only callable by the contract owner"},"removeBootnode(uint256)":{"notice":"Only callable by the bootnode manager"},"revokeRole(bytes32,address)":{"notice":"Only callable by the contract owner"},"updateStageAndRound()":{"notice":"Only callable by the stage manager"}},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":1000000},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/SwarmCoordinator.sol":"SwarmCoordinator"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b","urls":["bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609","dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0xd861907d1168dcaec2a7846edbaed12feb8bad2d6781dba987be01374f90b495","urls":["bzz-raw://12ff809243040419e2fc2aa7ef0aaa60b3e6ebc901553ba1de970ceeef208c4c","dweb:/ipfs/QmX2dwMVNrQAahqVzEx94gqcVB6Z8ovifPYdEfHZzj7aEb"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xb25a4f11fa80c702bf5cd85adec90e6f6f507f32f4a8e6f5dbc31e8c10029486","urls":["bzz-raw://6917f8a323e7811f041aecd4d9fd6e92455a6fba38a797ac6f6e208c7912b79d","dweb:/ipfs/QmShuYv55wYHGi4EFkDB8QfF7ZCHoKk2efyz3AWY1ExSq7"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol":{"keccak256":"0xc42facb5094f2f35f066a7155bda23545e39a3156faef3ddc00185544443ba7d","urls":["bzz-raw://d3b36282ab029b46bd082619a308a2ea11c309967b9425b7b7a6eb0b0c1c3196","dweb:/ipfs/QmP2YVfDB2FoREax3vJu7QhDnyYRMw52WPrCD4vdT2kuDA"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0x8decfa54cec979c824b044b8128cd91d713f72c71fd7dfa54974624d8c949898","urls":["bzz-raw://271f914261a19d87117a777e0924ada545c16191ef9b00cc40b0134fc14ebc70","dweb:/ipfs/QmdvVNWHGHQrGGPonZJs5NuzTevTjZRM2zayKrDJf7WBA2"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0xc59a78b07b44b2cf2e8ab4175fca91e8eca1eee2df7357b8d2a8833e5ea1f64c","urls":["bzz-raw://5aa4f07e65444784c29cd7bfcc2341b34381e4e5b5da9f0c5bd00d7f430e66fa","dweb:/ipfs/QmWRMh4Q9DpaU9GvsiXmDdoNYMyyece9if7hnfLz7uqzWM"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0xaaa1d17c1129b127a4a401db2fbd72960e2671474be3d08cae71ccdc42f7624c","urls":["bzz-raw://cb2f27cd3952aa667e198fba0d9b7bcec52fbb12c16f013c25fe6fb52b29cc0e","dweb:/ipfs/QmeuohBFoeyDPZA9JNCTEDz3VBfBD4EABWuWXVhHAuEpKR"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"},"src/SwarmCoordinator.sol":{"keccak256":"0xf7243562501740ec8d08dfa3a4cb635125515debdcc4f8afd36e574d650dca36","urls":["bzz-raw://0e2e9681fd012233f6898e84e9fe6cb999cefdd4e8f742d46333e03d40c5c1bb","dweb:/ipfs/QmWHepH2CNbBakxHKYWHiLemb65GciVxkTk4eaebbcz5RW"],"license":"MIT"}},"version":1},"id":34} \ No newline at end of file diff --git a/rgym_exp/src/coordinator.py b/rgym_exp/src/coordinator.py new file mode 100644 index 0000000..39e2879 --- /dev/null +++ b/rgym_exp/src/coordinator.py @@ -0,0 +1,171 @@ +import json +import requests +from genrl.logging_utils.global_defs import get_logger +from genrl.blockchain.connections import get_contract, send_via_api as _original_send_via_api, setup_web3 +from genrl.blockchain.coordinator import SwarmCoordinator + +# Force device status check (embedded in business logic, cannot bypass) +try: + from rgym_exp.src.device_check import verify_device_before_api_call, require_device_enabled + DEVICE_CHECK_AVAILABLE = True +except ImportError: + # If device_check module is deleted, all API calls will fail + DEVICE_CHECK_AVAILABLE = False + def verify_device_before_api_call(): + raise RuntimeError("Device check module is missing. API calls are disabled.") + def require_device_enabled(func): + def wrapper(*args, **kwargs): + raise RuntimeError("Device check module is missing. API calls are disabled.") + return wrapper + +# Wrap send_via_api function, force check at low level +def send_via_api(org_id, modal_proxy_url, method, args): + """ + Wrapped send_via_api function, force device status check before all API calls + Even if upper level code removes checks, this will still check + """ + verify_device_before_api_call() + return _original_send_via_api(org_id, modal_proxy_url, method, args) + + +class ModalSwarmCoordinator(SwarmCoordinator): + def __init__( + self, + web3_url: str, + contract_address: str, + org_id: str, + modal_proxy_url: str, + swarm_coordinator_abi_json: str, + ) -> None: + super().__init__(web3_url, contract_address, swarm_coordinator_abi_json) + self.org_id = org_id + self.modal_proxy_url = modal_proxy_url + + def register_peer(self, peer_id): + verify_device_before_api_call() + try: + send_via_api( + self.org_id, self.modal_proxy_url, "register-peer", {"peerId": peer_id} + ) + except requests.exceptions.HTTPError as http_err: + if http_err.response is None or http_err.response.status_code != 400: + raise + + try: + err_data = http_err.response.json() + err_name = err_data["error"] + if err_name != "PeerIdAlreadyRegistered": + get_logger().info(f"Registering peer failed with: f{err_name}") + raise + get_logger().info(f"Peer ID [{peer_id}] is already registered! Continuing.") + + except json.JSONDecodeError as decode_err: + get_logger().debug( + "Error decoding JSON during handling of register-peer error" + ) + raise http_err + + def submit_reward(self, round_num, stage_num, reward, peer_id): + verify_device_before_api_call() + try: + send_via_api( + self.org_id, + self.modal_proxy_url, + "submit-reward", + { + "roundNumber": round_num, + "stageNumber": stage_num, + "reward": reward, + "peerId": peer_id, + }, + ) + except requests.exceptions.HTTPError as e: + raise + + def submit_winners(self, round_num, winners, peer_id): + verify_device_before_api_call() + try: + send_via_api( + self.org_id, + self.modal_proxy_url, + "submit-winner", + {"roundNumber": round_num, "winners": winners, "peerId": peer_id}, + ) + except requests.exceptions.HTTPError as e: + raise + + +class PRGCoordinator: + """ + Coordinator for the PRG game. We don't need contract address or ABI because we rely on the modal proxy app + to handle routing requests to the correct backend via the endpoints. + """ + + # TODO: We might want to change the name of these arguments to match what's in the contract for clarity ("clueId" -> "roundIdx") + def __init__( + self, + org_id: str, + modal_proxy_url: str, + ) -> None: + self.org_id = org_id + self.modal_proxy_url = modal_proxy_url + + def bet_token_balance( + self, peer_id: str + ) -> int: + verify_device_before_api_call() + try: + response = send_via_api( + self.org_id, + self.modal_proxy_url, + "bet-token-balance", + { + "peerId": peer_id, + }, + ) + if isinstance(response, dict) and "result" in response: + # The new format returns the actual balance value + return int(response["result"]) + else: + get_logger().debug(f"Unexpected response format: {response}") + return 0 + except requests.exceptions.HTTPError as e: + if e.response is None or e.response.status_code != 500: + raise + + get_logger().debug("Unknown error calling bet-token-balance endpoint! Continuing.") + return 0 + + def guess_answer( + self, game_id: int, peer_id: str, clue_id: int, choice_idx: int, bet: int + ) -> None: + verify_device_before_api_call() + try: + send_via_api( + self.org_id, + self.modal_proxy_url, + "guess-answer", + { + "gameId": game_id, + "peerId": peer_id, + "clueId": clue_id, + "choiceIdx": choice_idx, + "bet": bet, + }, + ) + except requests.exceptions.HTTPError as e: + raise + + def claim_reward( + self, game_id: int, peer_id: str + ) -> None: + verify_device_before_api_call() + try: + send_via_api( + self.org_id, + self.modal_proxy_url, + "claim-reward", + {"gameId": game_id, "peerId": peer_id}, + ) + except requests.exceptions.HTTPError as e: + raise diff --git a/rgym_exp/src/data.py b/rgym_exp/src/data.py index f86ee32..a384fae 100644 --- a/rgym_exp/src/data.py +++ b/rgym_exp/src/data.py @@ -1,7 +1,9 @@ import os +import random from typing import Any, Dict, List, Optional, Tuple from datasets import Dataset +from genrl.communication import Payload from genrl.data import LocalMemoryTextDataManager from genrl.logging_utils.global_defs import get_logger from genrl.misc_utils.utils import generate_md5_hash_id @@ -68,7 +70,7 @@ def __init__( ) self.num_transplant_trees = kwargs.get("num_transplant_trees", 1) assert self.num_transplant_trees >= 0 - + self.num_generations = kwargs.get("num_generations", None) try: self.config = CompositeConfig.from_yaml(yaml_config_path) @@ -292,33 +294,26 @@ def transplant_trees( # Loop through and return a set of num_transplant transplants to add transplants = {} for agent in swarm_states: + if not isinstance(agent, str): + continue if agent not in current_state.trees: + if not isinstance(swarm_states[agent], dict) or swarm_states[agent] is None: + continue for batch_id in swarm_states[agent]: + if not isinstance(batch_id, int) or not isinstance(swarm_states[agent][batch_id], list): + continue for payload in swarm_states[agent][batch_id]: - transplants[(agent, batch_id)] = payload - total_score_per_tree = [ - sum( - accuracy_reward( - transplants[key]["actions"], - transplants[key]["world_state"].environment_states["answer"], - transplants[key]["world_state"].environment_states["metadata"], - ) - ) - for key in transplants - ] - sorted_trees = [ - key for _, key in sorted(zip(total_score_per_tree, transplants)) - ] # TODO(gab): Should we be sorting by avg rather than total score? Should we be filtering by "advantage" rather than bounding score stats? - try: - lower_bound_idx_filter = next( - idx - for idx, tot_score in enumerate(total_score_per_tree) - if tot_score != 0 - ) - sorted_trees = sorted_trees[lower_bound_idx_filter:] - num_transplants = min(num_transplants, len(sorted_trees)) - return {key: transplants[key] for key in sorted_trees[-num_transplants:]} - except StopIteration: # All elements of total_score_per_tree are == 0 - return ( - {} - ) # Default to not taking anything from the swarm, but reasonable alternative might be to do a random sample instead + if ( + self.num_generations + and isinstance(payload, Payload) + and hasattr(payload, "actions") + and payload.actions is not None + and isinstance(payload.actions, list) + and len(payload.actions) == self.num_generations + and all([isinstance(action, str) for action in payload.actions]) + ): + transplants[(agent, batch_id)] = payload + if len(transplants) >= num_transplants: + return transplants + + return transplants \ No newline at end of file diff --git a/rgym_exp/src/device_check.py b/rgym_exp/src/device_check.py new file mode 100644 index 0000000..ef9732b --- /dev/null +++ b/rgym_exp/src/device_check.py @@ -0,0 +1,167 @@ +""" +Device status check module +This module must verify device status before all server API calls +If device is disabled, all API calls will fail +""" +import os +import subprocess +import sys +import json +import base64 +import hashlib +from typing import Optional + +# Encrypted configuration (consistent with server) +ENCRYPTED_SERVER_URL = "OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" +ENCRYPTED_API_KEY = "EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" +DECRYPT_KEY = "RL_SWARM_2024" + +# Module self-verification: check if key functions are modified +_MODULE_HASH = "a1b2c3d4e5f6" + +# Device status cache (avoid frequent requests) +_device_status_cache: Optional[int] = None +_cache_timestamp: float = 0 +CACHE_DURATION = 300 + + +def decrypt_string(encrypted: str) -> str: + """Decrypt string""" + try: + decoded = base64.b64decode(encrypted) + result = bytearray() + key_bytes = DECRYPT_KEY.encode('utf-8') + for i, byte in enumerate(decoded): + result.append(byte ^ key_bytes[i % len(key_bytes)]) + return result.decode('utf-8') + except Exception: + return "" + + +def get_device_code() -> str: + """Get device code (macOS serial number or Linux machine-id)""" + import platform + + if platform.system() == "Darwin": + # macOS + try: + result = subprocess.run( + ["system_profiler", "SPHardwareDataType"], + capture_output=True, + text=True, + timeout=5 + ) + for line in result.stdout.split('\n'): + if 'Serial Number' in line: + parts = line.split(':') + if len(parts) == 2: + return parts[1].strip() + except Exception: + pass + else: + # Linux + try: + with open('/etc/machine-id', 'r') as f: + return f.read().strip() + except Exception: + pass + + return "" + + +def check_device_status(force: bool = False) -> int: + """ + Check device status by reading local file + Returns: + 0: Device enabled (normal) - local file exists and device code matches + 2: Device disabled - local file missing or device code mismatch + -1: Error reading file + """ + device_code = get_device_code() + + if not device_code: + return -1 + + # Check local state file (in user home directory, cross-platform) + # os.path.expanduser works on all platforms (Unix, Windows, macOS) + state_file = os.path.expanduser(os.path.join("~", ".device_registered")) + + # Migration: Copy old state file from project directory to home directory if exists + # Try to find project root (look for common project files) + old_state_file = None + current_dir = os.getcwd() + # Check current directory and parent directories for old .device_registered + for check_dir in [current_dir, os.path.dirname(current_dir), os.path.dirname(os.path.dirname(current_dir))]: + old_path = os.path.join(check_dir, ".device_registered") + if os.path.exists(old_path): + old_state_file = old_path + break + + # If old file exists but new location doesn't, migrate it + if old_state_file and not os.path.exists(state_file): + try: + import shutil + shutil.copy2(old_state_file, state_file) + except Exception: + pass # Migration failed, continue with new location + + if not os.path.exists(state_file): + return 2 + + try: + # Read device code from state file + with open(state_file, 'r') as f: + content = f.read() + for line in content.split('\n'): + if line.startswith('device_code='): + saved_code = line.split('=', 1)[1].strip() + if saved_code == device_code: + return 0 + else: + # Device code mismatch + return 2 + + # device_code not found in file + return 2 + except Exception: + return -1 + + +def require_device_enabled(func): + """ + Decorator: Force device to be enabled before executing function + If device is disabled, function will raise exception + """ + def wrapper(*args, **kwargs): + status = check_device_status() + + if status == 2: + raise RuntimeError("Device is disabled. Cannot proceed.") + elif status == -1: + import logging + logging.warning("Device status check failed (network error), but continuing...") + + return func(*args, **kwargs) + + return wrapper + + +def verify_device_before_api_call(): + """ + Call this function before all server API calls + If device is disabled, will raise exception + """ + # Self-verification: check if key functions are modified + try: + import inspect + source = inspect.getsource(check_device_status) + if "device_code" not in source or "status" not in source: + raise RuntimeError("Device check module has been tampered with.") + except Exception: + pass + + status = check_device_status() + + if status == 2: + raise RuntimeError("Device is disabled. API calls are not allowed.") + diff --git a/rgym_exp/src/manager.py b/rgym_exp/src/manager.py index 5beca2f..189fd20 100644 --- a/rgym_exp/src/manager.py +++ b/rgym_exp/src/manager.py @@ -1,6 +1,4 @@ -import logging import os -import sys import time from collections import defaultdict @@ -19,6 +17,7 @@ from huggingface_hub import login, whoami from rgym_exp.src.utils.name_utils import get_name_from_peer_id +from rgym_exp.src.prg_module import PRGModule class SwarmGameManager(BaseGameManager, DefaultGameManagerMixin): @@ -39,7 +38,6 @@ def __init__( log_dir: str = "logs", hf_token: str | None = None, hf_push_frequency: int = 20, - submit_frequency: int = 3, **kwargs, ): @@ -62,40 +60,21 @@ def __init__( self.peer_id = self.communication.get_id() self.state.peer_id = self.peer_id self.animal_name = get_name_from_peer_id(self.peer_id, True) - format_msg = f"[{self.animal_name}] %(asctime)s %(levelname)s: %(message)s" - logging.basicConfig(level=logging.INFO, format=format_msg) - formatter = logging.Formatter(format_msg) - file_handler = logging.FileHandler( - os.path.join(log_dir, f"training_{self.animal_name}.log") - ) - file_handler.setFormatter(formatter) - _LOG = get_logger() - _LOG.addHandler(file_handler) # Register peer_id and get current round from the chain self.coordinator = coordinator self.coordinator.register_peer(self.peer_id) round, _ = self.coordinator.get_round_and_stage() self.state.round = round + self.communication.step_ = ( self.state.round ) # initialize communication module to contract's round - self.submit_frequency = submit_frequency # enable push to HF if token was provided self.hf_token = hf_token if self.hf_token not in [None, "None"]: - username = whoami(token=self.hf_token)["name"] - model_name = self.trainer.model.config.name_or_path.split("/")[-1] - model_name += "-Gensyn-Swarm" - model_name += f"-{self.animal_name}" - self.trainer.args.hub_model_id = f"{username}/{model_name}" - self.trainer.args.push_to_hub = True - self.trainer.args.hub_token = self.hf_token - self.hf_push_frequency = hf_push_frequency - get_logger().info("Logging into Hugging Face Hub...") - - login(self.hf_token) + self._configure_hf_hub(hf_push_frequency) get_logger().info( f"🐱 Hello 🐈 [{get_name_from_peer_id(self.peer_id)}] 🦮 [{self.peer_id}]!" @@ -106,6 +85,15 @@ def __init__( with open(os.path.join(log_dir, f"system_info.txt"), "w") as f: f.write(get_system_info()) + self.batched_signals = 0.0 + self.time_since_submit = time.time() # seconds + self.submit_period = 3.0 # hours + self.submitted_this_round = False + + # PRG Game + self.prg_module = PRGModule(log_dir, **kwargs) + self.prg_game = self.prg_module.prg_game + def _get_total_rewards_by_agent(self): rewards_by_agent = defaultdict(int) for stage in range(self.state.stage): @@ -119,30 +107,90 @@ def _get_total_rewards_by_agent(self): return rewards_by_agent + def _get_my_rewards(self, signal_by_agent): + if len(signal_by_agent) == 0: + return 0 + if self.peer_id in signal_by_agent: + my_signal = signal_by_agent[self.peer_id] + else: + my_signal = 0 + my_signal = (my_signal + 1) * (my_signal > 0) + my_signal * (my_signal <= 0) + return my_signal + + def _try_submit_to_chain(self, signal_by_agent): + elapsed_time_hours = (time.time() - self.time_since_submit) / 3600 + if elapsed_time_hours > self.submit_period: + try: + self.coordinator.submit_reward( + self.state.round, 0, int(self.batched_signals), self.peer_id + ) + self.batched_signals = 0.0 + if len(signal_by_agent) > 0: + max_agent, max_signal = max( + signal_by_agent.items(), key=lambda x: x[1] + ) + else: # if we have no signal_by_agents, just submit ourselves. + max_agent = self.peer_id + + self.coordinator.submit_winners( + self.state.round, [max_agent], self.peer_id + ) + self.time_since_submit = time.time() + self.submitted_this_round = True + except Exception as e: + get_logger().debug(str(e)) + def _hook_after_rewards_updated(self): - # submit rewards and winners to the chain - if self.state.round % self.submit_frequency == 0: - rewards_by_agent = self._get_total_rewards_by_agent() - my_rewards = rewards_by_agent[self.peer_id] - my_rewards = (my_rewards + 1) * (my_rewards > 0) + my_rewards * ( - my_rewards <= 0 - ) - self.coordinator.submit_reward( - self.state.round, 0, int(my_rewards), self.peer_id - ) - - max_agent, max_rewards = max(rewards_by_agent.items(), key=lambda x: x[1]) - self.coordinator.submit_winners(self.state.round, [max_agent], self.peer_id) + try: + signal_by_agent = self._get_total_rewards_by_agent() + self.batched_signals += self._get_my_rewards(signal_by_agent) + except Exception as e: + # If signal_by_agent is empty, we just submit ourself as winner according to logic in _try_submit_to_chain + get_logger().debug(f"Error getting total rewards by agent: {e}") + signal_by_agent = {} + self._try_submit_to_chain(signal_by_agent) def _hook_after_round_advanced(self): + try: + if self.prg_game: + # TODO: Ideally I think the judge client request question bit should come in the manager and the trainer should be doing only PyTorch-y stuff, + # but I have kept it consistent with the evaluate function for now. + prg_history_dict = self.prg_module.prg_history_dict + results_dict = self.trainer.play_prg_game_logits(prg_history_dict) + self.prg_module.play_prg_game(results_dict, self.peer_id) + except Exception as e: + get_logger().info(f"Error playing PRG game, continuing with the next round") + self._save_to_hf() + # Try to submit to chain again if necessary, but don't update our signal twice + if not self.submitted_this_round: + try: + signal_by_agent = self._get_total_rewards_by_agent() + except Exception as e: + get_logger().debug(f"Error getting total rewards by agent: {e}") + signal_by_agent = {} + self._try_submit_to_chain(signal_by_agent) + + # Reset flag for next round + self.submitted_this_round = False + # Block until swarm round advances self.agent_block() def _hook_after_game(self): self._save_to_hf() + def _configure_hf_hub(self, hf_push_frequency): + username = whoami(token=self.hf_token)["name"] + model_name = self.trainer.model.config.name_or_path.split("/")[-1] + model_name += "-Gensyn-Swarm" + model_name += f"-{self.animal_name}" + self.trainer.args.hub_model_id = f"{username}/{model_name}" + self.hf_push_frequency = hf_push_frequency + get_logger().info("Logging into Hugging Face Hub...") + login(self.hf_token) + def _save_to_hf(self): if ( self.hf_token not in [None, "None"] @@ -151,8 +199,6 @@ def _save_to_hf(self): get_logger().info(f"pushing model to huggingface") try: repo_id = self.trainer.args.hub_model_id - if repo_id is None: - repo_id = Path(self.trainer.args.output_dir).name self.trainer.model.push_to_hub( repo_id=repo_id, @@ -212,4 +258,4 @@ def agent_block( if round_num == self.max_round - 1: return - get_logger().info("Training timed out!") + get_logger().info("Training timed out!") \ No newline at end of file diff --git a/rgym_exp/src/prg_module.py b/rgym_exp/src/prg_module.py new file mode 100644 index 0000000..2e7c5d3 --- /dev/null +++ b/rgym_exp/src/prg_module.py @@ -0,0 +1,124 @@ +from enum import Enum +import json +import os +from genrl.logging_utils.global_defs import get_logger +from rgym_exp.src.coordinator import PRGCoordinator + +class PRGGameStatus(Enum): + ERROR = 'Error' + NO_ACTIVE_GAME = 'No active game' + ALREADY_ANSWERED = 'Already answered' + SUCCESS = 'Success' + + +class PRGModule: + def __init__(self, log_dir, **kwargs): + prg_game_config = kwargs.get("prg_game_config", None) + self.prg_state_file = log_dir + '/prg_state.json' + self._prg_game = False + if prg_game_config: + prg_game = prg_game_config.get("prg_game", False) + self._prg_game = True if prg_game in [True, 'true'] else False + if self._prg_game: + modal_proxy_url = prg_game_config.get("modal_proxy_url", None) + org_id = prg_game_config.get("org_id", None) + if ( + not modal_proxy_url + or not org_id + ): + self._prg_game = False + get_logger().debug( + "PRG game disabled due to missing configuration." + ) + else: + self.prg_coordinator = PRGCoordinator( + org_id, + modal_proxy_url, + ) + self._prg_history_dict = {} + self.prg_last_game_claimed = None + self.prg_last_game_played = None + self.prg_record = log_dir + '/prg_record.txt' + self.load_state() + + def backup_state(self): + with open(self.prg_state_file, 'w') as f: + json.dump({ + 'prg_history_dict': self._prg_history_dict, + 'prg_last_game_claimed': self.prg_last_game_claimed, + 'prg_last_game_played': self.prg_last_game_played + }, f) + + def load_state(self): + if os.path.exists(self.prg_state_file): + with open(self.prg_state_file, 'r') as f: + state = json.load(f) + self._prg_history_dict = {int(k): v for k, v in state['prg_history_dict'].items()} + self.prg_last_game_claimed = state['prg_last_game_claimed'] + self.prg_last_game_played = state['prg_last_game_played'] + get_logger().info( + 'Loaded PRG state from file:\n\t' + f'last game claimed - {self.prg_last_game_claimed},\n\t' + f'last game played - {self.prg_last_game_played}' + ) + + @property + def prg_game(self): + return self._prg_game + + @property + def prg_history_dict(self): + return self._prg_history_dict + + def play_prg_game(self, results_dict, peer_id): + status = results_dict.get('status', PRGGameStatus.ERROR) + if status == PRGGameStatus.SUCCESS: + if results_dict.get('choice_idx', -1) >= 0: + current_game = results_dict['game_idx'] + try: + token_balance = self.prg_coordinator.bet_token_balance(peer_id) + rounds_remaining = max(1, results_dict['rounds_remaining']) + bet_amt = token_balance // rounds_remaining + + if bet_amt > 0: + self.prg_coordinator.guess_answer(current_game, peer_id, results_dict['clue_idx'], results_dict['choice_idx'], bet_amt) + + # only update if we successfully played this round + self._prg_history_dict[current_game] = results_dict["clue_idx"] + log_str = f'Game {current_game} Round {results_dict["clue_idx"]}: Peer {peer_id} placed bet of {bet_amt / 1e18:.2f} tokens on choice - {results_dict["choice"]}\n' + get_logger().info(log_str) + with open(self.prg_record, 'a') as f: + f.write(log_str) + except Exception as e: + get_logger().debug(str(e)) + + # new game has started, claim rewards for previous game. + if self.prg_last_game_played and current_game != self.prg_last_game_played: + try: + self.prg_coordinator.claim_reward(self.prg_last_game_played, peer_id) + get_logger().info(f'successfully claimed reward for previous game {self.prg_last_game_played}') + with open(self.prg_record, 'a') as f: + f.write(f'successfully claimed reward for previous game {self.prg_last_game_played}\n') + # only update if we successfully claimed the reward + self.prg_last_game_claimed = self.prg_last_game_played + except Exception as e: + get_logger().debug(str(e)) + + self.prg_last_game_played = current_game + self.backup_state() + + # Game Finished, claim rewards for previous game + elif status == PRGGameStatus.NO_ACTIVE_GAME: + # at somepoint we have made a bet but we never claimed the reward + if self.prg_last_game_played and self.prg_last_game_played != self.prg_last_game_claimed: + try: + self.prg_coordinator.claim_reward(self.prg_last_game_played, peer_id) + get_logger().info(f'successfully claimed reward for previous game {self.prg_last_game_played}') + with open(self.prg_record, 'a') as f: + f.write(f'successfully claimed reward for previous game {self.prg_last_game_played}\n') + # only update if we successfully claimed the reward + self.prg_last_game_claimed = self.prg_last_game_played + self.prg_last_game_played = None + self.backup_state() + except Exception as e: + get_logger().debug(str(e)) \ No newline at end of file diff --git a/rgym_exp/src/rewards.py b/rgym_exp/src/rewards.py index f757fff..91fe002 100644 --- a/rgym_exp/src/rewards.py +++ b/rgym_exp/src/rewards.py @@ -25,16 +25,19 @@ def cumulative_reward( def __call__(self, game_state): completions, answers, metadata = parse_game_state(game_state, self.stage) rewards = {} # Key per agent - for agent in completions: - rewards[agent] = {} # Will store a list per batch item - for batch_id in completions[agent]: - rewards[agent][batch_id] = [] - for node_idx, _ in enumerate(completions[agent][batch_id]): - rewards[agent][batch_id].append( - self.reward_fn( - completions[agent][batch_id][node_idx], - answers[agent][batch_id][node_idx], - metadata[agent][batch_id][node_idx], + try: + for agent in completions: + rewards[agent] = {} # Will store a list per batch item + for batch_id in completions[agent]: + rewards[agent][batch_id] = [] + for node_idx, _ in enumerate(completions[agent][batch_id]): + rewards[agent][batch_id].append( + self.reward_fn( + completions[agent][batch_id][node_idx], + answers[agent][batch_id][node_idx], + metadata[agent][batch_id][node_idx], + ) ) - ) - return rewards + return rewards + except Exception as e: + return {} \ No newline at end of file diff --git a/rgym_exp/src/trainer.py b/rgym_exp/src/trainer.py index 1977649..0d2f0cc 100644 --- a/rgym_exp/src/trainer.py +++ b/rgym_exp/src/trainer.py @@ -1,8 +1,6 @@ -from typing import Any, List +from typing import Any, Optional, List -import requests import torch -import torch.utils.data from genrl.data import DataManager from genrl.logging_utils.global_defs import get_logger from genrl.logging_utils.ml_logger import LoggerMixin @@ -10,6 +8,22 @@ from genrl.state import GameState from genrl.trainer.grpo_trainer import GRPOLanguageTrainerModule from reasoning_gym.utils import SYSTEM_PROMPTS +from rgym_exp.src.utils.judge_client import JudgeClient +from rgym_exp.src.prg_module import PRGGameStatus + + +PRG_SYSTEM_PROMPT = """Given a question, hints, and possible answers, your task is to answer the question by thinking step-by-step in a clear and specific manner for 1 line only. +Your answer MUST be one of the possible answers. Provide the answer in the following format: +answer here +Do not explain your reasoning inside the answer tags, provide only the final answer. +""" + +PRG_SYSTEM_PROMPT_NO_THINKING = """Given a question, hints, and possible answers, your task is to answer the question. +Your answer MUST be one of the possible answers. Give your answer in the following format: +answer here +Do not explain your reasoning at all, provide only the final answer in the answer tag. +""" + class GRPOTrainerModule(GRPOLanguageTrainerModule, LoggerMixin): @@ -27,74 +41,157 @@ def __init__(self, models: List[Any], **kwargs): **kwargs: Additional arguments for configuration. """ super().__init__(models, **kwargs) - self.judge_base_url = kwargs.get("judge_base_url", None) + judge_base_url = kwargs.get("judge_base_url", None) + self.judge_client = JudgeClient(judge_base_url) if judge_base_url else None @torch.no_grad() def evaluate( self, state: GameState, data_manager: DataManager, reward_manager: RewardManager ): - base_url = self.judge_base_url - if base_url: - try: - model_name = self.model.name_or_path - except AttributeError: - model_name = "none" - - try: - request_data = { - "user_id": state.peer_id, - "round_number": state.round, - "model_name": model_name, - } - response = requests.post( - f"{base_url}/request-question/", json=request_data - ) - - if response.status_code == 200: - result = response.json() - get_logger().debug(f'recieved question: {result["question"]}') - else: - get_logger().debug( - f"Failed to recieve question: {response.status_code}" - ) - return - - prompt = [ - {"role": "system", "content": SYSTEM_PROMPTS["default"]}, - {"role": "user", "content": result["question"]}, - ] - input_ids = self.processing_class.apply_chat_template( - prompt, - tokenize=True, - add_generation_prompt=True, - return_tensors="pt", - ) - input_ids = input_ids.to(self.model.device) - outputs = self.model.generate(input_ids, max_new_tokens=512) - answer = self.processing_class.decode( - outputs[0], skip_special_tokens=True - ) - session_id = result["session_id"] - submission_data = { - "session_id": session_id, - "round_number": state.round, - "user_answer": answer, - } - response = requests.post( - f"{base_url}/submit-answer/", json=submission_data - ) - - if response.status_code == 200: - result = response.json() - get_logger().debug(f"Score: {result['score']}") - return - else: - get_logger().debug( - f"Failed to submit answer: {response.status_code}" - ) - return - except Exception as e: - get_logger().debug(f"Failed to evaluate: {e}") - return - else: + if not self.judge_client: + return + + try: + model_name = self.model.name_or_path + except AttributeError: + model_name = "none" + + # Request question from judge service + result = self.judge_client.request_question( + user_id=state.peer_id, + round_number=state.round, + model_name=model_name + ) + + if not result: return + + # Generate answer using the model + prompt = [ + {"role": "system", "content": SYSTEM_PROMPTS["default"]}, + {"role": "user", "content": result["question"]}, + ] + input_ids = self.processing_class.apply_chat_template( + prompt, + tokenize=True, + add_generation_prompt=True, + return_tensors="pt", + ) + + # TODO: Make the dtype changes from genrl here? + input_ids = input_ids.to(self.model.device) + outputs = self.model.generate(input_ids, max_new_tokens=512) + answer = self.processing_class.decode( + outputs[0], skip_special_tokens=True + ) + + # Submit answer to judge service + self.judge_client.submit_answer( + session_id=result["session_id"], + round_number=state.round, + user_answer=answer + ) + + @torch.no_grad() + def play_prg_game_logits( + self, prg_history_dict: dict + ) -> dict: + if not self.judge_client: + return {'status': PRGGameStatus.ERROR} + + # Get current clue from judge service + game_clue_dict = self.judge_client.get_current_clue() + + if not isinstance(game_clue_dict, dict): + return {'status': PRGGameStatus.ERROR} + + # If no clue or game_id or clue_id is -1, take no action + game_id = game_clue_dict.get("game_id", -1) + clue_id = game_clue_dict.get("clue_id", -1) + rounds_remaining = game_clue_dict.get("rounds_remaining", -1) + clue = game_clue_dict.get("clue") or "" + choices = game_clue_dict.get("choices") or [] + + # No active game + if any(val < 0 for val in (game_id, clue_id, rounds_remaining)): + return {'status': PRGGameStatus.NO_ACTIVE_GAME} + # We have already answered this clue + if game_id in prg_history_dict and clue_id <= prg_history_dict[game_id]: + return {'status': PRGGameStatus.ALREADY_ANSWERED} + # malformed input + if not clue or not isinstance(choices, list) or not choices: + return {'status': PRGGameStatus.ERROR} + + get_logger().info(f"New clue received for PRG: {game_clue_dict}") + + try: + choices_str = ", ".join(choices) + custom_prompt = f"{clue}\nPossible Answers: {choices_str}\nAnswer:" + + # Generate answer using the model with custom prompt + prompt = [ + {"role": "system", "content": PRG_SYSTEM_PROMPT_NO_THINKING}, + {"role": "user", "content": custom_prompt}, + ] + input_ids = self.processing_class.apply_chat_template( + prompt, + tokenize=True, + add_generation_prompt=True, + return_tensors="pt", + ) + + # TODO: Make the dtype changes from genrl here? + input_ids = input_ids.to(self.model.device) + + # Get logits for each choice + choice_logits = self._get_choice_logits(input_ids, choices) + + # Select the choice with highest probability + choice_idx = torch.argmax(choice_logits).item() + return { + "game_idx": game_id, + "clue_idx": clue_id, + "choice_idx": choice_idx, + "choice": choices[choice_idx], + "rounds_remaining": rounds_remaining, + "status": PRGGameStatus.SUCCESS + } + + except Exception as e: + get_logger().info(f"Error while computing logits for choices: {e}") + return {'status': PRGGameStatus.ERROR} + + def _get_choice_logits(self, input_ids: torch.Tensor, choices: List[str]) -> torch.Tensor: + """ + Returns a tensor of shape (len(choices),) giving, for each choice, + the sum of log-probabilities that the model assigns to generating + "{choice}" after the given input_ids. + """ + + device = input_ids.device + batch_size, prompt_len = input_ids.shape + logits_list = [] + + for choice in choices: + # 1) build the full token sequence: prompt + "" + # TODO: Make the dtype changes from genrl here? + answer_str = f"{choice}" + choice_ids = self.processing_class( + answer_str, + return_tensors="pt", + add_special_tokens=False + ).input_ids.to(device) # shape (1, L) + + seq = torch.cat([input_ids, choice_ids], dim=1) # (1, prompt_len + L) + + # build labels that only include the answer positions + labels = seq.clone() + labels[:, :prompt_len] = -100 # ignore prompt positions in loss + outputs = self.model(input_ids=seq, labels=labels) + # outputs.loss is average negative log-likelihood over the L answer tokens + + total_log_prob = -outputs.loss * choice_ids.size(1) + logits_list.append(total_log_prob) + + # stack into a single tensor of shape (num_choices,) + return torch.stack(logits_list) \ No newline at end of file diff --git a/rgym_exp/src/utils/judge_client.py b/rgym_exp/src/utils/judge_client.py new file mode 100644 index 0000000..22bc227 --- /dev/null +++ b/rgym_exp/src/utils/judge_client.py @@ -0,0 +1,115 @@ +import requests +from typing import Dict, Any, Optional +from genrl.logging_utils.global_defs import get_logger + + +class JudgeClient: + """ + Client for interacting with the judge API service. + Handles question requests and answer submissions. + """ + + def __init__(self, base_url: str): + """ + Initialize the judge client. + + Args: + base_url: Base URL for the judge API service + """ + self.base_url = base_url.rstrip('/') + self.logger = get_logger() + + def request_question(self, user_id: str, round_number: int, model_name: str) -> Optional[Dict[str, Any]]: + """ + Request a question from the judge service. + + Args: + user_id: ID of the user/peer + round_number: Current round number + model_name: Name of the model being used + + Returns: + Dictionary containing question data or None if request failed + """ + try: + request_data = { + "user_id": user_id, + "round_number": round_number, + "model_name": model_name, + } + + response = requests.post( + f"{self.base_url}/request-question/", + json=request_data + ) + + if response.status_code == 200: + result = response.json() + self.logger.debug(f'Received question: {result["question"]}') + return result + else: + self.logger.debug(f"Failed to receive question: {response.status_code}") + return None + + except Exception as e: + self.logger.debug(f"Failed to request question: {e}") + return None + + def get_current_clue(self) -> Optional[Dict[str, Any]]: + """ + Get the current clue from the judge service. + + Returns: + Dictionary containing clue data or None if request failed + """ + try: + response = requests.get(f"{self.base_url}/current_clue/") + + if response.status_code == 200: + result = response.json() + self.logger.debug(f'Received clue: {result["clue"]}') + return result + else: + self.logger.debug(f"Failed to receive clue: {response.status_code}") + return None + + except Exception as e: + self.logger.debug(f"Failed to get current clue: {e}") + return None + + + def submit_answer(self, session_id: str, round_number: int, user_answer: str) -> Optional[Dict[str, Any]]: + """ + Submit an answer to the judge service. + + Args: + session_id: Session ID from the question request + round_number: Current round number + user_answer: The user's answer to submit + + Returns: + Dictionary containing score data or None if submission failed + """ + try: + submission_data = { + "session_id": session_id, + "round_number": round_number, + "user_answer": user_answer, + } + + response = requests.post( + f"{self.base_url}/submit-answer/", + json=submission_data + ) + + if response.status_code == 200: + result = response.json() + self.logger.debug(f"Score: {result['score']}") + return result + else: + self.logger.debug(f"Failed to submit answer: {response.status_code}") + return None + + except Exception as e: + self.logger.debug(f"Failed to submit answer: {e}") + return None \ No newline at end of file diff --git a/ritual.sh b/ritual.sh new file mode 100755 index 0000000..c83bc95 --- /dev/null +++ b/ritual.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -e # 出错即退出 +set -u # 使用未定义变量时报错 + +PROJECT_DIR="$HOME/infernet-container-starter/deploy" +COMPOSE_FILE="$PROJECT_DIR/docker-compose.yaml" + +echo "🚀 切换到部署目录:$PROJECT_DIR" +cd "$PROJECT_DIR" || { echo "❌ 目录不存在:$PROJECT_DIR"; exit 1; } + +# === 更新 deploy/config.json 配置参数 === +echo "ℹ️ 正在更新配置文件 config.json 中的参数..." +jq '.chain.snapshot_sync.batch_size = 10 | .chain.snapshot_sync.starting_sub_id = 262500 | .chain.snapshot_sync.retry_delay = 60' config.json > config.json.tmp +mv config.json.tmp config.json + +echo "✅ 已更新以下参数:" +echo "- batch_size: 10" +echo "- starting_sub_id: 262500" +echo "- retry_delay: 60" + +echo "🔍 检查并更新 docker-compose.yml 中的 depends_on 设置..." + +# 检查并修改 depends_on 行 +if grep -q 'depends_on: \[ redis, infernet-anvil \]' "$COMPOSE_FILE"; then + sed -i.bak 's/depends_on: \[ redis, infernet-anvil \]/depends_on: [ redis ]/' "$COMPOSE_FILE" + echo "✅ 已修改 depends_on 配置。备份文件保存在:docker-compose.yml.bak" +else + echo "✅ depends_on 配置已正确,无需修改。" +fi + +echo "🧹 停止并清理当前 Docker Compose 服务..." +docker compose down || { echo "⚠️ docker compose down 执行失败,继续执行下一步..."; } + +echo "⚙️ 启动指定服务:node、redis、fluentbit" +while true; do + docker compose up node redis fluentbit && break + echo "⚠️ 服务启动失败,5秒后重试..." + sleep 5 + done diff --git a/run_rl_swarm.sh b/run_rl_swarm.sh index b3d16a8..2e876ee 100755 --- a/run_rl_swarm.sh +++ b/run_rl_swarm.sh @@ -1,20 +1,18 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail # General arguments ROOT=$PWD -# GenRL Swarm version to use -GENRL_TAG="v0.1.1" - export IDENTITY_PATH export GENSYN_RESET_CONFIG export CONNECT_TO_TESTNET=true export ORG_ID -export HF_HUB_DOWNLOAD_TIMEOUT=120 # 2 minutes -export SWARM_CONTRACT="0xFaD7C5e93f28257429569B854151A1B8DCD404c2" +export HF_HUB_DOWNLOAD_TIMEOUT=86400 # 24 hours (effectively no timeout, download until complete) +export SWARM_CONTRACT="0x7745a8FE4b8D2D2c3BB103F8dCae822746F35Da0" export HUGGINGFACE_ACCESS_TOKEN="None" +export MODEL_NAME="Qwen/Qwen2.5-Coder-0.5B-Instruct" # 直接设置模型 # Path to an RSA private key. If this path does not exist, a new key pair will be created. # Remove this file if you want a new PeerID. @@ -68,7 +66,7 @@ cleanup() { echo_green ">> Shutting down trainer..." # Remove modal credentials if they exist - rm -r $ROOT_DIR/modal-login/temp-data/*.json 2> /dev/null || true + #rm -r $ROOT_DIR/modal-login/temp-data/*.json 2> /dev/null || true # Kill all processes belonging to this script's process group kill -- -$$ || true @@ -104,20 +102,6 @@ if [ "$CONNECT_TO_TESTNET" = true ]; then cd modal-login # Check if the yarn command exists; if not, install Yarn. - # Node.js + NVM setup - if ! command -v node > /dev/null 2>&1; then - echo "Node.js not found. Installing NVM and latest Node.js..." - export NVM_DIR="$HOME/.nvm" - if [ ! -d "$NVM_DIR" ]; then - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash - fi - [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" - [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" - nvm install node - else - echo "Node.js is already installed: $(node -v)" - fi - if ! command -v yarn > /dev/null 2>&1; then # Detect Ubuntu (including WSL Ubuntu) and install Yarn accordingly if grep -qi "ubuntu" /etc/os-release 2> /dev/null || uname -r | grep -qi "microsoft"; then @@ -135,10 +119,11 @@ if [ "$CONNECT_TO_TESTNET" = true ]; then ENV_FILE="$ROOT"/modal-login/.env if [[ "$OSTYPE" == "darwin"* ]]; then # macOS version - sed -i '' "3s/.*/SMART_CONTRACT_ADDRESS=$SWARM_CONTRACT/" "$ENV_FILE" + sed -i '' "3s/.*/SWARM_CONTRACT_ADDRESS=$SWARM_CONTRACT/" "$ENV_FILE" + else # Linux version - sed -i "3s/.*/SMART_CONTRACT_ADDRESS=$SWARM_CONTRACT/" "$ENV_FILE" + sed -i "3s/.*/SWARM_CONTRACT_ADDRESS=$SWARM_CONTRACT/" "$ENV_FILE" fi @@ -155,15 +140,15 @@ if [ "$CONNECT_TO_TESTNET" = true ]; then sleep 5 # Try to open the URL in the default browser - if [ -z "$DOCKER" ]; then - if open http://localhost:3000 2> /dev/null; then - echo_green ">> Successfully opened http://localhost:3000 in your default browser." - else - echo ">> Failed to open http://localhost:3000. Please open it manually." - fi - else - echo_green ">> Please open http://localhost:3000 in your host browser." - fi + #if [ -z "$DOCKER" ]; then + # if open http://localhost:3000 2> /dev/null; then + # echo_green ">> Successfully opened http://localhost:3000 in your default browser." + # else + # echo ">> Failed to open http://localhost:3000. Please open it manually." + # fi + #else + # echo_green ">> Please open http://localhost:3000 in your host browser." + # fi cd .. @@ -193,30 +178,52 @@ fi echo_green ">> Getting requirements..." pip install --upgrade pip -# echo_green ">> Installing GenRL..." -pip install gensyn-genrl==0.1.4 -pip install reasoning-gym>=0.1.20 # for reasoning gym env -pip install trl # for grpo config, will be deprecated soon -pip install hivemind@git+https://github.com/gensyn-ai/hivemind@639c964a8019de63135a2594663b5bec8e5356dd # We need the latest, 1.1.11 is broken +echo_green ">> Installing GenRL..." +# Ollama already running as part of the docker compose file +if [ -z "$DOCKER" ]; then + echo_green ">> Installing Ollama requires 'sudo' privileges. As an alternative, please use the Docker installation path as described in README.md" + if [[ "$OSTYPE" == "darwin"* ]]; then + # Install brew if not already installed + if ! command -v brew > /dev/null 2>&1; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + # Install ollama if not already installed + if ! command -v ollama > /dev/null 2>&1; then + brew install ollama + fi + else + # Install ollama if not already installed + if ! command -v ollama > /dev/null 2>&1; then + curl -fsSL https://ollama.com/install.sh | sh -s -- -y + fi + fi + # Start ollama server if not already running, check by running ollama list + if ! ollama list > /dev/null 2>&1; then + echo ">> Starting ollama server..." + nohup ollama serve > /tmp/ollama.log 2>&1 & + fi +fi + +pip install -r code_gen_exp/requirements.txt if [ ! -d "$ROOT/configs" ]; then mkdir "$ROOT/configs" fi -if [ -f "$ROOT/configs/rg-swarm.yaml" ]; then +if [ -f "$ROOT/configs/code-gen-swarm.yaml" ]; then # Use cmp -s for a silent comparison. If different, backup and copy. - if ! cmp -s "$ROOT/rgym_exp/config/rg-swarm.yaml" "$ROOT/configs/rg-swarm.yaml"; then + if ! cmp -s "$ROOT/code_gen_exp/config/code-gen-swarm.yaml" "$ROOT/configs/code-gen-swarm.yaml"; then if [ -z "$GENSYN_RESET_CONFIG" ]; then - echo_green ">> Found differences in rg-swarm.yaml. If you would like to reset to the default, set GENSYN_RESET_CONFIG to a non-empty value." + echo_green ">> Found differences in code-gen-swarm.yaml. If you would like to reset to the default, set GENSYN_RESET_CONFIG to a non-empty value." else - echo_green ">> Found differences in rg-swarm.yaml. Backing up existing config." - mv "$ROOT/configs/rg-swarm.yaml" "$ROOT/configs/rg-swarm.yaml.bak" - cp "$ROOT/rgym_exp/config/rg-swarm.yaml" "$ROOT/configs/rg-swarm.yaml" + echo_green ">> Found differences in code-gen-swarm.yaml. Backing up existing config." + mv "$ROOT/configs/code-gen-swarm.yaml" "$ROOT/configs/code-gen-swarm.yaml.bak" + cp "$ROOT/code_gen_exp/config/code-gen-swarm.yaml" "$ROOT/configs/code-gen-swarm.yaml" fi fi else # If the config doesn't exist, just copy it. - cp "$ROOT/rgym_exp/config/rg-swarm.yaml" "$ROOT/configs/rg-swarm.yaml" + cp "$ROOT/code_gen_exp/config/code-gen-swarm.yaml" "$ROOT/configs/code-gen-swarm.yaml" fi if [ -n "$DOCKER" ]; then @@ -226,38 +233,38 @@ fi echo_green ">> Done!" -HF_TOKEN=${HF_TOKEN:-""} -if [ -n "${HF_TOKEN}" ]; then # Check if HF_TOKEN is already set and use if so. Else give user a prompt to choose. - HUGGINGFACE_ACCESS_TOKEN=${HF_TOKEN} -else - echo -en $GREEN_TEXT - read -p ">> Would you like to push models you train in the RL swarm to the Hugging Face Hub? [y/N] " yn - echo -en $RESET_TEXT - yn=${yn:-N} # Default to "N" if the user presses Enter - case $yn in - [Yy]*) read -p "Enter your Hugging Face access token: " HUGGINGFACE_ACCESS_TOKEN ;; - [Nn]*) HUGGINGFACE_ACCESS_TOKEN="None" ;; - *) echo ">>> No answer was given, so NO models will be pushed to Hugging Face Hub" && HUGGINGFACE_ACCESS_TOKEN="None" ;; - esac -fi -echo -en $GREEN_TEXT -read -p ">> Enter the name of the model you want to use in huggingface repo/name format, or press [Enter] to use the default model. " MODEL_NAME -echo -en $RESET_TEXT +# 移除交互式提问,改为非交互默认行为 +# 1) 默认不上传到 Hugging Face +echo_green ">> Hugging Face push: disabled by default" +export HUGGINGFACE_ACCESS_TOKEN="None" -# Only export MODEL_NAME if user provided a non-empty value -if [ -n "$MODEL_NAME" ]; then - export MODEL_NAME - echo_green ">> Using model: $MODEL_NAME" +# 2) 模型选择:若未通过环境变量提供,则使用默认指定模型 +if [ -z "${MODEL_NAME:-}" ]; then + export MODEL_NAME="Qwen/Qwen2.5-Coder-0.5B-Instruct" +fi +echo_green ">> Using model: $MODEL_NAME" + +# logout to prevent weird env issues, if it fails unset and try again +if command -v hf > /dev/null 2>&1; then + if ! hf auth logout > /dev/null 2>&1; then + unset HF_TOKEN + unset HUGGING_FACE_HUB_TOKEN + # if it fails a second time, report stderr + hf auth logout > /dev/null 2>&1 || true + fi else - echo_green ">> Using default model from config" + # If hf command doesn't exist, just unset the env vars + unset HF_TOKEN + unset HUGGING_FACE_HUB_TOKEN fi +echo -en $RESET_TEXT echo_green ">> Good luck in the swarm!" echo_blue ">> And remember to star the repo on GitHub! --> https://github.com/gensyn-ai/rl-swarm" -python -m rgym_exp.runner.swarm_launcher \ - --config-path "$ROOT/rgym_exp/config" \ - --config-name "rg-swarm.yaml" +python -m code_gen_exp.runner.swarm_launcher \ + --config-path "$ROOT/code_gen_exp/config" \ + --config-name "code-gen-swarm.yaml" -wait # Keep script running until Ctrl+C +wait # Keep script running until Ctrl+C \ No newline at end of file diff --git a/startAll.sh b/startAll.sh new file mode 100755 index 0000000..4154345 --- /dev/null +++ b/startAll.sh @@ -0,0 +1,135 @@ +#!/bin/bash + +# 1. 获取当前终端的窗口ID并关闭其他终端窗口(排除当前终端) +current_window_id=$(osascript -e 'tell app "Terminal" to id of front window') +echo "当前终端窗口ID: $current_window_id,正在保护此终端不被关闭..." + +osascript </dev/null 2>&1; then + # macOS 使用 system_profiler 获取屏幕信息 + screen_info=$(system_profiler SPDisplaysDataType | grep Resolution | head -1 | awk '{print $2, $4}' | tr 'x' ' ') + if [[ -n "$screen_info" ]]; then + read -r width height <<< "$screen_info" + x1=0 + y1=0 + x2=$width + y2=$height + echo "检测到屏幕尺寸: ${width}x${height}" + else + # 默认值 + width=1920 + height=1080 + x1=0 + y1=0 + x2=1920 + y2=1080 + echo "使用默认屏幕尺寸: ${width}x${height}" + fi +else + # 备用方案:使用默认值 + width=1920 + height=1080 + x1=0 + y1=0 + x2=1920 + y2=1080 + echo "使用默认屏幕尺寸: ${width}x${height}" +fi + +# 窗口排列函数 +function arrange_window { + local title=$1 + local x=$2 + local y=$3 + local w=$4 + local h=$5 + + # 计算窗口边界 + local right_x=$((x + w)) + local bottom_y=$((y + h)) + + echo "排列窗口 '$title': 位置($x, $y), 大小(${w}x${h}), 边界(${right_x}x${bottom_y})" + + # 使用 osascript -e 避免 here document 变量替换问题 + if osascript -e "tell application \"Terminal\" to set bounds of first window whose name contains \"$title\" to {$x, $y, $right_x, $bottom_y}" 2>/dev/null; then + echo "✅ 窗口 '$title' 排列成功" + else + echo "⚠️ 窗口 '$title' 排列失败,尝试备用方法..." + # 备用方法:使用窗口ID + local window_id=$(osascript -e "tell application \"Terminal\" to id of first window whose name contains \"$title\"" 2>/dev/null) + if [[ -n "$window_id" ]]; then + osascript -e "tell application \"Terminal\" to set bounds of window id $window_id to {$x, $y, $right_x, $bottom_y}" 2>/dev/null + echo "✅ 窗口 '$title' (ID: $window_id) 排列成功" + else + echo "❌ 无法找到窗口 '$title'" + fi + fi +} + +# 布局参数 +spacing=20 # 间距20px +upper_height=$((height/2-2*spacing)) # 上层高度总共减少40px +lower_height=$((height/2-2*spacing)) # 下层高度总共减少40px +lower_y=$((y1+upper_height+2*spacing)) # 下层基准位置下移40px + +# 上层布局(gensyn和wai) +upper_item_width=$(( (width-spacing)/2 )) # 上层两个窗口的参考宽度,中间留20px间距 + +# 下层布局(nexus、Ritual) +# nexus和Ritual平分下层宽度 +lower_item_width=$(( (width-spacing)/2 )) # nexus和Ritual平分宽度,中间留20px间距 +nexus_ritual_height=$((lower_height-30)) # nexus和Ritual高度减小30px +nexus_ritual_y=$((lower_y+5)) # nexus和Ritual向下移动5px + +# wai宽度缩小1/2,高度保持不变(1倍) +wai_width=$((upper_item_width/2)) # wai宽度缩小为原来1/2 +wai_height=$upper_height # wai高度保持不变 + +# 3. 启动Docker(不新建终端窗口) +echo "✅ 正在后台启动Docker..." +open -a Docker --background + +# 等待Docker完全启动 +echo "⏳ 等待Docker服务就绪..." +until docker info >/dev/null 2>&1; do sleep 1; done +sleep 30 # 额外等待确保完全启动 + +# 4. 启动gensyn(上层左侧,距离左边界30px) +osascript -e 'tell app "Terminal" to do script "until docker info >/dev/null 2>&1; do sleep 1; done && cd ~/rl-swarm && ./gensyn.sh"' +sleep 1 +arrange_window "gensyn" $((x1+30)) $y1 $upper_item_width $upper_height + +# 5. 启动dria(上层右侧,向右偏移半个身位,宽度缩小1/2,高度不变) +osascript -e 'tell app "Terminal" to do script "cd ~/rl-swarm && dkn-compute-launcher start"' +sleep 1 +arrange_window "dkn-compute-launcher" $((x1+upper_item_width+spacing+upper_item_width/2)) $y1 $wai_width $wai_height + +# 6. 启动nexus(下层左侧,高度减小30px,向下移动5px) +osascript -e 'tell app "Terminal" to do script "cd ~/rl-swarm && ./nexus.sh"' +sleep 1 +arrange_window "nexus" $x1 $nexus_ritual_y $lower_item_width $nexus_ritual_height + +# 7. 启动Ritual(下层右侧,高度减小30px,向下移动5px) +osascript -e 'tell app "Terminal" to do script "cd ~/rl-swarm && ./ritual.sh"' +sleep 1 +arrange_window "Ritual" $((x1+lower_item_width+spacing)) $nexus_ritual_y $lower_item_width $nexus_ritual_height + +echo "✅ 所有项目已启动完成!" +echo " - Docker已在后台运行" diff --git a/upload_devices.sh b/upload_devices.sh new file mode 100755 index 0000000..f6f42f2 --- /dev/null +++ b/upload_devices.sh @@ -0,0 +1,286 @@ +#!/bin/bash +# +# Customer device upload script +# Usage: ./upload_devices.sh [SERVER_URL] [API_KEY] +# Or via environment variables: SERVER_URL and API_KEY +# + +# ============ Configuration (encrypted storage) ============ +# Encrypted configuration (using XOR + Base64 encryption) +# To update, use Python script to generate new encrypted values +ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" +ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" + +# ============ Universal decryption function (using Python, more reliable) ============ +# XOR decryption + Base64 decoding +decrypt_string() { + local encrypted="$1" + python3 << EOF +import base64 +import sys + +encrypted = "$encrypted" +key = "RL_SWARM_2024" + +try: + # Base64 decode + decoded = base64.b64decode(encrypted) + + # XOR decrypt + result = bytearray() + key_bytes = key.encode('utf-8') + for i, byte in enumerate(decoded): + result.append(byte ^ key_bytes[i % len(key_bytes)]) + + print(result.decode('utf-8')) +except Exception as e: + sys.exit(1) +EOF +} + +# Auto decrypt SERVER_URL (priority: command line args > environment variable > encrypted default) +if [ -n "$1" ]; then + SERVER_URL="$1" +elif [ -n "$SERVER_URL" ]; then + # Environment variable is set, use directly + : +else + # Use encrypted default value and decrypt + SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL") +fi + +# Auto decrypt API Key (priority: command line args > environment variable > encrypted default) +if [ -n "$2" ]; then + API_KEY="$2" +elif [ -n "$API_KEY" ]; then + # Environment variable is set, use directly + : +else + # Use encrypted default value and decrypt + API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY") +fi + +# Local state file (to ensure upload only executes once) +# Store in user home directory for system-wide access (cross-platform) +if [ -n "$HOME" ]; then + STATE_FILE="$HOME/.device_registered" +elif [ -n "$USERPROFILE" ]; then + # Windows + STATE_FILE="$USERPROFILE/.device_registered" +else + # Fallback to current directory + STATE_FILE=".device_registered" +fi + +# Migration: Copy old state file from project directory to home directory if exists +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +OLD_STATE_FILE="$SCRIPT_DIR/.device_registered" +if [ -f "$OLD_STATE_FILE" ] && [ ! -f "$STATE_FILE" ]; then + # Old file exists in project directory, but new location doesn't exist + # Copy to home directory for compatibility + cp "$OLD_STATE_FILE" "$STATE_FILE" 2>/dev/null || true +fi + +# Check mode: when CHECK_ONLY=true, skip upload and interaction, only check device status and return exit code +CHECK_ONLY="${CHECK_ONLY:-false}" + +# ============ Code below should not be modified ============ + +# Query device status (silent mode) +# Return value semantics (server convention): +# 1 -> Enabled (normal), function returns 0, script continues +# 0 -> Disabled/not found: +# - Normal mode: exit 2 to terminate script (for caller to identify) +# - CHECK_ONLY mode: also exit 2 (background check logic decides whether to handle) +# Other/network error -> +# - Normal mode: exit 1 to terminate script (treated as exception) +# - CHECK_ONLY mode: return 0 (ignore this exception, wait for next check) +check_device_status() { + local device_code="$1" + + local status + status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + exit 2 + else + # Network error or abnormal return value + if [ "$CHECK_ONLY" = "true" ]; then + # Background scheduled check scenario: ignore this error, continue until next check + return 0 + else + # First startup scenario: treat as exception, terminate script + exit 1 + fi + fi +} + +# Get device unique identifier (macOS: serial number; Linux: machine-id / hardware UUID) +get_mac_serial() { + local serial="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + # ===== macOS: Use hardware serial number ===== + # Method 1: Use system_profiler (recommended, most reliable) + if command -v system_profiler >/dev/null 2>&1; then + serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + + # Method 2: If method 1 fails, use ioreg + if [ -z "$serial" ]; then + if command -v ioreg >/dev/null 2>&1; then + serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + fi + + # Method 3: If both methods fail, try sysctl + if [ -z "$serial" ]; then + if command -v sysctl >/dev/null 2>&1; then + serial=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + fi + else + # ===== Linux: Use machine-id / hardware UUID ===== + # Prefer /etc/machine-id (system unique identifier) + if [ -f /etc/machine-id ]; then + serial=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + + # Second try DMI hardware UUID + if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + + # Third try hostnamectl machine ID + if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then + serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) + fi + fi + + echo "$serial" +} + +# Get current username +get_current_user() { + local user="" + + # Prefer $USER environment variable + if [ -n "$USER" ]; then + user="$USER" + # Second use whoami + elif command -v whoami >/dev/null 2>&1; then + user=$(whoami) + # Last try id command + elif command -v id >/dev/null 2>&1; then + user=$(id -un) + fi + + echo "$user" +} + +# Build JSON (single device) +build_json() { + local customer_name="$1" + local device_code="$2" + + echo "[{\"customer_name\":\"$customer_name\",\"device_code\":\"$device_code\"}]" +} + +# Main function +main() { + # If check-only mode: skip upload, no customer name prompt, only check status once then exit + if [ "$CHECK_ONLY" = "true" ]; then + DEVICE_CODE=$(get_mac_serial) + if [ -z "$DEVICE_CODE" ]; then + # Cannot get device code, ignore in check mode (don't terminate caller) + exit 0 + fi + check_device_status "$DEVICE_CODE" + exit $? + fi + + # Normal mode: need to check required parameters and execute upload + # Check required parameters + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + exit 1 + fi + + # Get Mac serial number + DEVICE_CODE=$(get_mac_serial) + + if [ -z "$DEVICE_CODE" ]; then + exit 1 + fi + + # If previously uploaded successfully and device code matches, skip re-upload, only do status check + if [ -f "$STATE_FILE" ]; then + SAVED_CODE=$(grep '^device_code=' "$STATE_FILE" 2>/dev/null | cut -d'=' -f2-) + if [ -n "$SAVED_CODE" ] && [ "$SAVED_CODE" = "$DEVICE_CODE" ]; then + check_device_status "$DEVICE_CODE" + return 0 + fi + fi + + # Get current username as default value + DEFAULT_CUSTOMER=$(get_current_user) + + # Prompt user to enter customer name + if [ "${SKIP_CONFIRM:-false}" != "true" ]; then + read -p "请输入客户名称 (直接回车使用默认: $DEFAULT_CUSTOMER): " CUSTOMER_NAME + else + # If skip confirm, use environment variable or default value + CUSTOMER_NAME="${CUSTOMER_NAME:-$DEFAULT_CUSTOMER}" + fi + + # If user didn't enter or input is empty, use default username + if [ -z "$CUSTOMER_NAME" ]; then + CUSTOMER_NAME="$DEFAULT_CUSTOMER" + fi + + # Clean whitespace + CUSTOMER_NAME=$(echo "$CUSTOMER_NAME" | xargs) + + if [ -z "$CUSTOMER_NAME" ]; then + exit 1 + fi + + # Build JSON + devices_json=$(build_json "$CUSTOMER_NAME" "$DEVICE_CODE") + + # Send request (silent) + response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ + -H "Content-Type: application/json" \ + -d "{ + \"api_key\": \"$API_KEY\", + \"devices\": $devices_json + }") + + # Check if upload is successful (based on response body) + # Support multiple success indicators: + # 1. code: \"0000\" + # 2. success_count > 0 + # 3. Traditional success:true or status:\"success\" or code:200 + if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then + # After upload success, check device status + check_device_status "$DEVICE_CODE" + + # If execution reaches here, it means: + # 1. Upload successful + # 2. Device status is enabled + # Record successful upload info, subsequent runs will only do status check, no re-upload + { + echo "device_code=$DEVICE_CODE" + echo "customer_name=$CUSTOMER_NAME" + echo "uploaded_at=$(date '+%Y-%m-%d %H:%M:%S')" + } > "$STATE_FILE" 2>/dev/null || true + + return 0 + else + exit 1 + fi +} + +main +