From 8967e9b6a27a74d403f6399e9e4767f4eb531d4b Mon Sep 17 00:00:00 2001 From: "Yuri (solarw) Turchenkov" Date: Mon, 5 Aug 2024 19:57:44 +0300 Subject: [PATCH 01/47] initial win support --- .github/workflows/release_win.yml | 59 +++++++++++++++++++++++++++ Makefile | 49 ++++++++++++++++++++++ build-win-tenderly.js | 38 +++++++++++++++++ build-win.js | 38 +++++++++++++++++ electron/main.js | 6 +++ frontend/package.json | 2 +- operate/ledger/__init__.py | 8 ++++ operate/services/deployment_runner.py | 32 +++++++++++++-- 8 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release_win.yml create mode 100644 Makefile create mode 100644 build-win-tenderly.js create mode 100644 build-win.js diff --git a/.github/workflows/release_win.yml b/.github/workflows/release_win.yml new file mode 100644 index 000000000..8c2739a63 --- /dev/null +++ b/.github/workflows/release_win.yml @@ -0,0 +1,59 @@ +name: Release for Windows + +# This workflow is triggered on pushing a tag BE CAREFUL this application AUTO UPDATES !!! +# git tag vX.Y.Z +# git push origin tag vX.Y.Z + +on: [pull_request] + +jobs: + build-windows: + runs-on: windows-latest + #strategy: + # matrix: + # arch: [ x64, arm64 ] + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + with: + # version: '1.4.0' + virtualenvs-create: true + virtualenvs-in-project: false + virtualenvs-path: ~/my-custom-path + installer-parallel: true + #- name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + - name: Install dependencies + run: poetry install + + - name: install node deps + run: yarn install-deps + + - name: set env vars to prod.env + env: + NODE_ENV: production + DEV_RPC: https://rpc-gate.autonolas.tech/gnosis-rpc/ + IS_STAGING: ${{ github.ref != 'refs/heads/main' && 'true' || 'false' }} + FORK_URL: https://rpc-gate.autonolas.tech/gnosis-rpc/ + run: | + echo NODE_ENV=$NODE_ENV >> prod.env + echo DEV_RPC=$DEV_RPC >> prod.env + echo IS_STAGING=$IS_STAGING >> prod.env + echo FORK_URL=$FORK_URL >> prod.env + cat prod.env + - run: rm -rf /dist + - name: "Build, notarize, publish" + run: make build diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..826bc5219 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ + +define setup_env + $(eval ENV_FILE := $(1).env) + @echo " - setup env $(ENV_FILE)" + $(eval include $(1).env) + $(eval export) +endef + + +./trader/: + pwd + git clone https://github.com/valory-xyz/trader.git + +./dist/aea_win.exe: ./trader/ + mkdir -p dist + cd trader && poetry install && poetry run pyinstaller --collect-data eth_account --collect-all aea --collect-all autonomy --collect-all operate --collect-all aea_ledger_ethereum --collect-all aea_ledger_cosmos --collect-all aea_ledger_ethereum_flashbots --hidden-import aea_ledger_ethereum --hidden-import aea_ledger_cosmos --hidden-import aea_ledger_ethereum_flashbots --hidden-import grpc --hidden-import openapi_core --collect-all google.protobuf --collect-all openapi_core --collect-all openapi_spec_validator --collect-all asn1crypto --hidden-import py_ecc --hidden-import pytz --onefile pyinstaller/trader_bin.py --name trader_win + cp -f trader/dist/trader_win.exe ./dist/aea_win.exe + pwd + + +./dist/tendermint_win.exe: ./operate + pwd + poetry install && poetry run pyinstaller operate/services/utils/tendermint.py --onefile --name tendermint_win + + +./dist/pearl_win.exe: ./dist/aea_win.exe ./dist/tendermint_win.exe + pwd + poetry install && poetry run pyinstaller --collect-data eth_account --collect-all aea --collect-all coincurve --collect-all autonomy --collect-all operate --collect-all aea_ledger_ethereum --collect-all aea_ledger_cosmos --collect-all aea_ledger_ethereum_flashbots --hidden-import aea_ledger_ethereum --hidden-import aea_ledger_cosmos --hidden-import aea_ledger_ethereum_flashbots operate/pearl.py --add-binary dist/aea_win.exe:. --add-binary dist/tendermint_win.exe:. --onefile --name pearl_win + + +.PHONY: build +build: ./dist/pearl_win.exe + $(call setup_env, prod) + echo ${DEV_RPC} + mkdir -p ./electron/bins + cp -f dist/pearl_win.exe ./electron/bins/pearl_win.exe + echo ${NODE_ENV} + NODE_ENV=${NODE_ENV} DEV_RPC=${DEV_RPC} FORK_URL=${FORK_URL} yarn build:frontend + node build-win.js + + +.PHONY: build-tenderly +build-tenderly: ./dist/pearl_win.exe + $(call setup_env, dev-tenderly) + echo ${DEV_RPC} + cp -f dist/pearl_win.exe ./electron/bins/pearl_win.exe + echo ${NODE_ENV} + NODE_ENV=${NODE_ENV} DEV_RPC=${DEV_RPC} FORK_URL=${FORK_URL} yarn build:frontend + node build-win-tenderly.js \ No newline at end of file diff --git a/build-win-tenderly.js b/build-win-tenderly.js new file mode 100644 index 000000000..68fbdbbf4 --- /dev/null +++ b/build-win-tenderly.js @@ -0,0 +1,38 @@ +/** + * This script is used to build the electron app **with notarization**. It is used for the final build and release process. + */ +require('dotenv').config(); +const build = require('electron-builder').build; + +const { publishOptions } = require('./electron/constants'); + +const main = async () => { + console.log('Building...'); + + /** @type import {CliOptions} from "electron-builder" */ + await build({ + publish: 'onTag', + config: { + appId: 'xyz.valory.olas-operate-app', + artifactName: '${productName}-${version}-${platform}-${arch}-tenderly.${ext}', + productName: 'Pearl', + files: ['electron/**/*', 'package.json'], + directories: { + output: 'dist', + }, + nsis: { + oneClick: false, + }, + extraResources: [ + { + from: 'electron/bins', + to: 'bins', + filter: ['**/*'], + }, + ], + + }, + }); +}; + +main().then((response) => { console.log('Build & Notarize complete'); }).catch((e) => console.error(e)); diff --git a/build-win.js b/build-win.js new file mode 100644 index 000000000..7bf8db3e2 --- /dev/null +++ b/build-win.js @@ -0,0 +1,38 @@ +/** + * This script is used to build the electron app **with notarization**. It is used for the final build and release process. + */ +require('dotenv').config(); +const build = require('electron-builder').build; + +const { publishOptions } = require('./electron/constants'); + +const main = async () => { + console.log('Building...'); + + /** @type import {CliOptions} from "electron-builder" */ + await build({ + publish: 'onTag', + config: { + appId: 'xyz.valory.olas-operate-app', + artifactName: '${productName}-${version}-${platform}-${arch}.${ext}', + productName: 'Pearl', + files: ['electron/**/*', 'package.json'], + directories: { + output: 'dist', + }, + nsis: { + oneClick: false, + }, + extraResources: [ + { + from: 'electron/bins', + to: 'bins', + filter: ['**/*'], + }, + ], + + }, + }); +}; + +main().then((response) => { console.log('Build & Notarize complete'); }).catch((e) => console.error(e)); diff --git a/electron/main.js b/electron/main.js index e2c66a668..e44ae3954 100644 --- a/electron/main.js +++ b/electron/main.js @@ -39,6 +39,9 @@ const binaryPaths = { arm64: 'bins/pearl_arm64', x64: 'bins/pearl_x64', }, + win32: { + x64: 'bins/pearl_win.exe', + }, }; let appConfig = { @@ -281,6 +284,7 @@ async function launchDaemon() { } catch (err) { logger.electron('Backend not running!'); } + logger.info("11111111 " + JSON.stringify(Env, null, " ")); const check = new Promise(function (resolve, _reject) { operateDaemon = spawn( @@ -324,6 +328,7 @@ async function launchDaemon() { } async function launchDaemonDev() { + const check = new Promise(function (resolve, _reject) { operateDaemon = spawn('poetry', [ 'run', @@ -388,6 +393,7 @@ async function launchNextAppDev() { 'yarn', ['dev:frontend', '--port', appConfig.ports.dev.next], { + shell: true, env: { ...process.env, NEXT_PUBLIC_BACKEND_PORT: appConfig.ports.dev.operate, diff --git a/frontend/package.json b/frontend/package.json index ec20ee33a..74a21ceaf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,7 +45,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "NODE_ENV=production next build", + "build": "bash -c 'NODE_ENV=production next build'", "lint": "next lint", "test": "jest", "start": "next start" diff --git a/operate/ledger/__init__.py b/operate/ledger/__init__.py index 078a93d1e..82807f71d 100644 --- a/operate/ledger/__init__.py +++ b/operate/ledger/__init__.py @@ -38,6 +38,14 @@ GOERLI_RPC = os.environ.get("DEV_RPC", "https://ethereum-goerli.publicnode.com") SOLANA_RPC = os.environ.get("DEV_RPC", "https://api.mainnet-beta.solana.com") + +print( + 11111111111111111111, + "DEV_RPC", + os.environ.get("DEV_RPC", "https://gnosis-rpc.publicnode.com"), + flush=True +) + PUBLIC_RPCS = { ChainType.ETHEREUM: ETHEREUM_PUBLIC_RPC, ChainType.GNOSIS: GNOSIS_PUBLIC_RPC, diff --git a/operate/services/deployment_runner.py b/operate/services/deployment_runner.py index 1bf81904e..810cfbce3 100644 --- a/operate/services/deployment_runner.py +++ b/operate/services/deployment_runner.py @@ -31,7 +31,6 @@ from pathlib import Path from typing import Any from venv import main as venv_cli - import psutil from aea.__version__ import __version__ as aea_version from autonomy.__version__ import __version__ as autonomy_version @@ -95,6 +94,7 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta): def _run_aea(self, *args: str, cwd: Path) -> Any: """Run aea command.""" + print(222222222222222, self._aea_bin, args, flush=True) return self._run_cmd(args=[self._aea_bin, *args], cwd=cwd) @staticmethod @@ -243,7 +243,7 @@ def _start_agent(self) -> None: stderr=subprocess.DEVNULL, env={**os.environ, **env}, creationflags=( - 0x00000008 if platform.system() == "Windows" else 0 + 0x00000200 if platform.system() == "Windows" else 0 ), # Detach process from the main process ) (working_dir / "agent.pid").write_text( @@ -263,7 +263,7 @@ def _start_tendermint(self) -> None: stderr=subprocess.DEVNULL, env={**os.environ, **env}, creationflags=( - 0x00000008 if platform.system() == "Windows" else 0 + 0x00000200 if platform.system() == "Windows" else 0 ), # Detach process from the main process ) (working_dir / "tendermint.pid").write_text( @@ -272,6 +272,23 @@ def _start_tendermint(self) -> None: ) +class PyInstallerHostDeploymentRunnerMac(PyInstallerHostDeploymentRunner): + pass + + +class PyInstallerHostDeploymentRunnerWindows(PyInstallerHostDeploymentRunner): + @property + def _aea_bin(self) -> str: + """Return aea_bin path.""" + abin = str(Path(sys._MEIPASS) / "aea_win.exe") # type: ignore # pylint: disable=protected-access + return abin + + @property + def _tendermint_bin(self) -> str: + """Return tendermint path.""" + return str(Path(sys._MEIPASS) / "tendermint_win.exe") # type: ignore # pylint: disable=protected-access + + class HostPythonHostDeploymentRunner(BaseDeploymentRunner): """Deployment runner for host installed python.""" @@ -368,8 +385,15 @@ def _setup_agent(self) -> None: def _get_host_deployment_runner(build_dir: Path) -> BaseDeploymentRunner: """Return depoyment runner according to running env.""" deployment_runner: BaseDeploymentRunner + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): - deployment_runner = PyInstallerHostDeploymentRunner(build_dir) + # pyinstaller inside! + if platform.system() == "Darwin": + deployment_runner = PyInstallerHostDeploymentRunner(build_dir) + elif platform.system() == "Windows": + deployment_runner = PyInstallerHostDeploymentRunnerWindows(build_dir) + else: + raise ValueError(f"Platform not supported {platform.system()}") else: deployment_runner = HostPythonHostDeploymentRunner(build_dir) return deployment_runner From d1015e79a62f152d3da76074728ad78e2b44bf2c Mon Sep 17 00:00:00 2001 From: "Yuri (solarw) Turchenkov" Date: Tue, 13 Aug 2024 18:03:59 +0300 Subject: [PATCH 02/47] win: process kill fixes --- electron/main.js | 1 - electron/processes.js | 36 ++++++++++++++++----------- frontend/package.json | 2 +- operate/ledger/__init__.py | 7 ------ operate/services/deployment_runner.py | 17 +++---------- 5 files changed, 26 insertions(+), 37 deletions(-) diff --git a/electron/main.js b/electron/main.js index f435dd67a..9b41fb84c 100644 --- a/electron/main.js +++ b/electron/main.js @@ -292,7 +292,6 @@ async function launchDaemon() { } catch (err) { logger.electron('Backend not running!'); } - logger.info("11111111 " + JSON.stringify(Env, null, " ")); const check = new Promise(function (resolve, _reject) { operateDaemon = spawn( diff --git a/electron/processes.js b/electron/processes.js index e67dcaac5..faaa9266f 100644 --- a/electron/processes.js +++ b/electron/processes.js @@ -3,7 +3,7 @@ const { exec } = require('child_process'); const unixKillCommand = 'kill -9'; const windowsKillCommand = 'taskkill /F /PID'; - +const { logger } = require('./logger'); const isWindows = process.platform === 'win32'; function killProcesses(pid) { @@ -16,23 +16,29 @@ function killProcesses(pid) { // Array of PIDs to kill, starting with the children const pidsToKill = children.map((p) => p.PID); - pidsToKill.push(pid); // Also kill the main process + logger.info("Pids to kill " + JSON.stringify(pidsToKill)); const killCommand = isWindows ? windowsKillCommand : unixKillCommand; - const joinedCommand = pidsToKill - .map((pid) => `${killCommand} ${pid}`) - .join('; '); // Separate commands with a semicolon, so they run in sequence even if one fails. Also works on Windows. - - exec(joinedCommand, (err) => { - if ( - err?.message?.includes(isWindows ? 'not found' : 'No such process') - ) { - return; // Ignore errors for processes that are already dead - } - reject(err); - }); - resolve(); + let errors = []; + for (const ppid of pidsToKill) { + logger.info("kill: " + ppid); + exec(`${killCommand} ${ppid}`, (err) => { + logger.error("Pids to kill error:" + err); + if ( + err?.message?.includes(isWindows ? 'not found' : 'No such process') + ) { + return; // Ignore errors for processes that are already dead + } + errors.push(err); + }); + } + + if (errors.length === 0) { + reject(errors); + + + } else resolve(); }); }); } diff --git a/frontend/package.json b/frontend/package.json index 74a21ceaf..0289f79d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,7 +45,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "bash -c 'NODE_ENV=production next build'", + "build": "bash -c \"DEV_RPC=$DEV_RPC FORK_URL=$FORK_URL NODE_ENV=production next build\"", "lint": "next lint", "test": "jest", "start": "next start" diff --git a/operate/ledger/__init__.py b/operate/ledger/__init__.py index 82807f71d..c54609833 100644 --- a/operate/ledger/__init__.py +++ b/operate/ledger/__init__.py @@ -39,13 +39,6 @@ SOLANA_RPC = os.environ.get("DEV_RPC", "https://api.mainnet-beta.solana.com") -print( - 11111111111111111111, - "DEV_RPC", - os.environ.get("DEV_RPC", "https://gnosis-rpc.publicnode.com"), - flush=True -) - PUBLIC_RPCS = { ChainType.ETHEREUM: ETHEREUM_PUBLIC_RPC, ChainType.GNOSIS: GNOSIS_PUBLIC_RPC, diff --git a/operate/services/deployment_runner.py b/operate/services/deployment_runner.py index 810cfbce3..42f0056fd 100644 --- a/operate/services/deployment_runner.py +++ b/operate/services/deployment_runner.py @@ -54,7 +54,6 @@ def stop(self) -> None: def _kill_process(pid: int) -> None: """Kill process.""" - print(f"Trying to kill process: {pid}") while True: if not psutil.pid_exists(pid=pid): return @@ -65,15 +64,8 @@ def _kill_process(pid: int) -> None: ): return try: - os.kill( - pid, - ( - signal.CTRL_C_EVENT # type: ignore - if platform.platform() == "Windows" - else signal.SIGKILL - ), - ) - except OSError: + process.kill() + except OSError as e: return time.sleep(1) @@ -94,7 +86,6 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta): def _run_aea(self, *args: str, cwd: Path) -> Any: """Run aea command.""" - print(222222222222222, self._aea_bin, args, flush=True) return self._run_cmd(args=[self._aea_bin, *args], cwd=cwd) @staticmethod @@ -243,7 +234,7 @@ def _start_agent(self) -> None: stderr=subprocess.DEVNULL, env={**os.environ, **env}, creationflags=( - 0x00000200 if platform.system() == "Windows" else 0 + 0x00000200 if platform.system() == "Windows" else 0 ), # Detach process from the main process ) (working_dir / "agent.pid").write_text( @@ -263,7 +254,7 @@ def _start_tendermint(self) -> None: stderr=subprocess.DEVNULL, env={**os.environ, **env}, creationflags=( - 0x00000200 if platform.system() == "Windows" else 0 + 0x00000200 if platform.system() == "Windows" else 0 ), # Detach process from the main process ) (working_dir / "tendermint.pid").write_text( From 0930ac24c107806f1f8aa308179e3878e4aad312 Mon Sep 17 00:00:00 2001 From: "Yuri (solarw) Turchenkov" Date: Tue, 13 Aug 2024 20:04:04 +0300 Subject: [PATCH 03/47] win: tendermint installation automated --- electron/install.js | 73 +++++++++++++++++++++++++++++++++++++++++++-- electron/main.js | 4 +-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/electron/install.js b/electron/install.js index 9da1dcad6..ef4554309 100644 --- a/electron/install.js +++ b/electron/install.js @@ -6,9 +6,9 @@ const process = require('process'); const axios = require('axios'); const { spawnSync } = require('child_process'); const { logger } = require('./logger'); - +const { execSync} = require('child_process'); const { paths } = require('./constants'); - +const homedir = os.homedir(); /** * current version of the pearl release * - use "" (nothing as a suffix) for latest release candidate, for example "0.1.0rc26" @@ -55,6 +55,21 @@ const TendermintUrls = { }, }; + +function execSyncExitCode(cmd) { + try { + execSync(cmd); + return 0; + } + catch (error) { + logger.electron(error.status); // Might be 127 in your example. + logger.electron(error.message); // Holds the message you typically want. + logger.electron(error.stderr.toString()); // Holds the stderr output. Use `.toString()`. + logger.electron(error.stdout.toString()); // Holds the stdout output. Use `.toString()`. + return error.status; + } +} + function getBinPath(command) { return spawnSync('/usr/bin/which', [command], { env: Env }) .stdout?.toString() @@ -79,6 +94,7 @@ function runCmdUnix(command, options) { logger.electron(`===== stderr ===== \n${output.stderr}`); } + function runSudoUnix(command, options) { let bin = getBinPath(command); if (!bin) { @@ -113,6 +129,10 @@ function isTendermintInstalledUnix() { return Boolean(getBinPath('tendermint')); } +function isTendermintInstalledWindows() { + return execSyncExitCode('tendermint --help') === 0; +} + async function downloadFile(url, dest) { const writer = fs.createWriteStream(dest); try { @@ -132,6 +152,39 @@ async function downloadFile(url, dest) { } } +async function installTendermintWindows() { + logger.electron(`Installing tendermint for ${os.platform()}-${process.arch}`); + const cwd = process.cwd(); + process.chdir(paths.tempDir); + + const url = TendermintUrls[os.platform()][process.arch]; + + logger.electron( + `Downloading ${url} to ${paths.tempDir}. This might take a while...`, + ); + await downloadFile(url, `${paths.tempDir}/tendermint.tar.gz`); + + logger.electron(`Installing tendermint binary`); + try { + execSync('tar -xvf tendermint.tar.gz'); + } catch (error){ + logger.electron(error.status); // Might be 127 in your example. + logger.electron(error.message); // Holds the message you typically want. + logger.electron(error.stderr.toString()); // Holds the stderr output. Use `.toString()`. + logger.electron(error.stdout.toString()); // Holds the stdout output. Use `.toString()`. + } + + const bin_dir = homedir + "//AppData//Local//Microsoft//WindowsApps//" + if (!Env.CI) { + if (!fs.existsSync(bin_dir)) { + fs.mkdirSync(bin_dir, {recursive: true}); + } + fs.copyFileSync("tendermint.exe", bin_dir + "tendermint.exe"); + } + process.chdir(cwd); +} + + async function installTendermintUnix() { logger.electron(`Installing tendermint for ${os.platform()}-${process.arch}`); const cwd = process.cwd(); @@ -202,8 +255,24 @@ async function setupUbuntu(ipcChannel) { } } + + +async function setupWindows(ipcChannel) { + logger.electron('Creating required directories'); + await createDirectory(`${paths.dotOperateDirectory}`); + await createDirectory(`${paths.tempDir}`); + + logger.electron('Checking tendermint installation: ' + isTendermintInstalledWindows()); + if (!isTendermintInstalledWindows()) { + ipcChannel.send('response', 'Installing tendermint'); + logger.electron('Installing tendermint'); + await installTendermintWindows(); + } +} + module.exports = { setupDarwin, setupUbuntu, + setupWindows, Env, }; diff --git a/electron/main.js b/electron/main.js index 9b41fb84c..62392c690 100644 --- a/electron/main.js +++ b/electron/main.js @@ -17,7 +17,7 @@ const http = require('http'); const AdmZip = require('adm-zip'); const { TRAY_ICONS, TRAY_ICONS_PATHS } = require('./icons'); -const { setupDarwin, setupUbuntu, Env } = require('./install'); +const { setupDarwin, setupUbuntu, setupWindows, Env } = require('./install'); const { paths } = require('./constants'); const { killProcesses } = require('./processes'); @@ -444,7 +444,7 @@ ipcMain.on('check', async function (event, _argument) { if (platform === 'darwin') { await setupDarwin(event.sender); } else if (platform === 'win32') { - // TODO + await setupWindows(event.sender); } else { await setupUbuntu(event.sender); } From e42ba6e26d997f056298a65086af8fe3307cf5c3 Mon Sep 17 00:00:00 2001 From: "Yuri (solarw) Turchenkov" Date: Mon, 19 Aug 2024 17:33:03 +0300 Subject: [PATCH 04/47] helthchecker env vars to switch it off. small refactoring --- operate/cli.py | 14 +++++--- operate/services/health_checker.py | 56 ++++++++++++++++++------------ 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/operate/cli.py b/operate/cli.py index 7d3292760..081010101 100644 --- a/operate/cli.py +++ b/operate/cli.py @@ -138,22 +138,26 @@ def json(self) -> dict: "home": str(self._path), } - + def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-statements home: t.Optional[Path] = None, ) -> FastAPI: """Create FastAPI object.""" + HEALTH_CHECKER_OFF = os.environ.get("HEALTH_CHECKER_OFF", "0") == "1" + number_of_fails = int(os.environ.get("HEALTH_CHECKER_TRIES", "5")) + logger = setup_logger(name="operate") + if HEALTH_CHECKER_OFF: + logger.warning("healthchecker is off!!!") operate = OperateApp(home=home, logger=logger) funding_jobs: t.Dict[str, asyncio.Task] = {} - health_checker = HealthChecker(operate.service_manager()) + health_checker = HealthChecker(operate.service_manager(), number_of_fails=number_of_fails) # Create shutdown endpoint shutdown_endpoint = uuid.uuid4().hex (operate._path / "operate.kill").write_text( # pylint: disable=protected-access shutdown_endpoint ) - thread_pool_executor = ThreadPoolExecutor() async def run_in_executor(fn: t.Callable, *args: t.Any) -> t.Any: @@ -188,7 +192,9 @@ def schedule_healthcheck_job( service: str, ) -> None: """Schedule a healthcheck job.""" - health_checker.start_for_service(service) + if not HEALTH_CHECKER_OFF: + # dont start health checker if it's switched off + health_checker.start_for_service(service) def cancel_funding_job(service: str) -> None: """Cancel funding job.""" diff --git a/operate/services/health_checker.py b/operate/services/health_checker.py index e1979be13..ba1c5d57c 100644 --- a/operate/services/health_checker.py +++ b/operate/services/health_checker.py @@ -34,19 +34,28 @@ class HealthChecker: """Health checker manager.""" - SLEEP_PERIOD = 30 - PORT_UP_TIMEOUT = 120 # seconds + SLEEP_PERIOD_DEFAULT = 30 + PORT_UP_TIMEOUT_DEFAULT = 120 # seconds + NUMBER_OF_FAILS_DEFAULT = 5 - def __init__(self, service_manager: ServiceManager) -> None: + def __init__( + self, + service_manager: ServiceManager, + port_up_timeout: int | None = None, + sleep_period: int | None = None, + number_of_fails: int | None = None, + ) -> None: """Init the healtch checker.""" self._jobs: t.Dict[str, asyncio.Task] = {} self.logger = setup_logger(name="operate.health_checker") - self.logger.info("[HEALTCHECKER]: created") self._service_manager = service_manager + self.port_up_timeout = port_up_timeout or self.PORT_UP_TIMEOUT_DEFAULT + self.sleep_period = sleep_period or self.SLEEP_PERIOD_DEFAULT + self.number_of_fails = number_of_fails or self.NUMBER_OF_FAILS_DEFAULT def start_for_service(self, service: str) -> None: """Start for a specific service.""" - self.logger.info(f"[HEALTCHECKER]: Starting healthcheck job for {service}") + self.logger.info(f"[HEALTH_CHECKER]: Starting healthcheck job for {service}") if service in self._jobs: self.stop_for_service(service=service) @@ -62,12 +71,12 @@ def stop_for_service(self, service: str) -> None: if service not in self._jobs: return self.logger.info( - f"[HEALTCHECKER]: Cancelling existing healthcheck_jobs job for {service}" + f"[HEALTH_CHECKER]: Cancelling existing healthcheck_jobs job for {service}" ) status = self._jobs[service].cancel() if not status: self.logger.info( - f"[HEALTCHECKER]: Healthcheck job cancellation for {service} failed" + f"[HEALTH_CHECKER]: Healthcheck job cancellation for {service} failed" ) @staticmethod @@ -90,22 +99,22 @@ async def healthcheck_job( try: self.logger.info( - f"[HEALTCHECKER] Start healthcheck job for service: {service}" + f"[HEALTH_CHECKER] Start healthcheck job for service: {service}" ) async def _wait_for_port(sleep_period: int = 15) -> None: - self.logger.info("[HEALTCHECKER]: wait port is up") + self.logger.info("[HEALTH_CHECKER]: wait port is up") while True: try: await self.check_service_health(service) - self.logger.info("[HEALTCHECKER]: port is UP") + self.logger.info("[HEALTH_CHECKER]: port is UP") return except aiohttp.ClientConnectionError: - self.logger.error("[HEALTCHECKER]: error connecting http port") + self.logger.error("[HEALTH_CHECKER]: error connecting http port") await asyncio.sleep(sleep_period) async def _check_port_ready( - timeout: int = self.PORT_UP_TIMEOUT, sleep_period: int = 15 + timeout: int = self.port_up_timeout, sleep_period: int = 15 ) -> bool: try: await asyncio.wait_for( @@ -116,7 +125,7 @@ async def _check_port_ready( return False async def _check_health( - number_of_fails: int = 5, sleep_period: int = self.SLEEP_PERIOD + number_of_fails: int = 5, sleep_period: int = self.sleep_period ) -> None: fails = 0 while True: @@ -125,24 +134,24 @@ async def _check_health( healthy = await self.check_service_health(service) except aiohttp.ClientConnectionError: self.logger.info( - f"[HEALTCHECKER] {service} port read failed. restart" + f"[HEALTH_CHECKER] {service} port read failed. restart" ) return if not healthy: fails += 1 self.logger.info( - f"[HEALTCHECKER] {service} not healthy for {fails} time in a row" + f"[HEALTH_CHECKER] {service} not healthy for {fails} time in a row" ) else: - self.logger.info(f"[HEALTCHECKER] {service} is HEALTHY") + self.logger.info(f"[HEALTH_CHECKER] {service} is HEALTHY") # reset fails if comes healty fails = 0 if fails >= number_of_fails: # too much fails, exit self.logger.error( - f"[HEALTCHECKER] {service} failed {fails} times in a row. restart" + f"[HEALTH_CHECKER] {service} failed {fails} times in a row. restart" ) return await asyncio.sleep(sleep_period) @@ -162,17 +171,20 @@ def _do_restart() -> None: # upper cycle while True: - self.logger.info(f"[HEALTCHECKER] {service} wait for port ready") - if await _check_port_ready(timeout=self.PORT_UP_TIMEOUT): + self.logger.info(f"[HEALTH_CHECKER] {service} wait for port ready") + if await _check_port_ready(timeout=self.port_up_timeout): # blocking till restart needed self.logger.info( - f"[HEALTCHECKER] {service} port is ready, checking health every {self.SLEEP_PERIOD}" + f"[HEALTH_CHECKER] {service} port is ready, checking health every {self.sleep_period}" + ) + await _check_health( + number_of_fails=self.number_of_fails, + sleep_period=self.sleep_period, ) - await _check_health(sleep_period=self.SLEEP_PERIOD) else: self.logger.info( - "[HEALTCHECKER] port not ready within timeout. restart deployment" + "[HEALTH_CHECKER] port not ready within timeout. restart deployment" ) # perform restart From 1d32bb94461eb723dde8158ae5ea30358673d559 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 21 Aug 2024 13:55:25 +0100 Subject: [PATCH 05/47] refactor: seperate the funding section component from the main page section --- .../MainPage/sections/AddFundsSection.tsx | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/frontend/components/MainPage/sections/AddFundsSection.tsx b/frontend/components/MainPage/sections/AddFundsSection.tsx index 97bf85641..d61970e7f 100644 --- a/frontend/components/MainPage/sections/AddFundsSection.tsx +++ b/frontend/components/MainPage/sections/AddFundsSection.tsx @@ -35,23 +35,6 @@ const CustomizedCardSection = styled(CardSection)<{ border?: boolean }>` export const AddFundsSection = () => { const [isAddFundsVisible, setIsAddFundsVisible] = useState(false); - const { masterSafeAddress } = useWallet(); - - const fundingAddress: Address | undefined = masterSafeAddress; - - const truncatedFundingAddress: string | undefined = useMemo( - () => fundingAddress && truncateAddress(fundingAddress), - [fundingAddress], - ); - - const handleCopyAddress = useCallback( - () => - fundingAddress && - copyToClipboard(fundingAddress).then(() => - message.success('Copied successfully!'), - ), - [fundingAddress], - ); return ( <> @@ -75,17 +58,38 @@ export const AddFundsSection = () => { - {isAddFundsVisible && ( - <> - - - - - )} + {isAddFundsVisible && } + + ); +}; + +export const OpenAddFundsSection = () => { + const { masterSafeAddress } = useWallet(); + + const fundingAddress: Address | undefined = masterSafeAddress; + + const truncatedFundingAddress: string | undefined = useMemo( + () => fundingAddress && truncateAddress(fundingAddress), + [fundingAddress], + ); + + const handleCopyAddress = useCallback( + () => + fundingAddress && + copyToClipboard(fundingAddress).then(() => + message.success('Copied successfully!'), + ), + [fundingAddress], + ); + return ( + <> + + + ); }; From c585d5994b73bc68acb15ab4d9b93b7ee0ec6406 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 21 Aug 2024 13:59:56 +0100 Subject: [PATCH 06/47] feat: add funding section to beta contract section --- .../StakingContractSection/index.tsx | 159 ++++++++++-------- 1 file changed, 91 insertions(+), 68 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index b8fdf664c..725d62ac7 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -1,7 +1,8 @@ import { Button, Flex, Popover, theme, Typography } from 'antd'; -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { DeploymentStatus } from '@/client'; +import { OpenAddFundsSection } from '@/components/MainPage/sections/AddFundsSection'; import { CardSection } from '@/components/styled/CardSection'; import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta'; import { UNICODE_SYMBOLS } from '@/constants/symbols'; @@ -59,8 +60,12 @@ export const StakingContractSection = ({ contractAddress: Address; }) => { const { goto } = usePageState(); - const { setServiceStatus, serviceStatus, setIsServicePollingPaused } = - useServices(); + const { + setServiceStatus, + serviceStatus, + setIsServicePollingPaused, + updateServiceStatus, + } = useServices(); const { serviceTemplate } = useServiceTemplates(); const { setMigrationModalOpen } = useModals(); const { activeStakingProgram, defaultStakingProgram, updateStakingProgram } = @@ -69,6 +74,7 @@ export const StakingContractSection = ({ const { token } = useToken(); const { totalOlasBalance, isBalanceLoaded } = useBalance(); const { isServiceStakedForMinimumDuration } = useStakingContractInfo(); + const [isFundingSectionOpen, setIsFundingSectionOpen] = useState(false); const stakingContractInfoForStakingProgram = stakingContractInfoRecord?.[stakingProgram]; @@ -213,40 +219,41 @@ export const StakingContractSection = ({ }, [activeStakingProgram, defaultStakingProgram, stakingProgram]); return ( - - {/* Title */} - - {`${activeStakingProgramMeta.name} contract`} - {/* TODO: pass `status` attribute */} - - {!isSelected && ( - // here instead of isSelected we should check that the contract is not the old staking contract - // but the one from staking factory (if we want to open govern) - - Contract details {UNICODE_SYMBOLS.EXTERNAL_LINK} - - )} - - - {/* TODO: fix */} - - {/* Contract details + <> + + {/* Title */} + + {`${activeStakingProgramMeta.name} contract`} + + {!isSelected && ( + // here instead of isSelected we should check that the contract is not the old staking contract + // but the one from staking factory (if we want to open govern) + + Contract details {UNICODE_SYMBOLS.EXTERNAL_LINK} + + )} + + + {/* TODO: redisplay once bugs resolved */} + + {/* Contract details {stakingContractInfo?.availableRewards && ( )} */} - {cantMigrateAlert} - {/* Switch to program button */} - {!isSelected && ( - + {cantMigrateAlert} + {/* Switch to program button */} + { + <> + + + + + } + {stakingProgram === StakingProgram.Beta && ( - + )} + + {stakingProgram === StakingProgram.Beta && isFundingSectionOpen && ( + )} - + ); }; From 15816fe07e1ed95b7474f9a634213d4d00e00632 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 21 Aug 2024 17:05:58 +0100 Subject: [PATCH 07/47] fix: provider order --- frontend/pages/_app.tsx | 52 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/frontend/pages/_app.tsx b/frontend/pages/_app.tsx index 42657f818..9227cb596 100644 --- a/frontend/pages/_app.tsx +++ b/frontend/pages/_app.tsx @@ -28,19 +28,19 @@ export default function App({ Component, pageProps }: AppProps) { }, []); return ( - - - - - - - - - - - - - + + + + + + + + + + + + + {isMounted ? ( @@ -50,18 +50,18 @@ export default function App({ Component, pageProps }: AppProps) { ) : null} - - - - - - - - - - - - - + + + + + + + + + + + + + ); } From ac5c6ca0f00fd9a003302869de7fc2c88d3c0b94 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 21 Aug 2024 17:23:06 +0100 Subject: [PATCH 08/47] chore: bump 116 for testing staging with rewards fix --- electron/install.js | 2 +- package.json | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/electron/install.js b/electron/install.js index 2c3de2faa..efebf9180 100644 --- a/electron/install.js +++ b/electron/install.js @@ -14,7 +14,7 @@ const { paths } = require('./constants'); * - use "" (nothing as a suffix) for latest release candidate, for example "0.1.0rc26" * - use "alpha" for alpha release, for example "0.1.0rc26-alpha" */ -const OlasMiddlewareVersion = '0.1.0rc115'; +const OlasMiddlewareVersion = '0.1.0rc116'; const path = require('path'); const { app } = require('electron'); diff --git a/package.json b/package.json index c5afe288e..5eaea98ea 100644 --- a/package.json +++ b/package.json @@ -58,5 +58,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc115" + "version": "0.1.0-rc116" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9a58deb5d..db96e9913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc115" +version = "0.1.0-rc116" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From 973b5d2884a805b8ec0e2c42488c831bd7557e37 Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:51:47 +0100 Subject: [PATCH 09/47] Bump --- electron/install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/install.js b/electron/install.js index efebf9180..a43856a9b 100644 --- a/electron/install.js +++ b/electron/install.js @@ -14,7 +14,7 @@ const { paths } = require('./constants'); * - use "" (nothing as a suffix) for latest release candidate, for example "0.1.0rc26" * - use "alpha" for alpha release, for example "0.1.0rc26-alpha" */ -const OlasMiddlewareVersion = '0.1.0rc116'; +const OlasMiddlewareVersion = '0.1.0rc117'; const path = require('path'); const { app } = require('electron'); From 21a035e0d052cbfc53ef65c26dcd1908d00372a4 Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:52:06 +0100 Subject: [PATCH 10/47] Bump --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5eaea98ea..b50e8fe5f 100644 --- a/package.json +++ b/package.json @@ -58,5 +58,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc116" -} \ No newline at end of file + "version": "0.1.0-rc117" +} From 219814e19127be0b3b945fab8cdec7eed53ab4c4 Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:52:22 +0100 Subject: [PATCH 11/47] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index db96e9913..3c4bce6cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc116" +version = "0.1.0-rc117" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From da7690878628a572c5e234360bd5456740efcb6b Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Thu, 22 Aug 2024 15:51:03 +0200 Subject: [PATCH 12/47] fix: Add subgraph check of agent ID --- operate/services/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operate/services/protocol.py b/operate/services/protocol.py index 120355c94..9a7fbffe8 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -974,7 +974,7 @@ def get_mint_tx_data( # pylint: disable=too-many-arguments ) .load_metadata() .verify_nft(nft=nft) - #.verify_service_dependencies(agent_id=agent_id) # TODO add this check once subgraph production indexes agent 25 + .verify_service_dependencies(agent_id=agent_id) .publish_metadata() ) instance = registry_contracts.service_manager.get_instance( From 6ccf85618707d54be434ee1fcfdc73a7537ce8b8 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Thu, 22 Aug 2024 16:14:05 +0200 Subject: [PATCH 13/47] chore: linters (black) --- operate/services/manage.py | 215 ++++++++++++++++++++++++----------- operate/services/protocol.py | 60 +++++----- operate/services/service.py | 56 +++++---- 3 files changed, 211 insertions(+), 120 deletions(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index 6acf0b462..a87f9e57c 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -159,7 +159,9 @@ def load_or_create( service = Service.load(path=path) if service_template is not None: - service.update_user_params_from_template(service_template=service_template) + service.update_user_params_from_template( + service_template=service_template + ) return service @@ -410,7 +412,9 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to # TODO fix this os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc - os.environ["OPEN_AUTONOMY_SUBGRAPH_URL"] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" + os.environ[ + "OPEN_AUTONOMY_SUBGRAPH_URL" + ] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" current_agent_id = None if chain_data.token > -1: @@ -425,7 +429,9 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to if user_params.use_staking: staking_params = sftxb.get_staking_params( - staking_contract=STAKING[ledger_config.chain][user_params.staking_program_id], + staking_contract=STAKING[ledger_config.chain][ + user_params.staking_program_id + ], ) else: # TODO fix this - using pearl beta params staking_params = dict( @@ -434,7 +440,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to staking_token="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f", # nosec service_registry_token_utility="0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8", # nosec min_staking_deposit=20000000000000000000, - activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8" # nosec + activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8", # nosec ) if user_params.use_staking: @@ -448,7 +454,8 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to OnChainState.PRE_REGISTRATION, ): required_olas = ( - staking_params["min_staking_deposit"] + staking_params["min_staking_deposit"] # bond = staking + staking_params["min_staking_deposit"] + + staking_params["min_staking_deposit"] # bond = staking ) elif chain_data.on_chain_state == OnChainState.ACTIVE_REGISTRATION: required_olas = staking_params["min_staking_deposit"] @@ -470,13 +477,21 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to ) on_chain_hash = self._get_on_chain_hash(chain_config=chain_config) - is_first_mint = self._get_on_chain_state(chain_config=chain_config) == OnChainState.NON_EXISTENT + is_first_mint = ( + self._get_on_chain_state(chain_config=chain_config) + == OnChainState.NON_EXISTENT + ) is_update = ( (not is_first_mint) and (on_chain_hash is not None) - and (on_chain_hash != service.hash or current_agent_id != staking_params["agent_ids"][0]) + and ( + on_chain_hash != service.hash + or current_agent_id != staking_params["agent_ids"][0] + ) + ) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb ) - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) self.logger.info(f"{current_staking_program=}") self.logger.info(f"{user_params.staking_program_id=}") @@ -488,13 +503,13 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to self.logger.info(f"{is_update=}") if is_update: - self._terminate_service_on_chain_from_safe( - hash=hash, - chain_id=chain_id - ) + self._terminate_service_on_chain_from_safe(hash=hash, chain_id=chain_id) # Update service - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.PRE_REGISTRATION: + if ( + self._get_on_chain_state(chain_config=chain_config) + == OnChainState.PRE_REGISTRATION + ): self.logger.info("Updating service") receipt = ( sftxb.new_tx() @@ -533,10 +548,14 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to service.store() # Mint service - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.NON_EXISTENT: - + if ( + self._get_on_chain_state(chain_config=chain_config) + == OnChainState.NON_EXISTENT + ): if user_params.use_staking and not sftxb.staking_slots_available( - staking_contract=STAKING[ledger_config.chain][user_params.staking_program_id] + staking_contract=STAKING[ledger_config.chain][ + user_params.staking_program_id + ] ): raise ValueError("No staking slots available") @@ -578,7 +597,10 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to chain_data.on_chain_state = OnChainState.PRE_REGISTRATION service.store() - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.PRE_REGISTRATION: + if ( + self._get_on_chain_state(chain_config=chain_config) + == OnChainState.PRE_REGISTRATION + ): cost_of_bond = staking_params["min_staking_deposit"] if user_params.use_staking: token_utility = staking_params["service_registry_token_utility"] @@ -628,7 +650,10 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to chain_data.on_chain_state = OnChainState.ACTIVE_REGISTRATION service.store() - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.ACTIVE_REGISTRATION: + if ( + self._get_on_chain_state(chain_config=chain_config) + == OnChainState.ACTIVE_REGISTRATION + ): cost_of_bond = user_params.cost_of_bond if user_params.use_staking: token_utility = staking_params["service_registry_token_utility"] @@ -682,7 +707,10 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to chain_data.on_chain_state = OnChainState.FINISHED_REGISTRATION service.store() - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.FINISHED_REGISTRATION: + if ( + self._get_on_chain_state(chain_config=chain_config) + == OnChainState.FINISHED_REGISTRATION + ): self.logger.info("Deploying service") reuse_multisig = True @@ -763,7 +791,9 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non chain_data.on_chain_state = OnChainState(info["service_state"]) # Determine if the service is staked in a known staking program - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) is_staked = current_staking_program is not None can_unstake = False @@ -780,7 +810,9 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non # Unstake the service if applies if is_staked and can_unstake: - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.unstake_service_on_chain_from_safe( + hash=hash, chain_id=chain_id, staking_program_id=current_staking_program + ) if self._get_on_chain_state(chain_config) in ( OnChainState.ACTIVE_REGISTRATION, @@ -813,12 +845,18 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non service_id=chain_data.token, # noqa: E800 multisig=chain_data.multisig, # TODO this can be read from the registry owner_key=str( - self.keys_manager.get(key=current_safe_owners[0]).private_key # TODO allow multiple owners + self.keys_manager.get( + key=current_safe_owners[0] + ).private_key # TODO allow multiple owners ), # noqa: E800 - new_owner_address=wallet.safe if wallet.safe else wallet.crypto.address # TODO it should always be safe address + new_owner_address=wallet.safe + if wallet.safe + else wallet.crypto.address, # TODO it should always be safe address ) # noqa: E800 - def _get_current_staking_program(self, chain_data, ledger_config, sftxb) -> t.Optional[str]: + def _get_current_staking_program( + self, chain_data, ledger_config, sftxb + ) -> t.Optional[str]: if chain_data.token == NON_EXISTENT_TOKEN: return None @@ -859,7 +897,9 @@ def unbond_service_on_chain(self, hash: str) -> None: service.chain_data.on_chain_state = OnChainState.UNBONDED service.store() - def stake_service_on_chain(self, hash: str, chain_id: int, staking_program_id: str) -> None: + def stake_service_on_chain( + self, hash: str, chain_id: int, staking_program_id: str + ) -> None: """ Stake service on-chain @@ -888,16 +928,28 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc # Determine if the service is staked in a known staking program - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) is_staked = current_staking_program is not None - current_staking_contract = STAKING[ledger_config.chain][current_staking_program] if is_staked else None + current_staking_contract = ( + STAKING[ledger_config.chain][current_staking_program] if is_staked else None + ) # perform the unstaking flow if necessary if is_staked: - can_unstake = sftxb.can_unstake(chain_config.chain_data.token, current_staking_contract) + can_unstake = sftxb.can_unstake( + chain_config.chain_data.token, current_staking_contract + ) if not chain_config.chain_data.user_params.use_staking and can_unstake: - self.logger.info(f"Use staking is set to false, but service {chain_config.chain_data.token} is staked and can be unstaked. Unstaking...") - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.logger.info( + f"Use staking is set to false, but service {chain_config.chain_data.token} is staked and can be unstaked. Unstaking..." + ) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) info = sftxb.info(token_id=chain_config.chain_data.token) chain_config.chain_data.on_chain_state = OnChainState(info["service_state"]) @@ -907,21 +959,43 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: ) if staking_state == StakingState.EVICTED and can_unstake: - self.logger.info(f"Service {chain_config.chain_data.token} has been evicted and can be unstaked. Unstaking...") - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.logger.info( + f"Service {chain_config.chain_data.token} has been evicted and can be unstaked. Unstaking..." + ) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) - if staking_state == StakingState.STAKED and can_unstake and not sftxb.staking_rewards_available(current_staking_contract): + if ( + staking_state == StakingState.STAKED + and can_unstake + and not sftxb.staking_rewards_available(current_staking_contract) + ): self.logger.info( f"There are no rewards available, service {chain_config.chain_data.token} " f"is already staked and can be unstaked. Unstaking..." ) - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) - if staking_state == StakingState.STAKED and current_staking_program != target_staking_contract and can_unstake: + if ( + staking_state == StakingState.STAKED + and current_staking_program != target_staking_contract + and can_unstake + ): self.logger.info( f"{chain_config.chain_data.token} is staked in a different staking program. Unstaking..." ) - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) staking_state = sftxb.staking_status( service_id=chain_config.chain_data.token, @@ -929,12 +1003,18 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: ) self.logger.info("Checking conditions to stake.") - staking_rewards_available = sftxb.staking_rewards_available(target_staking_contract) + staking_rewards_available = sftxb.staking_rewards_available( + target_staking_contract + ) staking_slots_available = sftxb.staking_slots_available(target_staking_contract) on_chain_state = self._get_on_chain_state(chain_config=chain_config) - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) - - self.logger.info(f"use_staking={chain_config.chain_data.user_params.use_staking}") + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) + + self.logger.info( + f"use_staking={chain_config.chain_data.user_params.use_staking}" + ) self.logger.info(f"{staking_state=}") self.logger.info(f"{staking_rewards_available=}") self.logger.info(f"{staking_slots_available=}") @@ -943,19 +1023,17 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: self.logger.info(f"{target_staking_program=}") if ( - chain_config.chain_data.user_params.use_staking - and staking_state == StakingState.UNSTAKED - and staking_rewards_available - and staking_slots_available - and on_chain_state == OnChainState.DEPLOYED + chain_config.chain_data.user_params.use_staking + and staking_state == StakingState.UNSTAKED + and staking_rewards_available + and staking_slots_available + and on_chain_state == OnChainState.DEPLOYED ): self.logger.info(f"Approving staking: {chain_config.chain_data.token}") sftxb.new_tx().add( sftxb.get_staking_approval_data( service_id=chain_config.chain_data.token, - service_registry=CONTRACTS[ledger_config.chain][ - "service_registry" - ], + service_registry=CONTRACTS[ledger_config.chain]["service_registry"], staking_contract=target_staking_contract, ) ).settle() @@ -970,7 +1048,9 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: chain_config.chain_data.staked = True service.store() - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) self.logger.info(f"{target_staking_program=}") self.logger.info(f"{current_staking_program=}") @@ -1007,7 +1087,9 @@ def unstake_service_on_chain(self, hash: str) -> None: service.chain_data.staked = False service.store() - def unstake_service_on_chain_from_safe(self, hash: str, chain_id: str, staking_program_id: str) -> None: + def unstake_service_on_chain_from_safe( + self, hash: str, chain_id: str, staking_program_id: str + ) -> None: """ Unbond service on-chain @@ -1029,9 +1111,7 @@ def unstake_service_on_chain_from_safe(self, hash: str, chain_id: str, staking_p service_id=chain_data.token, staking_contract=STAKING[ledger_config.chain][staking_program_id], ) - self.logger.info( - f"Staking status for service {chain_data.token}: {state}" - ) + self.logger.info(f"Staking status for service {chain_data.token}: {state}") if state not in {StakingState.STAKED, StakingState.EVICTED}: self.logger.info("Cannot unstake service, it's not staked") chain_data.staked = False @@ -1065,10 +1145,11 @@ def fund_service( # pylint: disable=too-many-arguments ledger_config = chain_config.ledger_config chain_data = chain_config.chain_data wallet = self.wallet_manager.load(ledger_config.type) - ledger_api = wallet.ledger_api(chain_type=ledger_config.chain, rpc=rpc if rpc else ledger_config.rpc) + ledger_api = wallet.ledger_api( + chain_type=ledger_config.chain, rpc=rpc if rpc else ledger_config.rpc + ) agent_fund_threshold = ( - agent_fund_threshold - or chain_data.user_params.fund_requirements.agent + agent_fund_threshold or chain_data.user_params.fund_requirements.agent ) for key in service.keys: @@ -1078,8 +1159,7 @@ def fund_service( # pylint: disable=too-many-arguments if agent_balance < agent_fund_threshold: self.logger.info("Funding agents") to_transfer = ( - agent_topup - or chain_data.user_params.fund_requirements.agent + agent_topup or chain_data.user_params.fund_requirements.agent ) self.logger.info(f"Transferring {to_transfer} units to {key.address}") wallet.transfer( @@ -1097,9 +1177,7 @@ def fund_service( # pylint: disable=too-many-arguments self.logger.info(f"Required balance: {safe_fund_treshold}") if safe_balance < safe_fund_treshold: self.logger.info("Funding safe") - to_transfer = ( - safe_topup or chain_data.user_params.fund_requirements.safe - ) + to_transfer = safe_topup or chain_data.user_params.fund_requirements.safe self.logger.info( f"Transferring {to_transfer} units to {chain_data.multisig}" ) @@ -1177,12 +1255,9 @@ def update_service( """Update a service.""" self.logger.info("-----Entering update local service-----") - old_service = self.load_or_create( - hash=old_hash - ) + old_service = self.load_or_create(hash=old_hash) new_service = self.load_or_create( - hash=new_hash, - service_template=service_template + hash=new_hash, service_template=service_template ) new_service.keys = old_service.keys # new_Service.home_chain_id = old_service.home_chain_id @@ -1193,9 +1268,13 @@ def update_service( new_service.chain_configs = {} for chain_id, config in old_service.chain_configs.items(): - new_service.chain_configs[chain_id] = config + new_service.chain_configs[chain_id] = config if service_template: - new_service.chain_configs[chain_id].chain_data.user_params = OnChainUserParams.from_json(service_template["configurations"][chain_id]) + new_service.chain_configs[ + chain_id + ].chain_data.user_params = OnChainUserParams.from_json( + service_template["configurations"][chain_id] + ) new_service.store() diff --git a/operate/services/protocol.py b/operate/services/protocol.py index 9a7fbffe8..f58f5b921 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -242,7 +242,7 @@ def service_info(self, staking_contract: str, service_id: int) -> dict: staking_contract, service_id, ).get("data") - + def agent_ids(self, staking_contract: str) -> t.List[int]: instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, @@ -536,7 +536,6 @@ def owner_of(self, token_id: int) -> str: chain_type=self.chain_type ) - def info(self, token_id: int) -> t.Dict: """Get service info.""" self._patch() @@ -574,7 +573,6 @@ def info(self, token_id: int) -> t.Dict: instances=instances, ) - def get_service_safe_owners(self, service_id: int) -> t.List[str]: """Get list of owners.""" ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( @@ -599,13 +597,8 @@ def get_service_safe_owners(self, service_id: int) -> t.List[str]: contract_address=multisig_address, ).get("owners", []) - def swap( # pylint: disable=too-many-arguments,too-many-locals - self, - service_id: int, - multisig: str, - owner_key: str, - new_owner_address: str + self, service_id: int, multisig: str, owner_key: str, new_owner_address: str ) -> None: """Swap safe owner.""" logging.info(f"Swapping safe for service {service_id} [{multisig}]...") @@ -634,9 +627,7 @@ def swap( # pylint: disable=too-many-arguments,too-many-locals ledger_api=manager.ledger_api, contract_address=multisig, old_owner=manager.ledger_api.api.to_checksum_address(owner_to_swap), - new_owner=manager.ledger_api.api.to_checksum_address( - new_owner_address - ), + new_owner=manager.ledger_api.api.to_checksum_address(new_owner_address), ).get("data") multisend_txs.append( { @@ -720,6 +711,7 @@ def staking_rewards_available(self, staking_contract: str) -> bool: ) return available_rewards > 0 + class OnChainManager(_ChainUtil): """On chain service management.""" @@ -1003,9 +995,9 @@ def get_mint_tx_data( # pylint: disable=too-many-arguments [agent_id], [[number_of_slots, cost_of_bond]], threshold, - update_token + update_token, ], - ) + ) return { "to": self.contracts["service_manager"], @@ -1139,7 +1131,11 @@ def get_deploy_data_from_safe( ) approve_hash_message = None if reuse_multisig: - _deployment_payload, approve_hash_message, error = get_reuse_multisig_from_safe_payload( + ( + _deployment_payload, + approve_hash_message, + error, + ) = get_reuse_multisig_from_safe_payload( ledger_api=self.ledger_api, chain_type=self.chain_type, service_id=service_id, @@ -1334,7 +1330,7 @@ def get_staking_params(self, staking_contract: str) -> t.Dict: staking_token=staking_token, service_registry_token_utility=service_registry_token_utility, min_staking_deposit=min_staking_deposit, - activity_checker=activity_checker + activity_checker=activity_checker, ) def can_unstake(self, service_id: int, staking_contract: str) -> bool: @@ -1358,26 +1354,25 @@ def get_swap_data(self, service_id: int, multisig: str, owner_key: str) -> t.Dic raise NotImplementedError() - def get_packed_signature_for_approved_hash(owners: t.Tuple[str]) -> bytes: - """Get the packed signatures.""" - sorted_owners = sorted(owners, key=str.lower) - signatures = b'' - for owner in sorted_owners: - # Convert address to bytes and ensure it is 32 bytes long (left-padded with zeros) - r_bytes = to_bytes(hexstr=owner[2:].rjust(64, '0')) + """Get the packed signatures.""" + sorted_owners = sorted(owners, key=str.lower) + signatures = b"" + for owner in sorted_owners: + # Convert address to bytes and ensure it is 32 bytes long (left-padded with zeros) + r_bytes = to_bytes(hexstr=owner[2:].rjust(64, "0")) - # `s` as 32 zero bytes - s_bytes = b'\x00' * 32 + # `s` as 32 zero bytes + s_bytes = b"\x00" * 32 - # `v` as a single byte - v_bytes = to_bytes(1) + # `v` as a single byte + v_bytes = to_bytes(1) - # Concatenate r, s, and v to form the packed signature - packed_signature = r_bytes + s_bytes + v_bytes - signatures += packed_signature + # Concatenate r, s, and v to form the packed signature + packed_signature = r_bytes + s_bytes + v_bytes + signatures += packed_signature - return signatures + return signatures def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals @@ -1477,7 +1472,7 @@ def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals contract_address=multisend_address, txs=txs, ) - signature_bytes = get_packed_signature_for_approved_hash(owners=(master_safe, )) + signature_bytes = get_packed_signature_for_approved_hash(owners=(master_safe,)) safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( ledger_api=ledger_api, @@ -1517,4 +1512,3 @@ def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals ) payload = multisig_address + safe_exec_data[2:] return payload, approve_hash_message, None - diff --git a/operate/services/service.py b/operate/services/service.py index ea6c563ca..10aa4d3a8 100644 --- a/operate/services/service.py +++ b/operate/services/service.py @@ -656,15 +656,19 @@ class Service(LocalResource): @classmethod def migrate_format(cls, path: Path) -> None: """Migrate the JSON file format if needed.""" - file_path = path / Service._file if Service._file is not None and path.name != Service._file else path - - with open(file_path, 'r', encoding='utf-8') as file: + file_path = ( + path / Service._file + if Service._file is not None and path.name != Service._file + else path + ) + + with open(file_path, "r", encoding="utf-8") as file: data = json.load(file) - - if 'version' in data: + + if "version" in data: # Data is already in the new format return - + # Migrate from old format to new format new_data = { "version": 2, @@ -676,30 +680,42 @@ def migrate_format(cls, path: Path) -> None: "ledger_config": { "rpc": data.get("ledger_config", {}).get("rpc"), "type": data.get("ledger_config", {}).get("type"), - "chain": data.get("ledger_config", {}).get("chain") + "chain": data.get("ledger_config", {}).get("chain"), }, "chain_data": { "instances": data.get("chain_data", {}).get("instances", []), "token": data.get("chain_data", {}).get("token"), "multisig": data.get("chain_data", {}).get("multisig"), "staked": data.get("chain_data", {}).get("staked", False), - "on_chain_state": data.get("chain_data", {}).get("on_chain_state", 3), + "on_chain_state": data.get("chain_data", {}).get( + "on_chain_state", 3 + ), "user_params": { "staking_program_id": "pearl_alpha", - "nft": data.get("chain_data", {}).get("user_params", {}).get("nft"), - "threshold": data.get("chain_data", {}).get("user_params", {}).get("threshold"), - "use_staking": data.get("chain_data", {}).get("user_params", {}).get("use_staking"), - "cost_of_bond": data.get("chain_data", {}).get("user_params", {}).get("cost_of_bond"), - "fund_requirements": data.get("chain_data", {}).get("user_params", {}).get("fund_requirements", {}) - } - } + "nft": data.get("chain_data", {}) + .get("user_params", {}) + .get("nft"), + "threshold": data.get("chain_data", {}) + .get("user_params", {}) + .get("threshold"), + "use_staking": data.get("chain_data", {}) + .get("user_params", {}) + .get("use_staking"), + "cost_of_bond": data.get("chain_data", {}) + .get("user_params", {}) + .get("cost_of_bond"), + "fund_requirements": data.get("chain_data", {}) + .get("user_params", {}) + .get("fund_requirements", {}), + }, + }, } }, "service_path": data.get("service_path", ""), - "name": data.get("name", "") + "name": data.get("name", ""), } - - with open(file_path, 'w', encoding='utf-8') as file: + + with open(file_path, "w", encoding="utf-8") as file: json.dump(new_data, file, indent=2) @classmethod @@ -781,7 +797,9 @@ def update_user_params_from_template(self, service_template: ServiceTemplate): """Update user params from template.""" for chain, config in service_template["configurations"].items(): for chain, config in service_template["configurations"].items(): - self.chain_configs[chain].chain_data.user_params = OnChainUserParams.from_json(config) + self.chain_configs[ + chain + ].chain_data.user_params = OnChainUserParams.from_json(config) self.store() From bcd3a1308325eef9d9e78770072d9d0defae2259 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Thu, 22 Aug 2024 16:36:13 +0200 Subject: [PATCH 14/47] fix: linters --- operate/cli.py | 7 ++++--- operate/services/manage.py | 33 +++++++++++++++++---------------- operate/services/protocol.py | 6 ++++++ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/operate/cli.py b/operate/cli.py index 7d5285d13..99cabe0f1 100644 --- a/operate/cli.py +++ b/operate/cli.py @@ -530,8 +530,8 @@ async def _create_services(request: Request) -> JSONResponse: if template.get("deploy", False): def _fn() -> None: + # deploy_service_onchain_from_safe includes stake_service_on_chain_from_safe manager.deploy_service_onchain_from_safe(hash=service.hash) - # manager.stake_service_on_chain_from_safe(hash=service.hash) # Done inside deploy_service_onchain manager.fund_service(hash=service.hash) manager.deploy_service_locally(hash=service.hash) @@ -556,8 +556,9 @@ async def _update_services(request: Request) -> JSONResponse: ) if template.get("deploy", False): manager = operate.service_manager() + + # deploy_service_onchain_from_safe includes stake_service_on_chain_from_safe manager.deploy_service_onchain_from_safe(hash=service.hash) - # manager.stake_service_on_chain_from_safe(hash=service.hash) # Done in deploy_service_onchain_from_safe manager.fund_service(hash=service.hash) manager.deploy_service_locally(hash=service.hash) schedule_funding_job(service=service.hash) @@ -675,7 +676,7 @@ async def _start_service_locally(request: Request) -> JSONResponse: def _fn() -> None: manager.deploy_service_onchain(hash=service) - # manager.stake_service_on_chain(hash=service) + manager.stake_service_on_chain(hash=service) manager.fund_service(hash=service) manager.deploy_service_locally(hash=service, force=True) diff --git a/operate/services/manage.py b/operate/services/manage.py index a87f9e57c..8459bfa2f 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -186,22 +186,21 @@ def load_or_create( return service - def _get_on_chain_state(self, chain_config: ChainConfig) -> OnChainState: + def _get_on_chain_state(self, service: Service, chain_id: str) -> OnChainState: + chain_config = service.chain_configs[chain_id] chain_data = chain_config.chain_data ledger_config = chain_config.ledger_config if chain_data.token == NON_EXISTENT_TOKEN: service_state = OnChainState.NON_EXISTENT chain_data.on_chain_state = service_state - # TODO save service state - # service.store() + service.store() return service_state sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config) info = sftxb.info(token_id=chain_data.token) service_state = OnChainState(info["service_state"]) chain_data.on_chain_state = service_state - # TODO save service state - # service.store() + service.store() return service_state def _get_on_chain_hash(self, chain_config: ChainConfig) -> t.Optional[str]: @@ -478,7 +477,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to on_chain_hash = self._get_on_chain_hash(chain_config=chain_config) is_first_mint = ( - self._get_on_chain_state(chain_config=chain_config) + self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.NON_EXISTENT ) is_update = ( @@ -507,7 +506,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to # Update service if ( - self._get_on_chain_state(chain_config=chain_config) + self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.PRE_REGISTRATION ): self.logger.info("Updating service") @@ -549,7 +548,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to # Mint service if ( - self._get_on_chain_state(chain_config=chain_config) + self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.NON_EXISTENT ): if user_params.use_staking and not sftxb.staking_slots_available( @@ -598,7 +597,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to service.store() if ( - self._get_on_chain_state(chain_config=chain_config) + self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.PRE_REGISTRATION ): cost_of_bond = staking_params["min_staking_deposit"] @@ -651,7 +650,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to service.store() if ( - self._get_on_chain_state(chain_config=chain_config) + self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.ACTIVE_REGISTRATION ): cost_of_bond = user_params.cost_of_bond @@ -708,7 +707,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to service.store() if ( - self._get_on_chain_state(chain_config=chain_config) + self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.FINISHED_REGISTRATION ): self.logger.info("Deploying service") @@ -1007,7 +1006,7 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: target_staking_contract ) staking_slots_available = sftxb.staking_slots_available(target_staking_contract) - on_chain_state = self._get_on_chain_state(chain_config=chain_config) + on_chain_state = self._get_on_chain_state(service=service, chain_id=chain_id) current_staking_program = self._get_current_staking_program( chain_data, ledger_config, sftxb ) @@ -1260,12 +1259,14 @@ def update_service( hash=new_hash, service_template=service_template ) new_service.keys = old_service.keys - # new_Service.home_chain_id = old_service.home_chain_id - # TODO - Ensure this works as expected - New service must copy all chain_data from old service, - # but if service_template is not None, it must copy the user_params - # passed in the service_template and copy the remaining attributes from old_service. + # TODO Ensure this is as intended. + new_service.home_chain_id = old_service.home_chain_id + # new_service must copy all chain_data from old_service. + # Additionally, if service_template is not None, it must overwrite + # the user_params on all chain_data by the values passed through the + # service_template. new_service.chain_configs = {} for chain_id, config in old_service.chain_configs.items(): new_service.chain_configs[chain_id] = config diff --git a/operate/services/protocol.py b/operate/services/protocol.py index f58f5b921..f7edf4eff 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -244,6 +244,7 @@ def service_info(self, staking_contract: str, service_id: int) -> dict: ).get("data") def agent_ids(self, staking_contract: str) -> t.List[int]: + """Get the agent IDs for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -251,6 +252,7 @@ def agent_ids(self, staking_contract: str) -> t.List[int]: return instance.functions.getAgentIds().call() def service_registry(self, staking_contract: str) -> str: + """Get the service registry address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -258,6 +260,7 @@ def service_registry(self, staking_contract: str) -> str: return instance.functions.serviceRegistry().call() def staking_token(self, staking_contract: str) -> str: + """Get the staking token address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -265,6 +268,7 @@ def staking_token(self, staking_contract: str) -> str: return instance.functions.stakingToken().call() def service_registry_token_utility(self, staking_contract: str) -> str: + """Get the service registry token utility address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -272,6 +276,7 @@ def service_registry_token_utility(self, staking_contract: str) -> str: return instance.functions.serviceRegistryTokenUtility().call() def min_staking_deposit(self, staking_contract: str) -> str: + """Get the minimum staking deposit for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -279,6 +284,7 @@ def min_staking_deposit(self, staking_contract: str) -> str: return instance.functions.minStakingDeposit().call() def activity_checker(self, staking_contract: str) -> str: + """Get the activity checker address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, From 51477779bdd06fde8209b72fecb88a638e428dc7 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Thu, 22 Aug 2024 18:43:21 +0200 Subject: [PATCH 15/47] fix: linters --- operate/services/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index 8459bfa2f..7c6d2b5a3 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -433,7 +433,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to ], ) else: # TODO fix this - using pearl beta params - staking_params = dict( + staking_params = dict( # nosec agent_ids=[25], service_registry="0x9338b5153AE39BB89f50468E608eD9d764B755fD", # nosec staking_token="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f", # nosec From 1efa08bbb998b733e726f71b058dbf442634104e Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Thu, 22 Aug 2024 19:22:23 +0200 Subject: [PATCH 16/47] chore: linters --- operate/services/manage.py | 24 +++++++++++++++--------- operate/services/protocol.py | 7 ------- operate/services/service.py | 18 +++++++++--------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index 7c6d2b5a3..81c7dd78c 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -767,7 +767,9 @@ def terminate_service_on_chain(self, hash: str) -> None: service.chain_data.on_chain_state = OnChainState.TERMINATED_BONDED service.store() - def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: + def _terminate_service_on_chain_from_safe( + self, hash: str, chain_id: str + ) -> None: # pylint: disable=too-many-locals """ Terminate service on-chain @@ -813,7 +815,7 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non hash=hash, chain_id=chain_id, staking_program_id=current_staking_program ) - if self._get_on_chain_state(chain_config) in ( + if self._get_on_chain_state(service=service, chain_id=chain_id) in ( OnChainState.ACTIVE_REGISTRATION, OnChainState.FINISHED_REGISTRATION, OnChainState.DEPLOYED, @@ -825,7 +827,10 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non ) ).settle() - if self._get_on_chain_state(chain_config) == OnChainState.TERMINATED_BONDED: + if ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.TERMINATED_BONDED + ): self.logger.info("Unbonding service") sftxb.new_tx().add( sftxb.get_unbond_data( @@ -853,8 +858,9 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non else wallet.crypto.address, # TODO it should always be safe address ) # noqa: E800 + @staticmethod def _get_current_staking_program( - self, chain_data, ledger_config, sftxb + chain_data, ledger_config, sftxb ) -> t.Optional[str]: if chain_data.token == NON_EXISTENT_TOKEN: return None @@ -896,9 +902,7 @@ def unbond_service_on_chain(self, hash: str) -> None: service.chain_data.on_chain_state = OnChainState.UNBONDED service.store() - def stake_service_on_chain( - self, hash: str, chain_id: int, staking_program_id: str - ) -> None: + def stake_service_on_chain(self, hash: str) -> None: """ Stake service on-chain @@ -906,7 +910,9 @@ def stake_service_on_chain( """ raise NotImplementedError - def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: + def stake_service_on_chain_from_safe( + self, hash: str, chain_id: str + ) -> None: # pylint: disable=too-many-statements,too-many-locals """ Stake service on-chain @@ -1127,7 +1133,7 @@ def unstake_service_on_chain_from_safe( chain_data.staked = False service.store() - def fund_service( # pylint: disable=too-many-arguments + def fund_service( # pylint: disable=too-many-arguments,too-many-locals self, hash: str, rpc: t.Optional[str] = None, diff --git a/operate/services/protocol.py b/operate/services/protocol.py index f7edf4eff..4c97a51c9 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -535,13 +535,6 @@ def ledger_api(self) -> LedgerApi: ) return ledger_api - def owner_of(self, token_id: int) -> str: - """Get owner of a service.""" - self._patch() - ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( - chain_type=self.chain_type - ) - def info(self, token_id: int) -> t.Dict: """Get service info.""" self._patch() diff --git a/operate/services/service.py b/operate/services/service.py index 10aa4d3a8..d164e29e3 100644 --- a/operate/services/service.py +++ b/operate/services/service.py @@ -82,12 +82,13 @@ ) +# pylint: disable=no-member,redefined-builtin,too-many-instance-attributes + SAFE_CONTRACT_ADDRESS = "safe_contract_address" ALL_PARTICIPANTS = "all_participants" CONSENSUS_THRESHOLD = "consensus_threshold" DELETE_PREFIX = "delete_" - -# pylint: disable=no-member,redefined-builtin,too-many-instance-attributes +SERVICE_CONFIG_VERSION = 2 DUMMY_MULTISIG = "0xm" NON_EXISTENT_TOKEN = -1 @@ -238,7 +239,7 @@ def __init__(self, path: Path) -> None: self.path = path self.config = load_service_config(service_path=path) - def ledger_configs(self) -> "LedgerConfigs": + def ledger_configs(self) -> LedgerConfigs: """Get ledger configs.""" ledger_configs = {} for override in self.config.overrides: @@ -741,7 +742,7 @@ def deployment(self) -> Deployment: return t.cast(Deployment, self._deployment) @staticmethod - def new( + def new( # pylint: disable=too-many-locals hash: str, keys: Keys, service_template: ServiceTemplate, @@ -781,7 +782,7 @@ def new( ) service = Service( - version=2, # TODO implement in appropriate place + version=SERVICE_CONFIG_VERSION, name=service_yaml["author"] + "/" + service_yaml["name"], hash=service_template["hash"], keys=keys, @@ -796,10 +797,9 @@ def new( def update_user_params_from_template(self, service_template: ServiceTemplate): """Update user params from template.""" for chain, config in service_template["configurations"].items(): - for chain, config in service_template["configurations"].items(): - self.chain_configs[ - chain - ].chain_data.user_params = OnChainUserParams.from_json(config) + self.chain_configs[ + chain + ].chain_data.user_params = OnChainUserParams.from_json(config) self.store() From 9a9906b5de47b11d0f090984551cab5c6680555e Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Thu, 22 Aug 2024 19:26:27 +0200 Subject: [PATCH 17/47] chore: linters --- operate/services/manage.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index 81c7dd78c..e6393e664 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -767,9 +767,9 @@ def terminate_service_on_chain(self, hash: str) -> None: service.chain_data.on_chain_state = OnChainState.TERMINATED_BONDED service.store() - def _terminate_service_on_chain_from_safe( + def _terminate_service_on_chain_from_safe( # pylint: disable=too-many-locals self, hash: str, chain_id: str - ) -> None: # pylint: disable=too-many-locals + ) -> None: """ Terminate service on-chain @@ -910,9 +910,9 @@ def stake_service_on_chain(self, hash: str) -> None: """ raise NotImplementedError - def stake_service_on_chain_from_safe( + def stake_service_on_chain_from_safe( # pylint: disable=too-many-statements,too-many-locals self, hash: str, chain_id: str - ) -> None: # pylint: disable=too-many-statements,too-many-locals + ) -> None: """ Stake service on-chain From 0610fce306e754332f1ca35ffc3eddb68a7aa30e Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Thu, 22 Aug 2024 19:45:31 +0200 Subject: [PATCH 18/47] chore: linters --- operate/services/service.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/operate/services/service.py b/operate/services/service.py index d164e29e3..2107cce56 100644 --- a/operate/services/service.py +++ b/operate/services/service.py @@ -417,7 +417,8 @@ def _build_docker( builder.deplopyment_type = DockerComposeGenerator.deployment_type builder.try_update_abci_connection_params() - home_chain_data = service.chain_configs[service.home_chain_id] + home_chain_data = service.chain_configs[service.home_chain_id].chain_data + home_chain_ledger_config = service.chain_configs[service.home_chain_id].ledger_config builder.try_update_runtime_params( multisig_address=home_chain_data.multisig, agent_instances=home_chain_data.instances, @@ -426,8 +427,8 @@ def _build_docker( ) # TODO: Support for multiledger builder.try_update_ledger_params( - chain=LedgerType(service.ledger_config.type).name.lower(), - address=service.ledger_config.rpc, + chain=LedgerType(home_chain_ledger_config.type).name.lower(), + address=home_chain_ledger_config.rpc, ) # build deployment @@ -773,7 +774,7 @@ def new( # pylint: disable=too-many-locals multisig=DUMMY_MULTISIG, staked=False, on_chain_state=OnChainState.NON_EXISTENT, - user_params=OnChainUserParams.from_json(config), + user_params=OnChainUserParams.from_json(config), # type: ignore ) chain_configs[chain] = ChainConfig( @@ -794,12 +795,12 @@ def new( # pylint: disable=too-many-locals service.store() return service - def update_user_params_from_template(self, service_template: ServiceTemplate): + def update_user_params_from_template(self, service_template: ServiceTemplate) -> None: """Update user params from template.""" for chain, config in service_template["configurations"].items(): self.chain_configs[ chain - ].chain_data.user_params = OnChainUserParams.from_json(config) + ].chain_data.user_params = OnChainUserParams.from_json(config) # type: ignore self.store() From 092e655350dbe9b6fda42ce816b40f8c43a6a2d5 Mon Sep 17 00:00:00 2001 From: Ardian Date: Thu, 22 Aug 2024 21:01:46 +0200 Subject: [PATCH 19/47] chore: ignore missing import for `eth_utils` --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index b7de2caf0..88426bd93 100644 --- a/tox.ini +++ b/tox.ini @@ -215,3 +215,6 @@ ignore_missing_imports = True [mypy-psutil.*] ignore_missing_imports = True + +[mypy-eth_utils.*] +ignore_missing_imports = True From bccc559efd0802fe933587a07bdaeab41b79246f Mon Sep 17 00:00:00 2001 From: "Yuri (solarw) Turchenkov" Date: Fri, 23 Aug 2024 17:58:04 +0300 Subject: [PATCH 20/47] service hash id updated --- frontend/constants/serviceTemplates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/constants/serviceTemplates.ts b/frontend/constants/serviceTemplates.ts index dcb2fd1aa..3002a9736 100644 --- a/frontend/constants/serviceTemplates.ts +++ b/frontend/constants/serviceTemplates.ts @@ -4,7 +4,7 @@ import { StakingProgram } from '@/enums/StakingProgram'; export const SERVICE_TEMPLATES: ServiceTemplate[] = [ { name: 'Trader Agent', - hash: 'bafybeidgjgjj5ul6xkubicbemppufgsbx5sr5rwhtrwttk2oivp5bkdnce', + hash: 'bafybeihqtqgohejyebrb4jvd2fgccuasmqk3gc456ylyfa7tcn6rucdrxm', // hash: 'bafybeibbloa4w33vj4bvdkso7pzk6tr3duvxjpecbx4mur4ix6ehnwb5uu', // temporary description: 'Trader agent for omen prediction markets', image: From 6c3cb7cbfec52578a6a90a8c336e815263716356 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Wed, 28 Aug 2024 11:07:38 +0200 Subject: [PATCH 21/47] chore: fix fund before swap transaction --- operate/services/manage.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/operate/services/manage.py b/operate/services/manage.py index e6393e664..6ea44db92 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -476,6 +476,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to ) on_chain_hash = self._get_on_chain_hash(chain_config=chain_config) + current_agent_bond = staking_params["min_staking_deposit"] # TODO fixme, read from service registry token utility contract is_first_mint = ( self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.NON_EXISTENT @@ -486,6 +487,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to and ( on_chain_hash != service.hash or current_agent_id != staking_params["agent_ids"][0] + or current_agent_bond != staking_params["min_staking_deposit"] ) ) current_staking_program = self._get_current_staking_program( @@ -845,6 +847,16 @@ def _terminate_service_on_chain_from_safe( # pylint: disable=too-many-locals if counter_current_safe_owners == counter_instances: self.logger.info("Swapping Safe owners") + + self.fund_service( + hash=hash, + rpc=ledger_config.rpc, + agent_topup=chain_data.user_params.fund_requirements.agent, + agent_fund_threshold=chain_data.user_params.fund_requirements.agent, + safe_topup=0, + safe_fund_treshold=0, + ) + sftxb.swap( # noqa: E800 service_id=chain_data.token, # noqa: E800 multisig=chain_data.multisig, # TODO this can be read from the registry From 75a9ccfc76a309f0409b4f26eaa6a0f9e270b6e7 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 28 Aug 2024 12:44:08 +0100 Subject: [PATCH 22/47] fix: required staking balance calculations --- .../StakingContractSection/index.tsx | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index 363f7ce55..edfe91959 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -65,10 +65,10 @@ export const StakingContractSection = ({ const { setMigrationModalOpen } = useModals(); const { activeStakingProgram, defaultStakingProgram, updateStakingProgram } = useStakingProgram(); - const { stakingContractInfoRecord } = useStakingContractInfo(); const { token } = useToken(); - const { totalOlasBalance, isBalanceLoaded } = useBalance(); - const { isServiceStakedForMinimumDuration } = useStakingContractInfo(); + const { safeBalance, totalOlasStakedBalance, isBalanceLoaded } = useBalance(); + const { isServiceStakedForMinimumDuration, stakingContractInfoRecord } = + useStakingContractInfo(); const stakingContractInfoForStakingProgram = stakingContractInfoRecord?.[stakingProgram]; @@ -87,10 +87,15 @@ export const StakingContractSection = ({ ); const hasEnoughOlasToMigrate = useMemo(() => { - if (totalOlasBalance === undefined) return false; - if (!minimumOlasRequiredToMigrate) return false; - return totalOlasBalance >= minimumOlasRequiredToMigrate; - }, [minimumOlasRequiredToMigrate, totalOlasBalance]); + if (safeBalance?.OLAS === undefined || totalOlasStakedBalance === undefined) + return false; + + const balanceForMigration = safeBalance.OLAS + totalOlasStakedBalance; + + if (minimumOlasRequiredToMigrate === undefined) return false; + + return balanceForMigration >= minimumOlasRequiredToMigrate; + }, [minimumOlasRequiredToMigrate, safeBalance?.OLAS, totalOlasStakedBalance]); const hasEnoughSlots = stakingContractInfoForStakingProgram?.maxNumServices && @@ -167,6 +172,7 @@ export const StakingContractSection = ({ isBalanceLoaded, isSelected, isServiceStakedForMinimumDuration, + minimumOlasRequiredToMigrate, serviceStatus, ]); @@ -181,7 +187,11 @@ export const StakingContractSection = ({ if (!hasEnoughOlasToMigrate) { return ( - + ); } @@ -191,10 +201,12 @@ export const StakingContractSection = ({ }, [ isSelected, isBalanceLoaded, - totalOlasBalance, hasEnoughSlots, hasEnoughOlasToMigrate, isAppVersionCompatible, + safeBalance?.OLAS, + totalOlasStakedBalance, + minimumOlasRequiredToMigrate, ]); const contractTagStatus = useMemo(() => { From 20114179012b90d6b0279ea2fe11085721fd7ceb Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 28 Aug 2024 12:44:19 +0100 Subject: [PATCH 23/47] refactor: update copy as per figma --- .../StakingContractSection/alerts.tsx | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx b/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx index f2c1cf6cd..bee80cbcb 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx @@ -6,28 +6,38 @@ import { UNICODE_SYMBOLS } from '@/constants/symbols'; const { Text } = Typography; export const AlertInsufficientMigrationFunds = ({ - totalOlasBalance, + masterSafeOlasBalance, + stakedOlasBalance, + totalOlasRequiredForStaking, }: { - totalOlasBalance: number; -}) => ( - - - Insufficient amount of funds to switch - + masterSafeOlasBalance?: number; + stakedOlasBalance?: number; + totalOlasRequiredForStaking: number; +}) => { + const requiredOlasDeposit = + stakedOlasBalance !== undefined && + masterSafeOlasBalance !== undefined && + totalOlasRequiredForStaking - (stakedOlasBalance + masterSafeOlasBalance); - Add funds to your account to meet the program requirements. - - Your current OLAS balance:{' '} - {totalOlasBalance} OLAS - - - } - /> -); + return ( + + + Additional {requiredOlasDeposit} OLAS are required to switch + + + + Add {requiredOlasDeposit} OLAS to your account to + meet the contract requirements and be able to switch. + + + } + /> + ); +}; export const AlertNoSlots = () => ( Date: Wed, 28 Aug 2024 12:51:51 +0100 Subject: [PATCH 24/47] refactor: ensure not undefined earlier --- .../ManageStakingPage/StakingContractSection/alerts.tsx | 6 ++---- .../ManageStakingPage/StakingContractSection/index.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx b/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx index bee80cbcb..6aeb6471d 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx @@ -10,13 +10,11 @@ export const AlertInsufficientMigrationFunds = ({ stakedOlasBalance, totalOlasRequiredForStaking, }: { - masterSafeOlasBalance?: number; - stakedOlasBalance?: number; + masterSafeOlasBalance: number; + stakedOlasBalance: number; totalOlasRequiredForStaking: number; }) => { const requiredOlasDeposit = - stakedOlasBalance !== undefined && - masterSafeOlasBalance !== undefined && totalOlasRequiredForStaking - (stakedOlasBalance + masterSafeOlasBalance); return ( diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index edfe91959..9acfc4c39 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -185,10 +185,14 @@ export const StakingContractSection = ({ return ; } - if (!hasEnoughOlasToMigrate) { + if ( + !hasEnoughOlasToMigrate && + safeBalance?.OLAS !== undefined && + totalOlasStakedBalance !== undefined + ) { return ( From 2c58a2161b3252a1ee09a6b8d1a900c019557daa Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Wed, 28 Aug 2024 16:13:35 +0200 Subject: [PATCH 25/47] chore: minor fixes --- operate/services/manage.py | 4 ++-- operate/services/protocol.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index 6ea44db92..cb4cb5e37 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -846,8 +846,7 @@ def _terminate_service_on_chain_from_safe( # pylint: disable=too-many-locals counter_instances = Counter(s.lower() for s in instances) if counter_current_safe_owners == counter_instances: - self.logger.info("Swapping Safe owners") - + self.logger.info("Service funded for safe swap") self.fund_service( hash=hash, rpc=ledger_config.rpc, @@ -857,6 +856,7 @@ def _terminate_service_on_chain_from_safe( # pylint: disable=too-many-locals safe_fund_treshold=0, ) + self.logger.info("Swapping Safe owners") sftxb.swap( # noqa: E800 service_id=chain_data.token, # noqa: E800 multisig=chain_data.multisig, # TODO this can be read from the registry diff --git a/operate/services/protocol.py b/operate/services/protocol.py index 4c97a51c9..6c52fb069 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -23,7 +23,6 @@ import contextlib import io import json -import logging import tempfile import time import typing as t @@ -600,7 +599,6 @@ def swap( # pylint: disable=too-many-arguments,too-many-locals self, service_id: int, multisig: str, owner_key: str, new_owner_address: str ) -> None: """Swap safe owner.""" - logging.info(f"Swapping safe for service {service_id} [{multisig}]...") self._patch() manager = ServiceManager( service_id=service_id, @@ -710,6 +708,11 @@ def staking_rewards_available(self, staking_contract: str) -> bool: ) return available_rewards > 0 + # def get_agent_bond(self, staking_contract: str) -> int: + # self._patch() + + + class OnChainManager(_ChainUtil): """On chain service management.""" From 9317ea38a0aaa6332198c35800ce3fc4b00e91a1 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Wed, 28 Aug 2024 16:15:44 +0200 Subject: [PATCH 26/47] fix: linters --- operate/services/manage.py | 4 +++- operate/services/protocol.py | 2 -- operate/services/service.py | 12 +++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/operate/services/manage.py b/operate/services/manage.py index cb4cb5e37..71f317b49 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -476,7 +476,9 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to ) on_chain_hash = self._get_on_chain_hash(chain_config=chain_config) - current_agent_bond = staking_params["min_staking_deposit"] # TODO fixme, read from service registry token utility contract + current_agent_bond = staking_params[ + "min_staking_deposit" + ] # TODO fixme, read from service registry token utility contract is_first_mint = ( self._get_on_chain_state(service=service, chain_id=chain_id) == OnChainState.NON_EXISTENT diff --git a/operate/services/protocol.py b/operate/services/protocol.py index 6c52fb069..709e65576 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -710,8 +710,6 @@ def staking_rewards_available(self, staking_contract: str) -> bool: # def get_agent_bond(self, staking_contract: str) -> int: # self._patch() - - class OnChainManager(_ChainUtil): diff --git a/operate/services/service.py b/operate/services/service.py index 2107cce56..675a526f1 100644 --- a/operate/services/service.py +++ b/operate/services/service.py @@ -418,7 +418,9 @@ def _build_docker( builder.try_update_abci_connection_params() home_chain_data = service.chain_configs[service.home_chain_id].chain_data - home_chain_ledger_config = service.chain_configs[service.home_chain_id].ledger_config + home_chain_ledger_config = service.chain_configs[ + service.home_chain_id + ].ledger_config builder.try_update_runtime_params( multisig_address=home_chain_data.multisig, agent_instances=home_chain_data.instances, @@ -795,12 +797,16 @@ def new( # pylint: disable=too-many-locals service.store() return service - def update_user_params_from_template(self, service_template: ServiceTemplate) -> None: + def update_user_params_from_template( + self, service_template: ServiceTemplate + ) -> None: """Update user params from template.""" for chain, config in service_template["configurations"].items(): self.chain_configs[ chain - ].chain_data.user_params = OnChainUserParams.from_json(config) # type: ignore + ].chain_data.user_params = OnChainUserParams.from_json( + config + ) # type: ignore self.store() From ce45d60e86ce395dbc630e9cdbc4653e07e3d672 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Wed, 28 Aug 2024 16:22:07 +0200 Subject: [PATCH 27/47] chore: minor fix --- operate/services/protocol.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/operate/services/protocol.py b/operate/services/protocol.py index 709e65576..4c97a51c9 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -23,6 +23,7 @@ import contextlib import io import json +import logging import tempfile import time import typing as t @@ -599,6 +600,7 @@ def swap( # pylint: disable=too-many-arguments,too-many-locals self, service_id: int, multisig: str, owner_key: str, new_owner_address: str ) -> None: """Swap safe owner.""" + logging.info(f"Swapping safe for service {service_id} [{multisig}]...") self._patch() manager = ServiceManager( service_id=service_id, @@ -708,9 +710,6 @@ def staking_rewards_available(self, staking_contract: str) -> bool: ) return available_rewards > 0 - # def get_agent_bond(self, staking_contract: str) -> int: - # self._patch() - class OnChainManager(_ChainUtil): """On chain service management.""" From e6d7af9d74638fe6c2e043bdbf1ed4424c7908be Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 28 Aug 2024 16:42:01 +0100 Subject: [PATCH 28/47] refactor: remove reassignment --- .../MainPage/sections/AddFundsSection.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/frontend/components/MainPage/sections/AddFundsSection.tsx b/frontend/components/MainPage/sections/AddFundsSection.tsx index d61970e7f..ad3e17053 100644 --- a/frontend/components/MainPage/sections/AddFundsSection.tsx +++ b/frontend/components/MainPage/sections/AddFundsSection.tsx @@ -18,7 +18,6 @@ import styled from 'styled-components'; import { UNICODE_SYMBOLS } from '@/constants/symbols'; import { COW_SWAP_GNOSIS_XDAI_OLAS_URL } from '@/constants/urls'; import { useWallet } from '@/hooks/useWallet'; -import { Address } from '@/types/Address'; import { copyToClipboard } from '@/utils/copyToClipboard'; import { truncateAddress } from '@/utils/truncate'; @@ -66,27 +65,25 @@ export const AddFundsSection = () => { export const OpenAddFundsSection = () => { const { masterSafeAddress } = useWallet(); - const fundingAddress: Address | undefined = masterSafeAddress; - const truncatedFundingAddress: string | undefined = useMemo( - () => fundingAddress && truncateAddress(fundingAddress), - [fundingAddress], + () => masterSafeAddress && truncateAddress(masterSafeAddress), + [masterSafeAddress], ); const handleCopyAddress = useCallback( () => - fundingAddress && - copyToClipboard(fundingAddress).then(() => + masterSafeAddress && + copyToClipboard(masterSafeAddress).then(() => message.success('Copied successfully!'), ), - [fundingAddress], + [masterSafeAddress], ); return ( <> From b3d4356c812f5c92e29c2425e03f0b013798b805 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 28 Aug 2024 16:42:36 +0100 Subject: [PATCH 29/47] refactor: remove fragment --- .../StakingContractSection/index.tsx | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index 0c8f3ccf0..856a5648f 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -287,40 +287,38 @@ export const StakingContractSection = ({ {cantMigrateAlert} {/* Switch to program button */} { - <> - - - - + + + } {stakingProgram === StakingProgram.Beta && (