From 4c0ef46b1363ecdb871c6f6f215c364a1f81628b Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Mon, 19 Aug 2024 22:25:01 +0200 Subject: [PATCH 1/4] feat: choose staking --- run_service.sh | 6 +- scripts/choose_staking.py | 25 +++-- scripts/staking.py | 195 ++++++++++++++++++++++---------------- 3 files changed, 133 insertions(+), 93 deletions(-) diff --git a/run_service.sh b/run_service.sh index 961a9d5e..e233b6cf 100755 --- a/run_service.sh +++ b/run_service.sh @@ -897,7 +897,7 @@ if [ "$local_service_hash" != "$remote_service_hash" ] || [ "$on_chain_agent_id" response="y" if [ "${USE_STAKING}" = true ]; then - echo "Your service is in a staking program. Updating your on-chain service requires that it is first unstaked." + echo "If your service is in a staking program, updating your on-chain service requires that it is first unstaked." echo "Unstaking your service will retrieve the accrued staking rewards." echo "" echo "Do you want to continue updating your service? (yes/no)" @@ -909,9 +909,7 @@ if [ "$local_service_hash" != "$remote_service_hash" ] || [ "$on_chain_agent_id" echo "Skipping on-chain service update." else # unstake the service - if [ "${USE_STAKING}" = true ]; then - perform_staking_ops true - fi + perform_staking_ops true # Check balances suggested_amount=$suggested_top_up_default diff --git a/scripts/choose_staking.py b/scripts/choose_staking.py index 468cb1c3..ca0060a9 100644 --- a/scripts/choose_staking.py +++ b/scripts/choose_staking.py @@ -229,14 +229,14 @@ def _get_nevermined_env_variables() -> Dict[str, str]: use_nevermined = True if use_nevermined: - print("A Nevermined subscription will be used to pay for the mech requests.") + print(" - A Nevermined subscription will be used to pay for the mech requests.") return { "MECH_CONTRACT_ADDRESS": NEVERMINED_MECH_CONTRACT_ADDRESS, "AGENT_REGISTRY_ADDRESS": NEVERMINED_AGENT_REGISTRY_ADDRESS, "MECH_REQUEST_PRICE": NEVERMINED_MECH_REQUEST_PRICE } else: - print("No Nevermined subscription set.") + print(" - No Nevermined subscription set.") return { "AGENT_REGISTRY_ADDRESS": "", "MECH_REQUEST_PRICE": "" @@ -249,24 +249,35 @@ def main() -> None: args = parser.parse_args() if args.reset: + env_file_vars = dotenv_values(DOTENV_PATH) + staking_program = env_file_vars.get("STAKING_PROGRAM") + print("=====================================") + print("Reset your staking program preference") + print("=====================================") + print("") + print(f"Your current staking program is set to '{staking_program}'") + response = input("Do you want to reset your staking program preference? (yes/no): ").strip().lower() + if response not in ['yes', 'y']: + return + + print("") unset_key(dotenv_path=DOTENV_PATH, key_to_unset="USE_STAKING") unset_key(dotenv_path=DOTENV_PATH, key_to_unset="STAKING_PROGRAM") print(f"Environment variables USE_STAKING and STAKING_PROGRAM have been reset in '{DOTENV_PATH}'.") - print("You can now execute './run_service.sh' and select a different staking program.") print("") - return program_id = _prompt_select_staking_program() - print("Populating staking program variables in the .env file") - print("") + print(" - Populating staking program variables in the .env file") staking_env_variables = _get_staking_env_variables(program_id) _set_dotenv_file_variables(staking_env_variables) - print("Populating Nevermined variables in the .env file") + print(" - Populating Nevermined variables in the .env file") print("") nevermined_env_variables = _get_nevermined_env_variables() _set_dotenv_file_variables(nevermined_env_variables) + print("") + print("Finished populating the .env file.") if __name__ == "__main__": diff --git a/scripts/staking.py b/scripts/staking.py index 610db5c6..7fcd68f8 100644 --- a/scripts/staking.py +++ b/scripts/staking.py @@ -153,6 +153,101 @@ def _check_unstaking_availability( return True +def _try_unstake_service( + ledger_api: EthereumApi, + service_id: int, + owner_crypto: EthereumCrypto, + service_registry_address: str, +) -> None: + + all_staking_programs = STAKING_PROGRAMS.copy() + all_staking_programs.update(DEPRECATED_STAKING_PROGRAMS) + del all_staking_programs["no_staking"] + del all_staking_programs["quickstart_alpha_everest"] # Very old program, not used likely - causes issues on "is_service_staked" + + # Determine the staking contract address + staking_program = None + staking_contract_address = None + for program, data in all_staking_programs.items(): + address = data["deployment"]["stakingTokenInstanceAddress"] + if is_service_staked( + ledger_api, service_id, address + ): + staking_program = program + staking_contract_address = address + print(f"Service {service_id} is staked on {program}.") + else: + print(f"Service {service_id} is not staked on {program}.") + + # Exit if not staked + if staking_contract_address is None: + sys.exit(0) + + # Collect information + next_ts = get_next_checkpoint_ts(ledger_api, staking_contract_address) + liveness_period = get_liveness_period(ledger_api, staking_contract_address) + last_ts = next_ts - liveness_period + now = time.time() + + if is_service_evicted( + ledger_api, service_id, staking_contract_address + ): + print( + f"WARNING: Service {service_id} has been evicted from the {staking_program} staking program due to inactivity." + ) + input("Press Enter to continue...") + + can_unstake = _check_unstaking_availability( + ledger_api, + service_id, + staking_contract_address, + staking_program, + ) + + if not can_unstake: + print("Terminating script.") + sys.exit(1) + + if now < next_ts: + formatted_last_ts = datetime.utcfromtimestamp(last_ts).strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) + formatted_next_ts = datetime.utcfromtimestamp(next_ts).strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) + + print( + "WARNING: Staking checkpoint call not available yet\n" + "--------------------------------------------------\n" + f"The liveness period ({liveness_period/3600} hours) has not passed since the last checkpoint call.\n" + f" - {formatted_last_ts} - Last checkpoint call.\n" + f" - {formatted_next_ts} - Next checkpoint call availability.\n" + "\n" + "If you proceed with unstaking, your agent's work done between the last checkpoint call until now will not be accounted for rewards.\n" + "(Note: To maximize agent work eligible for rewards, the recommended practice is to unstake shortly after a checkpoint has been called and stake again immediately after.)\n" + ) + + user_input = input( + f"Do you want to continue unstaking service {service_id} from {staking_program}? (yes/no)\n" + ).lower() + print() + + if user_input not in ["yes", "y"]: + print("Terminating script.") + sys.exit(1) + + print(f"Unstaking service {service_id} from {staking_program}...") + unstake_txs = get_unstake_txs( + ledger_api, service_id, staking_contract_address + ) + for tx in unstake_txs: + send_tx_and_wait_for_receipt(ledger_api, owner_crypto, tx) + print( + f"Successfully unstaked service {service_id} from {staking_program}." + ) + sys.exit(0) + + def _try_stake_service( ledger_api: EthereumApi, service_id: int, @@ -237,87 +332,26 @@ def main() -> None: private_key_path=args.owner_private_key_path, password=args.password ) - _unstake_all_old_programs( - ledger_api=ledger_api, - service_id=args.service_id, - owner_crypto=owner_crypto, - current_staking_contract_address=args.staking_contract_address - ) + # No need to execute this instruction here, as the user can choose the staking program + # + # _unstake_all_old_programs( + # ledger_api=ledger_api, + # service_id=args.service_id, + # owner_crypto=owner_crypto, + # current_staking_contract_address=args.staking_contract_address + # ) - # Collect information - next_ts = get_next_checkpoint_ts(ledger_api, args.staking_contract_address) - liveness_period = get_liveness_period(ledger_api, args.staking_contract_address) - last_ts = next_ts - liveness_period - now = time.time() available_rewards = get_available_rewards( ledger_api, args.staking_contract_address ) if args.unstake: - if not is_service_staked( - ledger_api, args.service_id, args.staking_contract_address - ): - # the service is not staked, so we don't need to do anything - print(f"Service {args.service_id} is not staked on {staking_program}..") - sys.exit(0) - - if is_service_evicted( - ledger_api, args.service_id, args.staking_contract_address - ): - print( - f"WARNING: Service {args.service_id} has been evicted from the {staking_program} staking program due to inactivity." - ) - input("Press Enter to continue...") - - can_unstake = _check_unstaking_availability( - ledger_api, - args.service_id, - args.staking_contract_address, - staking_program, - ) - - if not can_unstake: - print("Terminating script.") - sys.exit(1) - - if now < next_ts: - formatted_last_ts = datetime.utcfromtimestamp(last_ts).strftime( - "%Y-%m-%d %H:%M:%S UTC" - ) - formatted_next_ts = datetime.utcfromtimestamp(next_ts).strftime( - "%Y-%m-%d %H:%M:%S UTC" - ) - - print( - "WARNING: Staking checkpoint call not available yet\n" - "--------------------------------------------------\n" - f"The liveness period ({liveness_period/3600} hours) has not passed since the last checkpoint call.\n" - f" - {formatted_last_ts} - Last checkpoint call.\n" - f" - {formatted_next_ts} - Next checkpoint call availability.\n" - "\n" - "If you proceed with unstaking, your agent's work done between the last checkpoint call until now will not be accounted for rewards.\n" - "(Note: To maximize agent work eligible for rewards, the recommended practice is to unstake shortly after a checkpoint has been called and stake again immediately after.)\n" - ) - - user_input = input( - f"Do you want to continue unstaking service {args.service_id} from {staking_program}? (yes/no)\n" - ).lower() - print() - - if user_input not in ["yes", "y"]: - print("Terminating script.") - sys.exit(1) - - print(f"Unstaking service {args.service_id} from {staking_program}...") - unstake_txs = get_unstake_txs( - ledger_api, args.service_id, args.staking_contract_address - ) - for tx in unstake_txs: - send_tx_and_wait_for_receipt(ledger_api, owner_crypto, tx) - print( - f"Successfully unstaked service {args.service_id} from {staking_program}." + _try_unstake_service( + ledger_api=ledger_api, + service_id=args.service_id, + owner_crypto=owner_crypto, + service_registry_address=args.service_registry_address, ) - sys.exit(0) if is_service_staked( ledger_api, args.service_id, args.staking_contract_address @@ -371,14 +405,11 @@ def main() -> None: print( f"No rewards available. Unstaking service {args.service_id} from {staking_program}..." ) - unstake_txs = get_unstake_txs( - ledger_api, args.service_id, args.staking_contract_address - ) - for tx in unstake_txs: - send_tx_and_wait_for_receipt(ledger_api, owner_crypto, tx) - - print( - f"Successfully unstaked service {args.service_id} from {staking_program}." + _try_unstake_service( + ledger_api=ledger_api, + service_id=args.service_id, + owner_crypto=owner_crypto, + service_registry_address=args.service_registry_address, ) sys.exit(0) From 694bfec389556ef9eeca0c06010ba72880d249f2 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Tue, 20 Aug 2024 01:03:48 +0200 Subject: [PATCH 2/4] doc: readme --- README.md | 30 ++++++++++++++----- ...alpine_staking_fsm.svg => staking_fsm.svg} | 0 2 files changed, 23 insertions(+), 7 deletions(-) rename images/{alpine_staking_fsm.svg => staking_fsm.svg} (100%) diff --git a/README.md b/README.md index 1694596e..9d15ac7c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Do you want to use staking in this service? (yes/no): n > Using this code could potentially lead to loss of funds, compromised data, or asset risk. > Exercise caution and use this code at your own risk. Please refer to the [LICENSE](./LICENSE) file for details about the terms and conditions. -Before you proceed, ensure you have at least 20 OLAS on Gnosis Chain. For more information on staking, checkout the following [blogpost](https://www.valory.xyz/post/alpine). +Each staking program has different OLAS requirements. The script will check that your owner address meets the minimum required OLAS on the Gnosis Chain. Clone this repository locally and execute: @@ -57,22 +57,38 @@ chmod +x run_service.sh ./run_service.sh ``` -Answer 'Yes' when prompted: +Select your preferred staking program when prompted: ```text -Do you want to use staking in this service? (yes/no): y +Please, select your staking program preference +---------------------------------------------- +1) No staking + Your Olas Predict agent will still actively participate in prediction + markets, but it will not be staked within any staking program. + +2) Quickstart Beta - Hobbyist + The Quickstart Beta - Hobbyist staking contract offers 100 slots for + operators running Olas Predict agents with the quickstart. It is designed as + a step up from Coastal Staker Expeditions, requiring 100 OLAS for staking. + The rewards are also more attractive than with Coastal Staker Expeditions. + +3) Quickstart Beta - Expert + The Quickstart Beta - Expert staking contract offers 20 slots for operators + running Olas Predict agents with the quickstart. It is designed for + professional agent operators, requiring 1000 OLAS for staking. The rewards + are proportional to the Quickstart Beta - Hobbyist. ``` -Find below a diagram of the possible status a service can be in the **Alpine staking** program: +Find below a diagram of the possible status a service can be in the staking program: -![Alpine staking FSM](images/alpine_staking_fsm.svg) +![Staking FSM](images/staking_fsm.svg) -Services can become staked by invoking the `stake()` contract method, where service parameters and deposit amounts are verified. Staked services can call the `checkpoint()` method at regular intervals, ensuring liveness checks and calculating staking incentives. In case a service remains inactive beyond the specified `maxAllowedInactivity` time, it faces eviction from the staking program, ceasing to accrue additional rewards. Staked or evicted services can unstaked by calling the `unstake()` contract method. They can do so after `minStakingDuration` has passed or if no more staking rewards are available. +Services can become staked by invoking the `stake()` contract method, where service parameters and deposit amounts are verified. Staked services can call the `checkpoint()` method at regular intervals, ensuring liveness checks and calculating staking rewards. In case a service remains inactive beyond the specified `maxAllowedInactivity` time, it faces eviction from the staking program, ceasing to accrue additional rewards. Staked or evicted services can be unstaked by calling the `unstake()` contract method. They can do so after `minStakingDuration` has passed or if no more staking rewards are available. __Notes__: - Staking is currently in a testing phase, so the number of trader agents that can be staked might be limited. -- In the [Alpine staking program](https://www.valory.xyz/post/alpine) services are evicted after accumulating 2 consecutive checkpoints without meeting the activity threshold. +- Services are evicted after accumulating 2 consecutive checkpoints without meeting the activity threshold. - Currently, the minimum staking time is approximately 3 days. In particular, a service cannot be unstaked during the minimum staking period. ### Service is Running diff --git a/images/alpine_staking_fsm.svg b/images/staking_fsm.svg similarity index 100% rename from images/alpine_staking_fsm.svg rename to images/staking_fsm.svg From c1bb8ba00c35577b3cf67e76fa555b530a68ef63 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Tue, 20 Aug 2024 01:25:40 +0200 Subject: [PATCH 3/4] chore: update --- README.md | 7 +++++++ scripts/staking.py | 44 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9d15ac7c..f6775be1 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,13 @@ Services can become staked by invoking the `stake()` contract method, where serv - Staking is currently in a testing phase, so the number of trader agents that can be staked might be limited. - Services are evicted after accumulating 2 consecutive checkpoints without meeting the activity threshold. - Currently, the minimum staking time is approximately 3 days. In particular, a service cannot be unstaked during the minimum staking period. +- Once a staking program is selected, you can reset your preference by running the command + + ``` bash + cd trader; poetry run python ../scripts/choose_staking.py --reset; cd .. + ``` + + Keep in mind that your service must stay for `minStakingDuration` in a staking program (typically 3 days) before you can change to a new program. ### Service is Running diff --git a/scripts/staking.py b/scripts/staking.py index 7fcd68f8..61e26c10 100644 --- a/scripts/staking.py +++ b/scripts/staking.py @@ -46,6 +46,10 @@ send_tx_and_wait_for_receipt, ) +SCRIPT_PATH = Path(__file__).resolve().parent +STORE_PATH = Path(SCRIPT_PATH, "..", ".trader_runner") +DOTENV_PATH = Path(STORE_PATH, ".env") + def _format_duration(duration_seconds: int) -> str: days, remainder = divmod(duration_seconds, 86400) @@ -153,19 +157,12 @@ def _check_unstaking_availability( return True -def _try_unstake_service( - ledger_api: EthereumApi, - service_id: int, - owner_crypto: EthereumCrypto, - service_registry_address: str, -) -> None: - +def _get_current_staking_program(ledger_api, service_id): all_staking_programs = STAKING_PROGRAMS.copy() all_staking_programs.update(DEPRECATED_STAKING_PROGRAMS) del all_staking_programs["no_staking"] - del all_staking_programs["quickstart_alpha_everest"] # Very old program, not used likely - causes issues on "is_service_staked" - - # Determine the staking contract address + del all_staking_programs["quickstart_alpha_everest"] # Very old program, not used likely - causes issues on "is_service_staked" + staking_program = None staking_contract_address = None for program, data in all_staking_programs.items(): @@ -178,6 +175,17 @@ def _try_unstake_service( print(f"Service {service_id} is staked on {program}.") else: print(f"Service {service_id} is not staked on {program}.") + return staking_contract_address, staking_program + + +def _try_unstake_service( + ledger_api: EthereumApi, + service_id: int, + owner_crypto: EthereumCrypto, + service_registry_address: str, +) -> None: + + staking_contract_address, staking_program = _get_current_staking_program(ledger_api, service_id) # Exit if not staked if staking_contract_address is None: @@ -356,6 +364,22 @@ def main() -> None: if is_service_staked( ledger_api, args.service_id, args.staking_contract_address ): + + _, current_program = _get_current_staking_program(ledger_api, args.service_id) + env_file_vars = dotenv_values(DOTENV_PATH) + target_program = env_file_vars.get("STAKING_PROGRAM") + + if current_program != target_program: + print( + f"WARNING: Service {args.service_id} is staked on {current_program}, but target program is {target_program}. Unstaking..." + ) + _try_unstake_service( + ledger_api=ledger_api, + service_id=args.service_id, + owner_crypto=owner_crypto, + service_registry_address=args.service_registry_address, + ) + if is_service_evicted( ledger_api, args.service_id, args.staking_contract_address ): From 3f9d3b83bc70da4aa39c9d4ac11a6c12db12f073 Mon Sep 17 00:00:00 2001 From: jmoreira-valory <96571377+jmoreira-valory@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:38:29 +0200 Subject: [PATCH 4/4] Update README.md Co-authored-by: David Galindo <35235550+dagacha@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6775be1..49664449 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Services can become staked by invoking the `stake()` contract method, where serv - Staking is currently in a testing phase, so the number of trader agents that can be staked might be limited. - Services are evicted after accumulating 2 consecutive checkpoints without meeting the activity threshold. - Currently, the minimum staking time is approximately 3 days. In particular, a service cannot be unstaked during the minimum staking period. -- Once a staking program is selected, you can reset your preference by running the command +- Once a staking program is selected, you can reset your preference by stopping your agent by running ./stop_service.sh and then running the command ``` bash cd trader; poetry run python ../scripts/choose_staking.py --reset; cd ..