From 5af94c0d2201e6ee00dd4ada60f78d2d5da9690c Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 22 Feb 2024 14:10:48 +0100 Subject: [PATCH 01/33] simplify docker compose --- .../localnet/docker-compose-setup-only.yml | 13 ---- .../localnet/docker-compose-stresstest.yml | 72 +++---------------- contrib/localnet/docker-compose-upgrade.yml | 62 ---------------- 3 files changed, 8 insertions(+), 139 deletions(-) diff --git a/contrib/localnet/docker-compose-setup-only.yml b/contrib/localnet/docker-compose-setup-only.yml index ed210c3264..c4d3dbb4c3 100644 --- a/contrib/localnet/docker-compose-setup-only.yml +++ b/contrib/localnet/docker-compose-setup-only.yml @@ -5,18 +5,5 @@ version: "3" services: orchestrator: - image: orchestrator:latest - tty: true - container_name: orchestrator - build: - context: ../../. - dockerfile: contrib/localnet/orchestrator/Dockerfile - depends_on: - - zetacore0 - - eth - hostname: orchestrator - networks: - mynetwork: - ipv4_address: 172.20.0.2 entrypoint: ["/work/start.sh", "local", "setup-only"] diff --git a/contrib/localnet/docker-compose-stresstest.yml b/contrib/localnet/docker-compose-stresstest.yml index 07cb1820f4..f0d582b2e7 100644 --- a/contrib/localnet/docker-compose-stresstest.yml +++ b/contrib/localnet/docker-compose-stresstest.yml @@ -7,36 +7,10 @@ version: "3" services: zetacore0: - image: zetanode:latest - container_name: zetacore0 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetacore0 - ports: - - "1317:1317" - - "9545:8545" - - "9546:8546" - networks: - mynetwork: - ipv4_address: 172.20.0.11 entrypoint: ["/root/start-zetacored.sh", "4"] - environment: - - HOTKEY_BACKEND=test zetacore1: - image: zetanode:latest - container_name: zetacore1 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetacore1 - networks: - mynetwork: - ipv4_address: 172.20.0.12 entrypoint: ["/root/start-zetacored.sh", "4"] - environment: - - HOTKEY_BACKEND=test zetacore2: image: zetanode:latest @@ -50,7 +24,8 @@ services: ipv4_address: 172.20.0.13 entrypoint: [ "/root/start-zetacored.sh", "4" ] environment: - - HOTKEY_BACKEND=test + - HOTKEY_BACKEND=file + - HOTKEY_PASSWORD=password # test purposes only zetacore3: image: zetanode:latest @@ -64,39 +39,16 @@ services: ipv4_address: 172.20.0.14 entrypoint: [ "/root/start-zetacored.sh", "4" ] environment: - - HOTKEY_BACKEND=test + - HOTKEY_BACKEND=file + - HOTKEY_PASSWORD=password # test purposes only zetaclient0: - image: zetanode:latest - container_name: zetaclient0 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetaclient0 - networks: - mynetwork: - ipv4_address: 172.20.0.21 ports: - "8123:8123" entrypoint: /root/start-zetaclientd.sh - environment: - - ETHDEV_ENDPOINT=http://eth:8545 - - HOTKEY_BACKEND=test zetaclient1: - image: zetanode:latest - container_name: zetaclient1 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetaclient1 - networks: - mynetwork: - ipv4_address: 172.20.0.22 entrypoint: /root/start-zetaclientd.sh - environment: - - ETHDEV_ENDPOINT=http://eth:8545 - - HOTKEY_BACKEND=test zetaclient2: image: zetanode:latest @@ -111,7 +63,8 @@ services: entrypoint: /root/start-zetaclientd.sh environment: - ETHDEV_ENDPOINT=http://eth:8545 - - HOTKEY_BACKEND=test + - HOTKEY_BACKEND=file + - HOTKEY_PASSWORD=password # test purposes only zetaclient3: image: zetanode:latest @@ -126,19 +79,10 @@ services: entrypoint: /root/start-zetaclientd.sh environment: - ETHDEV_ENDPOINT=http://eth:8545 - - HOTKEY_BACKEND=test + - HOTKEY_BACKEND=file + - HOTKEY_PASSWORD=password # test purposes only orchestrator: - image: orchestrator:latest - container_name: orchestrator build: - context: ../../. dockerfile: contrib/localnet/orchestrator/Dockerfile.fastbuild - depends_on: - - zetacore0 - - eth - hostname: orchestrator - networks: - mynetwork: - ipv4_address: 172.20.0.2 entrypoint: ["/work/start.sh", "stress"] \ No newline at end of file diff --git a/contrib/localnet/docker-compose-upgrade.yml b/contrib/localnet/docker-compose-upgrade.yml index c55f847e43..ee677a0e5f 100644 --- a/contrib/localnet/docker-compose-upgrade.yml +++ b/contrib/localnet/docker-compose-upgrade.yml @@ -7,78 +7,16 @@ version: "3" services: zetacore0: - image: zetanode:latest - container_name: zetacore0 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetacore0 - ports: - - "1317:1317" - - "9545:8545" - - "9546:8546" - networks: - mynetwork: - ipv4_address: 172.20.0.11 entrypoint: ["/root/start-zetacored.sh", "2", "upgrade"] - environment: - - HOTKEY_BACKEND=test zetacore1: - image: zetanode:latest - container_name: zetacore1 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetacore1 - networks: - mynetwork: - ipv4_address: 172.20.0.12 entrypoint: ["/root/start-zetacored.sh", "2", "upgrade"] - environment: - - HOTKEY_BACKEND=test zetaclient0: - image: zetanode:latest - container_name: zetaclient0 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetaclient0 - networks: - mynetwork: - ipv4_address: 172.20.0.21 entrypoint: ["/root/start-zetaclientd.sh", "background"] - environment: - - HOTKEY_BACKEND=test - - ETHDEV_ENDPOINT=http://eth:8545 zetaclient1: - image: zetanode:latest - container_name: zetaclient1 - build: - context: ../../. - dockerfile: Dockerfile - hostname: zetaclient1 - networks: - mynetwork: - ipv4_address: 172.20.0.22 entrypoint: ["/root/start-zetaclientd.sh", "background"] - environment: - - HOTKEY_BACKEND=test - - ETHDEV_ENDPOINT=http://eth:8545 orchestrator: - image: orchestrator:latest - container_name: orchestrator - build: - context: ../../. - dockerfile: contrib/localnet/orchestrator/Dockerfile - depends_on: - - zetacore0 - - eth - hostname: orchestrator - networks: - mynetwork: - ipv4_address: 172.20.0.2 entrypoint: ["/work/start.sh", "local", "upgrade"] From 3a4a6eaf05e4d3c52f2c12ab7d49adb896793665 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 22 Feb 2024 14:34:22 +0100 Subject: [PATCH 02/33] fix makefi;e --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7caeeb66e7..4ec78d1d68 100644 --- a/Makefile +++ b/Makefile @@ -202,7 +202,7 @@ start-stress-test: zetanode start-upgrade-test: @echo "--> Starting upgrade test" - $(DOCKER) build --build-arg -t zetanode -f ./Dockerfile-upgrade . + $(DOCKER) build -t zetanode -f ./Dockerfile-upgrade . $(DOCKER) build -t orchestrator -f contrib/localnet/orchestrator/Dockerfile.fastbuild . cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-upgrade.yml up -d From ba58886dca8f8fd61710f091d9c76e30c0129670 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 22 Feb 2024 14:34:34 +0100 Subject: [PATCH 03/33] add new version --- Dockerfile-upgrade | 4 ++-- app/setup_handlers.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile-upgrade b/Dockerfile-upgrade index b9344981a4..065f8622f0 100644 --- a/Dockerfile-upgrade +++ b/Dockerfile-upgrade @@ -20,8 +20,8 @@ WORKDIR /go/delivery/zeta-node RUN mkdir -p $GOPATH/bin/old RUN mkdir -p $GOPATH/bin/new -ARG OLD_VERSION=v12.2.1 -ENV NEW_VERSION=v13 +ARG OLD_VERSION=v13.0.0 +ENV NEW_VERSION=v14 # Build new release from the current source COPY go.mod /go/delivery/zeta-node/ diff --git a/app/setup_handlers.go b/app/setup_handlers.go index b9fc28b562..b773745398 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -8,7 +8,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) -const releaseVersion = "v13" +const releaseVersion = "v14" func SetupHandlers(app *App) { app.UpgradeKeeper.SetUpgradeHandler(releaseVersion, func(ctx sdk.Context, plan types.Plan, vm module.VersionMap) (module.VersionMap, error) { From 67f79ed48be1724d0aaeb409354975046b9ccf3e Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 22 Feb 2024 14:35:19 +0100 Subject: [PATCH 04/33] type --- cmd/zetae2e/local/local.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 4e4ab4c8f5..e3d6a6b0b2 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -166,7 +166,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { setCosmosConfig() // wait for Genesis - // if setup is skipp, we assume that the genesis is already created + // if setup is skip, we assume that the genesis is already created if !skipSetup { logger.Print("⏳ wait 70s for genesis") time.Sleep(70 * time.Second) From c71c5200f609f0ae94043393158c692a8b9311b0 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 22 Feb 2024 14:35:42 +0100 Subject: [PATCH 05/33] fix restart client --- contrib/localnet/orchestrator/restart-zetaclientd.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/localnet/orchestrator/restart-zetaclientd.sh b/contrib/localnet/orchestrator/restart-zetaclientd.sh index 9b8f24e49f..7911deca01 100644 --- a/contrib/localnet/orchestrator/restart-zetaclientd.sh +++ b/contrib/localnet/orchestrator/restart-zetaclientd.sh @@ -46,5 +46,5 @@ echo upgrade height reached, restarting zetaclients for NODE in "${CLIENT_LIST[@]}"; do ssh -o "StrictHostKeyChecking no" "$NODE" -i ~/.ssh/localtest.pem killall zetaclientd - ssh -o "StrictHostKeyChecking no" "$NODE" -i ~/.ssh/localtest.pem "$GOPATH/bin/new/zetaclientd start < /dev/null > $HOME/zetaclient.log 2>&1 &" + ssh -o "StrictHostKeyChecking no" "$NODE" -i ~/.ssh/localtest.pem "$GOPATH/bin/new/zetaclientd start < /root/password.file > $HOME/zetaclient.log 2>&1 &" done From 5ce7868cdd0d28d2a6c4f80b50fd515530810752 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 22 Feb 2024 16:27:58 +0100 Subject: [PATCH 06/33] some fixes --- contrib/localnet/scripts/start-zetaclientd.sh | 2 +- e2e/runner/evm.go | 2 +- zetaclient/zetabridge/zetacore_bridge.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/localnet/scripts/start-zetaclientd.sh b/contrib/localnet/scripts/start-zetaclientd.sh index d519a1fbc9..761a58098d 100755 --- a/contrib/localnet/scripts/start-zetaclientd.sh +++ b/contrib/localnet/scripts/start-zetaclientd.sh @@ -2,7 +2,7 @@ # This script is used to start ZetaClient for the localnet # An optional argument can be passed and can have the following value: -# background: start the ZetaClient in the background +# background: start the ZetaClient in the background, this prevent the image from being stopped when ZetaClient must be restarted /usr/sbin/sshd diff --git a/e2e/runner/evm.go b/e2e/runner/evm.go index 75eaf75c2b..30f988f828 100644 --- a/e2e/runner/evm.go +++ b/e2e/runner/evm.go @@ -98,7 +98,7 @@ func (runner *E2ERunner) DepositERC20WithAmountAndMessage(amount *big.Int, msg [ runner.Logger.Info("USDT Approve receipt tx hash: %s", tx.Hash().Hex()) tx, err = runner.ERC20Custody.Deposit(runner.GoerliAuth, runner.DeployerAddress.Bytes(), runner.USDTERC20Addr, amount, msg) - runner.Logger.Print("TX: %v", tx) + runner.Logger.Info("TX: %v", tx) if err != nil { panic(err) } diff --git a/zetaclient/zetabridge/zetacore_bridge.go b/zetaclient/zetabridge/zetacore_bridge.go index 15a462f818..567c56be02 100644 --- a/zetaclient/zetabridge/zetacore_bridge.go +++ b/zetaclient/zetabridge/zetacore_bridge.go @@ -202,14 +202,14 @@ func (b *ZetaCoreBridge) UpdateConfigFromCore(cfg *config.Config, init bool) err if plan != nil && bn == plan.Height-1 { // stop zetaclients; notify operator to upgrade and restart b.logger.Warn().Msgf("Active upgrade plan detected and upgrade height reached: %s at height %d; ZetaClient is stopped;"+ "please kill this process, replace zetaclientd binary with upgraded version, and restart zetaclientd", plan.Name, plan.Height) - b.pause <- struct{}{} // notify CoreObserver to stop ChainClients, Signers, and CoreObservder itself + b.pause <- struct{}{} // notify CoreObserver to stop ChainClients, Signers, and CoreObserver itself } chainParams, err := b.GetChainParams() if err != nil { return err } - + newEVMParams := make(map[int64]*observertypes.ChainParams) var newBTCParams *observertypes.ChainParams From 09f81bd101227197909e14d90fae18d92736a9a4 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 22 Feb 2024 17:07:54 +0100 Subject: [PATCH 07/33] add light upgrade test --- LOCAL_TESTING.md | 16 +++++++-------- Makefile | 6 ++++++ .../localnet/docker-compose-upgrade-light.yml | 20 +++++++++++++++++++ contrib/localnet/orchestrator/start.sh | 16 ++++++++++++--- contrib/localnet/scripts/start-zetacored.sh | 5 ++++- zetaclient/zetabridge/zetacore_bridge.go | 2 +- 6 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 contrib/localnet/docker-compose-upgrade-light.yml diff --git a/LOCAL_TESTING.md b/LOCAL_TESTING.md index c127aa0e7d..0a13bfbbcd 100644 --- a/LOCAL_TESTING.md +++ b/LOCAL_TESTING.md @@ -17,7 +17,6 @@ $ make zetanode This Makefile rule builds the zetanode image. **Rebuild if zetacored/zetaclientd code is updated**. ```bash -# in zeta-node/ $ docker build -t zetanode . ``` @@ -25,7 +24,6 @@ $ docker build -t zetanode . Now we have built all the docker images, we can run the e2e test with make command: ```bash -# in zeta-node/ make start-e2e-test ``` @@ -46,10 +44,15 @@ NOTE: We only specify the major version for `NEW_VERSION` since we use major ver The upgrade tests can be run with the following command: ```bash -# in zeta-node/ make start-upgrade-test ``` +The test the upgrade script faster a light version of the upgrade test can be run with the following command: +```bash +make start-upgrade-test-light +``` +This command will run the upgrade test with a lower height and will not populate the state. + ### Run stress tests Stress tests run the E2E tests with a larger number of nodes and clients to test the performance of the network. @@ -57,7 +60,6 @@ It also stresses the network by sending a large number of cross-chain transactio The stress tests can be run with the following command: ```bash -# in zeta-node/ make start-stress-test ``` @@ -68,7 +70,7 @@ If everything works fine, it should finish without panic. The logs can be observed with the following command: ```bash -# in zeta-node/contrib/localnet/orchestrator +# in node/contrib/localnet/orchestrator $ docker logs -f orchestrator ``` @@ -76,7 +78,6 @@ $ docker logs -f orchestrator To stop the tests, ```bash -# in zeta-node/ make stop-test ``` @@ -86,7 +87,6 @@ Before starting the monitoring setup, make sure the Zetacore API is up at http:/ You can also add any additional ETH addresses to monitor in `zeta-node/contrib/localnet/grafana/addresses.txt` file ```bash -# in zeta-node/ make start-monitoring ``` @@ -97,7 +97,6 @@ The Grafana default credentials are admin:admin. The dashboards are located at h ### Stop monitoring setup ```bash -# in zeta-node/ make stop-monitoring ``` @@ -108,7 +107,6 @@ In addition to running automated tests, you can also interact with the localnet The localnet can be started without running tests with the following command: ```bash -# in zeta-node/ make start-localnet ``` diff --git a/Makefile b/Makefile index 4ec78d1d68..3a1501c611 100644 --- a/Makefile +++ b/Makefile @@ -206,6 +206,12 @@ start-upgrade-test: $(DOCKER) build -t orchestrator -f contrib/localnet/orchestrator/Dockerfile.fastbuild . cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-upgrade.yml up -d +start-upgrade-test-light: + @echo "--> Starting light upgrade test (no ZetaChain state populating before upgrade)" + $(DOCKER) build -t zetanode -f ./Dockerfile-upgrade . + $(DOCKER) build -t orchestrator -f contrib/localnet/orchestrator/Dockerfile.fastbuild . + cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-upgrade-light.yml up -d + start-localnet: @echo "--> Starting localnet" cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-setup-only.yml up -d diff --git a/contrib/localnet/docker-compose-upgrade-light.yml b/contrib/localnet/docker-compose-upgrade-light.yml new file mode 100644 index 0000000000..1a1c30b24c --- /dev/null +++ b/contrib/localnet/docker-compose-upgrade-light.yml @@ -0,0 +1,20 @@ +version: "3" + +# This docker-compose is similar to the docker-compose-upgrade.yml, but it uses a smaller height option for the upgrade (90) +# By using 90, the orchestrator will automatically run setup only for the first e2e test execution. + +services: + zetacore0: + entrypoint: ["/root/start-zetacored.sh", "2", "upgrade", "90"] + + zetacore1: + entrypoint: ["/root/start-zetacored.sh", "2", "upgrade", "90"] + + zetaclient0: + entrypoint: ["/root/start-zetaclientd.sh", "background"] + + zetaclient1: + entrypoint: ["/root/start-zetaclientd.sh", "background"] + + orchestrator: + entrypoint: ["/work/start.sh", "local", "upgrade", "90"] diff --git a/contrib/localnet/orchestrator/start.sh b/contrib/localnet/orchestrator/start.sh index 69fc9056a5..d5e49af490 100644 --- a/contrib/localnet/orchestrator/start.sh +++ b/contrib/localnet/orchestrator/start.sh @@ -8,6 +8,7 @@ ZETAE2E_CMD=$1 OPTION=$2 + echo "waiting for geth RPC to start..." sleep 2 @@ -51,8 +52,17 @@ if [ "$OPTION" == "upgrade" ]; then # Run the e2e tests, then restart zetaclientd at upgrade height and run the e2e tests again - echo "running E2E command to setup the networks and populate the state..." - zetae2e "$ZETAE2E_CMD" --config-out deployed.yml + # Fetch the height of the upgrade, default is 200, if arg3 is passed, use that value + UPGRADE_HEIGHT=${3:-200} + + # Run zetae2e, if the upgrade height is lower than 100, we use the setup-only flag + if [ "$UPGRADE_HEIGHT" -lt 100 ]; then + echo "running E2E command to setup the networks..." + zetae2e "$ZETAE2E_CMD" --setup-only --config-out deployed.yml + else + echo "running E2E command to setup the networks and populate the state..." + zetae2e "$ZETAE2E_CMD" --config-out deployed.yml + fi ZETAE2E_EXIT_CODE=$? if [ $ZETAE2E_EXIT_CODE -ne 0 ]; then @@ -63,7 +73,7 @@ if [ "$OPTION" == "upgrade" ]; then echo "E2E setup passed, waiting for upgrade height..." # Restart zetaclients at upgrade height - /work/restart-zetaclientd.sh -u 200 -n 2 + /work/restart-zetaclientd.sh -u "$UPGRADE_HEIGHT" -n 2 echo "waiting 10 seconds for node to restart..." diff --git a/contrib/localnet/scripts/start-zetacored.sh b/contrib/localnet/scripts/start-zetacored.sh index 559f07c862..328c25d9e3 100755 --- a/contrib/localnet/scripts/start-zetacored.sh +++ b/contrib/localnet/scripts/start-zetacored.sh @@ -208,10 +208,13 @@ else sleep 20 echo + # Fetch the height of the upgrade, default is 200, if arg3 is passed, use that value + UPGRADE_HEIGHT=${3:-200} + # If this is the first node, create a governance proposal for upgrade if [ $HOSTNAME = "zetacore0" ] then - /root/.zetacored/cosmovisor/genesis/bin/zetacored tx gov submit-legacy-proposal software-upgrade $UpgradeName --from hotkey --deposit 100000000azeta --upgrade-height 200 --title $UpgradeName --description $UpgradeName --keyring-backend test --chain-id $CHAINID --yes --no-validate --fees=2000000000000000azeta --broadcast-mode block + /root/.zetacored/cosmovisor/genesis/bin/zetacored tx gov submit-legacy-proposal software-upgrade $UpgradeName --from hotkey --deposit 100000000azeta --upgrade-height "$UPGRADE_HEIGHT" --title $UpgradeName --description $UpgradeName --keyring-backend test --chain-id $CHAINID --yes --no-validate --fees=2000000000000000azeta --broadcast-mode block fi # Wait for the proposal to be voted on diff --git a/zetaclient/zetabridge/zetacore_bridge.go b/zetaclient/zetabridge/zetacore_bridge.go index 567c56be02..1f923a690b 100644 --- a/zetaclient/zetabridge/zetacore_bridge.go +++ b/zetaclient/zetabridge/zetacore_bridge.go @@ -209,7 +209,7 @@ func (b *ZetaCoreBridge) UpdateConfigFromCore(cfg *config.Config, init bool) err if err != nil { return err } - + newEVMParams := make(map[int64]*observertypes.ChainParams) var newBTCParams *observertypes.ChainParams From e137e4832862fad9f29cd122c39d61597d0b61a2 Mon Sep 17 00:00:00 2001 From: lumtis Date: Fri, 23 Feb 2024 13:49:46 +0100 Subject: [PATCH 08/33] fix gov sending --- LOCAL_TESTING.md | 10 ++++++++++ contrib/localnet/scripts/start-zetacored.sh | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/LOCAL_TESTING.md b/LOCAL_TESTING.md index 0a13bfbbcd..471b226404 100644 --- a/LOCAL_TESTING.md +++ b/LOCAL_TESTING.md @@ -27,6 +27,16 @@ Now we have built all the docker images, we can run the e2e test with make comma make start-e2e-test ``` +#### Run admin functions e2e tests + +We define e2e tests allowing to test admin functionalities (emergency network pause for example). +Since these tests interact with the network functionalities, these can't be run concurrently with the regular e2e tests. +Moreover, these tests test scoped functionalities of the protocol, and won't be tested in the same pipeline as the regular e2e tests. +Therefore, we provide a separate command to run e2e admin functions tests: +```bash +make start-e2e-admin-test +``` + ### Run upgrade tests Upgrade tests run the E2E tests with an older version, upgrade the nodes to the new version, and run the E2E tests again. diff --git a/contrib/localnet/scripts/start-zetacored.sh b/contrib/localnet/scripts/start-zetacored.sh index 328c25d9e3..679a12e2b1 100755 --- a/contrib/localnet/scripts/start-zetacored.sh +++ b/contrib/localnet/scripts/start-zetacored.sh @@ -214,7 +214,7 @@ else # If this is the first node, create a governance proposal for upgrade if [ $HOSTNAME = "zetacore0" ] then - /root/.zetacored/cosmovisor/genesis/bin/zetacored tx gov submit-legacy-proposal software-upgrade $UpgradeName --from hotkey --deposit 100000000azeta --upgrade-height "$UPGRADE_HEIGHT" --title $UpgradeName --description $UpgradeName --keyring-backend test --chain-id $CHAINID --yes --no-validate --fees=2000000000000000azeta --broadcast-mode block + /root/.zetacored/cosmovisor/genesis/bin/zetacored tx gov submit-legacy-proposal software-upgrade $UpgradeName --from operator --deposit 100000000azeta --upgrade-height "$UPGRADE_HEIGHT" --title $UpgradeName --description $UpgradeName --keyring-backend test --chain-id $CHAINID --yes --no-validate --fees=2000000000000000azeta --broadcast-mode block fi # Wait for the proposal to be voted on From 1060abe48b809b626d92906fd02031cd8e15b5bf Mon Sep 17 00:00:00 2001 From: lumtis Date: Fri, 23 Feb 2024 14:32:19 +0100 Subject: [PATCH 09/33] add admin function test command --- Makefile | 9 +++++++-- contrib/localnet/docker-compose-admin.yml | 9 +++++++++ contrib/localnet/docker-compose-setup-only.yml | 2 +- contrib/localnet/orchestrator/start.sh | 18 ++---------------- 4 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 contrib/localnet/docker-compose-admin.yml diff --git a/Makefile b/Makefile index 3a1501c611..ab0605f928 100644 --- a/Makefile +++ b/Makefile @@ -193,11 +193,16 @@ install-zetae2e: go.sum @go install -mod=readonly $(BUILD_FLAGS) ./cmd/zetae2e .PHONY: install-zetae2e -start-e2e-test: +start-e2e-test: zetanode @echo "--> Starting e2e test" cd contrib/localnet/ && $(DOCKER) compose up -d +start-e2e-admin-test: zetanode + @echo "--> Starting e2e admin test" + cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-admin.yml up -d + start-stress-test: zetanode + @echo "--> Starting stress test" cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-stresstest.yml up -d start-upgrade-test: @@ -212,7 +217,7 @@ start-upgrade-test-light: $(DOCKER) build -t orchestrator -f contrib/localnet/orchestrator/Dockerfile.fastbuild . cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-upgrade-light.yml up -d -start-localnet: +start-localnet: zetanode @echo "--> Starting localnet" cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-setup-only.yml up -d diff --git a/contrib/localnet/docker-compose-admin.yml b/contrib/localnet/docker-compose-admin.yml new file mode 100644 index 0000000000..9ada20f51d --- /dev/null +++ b/contrib/localnet/docker-compose-admin.yml @@ -0,0 +1,9 @@ +version: "3" + +# This docker-compose file overrides the orcherstrator service to specify the flag to test the admin functions +# and skip the regular tests + +services: + orchestrator: + entrypoint: ["/work/start.sh", "local --skip-regular --test-admin"] + diff --git a/contrib/localnet/docker-compose-setup-only.yml b/contrib/localnet/docker-compose-setup-only.yml index c4d3dbb4c3..979f448527 100644 --- a/contrib/localnet/docker-compose-setup-only.yml +++ b/contrib/localnet/docker-compose-setup-only.yml @@ -5,5 +5,5 @@ version: "3" services: orchestrator: - entrypoint: ["/work/start.sh", "local", "setup-only"] + entrypoint: ["/work/start.sh", "local --setup-only"] diff --git a/contrib/localnet/orchestrator/start.sh b/contrib/localnet/orchestrator/start.sh index d5e49af490..bd31e8213c 100644 --- a/contrib/localnet/orchestrator/start.sh +++ b/contrib/localnet/orchestrator/start.sh @@ -92,27 +92,13 @@ if [ "$OPTION" == "upgrade" ]; then exit 1 fi -elif [ "$OPTION" == "setup-only" ]; then - - # Setup localnet with the --setup-only flag - - zetae2e "$ZETAE2E_CMD" --setup-only - - ZETAE2E_EXIT_CODE=$? - if [ $ZETAE2E_EXIT_CODE -eq 0 ]; then - echo "Localnet setup" - exit 0 - else - echo "Localnet failed to start" - exit 1 - fi - else # Run the e2e tests normally echo "running e2e tests..." - zetae2e "$ZETAE2E_CMD" + # zetae2e "$ZETAE2E_CMD" + eval "zetae2e $ZETAE2E_CMD" ZETAE2E_EXIT_CODE=$? # if e2e passed, exit with 0, otherwise exit with 1 From e99e7881906cb05e1f51ec673b035287b2a1dd86 Mon Sep 17 00:00:00 2001 From: lumtis Date: Fri, 23 Feb 2024 16:07:38 +0100 Subject: [PATCH 10/33] performance test --- Makefile | 4 + cmd/zetae2e/local/local.go | 13 ++ cmd/zetae2e/local/performance.go | 114 ++++++++++++++++++ contrib/localnet/docker-compose-admin.yml | 2 +- .../localnet/docker-compose-performance.yml | 9 ++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 cmd/zetae2e/local/performance.go create mode 100644 contrib/localnet/docker-compose-performance.yml diff --git a/Makefile b/Makefile index ab0605f928..99100766fb 100644 --- a/Makefile +++ b/Makefile @@ -201,6 +201,10 @@ start-e2e-admin-test: zetanode @echo "--> Starting e2e admin test" cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-admin.yml up -d +start-e2e-performance-test: zetanode + @echo "--> Starting e2e performance test" + cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-performance.yml up -d + start-stress-test: zetanode @echo "--> Starting stress test" cd contrib/localnet/ && $(DOCKER) compose -f docker-compose.yml -f docker-compose-stresstest.yml up -d diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index e3d6a6b0b2..196ceb981f 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -21,6 +21,7 @@ const ( FlagConfigFile = "config" flagVerbose = "verbose" flagTestAdmin = "test-admin" + flagTestPerformance = "test-performance" flagTestCustom = "test-custom" flagSkipRegular = "skip-regular" flagSetupOnly = "setup-only" @@ -65,6 +66,11 @@ func NewLocalCmd() *cobra.Command { false, "set to true to run admin tests", ) + cmd.Flags().Bool( + flagTestPerformance, + false, + "set to true to run performance tests", + ) cmd.Flags().Bool( flagTestCustom, false, @@ -113,6 +119,10 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if err != nil { panic(err) } + testPerformance, err := cmd.Flags().GetBool(flagTestPerformance) + if err != nil { + panic(err) + } testCustom, err := cmd.Flags().GetBool(flagTestCustom) if err != nil { panic(err) @@ -244,6 +254,9 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if testAdmin { eg.Go(adminTestRoutine(conf, deployerRunner, verbose)) } + if testPerformance { + eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose)) + } if testCustom { eg.Go(miscTestRoutine(conf, deployerRunner, verbose)) } diff --git a/cmd/zetae2e/local/performance.go b/cmd/zetae2e/local/performance.go new file mode 100644 index 0000000000..e34b6313c0 --- /dev/null +++ b/cmd/zetae2e/local/performance.go @@ -0,0 +1,114 @@ +package local + +// performance.go provides routines that run the stress tests for different actions (deposit, withdraw) to measure network performance +// Note: the routine provided here should not be used concurrently with other routines as these reuse the accounts of other routines + +import ( + "fmt" + "github.com/fatih/color" + "github.com/zeta-chain/zetacore/e2e/config" + "github.com/zeta-chain/zetacore/e2e/e2etests" + "github.com/zeta-chain/zetacore/e2e/runner" + "runtime" + "time" +) + +// ethereumDepositPerformanceRoutine runs Ethereum withdraw stress tests +func ethereumDepositPerformanceRoutine( + conf config.Config, + deployerRunner *runner.E2ERunner, + verbose bool, +) func() error { + return func() (err error) { + // return an error on panic + // TODO: remove and instead return errors in the tests + // https://github.com/zeta-chain/node/issues/1500 + defer func() { + if r := recover(); r != nil { + // print stack trace + stack := make([]byte, 4096) + n := runtime.Stack(stack, false) + err = fmt.Errorf("ethereum deposit perf panic: %v, stack trace %s", r, stack[:n]) + } + }() + + // initialize runner for ether test + ethereumRunner, err := initTestRunner( + "ether", + conf, + deployerRunner, + UserEtherAddress, + UserEtherPrivateKey, + runner.NewLogger(verbose, color.FgMagenta, "perf_eth_deposit"), + ) + if err != nil { + return err + } + + ethereumRunner.Logger.Print("🏃 starting Ethereum deposit performance tests") + startTime := time.Now() + + if err := ethereumRunner.RunE2ETestsFromNames( + e2etests.AllE2ETests, + e2etests.TestStressEtherDepositName, + ); err != nil { + return fmt.Errorf("thereum deposit performance test failed: %v", err) + } + + ethereumRunner.Logger.Print("🍾 Ethereum deposit performance test completed in %s", time.Since(startTime).String()) + + return err + } +} + +// ethereumWithdrawPerformanceRoutine runs Ethereum withdraw stress tests +func ethereumWithdrawPerformanceRoutine( + conf config.Config, + deployerRunner *runner.E2ERunner, + verbose bool, +) func() error { + return func() (err error) { + // return an error on panic + // TODO: remove and instead return errors in the tests + // https://github.com/zeta-chain/node/issues/1500 + defer func() { + if r := recover(); r != nil { + // print stack trace + stack := make([]byte, 4096) + n := runtime.Stack(stack, false) + err = fmt.Errorf("ethereum withdraw perf panic: %v, stack trace %s", r, stack[:n]) + } + }() + + // initialize runner for ether test + ethereumRunner, err := initTestRunner( + "ether", + conf, + deployerRunner, + UserEtherAddress, + UserEtherPrivateKey, + runner.NewLogger(verbose, color.FgMagenta, "perf_eth_withdraw"), + ) + if err != nil { + return err + } + + ethereumRunner.Logger.Print("🏃 starting Ethereum withdraw performance tests") + startTime := time.Now() + + // depositing the necessary tokens on ZetaChain + txEtherDeposit := ethereumRunner.DepositEther(true) + ethereumRunner.WaitForMinedCCTX(txEtherDeposit) + + if err := ethereumRunner.RunE2ETestsFromNames( + e2etests.AllE2ETests, + e2etests.TestStressEtherWithdrawName, + ); err != nil { + return fmt.Errorf("thereum withdraw performance test failed: %v", err) + } + + ethereumRunner.Logger.Print("🍾 Ethereum withdraw performance test completed in %s", time.Since(startTime).String()) + + return err + } +} diff --git a/contrib/localnet/docker-compose-admin.yml b/contrib/localnet/docker-compose-admin.yml index 9ada20f51d..53e57eec05 100644 --- a/contrib/localnet/docker-compose-admin.yml +++ b/contrib/localnet/docker-compose-admin.yml @@ -1,6 +1,6 @@ version: "3" -# This docker-compose file overrides the orcherstrator service to specify the flag to test the admin functions +# This docker-compose file overrides the orchestrator service to specify the flag to test the admin functions # and skip the regular tests services: diff --git a/contrib/localnet/docker-compose-performance.yml b/contrib/localnet/docker-compose-performance.yml new file mode 100644 index 0000000000..0e92d7f1bd --- /dev/null +++ b/contrib/localnet/docker-compose-performance.yml @@ -0,0 +1,9 @@ +version: "3" + +# This docker-compose file overrides the orchestrator service to specify the flag to test performance of cctxs +# and skip the regular tests + +services: + orchestrator: + entrypoint: ["/work/start.sh", "local --skip-regular --test-performance"] + From a7cb7f56a84e659abf18bcf840c11022ff05cb4f Mon Sep 17 00:00:00 2001 From: lumtis Date: Fri, 23 Feb 2024 17:44:16 +0100 Subject: [PATCH 11/33] fix lint --- cmd/zetae2e/local/local.go | 1 + cmd/zetae2e/local/performance.go | 9 +++++---- e2e/e2etests/test_stress_eth_deposit.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 196ceb981f..8cc2e7d77c 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -256,6 +256,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } if testPerformance { eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose)) + eg.Go(ethereumWithdrawPerformanceRoutine(conf, deployerRunner, verbose)) } if testCustom { eg.Go(miscTestRoutine(conf, deployerRunner, verbose)) diff --git a/cmd/zetae2e/local/performance.go b/cmd/zetae2e/local/performance.go index e34b6313c0..506dad9b33 100644 --- a/cmd/zetae2e/local/performance.go +++ b/cmd/zetae2e/local/performance.go @@ -5,12 +5,13 @@ package local import ( "fmt" + "runtime" + "time" + "github.com/fatih/color" "github.com/zeta-chain/zetacore/e2e/config" "github.com/zeta-chain/zetacore/e2e/e2etests" "github.com/zeta-chain/zetacore/e2e/runner" - "runtime" - "time" ) // ethereumDepositPerformanceRoutine runs Ethereum withdraw stress tests @@ -37,8 +38,8 @@ func ethereumDepositPerformanceRoutine( "ether", conf, deployerRunner, - UserEtherAddress, - UserEtherPrivateKey, + UserERC20Address, + UserERC20PrivateKey, runner.NewLogger(verbose, color.FgMagenta, "perf_eth_deposit"), ) if err != nil { diff --git a/e2e/e2etests/test_stress_eth_deposit.go b/e2e/e2etests/test_stress_eth_deposit.go index ea5f3919f5..74e99071e4 100644 --- a/e2e/e2etests/test_stress_eth_deposit.go +++ b/e2e/e2etests/test_stress_eth_deposit.go @@ -25,7 +25,7 @@ func TestStressEtherDeposit(r *runner.E2ERunner) { // send the deposits for i := 0; i < numDeposits; i++ { i := i - hash := r.DepositERC20WithAmountAndMessage(big.NewInt(100000), []byte{}) + hash := r.DepositEtherWithAmount(false, big.NewInt(100000)) r.Logger.Print("index %d: starting deposit, tx hash: %s", i, hash.Hex()) eg.Go(func() error { From 6e495df9075cd9eba37ec96b861740141368d1aa Mon Sep 17 00:00:00 2001 From: lumtis Date: Tue, 27 Feb 2024 10:49:28 +0100 Subject: [PATCH 12/33] add pprof port --- contrib/local-mainnet/zetacored/configs/config.toml | 2 +- contrib/localnet/docker-compose.yml | 1 + contrib/localnet/zetacored/common/config.toml | 2 +- .../localnet/zetacored/zetacored_zetacore0/config/config.toml | 2 +- .../localnet/zetacored/zetacored_zetacore1/config/config.toml | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/local-mainnet/zetacored/configs/config.toml b/contrib/local-mainnet/zetacored/configs/config.toml index 50611238fe..b44d63fb0d 100644 --- a/contrib/local-mainnet/zetacored/configs/config.toml +++ b/contrib/local-mainnet/zetacored/configs/config.toml @@ -32,7 +32,7 @@ max_body_bytes = 1000000 max_header_bytes = 1048576 tls_cert_file = "" tls_key_file = "" -pprof_laddr = "localhost:6060" +pprof_laddr = "0.0.0.0:6060" [p2p] laddr = "tcp://0.0.0.0:26656" diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index c43d77ff8b..a78e040e33 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -39,6 +39,7 @@ services: - "9545:8545" - "9546:8546" - "26657:26657" + - "6060:6060" networks: mynetwork: ipv4_address: 172.20.0.11 diff --git a/contrib/localnet/zetacored/common/config.toml b/contrib/localnet/zetacored/common/config.toml index c0f955ea4b..c357395ce1 100644 --- a/contrib/localnet/zetacored/common/config.toml +++ b/contrib/localnet/zetacored/common/config.toml @@ -191,7 +191,7 @@ tls_cert_file = "" tls_key_file = "" # pprof listen address (https://golang.org/pkg/net/http/pprof) -pprof_laddr = "localhost:6060" +pprof_laddr = "0.0.0.0:6060" ####################################################### ### P2P Configuration Options ### diff --git a/contrib/localnet/zetacored/zetacored_zetacore0/config/config.toml b/contrib/localnet/zetacored/zetacored_zetacore0/config/config.toml index 4178dde511..5f915131fc 100644 --- a/contrib/localnet/zetacored/zetacored_zetacore0/config/config.toml +++ b/contrib/localnet/zetacored/zetacored_zetacore0/config/config.toml @@ -191,7 +191,7 @@ tls_cert_file = "" tls_key_file = "" # pprof listen address (https://golang.org/pkg/net/http/pprof) -pprof_laddr = "localhost:6060" +pprof_laddr = "0.0.0.0:6060" ####################################################### ### P2P Configuration Options ### diff --git a/contrib/localnet/zetacored/zetacored_zetacore1/config/config.toml b/contrib/localnet/zetacored/zetacored_zetacore1/config/config.toml index 6bbaf13d27..d9f89c1f2e 100644 --- a/contrib/localnet/zetacored/zetacored_zetacore1/config/config.toml +++ b/contrib/localnet/zetacored/zetacored_zetacore1/config/config.toml @@ -191,7 +191,7 @@ tls_cert_file = "" tls_key_file = "" # pprof listen address (https://golang.org/pkg/net/http/pprof) -pprof_laddr = "localhost:6060" +pprof_laddr = "0.0.0.0:6060" ####################################################### ### P2P Configuration Options ### From 1b818c853e28f7755fc7e9b9b182f8accfb011fa Mon Sep 17 00:00:00 2001 From: lumtis Date: Tue, 27 Feb 2024 10:49:45 +0100 Subject: [PATCH 13/33] some fixes in tests --- cmd/zetae2e/local/local.go | 7 ++++++- cmd/zetae2e/local/performance.go | 24 ++++++++++++------------ e2e/e2etests/test_stress_eth_withdraw.go | 6 ++++++ e2e/runner/zeta.go | 22 +++++++++++++++++----- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 8cc2e7d77c..02ddcd5081 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -151,6 +151,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { logger.Print("⚠️ admin tests enabled") } + if testPerformance && !skipRegular { + logger.Print("⚠️ performance tests enabled, regular tests will be skipped") + skipRegular = true + } + // start timer go func() { time.Sleep(TestTimeout) @@ -255,7 +260,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { eg.Go(adminTestRoutine(conf, deployerRunner, verbose)) } if testPerformance { - eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose)) + //eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose)) eg.Go(ethereumWithdrawPerformanceRoutine(conf, deployerRunner, verbose)) } if testCustom { diff --git a/cmd/zetae2e/local/performance.go b/cmd/zetae2e/local/performance.go index 506dad9b33..f9557c117a 100644 --- a/cmd/zetae2e/local/performance.go +++ b/cmd/zetae2e/local/performance.go @@ -34,29 +34,29 @@ func ethereumDepositPerformanceRoutine( }() // initialize runner for ether test - ethereumRunner, err := initTestRunner( + r, err := initTestRunner( "ether", conf, deployerRunner, UserERC20Address, UserERC20PrivateKey, - runner.NewLogger(verbose, color.FgMagenta, "perf_eth_deposit"), + runner.NewLogger(verbose, color.FgHiMagenta, "perf_eth_deposit"), ) if err != nil { return err } - ethereumRunner.Logger.Print("🏃 starting Ethereum deposit performance tests") + r.Logger.Print("🏃 starting Ethereum deposit performance tests") startTime := time.Now() - if err := ethereumRunner.RunE2ETestsFromNames( + if err := r.RunE2ETestsFromNames( e2etests.AllE2ETests, e2etests.TestStressEtherDepositName, ); err != nil { return fmt.Errorf("thereum deposit performance test failed: %v", err) } - ethereumRunner.Logger.Print("🍾 Ethereum deposit performance test completed in %s", time.Since(startTime).String()) + r.Logger.Print("🍾 Ethereum deposit performance test completed in %s", time.Since(startTime).String()) return err } @@ -82,33 +82,33 @@ func ethereumWithdrawPerformanceRoutine( }() // initialize runner for ether test - ethereumRunner, err := initTestRunner( + r, err := initTestRunner( "ether", conf, deployerRunner, UserEtherAddress, UserEtherPrivateKey, - runner.NewLogger(verbose, color.FgMagenta, "perf_eth_withdraw"), + runner.NewLogger(verbose, color.FgHiBlue, "perf_eth_withdraw"), ) if err != nil { return err } - ethereumRunner.Logger.Print("🏃 starting Ethereum withdraw performance tests") + r.Logger.Print("🏃 starting Ethereum withdraw performance tests") startTime := time.Now() // depositing the necessary tokens on ZetaChain - txEtherDeposit := ethereumRunner.DepositEther(true) - ethereumRunner.WaitForMinedCCTX(txEtherDeposit) + txEtherDeposit := r.DepositEther(false) + r.WaitForMinedCCTX(txEtherDeposit) - if err := ethereumRunner.RunE2ETestsFromNames( + if err := r.RunE2ETestsFromNames( e2etests.AllE2ETests, e2etests.TestStressEtherWithdrawName, ); err != nil { return fmt.Errorf("thereum withdraw performance test failed: %v", err) } - ethereumRunner.Logger.Print("🍾 Ethereum withdraw performance test completed in %s", time.Since(startTime).String()) + r.Logger.Print("🍾 Ethereum withdraw performance test completed in %s", time.Since(startTime).String()) return err } diff --git a/e2e/e2etests/test_stress_eth_withdraw.go b/e2e/e2etests/test_stress_eth_withdraw.go index 2e1114d2aa..0d74b26689 100644 --- a/e2e/e2etests/test_stress_eth_withdraw.go +++ b/e2e/e2etests/test_stress_eth_withdraw.go @@ -19,6 +19,12 @@ func TestStressEtherWithdraw(r *runner.E2ERunner) { // number of withdraws to perform numWithdraws := 100 + tx, err := r.ETHZRC20.Approve(r.ZevmAuth, r.ETHZRC20Addr, big.NewInt(1e18)) + if err != nil { + panic(err) + } + r.WaitForTxReceiptOnZEVM(tx) + r.Logger.Print("starting stress test of %d withdraws", numWithdraws) // create a wait group to wait for all the withdraws to complete diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go index b5df8145d9..0795c59a1d 100644 --- a/e2e/runner/zeta.go +++ b/e2e/runner/zeta.go @@ -4,14 +4,26 @@ import ( "fmt" "math/big" - utils2 "github.com/zeta-chain/zetacore/e2e/utils" - ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" + "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/x/crosschain/types" ) +// WaitForTxReceiptOnZEVM waits for a tx receipt on ZEVM +func (runner *E2ERunner) WaitForTxReceiptOnZEVM(tx *ethtypes.Transaction) { + defer func() { + runner.Unlock() + }() + runner.Lock() + + receipt := utils.MustWaitForTxReceipt(runner.Ctx, runner.ZevmClient, tx, runner.Logger, runner.ReceiptTimeout) + if receipt.Status != 1 { + panic("tx failed") + } +} + // WaitForMinedCCTX waits for a cctx to be mined from a tx func (runner *E2ERunner) WaitForMinedCCTX(txHash ethcommon.Hash) { defer func() { @@ -19,7 +31,7 @@ func (runner *E2ERunner) WaitForMinedCCTX(txHash ethcommon.Hash) { }() runner.Lock() - cctx := utils2.WaitCctxMinedByInTxHash(runner.Ctx, txHash.Hex(), runner.CctxClient, runner.Logger, runner.CctxTimeout) + cctx := utils.WaitCctxMinedByInTxHash(runner.Ctx, txHash.Hex(), runner.CctxClient, runner.Logger, runner.CctxTimeout) if cctx.CctxStatus.Status != types.CctxStatus_OutboundMined { panic(fmt.Sprintf("expected cctx status to be mined; got %s, message: %s", cctx.CctxStatus.Status.String(), @@ -62,7 +74,7 @@ func (runner *E2ERunner) DepositZetaWithAmount(amount *big.Int) ethcommon.Hash { } runner.Logger.Info("Approve tx hash: %s", tx.Hash().Hex()) - receipt := utils2.MustWaitForTxReceipt(runner.Ctx, runner.GoerliClient, tx, runner.Logger, runner.ReceiptTimeout) + receipt := utils.MustWaitForTxReceipt(runner.Ctx, runner.GoerliClient, tx, runner.Logger, runner.ReceiptTimeout) runner.Logger.EVMReceipt(*receipt, "approve") if receipt.Status != 1 { panic("approve tx failed") @@ -89,7 +101,7 @@ func (runner *E2ERunner) DepositZetaWithAmount(amount *big.Int) ethcommon.Hash { } runner.Logger.Info("Send tx hash: %s", tx.Hash().Hex()) - receipt = utils2.MustWaitForTxReceipt(runner.Ctx, runner.GoerliClient, tx, runner.Logger, runner.ReceiptTimeout) + receipt = utils.MustWaitForTxReceipt(runner.Ctx, runner.GoerliClient, tx, runner.Logger, runner.ReceiptTimeout) runner.Logger.EVMReceipt(*receipt, "send") if receipt.Status != 1 { panic(fmt.Sprintf("expected tx receipt status to be 1; got %d", receipt.Status)) From 9d265928f3100b8d78f2b60de032cbfd5af15052 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Thu, 22 Feb 2024 13:07:53 -0500 Subject: [PATCH 14/33] test: emissions unit test (#1767) --- changelog.md | 7 + testutil/keeper/emissions.go | 102 +++- testutil/keeper/keeper.go | 24 +- testutil/keeper/mocks/crosschain/account.go | 2 +- testutil/keeper/mocks/crosschain/bank.go | 2 +- testutil/keeper/mocks/crosschain/fungible.go | 2 +- testutil/keeper/mocks/crosschain/observer.go | 2 +- testutil/keeper/mocks/crosschain/staking.go | 2 +- testutil/keeper/mocks/emissions/account.go | 49 ++ testutil/keeper/mocks/emissions/bank.go | 64 +++ testutil/keeper/mocks/emissions/observer.go | 78 +++ testutil/keeper/mocks/emissions/staking.go | 46 ++ testutil/keeper/mocks/fungible/account.go | 2 +- testutil/keeper/mocks/fungible/bank.go | 2 +- testutil/keeper/mocks/fungible/evm.go | 2 +- testutil/keeper/mocks/fungible/observer.go | 2 +- testutil/keeper/mocks/mocks.go | 21 + testutil/sample/observer.go | 29 ++ testutil/sample/sample.go | 5 + x/emissions/abci.go | 4 +- x/emissions/abci_test.go | 513 +++++++++++-------- x/emissions/genesis_test.go | 2 +- x/emissions/keeper/params_test.go | 5 +- 23 files changed, 686 insertions(+), 281 deletions(-) create mode 100644 testutil/keeper/mocks/emissions/account.go create mode 100644 testutil/keeper/mocks/emissions/bank.go create mode 100644 testutil/keeper/mocks/emissions/observer.go create mode 100644 testutil/keeper/mocks/emissions/staking.go diff --git a/changelog.md b/changelog.md index 2080bde846..9116cc3651 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # CHANGELOG +## Unreleased + +### Tests + +* [1767](https://github.com/zeta-chain/node/pull/1767) - add unit tests for emissions module begin blocker + ## Version: v13.0.0 * `zetaclientd start` : 2 inputs required from stdin @@ -41,6 +47,7 @@ * [1584](https://github.com/zeta-chain/node/pull/1584) - allow to run E2E tests on any networks * [1746](https://github.com/zeta-chain/node/pull/1746) - rename smoke tests to e2e tests * [1753](https://github.com/zeta-chain/node/pull/1753) - fix gosec errors on usage of rand package + * [1762](https://github.com/zeta-chain/node/pull/1762) - improve coverage for fungibile module * [1782](https://github.com/zeta-chain/node/pull/1782) - improve coverage for fungibile module system contract diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index 4e57ced16d..988859c762 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -3,57 +3,105 @@ package keeper import ( "testing" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - typesparams "github.com/cosmos/cosmos-sdk/x/params/types" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmdb "github.com/tendermint/tm-db" + emissionsmocks "github.com/zeta-chain/zetacore/testutil/keeper/mocks/emissions" "github.com/zeta-chain/zetacore/x/emissions/keeper" "github.com/zeta-chain/zetacore/x/emissions/types" - observerkeeper "github.com/zeta-chain/zetacore/x/observer/keeper" ) -func EmissionsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { +type EmissionMockOptions struct { + UseBankMock bool + UseStakingMock bool + UseObserverMock bool + UseAccountMock bool +} + +func EmissionsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, SDKKeepers, ZetaKeepers) { + return EmissionKeeperWithMockOptions(t, EmissionMockOptions{ + UseBankMock: false, + UseStakingMock: false, + UseObserverMock: false, + }) +} +func EmissionKeeperWithMockOptions( + t testing.TB, + mockOptions EmissionMockOptions, +) (*keeper.Keeper, sdk.Context, SDKKeepers, ZetaKeepers) { + SetConfig(false) storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + // Initialize local store db := tmdb.NewMemDB() stateStore := store.NewCommitMultiStore(db) + cdc := NewCodec() + + // Create regular keepers + sdkKeepers := NewSDKKeepers(cdc, db, stateStore) + + // Create zeta keepers + observerKeeperTmp := initObserverKeeper( + cdc, + db, + stateStore, + sdkKeepers.StakingKeeper, + sdkKeepers.SlashingKeeper, + sdkKeepers.ParamsKeeper, + ) + + zetaKeepers := ZetaKeepers{ + ObserverKeeper: observerKeeperTmp, + } + var observerKeeper types.ObserverKeeper = observerKeeperTmp + + // Create the fungible keeper stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, db) + stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) - registry := codectypes.NewInterfaceRegistry() - cdc := codec.NewProtoCodec(registry) + ctx := NewContext(stateStore) + + // Initialize modules genesis + sdkKeepers.InitGenesis(ctx) + zetaKeepers.InitGenesis(ctx) + + // Add a proposer to the context + ctx = sdkKeepers.InitBlockProposer(t, ctx) + + // Initialize mocks for mocked keepers + var authKeeper types.AccountKeeper = sdkKeepers.AuthKeeper + var bankKeeper types.BankKeeper = sdkKeepers.BankKeeper + var stakingKeeper types.StakingKeeper = sdkKeepers.StakingKeeper + if mockOptions.UseAccountMock { + authKeeper = emissionsmocks.NewEmissionAccountKeeper(t) + } + if mockOptions.UseBankMock { + bankKeeper = emissionsmocks.NewEmissionBankKeeper(t) + } + if mockOptions.UseStakingMock { + stakingKeeper = emissionsmocks.NewEmissionStakingKeeper(t) + } + if mockOptions.UseObserverMock { + observerKeeper = emissionsmocks.NewEmissionObserverKeeper(t) + } - paramsSubspace := typesparams.NewSubspace(cdc, - types.Amino, - storeKey, - memStoreKey, - "EmissionsParams", - ) k := keeper.NewKeeper( cdc, storeKey, memStoreKey, - paramsSubspace, + sdkKeepers.ParamsKeeper.Subspace(types.ModuleName), authtypes.FeeCollectorName, - bankkeeper.BaseKeeper{}, - stakingkeeper.Keeper{}, - observerkeeper.Keeper{}, - authkeeper.AccountKeeper{}, + bankKeeper, + stakingKeeper, + observerKeeper, + authKeeper, ) - - ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) k.SetParams(ctx, types.DefaultParams()) - return k, ctx + + return k, ctx, sdkKeepers, zetaKeepers } diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index b67e310544..82d02c9c4b 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -43,7 +43,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" emissionsmodule "github.com/zeta-chain/zetacore/x/emissions" emissionskeeper "github.com/zeta-chain/zetacore/x/emissions/keeper" - types2 "github.com/zeta-chain/zetacore/x/emissions/types" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" fungiblemodule "github.com/zeta-chain/zetacore/x/fungible" fungiblekeeper "github.com/zeta-chain/zetacore/x/fungible/keeper" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" @@ -98,16 +98,16 @@ type ZetaKeepers struct { } var moduleAccountPerms = map[string][]string{ - authtypes.FeeCollectorName: nil, - distrtypes.ModuleName: nil, - stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, - stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, - evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - types2.ModuleName: nil, - types2.UndistributedObserverRewardsPool: nil, - types2.UndistributedTssRewardsPool: nil, + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + emissionstypes.ModuleName: {authtypes.Minter}, + emissionstypes.UndistributedObserverRewardsPool: nil, + emissionstypes.UndistributedTssRewardsPool: nil, } // ModuleAccountAddrs returns all the app's module account addresses. @@ -375,7 +375,7 @@ func (zk ZetaKeepers) InitGenesis(ctx sdk.Context) { crosschainmodule.InitGenesis(ctx, *zk.CrosschainKeeper, *crosschaintypes.DefaultGenesis()) } if zk.EmissionsKeeper != nil { - emissionsmodule.InitGenesis(ctx, *zk.EmissionsKeeper, *types2.DefaultGenesis()) + emissionsmodule.InitGenesis(ctx, *zk.EmissionsKeeper, *emissionstypes.DefaultGenesis()) } if zk.FungibleKeeper != nil { fungiblemodule.InitGenesis(ctx, *zk.FungibleKeeper, *fungibletypes.DefaultGenesis()) diff --git a/testutil/keeper/mocks/crosschain/account.go b/testutil/keeper/mocks/crosschain/account.go index 0560f21be8..fbd7c0377b 100644 --- a/testutil/keeper/mocks/crosschain/account.go +++ b/testutil/keeper/mocks/crosschain/account.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/bank.go b/testutil/keeper/mocks/crosschain/bank.go index 9ac2b1278b..90f4e17e29 100644 --- a/testutil/keeper/mocks/crosschain/bank.go +++ b/testutil/keeper/mocks/crosschain/bank.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/fungible.go b/testutil/keeper/mocks/crosschain/fungible.go index e8e090189d..37bb347981 100644 --- a/testutil/keeper/mocks/crosschain/fungible.go +++ b/testutil/keeper/mocks/crosschain/fungible.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/observer.go b/testutil/keeper/mocks/crosschain/observer.go index a6fd222875..ff3cf4cd72 100644 --- a/testutil/keeper/mocks/crosschain/observer.go +++ b/testutil/keeper/mocks/crosschain/observer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/staking.go b/testutil/keeper/mocks/crosschain/staking.go index 1fb8a14061..5b7d3c501f 100644 --- a/testutil/keeper/mocks/crosschain/staking.go +++ b/testutil/keeper/mocks/crosschain/staking.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/emissions/account.go b/testutil/keeper/mocks/emissions/account.go new file mode 100644 index 0000000000..a660d40e72 --- /dev/null +++ b/testutil/keeper/mocks/emissions/account.go @@ -0,0 +1,49 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionAccountKeeper is an autogenerated mock type for the EmissionAccountKeeper type +type EmissionAccountKeeper struct { + mock.Mock +} + +// GetModuleAccount provides a mock function with given fields: ctx, moduleName +func (_m *EmissionAccountKeeper) GetModuleAccount(ctx types.Context, moduleName string) authtypes.ModuleAccountI { + ret := _m.Called(ctx, moduleName) + + if len(ret) == 0 { + panic("no return value specified for GetModuleAccount") + } + + var r0 authtypes.ModuleAccountI + if rf, ok := ret.Get(0).(func(types.Context, string) authtypes.ModuleAccountI); ok { + r0 = rf(ctx, moduleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(authtypes.ModuleAccountI) + } + } + + return r0 +} + +// NewEmissionAccountKeeper creates a new instance of EmissionAccountKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionAccountKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionAccountKeeper { + mock := &EmissionAccountKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/emissions/bank.go b/testutil/keeper/mocks/emissions/bank.go new file mode 100644 index 0000000000..2e3d6a702e --- /dev/null +++ b/testutil/keeper/mocks/emissions/bank.go @@ -0,0 +1,64 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionBankKeeper is an autogenerated mock type for the EmissionBankKeeper type +type EmissionBankKeeper struct { + mock.Mock +} + +// GetBalance provides a mock function with given fields: ctx, addr, denom +func (_m *EmissionBankKeeper) GetBalance(ctx types.Context, addr types.AccAddress, denom string) types.Coin { + ret := _m.Called(ctx, addr, denom) + + if len(ret) == 0 { + panic("no return value specified for GetBalance") + } + + var r0 types.Coin + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, string) types.Coin); ok { + r0 = rf(ctx, addr, denom) + } else { + r0 = ret.Get(0).(types.Coin) + } + + return r0 +} + +// SendCoinsFromModuleToModule provides a mock function with given fields: ctx, senderModule, recipientModule, amt +func (_m *EmissionBankKeeper) SendCoinsFromModuleToModule(ctx types.Context, senderModule string, recipientModule string, amt types.Coins) error { + ret := _m.Called(ctx, senderModule, recipientModule, amt) + + if len(ret) == 0 { + panic("no return value specified for SendCoinsFromModuleToModule") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, string, string, types.Coins) error); ok { + r0 = rf(ctx, senderModule, recipientModule, amt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewEmissionBankKeeper creates a new instance of EmissionBankKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionBankKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionBankKeeper { + mock := &EmissionBankKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/emissions/observer.go b/testutil/keeper/mocks/emissions/observer.go new file mode 100644 index 0000000000..7c2cfb3c48 --- /dev/null +++ b/testutil/keeper/mocks/emissions/observer.go @@ -0,0 +1,78 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionObserverKeeper is an autogenerated mock type for the EmissionObserverKeeper type +type EmissionObserverKeeper struct { + mock.Mock +} + +// GetBallot provides a mock function with given fields: ctx, index +func (_m *EmissionObserverKeeper) GetBallot(ctx types.Context, index string) (observertypes.Ballot, bool) { + ret := _m.Called(ctx, index) + + if len(ret) == 0 { + panic("no return value specified for GetBallot") + } + + var r0 observertypes.Ballot + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context, string) (observertypes.Ballot, bool)); ok { + return rf(ctx, index) + } + if rf, ok := ret.Get(0).(func(types.Context, string) observertypes.Ballot); ok { + r0 = rf(ctx, index) + } else { + r0 = ret.Get(0).(observertypes.Ballot) + } + + if rf, ok := ret.Get(1).(func(types.Context, string) bool); ok { + r1 = rf(ctx, index) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GetMaturedBallotList provides a mock function with given fields: ctx +func (_m *EmissionObserverKeeper) GetMaturedBallotList(ctx types.Context) []string { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetMaturedBallotList") + } + + var r0 []string + if rf, ok := ret.Get(0).(func(types.Context) []string); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + return r0 +} + +// NewEmissionObserverKeeper creates a new instance of EmissionObserverKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionObserverKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionObserverKeeper { + mock := &EmissionObserverKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/emissions/staking.go b/testutil/keeper/mocks/emissions/staking.go new file mode 100644 index 0000000000..7c58333bb5 --- /dev/null +++ b/testutil/keeper/mocks/emissions/staking.go @@ -0,0 +1,46 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionStakingKeeper is an autogenerated mock type for the EmissionStakingKeeper type +type EmissionStakingKeeper struct { + mock.Mock +} + +// BondedRatio provides a mock function with given fields: ctx +func (_m *EmissionStakingKeeper) BondedRatio(ctx types.Context) types.Dec { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for BondedRatio") + } + + var r0 types.Dec + if rf, ok := ret.Get(0).(func(types.Context) types.Dec); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(types.Dec) + } + + return r0 +} + +// NewEmissionStakingKeeper creates a new instance of EmissionStakingKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionStakingKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionStakingKeeper { + mock := &EmissionStakingKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/fungible/account.go b/testutil/keeper/mocks/fungible/account.go index f7a2788969..0522e833b4 100644 --- a/testutil/keeper/mocks/fungible/account.go +++ b/testutil/keeper/mocks/fungible/account.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/fungible/bank.go b/testutil/keeper/mocks/fungible/bank.go index 599ba3453f..db14226310 100644 --- a/testutil/keeper/mocks/fungible/bank.go +++ b/testutil/keeper/mocks/fungible/bank.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/fungible/evm.go b/testutil/keeper/mocks/fungible/evm.go index b59f7d477e..28fd46e25c 100644 --- a/testutil/keeper/mocks/fungible/evm.go +++ b/testutil/keeper/mocks/fungible/evm.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/fungible/observer.go b/testutil/keeper/mocks/fungible/observer.go index b5736cd960..3010f8faaf 100644 --- a/testutil/keeper/mocks/fungible/observer.go +++ b/testutil/keeper/mocks/fungible/observer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/mocks.go b/testutil/keeper/mocks/mocks.go index dbbdeccd76..ec32911a3f 100644 --- a/testutil/keeper/mocks/mocks.go +++ b/testutil/keeper/mocks/mocks.go @@ -2,6 +2,7 @@ package mocks import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" ) @@ -57,3 +58,23 @@ type FungibleObserverKeeper interface { type FungibleEVMKeeper interface { fungibletypes.EVMKeeper } + +//go:generate mockery --name EmissionAccountKeeper --filename account.go --case underscore --output ./emissions +type EmissionAccountKeeper interface { + emissionstypes.AccountKeeper +} + +//go:generate mockery --name EmissionBankKeeper --filename bank.go --case underscore --output ./emissions +type EmissionBankKeeper interface { + emissionstypes.BankKeeper +} + +//go:generate mockery --name EmissionStakingKeeper --filename staking.go --case underscore --output ./emissions +type EmissionStakingKeeper interface { + emissionstypes.StakingKeeper +} + +//go:generate mockery --name EmissionObserverKeeper --filename observer.go --case underscore --output ./emissions +type EmissionObserverKeeper interface { + emissionstypes.ObserverKeeper +} diff --git a/testutil/sample/observer.go b/testutil/sample/observer.go index b5d533c4e6..7cb0b9b27b 100644 --- a/testutil/sample/observer.go +++ b/testutil/sample/observer.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/cosmos" "github.com/zeta-chain/zetacore/x/observer/types" @@ -236,3 +237,31 @@ func LegacyObserverMapperList(t *testing.T, n int, index string) []*types.Observ } return observerMapperList } + +func BallotList(n int, observerSet []string) []types.Ballot { + r := newRandFromSeed(int64(n)) + ballotList := make([]types.Ballot, n) + + for i := 0; i < n; i++ { + identifier := crypto.Keccak256Hash([]byte(fmt.Sprintf("%d-%d-%d", r.Int63(), r.Int63(), r.Int63()))) + ballotList[i] = types.Ballot{ + Index: identifier.Hex(), + BallotIdentifier: identifier.Hex(), + VoterList: observerSet, + Votes: VotesSuccessOnly(len(observerSet)), + ObservationType: types.ObservationType_InBoundTx, + BallotThreshold: sdk.OneDec(), + BallotStatus: types.BallotStatus_BallotFinalized_SuccessObservation, + BallotCreationHeight: 0, + } + } + return ballotList +} + +func VotesSuccessOnly(voteCount int) []types.VoteType { + votes := make([]types.VoteType, voteCount) + for i := 0; i < voteCount; i++ { + votes[i] = types.VoteType_SuccessObservation + } + return votes +} diff --git a/testutil/sample/sample.go b/testutil/sample/sample.go index 4fcc9ac9e8..ba197ea932 100644 --- a/testutil/sample/sample.go +++ b/testutil/sample/sample.go @@ -147,3 +147,8 @@ func UintInRange(low, high uint64) sdkmath.Uint { u := Uint64InRange(low, high) return sdkmath.NewUint(u) } + +func IntInRange(low, high int64) sdkmath.Int { + i := Int64InRange(low, high) + return sdkmath.NewInt(i) +} diff --git a/x/emissions/abci.go b/x/emissions/abci.go index bc15398209..2fb0561bb6 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -14,7 +14,6 @@ import ( func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { emissionPoolBalance := keeper.GetReservesFactor(ctx) blockRewards := types.BlockReward - if blockRewards.GT(emissionPoolBalance) { ctx.Logger().Info(fmt.Sprintf("Block rewards %s are greater than emission pool balance %s", blockRewards.String(), emissionPoolBalance.String())) return @@ -22,6 +21,7 @@ func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { validatorRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() observerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() tssSignerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() + // Use a tmpCtx, which is a cache-wrapped context to avoid writing to the store // We commit only if all three distributions are successful, if not the funds stay in the emission pool tmpCtx, commit := ctx.CacheContext() @@ -93,7 +93,6 @@ func DistributeObserverRewards(ctx sdk.Context, amount sdkmath.Int, keeper keepe sortedKeys = append(sortedKeys, k) } sort.Strings(sortedKeys) - var finalDistributionList []*types.ObserverEmission for _, key := range sortedKeys { observerAddress, err := sdk.AccAddressFromBech32(key) @@ -123,6 +122,7 @@ func DistributeObserverRewards(ctx sdk.Context, amount sdkmath.Int, keeper keepe }) continue } + // Defensive check if rewardPerUnit.GT(sdk.ZeroInt()) { rewardAmount := rewardPerUnit.Mul(sdkmath.NewInt(observerRewardUnits)) diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index 28ca327d50..55e6188ba9 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -1,229 +1,288 @@ package emissions_test -//TODO : https://github.com/zeta-chain/node/issues/1659 -//func getaZetaFromString(amount string) sdk.Coins { -// emissionPoolInt, _ := sdk.NewIntFromString(amount) -// return sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emissionPoolInt)) -//} -// -//func SetupApp(t *testing.T, params emissionsModuleTypes.Params, emissionPoolCoins sdk.Coins) (*zetaapp.App, sdk.Context, *tmtypes.ValidatorSet, *authtypes.BaseAccount) { -// pk1 := ed25519.GenPrivKey().PubKey() -// acc1 := authtypes.NewBaseAccountWithAddress(sdk.AccAddress(pk1.Address())) -// // genDelActs and genDelBalances need to have the same addresses -// // bondAmount is specified separately , the Balances here are additional tokens for delegators to have in their accounts -// genDelActs := make(authtypes.GenesisAccounts, 1) -// genDelBalances := make([]banktypes.Balance, 1) -// genDelActs[0] = acc1 -// genDelBalances[0] = banktypes.Balance{ -// Address: acc1.GetAddress().String(), -// Coins: emissionPoolCoins, -// } -// delBondAmount := getaZetaFromString("1000000000000000000000000") -// -// //genBalances := make([]banktypes.Balance, 1) -// //genBalances[0] = banktypes.Balance{ -// // Address: emissionsModuleTypes.EmissionsModuleAddress.String(), -// // Coins: emissionPoolCoins, -// //} -// -// vset := tmtypes.NewValidatorSet([]*tmtypes.Validator{}) -// for i := 0; i < 1; i++ { -// privKey := ed25519.GenPrivKey() -// pubKey := privKey.PubKey() -// val := tmtypes.NewValidator(pubKey, 1) -// err := vset.UpdateWithChangeSet([]*tmtypes.Validator{val}) -// if err != nil { -// panic("Failed to add validator") -// } -// } -// -// app := simapp.SetupWithGenesisValSet(t, vset, genDelActs, delBondAmount.AmountOf(config.BaseDenom), params, genDelBalances, nil) -// ctx := app.BaseApp.NewContext(false, tmproto.Header{}) -// ctx = ctx.WithBlockHeight(app.LastBlockHeight()) -// return app, ctx, vset, acc1 -//} -// -//type EmissionTestData struct { -// BlockHeight int64 `json:"blockHeight,omitempty"` -// BondFactor sdk.Dec `json:"bondFactor"` -// ReservesFactor sdk.Dec `json:"reservesFactor"` -// DurationFactor string `json:"durationFactor"` -//} -// -//func TestAppModule_GetBlockRewardComponents(t *testing.T) { -// -// tests := []struct { -// name string -// startingEmissionPool string -// params emissionsModuleTypes.Params -// testMaxHeight int64 -// inputFilename string -// checkValues []EmissionTestData -// generateOnly bool -// }{ -// { -// name: "default values", -// params: emissionsModuleTypes.DefaultParams(), -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "higher starting pool", -// params: emissionsModuleTypes.DefaultParams(), -// startingEmissionPool: "100000000000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "lower starting pool", -// params: emissionsModuleTypes.DefaultParams(), -// startingEmissionPool: "100000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "different distribution percentages", -// params: emissionsModuleTypes.Params{ -// MaxBondFactor: "1.25", -// MinBondFactor: "0.75", -// AvgBlockTime: "6.00", -// TargetBondRatio: "00.67", -// ValidatorEmissionPercentage: "00.10", -// ObserverEmissionPercentage: "00.85", -// TssSignerEmissionPercentage: "00.05", -// DurationFactorConstant: "0.001877876953694702", -// }, -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "higher block time", -// params: emissionsModuleTypes.Params{ -// MaxBondFactor: "1.25", -// MinBondFactor: "0.75", -// AvgBlockTime: "20.00", -// TargetBondRatio: "00.67", -// ValidatorEmissionPercentage: "00.10", -// ObserverEmissionPercentage: "00.85", -// TssSignerEmissionPercentage: "00.05", -// DurationFactorConstant: "0.1", -// }, -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "different duration constant", -// params: emissionsModuleTypes.Params{ -// MaxBondFactor: "1.25", -// MinBondFactor: "0.75", -// AvgBlockTime: "6.00", -// TargetBondRatio: "00.67", -// ValidatorEmissionPercentage: "00.10", -// ObserverEmissionPercentage: "00.85", -// TssSignerEmissionPercentage: "00.05", -// DurationFactorConstant: "0.1", -// }, -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// } -// -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// app, ctx, _, minter := SetupApp(t, tt.params, getaZetaFromString(tt.startingEmissionPool)) -// err := app.BankKeeper.SendCoinsFromAccountToModule(ctx, minter.GetAddress(), emissionsModuleTypes.ModuleName, getaZetaFromString(tt.startingEmissionPool)) -// require.NoError(t, err) -// GenerateTestDataMaths(app, ctx, tt.testMaxHeight, tt.inputFilename) -// defer func(t *testing.T, fp string) { -// err := os.RemoveAll(fp) -// require.NoError(t, err) -// }(t, tt.inputFilename) -// -// if tt.generateOnly { -// return -// } -// inputTestData, err := GetInputData(tt.inputFilename) -// require.NoError(t, err) -// sort.SliceStable(inputTestData, func(i, j int) bool { return inputTestData[i].BlockHeight < inputTestData[j].BlockHeight }) -// startHeight := ctx.BlockHeight() -// require.Equal(t, startHeight, inputTestData[0].BlockHeight, "starting block height should be equal to the first block height in the input data") -// for i := startHeight; i < tt.testMaxHeight; i++ { -// //The First distribution will occur only when begin-block is triggered -// reservesFactor, bondFactor, durationFactor := app.EmissionsKeeper.GetBlockRewardComponents(ctx) -// require.Equal(t, inputTestData[i-1].ReservesFactor, reservesFactor, "reserves factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) -// require.Equal(t, inputTestData[i-1].BondFactor, bondFactor, "bond factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) -// require.Equal(t, inputTestData[i-1].DurationFactor, durationFactor.String(), "duration factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) -// emissionsModule.BeginBlocker(ctx, app.EmissionsKeeper) -// ctx = ctx.WithBlockHeight(i + 1) -// } -// }) -// } -//} -// -//func GetInputData(fp string) ([]EmissionTestData, error) { -// data := []EmissionTestData{} -// file, err := filepath.Abs(fp) -// if err != nil { -// -// return nil, err -// } -// file = filepath.Clean(file) -// input, err := ioutil.ReadFile(file) // #nosec G304 -// if err != nil { -// return nil, err -// } -// err = json.Unmarshal(input, &data) -// if err != nil { -// return nil, err -// } -// formatedData := make([]EmissionTestData, len(data)) -// for i, dd := range data { -// fl, err := strconv.ParseFloat(dd.DurationFactor, 64) -// if err != nil { -// return nil, err -// } -// dd.DurationFactor = fmt.Sprintf("%0.18f", fl) -// formatedData[i] = dd -// } -// return formatedData, nil -//} -// -//func GenerateTestDataMaths(app *zetaapp.App, ctx sdk.Context, testMaxHeight int64, fileName string) { -// var generatedTestData []EmissionTestData -// reserverCoins := app.BankKeeper.GetBalance(ctx, emissionsModuleTypes.EmissionsModuleAddress, config.BaseDenom) -// startHeight := ctx.BlockHeight() -// for i := startHeight; i < testMaxHeight; i++ { -// reservesFactor := sdk.NewDecFromInt(reserverCoins.Amount) -// bondFactor := app.EmissionsKeeper.GetBondFactor(ctx, app.StakingKeeper) -// durationFactor := app.EmissionsKeeper.GetDurationFactor(ctx) -// blockRewards := reservesFactor.Mul(bondFactor).Mul(durationFactor) -// generatedTestData = append(generatedTestData, EmissionTestData{ -// BlockHeight: i, -// BondFactor: bondFactor, -// DurationFactor: durationFactor.String(), -// ReservesFactor: reservesFactor, -// }) -// validatorRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() -// observerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() -// tssSignerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() -// truncatedRewards := validatorRewards.Add(observerRewards).Add(tssSignerRewards) -// reserverCoins = reserverCoins.Sub(sdk.NewCoin(config.BaseDenom, truncatedRewards)) -// ctx = ctx.WithBlockHeight(i + 1) -// } -// GenerateSampleFile(fileName, generatedTestData) -//} -// -//func GenerateSampleFile(fp string, data []EmissionTestData) { -// file, _ := json.MarshalIndent(data, "", " ") -// _ = ioutil.WriteFile(fp, file, 0600) -//} +import ( + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" + "github.com/zeta-chain/zetacore/common" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + emissionsModule "github.com/zeta-chain/zetacore/x/emissions" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" + observerTypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestBeginBlocker(t *testing.T) { + t.Run("no observer distribution happens if emissions module account is empty", func(t *testing.T) { + k, ctx, _, zk := keepertest.EmissionsKeeper(t) + var ballotIdentifiers []string + + observerSet := sample.ObserverSet(10) + zk.ObserverKeeper.SetObserverSet(ctx, observerSet) + + ballotList := sample.BallotList(10, observerSet.ObserverList) + for _, ballot := range ballotList { + zk.ObserverKeeper.SetBallot(ctx, &ballot) + ballotIdentifiers = append(ballotIdentifiers, ballot.BallotIdentifier) + } + zk.ObserverKeeper.SetBallotList(ctx, &observerTypes.BallotListForHeight{ + Height: 0, + BallotsIndexList: ballotIdentifiers, + }) + for i := 0; i < 100; i++ { + emissionsModule.BeginBlocker(ctx, *k) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + for _, observer := range observerSet.ObserverList { + _, found := k.GetWithdrawableEmission(ctx, observer) + require.False(t, found) + } + }) + t.Run("no validator distribution happens if emissions module account is empty", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + feeCollectorAddress := sk.AuthKeeper.GetModuleAccount(ctx, types.FeeCollectorName).GetAddress() + for i := 0; i < 100; i++ { + emissionsModule.BeginBlocker(ctx, *k) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + require.True(t, sk.BankKeeper.GetBalance(ctx, feeCollectorAddress, config.BaseDenom).Amount.IsZero()) + }) + t.Run("tmp ctx is not committed if any of the distribution fails", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + // Fund the emission pool to start the emission process + err := sk.BankKeeper.MintCoins(ctx, emissionstypes.ModuleName, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewInt(1000000000000)))) + require.NoError(t, err) + // Setup module accounts for emission pools except for observer pool , so that the observer distribution fails + _ = sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedTssRewardsPool).GetAddress() + feeCollectorAddress := sk.AuthKeeper.GetModuleAccount(ctx, types.FeeCollectorName).GetAddress() + _ = sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.ModuleName).GetAddress() + + for i := 0; i < 100; i++ { + // produce a block + emissionsModule.BeginBlocker(ctx, *k) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + require.True(t, sk.BankKeeper.GetBalance(ctx, feeCollectorAddress, config.BaseDenom).Amount.IsZero()) + require.True(t, sk.BankKeeper.GetBalance(ctx, emissionstypes.EmissionsModuleAddress, config.BaseDenom).Amount.Equal(sdk.NewInt(1000000000000))) + }) + t.Run("successfully distribute rewards", func(t *testing.T) { + numberOfTestBlocks := 100 + k, ctx, sk, zk := keepertest.EmissionsKeeper(t) + observerSet := sample.ObserverSet(10) + zk.ObserverKeeper.SetObserverSet(ctx, observerSet) + ballotList := sample.BallotList(10, observerSet.ObserverList) + + // set the ballot list + ballotIdentifiers := []string{} + for _, ballot := range ballotList { + zk.ObserverKeeper.SetBallot(ctx, &ballot) + ballotIdentifiers = append(ballotIdentifiers, ballot.BallotIdentifier) + } + zk.ObserverKeeper.SetBallotList(ctx, &observerTypes.BallotListForHeight{ + Height: 0, + BallotsIndexList: ballotIdentifiers, + }) + + // Total block rewards is the fixed amount of rewards that are distributed + totalBlockRewards, err := common.GetAzetaDecFromAmountInZeta(emissionstypes.BlockRewardsInZeta) + totalRewardCoins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalBlockRewards.TruncateInt())) + require.NoError(t, err) + // Fund the emission pool to start the emission process + err = sk.BankKeeper.MintCoins(ctx, emissionstypes.ModuleName, totalRewardCoins) + require.NoError(t, err) + + // Setup module accounts for emission pools + undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() + undistributedTssPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedTssRewardsPool).GetAddress() + feeCollecterAddress := sk.AuthKeeper.GetModuleAccount(ctx, types.FeeCollectorName).GetAddress() + emissionPool := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.ModuleName).GetAddress() + + blockRewards := emissionstypes.BlockReward + observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ObserverEmissionPercentage)).TruncateInt() + validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ValidatorEmissionPercentage)).TruncateInt() + tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).TssSignerEmissionPercentage)).TruncateInt() + distributedRewards := observerRewardsForABlock.Add(validatorRewardsForABlock).Add(tssSignerRewardsForABlock) + + require.True(t, blockRewards.TruncateInt().GT(distributedRewards)) + + for i := 0; i < numberOfTestBlocks; i++ { + emissionPoolBeforeBlockDistribution := sk.BankKeeper.GetBalance(ctx, emissionPool, config.BaseDenom).Amount + // produce a block + emissionsModule.BeginBlocker(ctx, *k) + + // require distribution amount + emissionPoolBalanceAfterBlockDistribution := sk.BankKeeper.GetBalance(ctx, emissionPool, config.BaseDenom).Amount + require.True(t, emissionPoolBeforeBlockDistribution.Sub(emissionPoolBalanceAfterBlockDistribution).Equal(distributedRewards)) + + // totalDistributedTillCurrentBlock is the net amount of rewards distributed till the current block, this works in a unit test as the fees are not being collected by validators + totalDistributedTillCurrentBlock := sk.BankKeeper.GetBalance(ctx, feeCollecterAddress, config.BaseDenom).Amount. + Add(sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount). + Add(sk.BankKeeper.GetBalance(ctx, undistributedTssPoolAddress, config.BaseDenom).Amount) + // require we are always under the max limit of block rewards + require.True(t, totalRewardCoins.AmountOf(config.BaseDenom). + Sub(totalDistributedTillCurrentBlock).GTE(sdk.ZeroInt())) + + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + + // We can simplify the calculation as the rewards are distributed equally among all the observers + rewardPerUnit := observerRewardsForABlock.Quo(sdk.NewInt(int64(len(ballotList) * len(observerSet.ObserverList)))) + emissionAmount := rewardPerUnit.Mul(sdk.NewInt(int64(len(ballotList)))) + + // Check if the rewards are distributed equally among all the observers + for _, observer := range observerSet.ObserverList { + observerEmission, found := k.GetWithdrawableEmission(ctx, observer) + require.True(t, found) + require.Equal(t, emissionAmount, observerEmission.Amount) + } + + // Check pool balances after the distribution + feeCollectorBalance := sk.BankKeeper.GetBalance(ctx, feeCollecterAddress, config.BaseDenom).Amount + require.Equal(t, feeCollectorBalance, validatorRewardsForABlock.Mul(sdk.NewInt(int64(numberOfTestBlocks)))) + + tssPoolBalances := sk.BankKeeper.GetBalance(ctx, undistributedTssPoolAddress, config.BaseDenom).Amount + require.Equal(t, tssSignerRewardsForABlock.Mul(sdk.NewInt(int64(numberOfTestBlocks))).String(), tssPoolBalances.String()) + + observerPoolBalances := sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount + require.Equal(t, observerRewardsForABlock.Mul(sdk.NewInt(int64(numberOfTestBlocks))).String(), observerPoolBalances.String()) + }) +} + +func TestDistributeObserverRewards(t *testing.T) { + + k, ctx, sk, zk := keepertest.EmissionsKeeper(t) + observerSet := sample.ObserverSet(4) + zk.ObserverKeeper.SetObserverSet(ctx, observerSet) + // Total block rewards is the fixed amount of rewards that are distributed + totalBlockRewards, err := common.GetAzetaDecFromAmountInZeta(emissionstypes.BlockRewardsInZeta) + totalRewardCoins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalBlockRewards.TruncateInt())) + require.NoError(t, err) + // Fund the emission pool to start the emission process + err = sk.BankKeeper.MintCoins(ctx, emissionstypes.ModuleName, totalRewardCoins) + require.NoError(t, err) + // Set starting emission for all observers to 100 so that we can calculate the rewards and slashes + for _, observer := range observerSet.ObserverList { + k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ + Address: observer, + Amount: sdkmath.NewInt(100), + }) + } + + tt := []struct { + name string + votes [][]observerTypes.VoteType + totalRewardsForBlock sdkmath.Int + expectedRewards map[string]int64 + ballotStatus observerTypes.BallotStatus + slashAmount sdkmath.Int + }{ + { + name: "all observers rewarded correctly", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 4 as all votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(100), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 125, + observerSet.ObserverList[1]: 125, + observerSet.ObserverList[2]: 125, + observerSet.ObserverList[3]: 125, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_SuccessObservation, + slashAmount: sdkmath.NewInt(25), + }, + { + name: "one observer slashed", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_FailureObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 3 as 3 votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(75), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 75, + observerSet.ObserverList[1]: 125, + observerSet.ObserverList[2]: 125, + observerSet.ObserverList[3]: 125, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_SuccessObservation, + slashAmount: sdkmath.NewInt(25), + }, + { + name: "all observer slashed", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 0 as no votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(100), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 75, + observerSet.ObserverList[1]: 75, + observerSet.ObserverList[2]: 75, + observerSet.ObserverList[3]: 75, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_FailureObservation, + slashAmount: sdkmath.NewInt(25), + }, + { + name: "slashed to zero if slash amount is greater than available emissions", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 0 as no votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(100), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 0, + observerSet.ObserverList[1]: 0, + observerSet.ObserverList[2]: 0, + observerSet.ObserverList[3]: 0, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_FailureObservation, + slashAmount: sdkmath.NewInt(2500), + }, + { + name: "withdraw able emissions unchanged if rewards and slashes are equal", + votes: [][]observerTypes.VoteType{ + {observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}, + {observerTypes.VoteType_FailureObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}, + }, + // total reward units would be 7 as 7 votes match the ballot status, including both ballots + totalRewardsForBlock: sdkmath.NewInt(70), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 100, + observerSet.ObserverList[1]: 120, + observerSet.ObserverList[2]: 120, + observerSet.ObserverList[3]: 120, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_SuccessObservation, + slashAmount: sdkmath.NewInt(25), + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + params := emissionstypes.DefaultParams() + params.ObserverSlashAmount = tc.slashAmount + k.SetParams(ctx, params) + ballotIdentifiers := []string{} + for i, votes := range tc.votes { + ballot := observerTypes.Ballot{ + BallotIdentifier: "ballot" + string(rune(i)), + BallotStatus: tc.ballotStatus, + VoterList: observerSet.ObserverList, + Votes: votes, + } + zk.ObserverKeeper.SetBallot(ctx, &ballot) + ballotIdentifiers = append(ballotIdentifiers, ballot.BallotIdentifier) + } + zk.ObserverKeeper.SetBallotList(ctx, &observerTypes.BallotListForHeight{ + Height: 0, + BallotsIndexList: ballotIdentifiers, + }) + + ctx = ctx.WithBlockHeight(100) + err := emissionsModule.DistributeObserverRewards(ctx, tc.totalRewardsForBlock, *k) + require.NoError(t, err) + for _, observer := range observerSet.ObserverList { + observerEmission, found := k.GetWithdrawableEmission(ctx, observer) + require.True(t, found) + require.Equal(t, tc.expectedRewards[observer], observerEmission.Amount.Int64()) + } + }) + } +} diff --git a/x/emissions/genesis_test.go b/x/emissions/genesis_test.go index cae3948ce1..e1bebddee5 100644 --- a/x/emissions/genesis_test.go +++ b/x/emissions/genesis_test.go @@ -22,7 +22,7 @@ func TestGenesis(t *testing.T) { } // Init and export - k, ctx := keepertest.EmissionsKeeper(t) + k, ctx, _, _ := keepertest.EmissionsKeeper(t) emissions.InitGenesis(ctx, *k, genesisState) got := emissions.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/emissions/keeper/params_test.go b/x/emissions/keeper/params_test.go index 128d9ced3e..f861a944a3 100644 --- a/x/emissions/keeper/params_test.go +++ b/x/emissions/keeper/params_test.go @@ -228,14 +228,13 @@ func TestKeeper_GetParams(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - k, ctx := keepertest.EmissionsKeeper(t) - defaultParams := k.GetParams(ctx) + k, ctx, _, _ := keepertest.EmissionsKeeper(t) assertPanic(t, func() { k.SetParams(ctx, tt.params) }, tt.isPanic) if tt.isPanic != "" { - require.Equal(t, defaultParams, k.GetParams(ctx)) + require.Equal(t, emissionstypes.DefaultParams(), k.GetParams(ctx)) } else { require.Equal(t, tt.params, k.GetParams(ctx)) } From 6f0e9c46180cf277f0f744f90f38c332c49b8704 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Thu, 22 Feb 2024 21:55:57 +0100 Subject: [PATCH 15/33] ci: run build workflow on develop push for code coverage generation (#1793) --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f997dd9e02..c273f4633d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,9 @@ name: PR Testing on: + push: + branches: + - develop pull_request: branches: - develop From 0798c381df87e6c20889645e2b778763d57612d0 Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 26 Feb 2024 17:04:14 +0000 Subject: [PATCH 16/33] refactor: zetaclient metrics (#1783) * Refactor zetaclient metrics * Fix blame counter init * Add changelog entry * Add unreleased to changelog * Remove blank line --- changelog.md | 3 + cmd/zetaclientd/keygen_tss.go | 10 +-- cmd/zetaclientd/start.go | 12 +-- cmd/zetaclientd/utils.go | 5 +- zetaclient/bitcoin/bitcoin_client.go | 17 +---- zetaclient/bitcoin/bitcoin_client_rpc_test.go | 3 +- zetaclient/evm/evm_client.go | 54 +++----------- zetaclient/interfaces/interfaces.go | 3 - zetaclient/metrics/chainmetrics.go | 51 ------------- zetaclient/metrics/metrics.go | 73 ++++++++----------- zetaclient/metrics/metrics_test.go | 6 +- zetaclient/tss/tss_signer.go | 40 ++-------- zetaclient/zetacore_observer.go | 49 +------------ 13 files changed, 73 insertions(+), 253 deletions(-) delete mode 100644 zetaclient/metrics/chainmetrics.go diff --git a/changelog.md b/changelog.md index 9116cc3651..e16dc3acf2 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,9 @@ ## Unreleased +### Refactor +* [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure + ### Tests * [1767](https://github.com/zeta-chain/node/pull/1767) - add unit tests for emissions module begin blocker diff --git a/cmd/zetaclientd/keygen_tss.go b/cmd/zetaclientd/keygen_tss.go index 215f680a99..119a547a3a 100644 --- a/cmd/zetaclientd/keygen_tss.go +++ b/cmd/zetaclientd/keygen_tss.go @@ -29,7 +29,6 @@ func GenerateTss(logger zerolog.Logger, priKey secp256k1.PrivKey, ts *metrics.TelemetryServer, tssHistoricalList []observertypes.TSS, - metrics *metrics.Metrics, tssPassword string, hotkeyPassword string) (*mc.TSS, error) { keygenLogger := logger.With().Str("module", "keygen").Logger() @@ -49,7 +48,6 @@ func GenerateTss(logger zerolog.Logger, cfg, zetaBridge, tssHistoricalList, - metrics, bitcoinChainID, tssPassword, hotkeyPassword, @@ -124,7 +122,6 @@ func GenerateTss(logger zerolog.Logger, CurrentPubkey: tss.CurrentPubkey, Signers: tss.Signers, CoreBridge: nil, - Metrics: nil, } // If TSS is successful , broadcast the vote to zetacore and set Pubkey @@ -173,12 +170,7 @@ func keygenTss(cfg *config.Config, tss *mc.TSS, keygenLogger zerolog.Logger) err // Increment Blame counter for _, node := range res.Blame.BlameNodes { - counter, err := tss.Metrics.GetPromCounter(node.Pubkey) - if err != nil { - keygenLogger.Error().Err(err).Msgf("error getting counter: %s", node.Pubkey) - continue - } - counter.Inc() + metrics.TssNodeBlamePerPubKey.WithLabelValues(node.Pubkey).Inc() } keygenLogger.Info().Msgf("keygen posted blame data tx hash: %s", zetaHash) diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index e22d86b356..911dd9a3dc 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -23,7 +23,7 @@ import ( observerTypes "github.com/zeta-chain/zetacore/x/observer/types" mc "github.com/zeta-chain/zetacore/zetaclient" "github.com/zeta-chain/zetacore/zetaclient/config" - metrics2 "github.com/zeta-chain/zetacore/zetaclient/metrics" + "github.com/zeta-chain/zetacore/zetaclient/metrics" ) type Multiaddr = core.Multiaddr @@ -73,7 +73,7 @@ func start(_ *cobra.Command, _ []string) error { waitForZetaCore(cfg, startLogger) startLogger.Info().Msgf("ZetaCore is ready , Trying to connect to %s", cfg.Peer) - telemetryServer := metrics2.NewTelemetryServer() + telemetryServer := metrics.NewTelemetryServer() go func() { err := telemetryServer.Start() if err != nil { @@ -153,7 +153,7 @@ func start(_ *cobra.Command, _ []string) error { } } - metrics, err := metrics2.NewMetrics() + metrics, err := metrics.NewMetrics() if err != nil { log.Error().Err(err).Msg("NewMetrics") return err @@ -167,7 +167,7 @@ func start(_ *cobra.Command, _ []string) error { } telemetryServer.SetIPAddress(cfg.PublicIP) - tss, err := GenerateTss(masterLogger, cfg, zetaBridge, peers, priKey, telemetryServer, tssHistoricalList, metrics, tssKeyPass, hotkeyPass) + tss, err := GenerateTss(masterLogger, cfg, zetaBridge, peers, priKey, telemetryServer, tssHistoricalList, tssKeyPass, hotkeyPass) if err != nil { return err } @@ -233,7 +233,7 @@ func start(_ *cobra.Command, _ []string) error { dbpath := filepath.Join(userDir, ".zetaclient/chainobserver") // CreateChainClientMap : This creates a map of all chain clients . Each chain client is responsible for listening to events on the chain and processing them - chainClientMap, err := CreateChainClientMap(zetaBridge, tss, dbpath, metrics, masterLogger, cfg, telemetryServer) + chainClientMap, err := CreateChainClientMap(zetaBridge, tss, dbpath, masterLogger, cfg, telemetryServer) if err != nil { startLogger.Err(err).Msg("CreateSignerMap") return err @@ -249,7 +249,7 @@ func start(_ *cobra.Command, _ []string) error { } // CreateCoreObserver : Core observer wraps the zetacore bridge and adds the client and signer maps to it . This is the high level object used for CCTX interactions - mo1 := mc.NewCoreObserver(zetaBridge, signerMap, chainClientMap, metrics, masterLogger, cfg, telemetryServer) + mo1 := mc.NewCoreObserver(zetaBridge, signerMap, chainClientMap, masterLogger, cfg, telemetryServer) mo1.MonitorCore() // start zeta supply checker diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index 87c009c11b..648f592a56 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -88,7 +88,6 @@ func CreateChainClientMap( bridge *zetabridge.ZetaCoreBridge, tss interfaces.TSSSigner, dbpath string, - metrics *metrics.Metrics, logger zerolog.Logger, cfg *config.Config, ts *metrics.TelemetryServer, @@ -99,7 +98,7 @@ func CreateChainClientMap( if evmConfig.Chain.IsZetaChain() { continue } - co, err := evm.NewEVMChainClient(bridge, tss, dbpath, metrics, logger, cfg, *evmConfig, ts) + co, err := evm.NewEVMChainClient(bridge, tss, dbpath, logger, cfg, *evmConfig, ts) if err != nil { logger.Error().Err(err).Msgf("NewEVMChainClient error for chain %s", evmConfig.Chain.String()) continue @@ -109,7 +108,7 @@ func CreateChainClientMap( // BTC client btcChain, btcConfig, enabled := cfg.GetBTCConfig() if enabled { - co, err := bitcoin.NewBitcoinClient(btcChain, bridge, tss, dbpath, metrics, logger, btcConfig, ts) + co, err := bitcoin.NewBitcoinClient(btcChain, bridge, tss, dbpath, logger, btcConfig, ts) if err != nil { logger.Error().Err(err).Msgf("NewBitcoinClient error for chain %s", btcChain.String()) diff --git a/zetaclient/bitcoin/bitcoin_client.go b/zetaclient/bitcoin/bitcoin_client.go index b0e409b756..7b9c18becb 100644 --- a/zetaclient/bitcoin/bitcoin_client.go +++ b/zetaclient/bitcoin/bitcoin_client.go @@ -30,7 +30,7 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - metricsPkg "github.com/zeta-chain/zetacore/zetaclient/metrics" + "github.com/zeta-chain/zetacore/zetaclient/metrics" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -54,8 +54,6 @@ type BTCLog struct { // BTCChainClient represents a chain configuration for Bitcoin // Filled with above constants depending on chain type BTCChainClient struct { - *metricsPkg.ChainMetrics - chain common.Chain netParams *chaincfg.Params rpcClient interfaces.BTCRPCClient @@ -76,7 +74,7 @@ type BTCChainClient struct { db *gorm.DB stop chan struct{} logger BTCLog - ts *metricsPkg.TelemetryServer + ts *metrics.TelemetryServer BlockCache *lru.Cache } @@ -136,14 +134,12 @@ func NewBitcoinClient( bridge interfaces.ZetaCoreBridger, tss interfaces.TSSSigner, dbpath string, - metrics *metricsPkg.Metrics, logger zerolog.Logger, btcCfg config.BTCConfig, - ts *metricsPkg.TelemetryServer, + ts *metrics.TelemetryServer, ) (*BTCChainClient, error) { ob := BTCChainClient{ - ChainMetrics: metricsPkg.NewChainMetrics(chain.ChainName.String(), metrics), - ts: ts, + ts: ts, } ob.stop = make(chan struct{}) ob.chain = chain @@ -195,11 +191,6 @@ func NewBitcoinClient( return nil, err } - err = ob.RegisterPromGauge(metricsPkg.PendingTxs, "Number of pending transactions") - if err != nil { - return nil, err - } - //Load btc chain client DB err = ob.loadDB(dbpath) if err != nil { diff --git a/zetaclient/bitcoin/bitcoin_client_rpc_test.go b/zetaclient/bitcoin/bitcoin_client_rpc_test.go index 83ba9b426c..8c5ffd43b1 100644 --- a/zetaclient/bitcoin/bitcoin_client_rpc_test.go +++ b/zetaclient/bitcoin/bitcoin_client_rpc_test.go @@ -43,8 +43,7 @@ func (suite *BitcoinClientTestSuite) SetupTest() { tss := interfaces.TestSigner{ PrivKey: privateKey, } - //client, err := NewBitcoinClient(common.BtcTestNetChain(), nil, tss, "", nil) - client, err := NewBitcoinClient(common.BtcRegtestChain(), nil, tss, "/tmp", nil, log.Logger, config.BTCConfig{}, nil) + client, err := NewBitcoinClient(common.BtcRegtestChain(), nil, tss, "/tmp", log.Logger, config.BTCConfig{}, nil) suite.Require().NoError(err) suite.BitcoinChainClient = client skBytes, err := hex.DecodeString(skHex) diff --git a/zetaclient/evm/evm_client.go b/zetaclient/evm/evm_client.go index db13df1f6f..5d4defb0d3 100644 --- a/zetaclient/evm/evm_client.go +++ b/zetaclient/evm/evm_client.go @@ -15,6 +15,7 @@ import ( "time" "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/zetabridge" "github.com/ethereum/go-ethereum" @@ -37,7 +38,6 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - metricsPkg "github.com/zeta-chain/zetacore/zetaclient/metrics" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -73,7 +73,6 @@ const ( // ChainClient represents the chain configuration for an EVM chain // Filled with above constants depending on chain type ChainClient struct { - *metricsPkg.ChainMetrics chain common.Chain evmClient interfaces.EVMRPCClient zetaClient interfaces.ZetaCoreBridger @@ -96,29 +95,24 @@ type ChainClient struct { logger Log cfg *config.Config params observertypes.ChainParams - ts *metricsPkg.TelemetryServer - - blockCache *lru.Cache - blockCacheV3 *lru.Cache // blockCacheV3 caches blocks containing type-3 (BlobTxType) transactions - headerCache *lru.Cache + ts *metrics.TelemetryServer + blockCache *lru.Cache + blockCacheV3 *lru.Cache // blockCacheV3 caches blocks containing type-3 (BlobTxType) transactions + headerCache *lru.Cache } -var _ interfaces.ChainClient = (*ChainClient)(nil) - // NewEVMChainClient returns a new configuration based on supplied target chain func NewEVMChainClient( bridge interfaces.ZetaCoreBridger, tss interfaces.TSSSigner, dbpath string, - metrics *metricsPkg.Metrics, logger zerolog.Logger, cfg *config.Config, evmCfg config.EVMConfig, - ts *metricsPkg.TelemetryServer, + ts *metrics.TelemetryServer, ) (*ChainClient, error) { ob := ChainClient{ - ChainMetrics: metricsPkg.NewChainMetrics(evmCfg.Chain.ChainName.String(), metrics), - ts: ts, + ts: ts, } chainLogger := logger.With().Str("chain", evmCfg.Chain.ChainName.String()).Logger() ob.logger = Log{ @@ -173,20 +167,6 @@ func NewEVMChainClient( return nil, err } - // create metric counters - err = ob.RegisterPromCounter("rpc_getFilterLogs_count", "Number of getLogs") - if err != nil { - return nil, err - } - err = ob.RegisterPromCounter("rpc_getBlockByNumber_count", "Number of getBlockByNumber") - if err != nil { - return nil, err - } - err = ob.RegisterPromGauge(metricsPkg.PendingTxs, "Number of pending transactions") - if err != nil { - return nil, err - } - err = ob.LoadDB(dbpath, ob.chain) if err != nil { return nil, err @@ -941,11 +921,7 @@ func (ob *ChainClient) observeInTX(sampledLogger zerolog.Logger) error { ob.SetLastBlockHeight(blockNumber) // increment prom counter - counter, err := ob.GetPromCounter("rpc_getBlockByNumber_count") - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("GetPromCounter:") - } - counter.Inc() + metrics.GetBlockByNumberPerChain.WithLabelValues(ob.chain.ChainName.String()).Inc() // skip if current height is too low if blockNumber < ob.GetChainParams().ConfirmationCount { @@ -1037,12 +1013,7 @@ func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { }) // increment prom counter - cnt, err := ob.GetPromCounter("rpc_getFilterLogs_count") - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("GetPromCounter:") - } else { - cnt.Inc() - } + metrics.GetFilterLogsPerChain.WithLabelValues(ob.chain.ChainName.String()).Inc() // post to zetabridge beingScanned := uint64(0) @@ -1114,12 +1085,7 @@ func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 }) // increment prom counter - cnt, err := ob.GetPromCounter("rpc_getFilterLogs_count") - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("GetPromCounter:") - } else { - cnt.Inc() - } + metrics.GetFilterLogsPerChain.WithLabelValues(ob.chain.ChainName.String()).Inc() // post to zetabridge guard := make(map[string]bool) // guard against multiple events in the same tx diff --git a/zetaclient/interfaces/interfaces.go b/zetaclient/interfaces/interfaces.go index 2d4ecff7c6..088016ef39 100644 --- a/zetaclient/interfaces/interfaces.go +++ b/zetaclient/interfaces/interfaces.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/prometheus/client_golang/prometheus" "github.com/rs/zerolog" "github.com/zeta-chain/go-tss/blame" "github.com/zeta-chain/zetacore/common" @@ -40,8 +39,6 @@ type ChainClient interface { IsSendOutTxProcessed(sendHash string, nonce uint64, cointype common.CoinType, logger zerolog.Logger) (bool, bool, error) SetChainParams(observertypes.ChainParams) GetChainParams() observertypes.ChainParams - GetPromGauge(name string) (prometheus.Gauge, error) - GetPromCounter(name string) (prometheus.Counter, error) GetTxID(nonce uint64) string ExternalChainWatcherForNewInboundTrackerSuggestions() } diff --git a/zetaclient/metrics/chainmetrics.go b/zetaclient/metrics/chainmetrics.go deleted file mode 100644 index df721d9d43..0000000000 --- a/zetaclient/metrics/chainmetrics.go +++ /dev/null @@ -1,51 +0,0 @@ -package metrics - -import ( - "errors" - - "github.com/prometheus/client_golang/prometheus" -) - -const MetricGroup = "zetaclient" - -type ChainMetrics struct { - chain string - metrics *Metrics -} - -func NewChainMetrics(chain string, metrics *Metrics) *ChainMetrics { - return &ChainMetrics{ - chain, - metrics, - } -} - -func (m *ChainMetrics) GetPromGauge(name string) (prometheus.Gauge, error) { - gauge, found := Gauges[m.buildGroupName(name)] - if !found { - return nil, errors.New("gauge not found") - } - return gauge, nil -} - -func (m *ChainMetrics) RegisterPromGauge(name string, help string) error { - gaugeName := m.buildGroupName(name) - return m.metrics.RegisterGauge(gaugeName, help) -} - -func (m *ChainMetrics) GetPromCounter(name string) (prometheus.Counter, error) { - if cnt, found := Counters[m.buildGroupName(name)]; found { - return cnt, nil - } - return nil, errors.New("counter not found") - -} - -func (m *ChainMetrics) RegisterPromCounter(name string, help string) error { - cntName := m.buildGroupName(name) - return m.metrics.RegisterCounter(cntName, help) -} - -func (m *ChainMetrics) buildGroupName(name string) string { - return MetricGroup + "_" + name + "_" + m.chain -} diff --git a/zetaclient/metrics/metrics.go b/zetaclient/metrics/metrics.go index a3a78564ce..a99db9f760 100644 --- a/zetaclient/metrics/metrics.go +++ b/zetaclient/metrics/metrics.go @@ -2,11 +2,12 @@ package metrics import ( "context" - "fmt" "net/http" "time" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog/log" ) @@ -15,19 +16,36 @@ type Metrics struct { s *http.Server } -type MetricName int - -const ( - //GAUGE_PENDING_TX MetricName = iota - // - //COUNTER_NUM_RPCS - PendingTxs = "pending_txs" -) - var ( - Counters = map[string]prometheus.Counter{} - - Gauges = map[string]prometheus.Gauge{} + PendingTxsPerChain = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "zetaclient", + Name: "pending_txs_total", + Help: "Number of pending transactions per chain", + }, []string{"chain"}) + + GetFilterLogsPerChain = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "zetaclient", + Name: "rpc_getFilterLogs_count", + Help: "Count of getLogs per chain", + }, []string{"chain"}) + + GetBlockByNumberPerChain = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "zetaclient", + Name: "rpc_getBlockByNumber_count", + Help: "Count of getLogs per chain", + }, []string{"chain"}) + + TssNodeBlamePerPubKey = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "zetaclient", + Name: "tss_node_blame_count", + Help: "Tss node blame counter per pubkey", + }, []string{"pubkey"}) + + HotKeyBurnRate = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: "zetaclient", + Name: "hotkey_burn_rate", + Help: "Fee burn rate of the hotkey", + }) ) func NewMetrics() (*Metrics, error) { @@ -43,7 +61,7 @@ func NewMetrics() (*Metrics, error) { ) s := &http.Server{ - Addr: fmt.Sprintf(":8886"), + Addr: ":8886", Handler: server, ReadTimeout: 5 * time.Second, ReadHeaderTimeout: 5 * time.Second, @@ -54,33 +72,6 @@ func NewMetrics() (*Metrics, error) { }, nil } -func (m *Metrics) RegisterCounter(name string, help string) error { - if _, found := Counters[name]; found { - return fmt.Errorf("counter %s already registered", name) - } - counter := prometheus.NewCounter(prometheus.CounterOpts{ - Name: name, - Help: help, - }) - prometheus.MustRegister(counter) - Counters[name] = counter - return nil -} - -func (m *Metrics) RegisterGauge(name string, help string) error { - if _, found := Gauges[name]; found { - return fmt.Errorf("gauge %s already registered", name) - } - - var gauge = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: name, - Help: help, - }) - prometheus.MustRegister(gauge) - Gauges[name] = gauge - return nil -} - func (m *Metrics) Start() { log.Info().Msg("metrics server starting") go func() { diff --git a/zetaclient/metrics/metrics_test.go b/zetaclient/metrics/metrics_test.go index ef2aaa760c..f40001bbee 100644 --- a/zetaclient/metrics/metrics_test.go +++ b/zetaclient/metrics/metrics_test.go @@ -24,9 +24,9 @@ func (ms *MetricsSuite) SetUpSuite(c *C) { } func (ms *MetricsSuite) TestMetrics(c *C) { - err := ms.m.RegisterCounter("cnt1", "help to cnt1") - c.Assert(err, IsNil) - Counters["cnt1"].Inc() + GetFilterLogsPerChain.WithLabelValues("chain1").Inc() + GetFilterLogsPerChain.WithLabelValues("chain2").Inc() + GetFilterLogsPerChain.WithLabelValues("chain2").Inc() time.Sleep(1 * time.Second) res, err := http.Get("http://127.0.0.1:8886/metrics") c.Assert(err, IsNil) diff --git a/zetaclient/tss/tss_signer.go b/zetaclient/tss/tss_signer.go index 8fba8315f3..00bdd43d6c 100644 --- a/zetaclient/tss/tss_signer.go +++ b/zetaclient/tss/tss_signer.go @@ -32,7 +32,7 @@ import ( zcommon "github.com/zeta-chain/zetacore/common/cosmos" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - zetametrics "github.com/zeta-chain/zetacore/zetaclient/metrics" + "github.com/zeta-chain/zetacore/zetaclient/metrics" ) const ( @@ -73,7 +73,6 @@ type TSS struct { logger zerolog.Logger Signers []string CoreBridge interfaces.ZetaCoreBridger - Metrics *zetametrics.ChainMetrics // TODO: support multiple Bitcoin network, not just one network // https://github.com/zeta-chain/node/issues/1397 @@ -88,7 +87,6 @@ func NewTSS( cfg *config.Config, bridge interfaces.ZetaCoreBridger, tssHistoricalList []observertypes.TSS, - metrics *zetametrics.Metrics, bitcoinChainID int64, tssPassword string, hotkeyPassword string, @@ -118,12 +116,13 @@ func NewTSS( if err != nil { bridge.GetLogger().Error().Err(err).Msg("VerifyKeysharesForPubkeys fail") } - err = newTss.RegisterMetrics(metrics) + keygenRes, err := newTss.CoreBridge.GetKeyGen() if err != nil { - bridge.GetLogger().Err(err).Msg("tss.RegisterMetrics") return nil, err } - + for _, key := range keygenRes.GranteePubkeys { + metrics.TssNodeBlamePerPubKey.WithLabelValues(key).Inc() + } return &newTss, nil } @@ -224,12 +223,7 @@ func (tss *TSS) Sign(digest []byte, height uint64, nonce uint64, chain *common.C // Increment Blame counter for _, node := range ksRes.Blame.BlameNodes { - counter, err := tss.Metrics.GetPromCounter(node.Pubkey) - if err != nil { - log.Error().Err(err).Msgf("error getting counter: %s", node.Pubkey) - continue - } - counter.Inc() + metrics.TssNodeBlamePerPubKey.WithLabelValues(node.Pubkey).Inc() } } signature := ksRes.Signatures @@ -299,12 +293,7 @@ func (tss *TSS) SignBatch(digests [][]byte, height uint64, nonce uint64, chain * // Increment Blame counter for _, node := range ksRes.Blame.BlameNodes { - counter, err := tss.Metrics.GetPromCounter(node.Pubkey) - if err != nil { - log.Error().Err(err).Msgf("error getting counter: %s", node.Pubkey) - continue - } - counter.Inc() + metrics.TssNodeBlamePerPubKey.WithLabelValues(node.Pubkey).Inc() } } @@ -427,21 +416,6 @@ func (tss *TSS) InsertPubKey(pk string) error { return nil } -func (tss *TSS) RegisterMetrics(metrics *zetametrics.Metrics) error { - tss.Metrics = zetametrics.NewChainMetrics("tss", metrics) - keygenRes, err := tss.CoreBridge.GetKeyGen() - if err != nil { - return err - } - for _, key := range keygenRes.GranteePubkeys { - err := tss.Metrics.RegisterPromCounter(key, "tss node blame counter") - if err != nil { - return err - } - } - return nil -} - func (tss *TSS) VerifyKeysharesForPubkeys(tssList []observertypes.TSS, granteePubKey32 string) error { for _, t := range tssList { if wasNodePartOfTss(granteePubKey32, t.TssParticipantList) { diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index d37e2967cf..441081e34d 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -7,25 +7,21 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/bitcoin" "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" observertypes "github.com/zeta-chain/zetacore/x/observer/types" sdkmath "cosmossdk.io/math" - "github.com/pkg/errors" - prom "github.com/prometheus/client_golang/prometheus" "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/metrics" ) const ( - MaxLookaheadNonce = 120 - OutboundTxSignCount = "zetaclient_Outbound_tx_sign_count" - HotKeyBurnRate = "zetaclient_hotkey_burn_rate" + MaxLookaheadNonce = 120 ) type ZetaCoreLog struct { @@ -38,7 +34,6 @@ type CoreObserver struct { bridge interfaces.ZetaCoreBridger signerMap map[common.Chain]interfaces.ChainSigner clientMap map[common.Chain]interfaces.ChainClient - metrics *metrics.Metrics logger ZetaCoreLog cfg *config.Config ts *metrics.TelemetryServer @@ -51,7 +46,6 @@ func NewCoreObserver( bridge interfaces.ZetaCoreBridger, signerMap map[common.Chain]interfaces.ChainSigner, clientMap map[common.Chain]interfaces.ChainClient, - metrics *metrics.Metrics, logger zerolog.Logger, cfg *config.Config, ts *metrics.TelemetryServer, @@ -73,16 +67,7 @@ func NewCoreObserver( co.signerMap = signerMap co.clientMap = clientMap - co.metrics = metrics co.logger.ChainLogger.Info().Msg("starting core observer") - err := metrics.RegisterCounter(OutboundTxSignCount, "number of Outbound tx signed") - if err != nil { - co.logger.ChainLogger.Error().Err(err).Msg("error registering counter") - } - err = metrics.RegisterGauge(HotKeyBurnRate, "Fee burn rate of the hotkey") - if err != nil { - co.logger.ChainLogger.Error().Err(err).Msg("error registering gauge") - } balance, err := bridge.GetZetaHotKeyBalance() if err != nil { co.logger.ChainLogger.Error().Err(err).Msg("error getting last balance of the hot key") @@ -96,22 +81,6 @@ func (co *CoreObserver) Config() *config.Config { return co.cfg } -func (co *CoreObserver) GetPromCounter(name string) (prom.Counter, error) { - cnt, found := metrics.Counters[name] - if !found { - return nil, errors.New("counter not found") - } - return cnt, nil -} - -func (co *CoreObserver) GetPromGauge(name string) (prom.Gauge, error) { - gauge, found := metrics.Gauges[name] - if !found { - return nil, errors.New("gauge not found") - } - return gauge, nil -} - func (co *CoreObserver) MonitorCore() { myid := co.bridge.GetKeys().GetAddress() co.logger.ZetaChainWatcher.Info().Msgf("Starting Send Scheduler for %s", myid) @@ -170,12 +139,7 @@ func (co *CoreObserver) startCctxScheduler() { } // Set Current Hot key burn rate - gauge, err := co.GetPromGauge(HotKeyBurnRate) - if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("scheduleCctxEVM: failed to get prometheus gauge: %s for observer", metrics.PendingTxs) - continue - } // Gauge only takes float values - gauge.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) + metrics.HotKeyBurnRate.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) // schedule keysign for pending cctxs on each chain supportedChains := co.Config().GetEnabledChains() @@ -196,12 +160,7 @@ func (co *CoreObserver) startCctxScheduler() { continue } // Set Pending transactions prometheus gauge - gauge, err := ob.GetPromGauge(metrics.PendingTxs) - if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("scheduleCctxEVM: failed to get prometheus gauge: %s for chain %d", metrics.PendingTxs, c.ChainId) - continue - } - gauge.Set(float64(totalPending)) + metrics.PendingTxsPerChain.WithLabelValues(c.ChainName.String()).Set(float64(totalPending)) // #nosec G701 range is verified zetaHeight := uint64(bn) From 8d3a78c3d0699594228f098f13b0ee7ea1c0d80d Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:19:23 -0600 Subject: [PATCH 17/33] feat: initiated feature of zetaclient-restricted-address (#1790) * initiated feature of zetaclient-banned-address * fix some issues in e2e tests * fix gosec issues * some unit tests, comments and refactor * unified log prints for restricted address detection * move restricted address to Unreleased section * improved compliance log prints * update compliance log print according to CharlieMc --- changelog.md | 5 + cmd/zetaclientd/init.go | 2 + cmd/zetaclientd/main.go | 47 ++++- cmd/zetaclientd/start.go | 18 +- cmd/zetaclientd/utils.go | 22 +-- common/utils.go | 23 +++ x/crosschain/keeper/evm_deposit.go | 25 +-- zetaclient/bitcoin/bitcoin_client.go | 172 +++++++++++------- zetaclient/bitcoin/bitcoin_client_rpc_test.go | 4 +- zetaclient/bitcoin/bitcoin_client_test.go | 168 ++++++++++++++++- zetaclient/bitcoin/bitcoin_signer.go | 52 ++++-- zetaclient/bitcoin/bitcoin_signer_test.go | 4 +- zetaclient/bitcoin/inbound_tracker.go | 3 + zetaclient/bitcoin/utils.go | 34 +++- zetaclient/bitcoin/utils_test.go | 165 ++++++++--------- zetaclient/common/utils.go | 52 ++++++ zetaclient/common/utils_test.go | 53 ++++++ zetaclient/config/config.go | 21 +++ zetaclient/config/types.go | 22 +++ zetaclient/evm/evm_client.go | 52 ++++-- zetaclient/evm/evm_client_test.go | 94 ++++++++++ zetaclient/evm/evm_signer.go | 39 ++-- zetaclient/evm/inbounds.go | 51 +++++- zetaclient/evm/inbounds_test.go | 88 +++++++++ zetaclient/interfaces/interfaces.go | 4 +- .../btc/block_mempool.space_8332_828440.json} | 0 .../btc/block_trimmed_8332_828440.json} | 0 .../btc/outtx_8332_148_raw_result.json | 111 +++++++++++ zetaclient/testdata/cctx/cctx_1_6270.json | 34 ++++ zetaclient/testdata/cctx/cctx_8332_148.json | 32 ++++ ...ffc40ca898e134525c42c2ae3cbc5725f9d76.json | 60 ++++++ zetaclient/testutils/constant.go | 9 + zetaclient/testutils/mock.go | 72 ++++++++ zetaclient/testutils/utils.go | 13 +- zetaclient/zetacore_observer.go | 4 +- 35 files changed, 1285 insertions(+), 270 deletions(-) create mode 100644 zetaclient/common/utils.go create mode 100644 zetaclient/common/utils_test.go create mode 100644 zetaclient/evm/inbounds_test.go rename zetaclient/{bitcoin/testdata/mempool.space_block_828440.json => testdata/btc/block_mempool.space_8332_828440.json} (100%) rename zetaclient/{bitcoin/testdata/bitcoin_block_trimmed_828440.json => testdata/btc/block_trimmed_8332_828440.json} (100%) create mode 100644 zetaclient/testdata/btc/outtx_8332_148_raw_result.json create mode 100644 zetaclient/testdata/cctx/cctx_1_6270.json create mode 100644 zetaclient/testdata/cctx/cctx_8332_148.json create mode 100644 zetaclient/testdata/evm/chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json create mode 100644 zetaclient/testutils/constant.go create mode 100644 zetaclient/testutils/mock.go diff --git a/changelog.md b/changelog.md index e16dc3acf2..857314ebea 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,12 @@ ## Unreleased +### Features + +* [1789](https://github.com/zeta-chain/node/issues/1789) - block cross-chain transactions that involve restricted addresses + ### Refactor + * [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure ### Tests diff --git a/cmd/zetaclientd/init.go b/cmd/zetaclientd/init.go index 3ae36cf66b..4af5a437b3 100644 --- a/cmd/zetaclientd/init.go +++ b/cmd/zetaclientd/init.go @@ -4,6 +4,7 @@ import ( "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/zeta-chain/zetacore/zetaclient/config" + "github.com/zeta-chain/zetacore/zetaclient/testutils" ) var InitCmd = &cobra.Command{ @@ -95,6 +96,7 @@ func Initialize(_ *cobra.Command, _ []string) error { configData.KeyringBackend = config.KeyringBackend(initArgs.KeyringBackend) configData.HsmMode = initArgs.HsmMode configData.HsmHotKey = initArgs.HsmHotKey + configData.ComplianceConfig = testutils.ComplianceConfigTest() //Save config file return config.Save(&configData, rootArgs.zetaCoreHome) diff --git a/cmd/zetaclientd/main.go b/cmd/zetaclientd/main.go index a51fb40d82..767b1fd78e 100644 --- a/cmd/zetaclientd/main.go +++ b/cmd/zetaclientd/main.go @@ -1,10 +1,15 @@ package main import ( + "path/filepath" + + "github.com/rs/zerolog/log" + ecdsakeygen "github.com/binance-chain/tss-lib/ecdsa/keygen" "github.com/cosmos/cosmos-sdk/server" svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" "github.com/rs/zerolog" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/cmd" @@ -20,6 +25,10 @@ import ( "github.com/zeta-chain/zetacore/app" ) +const ( + ComplianceLogFile = "compliance.log" +) + var ( preParams *ecdsakeygen.LocalPreParams ) @@ -51,19 +60,53 @@ func SetupConfigForTest() { } -func InitLogger(cfg *config.Config) zerolog.Logger { +func InitLogger(cfg *config.Config) (clientcommon.ClientLogger, error) { + // open compliance log file + file, err := OpenComplianceLogFile(cfg) + if err != nil { + return clientcommon.DefaultLoggers(), err + } + var logger zerolog.Logger + var loggerCompliance zerolog.Logger switch cfg.LogFormat { case "json": logger = zerolog.New(os.Stdout).Level(zerolog.Level(cfg.LogLevel)).With().Timestamp().Logger() + loggerCompliance = zerolog.New(file).Level(zerolog.Level(cfg.LogLevel)).With().Timestamp().Logger() case "text": logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}).Level(zerolog.Level(cfg.LogLevel)).With().Timestamp().Logger() + loggerCompliance = zerolog.New(file).Level(zerolog.Level(cfg.LogLevel)).With().Timestamp().Logger() default: logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}) + loggerCompliance = zerolog.New(file).With().Timestamp().Logger() } if cfg.LogSampler { logger = logger.Sample(&zerolog.BasicSampler{N: 5}) } - return logger + log.Logger = logger // set global logger + + return clientcommon.ClientLogger{ + Std: log.Logger, + Compliance: loggerCompliance, + }, nil +} + +func OpenComplianceLogFile(cfg *config.Config) (*os.File, error) { + // use zetacore home as default + logPath := cfg.ZetaCoreHome + if cfg.ComplianceConfig != nil && cfg.ComplianceConfig.LogPath != "" { + logPath = cfg.ComplianceConfig.LogPath + } + + // clean file name + name := filepath.Join(logPath, ComplianceLogFile) + name, err := filepath.Abs(name) + if err != nil { + return nil, err + } + name = filepath.Clean(name) + + // open (or create) compliance log file + return os.OpenFile(name, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) } diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 911dd9a3dc..e6046586cc 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -57,7 +57,12 @@ func start(_ *cobra.Command, _ []string) error { if err != nil { return err } - log.Logger = InitLogger(cfg) + loggers, err := InitLogger(cfg) + if err != nil { + log.Error().Err(err).Msg("InitLogger failed") + return err + } + //Wait until zetacore has started if len(cfg.Peer) != 0 { err := validatePeer(cfg.Peer) @@ -67,9 +72,10 @@ func start(_ *cobra.Command, _ []string) error { } } - masterLogger := log.Logger + masterLogger := loggers.Std startLogger := masterLogger.With().Str("module", "startup").Logger() + // Wait until zetacore is up waitForZetaCore(cfg, startLogger) startLogger.Info().Msgf("ZetaCore is ready , Trying to connect to %s", cfg.Peer) @@ -218,8 +224,8 @@ func start(_ *cobra.Command, _ []string) error { } } - // CreateSignerMap: This creates a map of all signers for each chain . Each signer is responsible for signing transactions for a particular chain - signerMap, err := CreateSignerMap(tss, masterLogger, cfg, telemetryServer) + // CreateSignerMap: This creates a map of all signers for each chain. Each signer is responsible for signing transactions for a particular chain + signerMap, err := CreateSignerMap(tss, loggers, cfg, telemetryServer) if err != nil { log.Error().Err(err).Msg("CreateSignerMap") return err @@ -232,8 +238,8 @@ func start(_ *cobra.Command, _ []string) error { } dbpath := filepath.Join(userDir, ".zetaclient/chainobserver") - // CreateChainClientMap : This creates a map of all chain clients . Each chain client is responsible for listening to events on the chain and processing them - chainClientMap, err := CreateChainClientMap(zetaBridge, tss, dbpath, masterLogger, cfg, telemetryServer) + // CreateChainClientMap : This creates a map of all chain clients. Each chain client is responsible for listening to events on the chain and processing them + chainClientMap, err := CreateChainClientMap(zetaBridge, tss, dbpath, loggers, cfg, telemetryServer) if err != nil { startLogger.Err(err).Msg("CreateSignerMap") return err diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index 648f592a56..e04f62bfc2 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -3,11 +3,11 @@ package main import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/cosmos" "github.com/zeta-chain/zetacore/zetaclient/authz" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -51,7 +51,7 @@ func CreateZetaBridge(cfg *config.Config, telemetry *metrics.TelemetryServer, ho func CreateSignerMap( tss interfaces.TSSSigner, - logger zerolog.Logger, + loggers clientcommon.ClientLogger, cfg *config.Config, ts *metrics.TelemetryServer, ) (map[common.Chain]interfaces.ChainSigner, error) { @@ -63,9 +63,9 @@ func CreateSignerMap( } mpiAddress := ethcommon.HexToAddress(evmConfig.ChainParams.ConnectorContractAddress) erc20CustodyAddress := ethcommon.HexToAddress(evmConfig.ChainParams.Erc20CustodyContractAddress) - signer, err := evm.NewEVMSigner(evmConfig.Chain, evmConfig.Endpoint, tss, config.GetConnectorABI(), config.GetERC20CustodyABI(), mpiAddress, erc20CustodyAddress, logger, ts) + signer, err := evm.NewEVMSigner(evmConfig.Chain, evmConfig.Endpoint, tss, config.GetConnectorABI(), config.GetERC20CustodyABI(), mpiAddress, erc20CustodyAddress, loggers, ts) if err != nil { - logger.Error().Err(err).Msgf("NewEVMSigner error for chain %s", evmConfig.Chain.String()) + loggers.Std.Error().Err(err).Msgf("NewEVMSigner error for chain %s", evmConfig.Chain.String()) continue } signerMap[evmConfig.Chain] = signer @@ -73,9 +73,9 @@ func CreateSignerMap( // BTC signer btcChain, btcConfig, enabled := cfg.GetBTCConfig() if enabled { - signer, err := bitcoin.NewBTCSigner(btcConfig, tss, logger, ts) + signer, err := bitcoin.NewBTCSigner(btcConfig, tss, loggers, ts) if err != nil { - logger.Error().Err(err).Msgf("NewBTCSigner error for chain %s", btcChain.String()) + loggers.Std.Error().Err(err).Msgf("NewBTCSigner error for chain %s", btcChain.String()) } else { signerMap[btcChain] = signer } @@ -88,7 +88,7 @@ func CreateChainClientMap( bridge *zetabridge.ZetaCoreBridge, tss interfaces.TSSSigner, dbpath string, - logger zerolog.Logger, + loggers clientcommon.ClientLogger, cfg *config.Config, ts *metrics.TelemetryServer, ) (map[common.Chain]interfaces.ChainClient, error) { @@ -98,9 +98,9 @@ func CreateChainClientMap( if evmConfig.Chain.IsZetaChain() { continue } - co, err := evm.NewEVMChainClient(bridge, tss, dbpath, logger, cfg, *evmConfig, ts) + co, err := evm.NewEVMChainClient(bridge, tss, dbpath, loggers, cfg, *evmConfig, ts) if err != nil { - logger.Error().Err(err).Msgf("NewEVMChainClient error for chain %s", evmConfig.Chain.String()) + loggers.Std.Error().Err(err).Msgf("NewEVMChainClient error for chain %s", evmConfig.Chain.String()) continue } clientMap[evmConfig.Chain] = co @@ -108,9 +108,9 @@ func CreateChainClientMap( // BTC client btcChain, btcConfig, enabled := cfg.GetBTCConfig() if enabled { - co, err := bitcoin.NewBitcoinClient(btcChain, bridge, tss, dbpath, logger, btcConfig, ts) + co, err := bitcoin.NewBitcoinClient(btcChain, bridge, tss, dbpath, loggers, btcConfig, ts) if err != nil { - logger.Error().Err(err).Msgf("NewBitcoinClient error for chain %s", btcChain.String()) + loggers.Std.Error().Err(err).Msgf("NewBitcoinClient error for chain %s", btcChain.String()) } else { clientMap[btcChain] = co diff --git a/common/utils.go b/common/utils.go index 773c709210..62c4e27ad5 100644 --- a/common/utils.go +++ b/common/utils.go @@ -41,3 +41,26 @@ func StringToHash(chainID int64, hash string) ([]byte, error) { } return nil, fmt.Errorf("cannot convert hash to bytes for chain %d", chainID) } + +// ParseAddressAndData parses the message string into an address and data +// message is hex encoded byte array +// [ contractAddress calldata ] +// [ 20B, variable] +func ParseAddressAndData(message string) (ethcommon.Address, []byte, error) { + if len(message) == 0 { + return ethcommon.Address{}, nil, nil + } + + data, err := hex.DecodeString(message) + if err != nil { + return ethcommon.Address{}, nil, fmt.Errorf("message should be a hex encoded string: " + err.Error()) + } + + if len(data) < 20 { + return ethcommon.Address{}, data, nil + } + + address := ethcommon.BytesToAddress(data[:20]) + data = data[20:] + return address, data, nil +} diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index c33adc8420..50d8277f15 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -43,7 +43,7 @@ func (k Keeper) HandleEVMDeposit( } } else { // cointype is Gas or ERC20; then it could be a ZRC20 deposit/depositAndCall cctx. - parsedAddress, data, err := parseAddressAndData(msg.Message) + parsedAddress, data, err := common.ParseAddressAndData(msg.Message) if err != nil { return false, errors.Wrap(types.ErrUnableToParseAddress, err.Error()) } @@ -110,26 +110,3 @@ func errShouldRevertCctx(err error) bool { errors.Is(err, fungibletypes.ErrCallNonContract) || errors.Is(err, fungibletypes.ErrPausedZRC20) } - -// parseAddressAndData parses the message string into an address and data -// message is hex encoded byte array -// [ contractAddress calldata ] -// [ 20B, variable] -func parseAddressAndData(message string) (ethcommon.Address, []byte, error) { - if len(message) == 0 { - return ethcommon.Address{}, nil, nil - } - - data, err := hex.DecodeString(message) - if err != nil { - return ethcommon.Address{}, nil, fmt.Errorf("message should be a hex encoded string: " + err.Error()) - } - - if len(data) < 20 { - return ethcommon.Address{}, data, nil - } - - address := ethcommon.BytesToAddress(data[:20]) - data = data[20:] - return address, data, nil -} diff --git a/zetaclient/bitcoin/bitcoin_client.go b/zetaclient/bitcoin/bitcoin_client.go index 7b9c18becb..6a5e3b336a 100644 --- a/zetaclient/bitcoin/bitcoin_client.go +++ b/zetaclient/bitcoin/bitcoin_client.go @@ -23,12 +23,14 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + ethcommon "github.com/ethereum/go-ethereum/common" lru "github.com/hashicorp/golang-lru" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/metrics" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" @@ -49,6 +51,7 @@ type BTCLog struct { ObserveOutTx zerolog.Logger WatchUTXOS zerolog.Logger WatchGasPrice zerolog.Logger + Compliance zerolog.Logger } // BTCChainClient represents a chain configuration for Bitcoin @@ -134,7 +137,7 @@ func NewBitcoinClient( bridge interfaces.ZetaCoreBridger, tss interfaces.TSSSigner, dbpath string, - logger zerolog.Logger, + loggers clientcommon.ClientLogger, btcCfg config.BTCConfig, ts *metrics.TelemetryServer, ) (*BTCChainClient, error) { @@ -149,13 +152,14 @@ func NewBitcoinClient( } ob.netParams = netParams ob.Mu = &sync.Mutex{} - chainLogger := logger.With().Str("chain", chain.ChainName.String()).Logger() + chainLogger := loggers.Std.With().Str("chain", chain.ChainName.String()).Logger() ob.logger = BTCLog{ ChainLogger: chainLogger, WatchInTx: chainLogger.With().Str("module", "WatchInTx").Logger(), ObserveOutTx: chainLogger.With().Str("module", "observeOutTx").Logger(), WatchUTXOS: chainLogger.With().Str("module", "WatchUTXOS").Logger(), WatchGasPrice: chainLogger.With().Str("module", "WatchGasPrice").Logger(), + Compliance: loggers.Compliance, } ob.zetaClient = bridge @@ -450,13 +454,15 @@ func (ob *BTCChainClient) observeInTx() error { // post inbound vote message to zetacore for _, inTx := range inTxs { msg := ob.GetInboundVoteMessageFromBtcEvent(inTx) - zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg) - if err != nil { - ob.logger.WatchInTx.Error().Err(err).Msgf("observeInTxBTC: error posting to zeta core for tx %s", inTx.TxHash) - return err // we have to re-scan this block next time - } else if zetaHash != "" { - ob.logger.WatchInTx.Info().Msgf("observeInTxBTC: PostVoteInbound zeta tx hash: %s inTx %s ballot %s fee %v", - zetaHash, inTx.TxHash, ballot, depositorFee) + if msg != nil { + zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg) + if err != nil { + ob.logger.WatchInTx.Error().Err(err).Msgf("observeInTxBTC: error posting to zeta core for tx %s", inTx.TxHash) + return err // we have to re-scan this block next time + } else if zetaHash != "" { + ob.logger.WatchInTx.Info().Msgf("observeInTxBTC: PostVoteInbound zeta tx hash: %s inTx %s ballot %s fee %v", + zetaHash, inTx.TxHash, ballot, depositorFee) + } } } } @@ -485,7 +491,12 @@ func (ob *BTCChainClient) ConfirmationsThreshold(amount *big.Int) int64 { } // IsSendOutTxProcessed returns isIncluded(or inMempool), isConfirmed, Error -func (ob *BTCChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, _ common.CoinType, logger zerolog.Logger) (bool, bool, error) { +func (ob *BTCChainClient) IsSendOutTxProcessed(cctx *types.CrossChainTx, logger zerolog.Logger) (bool, bool, error) { + params := *cctx.GetCurrentOutTxParam() + sendHash := cctx.Index + nonce := cctx.GetCurrentOutTxParam().OutboundTxTssNonce + + // get broadcasted outtx and tx result outTxID := ob.GetTxID(nonce) logger.Info().Msgf("IsSendOutTxProcessed %s", outTxID) @@ -494,13 +505,6 @@ func (ob *BTCChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, _ res, included := ob.includedTxResults[outTxID] ob.Mu.Unlock() - // Get original cctx parameters - params, err := ob.GetCctxParams(nonce) - if err != nil { - ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: can't find pending cctx for nonce %d", nonce) - return false, false, err - } - if !included { if !broadcasted { return false, false, nil @@ -515,10 +519,9 @@ func (ob *BTCChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, _ } // Try including this outTx broadcasted by myself - txResult, inMempool := ob.checkIncludedTx(txnHash, params) - if txResult == nil { - ob.logger.ObserveOutTx.Error().Err(err).Msg("IsSendOutTxProcessed: checkIncludedTx failed") - return false, false, err + txResult, inMempool := ob.checkIncludedTx(cctx, txnHash) + if txResult == nil { // check failed, try again next time + return false, false, nil } else if inMempool { // still in mempool (should avoid unnecessary Tss keysign) ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: outTx %s is still in mempool", outTxID) return true, false, nil @@ -673,6 +676,12 @@ func (ob *BTCChainClient) GetInboundVoteMessageFromBtcEvent(inTx *BTCInTxEvnet) amount = amount.Mul(amount, big.NewFloat(1e8)) amountInt, _ := amount.Int(nil) message := hex.EncodeToString(inTx.MemoBytes) + + // compliance check + if ob.IsInTxRestricted(inTx) { + return nil + } + return zetabridge.GetInBoundVoteMessage( inTx.FromAddress, ob.chain.ChainId, @@ -691,6 +700,21 @@ func (ob *BTCChainClient) GetInboundVoteMessageFromBtcEvent(inTx *BTCInTxEvnet) ) } +// IsInTxRestricted returns true if the inTx contains restricted addresses +func (ob *BTCChainClient) IsInTxRestricted(inTx *BTCInTxEvnet) bool { + receiver := "" + parsedAddress, _, err := common.ParseAddressAndData(hex.EncodeToString(inTx.MemoBytes)) + if err == nil && parsedAddress != (ethcommon.Address{}) { + receiver = parsedAddress.Hex() + } + if config.ContainRestrictedAddress(inTx.FromAddress, receiver) { + clientcommon.PrintComplianceLog(ob.logger.WatchInTx, ob.logger.Compliance, + false, ob.chain.ChainId, inTx.TxHash, inTx.FromAddress, receiver, "BTC") + return true + } + return false +} + func GetBtcEvent( tx btcjson.TxRawResult, targetAddress string, @@ -1051,17 +1075,6 @@ func (ob *BTCChainClient) SaveBroadcastedTx(txHash string, nonce uint64) { ob.logger.ObserveOutTx.Info().Msgf("SaveBroadcastedTx: saved broadcasted txHash %s for outTx %s", txHash, outTxID) } -func (ob *BTCChainClient) GetCctxParams(nonce uint64) (types.OutboundTxParams, error) { - send, err := ob.zetaClient.GetCctxByNonce(ob.chain.ChainId, nonce) - if err != nil { - return types.OutboundTxParams{}, err - } - if send.GetCurrentOutTxParam() == nil { // never happen - return types.OutboundTxParams{}, fmt.Errorf("GetPendingCctx: nil outbound tx params") - } - return *send.GetCurrentOutTxParam(), nil -} - func (ob *BTCChainClient) observeOutTx() { ticker, err := clienttypes.NewDynamicTicker("Bitcoin_observeOutTx", ob.GetChainParams().OutTxTicker) if err != nil { @@ -1081,13 +1094,14 @@ func (ob *BTCChainClient) observeOutTx() { for _, tracker := range trackers { // get original cctx parameters outTxID := ob.GetTxID(tracker.Nonce) - params, err := ob.GetCctxParams(tracker.Nonce) + cctx, err := ob.zetaClient.GetCctxByNonce(ob.chain.ChainId, tracker.Nonce) if err != nil { ob.logger.ObserveOutTx.Info().Err(err).Msgf("observeOutTx: can't find cctx for nonce %d", tracker.Nonce) break } - if tracker.Nonce != params.OutboundTxTssNonce { // Tanmay: it doesn't hurt to check - ob.logger.ObserveOutTx.Error().Msgf("observeOutTx: tracker nonce %d not match cctx nonce %d", tracker.Nonce, params.OutboundTxTssNonce) + nonce := cctx.GetCurrentOutTxParam().OutboundTxTssNonce + if tracker.Nonce != nonce { // Tanmay: it doesn't hurt to check + ob.logger.ObserveOutTx.Error().Msgf("observeOutTx: tracker nonce %d not match cctx nonce %d", tracker.Nonce, nonce) break } if len(tracker.HashList) > 1 { @@ -1098,7 +1112,7 @@ func (ob *BTCChainClient) observeOutTx() { txCount := 0 var txResult *btcjson.GetTransactionResult for _, txHash := range tracker.HashList { - result, inMempool := ob.checkIncludedTx(txHash.TxHash, params) + result, inMempool := ob.checkIncludedTx(cctx, txHash.TxHash) if result != nil && !inMempool { // included txCount++ txResult = result @@ -1126,8 +1140,8 @@ func (ob *BTCChainClient) observeOutTx() { // checkIncludedTx checks if a txHash is included and returns (txResult, inMempool) // Note: if txResult is nil, then inMempool flag should be ignored. -func (ob *BTCChainClient) checkIncludedTx(txHash string, params types.OutboundTxParams) (*btcjson.GetTransactionResult, bool) { - outTxID := ob.GetTxID(params.OutboundTxTssNonce) +func (ob *BTCChainClient) checkIncludedTx(cctx *types.CrossChainTx, txHash string) (*btcjson.GetTransactionResult, bool) { + outTxID := ob.GetTxID(cctx.GetCurrentOutTxParam().OutboundTxTssNonce) hash, getTxResult, err := ob.GetTxResultByHash(txHash) if err != nil { ob.logger.ObserveOutTx.Error().Err(err).Msgf("checkIncludedTx: error GetTxResultByHash: %s", txHash) @@ -1138,7 +1152,7 @@ func (ob *BTCChainClient) checkIncludedTx(txHash string, params types.OutboundTx return nil, false } if getTxResult.Confirmations >= 0 { // check included tx only - err = ob.checkTssOutTxResult(hash, getTxResult, params, params.OutboundTxTssNonce) + err = ob.checkTssOutTxResult(cctx, hash, getTxResult) if err != nil { ob.logger.ObserveOutTx.Error().Err(err).Msgf("checkIncludedTx: error verify bitcoin outTx %s outTxID %s", txHash, outTxID) return nil, false @@ -1199,7 +1213,9 @@ func (ob *BTCChainClient) removeIncludedTx(nonce uint64) { // - check if all inputs are segwit && TSS inputs // // Returns: true if outTx passes basic checks. -func (ob *BTCChainClient) checkTssOutTxResult(hash *chainhash.Hash, res *btcjson.GetTransactionResult, params types.OutboundTxParams, nonce uint64) error { +func (ob *BTCChainClient) checkTssOutTxResult(cctx *types.CrossChainTx, hash *chainhash.Hash, res *btcjson.GetTransactionResult) error { + params := cctx.GetCurrentOutTxParam() + nonce := params.OutboundTxTssNonce rawResult, err := ob.getRawTxResult(hash, res) if err != nil { return errors.Wrapf(err, "checkTssOutTxResult: error GetRawTxResultByHash %s", hash.String()) @@ -1208,9 +1224,18 @@ func (ob *BTCChainClient) checkTssOutTxResult(hash *chainhash.Hash, res *btcjson if err != nil { return errors.Wrapf(err, "checkTssOutTxResult: invalid TSS Vin in outTx %s nonce %d", hash, nonce) } - err = ob.checkTSSVout(rawResult.Vout, params, nonce) - if err != nil { - return errors.Wrapf(err, "checkTssOutTxResult: invalid TSS Vout in outTx %s nonce %d", hash, nonce) + + // differentiate between normal and restricted cctx + if clientcommon.IsCctxRestricted(cctx) { + err = ob.checkTSSVoutCancelled(params, rawResult.Vout) + if err != nil { + return errors.Wrapf(err, "checkTssOutTxResult: invalid TSS Vout in cancelled outTx %s nonce %d", hash, nonce) + } + } else { + err = ob.checkTSSVout(params, rawResult.Vout) + if err != nil { + return errors.Wrapf(err, "checkTssOutTxResult: invalid TSS Vout in outTx %s nonce %d", hash, nonce) + } } return nil } @@ -1291,37 +1316,19 @@ func (ob *BTCChainClient) checkTSSVin(vins []btcjson.Vin, nonce uint64) error { // - The first output is the nonce-mark // - The second output is the correct payment to recipient // - The third output is the change to TSS (optional) -func (ob *BTCChainClient) checkTSSVout(vouts []btcjson.Vout, params types.OutboundTxParams, nonce uint64) error { +func (ob *BTCChainClient) checkTSSVout(params *types.OutboundTxParams, vouts []btcjson.Vout) error { // vouts: [nonce-mark, payment to recipient, change to TSS (optional)] if !(len(vouts) == 2 || len(vouts) == 3) { return fmt.Errorf("checkTSSVout: invalid number of vouts: %d", len(vouts)) } + nonce := params.OutboundTxTssNonce tssAddress := ob.Tss.BTCAddress() for _, vout := range vouts { - amount, err := GetSatoshis(vout.Value) - if err != nil { - return errors.Wrap(err, "checkTSSVout: error getting satoshis") - } - // decode P2WPKH scriptPubKey - scriptPubKey := vout.ScriptPubKey.Hex - decodedScriptPubKey, err := hex.DecodeString(scriptPubKey) + recvAddress, amount, err := DecodeP2WPKHVout(vout, ob.chain) if err != nil { - return errors.Wrapf(err, "checkTSSVout: error decoding scriptPubKey %s", scriptPubKey) - } - if len(decodedScriptPubKey) != 22 { // P2WPKH script - return fmt.Errorf("checkTSSVout: unsupported scriptPubKey: %s", scriptPubKey) + return errors.Wrap(err, "checkTSSVout: error decoding P2WPKH vout") } - witnessVersion := decodedScriptPubKey[0] - witnessProgram := decodedScriptPubKey[2:] - if witnessVersion != 0 { - return fmt.Errorf("checkTSSVout: unsupported witness in scriptPubKey %s", scriptPubKey) - } - recvAddress, err := ob.chain.BTCAddressFromWitnessProgram(witnessProgram) - if err != nil { - return errors.Wrapf(err, "checkTSSVout: error getting receiver from witness program %s", witnessProgram) - } - // 1st vout: nonce-mark if vout.N == 0 { if recvAddress != tssAddress { @@ -1351,6 +1358,41 @@ func (ob *BTCChainClient) checkTSSVout(vouts []btcjson.Vout, params types.Outbou return nil } +// checkTSSVoutCancelled vout is valid if: +// - The first output is the nonce-mark +// - The second output is the change to TSS (optional) +func (ob *BTCChainClient) checkTSSVoutCancelled(params *types.OutboundTxParams, vouts []btcjson.Vout) error { + // vouts: [nonce-mark, change to TSS (optional)] + if !(len(vouts) == 1 || len(vouts) == 2) { + return fmt.Errorf("checkTSSVoutCancelled: invalid number of vouts: %d", len(vouts)) + } + + nonce := params.OutboundTxTssNonce + tssAddress := ob.Tss.BTCAddress() + for _, vout := range vouts { + recvAddress, amount, err := DecodeP2WPKHVout(vout, ob.chain) + if err != nil { + return errors.Wrap(err, "checkTSSVoutCancelled: error decoding P2WPKH vout") + } + // 1st vout: nonce-mark + if vout.N == 0 { + if recvAddress != tssAddress { + return fmt.Errorf("checkTSSVoutCancelled: nonce-mark address %s not match TSS address %s", recvAddress, tssAddress) + } + if amount != common.NonceMarkAmount(nonce) { + return fmt.Errorf("checkTSSVoutCancelled: nonce-mark amount %d not match nonce-mark amount %d", amount, common.NonceMarkAmount(nonce)) + } + } + // 2nd vout: change to TSS (optional) + if vout.N == 2 { + if recvAddress != tssAddress { + return fmt.Errorf("checkTSSVoutCancelled: change address %s not match TSS address %s", recvAddress, tssAddress) + } + } + } + return nil +} + func (ob *BTCChainClient) BuildBroadcastedTxMap() error { var broadcastedTransactions []clienttypes.OutTxHashSQLType if err := ob.db.Find(&broadcastedTransactions).Error; err != nil { diff --git a/zetaclient/bitcoin/bitcoin_client_rpc_test.go b/zetaclient/bitcoin/bitcoin_client_rpc_test.go index 8c5ffd43b1..d1a00b907f 100644 --- a/zetaclient/bitcoin/bitcoin_client_rpc_test.go +++ b/zetaclient/bitcoin/bitcoin_client_rpc_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/zeta-chain/zetacore/common" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/testutils" ) @@ -43,7 +44,8 @@ func (suite *BitcoinClientTestSuite) SetupTest() { tss := interfaces.TestSigner{ PrivKey: privateKey, } - client, err := NewBitcoinClient(common.BtcRegtestChain(), nil, tss, "/tmp", log.Logger, config.BTCConfig{}, nil) + //client, err := NewBitcoinClient(common.BtcTestNetChain(), nil, tss, "", nil) + client, err := NewBitcoinClient(common.BtcRegtestChain(), nil, tss, "/tmp", clientcommon.DefaultLoggers(), config.BTCConfig{}, nil) suite.Require().NoError(err) suite.BitcoinChainClient = client skBytes, err := hex.DecodeString(skHex) diff --git a/zetaclient/bitcoin/bitcoin_client_test.go b/zetaclient/bitcoin/bitcoin_client_test.go index fe211cdee6..b9a29db816 100644 --- a/zetaclient/bitcoin/bitcoin_client_test.go +++ b/zetaclient/bitcoin/bitcoin_client_test.go @@ -16,10 +16,31 @@ import ( "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/testutils" ) +func MockBTCClientMainnet() *BTCChainClient { + return &BTCChainClient{ + chain: common.BtcMainnetChain(), + zetaClient: testutils.MockCoreBridge(), + Tss: testutils.NewMockTSSMainnet(), + } +} + +// LoadTxRawResultNCctx loads archived outtx raw result and corresponding cctx +func LoadTxRawResultNCctx(t *testing.T, fileTxResult string, fileCctx string) (btcjson.TxRawResult, *crosschaintypes.CrossChainTx) { + var rawResult btcjson.TxRawResult + err := testutils.LoadObjectFromJSONFile(&rawResult, path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json")) + require.NoError(t, err) + + var cctx crosschaintypes.CrossChainTx + err = testutils.LoadObjectFromJSONFile(&cctx, path.Join("../", testutils.TestDataPathCctx, "cctx_8332_148.json")) + require.NoError(t, err) + return rawResult, &cctx +} + func TestConfirmationThreshold(t *testing.T) { client := &BTCChainClient{Mu: &sync.Mutex{}} t.Run("should return confirmations in chain param", func(t *testing.T) { @@ -41,12 +62,12 @@ func TestConfirmationThreshold(t *testing.T) { func TestAvgFeeRateBlock828440(t *testing.T) { // load archived block 828440 var blockVb btcjson.GetBlockVerboseTxResult - err := testutils.LoadObjectFromJSONFile(&blockVb, path.Join(testutils.TestDataPath, "bitcoin_block_trimmed_828440.json")) + err := testutils.LoadObjectFromJSONFile(&blockVb, path.Join("../", testutils.TestDataPathBTC, "block_trimmed_8332_828440.json")) require.NoError(t, err) // https://mempool.space/block/000000000000000000025ca01d2c1094b8fd3bacc5468cc3193ced6a14618c27 var blockMb testutils.MempoolBlock - err = testutils.LoadObjectFromJSONFile(&blockMb, path.Join(testutils.TestDataPath, "mempool.space_block_828440.json")) + err = testutils.LoadObjectFromJSONFile(&blockMb, path.Join("../", testutils.TestDataPathBTC, "block_mempool.space_8332_828440.json")) require.NoError(t, err) gasRate, err := CalcBlockAvgFeeRate(&blockVb, &chaincfg.MainNetParams) @@ -57,7 +78,7 @@ func TestAvgFeeRateBlock828440(t *testing.T) { func TestAvgFeeRateBlock828440Errors(t *testing.T) { // load archived block 828440 var blockVb btcjson.GetBlockVerboseTxResult - err := testutils.LoadObjectFromJSONFile(&blockVb, path.Join(testutils.TestDataPath, "bitcoin_block_trimmed_828440.json")) + err := testutils.LoadObjectFromJSONFile(&blockVb, path.Join("../", testutils.TestDataPathBTC, "block_trimmed_8332_828440.json")) require.NoError(t, err) t.Run("block has no transactions", func(t *testing.T) { @@ -144,7 +165,7 @@ func TestAvgFeeRateBlock828440Errors(t *testing.T) { func TestCalcDepositorFee828440(t *testing.T) { // load archived block 828440 var blockVb btcjson.GetBlockVerboseTxResult - err := testutils.LoadObjectFromJSONFile(&blockVb, path.Join(testutils.TestDataPath, "bitcoin_block_trimmed_828440.json")) + err := testutils.LoadObjectFromJSONFile(&blockVb, path.Join("../", testutils.TestDataPathBTC, "block_trimmed_8332_828440.json")) require.NoError(t, err) dynamicFee828440 := DepositorFee(32 * common.DefaultGasPriceMultiplier) @@ -168,3 +189,142 @@ func TestCalcDepositorFee828440(t *testing.T) { require.NotEqual(t, DefaultDepositorFee, fee) require.Equal(t, dynamicFee828440, fee) } + +func TestCheckTSSVout(t *testing.T) { + // the archived outtx raw result file and cctx file + // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 + fileCctx := path.Join("../", testutils.TestDataPathCctx, "cctx_8332_148.json") + fileTxResult := path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json") + + // create mainnet mock client + btcClient := MockBTCClientMainnet() + + t.Run("valid TSS vout should pass", func(t *testing.T) { + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + err := btcClient.checkTSSVout(params, rawResult.Vout) + require.NoError(t, err) + }) + t.Run("should fail if vout length < 2 or > 3", func(t *testing.T) { + _, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + + err := btcClient.checkTSSVout(params, []btcjson.Vout{{}}) + require.ErrorContains(t, err, "invalid number of vouts") + + err = btcClient.checkTSSVout(params, []btcjson.Vout{{}, {}, {}, {}}) + require.ErrorContains(t, err, "invalid number of vouts") + }) + t.Run("should fail if vout 0 is not to the TSS address", func(t *testing.T) { + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + + // not TSS address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus + rawResult.Vout[0].ScriptPubKey.Hex = "0014ba8be635673034d4d0ddc9447409b594385ec4aa" + err := btcClient.checkTSSVout(params, rawResult.Vout) + require.ErrorContains(t, err, "not match TSS address") + }) + t.Run("should fail if vout 0 not match nonce mark", func(t *testing.T) { + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + + // not match nonce mark + rawResult.Vout[0].Value = 0.00000147 + err := btcClient.checkTSSVout(params, rawResult.Vout) + require.ErrorContains(t, err, "not match nonce-mark amount") + }) + t.Run("should fail if vout 1 is not to the receiver address", func(t *testing.T) { + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + + // not receiver address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus + rawResult.Vout[1].ScriptPubKey.Hex = "0014ba8be635673034d4d0ddc9447409b594385ec4aa" + err := btcClient.checkTSSVout(params, rawResult.Vout) + require.ErrorContains(t, err, "not match params receiver") + }) + t.Run("should fail if vout 1 not match payment amount", func(t *testing.T) { + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + + // not match payment amount + rawResult.Vout[1].Value = 0.00011000 + err := btcClient.checkTSSVout(params, rawResult.Vout) + require.ErrorContains(t, err, "not match params amount") + }) + t.Run("should fail if vout 2 is not to the TSS address", func(t *testing.T) { + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + + // not TSS address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus + rawResult.Vout[2].ScriptPubKey.Hex = "0014ba8be635673034d4d0ddc9447409b594385ec4aa" + err := btcClient.checkTSSVout(params, rawResult.Vout) + require.ErrorContains(t, err, "not match TSS address") + }) +} + +func TestCheckTSSVoutCancelled(t *testing.T) { + // the archived outtx raw result file and cctx file + // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 + fileCctx := path.Join("../", testutils.TestDataPathCctx, "cctx_8332_148.json") + fileTxResult := path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json") + + // create mainnet mock client + btcClient := MockBTCClientMainnet() + + t.Run("valid TSS vout should pass", func(t *testing.T) { + // remove change vout to simulate cancelled tx + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult.Vout[1] = rawResult.Vout[2] + rawResult.Vout = rawResult.Vout[:2] + params := cctx.GetCurrentOutTxParam() + + err := btcClient.checkTSSVoutCancelled(params, rawResult.Vout) + require.NoError(t, err) + }) + t.Run("should fail if vout length < 1 or > 2", func(t *testing.T) { + _, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + params := cctx.GetCurrentOutTxParam() + + err := btcClient.checkTSSVoutCancelled(params, []btcjson.Vout{}) + require.ErrorContains(t, err, "invalid number of vouts") + + err = btcClient.checkTSSVoutCancelled(params, []btcjson.Vout{{}, {}, {}}) + require.ErrorContains(t, err, "invalid number of vouts") + }) + t.Run("should fail if vout 0 is not to the TSS address", func(t *testing.T) { + // remove change vout to simulate cancelled tx + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult.Vout[1] = rawResult.Vout[2] + rawResult.Vout = rawResult.Vout[:2] + params := cctx.GetCurrentOutTxParam() + + // not TSS address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus + rawResult.Vout[0].ScriptPubKey.Hex = "0014ba8be635673034d4d0ddc9447409b594385ec4aa" + err := btcClient.checkTSSVoutCancelled(params, rawResult.Vout) + require.ErrorContains(t, err, "not match TSS address") + }) + t.Run("should fail if vout 0 not match nonce mark", func(t *testing.T) { + // remove change vout to simulate cancelled tx + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult.Vout[1] = rawResult.Vout[2] + rawResult.Vout = rawResult.Vout[:2] + params := cctx.GetCurrentOutTxParam() + + // not match nonce mark + rawResult.Vout[0].Value = 0.00000147 + err := btcClient.checkTSSVoutCancelled(params, rawResult.Vout) + require.ErrorContains(t, err, "not match nonce-mark amount") + }) + t.Run("should fail if vout 1 is not to the TSS address", func(t *testing.T) { + // remove change vout to simulate cancelled tx + rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult.Vout[1] = rawResult.Vout[2] + rawResult.Vout = rawResult.Vout[:2] + params := cctx.GetCurrentOutTxParam() + + // not TSS address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus + rawResult.Vout[1].ScriptPubKey.Hex = "0014ba8be635673034d4d0ddc9447409b594385ec4aa" + err := btcClient.checkTSSVoutCancelled(params, rawResult.Vout) + require.ErrorContains(t, err, "not match TSS address") + }) +} diff --git a/zetaclient/bitcoin/bitcoin_signer.go b/zetaclient/bitcoin/bitcoin_signer.go index 6f4ade60f1..05106e9a48 100644 --- a/zetaclient/bitcoin/bitcoin_signer.go +++ b/zetaclient/bitcoin/bitcoin_signer.go @@ -8,6 +8,7 @@ import ( "math/rand" "time" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" @@ -34,15 +35,20 @@ const ( ) type BTCSigner struct { - tssSigner interfaces.TSSSigner - rpcClient interfaces.BTCRPCClient - logger zerolog.Logger - ts *metrics.TelemetryServer + tssSigner interfaces.TSSSigner + rpcClient interfaces.BTCRPCClient + logger zerolog.Logger + loggerCompliance zerolog.Logger + ts *metrics.TelemetryServer } var _ interfaces.ChainSigner = &BTCSigner{} -func NewBTCSigner(cfg config.BTCConfig, tssSigner interfaces.TSSSigner, logger zerolog.Logger, ts *metrics.TelemetryServer) (*BTCSigner, error) { +func NewBTCSigner( + cfg config.BTCConfig, + tssSigner interfaces.TSSSigner, + loggers clientcommon.ClientLogger, + ts *metrics.TelemetryServer) (*BTCSigner, error) { connCfg := &rpcclient.ConnConfig{ Host: cfg.RPCHost, User: cfg.RPCUsername, @@ -57,12 +63,11 @@ func NewBTCSigner(cfg config.BTCConfig, tssSigner interfaces.TSSSigner, logger z } return &BTCSigner{ - tssSigner: tssSigner, - rpcClient: client, - logger: logger.With(). - Str("chain", "BTC"). - Str("module", "BTCSigner").Logger(), - ts: ts, + tssSigner: tssSigner, + rpcClient: client, + logger: loggers.Std.With().Str("chain", "BTC").Str("module", "BTCSigner").Logger(), + loggerCompliance: loggers.Compliance, + ts: ts, }, nil } @@ -76,6 +81,7 @@ func (signer *BTCSigner) SignWithdrawTx( height uint64, nonce uint64, chain *common.Chain, + cancelTx bool, ) (*wire.MsgTx, error) { estimateFee := float64(gasPrice.Uint64()*outTxBytesMax) / 1e8 nonceMark := common.NonceMarkAmount(nonce) @@ -155,12 +161,14 @@ func (signer *BTCSigner) SignWithdrawTx( tx.AddTxOut(txOut1) // 2nd output: the payment to the recipient - pkScript, err := PayToWitnessPubKeyHashScript(to.WitnessProgram()) - if err != nil { - return nil, err + if !cancelTx { + pkScript, err := PayToWitnessPubKeyHashScript(to.WitnessProgram()) + if err != nil { + return nil, err + } + txOut2 := wire.NewTxOut(amountSatoshis, pkScript) + tx.AddTxOut(txOut2) } - txOut2 := wire.NewTxOut(amountSatoshis, pkScript) - tx.AddTxOut(txOut2) // 3rd output: the remaining btc to TSS self if remainingSats > 0 { @@ -305,6 +313,7 @@ func (signer *BTCSigner) TryProcessOutTx( logger.Error().Err(err).Msgf("cannot convert address %s to P2WPKH address", params.Receiver) return } + amount := float64(params.Amount.Uint64()) / 1e8 // Add 1 satoshi/byte to gasPrice to avoid minRelayTxFee issue networkInfo, err := signer.rpcClient.GetNetworkInfo() @@ -315,18 +324,27 @@ func (signer *BTCSigner) TryProcessOutTx( satPerByte := FeeRateToSatPerByte(networkInfo.RelayFee) gasprice.Add(gasprice, satPerByte) + // compliance check + cancelTx := clientcommon.IsCctxRestricted(cctx) + if cancelTx { + clientcommon.PrintComplianceLog(logger, signer.loggerCompliance, + true, btcClient.chain.ChainId, cctx.Index, cctx.InboundTxParams.Sender, params.Receiver, "BTC") + amount = 0.0 // zero out the amount to cancel the tx + } + logger.Info().Msgf("SignWithdrawTx: to %s, value %d sats", addr.EncodeAddress(), params.Amount.Uint64()) logger.Info().Msgf("using utxos: %v", btcClient.utxos) tx, err := signer.SignWithdrawTx( to, - float64(params.Amount.Uint64())/1e8, + amount, gasprice, sizelimit, btcClient, height, outboundTxTssNonce, &btcClient.chain, + cancelTx, ) if err != nil { logger.Warn().Err(err).Msgf("SignOutboundTx error: nonce %d chain %d", outboundTxTssNonce, params.ReceiverChainId) diff --git a/zetaclient/bitcoin/bitcoin_signer_test.go b/zetaclient/bitcoin/bitcoin_signer_test.go index bbf15c1bba..d5b3e0ca97 100644 --- a/zetaclient/bitcoin/bitcoin_signer_test.go +++ b/zetaclient/bitcoin/bitcoin_signer_test.go @@ -9,6 +9,7 @@ import ( "sync" "testing" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" @@ -21,7 +22,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/rs/zerolog" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/zetaclient/config" @@ -75,7 +75,7 @@ func (s *BTCSignerSuite) SetUpTest(c *C) { tss := interfaces.TestSigner{ PrivKey: privateKey, } - s.btcSigner, err = NewBTCSigner(config.BTCConfig{}, &tss, zerolog.Logger{}, &metrics.TelemetryServer{}) + s.btcSigner, err = NewBTCSigner(config.BTCConfig{}, &tss, clientcommon.DefaultLoggers(), &metrics.TelemetryServer{}) c.Assert(err, IsNil) } diff --git a/zetaclient/bitcoin/inbound_tracker.go b/zetaclient/bitcoin/inbound_tracker.go index af5fa42311..3aa83dc031 100644 --- a/zetaclient/bitcoin/inbound_tracker.go +++ b/zetaclient/bitcoin/inbound_tracker.go @@ -83,6 +83,9 @@ func (ob *BTCChainClient) CheckReceiptForBtcTxHash(txHash string, vote bool) (st return "", errors.New("no btc deposit event found") } msg := ob.GetInboundVoteMessageFromBtcEvent(event) + if msg == nil { + return "", errors.New("no message built for btc sent to TSS") + } if !vote { return msg.Digest(), nil } diff --git a/zetaclient/bitcoin/utils.go b/zetaclient/bitcoin/utils.go index 2b90e21280..b11c1a7056 100644 --- a/zetaclient/bitcoin/utils.go +++ b/zetaclient/bitcoin/utils.go @@ -20,7 +20,6 @@ import ( ) const ( - satoshiPerBitcoin = 1e8 bytesPerKB = 1000 bytesEmptyTx = 10 // an empty tx is about 10 bytes bytesPerInput = 41 // each input is about 41 bytes @@ -60,7 +59,7 @@ func PrettyPrintStruct(val interface{}) (string, error) { // FeeRateToSatPerByte converts a fee rate in BTC/KB to sat/byte. func FeeRateToSatPerByte(rate float64) *big.Int { // #nosec G701 always in range - satPerKB := new(big.Int).SetInt64(int64(rate * satoshiPerBitcoin)) + satPerKB := new(big.Int).SetInt64(int64(rate * btcutil.SatoshiPerBitcoin)) return new(big.Int).Div(satPerKB, big.NewInt(bytesPerKB)) } @@ -102,7 +101,7 @@ func SegWitTxSizeWithdrawer() uint64 { // DepositorFee calculates the depositor fee in BTC for a given sat/byte fee rate // Note: the depositor fee is charged in order to cover the cost of spending the deposited UTXO in the future func DepositorFee(satPerByte int64) float64 { - return float64(satPerByte) * float64(BtcOutTxBytesDepositor) / satoshiPerBitcoin + return float64(satPerByte) * float64(BtcOutTxBytesDepositor) / btcutil.SatoshiPerBitcoin } // CalcBlockAvgFeeRate calculates the average gas rate (in sat/vByte) for a given block @@ -209,7 +208,7 @@ func GetSatoshis(btc float64) (int64, error) { case btc < 0.0: return 0, errors.New("cannot be less than zero") } - return round(btc * satoshiPerBitcoin), nil + return round(btc * btcutil.SatoshiPerBitcoin), nil } func round(f float64) int64 { @@ -224,3 +223,30 @@ func round(f float64) int64 { func PayToWitnessPubKeyHashScript(pubKeyHash []byte) ([]byte, error) { return txscript.NewScriptBuilder().AddOp(txscript.OP_0).AddData(pubKeyHash).Script() } + +// DecodeP2WPKHVout decodes receiver and amount from P2WPKH output +func DecodeP2WPKHVout(vout btcjson.Vout, chain common.Chain) (string, int64, error) { + amount, err := GetSatoshis(vout.Value) + if err != nil { + return "", 0, errors.Wrap(err, "error getting satoshis") + } + // decode P2WPKH scriptPubKey + scriptPubKey := vout.ScriptPubKey.Hex + decodedScriptPubKey, err := hex.DecodeString(scriptPubKey) + if err != nil { + return "", 0, errors.Wrapf(err, "error decoding scriptPubKey %s", scriptPubKey) + } + if len(decodedScriptPubKey) != 22 { // P2WPKH script + return "", 0, fmt.Errorf("unsupported scriptPubKey: %s", scriptPubKey) + } + witnessVersion := decodedScriptPubKey[0] + witnessProgram := decodedScriptPubKey[2:] + if witnessVersion != 0 { + return "", 0, fmt.Errorf("unsupported witness in scriptPubKey %s", scriptPubKey) + } + recvAddress, err := chain.BTCAddressFromWitnessProgram(witnessProgram) + if err != nil { + return "", 0, errors.Wrapf(err, "error getting receiver from witness program %s", witnessProgram) + } + return recvAddress, amount, nil +} diff --git a/zetaclient/bitcoin/utils_test.go b/zetaclient/bitcoin/utils_test.go index ab6692e111..56c7e0d967 100644 --- a/zetaclient/bitcoin/utils_test.go +++ b/zetaclient/bitcoin/utils_test.go @@ -1,104 +1,83 @@ package bitcoin import ( - "fmt" + "path" "testing" - "github.com/zeta-chain/zetacore/zetaclient/evm" - - ethcommon "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/btcsuite/btcd/btcjson" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/zetaclient/testutils" ) -func TestCheckEvmTxLog(t *testing.T) { - // test data - connectorAddr := ethcommon.HexToAddress("0x00005e3125aba53c5652f9f0ce1a4cf91d8b15ea") - txHash := "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626" - topics := []ethcommon.Hash{ - // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog - ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), - ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), - ethcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000061"), - } +func TestDecodeP2WPKHVout(t *testing.T) { + // load archived outtx raw result + // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 + var rawResult btcjson.TxRawResult + err := testutils.LoadObjectFromJSONFile(&rawResult, path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json")) + require.NoError(t, err) + require.Len(t, rawResult.Vout, 3) + + // it's a mainnet outtx + chain := common.BtcMainnetChain() + nonce := uint64(148) + + // decode vout 0, nonce mark 148 + receiver, amount, err := DecodeP2WPKHVout(rawResult.Vout[0], chain) + require.NoError(t, err) + require.Equal(t, testutils.TSSAddressBTCMainnet, receiver) + require.Equal(t, common.NonceMarkAmount(nonce), amount) + + // decode vout 1, payment 0.00012000 BTC + receiver, amount, err = DecodeP2WPKHVout(rawResult.Vout[1], chain) + require.NoError(t, err) + require.Equal(t, "bc1qpsdlklfcmlcfgm77c43x65ddtrt7n0z57hsyjp", receiver) + require.Equal(t, int64(12000), amount) + + // decode vout 2, change 0.39041489 BTC + receiver, amount, err = DecodeP2WPKHVout(rawResult.Vout[2], chain) + require.NoError(t, err) + require.Equal(t, testutils.TSSAddressBTCMainnet, receiver) + require.Equal(t, int64(39041489), amount) +} + +func TestDecodeP2WPKHVoutErrors(t *testing.T) { + // load archived outtx raw result + // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 + var rawResult btcjson.TxRawResult + err := testutils.LoadObjectFromJSONFile(&rawResult, path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json")) + require.NoError(t, err) - tests := []struct { - name string - vLog *ethtypes.Log - fail bool - }{ - { - name: "chain reorganization", - vLog: ðtypes.Log{ - Removed: true, - Address: connectorAddr, - TxHash: ethcommon.HexToHash(txHash), - Topics: topics, - }, - fail: true, - }, - { - name: "emitter address mismatch", - vLog: ðtypes.Log{ - Removed: false, - Address: ethcommon.HexToAddress("0x184ba627DB853244c9f17f3Cb4378cB8B39bf147"), - TxHash: ethcommon.HexToHash(txHash), - Topics: topics, - }, - fail: true, - }, - { - name: "tx hash mismatch", - vLog: ðtypes.Log{ - Removed: false, - Address: connectorAddr, - TxHash: ethcommon.HexToHash("0x781c018d604af4dad0fe5e3cea4ad9fb949a996d8cd0cd04a92cadd7f08c05f2"), - Topics: topics, - }, - fail: true, - }, - { - name: "topics mismatch", - vLog: ðtypes.Log{ - Removed: false, - Address: connectorAddr, - TxHash: ethcommon.HexToHash(txHash), - Topics: []ethcommon.Hash{ - // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog - ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), - ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), - }, - }, - fail: true, - }, - { - name: "should pass", - vLog: ðtypes.Log{ - Removed: false, - Address: connectorAddr, - TxHash: ethcommon.HexToHash(txHash), - Topics: topics, - }, - fail: false, - }, - } + chain := common.BtcMainnetChain() - evmClient := evm.ChainClient{} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fmt.Printf("check test: %s\n", tt.name) - err := evmClient.CheckEvmTxLog( - tt.vLog, - connectorAddr, - "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626", - evm.TopicsZetaSent, - ) - if tt.fail { - require.Error(t, err) - return - } else { - require.NoError(t, err) - } - }) - } + t.Run("should return error on invalid amount", func(t *testing.T) { + invalidVout := rawResult.Vout[0] + invalidVout.Value = -0.5 // negative amount, should not happen + _, _, err := DecodeP2WPKHVout(invalidVout, chain) + require.Error(t, err) + require.ErrorContains(t, err, "error getting satoshis") + }) + t.Run("should return error on invalid script", func(t *testing.T) { + invalidVout := rawResult.Vout[0] + invalidVout.ScriptPubKey.Hex = "invalid script" + _, _, err := DecodeP2WPKHVout(invalidVout, chain) + require.Error(t, err) + require.ErrorContains(t, err, "error decoding scriptPubKey") + }) + t.Run("should return error on unsupported script", func(t *testing.T) { + invalidVout := rawResult.Vout[0] + // can use any invalid script, https://blockstream.info/tx/e95c6ff206103716129c8e3aa8def1427782af3490589d1ea35ccf0122adbc25 (P2SH) + invalidVout.ScriptPubKey.Hex = "a91413b2388e6532653a4b369b7e4ed130f7b81626cc87" + _, _, err := DecodeP2WPKHVout(invalidVout, chain) + require.Error(t, err) + require.ErrorContains(t, err, "unsupported scriptPubKey") + }) + t.Run("should return error on unsupported witness version", func(t *testing.T) { + invalidVout := rawResult.Vout[0] + // use a fake witness version 1, even if version 0 is the only witness version defined in BIP141 + invalidVout.ScriptPubKey.Hex = "01140c1bfb7d38dff0946fdec5626d51ad58d7e9bc54" + _, _, err := DecodeP2WPKHVout(invalidVout, chain) + require.Error(t, err) + require.ErrorContains(t, err, "unsupported witness in scriptPubKey") + }) } diff --git a/zetaclient/common/utils.go b/zetaclient/common/utils.go new file mode 100644 index 0000000000..af5d2d0771 --- /dev/null +++ b/zetaclient/common/utils.go @@ -0,0 +1,52 @@ +package common + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/config" +) + +type ClientLogger struct { + Std zerolog.Logger + Compliance zerolog.Logger +} + +func DefaultLoggers() ClientLogger { + return ClientLogger{ + Std: log.Logger, + Compliance: log.Logger, + } +} + +// IsCctxRestricted returns true if the cctx involves restricted addresses +func IsCctxRestricted(cctx *crosschaintypes.CrossChainTx) bool { + sender := cctx.InboundTxParams.Sender + receiver := cctx.GetCurrentOutTxParam().Receiver + return config.ContainRestrictedAddress(sender, receiver) +} + +// PrintComplianceLog prints compliance log with fields [chain, cctx/intx, chain, sender, receiver, token] +func PrintComplianceLog( + logger1 zerolog.Logger, + logger2 zerolog.Logger, + outbound bool, + chainID int64, + identifier, sender, receiver, token string) { + var logMsg string + var logWithFields1 zerolog.Logger + var logWithFields2 zerolog.Logger + if outbound { + // we print cctx for outbound tx + logMsg = "Restricted address detected in cctx" + logWithFields1 = logger1.With().Int64("chain", chainID).Str("cctx", identifier).Str("sender", sender).Str("receiver", receiver).Str("token", token).Logger() + logWithFields2 = logger2.With().Int64("chain", chainID).Str("cctx", identifier).Str("sender", sender).Str("receiver", receiver).Str("token", token).Logger() + } else { + // we print intx for inbound tx + logMsg = "Restricted address detected in intx" + logWithFields1 = logger1.With().Int64("chain", chainID).Str("intx", identifier).Str("sender", sender).Str("receiver", receiver).Str("token", token).Logger() + logWithFields2 = logger2.With().Int64("chain", chainID).Str("intx", identifier).Str("sender", sender).Str("receiver", receiver).Str("token", token).Logger() + } + logWithFields1.Warn().Msg(logMsg) + logWithFields2.Warn().Msg(logMsg) +} diff --git a/zetaclient/common/utils_test.go b/zetaclient/common/utils_test.go new file mode 100644 index 0000000000..41b3f0c4cf --- /dev/null +++ b/zetaclient/common/utils_test.go @@ -0,0 +1,53 @@ +package common + +import ( + "path" + "testing" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/config" + "github.com/zeta-chain/zetacore/zetaclient/testutils" +) + +func TestCctxRestricted(t *testing.T) { + // load archived cctx + var cctx crosschaintypes.CrossChainTx + err := testutils.LoadObjectFromJSONFile(&cctx, path.Join("../", testutils.TestDataPathCctx, "cctx_1_6270.json")) + require.NoError(t, err) + + // create config + cfg := &config.Config{ + ComplianceConfig: &config.ComplianceConfig{}, + } + + t.Run("should return true if sender is restricted", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{cctx.InboundTxParams.Sender} + config.LoadComplianceConfig(cfg) + require.True(t, IsCctxRestricted(&cctx)) + }) + t.Run("should return true if receiver is restricted", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{cctx.GetCurrentOutTxParam().Receiver} + config.LoadComplianceConfig(cfg) + require.True(t, IsCctxRestricted(&cctx)) + }) + t.Run("should return false if sender and receiver are not restricted", func(t *testing.T) { + // restrict other address + cfg.ComplianceConfig.RestrictedAddresses = []string{"0x27104b8dB4aEdDb054fCed87c346C0758Ff5dFB1"} + config.LoadComplianceConfig(cfg) + require.False(t, IsCctxRestricted(&cctx)) + }) + t.Run("should be able to restrict coinbase address", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{ethcommon.Address{}.String()} + config.LoadComplianceConfig(cfg) + cctx.InboundTxParams.Sender = ethcommon.Address{}.String() + require.True(t, IsCctxRestricted(&cctx)) + }) + t.Run("should ignore empty address", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{""} + config.LoadComplianceConfig(cfg) + cctx.InboundTxParams.Sender = "" + require.False(t, IsCctxRestricted(&cctx)) + }) +} diff --git a/zetaclient/config/config.go b/zetaclient/config/config.go index 6b1fc1ec30..78c2d6ae9a 100644 --- a/zetaclient/config/config.go +++ b/zetaclient/config/config.go @@ -8,6 +8,9 @@ import ( "strings" ) +// restrictedAddressBook is a map of restricted addresses +var restrictedAddressBook = map[string]bool{} + const filename string = "zetaclient_config.json" const folder string = "config" @@ -67,9 +70,16 @@ func Load(path string) (*Config, error) { cfg.CurrentTssPubkey = "" cfg.ZetaCoreHome = path + // load compliance config + LoadComplianceConfig(cfg) + return cfg, nil } +func LoadComplianceConfig(cfg *Config) { + restrictedAddressBook = cfg.GetRestrictedAddressBook() +} + func GetPath(inputPath string) string { path := strings.Split(inputPath, "/") if len(path) > 0 { @@ -83,3 +93,14 @@ func GetPath(inputPath string) string { } return filepath.Join(path...) } + +// ContainRestrictedAddress returns true if any one of the addresses is restricted +// Note: the addrs can contains both ETH and BTC addresses +func ContainRestrictedAddress(addrs ...string) bool { + for _, addr := range addrs { + if addr != "" && restrictedAddressBook[strings.ToLower(addr)] { + return true + } + } + return false +} diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 17467affd1..e2a9ccc1c2 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -47,6 +47,11 @@ type BTCConfig struct { RPCParams string // "regtest", "mainnet", "testnet3" } +type ComplianceConfig struct { + LogPath string `json:"LogPath"` + RestrictedAddresses []string `json:"RestrictedAddresses"` +} + // Config is the config for ZetaClient // TODO: use snake case for json fields // https://github.com/zeta-chain/node/issues/1020 @@ -78,6 +83,9 @@ type Config struct { ChainsEnabled []common.Chain `json:"ChainsEnabled"` EVMChainConfigs map[int64]*EVMConfig `json:"EVMChainConfigs"` BitcoinConfig *BTCConfig `json:"BitcoinConfig"` + + // compliance config + ComplianceConfig *ComplianceConfig `json:"ComplianceConfig"` } func NewConfig() *Config { @@ -151,6 +159,20 @@ func (c *Config) GetBTCConfig() (common.Chain, BTCConfig, bool) { return *chain, *c.BitcoinConfig, true } +// GetRestrictedAddressBook returns a map of restricted addresses +// Note: the restricted address book contains both ETH and BTC addresses +func (c *Config) GetRestrictedAddressBook() map[string]bool { + restrictedAddresses := make(map[string]bool) + if c.ComplianceConfig != nil { + for _, address := range c.ComplianceConfig.RestrictedAddresses { + if address != "" { + restrictedAddresses[strings.ToLower(address)] = true + } + } + } + return restrictedAddresses +} + func (c *Config) GetKeyringBackend() KeyringBackend { c.cfgLock.RLock() defer c.cfgLock.RUnlock() diff --git a/zetaclient/evm/evm_client.go b/zetaclient/evm/evm_client.go index 5d4defb0d3..88cee1784d 100644 --- a/zetaclient/evm/evm_client.go +++ b/zetaclient/evm/evm_client.go @@ -37,6 +37,7 @@ import ( "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/config" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "gorm.io/driver/sqlite" @@ -58,7 +59,7 @@ type Log struct { ExternalChainWatcher zerolog.Logger // Observes external Chains for incoming trasnactions WatchGasPrice zerolog.Logger // Observes external Chains for Gas prices and posts to core ObserveOutTx zerolog.Logger // Observes external Chains for Outgoing transactions - + Compliance zerolog.Logger // Compliance logger } const ( @@ -91,7 +92,6 @@ type ChainClient struct { MaxNonce int64 OutTxChan chan OutTx // send to this channel if you want something back! stop chan struct{} - fileLogger *zerolog.Logger // for critical info logger Log cfg *config.Config params observertypes.ChainParams @@ -106,7 +106,7 @@ func NewEVMChainClient( bridge interfaces.ZetaCoreBridger, tss interfaces.TSSSigner, dbpath string, - logger zerolog.Logger, + loggers clientcommon.ClientLogger, cfg *config.Config, evmCfg config.EVMConfig, ts *metrics.TelemetryServer, @@ -114,12 +114,13 @@ func NewEVMChainClient( ob := ChainClient{ ts: ts, } - chainLogger := logger.With().Str("chain", evmCfg.Chain.ChainName.String()).Logger() + chainLogger := loggers.Std.With().Str("chain", evmCfg.Chain.ChainName.String()).Logger() ob.logger = Log{ ChainLogger: chainLogger, ExternalChainWatcher: chainLogger.With().Str("module", "ExternalChainWatcher").Logger(), WatchGasPrice: chainLogger.With().Str("module", "WatchGasPrice").Logger(), ObserveOutTx: chainLogger.With().Str("module", "ObserveOutTx").Logger(), + Compliance: loggers.Compliance, } ob.cfg = cfg ob.params = evmCfg.ChainParams @@ -134,13 +135,6 @@ func NewEVMChainClient( ob.outTXConfirmedTransactions = make(map[string]*ethtypes.Transaction) ob.OutTxChan = make(chan OutTx, 100) - logFile, err := os.OpenFile(ob.chain.ChainName.String()+"_debug.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - log.Error().Err(err).Msgf("there was an error creating a logFile chain %s", ob.chain.ChainName.String()) - } - fileLogger := zerolog.New(logFile).With().Logger() - ob.fileLogger = &fileLogger - ob.logger.ChainLogger.Info().Msgf("Chain %s endpoint %s", ob.chain.ChainName.String(), evmCfg.Endpoint) client, err := ethclient.Dial(evmCfg.Endpoint) if err != nil { @@ -330,7 +324,12 @@ func (ob *ChainClient) Stop() { // returns: isIncluded, isConfirmed, Error // If isConfirmed, it also post to ZetaCore -func (ob *ChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, cointype common.CoinType, logger zerolog.Logger) (bool, bool, error) { +func (ob *ChainClient) IsSendOutTxProcessed(cctx *types.CrossChainTx, logger zerolog.Logger) (bool, bool, error) { + sendHash := cctx.Index + cointype := cctx.GetCurrentOutTxParam().CoinType + nonce := cctx.GetCurrentOutTxParam().OutboundTxTssNonce + + // skip if outtx is not confirmed params := ob.GetChainParams() receipt, transaction := ob.GetTxNReceipt(nonce) if receipt == nil || transaction == nil { // not confirmed yet @@ -339,6 +338,35 @@ func (ob *ChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64, coint sendID := fmt.Sprintf("%s-%d", ob.chain.String(), nonce) logger = logger.With().Str("sendID", sendID).Logger() + + // compliance check, special handling the cancelled cctx + if clientcommon.IsCctxRestricted(cctx) { + recvStatus := common.ReceiveStatus_Failed + if receipt.Status == 1 { + recvStatus = common.ReceiveStatus_Success + } + zetaTxHash, ballot, err := ob.zetaClient.PostVoteOutbound( + sendHash, + receipt.TxHash.Hex(), + receipt.BlockNumber.Uint64(), + receipt.GasUsed, + transaction.GasPrice(), + transaction.Gas(), + // use cctx's amount to bypass the amount check in zetacore + cctx.GetCurrentOutTxParam().Amount.BigInt(), + recvStatus, + ob.chain, + nonce, + common.CoinType_Cmd, + ) + if err != nil { + logger.Error().Err(err).Msgf("error posting confirmation to meta core for cctx %s nonce %d", sendHash, nonce) + } else if zetaTxHash != "" { + logger.Info().Msgf("Zeta tx hash: %s cctx %s nonce %d ballot %s", zetaTxHash, sendHash, nonce, ballot) + } + return true, true, nil + } + if cointype == common.CoinType_Cmd { recvStatus := common.ReceiveStatus_Failed if receipt.Status == 1 { diff --git a/zetaclient/evm/evm_client_test.go b/zetaclient/evm/evm_client_test.go index 730cba83f9..fdcee8c837 100644 --- a/zetaclient/evm/evm_client_test.go +++ b/zetaclient/evm/evm_client_test.go @@ -1,9 +1,11 @@ package evm import ( + "fmt" "math/big" "testing" + ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" lru "github.com/hashicorp/golang-lru" "github.com/stretchr/testify/require" @@ -35,3 +37,95 @@ func TestEVMBlockCache(t *testing.T) { // delete the block should not panic ob.RemoveCachedBlock(uint64(blockNumber)) } + +func TestCheckEvmTxLog(t *testing.T) { + // test data + connectorAddr := ethcommon.HexToAddress("0x00005e3125aba53c5652f9f0ce1a4cf91d8b15ea") + txHash := "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626" + topics := []ethcommon.Hash{ + // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog + ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), + ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), + ethcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000061"), + } + + tests := []struct { + name string + vLog *ethtypes.Log + fail bool + }{ + { + name: "chain reorganization", + vLog: ðtypes.Log{ + Removed: true, + Address: connectorAddr, + TxHash: ethcommon.HexToHash(txHash), + Topics: topics, + }, + fail: true, + }, + { + name: "emitter address mismatch", + vLog: ðtypes.Log{ + Removed: false, + Address: ethcommon.HexToAddress("0x184ba627DB853244c9f17f3Cb4378cB8B39bf147"), + TxHash: ethcommon.HexToHash(txHash), + Topics: topics, + }, + fail: true, + }, + { + name: "tx hash mismatch", + vLog: ðtypes.Log{ + Removed: false, + Address: connectorAddr, + TxHash: ethcommon.HexToHash("0x781c018d604af4dad0fe5e3cea4ad9fb949a996d8cd0cd04a92cadd7f08c05f2"), + Topics: topics, + }, + fail: true, + }, + { + name: "topics mismatch", + vLog: ðtypes.Log{ + Removed: false, + Address: connectorAddr, + TxHash: ethcommon.HexToHash(txHash), + Topics: []ethcommon.Hash{ + // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog + ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), + ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), + }, + }, + fail: true, + }, + { + name: "should pass", + vLog: ðtypes.Log{ + Removed: false, + Address: connectorAddr, + TxHash: ethcommon.HexToHash(txHash), + Topics: topics, + }, + fail: false, + }, + } + + evmClient := ChainClient{} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Printf("check test: %s\n", tt.name) + err := evmClient.CheckEvmTxLog( + tt.vLog, + connectorAddr, + "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626", + TopicsZetaSent, + ) + if tt.fail { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/zetaclient/evm/evm_signer.go b/zetaclient/evm/evm_signer.go index 37988ab3ab..ae632df241 100644 --- a/zetaclient/evm/evm_signer.go +++ b/zetaclient/evm/evm_signer.go @@ -12,6 +12,7 @@ import ( "sync" "time" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" @@ -47,7 +48,7 @@ type Signer struct { erc20CustodyABI abi.ABI metaContractAddress ethcommon.Address erc20CustodyContractAddress ethcommon.Address - logger zerolog.Logger + logger clientcommon.ClientLogger ts *metrics.TelemetryServer // for outTx tracker report only @@ -65,7 +66,7 @@ func NewEVMSigner( erc20CustodyABIString string, metaContract ethcommon.Address, erc20CustodyContract ethcommon.Address, - logger zerolog.Logger, + loggers clientcommon.ClientLogger, ts *metrics.TelemetryServer, ) (*Signer, error) { client, err := ethclient.Dial(endpoint) @@ -97,9 +98,10 @@ func NewEVMSigner( erc20CustodyABI: erc20CustodyABI, metaContractAddress: metaContract, erc20CustodyContractAddress: erc20CustodyContract, - logger: logger.With(). - Str("chain", chain.ChainName.String()). - Str("module", "EVMSigner").Logger(), + logger: clientcommon.ClientLogger{ + Std: loggers.Std.With().Str("chain", chain.ChainName.String()).Str("module", "EVMSigner").Logger(), + Compliance: loggers.Compliance, + }, ts: ts, mu: &sync.Mutex{}, outTxHashBeingReported: make(map[string]bool), @@ -127,10 +129,10 @@ func (signer *Signer) Sign( log.Debug().Msgf("Sign: Signature: %s", hex.EncodeToString(sig[:])) pubk, err := crypto.SigToPub(hashBytes, sig[:]) if err != nil { - signer.logger.Error().Err(err).Msgf("SigToPub error") + signer.logger.Std.Error().Err(err).Msgf("SigToPub error") } addr := crypto.PubkeyToAddress(*pubk) - signer.logger.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) + signer.logger.Std.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) signedTX, err := tx.WithSignature(signer.ethSigner, sig[:]) if err != nil { return nil, nil, nil, err @@ -234,10 +236,10 @@ func (signer *Signer) SignCancelTx(nonce uint64, gasPrice *big.Int, height uint6 } pubk, err := crypto.SigToPub(hashBytes, sig[:]) if err != nil { - signer.logger.Error().Err(err).Msgf("SigToPub error") + signer.logger.Std.Error().Err(err).Msgf("SigToPub error") } addr := crypto.PubkeyToAddress(*pubk) - signer.logger.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) + signer.logger.Std.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) signedTX, err := tx.WithSignature(signer.ethSigner, sig[:]) if err != nil { return nil, err @@ -261,10 +263,10 @@ func (signer *Signer) SignWithdrawTx( } pubk, err := crypto.SigToPub(hashBytes, sig[:]) if err != nil { - signer.logger.Error().Err(err).Msgf("SigToPub error") + signer.logger.Std.Error().Err(err).Msgf("SigToPub error") } addr := crypto.PubkeyToAddress(*pubk) - signer.logger.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) + signer.logger.Std.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) signedTX, err := tx.WithSignature(signer.ethSigner, sig[:]) if err != nil { return nil, err @@ -310,10 +312,10 @@ func (signer *Signer) SignCommandTx( } pubk, err := crypto.SigToPub(hashBytes, sig[:]) if err != nil { - signer.logger.Error().Err(err).Msgf("SigToPub error") + signer.logger.Std.Error().Err(err).Msgf("SigToPub error") } addr := crypto.PubkeyToAddress(*pubk) - signer.logger.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) + signer.logger.Std.Info().Msgf("Sign: Ecrecovery of signature: %s", addr.Hex()) signedTX, err := tx.WithSignature(signer.ethSigner, sig[:]) if err != nil { return nil, err @@ -333,7 +335,7 @@ func (signer *Signer) TryProcessOutTx( zetaBridge interfaces.ZetaCoreBridger, height uint64, ) { - logger := signer.logger.With(). + logger := signer.logger.Std.With(). Str("outTxID", outTxID). Str("SendHash", cctx.Index). Logger() @@ -374,7 +376,7 @@ func (signer *Signer) TryProcessOutTx( // Early return if the cctx is already processed nonce := cctx.GetCurrentOutTxParam().OutboundTxTssNonce - included, confirmed, err := evmClient.IsSendOutTxProcessed(cctx.Index, nonce, cctx.GetCurrentOutTxParam().CoinType, logger) + included, confirmed, err := evmClient.IsSendOutTxProcessed(cctx, logger) if err != nil { logger.Error().Err(err).Msg("IsSendOutTxProcessed failed") } @@ -452,7 +454,12 @@ func (signer *Signer) TryProcessOutTx( var tx *ethtypes.Transaction - if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_Cmd { // admin command + // compliance check goes first + if clientcommon.IsCctxRestricted(cctx) { + clientcommon.PrintComplianceLog(logger, signer.logger.Compliance, + true, evmClient.chain.ChainId, cctx.Index, cctx.InboundTxParams.Sender, to.Hex(), cctx.GetCurrentOutTxParam().CoinType.String()) + tx, err = signer.SignCancelTx(nonce, gasprice, height) // cancel the tx + } else if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_Cmd { // admin command to := ethcommon.HexToAddress(cctx.GetCurrentOutTxParam().Receiver) if to == (ethcommon.Address{}) { logger.Error().Msgf("invalid receiver %s", cctx.GetCurrentOutTxParam().Receiver) diff --git a/zetaclient/evm/inbounds.go b/zetaclient/evm/inbounds.go index 24f4006c2e..9eca478955 100644 --- a/zetaclient/evm/inbounds.go +++ b/zetaclient/evm/inbounds.go @@ -13,6 +13,8 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/config" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" ethcommon "github.com/ethereum/go-ethereum/common" @@ -296,6 +298,19 @@ func (ob *ChainClient) GetTransactionSender(tx *ethtypes.Transaction, blockHash } func (ob *ChainClient) GetInboundVoteMsgForDepositedEvent(event *erc20custody.ERC20CustodyDeposited, sender ethcommon.Address) *types.MsgVoteOnObservedInboundTx { + // compliance check + maybeReceiver := "" + parsedAddress, _, err := common.ParseAddressAndData(hex.EncodeToString(event.Message)) + if err == nil && parsedAddress != (ethcommon.Address{}) { + maybeReceiver = parsedAddress.Hex() + } + if config.ContainRestrictedAddress(sender.Hex(), clienttypes.BytesToEthHex(event.Recipient), maybeReceiver) { + clientcommon.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, + false, ob.chain.ChainId, event.Raw.TxHash.Hex(), sender.Hex(), clienttypes.BytesToEthHex(event.Recipient), "ERC20") + return nil + } + + // donation check if bytes.Equal(event.Message, []byte(DonationMessage)) { ob.logger.ExternalChainWatcher.Info().Msgf("thank you rich folk for your donation! tx %s chain %d", event.Raw.TxHash.Hex(), ob.chain.ChainId) return nil @@ -329,6 +344,15 @@ func (ob *ChainClient) GetInboundVoteMsgForZetaSentEvent(event *zetaconnector.Ze return nil } destAddr := clienttypes.BytesToEthHex(event.DestinationAddress) + + // compliance check + sender := event.ZetaTxSenderAddress.Hex() + if config.ContainRestrictedAddress(sender, destAddr, event.SourceTxOriginAddress.Hex()) { + clientcommon.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, + false, ob.chain.ChainId, event.Raw.TxHash.Hex(), sender, destAddr, "Zeta") + return nil + } + if !destChain.IsZetaChain() { cfgDest, found := ob.cfg.GetEVMConfig(destChain.ChainId) if !found { @@ -342,13 +366,13 @@ func (ob *ChainClient) GetInboundVoteMsgForZetaSentEvent(event *zetaconnector.Ze } message := base64.StdEncoding.EncodeToString(event.Message) ob.logger.ExternalChainWatcher.Info().Msgf("ZetaSent inTx detected on chain %d tx %s block %d from %s value %s message %s", - ob.chain.ChainId, event.Raw.TxHash.Hex(), event.Raw.BlockNumber, event.ZetaTxSenderAddress.Hex(), event.ZetaValueAndGas.String(), message) + ob.chain.ChainId, event.Raw.TxHash.Hex(), event.Raw.BlockNumber, sender, event.ZetaValueAndGas.String(), message) return zetabridge.GetInBoundVoteMessage( - event.ZetaTxSenderAddress.Hex(), + sender, ob.chain.ChainId, event.SourceTxOriginAddress.Hex(), - clienttypes.BytesToEthHex(event.DestinationAddress), + destAddr, destChain.ChainId, sdkmath.NewUintFromBigInt(event.ZetaValueAndGas), message, @@ -363,16 +387,27 @@ func (ob *ChainClient) GetInboundVoteMsgForZetaSentEvent(event *zetaconnector.Ze } func (ob *ChainClient) GetInboundVoteMsgForTokenSentToTSS(tx *ethtypes.Transaction, sender ethcommon.Address, blockNumber uint64) *types.MsgVoteOnObservedInboundTx { + message := hex.EncodeToString(tx.Data()) + + // compliance check + maybeReceiver := "" + parsedAddress, _, err := common.ParseAddressAndData(message) + if err == nil && parsedAddress != (ethcommon.Address{}) { + maybeReceiver = parsedAddress.Hex() + } + if config.ContainRestrictedAddress(sender.Hex(), maybeReceiver) { + clientcommon.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, + false, ob.chain.ChainId, tx.Hash().Hex(), sender.Hex(), sender.Hex(), "Gas") + return nil + } + + // donation check if bytes.Equal(tx.Data(), []byte(DonationMessage)) { ob.logger.ExternalChainWatcher.Info().Msgf("thank you rich folk for your donation! tx %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) return nil } - message := "" - if len(tx.Data()) != 0 { - message = hex.EncodeToString(tx.Data()) - } ob.logger.ExternalChainWatcher.Info().Msgf("TSS inTx detected on chain %d tx %s block %d from %s value %s message %s", - ob.chain.ChainId, tx.Hash().Hex(), blockNumber, sender.Hex(), tx.Value().String(), hex.EncodeToString(tx.Data())) + ob.chain.ChainId, tx.Hash().Hex(), blockNumber, sender.Hex(), tx.Value().String(), message) return zetabridge.GetInBoundVoteMessage( sender.Hex(), diff --git a/zetaclient/evm/inbounds_test.go b/zetaclient/evm/inbounds_test.go new file mode 100644 index 0000000000..84b24dd200 --- /dev/null +++ b/zetaclient/evm/inbounds_test.go @@ -0,0 +1,88 @@ +package evm + +import ( + "path" + "testing" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/config" + "github.com/zeta-chain/zetacore/zetaclient/testutils" +) + +func MockEVMClient(chain common.Chain) *ChainClient { + return &ChainClient{ + chain: chain, + zetaClient: testutils.MockCoreBridge(), + } +} + +func MockConnectorNonEth() *zetaconnector.ZetaConnectorNonEth { + connector, err := zetaconnector.NewZetaConnectorNonEth(ethcommon.Address{}, ðclient.Client{}) + if err != nil { + panic(err) + } + return connector +} + +func ParseReceiptZetaSent( + receipt *ethtypes.Receipt, + ob *ChainClient, + connector *zetaconnector.ZetaConnectorNonEth) *types.MsgVoteOnObservedInboundTx { + var msg *types.MsgVoteOnObservedInboundTx + for _, log := range receipt.Logs { + event, err := connector.ParseZetaSent(*log) + if err == nil && event != nil { + msg = ob.GetInboundVoteMsgForZetaSentEvent(event) + break // found + } + } + return msg +} + +func TestEthereum_GetInboundVoteMsgForZetaSentEvent(t *testing.T) { + // load archived ZetaSent receipt + // zeta-chain/crosschain/cctx/0x477544c4b8c8be544b23328b21286125c89cd6bb5d1d6d388d91eea8ea1a6f1f + receipt := ethtypes.Receipt{} + name := "chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json" + err := testutils.LoadObjectFromJSONFile(&receipt, path.Join("../", testutils.TestDataPathEVM, name)) + require.NoError(t, err) + + // create mock client and connector + ob := MockEVMClient(common.EthChain()) + connector := MockConnectorNonEth() + + // parse ZetaSent event + msg := ParseReceiptZetaSent(&receipt, ob, connector) + require.NotNil(t, msg) + require.Equal(t, "0x477544c4b8c8be544b23328b21286125c89cd6bb5d1d6d388d91eea8ea1a6f1f", msg.Digest()) + + // create config + cfg := &config.Config{ + ComplianceConfig: &config.ComplianceConfig{}, + } + + t.Run("should return nil msg if sender is restricted", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{msg.Sender} + config.LoadComplianceConfig(cfg) + msgRestricted := ParseReceiptZetaSent(&receipt, ob, connector) + require.Nil(t, msgRestricted) + }) + t.Run("should return nil msg if receiver is restricted", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{msg.Receiver} + config.LoadComplianceConfig(cfg) + msgRestricted := ParseReceiptZetaSent(&receipt, ob, connector) + require.Nil(t, msgRestricted) + }) + t.Run("should return nil msg if txOrigin is restricted", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{msg.TxOrigin} + config.LoadComplianceConfig(cfg) + msgRestricted := ParseReceiptZetaSent(&receipt, ob, connector) + require.Nil(t, msgRestricted) + }) +} diff --git a/zetaclient/interfaces/interfaces.go b/zetaclient/interfaces/interfaces.go index 088016ef39..422e36cc59 100644 --- a/zetaclient/interfaces/interfaces.go +++ b/zetaclient/interfaces/interfaces.go @@ -36,7 +36,7 @@ const ( type ChainClient interface { Start() Stop() - IsSendOutTxProcessed(sendHash string, nonce uint64, cointype common.CoinType, logger zerolog.Logger) (bool, bool, error) + IsSendOutTxProcessed(cctx *crosschaintypes.CrossChainTx, logger zerolog.Logger) (bool, bool, error) SetChainParams(observertypes.ChainParams) GetChainParams() observertypes.ChainParams GetTxID(nonce uint64) string @@ -46,7 +46,7 @@ type ChainClient interface { // ChainSigner is the interface to sign transactions for a chain type ChainSigner interface { TryProcessOutTx( - send *crosschaintypes.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, outTxMan *outtxprocessor.Processor, outTxID string, evmClient ChainClient, diff --git a/zetaclient/bitcoin/testdata/mempool.space_block_828440.json b/zetaclient/testdata/btc/block_mempool.space_8332_828440.json similarity index 100% rename from zetaclient/bitcoin/testdata/mempool.space_block_828440.json rename to zetaclient/testdata/btc/block_mempool.space_8332_828440.json diff --git a/zetaclient/bitcoin/testdata/bitcoin_block_trimmed_828440.json b/zetaclient/testdata/btc/block_trimmed_8332_828440.json similarity index 100% rename from zetaclient/bitcoin/testdata/bitcoin_block_trimmed_828440.json rename to zetaclient/testdata/btc/block_trimmed_8332_828440.json diff --git a/zetaclient/testdata/btc/outtx_8332_148_raw_result.json b/zetaclient/testdata/btc/outtx_8332_148_raw_result.json new file mode 100644 index 0000000000..01aff1a4b7 --- /dev/null +++ b/zetaclient/testdata/btc/outtx_8332_148_raw_result.json @@ -0,0 +1,111 @@ +{ + "hex": "0100000000010792fe0c144838ef41b22aa655771547acca8ee913efafb8b3eb8cbd182a30caef0000000000ffffffffefceb531b2f8db989f7f7cfba458a434e313aa84ea7070713e391d0ceb05c03d0000000000ffffffffd7e5f6f569fa1b52bdab189ca836e8c1d4409c934d34ee821bb2f325a8acc3740000000000ffffffffbfc5c2e4988acfa851d68880b4869621c3be2132c5993cab4a1f580eef4c26870000000000ffffffff7400bc3a0f71a60a4241c8ffc2dbe860a8791d34e14a62963df03d973349f25a0000000000ffffffff92fe0c144838ef41b22aa655771547acca8ee913efafb8b3eb8cbd182a30caef0200000000ffffffff30a5ad76ee984c7e2da0b44e2b7153f4885201cfba5f3ed1b226c08a935557b80000000000ffffffff036408000000000000160014daaae0d3de9d8fdee31661e61aea828b59be7864e02e0000000000001600140c1bfb7d38dff0946fdec5626d51ad58d7e9bc54d1b9530200000000160014daaae0d3de9d8fdee31661e61aea828b59be786402483045022100febd70794057e61b83e2aac09302d89910e3b79a031b084b4f148c5529a189c602207c60a3834eeb7dd989c108c735bc17e0d53ce77a016e8c0890dbc1e5dc573b6c012102fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc02483045022100e2a7db33480b4623c191be348525f48d2c6c7fd8c7a976ab87d556a3d9f1bf0c022060c51f38542f13c332c114aaaa2093728343a95506930d5c810bddde4a312153012102fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc0247304402203cebf1856c0af2c5c2d8ece435418e999e700507ea647bb5cf109949b337d57302206753f01b7c81b731046b18aff7589e8471524aefe6349a346870bc09ca1de8d7012102fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc02473044022053358333d728affa4a95e23f66599f66c9401e4f802bbb13f014acfae28217680220793c58356267a7c0052889a7da83d37eb01306277496b3e044e4a5b4abdc4461012102fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc02473044022021552ae9009183979b534dedb251d555aacaa68f140c016ebfb1e6787a35c5e00220309df76bd511124352c3955f92d0db1cbab6504fe5649b0585b0c7400403570b012102fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc02483045022100fabcbdbccc0d2468af60743fa817159fdd09a3f97193f119780ee95ab4014c9202205f728cb2dea3a9272651d09c23822649b38c160d0cc222613cb75db1bcde5e18012102fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc024830450221008535da95650bcd786495f3fdbf19103fc0d6408d002d60212bb61fbc7bf6894502202ba5e5e32989ac3ef293f556196c2633e6e73750e3253ed474e366c5649ac1dd012102fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc00000000", + "txid": "030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0", + "hash": "a9ae1d437cc910eeaf82b566102db68a9180c21f5f23f4fcff386c59c45359c1", + "size": 1145, + "vsize": 579, + "weight": 2315, + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": "efca302a18bd8cebb3b8afef13e98ecaac47157755a62ab241ef3848140cfe92", + "vout": 0, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "3045022100febd70794057e61b83e2aac09302d89910e3b79a031b084b4f148c5529a189c602207c60a3834eeb7dd989c108c735bc17e0d53ce77a016e8c0890dbc1e5dc573b6c01", + "02fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc" + ], + "sequence": 4294967295 + }, + { + "txid": "3dc005eb0c1d393e717070ea84aa13e334a458a4fb7c7f9f98dbf8b231b5ceef", + "vout": 0, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "3045022100e2a7db33480b4623c191be348525f48d2c6c7fd8c7a976ab87d556a3d9f1bf0c022060c51f38542f13c332c114aaaa2093728343a95506930d5c810bddde4a31215301", + "02fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc" + ], + "sequence": 4294967295 + }, + { + "txid": "74c3aca825f3b21b82ee344d939c40d4c1e836a89c18abbd521bfa69f5f6e5d7", + "vout": 0, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "304402203cebf1856c0af2c5c2d8ece435418e999e700507ea647bb5cf109949b337d57302206753f01b7c81b731046b18aff7589e8471524aefe6349a346870bc09ca1de8d701", + "02fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc" + ], + "sequence": 4294967295 + }, + { + "txid": "87264cef0e581f4aab3c99c53221bec3219686b48088d651a8cf8a98e4c2c5bf", + "vout": 0, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "3044022053358333d728affa4a95e23f66599f66c9401e4f802bbb13f014acfae28217680220793c58356267a7c0052889a7da83d37eb01306277496b3e044e4a5b4abdc446101", + "02fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc" + ], + "sequence": 4294967295 + }, + { + "txid": "5af24933973df03d96624ae1341d79a860e8dbc2ffc841420aa6710f3abc0074", + "vout": 0, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "3044022021552ae9009183979b534dedb251d555aacaa68f140c016ebfb1e6787a35c5e00220309df76bd511124352c3955f92d0db1cbab6504fe5649b0585b0c7400403570b01", + "02fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc" + ], + "sequence": 4294967295 + }, + { + "txid": "efca302a18bd8cebb3b8afef13e98ecaac47157755a62ab241ef3848140cfe92", + "vout": 2, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "3045022100fabcbdbccc0d2468af60743fa817159fdd09a3f97193f119780ee95ab4014c9202205f728cb2dea3a9272651d09c23822649b38c160d0cc222613cb75db1bcde5e1801", + "02fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc" + ], + "sequence": 4294967295 + }, + { + "txid": "b85755938ac026b2d13e5fbacf015288f453712b4eb4a02d7e4c98ee76ada530", + "vout": 0, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "30450221008535da95650bcd786495f3fdbf19103fc0d6408d002d60212bb61fbc7bf6894502202ba5e5e32989ac3ef293f556196c2633e6e73750e3253ed474e366c5649ac1dd01", + "02fad3348b7c7d73e85a7cfe53af42b535b750419efc55c36c807f38dd0ba06edc" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00002148, + "n": 0, + "scriptPubKey": { + "asm": "0 daaae0d3de9d8fdee31661e61aea828b59be7864", + "hex": "0014daaae0d3de9d8fdee31661e61aea828b59be7864", + "type": "witness_v0_keyhash" + } + }, + { + "value": 0.00012, + "n": 1, + "scriptPubKey": { + "asm": "0 0c1bfb7d38dff0946fdec5626d51ad58d7e9bc54", + "hex": "00140c1bfb7d38dff0946fdec5626d51ad58d7e9bc54", + "type": "witness_v0_keyhash" + } + }, + { + "value": 0.39041489, + "n": 2, + "scriptPubKey": { + "asm": "0 daaae0d3de9d8fdee31661e61aea828b59be7864", + "hex": "0014daaae0d3de9d8fdee31661e61aea828b59be7864", + "type": "witness_v0_keyhash" + } + } + ] +} diff --git a/zetaclient/testdata/cctx/cctx_1_6270.json b/zetaclient/testdata/cctx/cctx_1_6270.json new file mode 100644 index 0000000000..3797b52c3c --- /dev/null +++ b/zetaclient/testdata/cctx/cctx_1_6270.json @@ -0,0 +1,34 @@ +{ + "index": "0xe930f363591b348a07e0a6d309b4301b84f702e3e81e0d0902340c7f7da4b5af", + "zeta_fees": "0", + "cctx_status": { "status": 3, "lastUpdate_timestamp": 1708464433 }, + "inbound_tx_params": { + "sender": "0xd91b507F2A3e2D4A32d0C86Ac19FEAD2D461008D", + "sender_chain_id": 7000, + "tx_origin": "0x18D0E2c38b4188D8Ae07008C3BeeB1c80748b41c", + "coin_type": 1, + "amount": "9831832641427386", + "inbound_tx_observed_hash": "0x8bd0df31e512c472e3162a41281b740b518216cc8eb787c2eb59c81e0cffbe89", + "inbound_tx_observed_external_height": 1846989, + "inbound_tx_ballot_index": "0xe930f363591b348a07e0a6d309b4301b84f702e3e81e0d0902340c7f7da4b5af" + }, + "outbound_tx_params": [ + { + "receiver": "0x18D0E2c38b4188D8Ae07008C3BeeB1c80748b41c", + "receiver_chainId": 1, + "coin_type": 1, + "amount": "9831832641427386", + "outbound_tx_tss_nonce": 6270, + "outbound_tx_gas_limit": 21000, + "outbound_tx_gas_price": "69197693654", + "outbound_tx_hash": "0x20104d41e042db754cf7908c5441914e581b498eedbca40979c9853f4b7f8460", + "outbound_tx_ballot_index": "0x346a1d00a4d26a2065fe1dc7d5af59a49ad6a8af25853ae2ec976c07349f48c1", + "outbound_tx_observed_external_height": 19271550, + "outbound_tx_gas_used": 21000, + "outbound_tx_effective_gas_price": "69197693654", + "outbound_tx_effective_gas_limit": 21000, + "tss_pubkey": "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc", + "tx_finalization_status": 2 + } + ] +} diff --git a/zetaclient/testdata/cctx/cctx_8332_148.json b/zetaclient/testdata/cctx/cctx_8332_148.json new file mode 100644 index 0000000000..4564858f58 --- /dev/null +++ b/zetaclient/testdata/cctx/cctx_8332_148.json @@ -0,0 +1,32 @@ +{ + "index": "0xb3f5f3cf2ed2e0c3fa64c8297c9e50fbc07351fb2d26d8eae4cfbbd45e47a524", + "zeta_fees": "0", + "cctx_status": { "status": 3, "lastUpdate_timestamp": 1708608895 }, + "inbound_tx_params": { + "sender": "0x13A0c5930C028511Dc02665E7285134B6d11A5f4", + "sender_chain_id": 7000, + "tx_origin": "0xe99174F08e1186134830f8511De06bd010978533", + "coin_type": 1, + "amount": "12000", + "inbound_tx_observed_hash": "0x06455013319acb1b027461134853c77b003d8eab162b1f37673da5ad8a50b74f", + "inbound_tx_observed_external_height": 1870408, + "inbound_tx_ballot_index": "0xb3f5f3cf2ed2e0c3fa64c8297c9e50fbc07351fb2d26d8eae4cfbbd45e47a524" + }, + "outbound_tx_params": [ + { + "receiver": "bc1qpsdlklfcmlcfgm77c43x65ddtrt7n0z57hsyjp", + "receiver_chainId": 8332, + "coin_type": 1, + "amount": "12000", + "outbound_tx_tss_nonce": 148, + "outbound_tx_gas_limit": 254, + "outbound_tx_gas_price": "46", + "outbound_tx_hash": "030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0", + "outbound_tx_ballot_index": "0x43845693f799b7a5e84dcf11321ae681ec018d709ecc919773968018f93e21c1", + "outbound_tx_observed_external_height": 150, + "outbound_tx_effective_gas_price": "0", + "tss_pubkey": "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc", + "tx_finalization_status": 2 + } + ] +} diff --git a/zetaclient/testdata/evm/chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json b/zetaclient/testdata/evm/chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json new file mode 100644 index 0000000000..77db2b356f --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json @@ -0,0 +1,60 @@ +{ + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xd980e6", + "logsBloom": "0x00000000000000000000000000000000001802000000000000000000080000000000000000000000000000000000000008000000000000000004000000200000000000000000000000000008000000000000000000000000000000000080080000000000100000000000040010000000000000000000000000000010000000000000000000000100000000000000000200000000000000000000000000000000020000000000000000000000000000000000000000000002000000000000000000000002000000000000000000000080000000000000000000200000000000000010100000000000000000000000000000000000000000000000002000000000", + "logs": [ + { + "address": "0xf091867ec603a6628ed83d274e835539d82e9cc8", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000002f993766e8e1ef9288b1f33f6aa244911a0a77a7", + "0x000000000000000000000000000007cf399229b2f5a4d043f20e90c9c98b7c6a" + ], + "data": "0x000000000000000000000000000000000000000000000001158e460913d00000", + "blockNumber": "0x12617e6", + "transactionHash": "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76", + "transactionIndex": "0xbd", + "blockHash": "0x68afbd4ae4a74a74e6ce0dd85f208a5422f453f19ef20b36443eb1bba2ba77fa", + "logIndex": "0x121", + "removed": false + }, + { + "address": "0xf091867ec603a6628ed83d274e835539d82e9cc8", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000002f993766e8e1ef9288b1f33f6aa244911a0a77a7", + "0x000000000000000000000000000007cf399229b2f5a4d043f20e90c9c98b7c6a" + ], + "data": "0x00000000000000000000000000000000000000000001a783220f53d22e300000", + "blockNumber": "0x12617e6", + "transactionHash": "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76", + "transactionIndex": "0xbd", + "blockHash": "0x68afbd4ae4a74a74e6ce0dd85f208a5422f453f19ef20b36443eb1bba2ba77fa", + "logIndex": "0x122", + "removed": false + }, + { + "address": "0x000007cf399229b2f5a4d043f20e90c9c98b7c6a", + "topics": [ + "0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4", + "0x0000000000000000000000002f993766e8e1ef9288b1f33f6aa244911a0a77a7", + "0x0000000000000000000000000000000000000000000000000000000000001b58" + ], + "data": "0x0000000000000000000000002f993766e8e1ef9288b1f33f6aa244911a0a77a700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000001158e460913d0000000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000142f993766e8e1ef9288b1f33f6aa244911a0a77a700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x12617e6", + "transactionHash": "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76", + "transactionIndex": "0xbd", + "blockHash": "0x68afbd4ae4a74a74e6ce0dd85f208a5422f453f19ef20b36443eb1bba2ba77fa", + "logIndex": "0x123", + "removed": false + } + ], + "transactionHash": "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0xd956", + "blockHash": "0x68afbd4ae4a74a74e6ce0dd85f208a5422f453f19ef20b36443eb1bba2ba77fa", + "blockNumber": "0x12617e6", + "transactionIndex": "0xbd" +} diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go new file mode 100644 index 0000000000..f767f4905a --- /dev/null +++ b/zetaclient/testutils/constant.go @@ -0,0 +1,9 @@ +package testutils + +const ( + TSSAddressEVMMainnet = "0x70e967acFcC17c3941E87562161406d41676FD83" + TSSAddressBTCMainnet = "bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y" + + TSSAddressEVMAthens3 = "0x8531a5aB847ff5B22D855633C25ED1DA3255247e" + TSSAddressBTCAthens3 = "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur" +) diff --git a/zetaclient/testutils/mock.go b/zetaclient/testutils/mock.go new file mode 100644 index 0000000000..92c0f6d1b2 --- /dev/null +++ b/zetaclient/testutils/mock.go @@ -0,0 +1,72 @@ +package testutils + +import ( + "github.com/btcsuite/btcutil" + "github.com/cosmos/cosmos-sdk/types" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/keys" + "github.com/zeta-chain/zetacore/zetaclient/zetabridge" +) + +var _ interfaces.TSSSigner = (*MockTSS)(nil) + +// MockTSS is a mock of TSS signer for testing +type MockTSS struct { + evmAddress string + btcAddress string +} + +func NewMockTSS(evmAddress string, btcAddress string) *MockTSS { + return &MockTSS{ + evmAddress: evmAddress, + btcAddress: btcAddress, + } +} + +func NewMockTSSMainnet() *MockTSS { + return NewMockTSS(TSSAddressEVMMainnet, TSSAddressBTCMainnet) +} + +func NewMockTSSAthens3() *MockTSS { + return NewMockTSS(TSSAddressEVMAthens3, TSSAddressBTCAthens3) +} + +func (s *MockTSS) Sign(_ []byte, _ uint64, _ uint64, _ *common.Chain, _ string) ([65]byte, error) { + return [65]byte{}, nil +} + +func (s *MockTSS) Pubkey() []byte { + return []byte{} +} + +func (s *MockTSS) EVMAddress() ethcommon.Address { + return ethcommon.HexToAddress(s.evmAddress) +} + +func (s *MockTSS) BTCAddress() string { + return s.btcAddress +} + +func (s *MockTSS) BTCAddressWitnessPubkeyHash() *btcutil.AddressWitnessPubKeyHash { + return nil +} + +func (s *MockTSS) PubKeyCompressedBytes() []byte { + return []byte{} +} + +func MockCoreBridge() *zetabridge.ZetaCoreBridge { + bridge, err := zetabridge.NewZetaCoreBridge( + &keys.Keys{OperatorAddress: types.AccAddress{}}, + "127.0.0.1", + "", + "zetachain_7000-1", + false, + nil) + if err != nil { + panic(err) + } + return bridge +} diff --git a/zetaclient/testutils/utils.go b/zetaclient/testutils/utils.go index 80d4bb00e7..4f00f10bfd 100644 --- a/zetaclient/testutils/utils.go +++ b/zetaclient/testutils/utils.go @@ -6,10 +6,15 @@ import ( "path/filepath" "github.com/btcsuite/btcd/btcjson" + "github.com/zeta-chain/zetacore/zetaclient/config" ) const ( - TestDataPath = "testdata" + TestDataPathEVM = "testdata/evm" + TestDataPathBTC = "testdata/btc" + TestDataPathCctx = "testdata/cctx" + RestrictedEVMAddressTest = "0x8a81Ba8eCF2c418CAe624be726F505332DF119C6" + RestrictedBtcAddressTest = "bcrt1qzp4gt6fc7zkds09kfzaf9ln9c5rvrzxmy6qmpp" ) // SaveObjectToJSONFile saves an object to a file in JSON format @@ -50,3 +55,9 @@ func SaveBTCBlockTrimTx(blockVb *btcjson.GetBlockVerboseTxResult, filename strin } return SaveObjectToJSONFile(blockVb, filename) } + +func ComplianceConfigTest() *config.ComplianceConfig { + return &config.ComplianceConfig{ + RestrictedAddresses: []string{RestrictedEVMAddressTest, RestrictedBtcAddressTest}, + } +} diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index 441081e34d..c3b94c8c54 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -216,7 +216,7 @@ func (co *CoreObserver) scheduleCctxEVM( } // try confirming the outtx - included, _, err := ob.IsSendOutTxProcessed(cctx.Index, params.OutboundTxTssNonce, params.CoinType, co.logger.ZetaChainWatcher) + included, _, err := ob.IsSendOutTxProcessed(cctx, co.logger.ZetaChainWatcher) if err != nil { co.logger.ZetaChainWatcher.Error().Err(err).Msgf("scheduleCctxEVM: IsSendOutTxProcessed faild for chain %d nonce %d", chainID, nonce) continue @@ -296,7 +296,7 @@ func (co *CoreObserver) scheduleCctxBTC( continue } // try confirming the outtx - included, confirmed, err := btcClient.IsSendOutTxProcessed(cctx.Index, nonce, params.CoinType, co.logger.ZetaChainWatcher) + included, confirmed, err := btcClient.IsSendOutTxProcessed(cctx, co.logger.ZetaChainWatcher) if err != nil { co.logger.ZetaChainWatcher.Error().Err(err).Msgf("scheduleCctxBTC: IsSendOutTxProcessed faild for chain %d nonce %d", chainID, nonce) continue From 60077bc7eda07c61a0ee83edaf301616ba4a757b Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Tue, 27 Feb 2024 16:05:18 +0100 Subject: [PATCH 18/33] chore(codecov.yml): fix ignore extension (#1814) * chore(codecov.yml): fix ignore extension * changelog --- changelog.md | 4 ++++ codecov.yml | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/changelog.md b/changelog.md index 857314ebea..73c5d49f38 100644 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,10 @@ * [1767](https://github.com/zeta-chain/node/pull/1767) - add unit tests for emissions module begin blocker +### Chores + +* [1814](https://github.com/zeta-chain/node/pull/1814) - fix code coverage ignore for protobuf generated files + ## Version: v13.0.0 * `zetaclientd start` : 2 inputs required from stdin diff --git a/codecov.yml b/codecov.yml index 647094cf84..48d3aed9a9 100644 --- a/codecov.yml +++ b/codecov.yml @@ -54,13 +54,13 @@ ignore: - "x/**/module.go" - "x/**/module_simulation.go" - "x/**/simulation/" - - "*.proto" - - "*.md" - - "*.yml" - - "*.yaml" - - "*.pb.go" - - "*.pb.gw.go" - - "*.json" + - "**/*.proto" + - "**/*.md" + - "**/*.yml" + - "**/*.yaml" + - "**/*.pb.go" + - "**/*.pb.gw.go" + - "**/*.json" - ".github/" - "app/" - "cmd/" From 920279d12d4ba083163c375848c77da529fabd73 Mon Sep 17 00:00:00 2001 From: skosito Date: Tue, 27 Feb 2024 19:53:23 +0000 Subject: [PATCH 19/33] refactor: remove params from config and introduce app context (#1774) --- changelog.md | 1 + cmd/zetaclientd/debug.go | 11 +- cmd/zetaclientd/keygen_tss.go | 24 +-- cmd/zetaclientd/start.go | 30 ++-- cmd/zetaclientd/start_utils.go | 5 +- cmd/zetaclientd/utils.go | 26 +-- zetaclient/app_context/app_context.go | 44 +++++ zetaclient/bitcoin/bitcoin_client.go | 17 +- zetaclient/bitcoin/bitcoin_client_rpc_test.go | 12 +- zetaclient/config/config.go | 1 - zetaclient/config/config_chain.go | 1 - zetaclient/config/types.go | 140 +-------------- zetaclient/core_context/zeta_core_context.go | 166 ++++++++++++++++++ zetaclient/evm/evm_client.go | 40 ++--- zetaclient/evm/inbounds.go | 7 +- .../supplychecker/zeta_supply_checker.go | 24 +-- zetaclient/tss/tss_signer.go | 11 +- zetaclient/zetabridge/tx.go | 17 +- zetaclient/zetabridge/zetacore_bridge.go | 11 +- zetaclient/zetacore_observer.go | 40 ++--- 20 files changed, 362 insertions(+), 266 deletions(-) create mode 100644 zetaclient/app_context/app_context.go create mode 100644 zetaclient/core_context/zeta_core_context.go diff --git a/changelog.md b/changelog.md index 73c5d49f38..6f48d5264d 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ ### Refactor * [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure +* [1774](https://github.com/zeta-chain/node/pull/1774) - split params and config in zetaclient ### Tests diff --git a/cmd/zetaclientd/debug.go b/cmd/zetaclientd/debug.go index 4bc251d54a..8dcb6e666a 100644 --- a/cmd/zetaclientd/debug.go +++ b/cmd/zetaclientd/debug.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" "github.com/zeta-chain/zetacore/zetaclient/evm" "github.com/zeta-chain/zetacore/zetaclient/keys" "github.com/zeta-chain/zetacore/zetaclient/metrics" @@ -51,6 +52,7 @@ func DebugCmd() *cobra.Command { if err != nil { return err } + coreContext := corecontext.NewZetaCoreContext(cfg) chainID, err := strconv.ParseInt(args[1], 10, 64) if err != nil { return err @@ -122,14 +124,17 @@ func DebugCmd() *cobra.Command { for _, chainParams := range chainParams { if chainParams.ChainId == chainID { - ob.WithParams(observertypes.ChainParams{ + ob.SetChainParams(observertypes.ChainParams{ ChainId: chainID, ConnectorContractAddress: chainParams.ConnectorContractAddress, ZetaTokenContractAddress: chainParams.ZetaTokenContractAddress, Erc20CustodyContractAddress: chainParams.Erc20CustodyContractAddress, }) - cfg.EVMChainConfigs[chainID].ZetaTokenContractAddress = chainParams.ZetaTokenContractAddress - ob.SetConfig(cfg) + evmChainParams, found := coreContext.GetEVMChainParams(chainID) + if !found { + return fmt.Errorf("missing chain params for chain %d", chainID) + } + evmChainParams.ZetaTokenContractAddress = chainParams.ZetaTokenContractAddress if strings.EqualFold(tx.To().Hex(), chainParams.ConnectorContractAddress) { coinType = common.CoinType_Zeta } else if strings.EqualFold(tx.To().Hex(), chainParams.Erc20CustodyContractAddress) { diff --git a/cmd/zetaclientd/keygen_tss.go b/cmd/zetaclientd/keygen_tss.go index 119a547a3a..16f79581b5 100644 --- a/cmd/zetaclientd/keygen_tss.go +++ b/cmd/zetaclientd/keygen_tss.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" mc "github.com/zeta-chain/zetacore/zetaclient/tss" "github.com/zeta-chain/zetacore/zetaclient/zetabridge" @@ -18,12 +19,12 @@ import ( "github.com/zeta-chain/go-tss/p2p" "github.com/zeta-chain/zetacore/common" observertypes "github.com/zeta-chain/zetacore/x/observer/types" - "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/metrics" ) -func GenerateTss(logger zerolog.Logger, - cfg *config.Config, +func GenerateTss( + appContext *appcontext.AppContext, + logger zerolog.Logger, zetaBridge *zetabridge.ZetaCoreBridge, peers p2p.AddrList, priKey secp256k1.PrivKey, @@ -37,15 +38,16 @@ func GenerateTss(logger zerolog.Logger, // TODO: remove this once we have a better way to determine the signature format // https://github.com/zeta-chain/node/issues/1397 bitcoinChainID := common.BtcRegtestChain().ChainId - if cfg.BitcoinConfig != nil { - bitcoinChainID = cfg.BitcoinConfig.ChainId + btcChain, _, btcEnabled := appContext.GetBTCChainAndConfig() + if btcEnabled { + bitcoinChainID = btcChain.ChainId } tss, err := mc.NewTSS( + appContext, peers, priKey, preParams, - cfg, zetaBridge, tssHistoricalList, bitcoinChainID, @@ -72,7 +74,7 @@ func GenerateTss(logger zerolog.Logger, // This loop will try keygen at the keygen block and then wait for keygen to be successfully reported by all nodes before breaking out of the loop. // If keygen is unsuccessful, it will reset the triedKeygenAtBlock flag and try again at a new keygen block. - keyGen := cfg.GetKeygen() + keyGen := appContext.ZetaCoreContext().GetKeygen() if keyGen.Status == observertypes.KeygenStatus_KeyGenSuccess { return tss, nil } @@ -98,13 +100,13 @@ func GenerateTss(logger zerolog.Logger, if currentBlock != keyGen.BlockNumber { if currentBlock > lastBlock { lastBlock = currentBlock - keygenLogger.Info().Msgf("Waiting For Keygen Block to arrive or new keygen block to be set. Keygen Block : %d Current Block : %d ChainID %s ", keyGen.BlockNumber, currentBlock, cfg.ChainID) + keygenLogger.Info().Msgf("Waiting For Keygen Block to arrive or new keygen block to be set. Keygen Block : %d Current Block : %d ChainID %s ", keyGen.BlockNumber, currentBlock, appContext.Config().ChainID) } continue } // Try keygen only once at a particular block, irrespective of whether it is successful or failure triedKeygenAtBlock = true - err = keygenTss(cfg, tss, keygenLogger) + err = keygenTss(keyGen, tss, keygenLogger) if err != nil { keygenLogger.Error().Err(err).Msg("keygenTss error") tssFailedVoteHash, err := zetaBridge.SetTSS("", keyGen.BlockNumber, common.ReceiveStatus_Failed) @@ -147,9 +149,7 @@ func GenerateTss(logger zerolog.Logger, return nil, errors.New("unexpected state for TSS generation") } -func keygenTss(cfg *config.Config, tss *mc.TSS, keygenLogger zerolog.Logger) error { - - keyGen := cfg.GetKeygen() +func keygenTss(keyGen observertypes.Keygen, tss *mc.TSS, keygenLogger zerolog.Logger) error { keygenLogger.Info().Msgf("Keygen at blocknum %d , TSS signers %s ", keyGen.BlockNumber, keyGen.GranteePubkeys) var req keygen.Request req = keygen.NewRequest(keyGen.GranteePubkeys, keyGen.BlockNumber, "0.14.0") diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index e6046586cc..8458acb802 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -22,7 +22,9 @@ import ( "github.com/zeta-chain/zetacore/common" observerTypes "github.com/zeta-chain/zetacore/x/observer/types" mc "github.com/zeta-chain/zetacore/zetaclient" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/config" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" "github.com/zeta-chain/zetacore/zetaclient/metrics" ) @@ -119,15 +121,15 @@ func start(_ *cobra.Command, _ []string) error { startLogger.Debug().Msgf("CreateAuthzSigner is ready") // Initialize core parameters from zetacore - err = zetaBridge.UpdateConfigFromCore(cfg, true) + appContext := appcontext.NewAppContext(corecontext.NewZetaCoreContext(cfg), cfg) + err = zetaBridge.UpdateZetaCoreContext(appContext.ZetaCoreContext(), true) if err != nil { startLogger.Error().Err(err).Msg("Error getting core parameters") return err } startLogger.Info().Msgf("Config is updated from ZetaCore %s", maskCfg(cfg)) - // ConfigUpdater: A polling goroutine checks and updates core parameters at every height. Zetacore stores core parameters for all clients - go zetaBridge.ConfigUpdater(cfg) + go zetaBridge.CoreContextUpdater(appContext) // Generate TSS address . The Tss address is generated through Keygen ceremony. The TSS key is used to sign all outbound transactions . // The bridgePk is private key for the Hotkey. The Hotkey is used to sign all inbound transactions @@ -173,7 +175,7 @@ func start(_ *cobra.Command, _ []string) error { } telemetryServer.SetIPAddress(cfg.PublicIP) - tss, err := GenerateTss(masterLogger, cfg, zetaBridge, peers, priKey, telemetryServer, tssHistoricalList, tssKeyPass, hotkeyPass) + tss, err := GenerateTss(appContext, masterLogger, zetaBridge, peers, priKey, telemetryServer, tssHistoricalList, tssKeyPass, hotkeyPass) if err != nil { return err } @@ -188,8 +190,8 @@ func start(_ *cobra.Command, _ []string) error { // For existing keygen, this should directly proceed to the next step ticker := time.NewTicker(time.Second * 1) for range ticker.C { - if cfg.Keygen.Status != observerTypes.KeygenStatus_KeyGenSuccess { - startLogger.Info().Msgf("Waiting for TSS Keygen to be a success, current status %s", cfg.Keygen.Status) + if appContext.ZetaCoreContext().GetKeygen().Status != observerTypes.KeygenStatus_KeyGenSuccess { + startLogger.Info().Msgf("Waiting for TSS Keygen to be a success, current status %s", appContext.ZetaCoreContext().GetKeygen().Status) continue } break @@ -207,7 +209,7 @@ func start(_ *cobra.Command, _ []string) error { // Defensive check: Make sure the tss address is set to the current TSS address and not the newly generated one tss.CurrentPubkey = currentTss.TssPubkey startLogger.Info().Msgf("Current TSS address \n ETH : %s \n BTC : %s \n PubKey : %s ", tss.EVMAddress(), tss.BTCAddress(), tss.CurrentPubkey) - if len(cfg.ChainsEnabled) == 0 { + if len(appContext.ZetaCoreContext().GetEnabledChains()) == 0 { startLogger.Error().Msgf("No chains enabled in updated config %s ", cfg.String()) } @@ -224,8 +226,8 @@ func start(_ *cobra.Command, _ []string) error { } } - // CreateSignerMap: This creates a map of all signers for each chain. Each signer is responsible for signing transactions for a particular chain - signerMap, err := CreateSignerMap(tss, loggers, cfg, telemetryServer) + // CreateSignerMap: This creates a map of all signers for each chain . Each signer is responsible for signing transactions for a particular chain + signerMap, err := CreateSignerMap(appContext, tss, loggers, telemetryServer) if err != nil { log.Error().Err(err).Msg("CreateSignerMap") return err @@ -238,10 +240,10 @@ func start(_ *cobra.Command, _ []string) error { } dbpath := filepath.Join(userDir, ".zetaclient/chainobserver") - // CreateChainClientMap : This creates a map of all chain clients. Each chain client is responsible for listening to events on the chain and processing them - chainClientMap, err := CreateChainClientMap(zetaBridge, tss, dbpath, loggers, cfg, telemetryServer) + // CreateChainClientMap : This creates a map of all chain clients . Each chain client is responsible for listening to events on the chain and processing them + chainClientMap, err := CreateChainClientMap(appContext, zetaBridge, tss, dbpath, loggers, telemetryServer) if err != nil { - startLogger.Err(err).Msg("CreateSignerMap") + startLogger.Err(err).Msg("CreateChainClientMap") return err } @@ -255,8 +257,8 @@ func start(_ *cobra.Command, _ []string) error { } // CreateCoreObserver : Core observer wraps the zetacore bridge and adds the client and signer maps to it . This is the high level object used for CCTX interactions - mo1 := mc.NewCoreObserver(zetaBridge, signerMap, chainClientMap, masterLogger, cfg, telemetryServer) - mo1.MonitorCore() + mo1 := mc.NewCoreObserver(zetaBridge, signerMap, chainClientMap, masterLogger, telemetryServer) + mo1.MonitorCore(appContext) // start zeta supply checker // TODO: enable diff --git a/cmd/zetaclientd/start_utils.go b/cmd/zetaclientd/start_utils.go index 47ed135442..042f2fb20d 100644 --- a/cmd/zetaclientd/start_utils.go +++ b/cmd/zetaclientd/start_utils.go @@ -68,9 +68,8 @@ func maskCfg(cfg *config.Config) string { maskedCfg.EVMChainConfigs = map[int64]*config.EVMConfig{} for key, val := range cfg.EVMChainConfigs { maskedCfg.EVMChainConfigs[key] = &config.EVMConfig{ - ChainParams: val.ChainParams, - Chain: val.Chain, - Endpoint: val.Endpoint, + Chain: val.Chain, + Endpoint: val.Endpoint, } } diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index e04f62bfc2..82a16dcb7f 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -5,6 +5,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/cosmos" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/authz" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" @@ -50,19 +51,24 @@ func CreateZetaBridge(cfg *config.Config, telemetry *metrics.TelemetryServer, ho } func CreateSignerMap( + appContext *appcontext.AppContext, tss interfaces.TSSSigner, loggers clientcommon.ClientLogger, - cfg *config.Config, ts *metrics.TelemetryServer, ) (map[common.Chain]interfaces.ChainSigner, error) { signerMap := make(map[common.Chain]interfaces.ChainSigner) // EVM signers - for _, evmConfig := range cfg.GetAllEVMConfigs() { + for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { if evmConfig.Chain.IsZetaChain() { continue } - mpiAddress := ethcommon.HexToAddress(evmConfig.ChainParams.ConnectorContractAddress) - erc20CustodyAddress := ethcommon.HexToAddress(evmConfig.ChainParams.Erc20CustodyContractAddress) + evmChainParams, found := appContext.ZetaCoreContext().GetEVMChainParams(evmConfig.Chain.ChainId) + if !found { + loggers.Std.Error().Msgf("ChainParam not found for chain %s", evmConfig.Chain.String()) + continue + } + mpiAddress := ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress) + erc20CustodyAddress := ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress) signer, err := evm.NewEVMSigner(evmConfig.Chain, evmConfig.Endpoint, tss, config.GetConnectorABI(), config.GetERC20CustodyABI(), mpiAddress, erc20CustodyAddress, loggers, ts) if err != nil { loggers.Std.Error().Err(err).Msgf("NewEVMSigner error for chain %s", evmConfig.Chain.String()) @@ -71,7 +77,7 @@ func CreateSignerMap( signerMap[evmConfig.Chain] = signer } // BTC signer - btcChain, btcConfig, enabled := cfg.GetBTCConfig() + btcChain, btcConfig, enabled := appContext.GetBTCChainAndConfig() if enabled { signer, err := bitcoin.NewBTCSigner(btcConfig, tss, loggers, ts) if err != nil { @@ -85,20 +91,20 @@ func CreateSignerMap( } func CreateChainClientMap( + appContext *appcontext.AppContext, bridge *zetabridge.ZetaCoreBridge, tss interfaces.TSSSigner, dbpath string, loggers clientcommon.ClientLogger, - cfg *config.Config, ts *metrics.TelemetryServer, ) (map[common.Chain]interfaces.ChainClient, error) { clientMap := make(map[common.Chain]interfaces.ChainClient) // EVM clients - for _, evmConfig := range cfg.GetAllEVMConfigs() { + for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { if evmConfig.Chain.IsZetaChain() { continue } - co, err := evm.NewEVMChainClient(bridge, tss, dbpath, loggers, cfg, *evmConfig, ts) + co, err := evm.NewEVMChainClient(appContext, bridge, tss, dbpath, loggers, *evmConfig, ts) if err != nil { loggers.Std.Error().Err(err).Msgf("NewEVMChainClient error for chain %s", evmConfig.Chain.String()) continue @@ -106,9 +112,9 @@ func CreateChainClientMap( clientMap[evmConfig.Chain] = co } // BTC client - btcChain, btcConfig, enabled := cfg.GetBTCConfig() + btcChain, _, enabled := appContext.GetBTCChainAndConfig() if enabled { - co, err := bitcoin.NewBitcoinClient(btcChain, bridge, tss, dbpath, loggers, btcConfig, ts) + co, err := bitcoin.NewBitcoinClient(appContext, btcChain, bridge, tss, dbpath, loggers, ts) if err != nil { loggers.Std.Error().Err(err).Msgf("NewBitcoinClient error for chain %s", btcChain.String()) diff --git a/zetaclient/app_context/app_context.go b/zetaclient/app_context/app_context.go new file mode 100644 index 0000000000..deab613618 --- /dev/null +++ b/zetaclient/app_context/app_context.go @@ -0,0 +1,44 @@ +package appcontext + +import ( + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/zetaclient/config" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" +) + +// AppContext contains global app structs like config, core context and logger +type AppContext struct { + coreContext *corecontext.ZetaCoreContext + config *config.Config +} + +// NewAppContext creates and returns new AppContext +func NewAppContext( + coreContext *corecontext.ZetaCoreContext, + config *config.Config, +) *AppContext { + return &AppContext{ + coreContext: coreContext, + config: config, + } +} + +func (a *AppContext) Config() *config.Config { + return a.config +} + +func (a *AppContext) ZetaCoreContext() *corecontext.ZetaCoreContext { + return a.coreContext +} + +// GetBTCChainAndConfig returns btc chain and config if enabled +func (a *AppContext) GetBTCChainAndConfig() (common.Chain, config.BTCConfig, bool) { + btcConfig, configEnabled := a.Config().GetBTCConfig() + btcChain, _, paramsEnabled := a.ZetaCoreContext().GetBTCChainParams() + + if !configEnabled || !paramsEnabled { + return common.Chain{}, config.BTCConfig{}, false + } + + return btcChain, btcConfig, true +} diff --git a/zetaclient/bitcoin/bitcoin_client.go b/zetaclient/bitcoin/bitcoin_client.go index 6a5e3b336a..f4b0cc4d42 100644 --- a/zetaclient/bitcoin/bitcoin_client.go +++ b/zetaclient/bitcoin/bitcoin_client.go @@ -13,7 +13,11 @@ import ( "sync/atomic" "time" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/zetabridge" cosmosmath "cosmossdk.io/math" @@ -30,9 +34,6 @@ import ( "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" - clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" - "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/metrics" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -133,12 +134,12 @@ func (ob *BTCChainClient) GetChainParams() observertypes.ChainParams { // NewBitcoinClient returns a new configuration based on supplied target chain func NewBitcoinClient( + appcontext *appcontext.AppContext, chain common.Chain, bridge interfaces.ZetaCoreBridger, tss interfaces.TSSSigner, dbpath string, loggers clientcommon.ClientLogger, - btcCfg config.BTCConfig, ts *metrics.TelemetryServer, ) (*BTCChainClient, error) { ob := BTCChainClient{ @@ -167,9 +168,13 @@ func NewBitcoinClient( ob.includedTxHashes = make(map[string]bool) ob.includedTxResults = make(map[string]*btcjson.GetTransactionResult) ob.broadcastedTx = make(map[string]string) - ob.params = btcCfg.ChainParams - + _, chainParams, found := appcontext.ZetaCoreContext().GetBTCChainParams() + if !found { + return nil, fmt.Errorf("btc chains params not initialized") + } + ob.params = *chainParams // initialize the Client + btcCfg := appcontext.Config().BitcoinConfig ob.logger.ChainLogger.Info().Msgf("Chain %s endpoint %s", ob.chain.String(), btcCfg.RPCHost) connCfg := &rpcclient.ConnConfig{ Host: btcCfg.RPCHost, diff --git a/zetaclient/bitcoin/bitcoin_client_rpc_test.go b/zetaclient/bitcoin/bitcoin_client_rpc_test.go index d1a00b907f..d40a08152c 100644 --- a/zetaclient/bitcoin/bitcoin_client_rpc_test.go +++ b/zetaclient/bitcoin/bitcoin_client_rpc_test.go @@ -9,6 +9,11 @@ import ( "testing" "time" + "github.com/zeta-chain/zetacore/common" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/config" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/btcsuite/btcd/btcjson" @@ -20,9 +25,6 @@ import ( "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/zeta-chain/zetacore/common" - clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" - "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/testutils" ) @@ -44,8 +46,8 @@ func (suite *BitcoinClientTestSuite) SetupTest() { tss := interfaces.TestSigner{ PrivKey: privateKey, } - //client, err := NewBitcoinClient(common.BtcTestNetChain(), nil, tss, "", nil) - client, err := NewBitcoinClient(common.BtcRegtestChain(), nil, tss, "/tmp", clientcommon.DefaultLoggers(), config.BTCConfig{}, nil) + appContext := appcontext.NewAppContext(&corecontext.ZetaCoreContext{}, &config.Config{}) + client, err := NewBitcoinClient(appContext, common.BtcRegtestChain(), nil, tss, "/tmp", clientcommon.DefaultLoggers(), nil) suite.Require().NoError(err) suite.BitcoinChainClient = client skBytes, err := hex.DecodeString(skHex) diff --git a/zetaclient/config/config.go b/zetaclient/config/config.go index 78c2d6ae9a..08a9d4c002 100644 --- a/zetaclient/config/config.go +++ b/zetaclient/config/config.go @@ -67,7 +67,6 @@ func Load(path string) (*Config, error) { // fields sanitization cfg.TssPath = GetPath(cfg.TssPath) cfg.PreParamsPath = GetPath(cfg.PreParamsPath) - cfg.CurrentTssPubkey = "" cfg.ZetaCoreHome = path // load compliance config diff --git a/zetaclient/config/config_chain.go b/zetaclient/config/config_chain.go index 369ebb994b..2596c554a5 100644 --- a/zetaclient/config/config_chain.go +++ b/zetaclient/config/config_chain.go @@ -35,7 +35,6 @@ func New() Config { return Config{ EVMChainConfigs: evmChainsConfigs, BitcoinConfig: bitcoinConfigRegnet, - ChainsEnabled: []common.Chain{}, } } diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index e2a9ccc1c2..a85f9f269e 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -3,12 +3,10 @@ package config import ( "encoding/json" "fmt" - "sort" "strings" "sync" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -32,14 +30,11 @@ type ClientConfiguration struct { } type EVMConfig struct { - observertypes.ChainParams Chain common.Chain Endpoint string } type BTCConfig struct { - observertypes.ChainParams - // the following are rpcclient ConnConfig fields RPCUsername string RPCPassword string @@ -56,6 +51,8 @@ type ComplianceConfig struct { // TODO: use snake case for json fields // https://github.com/zeta-chain/node/issues/1020 type Config struct { + cfgLock *sync.RWMutex `json:"-"` + Peer string `json:"Peer"` PublicIP string `json:"PublicIP"` LogFormat string `json:"LogFormat"` @@ -72,15 +69,10 @@ type Config struct { P2PDiagnosticTicker uint64 `json:"P2PDiagnosticTicker"` TssPath string `json:"TssPath"` TestTssKeysign bool `json:"TestTssKeysign"` - CurrentTssPubkey string `json:"CurrentTssPubkey"` KeyringBackend KeyringBackend `json:"KeyringBackend"` HsmMode bool `json:"HsmMode"` HsmHotKey string `json:"HsmHotKey"` - // chain specific fields are updatable at runtime and shared across threads - cfgLock *sync.RWMutex `json:"-"` - Keygen observertypes.Keygen `json:"Keygen"` - ChainsEnabled []common.Chain `json:"ChainsEnabled"` EVMChainConfigs map[int64]*EVMConfig `json:"EVMChainConfigs"` BitcoinConfig *BTCConfig `json:"BitcoinConfig"` @@ -104,27 +96,6 @@ func (c *Config) String() string { return string(s) } -func (c *Config) GetKeygen() observertypes.Keygen { - c.cfgLock.RLock() - defer c.cfgLock.RUnlock() - copiedPubkeys := make([]string, len(c.Keygen.GranteePubkeys)) - copy(copiedPubkeys, c.Keygen.GranteePubkeys) - - return observertypes.Keygen{ - Status: c.Keygen.Status, - GranteePubkeys: copiedPubkeys, - BlockNumber: c.Keygen.BlockNumber, - } -} - -func (c *Config) GetEnabledChains() []common.Chain { - c.cfgLock.RLock() - defer c.cfgLock.RUnlock() - copiedChains := make([]common.Chain, len(c.ChainsEnabled)) - copy(copiedChains, c.ChainsEnabled) - return copiedChains -} - func (c *Config) GetEVMConfig(chainID int64) (EVMConfig, bool) { c.cfgLock.RLock() defer c.cfgLock.RUnlock() @@ -145,18 +116,14 @@ func (c *Config) GetAllEVMConfigs() map[int64]*EVMConfig { return copied } -func (c *Config) GetBTCConfig() (common.Chain, BTCConfig, bool) { +func (c *Config) GetBTCConfig() (BTCConfig, bool) { c.cfgLock.RLock() defer c.cfgLock.RUnlock() if c.BitcoinConfig == nil { // bitcoin is not enabled - return common.Chain{}, BTCConfig{}, false - } - chain := common.GetChainFromChainID(c.BitcoinConfig.ChainId) - if chain == nil { - panic(fmt.Sprintf("BTCChain is missing for chainID %d", c.BitcoinConfig.ChainId)) + return BTCConfig{}, false } - return *chain, *c.BitcoinConfig, true + return *c.BitcoinConfig, true } // GetRestrictedAddressBook returns a map of restricted addresses @@ -179,103 +146,6 @@ func (c *Config) GetKeyringBackend() KeyringBackend { return c.KeyringBackend } -// UpdateChainParams updates core params for all chains -// this must be the ONLY function that writes to core params -func (c *Config) UpdateChainParams( - keygen *observertypes.Keygen, - newChains []common.Chain, - evmChainParams map[int64]*observertypes.ChainParams, - btcChainParams *observertypes.ChainParams, - init bool, - logger zerolog.Logger, -) { - c.cfgLock.Lock() - defer c.cfgLock.Unlock() - - // Ignore whatever order zetabridge organizes chain list in state - sort.SliceStable(newChains, func(i, j int) bool { - return newChains[i].ChainId < newChains[j].ChainId - }) - if len(newChains) == 0 { - logger.Warn().Msg("UpdateChainParams: No chains enabled in ZeroCore") - } - - // Add some warnings if chain list changes at runtime - if !init { - if len(c.ChainsEnabled) != len(newChains) { - logger.Warn().Msgf( - "UpdateChainParams: ChainsEnabled changed at runtime!! current: %v, new: %v", - c.ChainsEnabled, - newChains, - ) - } else { - for i, chain := range newChains { - if chain != c.ChainsEnabled[i] { - logger.Warn().Msgf( - "UpdateChainParams: ChainsEnabled changed at runtime!! current: %v, new: %v", - c.ChainsEnabled, - newChains, - ) - } - } - } - } - c.Keygen = *keygen - c.ChainsEnabled = newChains - // update chain params for bitcoin if it has config in file - if c.BitcoinConfig != nil && btcChainParams != nil { - c.BitcoinConfig.ChainParams = *btcChainParams - } - // update core params for evm chains we have configs in file - for _, params := range evmChainParams { - curCfg, found := c.EVMChainConfigs[params.ChainId] - if found { - curCfg.ChainParams = *params - } - } -} - -// Clone makes a separate (deep) copy of the config -func (c *Config) Clone() *Config { - c.cfgLock.RLock() - defer c.cfgLock.RUnlock() - copied := &Config{ - Peer: c.Peer, - PublicIP: c.PublicIP, - LogFormat: c.LogFormat, - LogLevel: c.LogLevel, - LogSampler: c.LogSampler, - PreParamsPath: c.PreParamsPath, - ChainID: c.ChainID, - ZetaCoreURL: c.ZetaCoreURL, - AuthzGranter: c.AuthzGranter, - AuthzHotkey: c.AuthzHotkey, - P2PDiagnostic: c.P2PDiagnostic, - ConfigUpdateTicker: c.ConfigUpdateTicker, - P2PDiagnosticTicker: c.P2PDiagnosticTicker, - TssPath: c.TssPath, - TestTssKeysign: c.TestTssKeysign, - KeyringBackend: c.KeyringBackend, - - cfgLock: &sync.RWMutex{}, - Keygen: c.GetKeygen(), - ChainsEnabled: c.GetEnabledChains(), - EVMChainConfigs: make(map[int64]*EVMConfig, len(c.EVMChainConfigs)), - BitcoinConfig: nil, - } - // deep copy evm & btc configs - for chainID, evmConfig := range c.EVMChainConfigs { - copied.EVMChainConfigs[chainID] = &EVMConfig{} - *copied.EVMChainConfigs[chainID] = *evmConfig - } - if c.BitcoinConfig != nil { - copied.BitcoinConfig = &BTCConfig{} - *copied.BitcoinConfig = *c.BitcoinConfig - } - - return copied -} - // ValidateChainParams performs some basic checks on core params func ValidateChainParams(chainParams *observertypes.ChainParams) error { if chainParams == nil { diff --git a/zetaclient/core_context/zeta_core_context.go b/zetaclient/core_context/zeta_core_context.go new file mode 100644 index 0000000000..27a88b8951 --- /dev/null +++ b/zetaclient/core_context/zeta_core_context.go @@ -0,0 +1,166 @@ +package corecontext + +import ( + "fmt" + "sort" + "sync" + + "github.com/rs/zerolog" + "github.com/zeta-chain/zetacore/common" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/config" +) + +// ZetaCoreContext contains core context params +// these are initialized and updated at runtime at every height +type ZetaCoreContext struct { + coreContextLock *sync.RWMutex + keygen *observertypes.Keygen + chainsEnabled []common.Chain + evmChainParams map[int64]*observertypes.ChainParams + bitcoinChainParams *observertypes.ChainParams + currentTssPubkey string +} + +// NewZetaCoreContext creates and returns new ZetaCoreContext +// it is initializing chain params from provided config +func NewZetaCoreContext(cfg *config.Config) *ZetaCoreContext { + evmChainParams := make(map[int64]*observertypes.ChainParams) + for _, e := range cfg.EVMChainConfigs { + evmChainParams[e.Chain.ChainId] = &observertypes.ChainParams{} + } + var bitcoinChainParams *observertypes.ChainParams + _, found := cfg.GetBTCConfig() + if found { + bitcoinChainParams = &observertypes.ChainParams{} + } + return &ZetaCoreContext{ + coreContextLock: new(sync.RWMutex), + chainsEnabled: []common.Chain{}, + evmChainParams: evmChainParams, + bitcoinChainParams: bitcoinChainParams, + } +} + +func (c *ZetaCoreContext) GetKeygen() observertypes.Keygen { + c.coreContextLock.RLock() + defer c.coreContextLock.RUnlock() + copiedPubkeys := make([]string, len(c.keygen.GranteePubkeys)) + copy(copiedPubkeys, c.keygen.GranteePubkeys) + + return observertypes.Keygen{ + Status: c.keygen.Status, + GranteePubkeys: copiedPubkeys, + BlockNumber: c.keygen.BlockNumber, + } +} + +func (c *ZetaCoreContext) GetCurrentTssPubkey() string { + c.coreContextLock.RLock() + defer c.coreContextLock.RUnlock() + return c.currentTssPubkey +} + +func (c *ZetaCoreContext) GetEnabledChains() []common.Chain { + c.coreContextLock.RLock() + defer c.coreContextLock.RUnlock() + copiedChains := make([]common.Chain, len(c.chainsEnabled)) + copy(copiedChains, c.chainsEnabled) + return copiedChains +} + +func (c *ZetaCoreContext) GetEVMChainParams(chainID int64) (*observertypes.ChainParams, bool) { + c.coreContextLock.RLock() + defer c.coreContextLock.RUnlock() + evmChainParams, found := c.evmChainParams[chainID] + return evmChainParams, found +} + +func (c *ZetaCoreContext) GetAllEVMChainParams() map[int64]*observertypes.ChainParams { + c.coreContextLock.RLock() + defer c.coreContextLock.RUnlock() + + // deep copy evm chain params + copied := make(map[int64]*observertypes.ChainParams, len(c.evmChainParams)) + for chainID, evmConfig := range c.evmChainParams { + copied[chainID] = &observertypes.ChainParams{} + *copied[chainID] = *evmConfig + } + return copied +} + +func (c *ZetaCoreContext) GetBTCChainParams() (common.Chain, *observertypes.ChainParams, bool) { + c.coreContextLock.RLock() + defer c.coreContextLock.RUnlock() + + if c.bitcoinChainParams == nil { // bitcoin is not enabled + return common.Chain{}, &observertypes.ChainParams{}, false + } + chain := common.GetChainFromChainID(c.bitcoinChainParams.ChainId) + if chain == nil { + panic(fmt.Sprintf("BTCChain is missing for chainID %d", c.bitcoinChainParams.ChainId)) + } + return *chain, c.bitcoinChainParams, true +} + +// Update updates core context and params for all chains +// this must be the ONLY function that writes to core context +func (c *ZetaCoreContext) Update( + keygen *observertypes.Keygen, + newChains []common.Chain, + evmChainParams map[int64]*observertypes.ChainParams, + btcChainParams *observertypes.ChainParams, + tssPubKey string, + init bool, + logger zerolog.Logger, +) { + c.coreContextLock.Lock() + defer c.coreContextLock.Unlock() + + // Ignore whatever order zetabridge organizes chain list in state + sort.SliceStable(newChains, func(i, j int) bool { + return newChains[i].ChainId < newChains[j].ChainId + }) + if len(newChains) == 0 { + logger.Warn().Msg("UpdateChainParams: No chains enabled in ZeroCore") + } + + // Add some warnings if chain list changes at runtime + if !init { + if len(c.chainsEnabled) != len(newChains) { + logger.Warn().Msgf( + "UpdateChainParams: ChainsEnabled changed at runtime!! current: %v, new: %v", + c.chainsEnabled, + newChains, + ) + } else { + for i, chain := range newChains { + if chain != c.chainsEnabled[i] { + logger.Warn().Msgf( + "UpdateChainParams: ChainsEnabled changed at runtime!! current: %v, new: %v", + c.chainsEnabled, + newChains, + ) + } + } + } + } + c.keygen = keygen + c.chainsEnabled = newChains + // update chain params for bitcoin if it has config in file + if c.bitcoinChainParams != nil && btcChainParams != nil { + c.bitcoinChainParams = btcChainParams + } + // update core params for evm chains we have configs in file + for _, params := range evmChainParams { + _, found := c.evmChainParams[params.ChainId] + if !found { + continue + } + c.evmChainParams[params.ChainId] = params + } + + if tssPubKey != "" { + c.currentTssPubkey = tssPubKey + } +} diff --git a/zetaclient/evm/evm_client.go b/zetaclient/evm/evm_client.go index 88cee1784d..b0d85631ca 100644 --- a/zetaclient/evm/evm_client.go +++ b/zetaclient/evm/evm_client.go @@ -14,6 +14,9 @@ import ( "sync/atomic" "time" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/zetabridge" @@ -93,21 +96,22 @@ type ChainClient struct { OutTxChan chan OutTx // send to this channel if you want something back! stop chan struct{} logger Log - cfg *config.Config - params observertypes.ChainParams + coreContext *corecontext.ZetaCoreContext + chainParams observertypes.ChainParams ts *metrics.TelemetryServer - blockCache *lru.Cache - blockCacheV3 *lru.Cache // blockCacheV3 caches blocks containing type-3 (BlobTxType) transactions - headerCache *lru.Cache + + blockCache *lru.Cache + blockCacheV3 *lru.Cache // blockCacheV3 caches blocks containing type-3 (BlobTxType) transactions + headerCache *lru.Cache } // NewEVMChainClient returns a new configuration based on supplied target chain func NewEVMChainClient( + appContext *appcontext.AppContext, bridge interfaces.ZetaCoreBridger, tss interfaces.TSSSigner, dbpath string, loggers clientcommon.ClientLogger, - cfg *config.Config, evmCfg config.EVMConfig, ts *metrics.TelemetryServer, ) (*ChainClient, error) { @@ -122,8 +126,12 @@ func NewEVMChainClient( ObserveOutTx: chainLogger.With().Str("module", "ObserveOutTx").Logger(), Compliance: loggers.Compliance, } - ob.cfg = cfg - ob.params = evmCfg.ChainParams + ob.coreContext = appContext.ZetaCoreContext() + chainParams, found := ob.coreContext.GetEVMChainParams(evmCfg.Chain.ChainId) + if !found { + return nil, fmt.Errorf("evm chains params not initialized for chain %d", evmCfg.Chain.ChainId) + } + ob.chainParams = *chainParams ob.stop = make(chan struct{}) ob.chain = evmCfg.Chain ob.Mu = &sync.Mutex{} @@ -198,28 +206,16 @@ func (ob *ChainClient) WithZetaClient(bridge *zetabridge.ZetaCoreBridge) { ob.zetaClient = bridge } -func (ob *ChainClient) WithParams(params observertypes.ChainParams) { - ob.Mu.Lock() - defer ob.Mu.Unlock() - ob.params = params -} - -func (ob *ChainClient) SetConfig(cfg *config.Config) { - ob.Mu.Lock() - defer ob.Mu.Unlock() - ob.cfg = cfg -} - func (ob *ChainClient) SetChainParams(params observertypes.ChainParams) { ob.Mu.Lock() defer ob.Mu.Unlock() - ob.params = params + ob.chainParams = params } func (ob *ChainClient) GetChainParams() observertypes.ChainParams { ob.Mu.Lock() defer ob.Mu.Unlock() - return ob.params + return ob.chainParams } func (ob *ChainClient) GetConnectorContract() (ethcommon.Address, *zetaconnector.ZetaConnectorNonEth, error) { diff --git a/zetaclient/evm/inbounds.go b/zetaclient/evm/inbounds.go index 9eca478955..ffb65fd5f4 100644 --- a/zetaclient/evm/inbounds.go +++ b/zetaclient/evm/inbounds.go @@ -354,12 +354,13 @@ func (ob *ChainClient) GetInboundVoteMsgForZetaSentEvent(event *zetaconnector.Ze } if !destChain.IsZetaChain() { - cfgDest, found := ob.cfg.GetEVMConfig(destChain.ChainId) + paramsDest, found := ob.coreContext.GetEVMChainParams(destChain.ChainId) if !found { - ob.logger.ExternalChainWatcher.Warn().Msgf("chain id not present in EVMChainConfigs %d", event.DestinationChainId.Int64()) + ob.logger.ExternalChainWatcher.Warn().Msgf("chain id not present in EVMChainParams %d", event.DestinationChainId.Int64()) return nil } - if strings.EqualFold(destAddr, cfgDest.ZetaTokenContractAddress) { + + if strings.EqualFold(destAddr, paramsDest.ZetaTokenContractAddress) { ob.logger.ExternalChainWatcher.Warn().Msgf("potential attack attempt: %s destination address is ZETA token contract address %s", destChain, destAddr) return nil } diff --git a/zetaclient/supplychecker/zeta_supply_checker.go b/zetaclient/supplychecker/zeta_supply_checker.go index 1d560794e5..2a2e112c4a 100644 --- a/zetaclient/supplychecker/zeta_supply_checker.go +++ b/zetaclient/supplychecker/zeta_supply_checker.go @@ -3,6 +3,7 @@ package supplychecker import ( "fmt" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/zetabridge" @@ -16,12 +17,12 @@ import ( "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" - "github.com/zeta-chain/zetacore/zetaclient/config" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" ) type ZetaSupplyChecker struct { - cfg *config.Config + coreContext *corecontext.ZetaCoreContext evmClient map[int64]*ethclient.Client zetaClient *zetabridge.ZetaCoreBridge ticker *clienttypes.DynamicTicker @@ -32,7 +33,7 @@ type ZetaSupplyChecker struct { genesisSupply sdkmath.Int } -func NewZetaSupplyChecker(cfg *config.Config, zetaClient *zetabridge.ZetaCoreBridge, logger zerolog.Logger) (ZetaSupplyChecker, error) { +func NewZetaSupplyChecker(appContext *appcontext.AppContext, zetaClient *zetabridge.ZetaCoreBridge, logger zerolog.Logger) (ZetaSupplyChecker, error) { dynamicTicker, err := clienttypes.NewDynamicTicker("ZETASupplyTicker", 15) if err != nil { return ZetaSupplyChecker{}, err @@ -45,10 +46,10 @@ func NewZetaSupplyChecker(cfg *config.Config, zetaClient *zetabridge.ZetaCoreBri logger: logger.With(). Str("module", "ZetaSupplyChecker"). Logger(), - cfg: cfg, - zetaClient: zetaClient, + coreContext: appContext.ZetaCoreContext(), + zetaClient: zetaClient, } - for _, evmConfig := range cfg.GetAllEVMConfigs() { + for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { if evmConfig.Chain.IsZetaChain() { continue } @@ -106,11 +107,12 @@ func (zs *ZetaSupplyChecker) CheckZetaTokenSupply() error { externalChainTotalSupply := sdkmath.ZeroInt() for _, chain := range zs.externalEvmChain { - externalEvmChainConfig, ok := zs.cfg.GetEVMConfig(chain.ChainId) + externalEvmChainParams, ok := zs.coreContext.GetEVMChainParams(chain.ChainId) if !ok { - return fmt.Errorf("externalEvmChainConfig not found for chain id %d", chain.ChainId) + return fmt.Errorf("externalEvmChainParams not found for chain id %d", chain.ChainId) } - zetaTokenAddressString := externalEvmChainConfig.ZetaTokenContractAddress + + zetaTokenAddressString := externalEvmChainParams.ZetaTokenContractAddress zetaTokenAddress := ethcommon.HexToAddress(zetaTokenAddressString) zetatokenNonEth, err := evm.FetchZetaZetaNonEthTokenContract(zetaTokenAddress, zs.evmClient[chain.ChainId]) if err != nil { @@ -128,11 +130,11 @@ func (zs *ZetaSupplyChecker) CheckZetaTokenSupply() error { externalChainTotalSupply = externalChainTotalSupply.Add(totalSupplyInt) } - ethConfig, ok := zs.cfg.GetEVMConfig(zs.ethereumChain.ChainId) + evmChainParams, ok := zs.coreContext.GetEVMChainParams(zs.ethereumChain.ChainId) if !ok { return fmt.Errorf("eth config not found for chain id %d", zs.ethereumChain.ChainId) } - ethConnectorAddressString := ethConfig.ConnectorContractAddress + ethConnectorAddressString := evmChainParams.ConnectorContractAddress ethConnectorAddress := ethcommon.HexToAddress(ethConnectorAddressString) ethConnectorContract, err := evm.FetchConnectorContractEth(ethConnectorAddress, zs.evmClient[zs.ethereumChain.ChainId]) if err != nil { diff --git a/zetaclient/tss/tss_signer.go b/zetaclient/tss/tss_signer.go index 00bdd43d6c..f9abdb78ee 100644 --- a/zetaclient/tss/tss_signer.go +++ b/zetaclient/tss/tss_signer.go @@ -12,6 +12,7 @@ import ( "strings" "time" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -81,34 +82,34 @@ type TSS struct { // NewTSS creates a new TSS instance func NewTSS( + appContext *appcontext.AppContext, peer p2p.AddrList, privkey tmcrypto.PrivKey, preParams *keygen.LocalPreParams, - cfg *config.Config, bridge interfaces.ZetaCoreBridger, tssHistoricalList []observertypes.TSS, bitcoinChainID int64, tssPassword string, hotkeyPassword string, ) (*TSS, error) { - server, err := SetupTSSServer(peer, privkey, preParams, cfg, tssPassword) + server, err := SetupTSSServer(peer, privkey, preParams, appContext.Config(), tssPassword) if err != nil { return nil, fmt.Errorf("SetupTSSServer error: %w", err) } newTss := TSS{ Server: server, Keys: make(map[string]*Key), - CurrentPubkey: cfg.CurrentTssPubkey, + CurrentPubkey: appContext.ZetaCoreContext().GetCurrentTssPubkey(), logger: log.With().Str("module", "tss_signer").Logger(), CoreBridge: bridge, BitcoinChainID: bitcoinChainID, } - err = newTss.LoadTssFilesFromDirectory(cfg.TssPath) + err = newTss.LoadTssFilesFromDirectory(appContext.Config().TssPath) if err != nil { return nil, err } - _, pubkeyInBech32, err := keys.GetKeyringKeybase(cfg, hotkeyPassword) + _, pubkeyInBech32, err := keys.GetKeyringKeybase(appContext.Config(), hotkeyPassword) if err != nil { return nil, err } diff --git a/zetaclient/zetabridge/tx.go b/zetaclient/zetabridge/tx.go index 863f7a3254..a142ad44f0 100644 --- a/zetaclient/zetabridge/tx.go +++ b/zetaclient/zetabridge/tx.go @@ -7,6 +7,7 @@ import ( "time" "cosmossdk.io/math" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" authz2 "github.com/zeta-chain/zetacore/zetaclient/authz" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,7 +17,6 @@ import ( "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" observerTypes "github.com/zeta-chain/zetacore/x/observer/types" - "github.com/zeta-chain/zetacore/zetaclient/config" ) const ( @@ -151,19 +151,20 @@ func (b *ZetaCoreBridge) SetTSS(tssPubkey string, keyGenZetaHeight int64, status return "", fmt.Errorf("set tss failed | err %s", err.Error()) } -func (b *ZetaCoreBridge) ConfigUpdater(cfg *config.Config) { - b.logger.Info().Msg("ConfigUpdater started") - ticker := time.NewTicker(time.Duration(cfg.ConfigUpdateTicker) * time.Second) +// CoreContextUpdater is a polling goroutine that checks and updates core context at every height +func (b *ZetaCoreBridge) CoreContextUpdater(appContext *appcontext.AppContext) { + b.logger.Info().Msg("CoreContextUpdater started") + ticker := time.NewTicker(time.Duration(appContext.Config().ConfigUpdateTicker) * time.Second) for { select { case <-ticker.C: b.logger.Debug().Msg("Running Updater") - err := b.UpdateConfigFromCore(cfg, false) + err := b.UpdateZetaCoreContext(appContext.ZetaCoreContext(), false) if err != nil { - b.logger.Err(err).Msg("ConfigUpdater failed to update config") + b.logger.Err(err).Msg("CoreContextUpdater failed to update config") } case <-b.stop: - b.logger.Info().Msg("ConfigUpdater stopped") + b.logger.Info().Msg("CoreContextUpdater stopped") return } } @@ -301,7 +302,6 @@ func (b *ZetaCoreBridge) MonitorVoteInboundTxResult(zetaTxHash string, retryGasL b.logger.Error().Err(lastErr).Msgf( "MonitorInboundTxResult: unable to query tx result for txHash %s, err %s", zetaTxHash, lastErr.Error(), ) - return } // PostVoteOutbound posts a vote on an observed outbound tx @@ -427,5 +427,4 @@ func (b *ZetaCoreBridge) MonitorVoteOutboundTxResult(zetaTxHash string, retryGas b.logger.Error().Err(lastErr).Msgf( "MonitorVoteOutboundTxResult: unable to query tx result for txHash %s, err %s", zetaTxHash, lastErr.Error(), ) - return } diff --git a/zetaclient/zetabridge/zetacore_bridge.go b/zetaclient/zetabridge/zetacore_bridge.go index 1f923a690b..0a605fb914 100644 --- a/zetaclient/zetabridge/zetacore_bridge.go +++ b/zetaclient/zetabridge/zetacore_bridge.go @@ -22,6 +22,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" + corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" "google.golang.org/grpc" ) @@ -189,7 +190,9 @@ func (b *ZetaCoreBridge) GetKeys() *keys.Keys { return b.keys } -func (b *ZetaCoreBridge) UpdateConfigFromCore(cfg *config.Config, init bool) error { +// UpdateZetaCoreContext updates core context +// zetacore stores core context for all clients +func (b *ZetaCoreBridge) UpdateZetaCoreContext(coreContext *corecontext.ZetaCoreContext, init bool) error { bn, err := b.GetZetaBlockHeight() if err != nil { return err @@ -243,14 +246,16 @@ func (b *ZetaCoreBridge) UpdateConfigFromCore(cfg *config.Config, init bool) err if err != nil { b.logger.Info().Msg("Unable to fetch keygen from zetabridge") } - cfg.UpdateChainParams(keyGen, newChains, newEVMParams, newBTCParams, init, b.logger) + tssPubKey := "" tss, err := b.GetCurrentTss() if err != nil { b.logger.Debug().Err(err).Msg("Unable to fetch TSS from zetabridge") } else { - cfg.CurrentTssPubkey = tss.GetTssPubkey() + tssPubKey = tss.GetTssPubkey() } + + coreContext.Update(keyGen, newChains, newEVMParams, newBTCParams, tssPubKey, init, b.logger) return nil } diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index c3b94c8c54..6d3320c746 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -5,9 +5,9 @@ import ( "math" "time" + appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" "github.com/zeta-chain/zetacore/zetaclient/interfaces" - "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -17,7 +17,7 @@ import ( "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" - "github.com/zeta-chain/zetacore/zetaclient/config" + "github.com/zeta-chain/zetacore/zetaclient/metrics" ) const ( @@ -35,7 +35,6 @@ type CoreObserver struct { signerMap map[common.Chain]interfaces.ChainSigner clientMap map[common.Chain]interfaces.ChainClient logger ZetaCoreLog - cfg *config.Config ts *metrics.TelemetryServer stop chan struct{} lastOperatorBalance sdkmath.Int @@ -47,14 +46,12 @@ func NewCoreObserver( signerMap map[common.Chain]interfaces.ChainSigner, clientMap map[common.Chain]interfaces.ChainClient, logger zerolog.Logger, - cfg *config.Config, ts *metrics.TelemetryServer, ) *CoreObserver { co := CoreObserver{ ts: ts, stop: make(chan struct{}), } - co.cfg = cfg chainLogger := logger.With(). Str("chain", "ZetaChain"). Logger() @@ -77,14 +74,10 @@ func NewCoreObserver( return &co } -func (co *CoreObserver) Config() *config.Config { - return co.cfg -} - -func (co *CoreObserver) MonitorCore() { +func (co *CoreObserver) MonitorCore(appContext *appcontext.AppContext) { myid := co.bridge.GetKeys().GetAddress() co.logger.ZetaChainWatcher.Info().Msgf("Starting Send Scheduler for %s", myid) - go co.startCctxScheduler() + go co.startCctxScheduler(appContext) go func() { // bridge queries UpgradePlan from zetabridge and send to its pause channel if upgrade height is reached @@ -98,7 +91,7 @@ func (co *CoreObserver) MonitorCore() { } // startCctxScheduler schedules keysigns for cctxs on each ZetaChain block (the ticker) -func (co *CoreObserver) startCctxScheduler() { +func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { outTxMan := outtxprocessor.NewOutTxProcessorManager(co.logger.ChainLogger) observeTicker := time.NewTicker(3 * time.Second) var lastBlockNum int64 @@ -142,7 +135,7 @@ func (co *CoreObserver) startCctxScheduler() { metrics.HotKeyBurnRate.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) // schedule keysign for pending cctxs on each chain - supportedChains := co.Config().GetEnabledChains() + supportedChains := appContext.ZetaCoreContext().GetEnabledChains() for _, c := range supportedChains { if c.ChainId == co.bridge.ZetaChain().ChainId { continue @@ -154,7 +147,7 @@ func (co *CoreObserver) startCctxScheduler() { co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: ListPendingCctx failed for chain %d", c.ChainId) continue } - ob, err := co.getUpdatedChainOb(c.ChainId) + ob, err := co.getUpdatedChainOb(appContext, c.ChainId) if err != nil { co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: getTargetChainOb failed for chain %d", c.ChainId) continue @@ -324,7 +317,7 @@ func (co *CoreObserver) scheduleCctxBTC( } } -func (co *CoreObserver) getUpdatedChainOb(chainID int64) (interfaces.ChainClient, error) { +func (co *CoreObserver) getUpdatedChainOb(appContext *appcontext.AppContext, chainID int64) (interfaces.ChainClient, error) { chainOb, err := co.getTargetChainOb(chainID) if err != nil { return nil, err @@ -332,22 +325,23 @@ func (co *CoreObserver) getUpdatedChainOb(chainID int64) (interfaces.ChainClient // update chain client core parameters curParams := chainOb.GetChainParams() if common.IsEVMChain(chainID) { - evmCfg, found := co.cfg.GetEVMConfig(chainID) - if found && !observertypes.ChainParamsEqual(curParams, evmCfg.ChainParams) { - chainOb.SetChainParams(evmCfg.ChainParams) + evmParams, found := appContext.ZetaCoreContext().GetEVMChainParams(chainID) + if found && !observertypes.ChainParamsEqual(curParams, *evmParams) { + chainOb.SetChainParams(*evmParams) co.logger.ZetaChainWatcher.Info().Msgf( "updated chain params for chainID %d, new params: %v", chainID, - evmCfg.ChainParams, + *evmParams, ) } } else if common.IsBitcoinChain(chainID) { - _, btcCfg, found := co.cfg.GetBTCConfig() - if found && !observertypes.ChainParamsEqual(curParams, btcCfg.ChainParams) { - chainOb.SetChainParams(btcCfg.ChainParams) + _, btcParams, found := appContext.ZetaCoreContext().GetBTCChainParams() + + if found && !observertypes.ChainParamsEqual(curParams, *btcParams) { + chainOb.SetChainParams(*btcParams) co.logger.ZetaChainWatcher.Info().Msgf( "updated chain params for Bitcoin, new params: %v", - btcCfg.ChainParams, + *btcParams, ) } } From 6dbb3c18ba989165a3c3c992a612ae9cf85987c9 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Thu, 29 Feb 2024 07:53:21 +0100 Subject: [PATCH 20/33] refactor(`crosschain`): move ballot voting logic into `observer` (#1511) * move logic for ballot voting for inbound * fix mocks * goimports * changelog * fix unit tests * fix CI = * x/observer/keeper/vote_outbound.go x/crosschain/types/expected_keepers.go x/crosschain/keeper/msg_server_vote_outbound_tx.go * update mocks * refactor finalized check * make generate * initialize vote inbound tests * add sdk keepers in observer testutil * implement mock option for observer * inbound vote test * initialize outbound tests * goimports * vote outbound tests * update changelog * test with integration tests * add isNew in vote_inbound * add tests back * use tmp context for ballot logic * add new test for finalized ballot * add not finzalized test --- Dockerfile | 8 +- changelog.md | 19 +- common/chain.go | 11 +- testutil/keeper/crosschain.go | 2 +- testutil/keeper/mocks/crosschain/fungible.go | 22 +- testutil/keeper/mocks/crosschain/observer.go | 84 ++++ testutil/keeper/mocks/mocks.go | 19 + testutil/keeper/mocks/observer/slashing.go | 53 +++ testutil/keeper/mocks/observer/staking.go | 91 ++++ testutil/keeper/observer.go | 81 +++- testutil/network/network_setup.go | 2 + testutil/sample/sample.go | 5 + .../integrationtests/inbound_voter_test.go | 14 +- x/crosschain/client/integrationtests/suite.go | 2 +- x/crosschain/keeper/cctx.go | 14 +- x/crosschain/keeper/evm_deposit.go | 6 +- x/crosschain/keeper/evm_deposit_test.go | 22 +- x/crosschain/keeper/evm_hooks.go | 22 +- .../keeper/msg_server_vote_inbound_tx.go | 107 ++--- .../keeper/msg_server_vote_inbound_tx_test.go | 160 +++---- .../keeper/msg_server_vote_outbound_tx.go | 61 +-- x/crosschain/types/errors.go | 5 - x/crosschain/types/expected_keepers.go | 18 +- x/fungible/keeper/deposits.go | 8 +- x/fungible/keeper/deposits_test.go | 36 +- x/observer/genesis_test.go | 2 +- x/observer/keeper/chain_nonces_test.go | 6 +- x/observer/keeper/chain_params_test.go | 8 +- x/observer/keeper/grpc_query_blame_test.go | 12 +- x/observer/keeper/grpc_query_nonces_test.go | 4 +- .../msg_server_add_block_header_test.go | 2 +- .../msg_server_remove_chain_params_test.go | 6 +- x/observer/keeper/msg_server_test.go | 14 - .../msg_server_update_chain_params_test.go | 4 +- ...msg_server_update_crosschain_flags_test.go | 4 +- .../keeper/msg_server_update_observer_test.go | 16 +- x/observer/keeper/nonce_to_cctx_test.go | 6 +- x/observer/keeper/nonces_test.go | 6 +- x/observer/keeper/observer_set_test.go | 14 +- x/observer/keeper/pending_nonces_test.go | 6 +- x/observer/keeper/tss_funds_migrator_test.go | 4 +- x/observer/keeper/tss_test.go | 16 +- x/observer/keeper/utils_test.go | 6 +- x/observer/keeper/vote_inbound.go | 84 ++++ x/observer/keeper/vote_inbound_test.go | 438 ++++++++++++++++++ x/observer/keeper/vote_outbound.go | 52 +++ x/observer/keeper/vote_outbound_test.go | 295 ++++++++++++ x/observer/migrations/v3/migrate_test.go | 2 +- x/observer/migrations/v4/migrate_test.go | 2 +- x/observer/migrations/v5/migrate_test.go | 4 +- x/observer/migrations/v6/migrate_test.go | 4 +- x/observer/types/ballot.go | 2 +- x/observer/types/errors.go | 24 +- x/observer/types/observer_mapper.go | 28 -- x/observer/types/observer_set.go | 27 ++ 55 files changed, 1576 insertions(+), 394 deletions(-) create mode 100644 testutil/keeper/mocks/observer/slashing.go create mode 100644 testutil/keeper/mocks/observer/staking.go delete mode 100644 x/observer/keeper/msg_server_test.go create mode 100644 x/observer/keeper/vote_inbound.go create mode 100644 x/observer/keeper/vote_inbound_test.go create mode 100644 x/observer/keeper/vote_outbound.go create mode 100644 x/observer/keeper/vote_outbound_test.go delete mode 100644 x/observer/types/observer_mapper.go diff --git a/Dockerfile b/Dockerfile index 8785a09ef5..e6da909959 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,15 +16,9 @@ RUN ssh-keygen -b 2048 -t rsa -f /root/.ssh/localtest.pem -q -N "" WORKDIR /go/delivery/zeta-node COPY go.mod . COPY go.sum . -#RUN --mount=type=cache,target=/root/.cache/go-build \ -# go mod download + RUN go mod download COPY . . - -#RUN --mount=type=cache,target=/root/.cache/go-build \ -# make install -#RUN --mount=type=cache,target=/root/.cache/go-build \ -# make install-zetae2e RUN make install RUN make install-zetae2e # diff --git a/changelog.md b/changelog.md index 6f48d5264d..54903786f5 100644 --- a/changelog.md +++ b/changelog.md @@ -2,15 +2,16 @@ ## Unreleased -### Features - -* [1789](https://github.com/zeta-chain/node/issues/1789) - block cross-chain transactions that involve restricted addresses - ### Refactor +* [1511](https://github.com/zeta-chain/node/pull/1511) - move ballot voting logic from `crosschain` to `observer` * [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure * [1774](https://github.com/zeta-chain/node/pull/1774) - split params and config in zetaclient +### Features + +* [1789](https://github.com/zeta-chain/node/issues/1789) - block cross-chain transactions that involve restricted addresses + ### Tests * [1767](https://github.com/zeta-chain/node/pull/1767) - add unit tests for emissions module begin blocker @@ -21,7 +22,10 @@ ## Version: v13.0.0 -* `zetaclientd start` : 2 inputs required from stdin +### Breaking Changes + +* `zetaclientd start`: now requires 2 inputs from stdin: hotkey password and tss keyshare password + Starting zetaclient now requires two passwords to be input; one for the hotkey and another for the tss key-share. ### Features @@ -36,9 +40,9 @@ *[1728] (https://github.com/zeta-chain/node/pull/1728) - allow aborted transactions to be refunded by minting tokens to zEvm. ### Refactor + * [1766](https://github.com/zeta-chain/node/pull/1766) - Refactors the `PostTxProcessing` EVM hook functionality to deal with invalid withdraw events -* [1630](https://github.com/zeta-chain/node/pull/1630) added password prompts for hotkey and tss keyshare in zetaclient - Starting zetaclient now requires two passwords to be input; one for the hotkey and another for the tss key-share. +* [1630](https://github.com/zeta-chain/node/pull/1630) - added password prompts for hotkey and tss keyshare in zetaclient * [1760](https://github.com/zeta-chain/node/pull/1760) - Make staking keeper private in crosschain module ### Fixes @@ -54,7 +58,6 @@ * [1721](https://github.com/zeta-chain/node/issues/1721) - zetaclient should provide bitcoin_chain_id when querying TSS address * [1744](https://github.com/zeta-chain/node/pull/1744) - added cmd to encrypt tss keyshare file, allowing empty tss password for backward compatibility. - ### Tests * [1584](https://github.com/zeta-chain/node/pull/1584) - allow to run E2E tests on any networks diff --git a/common/chain.go b/common/chain.go index 5254f150f6..8f02baa388 100644 --- a/common/chain.go +++ b/common/chain.go @@ -75,12 +75,17 @@ func (chain Chain) BTCAddressFromWitnessProgram(witnessProgram []byte) (string, // DecodeAddress decode the address string to bytes func (chain Chain) DecodeAddress(addr string) ([]byte, error) { - if IsEVMChain(chain.ChainId) { + return DecodeAddressFromChainID(chain.ChainId, addr) +} + +// DecodeAddressFromChainID decode the address string to bytes +func DecodeAddressFromChainID(chainID int64, addr string) ([]byte, error) { + if IsEVMChain(chainID) { return ethcommon.HexToAddress(addr).Bytes(), nil - } else if IsBitcoinChain(chain.ChainId) { + } else if IsBitcoinChain(chainID) { return []byte(addr), nil } - return nil, fmt.Errorf("chain (%d) not supported", chain.ChainId) + return nil, fmt.Errorf("chain (%d) not supported", chainID) } func IsZetaChain(chainID int64) bool { diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index 69f2ae68f4..45a68a5627 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -32,7 +32,7 @@ var ( CrosschainNoMocks = CrosschainMockOptions{} ) -// CrosschainKeeper initializes a crosschain keeper for testing purposes with option to mock specific keepers +// CrosschainKeeperWithMocks initializes a crosschain keeper for testing purposes with option to mock specific keepers func CrosschainKeeperWithMocks( t testing.TB, mockOptions CrosschainMockOptions, diff --git a/testutil/keeper/mocks/crosschain/fungible.go b/testutil/keeper/mocks/crosschain/fungible.go index 37bb347981..7a0a40a6de 100644 --- a/testutil/keeper/mocks/crosschain/fungible.go +++ b/testutil/keeper/mocks/crosschain/fungible.go @@ -597,9 +597,9 @@ func (_m *CrosschainFungibleKeeper) WithdrawFromGasStabilityPool(ctx types.Conte return r0 } -// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChain, data, coinType, asset -func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChain *zetacorecommon.Chain, data []byte, coinType zetacorecommon.CoinType, asset string) (*evmtypes.MsgEthereumTxResponse, bool, error) { - ret := _m.Called(ctx, from, to, amount, senderChain, data, coinType, asset) +// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChainID, data, coinType, asset +func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChainID int64, data []byte, coinType zetacorecommon.CoinType, asset string) (*evmtypes.MsgEthereumTxResponse, bool, error) { + ret := _m.Called(ctx, from, to, amount, senderChainID, data, coinType, asset) if len(ret) == 0 { panic("no return value specified for ZRC20DepositAndCallContract") @@ -608,25 +608,25 @@ func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Contex var r0 *evmtypes.MsgEthereumTxResponse var r1 bool var r2 error - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { - return rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { + return rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) *evmtypes.MsgEthereumTxResponse); ok { - r0 = rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) *evmtypes.MsgEthereumTxResponse); ok { + r0 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*evmtypes.MsgEthereumTxResponse) } } - if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) bool); ok { - r1 = rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) bool); ok { + r1 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } else { r1 = ret.Get(1).(bool) } - if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) error); ok { - r2 = rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) error); ok { + r2 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } else { r2 = ret.Error(2) } diff --git a/testutil/keeper/mocks/crosschain/observer.go b/testutil/keeper/mocks/crosschain/observer.go index ff3cf4cd72..8e2a40b9cb 100644 --- a/testutil/keeper/mocks/crosschain/observer.go +++ b/testutil/keeper/mocks/crosschain/observer.go @@ -800,6 +800,90 @@ func (_m *CrosschainObserverKeeper) SetTssAndUpdateNonce(ctx types.Context, tss _m.Called(ctx, tss) } +// VoteOnInboundBallot provides a mock function with given fields: ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash +func (_m *CrosschainObserverKeeper) VoteOnInboundBallot(ctx types.Context, senderChainID int64, receiverChainID int64, coinType common.CoinType, voter string, ballotIndex string, inTxHash string) (bool, bool, error) { + ret := _m.Called(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + + if len(ret) == 0 { + panic("no return value specified for VoteOnInboundBallot") + } + + var r0 bool + var r1 bool + var r2 error + if rf, ok := ret.Get(0).(func(types.Context, int64, int64, common.CoinType, string, string, string) (bool, bool, error)); ok { + return rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } + if rf, ok := ret.Get(0).(func(types.Context, int64, int64, common.CoinType, string, string, string) bool); ok { + r0 = rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(types.Context, int64, int64, common.CoinType, string, string, string) bool); ok { + r1 = rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } else { + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(types.Context, int64, int64, common.CoinType, string, string, string) error); ok { + r2 = rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// VoteOnOutboundBallot provides a mock function with given fields: ctx, ballotIndex, outTxChainID, receiveStatus, voter +func (_m *CrosschainObserverKeeper) VoteOnOutboundBallot(ctx types.Context, ballotIndex string, outTxChainID int64, receiveStatus common.ReceiveStatus, voter string) (bool, bool, observertypes.Ballot, string, error) { + ret := _m.Called(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + + if len(ret) == 0 { + panic("no return value specified for VoteOnOutboundBallot") + } + + var r0 bool + var r1 bool + var r2 observertypes.Ballot + var r3 string + var r4 error + if rf, ok := ret.Get(0).(func(types.Context, string, int64, common.ReceiveStatus, string) (bool, bool, observertypes.Ballot, string, error)); ok { + return rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } + if rf, ok := ret.Get(0).(func(types.Context, string, int64, common.ReceiveStatus, string) bool); ok { + r0 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(types.Context, string, int64, common.ReceiveStatus, string) bool); ok { + r1 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(types.Context, string, int64, common.ReceiveStatus, string) observertypes.Ballot); ok { + r2 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r2 = ret.Get(2).(observertypes.Ballot) + } + + if rf, ok := ret.Get(3).(func(types.Context, string, int64, common.ReceiveStatus, string) string); ok { + r3 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r3 = ret.Get(3).(string) + } + + if rf, ok := ret.Get(4).(func(types.Context, string, int64, common.ReceiveStatus, string) error); ok { + r4 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r4 = ret.Error(4) + } + + return r0, r1, r2, r3, r4 +} + // NewCrosschainObserverKeeper creates a new instance of CrosschainObserverKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewCrosschainObserverKeeper(t interface { diff --git a/testutil/keeper/mocks/mocks.go b/testutil/keeper/mocks/mocks.go index ec32911a3f..1156061b90 100644 --- a/testutil/keeper/mocks/mocks.go +++ b/testutil/keeper/mocks/mocks.go @@ -4,6 +4,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) /** @@ -59,6 +60,10 @@ type FungibleEVMKeeper interface { fungibletypes.EVMKeeper } +/** + * Emissions Mocks + */ + //go:generate mockery --name EmissionAccountKeeper --filename account.go --case underscore --output ./emissions type EmissionAccountKeeper interface { emissionstypes.AccountKeeper @@ -78,3 +83,17 @@ type EmissionStakingKeeper interface { type EmissionObserverKeeper interface { emissionstypes.ObserverKeeper } + +/** + * Observer Mocks + */ + +//go:generate mockery --name ObserverStakingKeeper --filename staking.go --case underscore --output ./observer +type ObserverStakingKeeper interface { + observertypes.StakingKeeper +} + +//go:generate mockery --name ObserverSlashingKeeper --filename slashing.go --case underscore --output ./observer +type ObserverSlashingKeeper interface { + observertypes.SlashingKeeper +} diff --git a/testutil/keeper/mocks/observer/slashing.go b/testutil/keeper/mocks/observer/slashing.go new file mode 100644 index 0000000000..a7793ef8dc --- /dev/null +++ b/testutil/keeper/mocks/observer/slashing.go @@ -0,0 +1,53 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// ObserverSlashingKeeper is an autogenerated mock type for the ObserverSlashingKeeper type +type ObserverSlashingKeeper struct { + mock.Mock +} + +// IsTombstoned provides a mock function with given fields: ctx, addr +func (_m *ObserverSlashingKeeper) IsTombstoned(ctx types.Context, addr types.ConsAddress) bool { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for IsTombstoned") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(types.Context, types.ConsAddress) bool); ok { + r0 = rf(ctx, addr) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// SetValidatorSigningInfo provides a mock function with given fields: ctx, address, info +func (_m *ObserverSlashingKeeper) SetValidatorSigningInfo(ctx types.Context, address types.ConsAddress, info slashingtypes.ValidatorSigningInfo) { + _m.Called(ctx, address, info) +} + +// NewObserverSlashingKeeper creates a new instance of ObserverSlashingKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewObserverSlashingKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *ObserverSlashingKeeper { + mock := &ObserverSlashingKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/observer/staking.go b/testutil/keeper/mocks/observer/staking.go new file mode 100644 index 0000000000..90007b6c35 --- /dev/null +++ b/testutil/keeper/mocks/observer/staking.go @@ -0,0 +1,91 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// ObserverStakingKeeper is an autogenerated mock type for the ObserverStakingKeeper type +type ObserverStakingKeeper struct { + mock.Mock +} + +// GetDelegation provides a mock function with given fields: ctx, delAddr, valAddr +func (_m *ObserverStakingKeeper) GetDelegation(ctx types.Context, delAddr types.AccAddress, valAddr types.ValAddress) (stakingtypes.Delegation, bool) { + ret := _m.Called(ctx, delAddr, valAddr) + + if len(ret) == 0 { + panic("no return value specified for GetDelegation") + } + + var r0 stakingtypes.Delegation + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, types.ValAddress) (stakingtypes.Delegation, bool)); ok { + return rf(ctx, delAddr, valAddr) + } + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, types.ValAddress) stakingtypes.Delegation); ok { + r0 = rf(ctx, delAddr, valAddr) + } else { + r0 = ret.Get(0).(stakingtypes.Delegation) + } + + if rf, ok := ret.Get(1).(func(types.Context, types.AccAddress, types.ValAddress) bool); ok { + r1 = rf(ctx, delAddr, valAddr) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GetValidator provides a mock function with given fields: ctx, addr +func (_m *ObserverStakingKeeper) GetValidator(ctx types.Context, addr types.ValAddress) (stakingtypes.Validator, bool) { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for GetValidator") + } + + var r0 stakingtypes.Validator + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context, types.ValAddress) (stakingtypes.Validator, bool)); ok { + return rf(ctx, addr) + } + if rf, ok := ret.Get(0).(func(types.Context, types.ValAddress) stakingtypes.Validator); ok { + r0 = rf(ctx, addr) + } else { + r0 = ret.Get(0).(stakingtypes.Validator) + } + + if rf, ok := ret.Get(1).(func(types.Context, types.ValAddress) bool); ok { + r1 = rf(ctx, addr) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// SetValidator provides a mock function with given fields: ctx, validator +func (_m *ObserverStakingKeeper) SetValidator(ctx types.Context, validator stakingtypes.Validator) { + _m.Called(ctx, validator) +} + +// NewObserverStakingKeeper creates a new instance of ObserverStakingKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewObserverStakingKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *ObserverStakingKeeper { + mock := &ObserverStakingKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/observer.go b/testutil/keeper/observer.go index 946dac84c6..1a6dd2e257 100644 --- a/testutil/keeper/observer.go +++ b/testutil/keeper/observer.go @@ -3,20 +3,36 @@ package keeper import ( "testing" - slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" tmdb "github.com/tendermint/tm-db" + observermocks "github.com/zeta-chain/zetacore/testutil/keeper/mocks/observer" "github.com/zeta-chain/zetacore/x/observer/keeper" "github.com/zeta-chain/zetacore/x/observer/types" ) +// ObserverMockOptions represents options for instantiating an observer keeper with mocks +type ObserverMockOptions struct { + UseStakingMock bool + UseSlashingMock bool +} + +var ( + ObserverMocksAll = ObserverMockOptions{ + UseStakingMock: true, + UseSlashingMock: true, + } + ObserverNoMocks = ObserverMockOptions{} +) + func initObserverKeeper( cdc codec.Codec, db *tmdb.MemDB, @@ -40,8 +56,8 @@ func initObserverKeeper( ) } -// ObserverKeeper instantiates an observer keeper for testing purposes -func ObserverKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { +// ObserverKeeperWithMocks instantiates an observer keeper for testing purposes with the option to mock specific keepers +func ObserverKeeperWithMocks(t testing.TB, mockOptions ObserverMockOptions) (*keeper.Keeper, sdk.Context, SDKKeepers) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -66,16 +82,67 @@ func ObserverKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { // Add a proposer to the context ctx = sdkKeepers.InitBlockProposer(t, ctx) + // Initialize mocks for mocked keepers + var stakingKeeper types.StakingKeeper = sdkKeepers.StakingKeeper + var slashingKeeper types.SlashingKeeper = sdkKeepers.SlashingKeeper + if mockOptions.UseStakingMock { + stakingKeeper = observermocks.NewObserverStakingKeeper(t) + } + if mockOptions.UseSlashingMock { + slashingKeeper = observermocks.NewObserverSlashingKeeper(t) + } + k := keeper.NewKeeper( cdc, storeKey, memStoreKey, sdkKeepers.ParamsKeeper.Subspace(types.ModuleName), - sdkKeepers.StakingKeeper, - sdkKeepers.SlashingKeeper, + stakingKeeper, + slashingKeeper, ) k.SetParams(ctx, types.DefaultParams()) - return k, ctx + return k, ctx, sdkKeepers +} + +// ObserverKeeper instantiates an observer keeper for testing purposes +func ObserverKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, SDKKeepers) { + return ObserverKeeperWithMocks(t, ObserverNoMocks) +} + +// GetObserverStakingMock returns a new observer staking keeper mock +func GetObserverStakingMock(t testing.TB, keeper *keeper.Keeper) *ObserverMockStakingKeeper { + k, ok := keeper.GetStakingKeeper().(*observermocks.ObserverStakingKeeper) + require.True(t, ok) + return &ObserverMockStakingKeeper{ + ObserverStakingKeeper: k, + } +} + +// GetObserverSlashingMock returns a new observer slashing keeper mock +func GetObserverSlashingMock(t testing.TB, keeper *keeper.Keeper) *ObserverMockSlashingKeeper { + k, ok := keeper.GetSlashingKeeper().(*observermocks.ObserverSlashingKeeper) + require.True(t, ok) + return &ObserverMockSlashingKeeper{ + ObserverSlashingKeeper: k, + } +} + +// ObserverMockStakingKeeper is a wrapper of the observer staking keeper mock that add methods to mock the GetValidator method +type ObserverMockStakingKeeper struct { + *observermocks.ObserverStakingKeeper +} + +func (m *ObserverMockStakingKeeper) MockGetValidator(validator stakingtypes.Validator) { + m.On("GetValidator", mock.Anything, mock.Anything).Return(validator, true) +} + +// ObserverMockSlashingKeeper is a wrapper of the observer slashing keeper mock that add methods to mock the IsTombstoned method +type ObserverMockSlashingKeeper struct { + *observermocks.ObserverSlashingKeeper +} + +func (m *ObserverMockSlashingKeeper) MockIsTombstoned(isTombstoned bool) { + m.On("IsTombstoned", mock.Anything, mock.Anything).Return(isTombstoned) } diff --git a/testutil/network/network_setup.go b/testutil/network/network_setup.go index 08bc9277fd..987bb38fe5 100644 --- a/testutil/network/network_setup.go +++ b/testutil/network/network_setup.go @@ -528,6 +528,8 @@ func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, err if latestHeight >= h { return latestHeight, nil } + } else if err != nil { + fmt.Printf("error trying to fetch block height: %v\n", err) } } } diff --git a/testutil/sample/sample.go b/testutil/sample/sample.go index ba197ea932..020ff9104b 100644 --- a/testutil/sample/sample.go +++ b/testutil/sample/sample.go @@ -34,6 +34,11 @@ func newRandFromStringSeed(t *testing.T, s string) *rand.Rand { return newRandFromSeed(int64(h.Sum64())) } +// Rand returns a new random number generator +func Rand() *rand.Rand { + return newRandFromSeed(42) +} + // PubKey returns a sample account PubKey func PubKey(r *rand.Rand) cryptotypes.PubKey { seed := []byte(strconv.Itoa(r.Int())) diff --git a/x/crosschain/client/integrationtests/inbound_voter_test.go b/x/crosschain/client/integrationtests/inbound_voter_test.go index b838250745..50cf041a64 100644 --- a/x/crosschain/client/integrationtests/inbound_voter_test.go +++ b/x/crosschain/client/integrationtests/inbound_voter_test.go @@ -241,13 +241,10 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { s.Require().NoError(s.network.WaitForNBlocks(2)) out, err := clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, observercli.CmdListPendingNonces(), []string{"--output", "json"}) s.Require().NoError(err) - //fmt.Println(out.String()) out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, observercli.CmdGetSupportedChains(), []string{"--output", "json"}) s.Require().NoError(err) - //fmt.Println(out.String()) out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, crosschaincli.CmdListGasPrice(), []string{"--output", "json"}) s.Require().NoError(err) - //fmt.Println(out.String()) // Vote the inbound tx for _, val := range s.network.Validators { @@ -264,8 +261,9 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { message = message + "falseVote" } signedTx := BuildSignedInboundVote(s.T(), val, s.cfg.BondDenom, account, message, i) - out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, authcli.GetBroadcastCommand(), []string{signedTx.Name(), "--broadcast-mode", "sync"}) + out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, authcli.GetBroadcastCommand(), []string{signedTx.Name(), "--broadcast-mode", "block"}) s.Require().NoError(err) + fmt.Println(out.String()) } s.Require().NoError(s.network.WaitForNBlocks(2)) @@ -277,15 +275,15 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { s.NoError(broadcaster.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &ballot)) // Check the vote in the ballot - s.Assert().Equal(len(test.votes), len(ballot.Voters)) + s.Require().Equal(len(test.votes), len(ballot.Voters)) for _, vote := range ballot.Voters { if test.votes[vote.VoterAddress] == observerTypes.VoteType_FailureObservation { s.Assert().Equal(observerTypes.VoteType_NotYetVoted.String(), vote.VoteType.String()) continue } - s.Assert().Equal(test.votes[vote.VoterAddress].String(), vote.VoteType.String()) + s.Assert().Equal(test.votes[vote.VoterAddress].String(), vote.VoteType.String(), "incorrect vote for voter: %s", vote.VoterAddress) } - s.Assert().Equal(test.ballotResult.String(), ballot.BallotStatus.String()) + s.Require().Equal(test.ballotResult.String(), ballot.BallotStatus.String()) // Get the cctx and check its status cctxIdentifier := ballotIdentifier @@ -298,7 +296,7 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { s.Require().Contains(out.String(), "not found") } else { s.NoError(broadcaster.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &cctx)) - s.Assert().Equal(test.cctxStatus.String(), cctx.CrossChainTx.CctxStatus.Status.String(), cctx.CrossChainTx.CctxStatus.StatusMessage) + s.Require().Equal(test.cctxStatus.String(), cctx.CrossChainTx.CctxStatus.Status.String(), cctx.CrossChainTx.CctxStatus.StatusMessage) } }) } diff --git a/x/crosschain/client/integrationtests/suite.go b/x/crosschain/client/integrationtests/suite.go index 65a45bc4b3..b5f16a683d 100644 --- a/x/crosschain/client/integrationtests/suite.go +++ b/x/crosschain/client/integrationtests/suite.go @@ -52,7 +52,7 @@ func (s *IntegrationTestSuite) SetupSuite() { network.AddCrosschainData(s.T(), 0, s.cfg.GenesisState, s.cfg.Codec) network.AddObserverData(s.T(), 0, s.cfg.GenesisState, s.cfg.Codec, nil) net, err := network.New(s.T(), app.NodeDir, s.cfg) - s.Assert().NoError(err) + s.Require().NoError(err) s.network = net time.Sleep(3 * time.Second) _, err = s.network.WaitForHeight(1) diff --git a/x/crosschain/keeper/cctx.go b/x/crosschain/keeper/cctx.go index 7dad9f4197..d6fb9b117b 100644 --- a/x/crosschain/keeper/cctx.go +++ b/x/crosschain/keeper/cctx.go @@ -99,13 +99,21 @@ func (k Keeper) RemoveCrossChainTx(ctx sdk.Context, index string) { store.Delete(types.KeyPrefix(index)) } -func (k Keeper) CreateNewCCTX(ctx sdk.Context, msg *types.MsgVoteOnObservedInboundTx, index string, tssPubkey string, s types.CctxStatus, senderChain, receiverChain *common.Chain) types.CrossChainTx { +func (k Keeper) CreateNewCCTX( + ctx sdk.Context, + msg *types.MsgVoteOnObservedInboundTx, + index string, + tssPubkey string, + s types.CctxStatus, + senderChainID, + receiverChainID int64, +) types.CrossChainTx { if msg.TxOrigin == "" { msg.TxOrigin = msg.Sender } inboundParams := &types.InboundTxParams{ Sender: msg.Sender, - SenderChainId: senderChain.ChainId, + SenderChainId: senderChainID, TxOrigin: msg.TxOrigin, Asset: msg.Asset, Amount: msg.Amount, @@ -118,7 +126,7 @@ func (k Keeper) CreateNewCCTX(ctx sdk.Context, msg *types.MsgVoteOnObservedInbou outBoundParams := &types.OutboundTxParams{ Receiver: msg.Receiver, - ReceiverChainId: receiverChain.ChainId, + ReceiverChainId: receiverChainID, OutboundTxHash: "", OutboundTxTssNonce: 0, OutboundTxGasLimit: msg.GasLimit, diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index 50d8277f15..c3392e005e 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -22,7 +22,7 @@ func (k Keeper) HandleEVMDeposit( ctx sdk.Context, cctx *types.CrossChainTx, msg types.MsgVoteOnObservedInboundTx, - senderChain *common.Chain, + senderChainID int64, ) (bool, error) { to := ethcommon.HexToAddress(msg.Receiver) var ethTxHash ethcommon.Hash @@ -51,7 +51,7 @@ func (k Keeper) HandleEVMDeposit( to = parsedAddress } - from, err := senderChain.DecodeAddress(msg.Sender) + from, err := common.DecodeAddressFromChainID(senderChainID, msg.Sender) if err != nil { return false, fmt.Errorf("HandleEVMDeposit: unable to decode address: %s", err.Error()) } @@ -61,7 +61,7 @@ func (k Keeper) HandleEVMDeposit( from, to, msg.Amount.BigInt(), - senderChain, + senderChainID, data, msg.CoinType, msg.Asset, diff --git a/x/crosschain/keeper/evm_deposit_test.go b/x/crosschain/keeper/evm_deposit_test.go index 65483d89c1..dd6528e639 100644 --- a/x/crosschain/keeper/evm_deposit_test.go +++ b/x/crosschain/keeper/evm_deposit_test.go @@ -39,7 +39,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { Amount: math.NewUintFromBigInt(amount), CoinType: common.CoinType_Zeta, }, - nil, + 0, ) require.NoError(t, err) require.False(t, reverted) @@ -68,7 +68,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { Amount: math.NewUintFromBigInt(amount), CoinType: common.CoinType_Zeta, }, - nil, + 0, ) require.ErrorIs(t, err, errDeposit) require.False(t, reverted) @@ -80,7 +80,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -124,7 +124,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -169,7 +169,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -214,7 +214,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -258,7 +258,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -302,7 +302,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -343,7 +343,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) _, err := k.HandleEVMDeposit( ctx, @@ -366,7 +366,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -409,7 +409,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 998da57672..7eca564f3a 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -168,7 +168,15 @@ func (k Keeper) ProcessZRC20WithdrawalEvent(ctx sdk.Context, event *zrc20.ZRC20W ) sendHash := msg.Digest() - cctx := k.CreateNewCCTX(ctx, msg, sendHash, tss.TssPubkey, types.CctxStatus_PendingOutbound, &senderChain, receiverChain) + cctx := k.CreateNewCCTX( + ctx, + msg, + sendHash, + tss.TssPubkey, + types.CctxStatus_PendingOutbound, + senderChain.ChainId, + receiverChain.ChainId, + ) // Get gas price and amount gasprice, found := k.GetGasPrice(ctx, receiverChain.ChainId) @@ -208,7 +216,7 @@ func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *connectorzevm.ZetaC // Validation if we want to send ZETA to an external chain, but there is no ZETA token. chainParams, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, receiverChain.ChainId) if !found { - return types.ErrNotFoundChainParams + return observertypes.ErrChainParamsNotFound } if receiverChain.IsExternalChain() && chainParams.ZetaTokenContractAddress == "" { return types.ErrUnableToSendCoinType @@ -239,7 +247,15 @@ func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *connectorzevm.ZetaC sendHash := msg.Digest() // Create the CCTX - cctx := k.CreateNewCCTX(ctx, msg, sendHash, tss.TssPubkey, types.CctxStatus_PendingOutbound, &senderChain, receiverChain) + cctx := k.CreateNewCCTX( + ctx, + msg, + sendHash, + tss.TssPubkey, + types.CctxStatus_PendingOutbound, + senderChain.ChainId, + receiverChain.ChainId, + ) if err := k.PayGasAndUpdateCctx( ctx, diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 930bbd8421..db287e6b82 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -8,8 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" - observerKeeper "github.com/zeta-chain/zetacore/x/observer/keeper" - observerTypes "github.com/zeta-chain/zetacore/x/observer/types" ) // FIXME: use more specific error types & codes @@ -57,74 +55,60 @@ import ( // Only observer validators are authorized to broadcast this message. func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.MsgVoteOnObservedInboundTx) (*types.MsgVoteOnObservedInboundTxResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - - observationType := observerTypes.ObservationType_InBoundTx - if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { - return nil, types.ErrNotEnoughPermissions - } - - // GetChainFromChainID makes sure we are getting only supported chains , if a chain support has been turned on using gov proposal, this function returns nil - observationChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.SenderChainId) - if observationChain == nil { - return nil, cosmoserrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Observation %s", msg.SenderChainId, observationType.String())) - } - receiverChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.ReceiverChain) - if receiverChain == nil { - return nil, cosmoserrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Observation %s", msg.ReceiverChain, observationType.String())) - } - tssPub := "" - tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) - if tssFound { - tssPub = tss.TssPubkey - } - // IsAuthorized does various checks against the list of observer mappers - if ok := k.zetaObserverKeeper.IsAuthorized(ctx, msg.Creator); !ok { - return nil, observerTypes.ErrNotAuthorizedPolicy - } - index := msg.Digest() - // Add votes and Set Ballot - // GetBallot checks against the supported chains list before querying for Ballot - ballot, isNew, err := k.zetaObserverKeeper.FindBallot(ctx, index, observationChain, observationType) + + // vote on inbound ballot + // use a temporary context to not commit any ballot state change in case of error + tmpCtx, commit := ctx.CacheContext() + finalized, isNew, err := k.zetaObserverKeeper.VoteOnInboundBallot( + tmpCtx, + msg.SenderChainId, + msg.ReceiverChain, + msg.CoinType, + msg.Creator, + index, + msg.InTxHash, + ) if err != nil { return nil, err } + + // If it is a new ballot, check if an inbound with the same hash, sender chain and event index has already been finalized + // This may happen if the same inbound is observed twice where msg.Digest gives a different index + // This check prevents double spending if isNew { - // Check if the inbound has already been processed. - if k.IsFinalizedInbound(ctx, msg.InTxHash, msg.SenderChainId, msg.EventIndex) { - return nil, cosmoserrors.Wrap(types.ErrObservedTxAlreadyFinalized, fmt.Sprintf("InTxHash:%s, SenderChainID:%d, EventIndex:%d", msg.InTxHash, msg.SenderChainId, msg.EventIndex)) + if k.IsFinalizedInbound(tmpCtx, msg.InTxHash, msg.SenderChainId, msg.EventIndex) { + return nil, cosmoserrors.Wrap( + types.ErrObservedTxAlreadyFinalized, + fmt.Sprintf("InTxHash:%s, SenderChainID:%d, EventIndex:%d", msg.InTxHash, msg.SenderChainId, msg.EventIndex), + ) } - observerKeeper.EmitEventBallotCreated(ctx, ballot, msg.InTxHash, observationChain.String()) - } - // AddVoteToBallot adds a vote and sets the ballot - ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.VoteType_SuccessObservation) - if err != nil { - return nil, err } + commit() - _, isFinalizedInThisBlock := k.zetaObserverKeeper.CheckIfFinalizingVote(ctx, ballot) - if !isFinalizedInThisBlock { - // Return nil here to add vote to ballot and commit state + // If the ballot is not finalized return nil here to add vote to commit state + if !finalized { return &types.MsgVoteOnObservedInboundTxResponse{}, nil } - // Validation if we want to send ZETA to external chain, but there is no ZETA token. - if receiverChain.IsExternalChain() { - chainParams, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, receiverChain.ChainId) - if !found { - return nil, types.ErrNotFoundChainParams - } - if chainParams.ZetaTokenContractAddress == "" && msg.CoinType == common.CoinType_Zeta { - return nil, types.ErrUnableToSendCoinType - } + // get the latest TSS to set the TSS public key in the CCTX + tssPub := "" + tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) + if tssFound { + tssPub = tss.TssPubkey } - // ****************************************************************************** - // below only happens when ballot is finalized: exactly when threshold vote is in - // ****************************************************************************** + // create the CCTX + cctx := k.CreateNewCCTX( + ctx, + msg, + index, + tssPub, + types.CctxStatus_PendingInbound, + msg.SenderChainId, + msg.ReceiverChain, + ) - // Inbound Ballot has been finalized , Create CCTX - cctx := k.CreateNewCCTX(ctx, msg, index, tssPub, types.CctxStatus_PendingInbound, observationChain, receiverChain) defer func() { EmitEventInboundFinalized(ctx, &cctx) k.AddFinalizedInbound(ctx, msg.InTxHash, msg.SenderChainId, msg.EventIndex) @@ -134,11 +118,12 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg k.RemoveInTxTrackerIfExists(ctx, cctx.InboundTxParams.SenderChainId, cctx.InboundTxParams.InboundTxObservedHash) k.SetCctxAndNonceToCctxAndInTxHashToCctx(ctx, cctx) }() + // FinalizeInbound updates CCTX Prices and Nonce // Aborts is any of the updates fail - if receiverChain.IsZetaChain() { + if common.IsZetaChain(msg.ReceiverChain) { tmpCtx, commit := ctx.CacheContext() - isContractReverted, err := k.HandleEVMDeposit(tmpCtx, &cctx, *msg, observationChain) + isContractReverted, err := k.HandleEVMDeposit(tmpCtx, &cctx, *msg, msg.SenderChainId) if err != nil && !isContractReverted { // exceptional case; internal error; should abort CCTX cctx.CctxStatus.ChangeStatus(types.CctxStatus_Aborted, err.Error()) @@ -201,11 +186,11 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg } // Receiver is not ZetaChain: Cross Chain SWAP - tmpCtx, commit := ctx.CacheContext() + tmpCtx, commit = ctx.CacheContext() err = func() error { err := k.PayGasAndUpdateCctx( tmpCtx, - receiverChain.ChainId, + msg.ReceiverChain, &cctx, cctx.InboundTxParams.Amount, false, @@ -213,7 +198,7 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg if err != nil { return err } - return k.UpdateNonce(tmpCtx, receiverChain.ChainId, &cctx) + return k.UpdateNonce(tmpCtx, msg.ReceiverChain, &cctx) }() if err != nil { // do not commit anything here as the CCTX should be aborted diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go index 1605a37e33..803de84576 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "testing" - //"github.com/zeta-chain/zetacore/common" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -13,7 +12,6 @@ import ( "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/crosschain/keeper" "github.com/zeta-chain/zetacore/x/crosschain/types" - observerTypes "github.com/zeta-chain/zetacore/x/observer/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -35,6 +33,9 @@ func setObservers(t *testing.T, k *keeper.Keeper, ctx sdk.Context, zk keepertest }) return validatorAddressListFormatted } + +// TODO: Complete the test cases +// https://github.com/zeta-chain/node/issues/1542 func TestKeeper_VoteOnObservedInboundTx(t *testing.T) { t.Run("successfully vote on evm deposit", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) @@ -59,97 +60,100 @@ func TestKeeper_VoteOnObservedInboundTx(t *testing.T) { ) require.NoError(t, err) } - ballot, _, _ := zk.ObserverKeeper.FindBallot(ctx, msg.Digest(), zk.ObserverKeeper.GetSupportedChainFromChainID(ctx, msg.SenderChainId), observerTypes.ObservationType_InBoundTx) + ballot, _, _ := zk.ObserverKeeper.FindBallot( + ctx, + msg.Digest(), + zk.ObserverKeeper.GetSupportedChainFromChainID(ctx, msg.SenderChainId), + observertypes.ObservationType_InBoundTx, + ) require.Equal(t, ballot.BallotStatus, observertypes.BallotStatus_BallotFinalized_SuccessObservation) cctx, found := k.GetCrossChainTx(ctx, msg.Digest()) require.True(t, found) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) require.Equal(t, cctx.InboundTxParams.TxFinalizationStatus, types.TxFinalizationStatus_Executed) }) - // TODO : https://github.com/zeta-chain/node/issues/1542 -} -/* -Potential Double Event Submission -*/ -func TestNoDoubleEventProtections(t *testing.T) { - k, ctx, _, zk := keepertest.CrosschainKeeper(t) + t.Run("prevent double event submission", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) - // MsgServer for the crosschain keeper - msgServer := keeper.NewMsgServerImpl(*k) + // MsgServer for the crosschain keeper + msgServer := keeper.NewMsgServerImpl(*k) - // Set the chain ids we want to use to be valid - params := observertypes.DefaultParams() - zk.ObserverKeeper.SetParams( - ctx, params, - ) + // Set the chain ids we want to use to be valid + params := observertypes.DefaultParams() + zk.ObserverKeeper.SetParams( + ctx, params, + ) - // Convert the validator address into a user address. - validators := k.GetStakingKeeper().GetAllValidators(ctx) - validatorAddress := validators[0].OperatorAddress - valAddr, _ := sdk.ValAddressFromBech32(validatorAddress) - addresstmp, _ := sdk.AccAddressFromHexUnsafe(hex.EncodeToString(valAddr.Bytes())) - validatorAddr := addresstmp.String() + // Convert the validator address into a user address. + validators := k.GetStakingKeeper().GetAllValidators(ctx) + validatorAddress := validators[0].OperatorAddress + valAddr, _ := sdk.ValAddressFromBech32(validatorAddress) + addresstmp, _ := sdk.AccAddressFromHexUnsafe(hex.EncodeToString(valAddr.Bytes())) + validatorAddr := addresstmp.String() - // Add validator to the observer list for voting - zk.ObserverKeeper.SetObserverSet(ctx, observertypes.ObserverSet{ - ObserverList: []string{validatorAddr}, - }) + // Add validator to the observer list for voting + zk.ObserverKeeper.SetObserverSet(ctx, observertypes.ObserverSet{ + ObserverList: []string{validatorAddr}, + }) - // Vote on the FIRST message. - msg := &types.MsgVoteOnObservedInboundTx{ - Creator: validatorAddr, - Sender: "0x954598965C2aCdA2885B037561526260764095B8", - SenderChainId: 1337, // ETH - Receiver: "0x954598965C2aCdA2885B037561526260764095B8", - ReceiverChain: 101, // zetachain - Amount: sdkmath.NewUintFromString("10000000"), - Message: "", - InBlockHeight: 1, - GasLimit: 1000000000, - InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", - CoinType: 0, // zeta - TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", - Asset: "", - EventIndex: 1, - } - _, err := msgServer.VoteOnObservedInboundTx( - ctx, - msg, - ) - require.NoError(t, err) + // Vote on the FIRST message. + msg := &types.MsgVoteOnObservedInboundTx{ + Creator: validatorAddr, + Sender: "0x954598965C2aCdA2885B037561526260764095B8", + SenderChainId: 1337, // ETH + Receiver: "0x954598965C2aCdA2885B037561526260764095B8", + ReceiverChain: 101, // zetachain + Amount: sdkmath.NewUintFromString("10000000"), + Message: "", + InBlockHeight: 1, + GasLimit: 1000000000, + InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", + CoinType: 0, // zeta + TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", + Asset: "", + EventIndex: 1, + } + _, err := msgServer.VoteOnObservedInboundTx( + ctx, + msg, + ) + require.NoError(t, err) - // Check that the vote passed - ballot, found := zk.ObserverKeeper.GetBallot(ctx, msg.Digest()) - require.True(t, found) - require.Equal(t, ballot.BallotStatus, observertypes.BallotStatus_BallotFinalized_SuccessObservation) - //Perform the SAME event. Except, this time, we resubmit the event. - msg2 := &types.MsgVoteOnObservedInboundTx{ - Creator: validatorAddr, - Sender: "0x954598965C2aCdA2885B037561526260764095B8", - SenderChainId: 1337, - Receiver: "0x954598965C2aCdA2885B037561526260764095B8", - ReceiverChain: 101, - Amount: sdkmath.NewUintFromString("10000000"), - Message: "", - InBlockHeight: 1, - GasLimit: 1000000001, // <---- Change here - InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", - CoinType: 0, - TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", - Asset: "", - EventIndex: 1, - } + // Check that the vote passed + ballot, found := zk.ObserverKeeper.GetBallot(ctx, msg.Digest()) + require.True(t, found) + require.Equal(t, ballot.BallotStatus, observertypes.BallotStatus_BallotFinalized_SuccessObservation) + //Perform the SAME event. Except, this time, we resubmit the event. + msg2 := &types.MsgVoteOnObservedInboundTx{ + Creator: validatorAddr, + Sender: "0x954598965C2aCdA2885B037561526260764095B8", + SenderChainId: 1337, + Receiver: "0x954598965C2aCdA2885B037561526260764095B8", + ReceiverChain: 101, + Amount: sdkmath.NewUintFromString("10000000"), + Message: "", + InBlockHeight: 1, + GasLimit: 1000000001, // <---- Change here + InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", + CoinType: 0, + TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", + Asset: "", + EventIndex: 1, + } - _, err = msgServer.VoteOnObservedInboundTx( - ctx, - msg2, - ) - require.ErrorIs(t, err, types.ErrObservedTxAlreadyFinalized) - _, found = zk.ObserverKeeper.GetBallot(ctx, msg2.Digest()) - require.False(t, found) + _, err = msgServer.VoteOnObservedInboundTx( + ctx, + msg2, + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrObservedTxAlreadyFinalized) + _, found = zk.ObserverKeeper.GetBallot(ctx, msg2.Digest()) + require.False(t, found) + }) } -func TestStatus_StatusTransition(t *testing.T) { + +func TestStatus_ChangeStatus(t *testing.T) { tt := []struct { Name string Status types.Status diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index 72555df8cd..5c36a2abf9 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -14,8 +14,8 @@ import ( "github.com/rs/zerolog/log" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" - observerKeeper "github.com/zeta-chain/zetacore/x/observer/keeper" - observerTypes "github.com/zeta-chain/zetacore/x/observer/types" + observerkeeper "github.com/zeta-chain/zetacore/x/observer/keeper" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // VoteOnObservedOutboundTx casts a vote on an outbound transaction observed on a connected chain (after @@ -61,59 +61,45 @@ import ( // Only observer validators are authorized to broadcast this message. func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.MsgVoteOnObservedOutboundTx) (*types.MsgVoteOnObservedOutboundTxResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - observationType := observerTypes.ObservationType_OutBoundTx - // Observer Chain already checked then inbound is created - /* EDGE CASE : Params updated in during the finalization process - i.e Inbound has been finalized but outbound is still pending - */ - observationChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.OutTxChain) - if observationChain == nil { - return nil, observerTypes.ErrSupportedChains - } - err := observerTypes.CheckReceiveStatus(msg.Status) - if err != nil { - return nil, err - } - //Check is msg.Creator is authorized to vote - if ok := k.zetaObserverKeeper.IsAuthorized(ctx, msg.Creator); !ok { - return nil, observerTypes.ErrNotAuthorizedPolicy - } - // Check if CCTX exists + // check if CCTX exists and if the nonce matches cctx, found := k.GetCrossChainTx(ctx, msg.CctxHash) if !found { return nil, cosmoserrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("CCTX %s does not exist", msg.CctxHash)) } - if cctx.GetCurrentOutTxParam().OutboundTxTssNonce != msg.OutTxTssNonce { return nil, cosmoserrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("OutTxTssNonce %d does not match CCTX OutTxTssNonce %d", msg.OutTxTssNonce, cctx.GetCurrentOutTxParam().OutboundTxTssNonce)) } + // get ballot index ballotIndex := msg.Digest() - // Add votes and Set Ballot - ballot, isNew, err := k.zetaObserverKeeper.FindBallot(ctx, ballotIndex, observationChain, observationType) + + // vote on outbound ballot + isFinalized, isNew, ballot, observationChain, err := k.zetaObserverKeeper.VoteOnOutboundBallot( + ctx, + ballotIndex, + msg.OutTxChain, + msg.Status, + msg.Creator) if err != nil { return nil, err } + + // if the ballot is new, set the index to the CCTX if isNew { - observerKeeper.EmitEventBallotCreated(ctx, ballot, msg.ObservedOutTxHash, observationChain.String()) + observerkeeper.EmitEventBallotCreated(ctx, ballot, msg.ObservedOutTxHash, observationChain) // Set this the first time when the ballot is created // The ballot might change if there are more votes in a different outbound ballot for this cctx hash cctx.GetCurrentOutTxParam().OutboundTxBallotIndex = ballotIndex - //k.SetCctxAndNonceToCctxAndInTxHashToCctx(ctx, cctx) - } - // AddVoteToBallot adds a vote and sets the ballot - ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.ConvertReceiveStatusToVoteType(msg.Status)) - if err != nil { - return nil, err } - ballot, isFinalizedInThisBlock := k.zetaObserverKeeper.CheckIfFinalizingVote(ctx, ballot) - if !isFinalizedInThisBlock { - // Return nil here to add vote to ballot and commit state + // if not finalized commit state here + if !isFinalized { return &types.MsgVoteOnObservedOutboundTxResponse{}, nil } - if ballot.BallotStatus != observerTypes.BallotStatus_BallotFinalized_FailureObservation { + + // if ballot successful, the value received should be the out tx amount + if ballot.BallotStatus != observertypes.BallotStatus_BallotFinalized_FailureObservation { if !msg.ValueReceived.Equal(cctx.GetCurrentOutTxParam().Amount) { log.Error().Msgf("VoteOnObservedOutboundTx: Mint mismatch: %s value received vs %s cctx amount", msg.ValueReceived, @@ -141,15 +127,12 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms return nil, types.ErrCannotFindTSSKeys } - // FinalizeOutbound sets final status for a successful vote - // FinalizeOutbound updates CCTX Prices and Nonce for a revert - tmpCtx, commit := ctx.CacheContext() err = func() error { //err = FinalizeOutbound(k, ctx, &cctx, msg, ballot.BallotStatus) cctx.GetCurrentOutTxParam().OutboundTxObservedExternalHeight = msg.ObservedOutTxBlockHeight oldStatus := cctx.CctxStatus.Status switch ballot.BallotStatus { - case observerTypes.BallotStatus_BallotFinalized_SuccessObservation: + case observertypes.BallotStatus_BallotFinalized_SuccessObservation: switch oldStatus { case types.CctxStatus_PendingRevert: cctx.CctxStatus.ChangeStatus(types.CctxStatus_Reverted, "") @@ -158,7 +141,7 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms } newStatus := cctx.CctxStatus.Status.String() EmitOutboundSuccess(tmpCtx, msg, oldStatus.String(), newStatus, cctx) - case observerTypes.BallotStatus_BallotFinalized_FailureObservation: + case observertypes.BallotStatus_BallotFinalized_FailureObservation: if msg.CoinType == common.CoinType_Cmd || common.IsZetaChain(cctx.InboundTxParams.SenderChainId) { // if the cctx is of coin type cmd or the sender chain is zeta chain, then we do not revert, the cctx is aborted cctx.CctxStatus.ChangeStatus(types.CctxStatus_Aborted, "") diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index 680e9b6096..2c5a595eb5 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -7,7 +7,6 @@ import ( var ( ErrUnsupportedChain = errorsmod.Register(ModuleName, 1102, "chain parse error") ErrInvalidChainID = errorsmod.Register(ModuleName, 1101, "chain id cannot be negative") - ErrInvalidPubKeySet = errorsmod.Register(ModuleName, 1106, "invalid pubkeyset") ErrUnableToGetGasPrice = errorsmod.Register(ModuleName, 1107, "unable to get gas price") ErrNotEnoughZetaBurnt = errorsmod.Register(ModuleName, 1109, "not enough zeta burnt") ErrCannotFindReceiverNonce = errorsmod.Register(ModuleName, 1110, "cannot find receiver chain nonce") @@ -15,11 +14,9 @@ var ( ErrUnableToParseAddress = errorsmod.Register(ModuleName, 1115, "cannot parse address and data") ErrCannotProcessWithdrawal = errorsmod.Register(ModuleName, 1116, "cannot process withdrawal event") ErrForeignCoinNotFound = errorsmod.Register(ModuleName, 1118, "foreign coin not found for sender chain") - ErrNotEnoughPermissions = errorsmod.Register(ModuleName, 1119, "not enough permissions for current actions") ErrCannotFindPendingNonces = errorsmod.Register(ModuleName, 1121, "cannot find pending nonces") ErrCannotFindTSSKeys = errorsmod.Register(ModuleName, 1122, "cannot find TSS keys") ErrNonceMismatch = errorsmod.Register(ModuleName, 1123, "nonce mismatch") - ErrNotFoundChainParams = errorsmod.Register(ModuleName, 1126, "not found chain chain params") ErrUnableToSendCoinType = errorsmod.Register(ModuleName, 1127, "unable to send this coin type to a receiver chain") ErrInvalidAddress = errorsmod.Register(ModuleName, 1128, "invalid address") ErrDeployContract = errorsmod.Register(ModuleName, 1129, "unable to deploy contract") @@ -44,6 +41,4 @@ var ( ErrUnableProcessRefund = errorsmod.Register(ModuleName, 1148, "unable to process refund") ErrUnableToFindZetaAccounting = errorsmod.Register(ModuleName, 1149, "unable to find zeta accounting") ErrInsufficientZetaAmount = errorsmod.Register(ModuleName, 1150, "insufficient zeta amount") - - ErrProcessingZRC20Withdrawal = errorsmod.Register(ModuleName, 1151, "error processing zrc20 withdrawal") ) diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 540600b73b..d8d73487fa 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -72,6 +72,22 @@ type ObserverKeeper interface { SetTssAndUpdateNonce(ctx sdk.Context, tss observertypes.TSS) RemoveFromPendingNonces(ctx sdk.Context, tss string, chainID int64, nonce int64) GetAllNonceToCctx(ctx sdk.Context) (list []observertypes.NonceToCctx) + VoteOnInboundBallot( + ctx sdk.Context, + senderChainID int64, + receiverChainID int64, + coinType common.CoinType, + voter string, + ballotIndex string, + inTxHash string, + ) (bool, bool, error) + VoteOnOutboundBallot( + ctx sdk.Context, + ballotIndex string, + outTxChainID int64, + receiveStatus common.ReceiveStatus, + voter string, + ) (bool, bool, observertypes.Ballot, string, error) GetSupportedChainFromChainID(ctx sdk.Context, chainID int64) *common.Chain GetSupportedChains(ctx sdk.Context) []*common.Chain } @@ -103,7 +119,7 @@ type FungibleKeeper interface { from []byte, to eth.Address, amount *big.Int, - senderChain *common.Chain, + senderChainID int64, data []byte, coinType common.CoinType, asset string, diff --git a/x/fungible/keeper/deposits.go b/x/fungible/keeper/deposits.go index 832613aa2c..3d8ebfb28b 100644 --- a/x/fungible/keeper/deposits.go +++ b/x/fungible/keeper/deposits.go @@ -26,7 +26,7 @@ func (k Keeper) ZRC20DepositAndCallContract( from []byte, to eth.Address, amount *big.Int, - senderChain *common.Chain, + senderChainID int64, data []byte, coinType common.CoinType, asset string, @@ -37,12 +37,12 @@ func (k Keeper) ZRC20DepositAndCallContract( // get foreign coin if coinType == common.CoinType_Gas { - coin, found = k.GetGasCoinForForeignCoin(ctx, senderChain.ChainId) + coin, found = k.GetGasCoinForForeignCoin(ctx, senderChainID) if !found { return nil, false, crosschaintypes.ErrGasCoinNotFound } } else { - coin, found = k.GetForeignCoinFromAsset(ctx, asset, senderChain.ChainId) + coin, found = k.GetForeignCoinFromAsset(ctx, asset, senderChainID) if !found { return nil, false, crosschaintypes.ErrForeignCoinNotFound } @@ -75,7 +75,7 @@ func (k Keeper) ZRC20DepositAndCallContract( context := systemcontract.ZContext{ Origin: from, Sender: eth.Address{}, - ChainID: big.NewInt(senderChain.ChainId), + ChainID: big.NewInt(senderChainID), } res, err := k.DepositZRC20AndCallContract(ctx, context, ZRC20Contract, to, amount, data) return res, true, err diff --git a/x/fungible/keeper/deposits_test.go b/x/fungible/keeper/deposits_test.go index 4131a1e00d..bfd3f7eecd 100644 --- a/x/fungible/keeper/deposits_test.go +++ b/x/fungible/keeper/deposits_test.go @@ -22,11 +22,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // deposit to := sample.EthAddress() @@ -53,12 +53,12 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId assetAddress := sample.EthAddress().String() // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", assetAddress, "foobar") + zrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", assetAddress, "foobar") // deposit to := sample.EthAddress() @@ -85,12 +85,12 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId assetAddress := sample.EthAddress().String() // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", assetAddress, "foobar") + deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", assetAddress, "foobar") // deposit to := sample.EthAddress() @@ -113,11 +113,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // there is an initial total supply minted during gas pool setup initialTotalSupply, err := k.TotalSupplyZRC4(ctx, zrc20) @@ -159,11 +159,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // pause the coin coin, found := k.GetForeignCoins(ctx, zrc20.String()) @@ -191,11 +191,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // there is an initial total supply minted during gas pool setup initialTotalSupply, err := k.TotalSupplyZRC4(ctx, zrc20) @@ -232,7 +232,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) @@ -257,7 +257,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId assetAddress := sample.EthAddress().String() // deploy the system contracts @@ -284,11 +284,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") example, err := k.DeployContract(ctx, contracts.ExampleMetaData) require.NoError(t, err) @@ -322,11 +322,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") reverter, err := k.DeployContract(ctx, contracts.ReverterMetaData) require.NoError(t, err) diff --git a/x/observer/genesis_test.go b/x/observer/genesis_test.go index 56cc00352f..73193de928 100644 --- a/x/observer/genesis_test.go +++ b/x/observer/genesis_test.go @@ -44,7 +44,7 @@ func TestGenesis(t *testing.T) { } // Init and export - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) observer.InitGenesis(ctx, *k, genesisState) got := observer.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/observer/keeper/chain_nonces_test.go b/x/observer/keeper/chain_nonces_test.go index 0c511d3f79..d133c7fc53 100644 --- a/x/observer/keeper/chain_nonces_test.go +++ b/x/observer/keeper/chain_nonces_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetChainNonces(t *testing.T) { t.Run("Get chain nonces", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainNoncesList := sample.ChainNoncesList(t, 10) for _, n := range chainNoncesList { k.SetChainNonces(ctx, n) @@ -22,7 +22,7 @@ func TestKeeper_GetChainNonces(t *testing.T) { } }) t.Run("Get chain nonces not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainNoncesList := sample.ChainNoncesList(t, 10) for _, n := range chainNoncesList { k.SetChainNonces(ctx, n) @@ -31,7 +31,7 @@ func TestKeeper_GetChainNonces(t *testing.T) { require.False(t, found) }) t.Run("Get all chain nonces", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainNoncesList := sample.ChainNoncesList(t, 10) for _, n := range chainNoncesList { k.SetChainNonces(ctx, n) diff --git a/x/observer/keeper/chain_params_test.go b/x/observer/keeper/chain_params_test.go index c9142b7526..9bf63cd65b 100644 --- a/x/observer/keeper/chain_params_test.go +++ b/x/observer/keeper/chain_params_test.go @@ -12,7 +12,7 @@ import ( func TestKeeper_GetSupportedChainFromChainID(t *testing.T) { t.Run("return nil if chain not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) // no core params list require.Nil(t, k.GetSupportedChainFromChainID(ctx, getValidEthChainIDWithIndex(t, 0))) @@ -31,7 +31,7 @@ func TestKeeper_GetSupportedChainFromChainID(t *testing.T) { }) t.Run("return chain if chain found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainID := getValidEthChainIDWithIndex(t, 0) setSupportedChain(ctx, *k, getValidEthChainIDWithIndex(t, 1), chainID) chain := k.GetSupportedChainFromChainID(ctx, chainID) @@ -42,12 +42,12 @@ func TestKeeper_GetSupportedChainFromChainID(t *testing.T) { func TestKeeper_GetSupportedChains(t *testing.T) { t.Run("return empty list if no core params list", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) require.Empty(t, k.GetSupportedChains(ctx)) }) t.Run("return list containing supported chains", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) require.Greater(t, len(common.ExternalChainList()), 5) supported1 := common.ExternalChainList()[0] diff --git a/x/observer/keeper/grpc_query_blame_test.go b/x/observer/keeper/grpc_query_blame_test.go index 8e66e3a2da..141246af47 100644 --- a/x/observer/keeper/grpc_query_blame_test.go +++ b/x/observer/keeper/grpc_query_blame_test.go @@ -12,7 +12,7 @@ import ( ) func TestKeeper_BlameByIdentifier(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) var chainId int64 = 97 var nonce uint64 = 101 digest := "85f5e10431f69bc2a14046a13aabaefc660103b6de7a84f75c4b96181d03f0b5" @@ -31,7 +31,7 @@ func TestKeeper_BlameByIdentifier(t *testing.T) { } func TestKeeper_BlameByChainAndNonce(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) var chainId int64 = 97 var nonce uint64 = 101 digest := "85f5e10431f69bc2a14046a13aabaefc660103b6de7a84f75c4b96181d03f0b5" @@ -52,7 +52,7 @@ func TestKeeper_BlameByChainAndNonce(t *testing.T) { func TestKeeper_BlameAll(t *testing.T) { t.Run("GetBlameRecord by limit ", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) blameList := sample.BlameRecordsList(t, 10) for _, record := range blameList { k.SetBlame(ctx, record) @@ -69,7 +69,7 @@ func TestKeeper_BlameAll(t *testing.T) { require.Equal(t, len(blameList), int(pageRes.Total)) }) t.Run("GetBlameRecord by offset ", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) blameList := sample.BlameRecordsList(t, 20) offset := 10 for _, record := range blameList { @@ -88,7 +88,7 @@ func TestKeeper_BlameAll(t *testing.T) { require.Equal(t, len(blameList), int(pageRes.Total)) }) t.Run("GetAllBlameRecord", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) blameList := sample.BlameRecordsList(t, 100) for _, record := range blameList { k.SetBlame(ctx, record) @@ -103,7 +103,7 @@ func TestKeeper_BlameAll(t *testing.T) { require.Equal(t, blameList, rst) }) t.Run("Get no records if nothing is set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) rst := k.GetAllBlame(ctx) require.Len(t, rst, 0) }) diff --git a/x/observer/keeper/grpc_query_nonces_test.go b/x/observer/keeper/grpc_query_nonces_test.go index 6ec06efd28..74f4777b5d 100644 --- a/x/observer/keeper/grpc_query_nonces_test.go +++ b/x/observer/keeper/grpc_query_nonces_test.go @@ -14,7 +14,7 @@ import ( ) func TestChainNoncesQuerySingle(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) chainNonces := sample.ChainNoncesList(t, 2) for _, nonce := range chainNonces { @@ -59,7 +59,7 @@ func TestChainNoncesQuerySingle(t *testing.T) { } func TestChainNoncesQueryPaginated(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) chainNonces := sample.ChainNoncesList(t, 5) for _, nonce := range chainNonces { diff --git a/x/observer/keeper/msg_server_add_block_header_test.go b/x/observer/keeper/msg_server_add_block_header_test.go index 34bde9c115..85a7ec505b 100644 --- a/x/observer/keeper/msg_server_add_block_header_test.go +++ b/x/observer/keeper/msg_server_add_block_header_test.go @@ -149,7 +149,7 @@ func TestMsgServer_AddBlockHeader(t *testing.T) { } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) k.SetObserverSet(ctx, types.ObserverSet{ ObserverList: []string{observerAddress.String()}, diff --git a/x/observer/keeper/msg_server_remove_chain_params_test.go b/x/observer/keeper/msg_server_remove_chain_params_test.go index d99b301403..238f86e256 100644 --- a/x/observer/keeper/msg_server_remove_chain_params_test.go +++ b/x/observer/keeper/msg_server_remove_chain_params_test.go @@ -14,7 +14,7 @@ import ( func TestMsgServer_RemoveChainParams(t *testing.T) { t.Run("can update chain params", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) chain1 := common.ExternalChainList()[0].ChainId @@ -75,7 +75,7 @@ func TestMsgServer_RemoveChainParams(t *testing.T) { }) t.Run("cannot remove chain params if not authorized", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) _, err := srv.UpdateChainParams(sdk.WrapSDKContext(ctx), &types.MsgUpdateChainParams{ @@ -97,7 +97,7 @@ func TestMsgServer_RemoveChainParams(t *testing.T) { }) t.Run("cannot remove if chain ID not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // set admin diff --git a/x/observer/keeper/msg_server_test.go b/x/observer/keeper/msg_server_test.go deleted file mode 100644 index 98911a5a70..0000000000 --- a/x/observer/keeper/msg_server_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package keeper - -import ( - "context" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/x/observer/types" -) - -func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { - k, ctx := SetupKeeper(t) - return NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) -} diff --git a/x/observer/keeper/msg_server_update_chain_params_test.go b/x/observer/keeper/msg_server_update_chain_params_test.go index 7b2f8a5cb3..1e4a707aeb 100644 --- a/x/observer/keeper/msg_server_update_chain_params_test.go +++ b/x/observer/keeper/msg_server_update_chain_params_test.go @@ -14,7 +14,7 @@ import ( func TestMsgServer_UpdateChainParams(t *testing.T) { t.Run("can update chain params", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) chain1 := common.ExternalChainList()[0].ChainId @@ -92,7 +92,7 @@ func TestMsgServer_UpdateChainParams(t *testing.T) { }) t.Run("cannot update chain params if not authorized", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) _, err := srv.UpdateChainParams(sdk.WrapSDKContext(ctx), &types.MsgUpdateChainParams{ diff --git a/x/observer/keeper/msg_server_update_crosschain_flags_test.go b/x/observer/keeper/msg_server_update_crosschain_flags_test.go index 555988694c..aaa33f4e23 100644 --- a/x/observer/keeper/msg_server_update_crosschain_flags_test.go +++ b/x/observer/keeper/msg_server_update_crosschain_flags_test.go @@ -26,7 +26,7 @@ func setAdminCrossChainFlags(ctx sdk.Context, k *keeper.Keeper, admin string, gr func TestMsgServer_UpdateCrosschainFlags(t *testing.T) { t.Run("can update crosschain flags", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) admin := sample.AccAddress() @@ -157,7 +157,7 @@ func TestMsgServer_UpdateCrosschainFlags(t *testing.T) { }) t.Run("cannot update crosschain flags if not authorized", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) _, err := srv.UpdateCrosschainFlags(sdk.WrapSDKContext(ctx), &types.MsgUpdateCrosschainFlags{ diff --git a/x/observer/keeper/msg_server_update_observer_test.go b/x/observer/keeper/msg_server_update_observer_test.go index 7fe73734d8..679e9851c5 100644 --- a/x/observer/keeper/msg_server_update_observer_test.go +++ b/x/observer/keeper/msg_server_update_observer_test.go @@ -17,7 +17,7 @@ import ( func TestMsgServer_UpdateObserver(t *testing.T) { t.Run("successfully update tombstoned observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -73,7 +73,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { }) t.Run("unable to update to a non validator address", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -122,7 +122,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { }) t.Run("unable to update tombstoned validator with with non operator account", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -173,7 +173,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrUpdateObserver) }) t.Run("unable to update non-tombstoned observer with update reason tombstoned", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -223,7 +223,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrUpdateObserver) }) t.Run("unable to update observer with no node account", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -269,7 +269,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrNodeAccountNotFound) }) t.Run("unable to update observer when last observer count is missing", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -314,7 +314,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrLastObserverCountNotFound) }) t.Run("update observer using admin policy", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) admin := sample.AccAddress() @@ -369,7 +369,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.Equal(t, newOperatorAddress.String(), acc.Operator) }) t.Run("fail to update observer using regular account and update type admin", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here diff --git a/x/observer/keeper/nonce_to_cctx_test.go b/x/observer/keeper/nonce_to_cctx_test.go index 1cbd023fbd..75df5eb311 100644 --- a/x/observer/keeper/nonce_to_cctx_test.go +++ b/x/observer/keeper/nonce_to_cctx_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetNonceToCctx(t *testing.T) { t.Run("Get nonce to cctx", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonceToCctxList := sample.NonceToCctxList(t, "sample", 1) for _, n := range nonceToCctxList { k.SetNonceToCctx(ctx, n) @@ -22,7 +22,7 @@ func TestKeeper_GetNonceToCctx(t *testing.T) { } }) t.Run("Get nonce to cctx not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonceToCctxList := sample.NonceToCctxList(t, "sample", 1) for _, n := range nonceToCctxList { k.SetNonceToCctx(ctx, n) @@ -31,7 +31,7 @@ func TestKeeper_GetNonceToCctx(t *testing.T) { require.False(t, found) }) t.Run("Get all nonce to cctx", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonceToCctxList := sample.NonceToCctxList(t, "sample", 10) for _, n := range nonceToCctxList { k.SetNonceToCctx(ctx, n) diff --git a/x/observer/keeper/nonces_test.go b/x/observer/keeper/nonces_test.go index aaf7369df1..aa972eab3e 100644 --- a/x/observer/keeper/nonces_test.go +++ b/x/observer/keeper/nonces_test.go @@ -9,7 +9,7 @@ import ( ) func TestChainNoncesGet(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) items := sample.ChainNoncesList(t, 10) for _, item := range items { k.SetChainNonces(ctx, item) @@ -21,7 +21,7 @@ func TestChainNoncesGet(t *testing.T) { } } func TestChainNoncesRemove(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) items := sample.ChainNoncesList(t, 10) for _, item := range items { k.SetChainNonces(ctx, item) @@ -34,7 +34,7 @@ func TestChainNoncesRemove(t *testing.T) { } func TestChainNoncesGetAll(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) items := sample.ChainNoncesList(t, 10) for _, item := range items { k.SetChainNonces(ctx, item) diff --git a/x/observer/keeper/observer_set_test.go b/x/observer/keeper/observer_set_test.go index ca7212704a..6561405acf 100644 --- a/x/observer/keeper/observer_set_test.go +++ b/x/observer/keeper/observer_set_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetObserverSet(t *testing.T) { t.Run("get observer set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) tfm, found := k.GetObserverSet(ctx) @@ -21,7 +21,7 @@ func TestKeeper_GetObserverSet(t *testing.T) { func TestKeeper_IsAddressPartOfObserverSet(t *testing.T) { t.Run("address is part of observer set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) require.True(t, k.IsAddressPartOfObserverSet(ctx, os.ObserverList[0])) @@ -31,7 +31,7 @@ func TestKeeper_IsAddressPartOfObserverSet(t *testing.T) { func TestKeeper_AddObserverToSet(t *testing.T) { t.Run("add observer to set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) newObserver := sample.AccAddress() @@ -46,7 +46,7 @@ func TestKeeper_AddObserverToSet(t *testing.T) { func TestKeeper_RemoveObserverFromSet(t *testing.T) { t.Run("remove observer from set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) k.RemoveObserverFromSet(ctx, os.ObserverList[0]) @@ -59,7 +59,7 @@ func TestKeeper_RemoveObserverFromSet(t *testing.T) { func TestKeeper_UpdateObserverAddress(t *testing.T) { t.Run("update observer address", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) oldObserverAddress := sample.AccAddress() newObserverAddress := sample.AccAddress() observerSet := sample.ObserverSet(10) @@ -72,7 +72,7 @@ func TestKeeper_UpdateObserverAddress(t *testing.T) { require.Equal(t, newObserverAddress, observerSet.ObserverList[len(observerSet.ObserverList)-1]) }) t.Run("update observer address long observerList", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) oldObserverAddress := sample.AccAddress() newObserverAddress := sample.AccAddress() observerSet := sample.ObserverSet(10000) @@ -85,7 +85,7 @@ func TestKeeper_UpdateObserverAddress(t *testing.T) { require.Equal(t, newObserverAddress, observerMappers.ObserverList[len(observerMappers.ObserverList)-1]) }) t.Run("update observer address short observerList", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) oldObserverAddress := sample.AccAddress() newObserverAddress := sample.AccAddress() observerSet := sample.ObserverSet(1) diff --git a/x/observer/keeper/pending_nonces_test.go b/x/observer/keeper/pending_nonces_test.go index 0cb61f090d..8ada35c5ba 100644 --- a/x/observer/keeper/pending_nonces_test.go +++ b/x/observer/keeper/pending_nonces_test.go @@ -12,7 +12,7 @@ import ( func TestKeeper_PendingNoncesAll(t *testing.T) { t.Run("Get all pending nonces paginated by limit", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonces := sample.PendingNoncesList(t, "sample", 10) sort.SliceStable(nonces, func(i, j int) bool { return nonces[i].ChainId < nonces[j].ChainId @@ -29,7 +29,7 @@ func TestKeeper_PendingNoncesAll(t *testing.T) { require.Equal(t, len(nonces), int(pageRes.Total)) }) t.Run("Get all pending nonces paginated by offset", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonces := sample.PendingNoncesList(t, "sample", 42) sort.SliceStable(nonces, func(i, j int) bool { return nonces[i].ChainId < nonces[j].ChainId @@ -48,7 +48,7 @@ func TestKeeper_PendingNoncesAll(t *testing.T) { require.Equal(t, len(nonces), int(pageRes.Total)) }) t.Run("Get all pending nonces ", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonces := sample.PendingNoncesList(t, "sample", 10) sort.SliceStable(nonces, func(i, j int) bool { return nonces[i].ChainId < nonces[j].ChainId diff --git a/x/observer/keeper/tss_funds_migrator_test.go b/x/observer/keeper/tss_funds_migrator_test.go index 62d622e179..46c8891ef9 100644 --- a/x/observer/keeper/tss_funds_migrator_test.go +++ b/x/observer/keeper/tss_funds_migrator_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetTssFundMigrator(t *testing.T) { t.Run("Successfully set funds migrator for chain", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chain := sample.TssFundsMigrator(1) k.SetFundMigrator(ctx, chain) tfm, found := k.GetFundMigrator(ctx, chain.ChainId) @@ -18,7 +18,7 @@ func TestKeeper_GetTssFundMigrator(t *testing.T) { require.Equal(t, chain, tfm) }) t.Run("Verify only one migrator can be created for a chain", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tfm1 := sample.TssFundsMigrator(1) k.SetFundMigrator(ctx, tfm1) tfm2 := tfm1 diff --git a/x/observer/keeper/tss_test.go b/x/observer/keeper/tss_test.go index 0af8bae7f3..06f763e4d3 100644 --- a/x/observer/keeper/tss_test.go +++ b/x/observer/keeper/tss_test.go @@ -17,7 +17,7 @@ import ( ) func TestTSSGet(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tss := sample.Tss() k.SetTSS(ctx, tss) tssQueried, found := k.GetTSS(ctx) @@ -26,7 +26,7 @@ func TestTSSGet(t *testing.T) { } func TestTSSRemove(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tss := sample.Tss() k.SetTSS(ctx, tss) k.RemoveTSS(ctx) @@ -35,7 +35,7 @@ func TestTSSRemove(t *testing.T) { } func TestTSSQuerySingle(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) //msgs := createTSS(keeper, ctx, 1) tss := sample.Tss() @@ -69,7 +69,7 @@ func TestTSSQuerySingle(t *testing.T) { } func TestTSSQueryHistory(t *testing.T) { - keeper, ctx := keepertest.ObserverKeeper(t) + keeper, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) for _, tc := range []struct { desc string @@ -115,7 +115,7 @@ func TestTSSQueryHistory(t *testing.T) { func TestKeeper_TssHistory(t *testing.T) { t.Run("Get tss history paginated by limit", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(10) for _, tss := range tssList { k.SetTSSHistory(ctx, tss) @@ -132,7 +132,7 @@ func TestKeeper_TssHistory(t *testing.T) { require.Equal(t, len(tssList), int(pageRes.Total)) }) t.Run("Get tss history paginated by offset", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(100) offset := 20 for _, tss := range tssList { @@ -151,7 +151,7 @@ func TestKeeper_TssHistory(t *testing.T) { require.Equal(t, len(tssList), int(pageRes.Total)) }) t.Run("Get all TSS without pagination", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(100) for _, tss := range tssList { k.SetTSSHistory(ctx, tss) @@ -166,7 +166,7 @@ func TestKeeper_TssHistory(t *testing.T) { require.Equal(t, tssList, rst) }) t.Run("Get historical TSS", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(100) for _, tss := range tssList { k.SetTSSHistory(ctx, tss) diff --git a/x/observer/keeper/utils_test.go b/x/observer/keeper/utils_test.go index 6534fc6200..e68fb2edd7 100644 --- a/x/observer/keeper/utils_test.go +++ b/x/observer/keeper/utils_test.go @@ -43,7 +43,7 @@ func getValidEthChainIDWithIndex(t *testing.T, index int) int64 { func TestKeeper_IsAuthorized(t *testing.T) { t.Run("authorized observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) r := rand.New(rand.NewSource(9)) @@ -69,7 +69,7 @@ func TestKeeper_IsAuthorized(t *testing.T) { }) t.Run("not authorized for tombstoned observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) r := rand.New(rand.NewSource(9)) @@ -95,7 +95,7 @@ func TestKeeper_IsAuthorized(t *testing.T) { }) t.Run("not authorized for non-validator observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) r := rand.New(rand.NewSource(9)) diff --git a/x/observer/keeper/vote_inbound.go b/x/observer/keeper/vote_inbound.go new file mode 100644 index 0000000000..5bc70ede88 --- /dev/null +++ b/x/observer/keeper/vote_inbound.go @@ -0,0 +1,84 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +// VoteOnInboundBallot casts a vote on an inbound transaction observed on a connected chain. If this +// is the first vote, a new ballot is created. When a threshold of votes is +// reached, the ballot is finalized. +func (k Keeper) VoteOnInboundBallot( + ctx sdk.Context, + senderChainID int64, + receiverChainID int64, + coinType common.CoinType, + voter string, + ballotIndex string, + inTxHash string, +) (bool, bool, error) { + if !k.IsInboundEnabled(ctx) { + return false, false, types.ErrInboundDisabled + } + + // makes sure we are getting only supported chains + // if a chain support has been turned on using gov proposal + // this function returns nil + senderChain := k.GetSupportedChainFromChainID(ctx, senderChainID) + if senderChain == nil { + return false, false, sdkerrors.Wrap(types.ErrSupportedChains, fmt.Sprintf( + "ChainID %d, Observation %s", + senderChainID, + types.ObservationType_InBoundTx.String()), + ) + } + + // checks the voter is authorized to vote on the observation chain + if ok := k.IsAuthorized(ctx, voter); !ok { + return false, false, types.ErrNotObserver + } + + // makes sure we are getting only supported chains + receiverChain := k.GetSupportedChainFromChainID(ctx, receiverChainID) + if receiverChain == nil { + return false, false, sdkerrors.Wrap(types.ErrSupportedChains, fmt.Sprintf( + "ChainID %d, Observation %s", + receiverChainID, + types.ObservationType_InBoundTx.String()), + ) + } + + // check if we want to send ZETA to external chain, but there is no ZETA token. + if receiverChain.IsExternalChain() { + coreParams, found := k.GetChainParamsByChainID(ctx, receiverChain.ChainId) + if !found { + return false, false, types.ErrChainParamsNotFound + } + if coreParams.ZetaTokenContractAddress == "" && coinType == common.CoinType_Zeta { + return false, false, types.ErrInvalidZetaCoinTypes + } + } + + // checks against the supported chains list before querying for Ballot + ballot, isNew, err := k.FindBallot(ctx, ballotIndex, senderChain, types.ObservationType_InBoundTx) + if err != nil { + return false, false, err + } + if isNew { + EmitEventBallotCreated(ctx, ballot, inTxHash, senderChain.String()) + } + + // adds a vote and sets the ballot + ballot, err = k.AddVoteToBallot(ctx, ballot, voter, types.VoteType_SuccessObservation) + if err != nil { + return false, isNew, err + } + + // checks if the ballot is finalized + _, isFinalized := k.CheckIfFinalizingVote(ctx, ballot) + return isFinalized, isNew, nil +} diff --git a/x/observer/keeper/vote_inbound_test.go b/x/observer/keeper/vote_inbound_test.go new file mode 100644 index 0000000000..e1391c051f --- /dev/null +++ b/x/observer/keeper/vote_inbound_test.go @@ -0,0 +1,438 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestKeeper_VoteOnInboundBallot(t *testing.T) { + + t.Run("fail if inbound not enabled", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: false, + }) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + + require.Error(t, err) + require.ErrorIs(t, err, types.ErrInboundDisabled) + }) + + t.Run("fail if sender chain not supported", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{}) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + + // set the chain but not supported + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: false, + }, + }, + }) + + _, _, err = k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("fail if not authorized", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{}) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrNotObserver) + }) + + t.Run("fail if receiver chain not supported", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + + // set the chain but not supported + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: common.ZetaPrivnetChain().ChainId, + IsSupported: false, + }, + }, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + _, _, err = k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("fail if inbound contain ZETA but receiver chain doesn't support ZETA", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + ZetaTokenContractAddress: "", + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_Zeta, + observer, + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrInvalidZetaCoinTypes) + }) + + t.Run("can add vote and create ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.True(t, isFinalized) + require.True(t, isNew) + }) + + t.Run("can add vote and create ballot without finalizing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + // threshold high enough to not finalize ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + BallotThreshold: threshold, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + BallotThreshold: threshold, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{ + observer, + sample.AccAddress(), + }, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.False(t, isFinalized) + require.True(t, isNew) + }) + + t.Run("can add vote to an existing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + sample.AccAddress(), + sample.AccAddress(), + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(5), + ObservationType: types.ObservationType_InBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should not be finalized as the threshold is not reached + require.False(t, isFinalized) + require.False(t, isNew) + }) + + t.Run("can add vote to an existing ballot and finalize ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.1") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(3), + ObservationType: types.ObservationType_InBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should not be finalized as the threshold is not reached + require.True(t, isFinalized) + require.False(t, isNew) + }) +} diff --git a/x/observer/keeper/vote_outbound.go b/x/observer/keeper/vote_outbound.go new file mode 100644 index 0000000000..3ee0c6b585 --- /dev/null +++ b/x/observer/keeper/vote_outbound.go @@ -0,0 +1,52 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/common" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +// VoteOnOutboundBallot casts a vote on an outbound transaction observed on a connected chain (after +// it has been broadcasted to and finalized on a connected chain). If this is +// the first vote, a new ballot is created. When a threshold of votes is +// reached, the ballot is finalized. +// returns if the vote is finalized, if the ballot is new, the ballot status and the name of the observation chain +func (k Keeper) VoteOnOutboundBallot( + ctx sdk.Context, + ballotIndex string, + outTxChainID int64, + receiveStatus common.ReceiveStatus, + voter string, +) (isFinalized bool, isNew bool, ballot observertypes.Ballot, observationChainName string, err error) { + // Observer Chain already checked then inbound is created + /* EDGE CASE : Params updated in during the finalization process + i.e Inbound has been finalized but outbound is still pending + */ + observationChain := k.GetSupportedChainFromChainID(ctx, outTxChainID) + if observationChain == nil { + return false, false, ballot, "", observertypes.ErrSupportedChains + } + if observertypes.CheckReceiveStatus(receiveStatus) != nil { + return false, false, ballot, "", observertypes.ErrInvalidStatus + } + + // check if voter is authorized + if ok := k.IsAuthorized(ctx, voter); !ok { + return false, false, ballot, "", observertypes.ErrNotObserver + } + + // fetch or create ballot + ballot, isNew, err = k.FindBallot(ctx, ballotIndex, observationChain, observertypes.ObservationType_OutBoundTx) + if err != nil { + return false, false, ballot, "", err + } + + // add vote to ballot + ballot, err = k.AddVoteToBallot(ctx, ballot, voter, observertypes.ConvertReceiveStatusToVoteType(receiveStatus)) + if err != nil { + return false, false, ballot, "", err + } + + ballot, isFinalizedInThisBlock := k.CheckIfFinalizingVote(ctx, ballot) + return isFinalizedInThisBlock, isNew, ballot, observationChain.String(), nil +} diff --git a/x/observer/keeper/vote_outbound_test.go b/x/observer/keeper/vote_outbound_test.go new file mode 100644 index 0000000000..0e087bac1f --- /dev/null +++ b/x/observer/keeper/vote_outbound_test.go @@ -0,0 +1,295 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestKeeper_VoteOnOutboundBallot(t *testing.T) { + t.Run("fail if chain is not supported", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{}) + + _, _, _, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + + // set the chain but not supported + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: false, + }, + }, + }) + + _, _, _, _, err = k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("fail if receive status is invalid", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + + _, _, _, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus(1000), + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrInvalidStatus) + }) + + t.Run("fail if sender is not authorized", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{}) + + _, _, _, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrNotObserver) + }) + + t.Run("can add vote and create ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.True(t, isFinalized) + require.True(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("can add vote and create ballot without finalizing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + // threshold high enough to not finalize the ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + BallotThreshold: threshold, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{ + observer, + sample.AccAddress(), + }, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.False(t, isFinalized) + require.True(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("can add vote to an existing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + sample.AccAddress(), + sample.AccAddress(), + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(5), + ObservationType: types.ObservationType_OutBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.False(t, isFinalized) + require.False(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("can add vote to an existing ballot and finalize ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.1") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(3), + ObservationType: types.ObservationType_OutBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.True(t, isFinalized) + require.False(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) +} diff --git a/x/observer/migrations/v3/migrate_test.go b/x/observer/migrations/v3/migrate_test.go index 604fd0a9c7..ce580cbf42 100644 --- a/x/observer/migrations/v3/migrate_test.go +++ b/x/observer/migrations/v3/migrate_test.go @@ -11,7 +11,7 @@ import ( ) func TestMigrateStore(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) // nothing if no admin policy params := types.DefaultParams() diff --git a/x/observer/migrations/v4/migrate_test.go b/x/observer/migrations/v4/migrate_test.go index a5bfd67f56..55c87d6541 100644 --- a/x/observer/migrations/v4/migrate_test.go +++ b/x/observer/migrations/v4/migrate_test.go @@ -11,7 +11,7 @@ import ( ) func TestMigrateCrosschainFlags(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) store := prefix.NewStore(ctx.KVStore(k.StoreKey()), types.KeyPrefix(types.CrosschainFlagsKey)) legacyFlags := types.LegacyCrosschainFlags{ IsInboundEnabled: false, diff --git a/x/observer/migrations/v5/migrate_test.go b/x/observer/migrations/v5/migrate_test.go index 8b83d5d0ed..f5aa035414 100644 --- a/x/observer/migrations/v5/migrate_test.go +++ b/x/observer/migrations/v5/migrate_test.go @@ -15,7 +15,7 @@ import ( func TestMigrateObserverMapper(t *testing.T) { t.Run("TestMigrateStore", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) legacyObserverMapperStore := prefix.NewStore(ctx.KVStore(k.StoreKey()), types.KeyPrefix(types.ObserverMapperKey)) legacyObserverMapperList := sample.LegacyObserverMapperList(t, 12, "sample") for _, legacyObserverMapper := range legacyObserverMapperList { @@ -43,7 +43,7 @@ func TestMigrateObserverMapper(t *testing.T) { } func TestMigrateObserverParams(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) // set chain params previousChainParamsList := types.ChainParamsList{ diff --git a/x/observer/migrations/v6/migrate_test.go b/x/observer/migrations/v6/migrate_test.go index 925c1b0b32..b99242aabc 100644 --- a/x/observer/migrations/v6/migrate_test.go +++ b/x/observer/migrations/v6/migrate_test.go @@ -12,7 +12,7 @@ import ( func TestMigrateObserverParams(t *testing.T) { t.Run("Migrate when keygen is Pending", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) k.SetKeygen(ctx, types.Keygen{ Status: types.KeygenStatus_PendingKeygen, BlockNumber: math.MaxInt64, @@ -57,7 +57,7 @@ func TestMigrateObserverParams(t *testing.T) { require.Equal(t, participantList, participantList) }) t.Run("Migrate when keygen is not Pending", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) participantList := []string{ "zetapub1addwnpepqglunjrgl3qg08duxq9pf28jmvrer3crwnnfzp6m0u0yh9jk9mnn5p76utc", "zetapub1addwnpepqwwpjwwnes7cywfkr0afme7ymk8rf5jzhn8pfr6qqvfm9v342486qsrh4f5", diff --git a/x/observer/types/ballot.go b/x/observer/types/ballot.go index bc6bfd66ee..10f21119d1 100644 --- a/x/observer/types/ballot.go +++ b/x/observer/types/ballot.go @@ -34,7 +34,7 @@ func (m Ballot) GetVoterIndex(address string) int { return index } -// Is finalzing vote checks sets the ballot to a final status if enough votes have been added +// IsFinalizingVote checks sets the ballot to a final status if enough votes have been added // If it has already been finalized it returns false // It enough votes have not been added it returns false func (m Ballot) IsFinalizingVote() (Ballot, bool) { diff --git a/x/observer/types/errors.go b/x/observer/types/errors.go index 0add35d4ce..4d5fd240a5 100644 --- a/x/observer/types/errors.go +++ b/x/observer/types/errors.go @@ -12,17 +12,15 @@ var ( ErrSupportedChains = errorsmod.Register(ModuleName, 1102, "chain not supported") ErrInvalidStatus = errorsmod.Register(ModuleName, 1103, "invalid Voting Status") - ErrObserverNotPresent = errorsmod.Register(ModuleName, 1105, "observer for type and observation does not exist") - ErrNotValidator = errorsmod.Register(ModuleName, 1106, "user needs to be a validator before applying to become an observer") - ErrValidatorStatus = errorsmod.Register(ModuleName, 1107, "corresponding validator needs to be bonded and not jailed") - ErrInvalidAddress = errorsmod.Register(ModuleName, 1108, "invalid Address") - ErrSelfDelegation = errorsmod.Register(ModuleName, 1109, "self Delegation for operator not found") - ErrCheckObserverDelegation = errorsmod.Register(ModuleName, 1110, "observer delegation not sufficient") - ErrNotAuthorizedPolicy = errorsmod.Register(ModuleName, 1111, "msg Sender is not the authorized policy") - ErrKeygenNotFound = errorsmod.Register(ModuleName, 1113, "Keygen not found, Keygen block can only be updated,New keygen cannot be set") - ErrKeygenBlockTooLow = errorsmod.Register(ModuleName, 1114, "please set a block number at-least 10 blocks higher than the current block number") - ErrKeygenCompleted = errorsmod.Register(ModuleName, 1115, "keygen already completed") - ErrNotAuthorized = errorsmod.Register(ModuleName, 1116, "not authorized") + ErrNotValidator = errorsmod.Register(ModuleName, 1106, "user needs to be a validator before applying to become an observer") + ErrValidatorStatus = errorsmod.Register(ModuleName, 1107, "corresponding validator needs to be bonded and not jailed") + ErrInvalidAddress = errorsmod.Register(ModuleName, 1108, "invalid Address") + ErrSelfDelegation = errorsmod.Register(ModuleName, 1109, "self Delegation for operator not found") + ErrNotAuthorizedPolicy = errorsmod.Register(ModuleName, 1111, "msg Sender is not the authorized policy") + ErrKeygenNotFound = errorsmod.Register(ModuleName, 1113, "Keygen not found, Keygen block can only be updated,New keygen cannot be set") + ErrKeygenBlockTooLow = errorsmod.Register(ModuleName, 1114, "please set a block number at-least 10 blocks higher than the current block number") + ErrKeygenCompleted = errorsmod.Register(ModuleName, 1115, "keygen already completed") + ErrNotAuthorized = errorsmod.Register(ModuleName, 1116, "not authorized") ErrBlockAlreadyExist = errorsmod.Register(ModuleName, 1119, "block already exists") ErrNoParentHash = errorsmod.Register(ModuleName, 1120, "no parent hash") @@ -38,5 +36,7 @@ var ( ErrObserverSetNotFound = errorsmod.Register(ModuleName, 1130, "observer set not found") ErrTssNotFound = errorsmod.Register(ModuleName, 1131, "tss not found") - ErrInboundDisabled = errorsmod.Register(ModuleName, 1132, "inbound tx processing is disabled") + ErrInboundDisabled = errorsmod.Register(ModuleName, 1132, "inbound tx processing is disabled") + ErrInvalidZetaCoinTypes = errorsmod.Register(ModuleName, 1133, "invalid zeta coin types") + ErrNotObserver = errorsmod.Register(ModuleName, 1134, "sender is not an observer") ) diff --git a/x/observer/types/observer_mapper.go b/x/observer/types/observer_mapper.go deleted file mode 100644 index b78f5daa8c..0000000000 --- a/x/observer/types/observer_mapper.go +++ /dev/null @@ -1,28 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/common" -) - -// Validate observer mapper contains an existing chain -func (m *ObserverSet) Validate() error { - for _, observerAddress := range m.ObserverList { - _, err := sdk.AccAddressFromBech32(observerAddress) - if err != nil { - return err - } - } - return nil -} - -func CheckReceiveStatus(status common.ReceiveStatus) error { - switch status { - case common.ReceiveStatus_Success: - return nil - case common.ReceiveStatus_Failed: - return nil - default: - return ErrInvalidStatus - } -} diff --git a/x/observer/types/observer_set.go b/x/observer/types/observer_set.go index 71798f213f..e1b6c61c9b 100644 --- a/x/observer/types/observer_set.go +++ b/x/observer/types/observer_set.go @@ -1,5 +1,10 @@ package types +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/common" +) + func (m *ObserverSet) Len() int { return len(m.ObserverList) } @@ -7,3 +12,25 @@ func (m *ObserverSet) Len() int { func (m *ObserverSet) LenUint() uint64 { return uint64(len(m.ObserverList)) } + +// Validate observer mapper contains an existing chain +func (m *ObserverSet) Validate() error { + for _, observerAddress := range m.ObserverList { + _, err := sdk.AccAddressFromBech32(observerAddress) + if err != nil { + return err + } + } + return nil +} + +func CheckReceiveStatus(status common.ReceiveStatus) error { + switch status { + case common.ReceiveStatus_Success: + return nil + case common.ReceiveStatus_Failed: + return nil + default: + return ErrInvalidStatus + } +} From 3cb13bcfd11d846244c95165b66ccb9a1c8a884c Mon Sep 17 00:00:00 2001 From: lumtis Date: Fri, 1 Mar 2024 17:45:24 +0100 Subject: [PATCH 21/33] remove unused comment --- cmd/zetae2e/config/clients.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/zetae2e/config/clients.go b/cmd/zetae2e/config/clients.go index e85acd2dc3..636537c9d6 100644 --- a/cmd/zetae2e/config/clients.go +++ b/cmd/zetae2e/config/clients.go @@ -80,7 +80,6 @@ func getBtcClient(rpcConf config.BitcoinRPC) (*rpcclient.Client, error) { HTTPPostMode: rpcConf.HTTPPostMode, DisableTLS: rpcConf.DisableTLS, Params: param, - //Endpoint: "/wallet/user", } return rpcclient.New(connCfg, nil) } From b0971e12a4462e7709f62425cd80838ae9f5a6c3 Mon Sep 17 00:00:00 2001 From: lumtis Date: Tue, 5 Mar 2024 18:58:49 +0100 Subject: [PATCH 22/33] fix bitcoin credentials --- contrib/localnet/docker-compose-upgrade-light.yml | 12 ++++++++++++ contrib/localnet/docker-compose-upgrade.yml | 12 ++++++++++++ contrib/localnet/docker-compose.yml | 2 +- contrib/localnet/orchestrator/start.sh | 3 +-- contrib/localnet/scripts/start-zetaclientd.sh | 8 ++++---- e2e/config/config.go | 2 +- zetaclient/config/config_chain.go | 2 +- 7 files changed, 32 insertions(+), 9 deletions(-) diff --git a/contrib/localnet/docker-compose-upgrade-light.yml b/contrib/localnet/docker-compose-upgrade-light.yml index 1a1c30b24c..680206354d 100644 --- a/contrib/localnet/docker-compose-upgrade-light.yml +++ b/contrib/localnet/docker-compose-upgrade-light.yml @@ -6,15 +6,27 @@ version: "3" services: zetacore0: entrypoint: ["/root/start-zetacored.sh", "2", "upgrade", "90"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade zetacore1: entrypoint: ["/root/start-zetacored.sh", "2", "upgrade", "90"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade zetaclient0: entrypoint: ["/root/start-zetaclientd.sh", "background"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade zetaclient1: entrypoint: ["/root/start-zetaclientd.sh", "background"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade orchestrator: entrypoint: ["/work/start.sh", "local", "upgrade", "90"] diff --git a/contrib/localnet/docker-compose-upgrade.yml b/contrib/localnet/docker-compose-upgrade.yml index ee677a0e5f..3006ca77f2 100644 --- a/contrib/localnet/docker-compose-upgrade.yml +++ b/contrib/localnet/docker-compose-upgrade.yml @@ -8,15 +8,27 @@ version: "3" services: zetacore0: entrypoint: ["/root/start-zetacored.sh", "2", "upgrade"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade zetacore1: entrypoint: ["/root/start-zetacored.sh", "2", "upgrade"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade zetaclient0: entrypoint: ["/root/start-zetaclientd.sh", "background"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade zetaclient1: entrypoint: ["/root/start-zetaclientd.sh", "background"] + build: + context: ../../. + dockerfile: Dockerfile-upgrade orchestrator: entrypoint: ["/work/start.sh", "local", "upgrade"] diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index 4b56601768..e546662fb1 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -120,7 +120,7 @@ services: -regtest=1 -rpcallowip=172.20.0.0/16 -rpcbind=0.0.0.0 - -rpcauth=e2e:63acf9b8dccecce914d85ff8c044b78b$$5892f9bbc84f4364e79f0970039f88bdd823f168d4acc76099ab97b14a766a99 + -rpcauth=smoketest:63acf9b8dccecce914d85ff8c044b78b$$5892f9bbc84f4364e79f0970039f88bdd823f168d4acc76099ab97b14a766a99 -txindex=1 orchestrator: diff --git a/contrib/localnet/orchestrator/start.sh b/contrib/localnet/orchestrator/start.sh index bd31e8213c..dd1815a391 100644 --- a/contrib/localnet/orchestrator/start.sh +++ b/contrib/localnet/orchestrator/start.sh @@ -8,7 +8,6 @@ ZETAE2E_CMD=$1 OPTION=$2 - echo "waiting for geth RPC to start..." sleep 2 @@ -97,7 +96,7 @@ else # Run the e2e tests normally echo "running e2e tests..." - # zetae2e "$ZETAE2E_CMD" + eval "zetae2e $ZETAE2E_CMD" ZETAE2E_EXIT_CODE=$? diff --git a/contrib/localnet/scripts/start-zetaclientd.sh b/contrib/localnet/scripts/start-zetaclientd.sh index 761a58098d..6f25e1d659 100755 --- a/contrib/localnet/scripts/start-zetaclientd.sh +++ b/contrib/localnet/scripts/start-zetaclientd.sh @@ -45,8 +45,8 @@ else zetaclientd start < /root/password.file fi -if [ "$OPTION" == "background" ]; then - sleep 3 - tail -f $HOME/zetaclient.log -fi +#if [ "$OPTION" == "background" ]; then +# sleep 3 +# tail -f $HOME/zetaclient.log +#fi diff --git a/e2e/config/config.go b/e2e/config/config.go index 46281b5558..1b2e510a76 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -81,7 +81,7 @@ func DefaultConfig() Config { EVM: "http://eth:8545", Bitcoin: BitcoinRPC{ Host: "bitcoin:18443", - User: "e2e", + User: "smoketest", Pass: "123", HTTPPostMode: true, DisableTLS: true, diff --git a/zetaclient/config/config_chain.go b/zetaclient/config/config_chain.go index 2596c554a5..61c3081582 100644 --- a/zetaclient/config/config_chain.go +++ b/zetaclient/config/config_chain.go @@ -39,7 +39,7 @@ func New() Config { } var bitcoinConfigRegnet = &BTCConfig{ - RPCUsername: "e2e", + RPCUsername: "smoketest", // smoketest is the previous name for E2E test, we keep this name for compatibility between client versions in upgrade test RPCPassword: "123", RPCHost: "bitcoin:18443", RPCParams: "regtest", From 351ec83800195799a42427a91248b3a0d95a5e4d Mon Sep 17 00:00:00 2001 From: lumtis Date: Wed, 6 Mar 2024 15:50:01 +0100 Subject: [PATCH 23/33] change upgrade version --- Dockerfile-upgrade | 4 ++-- app/setup_handlers.go | 2 +- cmd/zetae2e/local/bitcoin.go | 2 +- zetaclient/zetabridge/zetacore_bridge.go | 7 +++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Dockerfile-upgrade b/Dockerfile-upgrade index 065f8622f0..e53b766256 100644 --- a/Dockerfile-upgrade +++ b/Dockerfile-upgrade @@ -20,8 +20,8 @@ WORKDIR /go/delivery/zeta-node RUN mkdir -p $GOPATH/bin/old RUN mkdir -p $GOPATH/bin/new -ARG OLD_VERSION=v13.0.0 -ENV NEW_VERSION=v14 +ARG OLD_VERSION=v14.0.0 +ENV NEW_VERSION=v15 # Build new release from the current source COPY go.mod /go/delivery/zeta-node/ diff --git a/app/setup_handlers.go b/app/setup_handlers.go index b773745398..26ed86a255 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -8,7 +8,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) -const releaseVersion = "v14" +const releaseVersion = "v15" func SetupHandlers(app *App) { app.UpgradeKeeper.SetUpgradeHandler(releaseVersion, func(ctx sdk.Context, plan types.Plan, vm module.VersionMap) (module.VersionMap, error) { diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index fb572d973c..0d6bfd77e2 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -70,7 +70,7 @@ func bitcoinTestRoutine( e2etests.TestBitcoinWithdrawName, e2etests.TestZetaWithdrawBTCRevertName, e2etests.TestCrosschainSwapName, - e2etests.TestBitcoinWithdrawRestrictedName, + //e2etests.TestBitcoinWithdrawRestrictedName, ); err != nil { return fmt.Errorf("bitcoin tests failed: %v", err) } diff --git a/zetaclient/zetabridge/zetacore_bridge.go b/zetaclient/zetabridge/zetacore_bridge.go index 0a605fb914..fbe5187088 100644 --- a/zetaclient/zetabridge/zetacore_bridge.go +++ b/zetaclient/zetabridge/zetacore_bridge.go @@ -48,11 +48,14 @@ type ZetaCoreBridge struct { } // NewZetaCoreBridge create a new instance of ZetaCoreBridge -func NewZetaCoreBridge(k *keys.Keys, chainIP string, +func NewZetaCoreBridge( + k *keys.Keys, + chainIP string, signerName string, chainID string, hsmMode bool, - telemetry *metrics.TelemetryServer) (*ZetaCoreBridge, error) { + telemetry *metrics.TelemetryServer, +) (*ZetaCoreBridge, error) { // main module logger logger := log.With().Str("module", "CoreBridge").Logger() From a1c12323109caae20a66043cde573e87eac2fe68 Mon Sep 17 00:00:00 2001 From: lumtis Date: Wed, 6 Mar 2024 16:12:58 +0100 Subject: [PATCH 24/33] fix performance tests --- cmd/zetae2e/local/performance.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/cmd/zetae2e/local/performance.go b/cmd/zetae2e/local/performance.go index f9557c117a..69bbd81860 100644 --- a/cmd/zetae2e/local/performance.go +++ b/cmd/zetae2e/local/performance.go @@ -49,11 +49,16 @@ func ethereumDepositPerformanceRoutine( r.Logger.Print("🏃 starting Ethereum deposit performance tests") startTime := time.Now() - if err := r.RunE2ETestsFromNames( + tests, err := r.GetE2ETestsToRunByName( e2etests.AllE2ETests, e2etests.TestStressEtherDepositName, - ); err != nil { - return fmt.Errorf("thereum deposit performance test failed: %v", err) + ) + if err != nil { + return fmt.Errorf("ethereum deposit performance test failed: %v", err) + } + + if err := r.RunE2ETests(tests); err != nil { + return fmt.Errorf("misc tests failed: %v", err) } r.Logger.Print("🍾 Ethereum deposit performance test completed in %s", time.Since(startTime).String()) @@ -101,11 +106,16 @@ func ethereumWithdrawPerformanceRoutine( txEtherDeposit := r.DepositEther(false) r.WaitForMinedCCTX(txEtherDeposit) - if err := r.RunE2ETestsFromNames( + tests, err := r.GetE2ETestsToRunByName( e2etests.AllE2ETests, e2etests.TestStressEtherWithdrawName, - ); err != nil { - return fmt.Errorf("thereum withdraw performance test failed: %v", err) + ) + if err != nil { + return fmt.Errorf("ethereum withdraw performance test failed: %v", err) + } + + if err := r.RunE2ETests(tests); err != nil { + return fmt.Errorf("misc tests failed: %v", err) } r.Logger.Print("🍾 Ethereum withdraw performance test completed in %s", time.Since(startTime).String()) From 4803004d23123081157a1106c223ab99a17b20b3 Mon Sep 17 00:00:00 2001 From: lumtis Date: Wed, 6 Mar 2024 18:02:33 +0100 Subject: [PATCH 25/33] handler --- app/setup_handlers.go | 4 ++-- contrib/localnet/scripts/start-zetaclientd.sh | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/setup_handlers.go b/app/setup_handlers.go index 26ed86a255..a9c56f228d 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -5,7 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/upgrade/types" - crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" ) const releaseVersion = "v15" @@ -17,7 +17,6 @@ func SetupHandlers(app *App) { for m, mb := range app.mm.Modules { vm[m] = mb.ConsensusVersion() } - VersionMigrator{v: vm}.TriggerMigration(crosschaintypes.ModuleName) return app.mm.RunMigrations(ctx, app.configurator, vm) }) @@ -28,6 +27,7 @@ func SetupHandlers(app *App) { } if upgradeInfo.Name == releaseVersion && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{authoritytypes.ModuleName}, // Added: []string{}, } // Use upgrade store loader for the initial loading of all stores when app starts, diff --git a/contrib/localnet/scripts/start-zetaclientd.sh b/contrib/localnet/scripts/start-zetaclientd.sh index 6f25e1d659..761a58098d 100755 --- a/contrib/localnet/scripts/start-zetaclientd.sh +++ b/contrib/localnet/scripts/start-zetaclientd.sh @@ -45,8 +45,8 @@ else zetaclientd start < /root/password.file fi -#if [ "$OPTION" == "background" ]; then -# sleep 3 -# tail -f $HOME/zetaclient.log -#fi +if [ "$OPTION" == "background" ]; then + sleep 3 + tail -f $HOME/zetaclient.log +fi From d1f542dee612e346bd7555ebb529429b19415c60 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 7 Mar 2024 12:28:24 +0100 Subject: [PATCH 26/33] fix setup handler --- app/setup_handlers.go | 3 ++- cmd/zetae2e/config/config.go | 2 ++ cmd/zetae2e/local/bitcoin.go | 9 +++++---- cmd/zetae2e/local/erc20.go | 8 ++++---- cmd/zetae2e/local/ethereum.go | 11 ++++++----- cmd/zetae2e/local/local.go | 12 +++++++++++- cmd/zetae2e/local/zeta.go | 8 ++++---- contrib/athens3/zetacored/docker-compose.yml | 2 +- contrib/localnet/docker-compose-admin.yml | 2 +- contrib/localnet/docker-compose-performance.yml | 2 +- contrib/localnet/docker-compose-setup-only.yml | 2 +- contrib/localnet/docker-compose-stresstest.yml | 2 +- contrib/localnet/docker-compose-upgrade-light.yml | 2 +- contrib/localnet/docker-compose-upgrade.yml | 2 +- contrib/localnet/docker-compose.yml | 2 +- contrib/localnet/orchestrator/Dockerfile | 2 +- contrib/localnet/orchestrator/Dockerfile.fastbuild | 2 +- .../orchestrator/{start.sh => start-zetae2e.sh} | 8 +++++++- contrib/mainnet/zetacored/docker-compose.yml | 2 +- x/observer/migrations/v7/migrate.go | 1 + 20 files changed, 53 insertions(+), 31 deletions(-) rename contrib/localnet/orchestrator/{start.sh => start-zetae2e.sh} (92%) diff --git a/app/setup_handlers.go b/app/setup_handlers.go index a9c56f228d..d9119ee8f6 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/upgrade/types" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) const releaseVersion = "v15" @@ -17,6 +18,7 @@ func SetupHandlers(app *App) { for m, mb := range app.mm.Modules { vm[m] = mb.ConsensusVersion() } + VersionMigrator{v: vm}.TriggerMigration(observertypes.ModuleName) return app.mm.RunMigrations(ctx, app.configurator, vm) }) @@ -28,7 +30,6 @@ func SetupHandlers(app *App) { if upgradeInfo.Name == releaseVersion && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := storetypes.StoreUpgrades{ Added: []string{authoritytypes.ModuleName}, - // Added: []string{}, } // Use upgrade store loader for the initial loading of all stores when app starts, // it checks if version == upgradeHeight and applies store upgrades before loading the stores, diff --git a/cmd/zetae2e/config/config.go b/cmd/zetae2e/config/config.go index 2d0cd3fed0..461e469f8a 100644 --- a/cmd/zetae2e/config/config.go +++ b/cmd/zetae2e/config/config.go @@ -100,6 +100,8 @@ func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.C conf.Contracts.ZEVM.BTCZRC20Addr = r.BTCZRC20Addr.Hex() conf.Contracts.ZEVM.UniswapFactoryAddr = r.UniswapV2FactoryAddr.Hex() conf.Contracts.ZEVM.UniswapRouterAddr = r.UniswapV2RouterAddr.Hex() + conf.Contracts.ZEVM.ConnectorZEVMAddr = r.ConnectorZEVMAddr.Hex() + conf.Contracts.ZEVM.WZetaAddr = r.WZetaAddr.Hex() conf.Contracts.ZEVM.ZEVMSwapAppAddr = r.ZEVMSwapAppAddr.Hex() conf.Contracts.ZEVM.ContextAppAddr = r.ContextAppAddr.Hex() conf.Contracts.ZEVM.TestDappAddr = r.TestDAppAddr.Hex() diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index cb6158d666..dc033177dd 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -17,6 +17,7 @@ func bitcoinTestRoutine( deployerRunner *runner.E2ERunner, verbose bool, initBitcoinNetwork bool, + testHeader bool, ) func() error { return func() (err error) { // return an error on panic @@ -59,17 +60,17 @@ func bitcoinTestRoutine( bitcoinRunner.WaitForMinedCCTX(txERC20Deposit) bitcoinRunner.SetupBitcoinAccount(initBitcoinNetwork) - bitcoinRunner.DepositBTC(true) + bitcoinRunner.DepositBTC(testHeader) // run bitcoin test // Note: due to the extensive block generation in Bitcoin localnet, block header test is run first // to make it faster to catch up with the latest block header testsToRun, err := bitcoinRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestBitcoinWithdrawInvalidAddressName, e2etests.TestBitcoinWithdrawName, - e2etests.TestZetaWithdrawBTCRevertName, - e2etests.TestCrosschainSwapName, + //e2etests.TestBitcoinWithdrawInvalidAddressName, + //e2etests.TestZetaWithdrawBTCRevertName, + //e2etests.TestCrosschainSwapName, //e2etests.TestBitcoinWithdrawRestrictedName, ) if err != nil { diff --git a/cmd/zetae2e/local/erc20.go b/cmd/zetae2e/local/erc20.go index c8eb63163f..628529a714 100644 --- a/cmd/zetae2e/local/erc20.go +++ b/cmd/zetae2e/local/erc20.go @@ -63,10 +63,10 @@ func erc20TestRoutine( testsToRun, err := erc20Runner.GetE2ETestsToRunByName( e2etests.AllE2ETests, e2etests.TestERC20WithdrawName, - e2etests.TestMultipleWithdrawsName, - e2etests.TestERC20DepositAndCallRefundName, - e2etests.TestZRC20SwapName, - e2etests.TestERC20DepositRestrictedName, + //e2etests.TestMultipleWithdrawsName, + //e2etests.TestERC20DepositAndCallRefundName, + //e2etests.TestZRC20SwapName, + //e2etests.TestERC20DepositRestrictedName, ) if err != nil { return fmt.Errorf("erc20 tests failed: %v", err) diff --git a/cmd/zetae2e/local/ethereum.go b/cmd/zetae2e/local/ethereum.go index 72fe03fe2c..02f8111147 100644 --- a/cmd/zetae2e/local/ethereum.go +++ b/cmd/zetae2e/local/ethereum.go @@ -16,6 +16,7 @@ func ethereumTestRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, + testHeader bool, ) func() error { return func() (err error) { // return an error on panic @@ -47,7 +48,7 @@ func ethereumTestRoutine( startTime := time.Now() // depositing the necessary tokens on ZetaChain - txEtherDeposit := ethereumRunner.DepositEther(true) + txEtherDeposit := ethereumRunner.DepositEther(testHeader) ethereumRunner.WaitForMinedCCTX(txEtherDeposit) // run ethereum test @@ -56,10 +57,10 @@ func ethereumTestRoutine( testsToRun, err := ethereumRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, e2etests.TestEtherWithdrawName, - e2etests.TestContextUpgradeName, - e2etests.TestEtherDepositAndCallName, - e2etests.TestDepositAndCallRefundName, - e2etests.TestEtherWithdrawRestrictedName, + //e2etests.TestContextUpgradeName, + //e2etests.TestEtherDepositAndCallName, + //e2etests.TestDepositAndCallRefundName, + //e2etests.TestEtherWithdrawRestrictedName, ) if err != nil { return fmt.Errorf("ethereum tests failed: %v", err) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 02ddcd5081..ea7c09595d 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -27,6 +27,7 @@ const ( flagSetupOnly = "setup-only" flagConfigOut = "config-out" flagSkipSetup = "skip-setup" + flagSkipBitcoinSetup = "skip-bitcoin-setup" ) var ( @@ -96,6 +97,11 @@ func NewLocalCmd() *cobra.Command { false, "set to true to skip setup", ) + cmd.Flags().Bool( + flagSkipBitcoinSetup, + false, + "set to true to skip bitcoin wallet setup", + ) return cmd } @@ -143,6 +149,10 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if err != nil { panic(err) } + skipBitcoinSetup, err := cmd.Flags().GetBool(flagSkipBitcoinSetup) + if err != nil { + panic(err) + } testStartTime := time.Now() logger.Print("starting E2E tests") @@ -253,7 +263,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if !skipRegular { eg.Go(erc20TestRoutine(conf, deployerRunner, verbose)) eg.Go(zetaTestRoutine(conf, deployerRunner, verbose)) - eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipSetup)) + eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup)) eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose)) } if testAdmin { diff --git a/cmd/zetae2e/local/zeta.go b/cmd/zetae2e/local/zeta.go index 13ff2bfbab..9c8f3d4688 100644 --- a/cmd/zetae2e/local/zeta.go +++ b/cmd/zetae2e/local/zeta.go @@ -60,10 +60,10 @@ func zetaTestRoutine( testsToRun, err := zetaRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, e2etests.TestZetaWithdrawName, - e2etests.TestMessagePassingName, - e2etests.TestMessagePassingRevertFailName, - e2etests.TestMessagePassingRevertSuccessName, - e2etests.TestZetaDepositRestrictedName, + //e2etests.TestMessagePassingName, + //e2etests.TestMessagePassingRevertFailName, + //e2etests.TestMessagePassingRevertSuccessName, + //e2etests.TestZetaDepositRestrictedName, ) if err != nil { return fmt.Errorf("zeta tests failed: %v", err) diff --git a/contrib/athens3/zetacored/docker-compose.yml b/contrib/athens3/zetacored/docker-compose.yml index 6db48703d2..4cb3a32251 100644 --- a/contrib/athens3/zetacored/docker-compose.yml +++ b/contrib/athens3/zetacored/docker-compose.yml @@ -38,7 +38,7 @@ services: - "9091:9091" volumes: - zetacored_data:/root/.zetacored/ - entrypoint: bash /scripts/start.sh + entrypoint: bash /scripts/start-zetae2e.sh volumes: zetacored_data: diff --git a/contrib/localnet/docker-compose-admin.yml b/contrib/localnet/docker-compose-admin.yml index 53e57eec05..46b24bfb24 100644 --- a/contrib/localnet/docker-compose-admin.yml +++ b/contrib/localnet/docker-compose-admin.yml @@ -5,5 +5,5 @@ version: "3" services: orchestrator: - entrypoint: ["/work/start.sh", "local --skip-regular --test-admin"] + entrypoint: ["/work/start-zetae2e.sh", "local --skip-regular --test-admin"] diff --git a/contrib/localnet/docker-compose-performance.yml b/contrib/localnet/docker-compose-performance.yml index 0e92d7f1bd..e6fd10dd4e 100644 --- a/contrib/localnet/docker-compose-performance.yml +++ b/contrib/localnet/docker-compose-performance.yml @@ -5,5 +5,5 @@ version: "3" services: orchestrator: - entrypoint: ["/work/start.sh", "local --skip-regular --test-performance"] + entrypoint: ["/work/start-zetae2e.sh", "local --skip-regular --test-performance"] diff --git a/contrib/localnet/docker-compose-setup-only.yml b/contrib/localnet/docker-compose-setup-only.yml index 979f448527..11761ad817 100644 --- a/contrib/localnet/docker-compose-setup-only.yml +++ b/contrib/localnet/docker-compose-setup-only.yml @@ -5,5 +5,5 @@ version: "3" services: orchestrator: - entrypoint: ["/work/start.sh", "local --setup-only"] + entrypoint: ["/work/start-zetae2e.sh", "local --setup-only"] diff --git a/contrib/localnet/docker-compose-stresstest.yml b/contrib/localnet/docker-compose-stresstest.yml index b67d9a5a6e..38df1133f3 100644 --- a/contrib/localnet/docker-compose-stresstest.yml +++ b/contrib/localnet/docker-compose-stresstest.yml @@ -85,4 +85,4 @@ services: orchestrator: build: dockerfile: contrib/localnet/orchestrator/Dockerfile.fastbuild - entrypoint: ["/work/start.sh", "stress"] \ No newline at end of file + entrypoint: ["/work/start-zetae2e.sh", "stress"] \ No newline at end of file diff --git a/contrib/localnet/docker-compose-upgrade-light.yml b/contrib/localnet/docker-compose-upgrade-light.yml index 680206354d..0599f1acfc 100644 --- a/contrib/localnet/docker-compose-upgrade-light.yml +++ b/contrib/localnet/docker-compose-upgrade-light.yml @@ -29,4 +29,4 @@ services: dockerfile: Dockerfile-upgrade orchestrator: - entrypoint: ["/work/start.sh", "local", "upgrade", "90"] + entrypoint: ["/work/start-zetae2e.sh", "local", "upgrade", "90"] diff --git a/contrib/localnet/docker-compose-upgrade.yml b/contrib/localnet/docker-compose-upgrade.yml index 3006ca77f2..3bf858ec9a 100644 --- a/contrib/localnet/docker-compose-upgrade.yml +++ b/contrib/localnet/docker-compose-upgrade.yml @@ -31,4 +31,4 @@ services: dockerfile: Dockerfile-upgrade orchestrator: - entrypoint: ["/work/start.sh", "local", "upgrade"] + entrypoint: ["/work/start-zetae2e.sh", "local", "upgrade"] diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index 436e358c7b..bce13ad09f 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -138,5 +138,5 @@ services: networks: mynetwork: ipv4_address: 172.20.0.2 - entrypoint: ["/work/start.sh", "local"] + entrypoint: ["/work/start-zetae2e.sh", "local"] diff --git a/contrib/localnet/orchestrator/Dockerfile b/contrib/localnet/orchestrator/Dockerfile index 5e579916cc..e159d8ba84 100644 --- a/contrib/localnet/orchestrator/Dockerfile +++ b/contrib/localnet/orchestrator/Dockerfile @@ -11,7 +11,7 @@ COPY --from=geth /usr/local/bin/geth /usr/local/bin/ COPY --from=zeta /root/.ssh/localtest.pem.pub /root/.ssh/authorized_keys COPY --from=zeta /root/.ssh/localtest.pem /root/.ssh/localtest.pem -COPY contrib/localnet/orchestrator/start.sh /work/ +COPY contrib/localnet/orchestrator/start-zetae2e.sh /work/ COPY contrib/localnet/orchestrator/restart-zetaclientd.sh /work/ RUN chmod +x /work/*.sh diff --git a/contrib/localnet/orchestrator/Dockerfile.fastbuild b/contrib/localnet/orchestrator/Dockerfile.fastbuild index 198205c000..64d9e9c87e 100644 --- a/contrib/localnet/orchestrator/Dockerfile.fastbuild +++ b/contrib/localnet/orchestrator/Dockerfile.fastbuild @@ -11,7 +11,7 @@ COPY --from=geth /usr/local/bin/geth /usr/local/bin/ COPY --from=zeta /root/.ssh/localtest.pem.pub /root/.ssh/authorized_keys COPY --from=zeta /root/.ssh/localtest.pem /root/.ssh/localtest.pem -COPY contrib/localnet/orchestrator/start.sh /work/ +COPY contrib/localnet/orchestrator/start-zetae2e.sh /work/ COPY contrib/localnet/orchestrator/restart-zetaclientd.sh /work/ RUN chmod +x /work/*.sh diff --git a/contrib/localnet/orchestrator/start.sh b/contrib/localnet/orchestrator/start-zetae2e.sh similarity index 92% rename from contrib/localnet/orchestrator/start.sh rename to contrib/localnet/orchestrator/start-zetae2e.sh index d1d3063754..9c0b0c8a4f 100644 --- a/contrib/localnet/orchestrator/start.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -80,7 +80,13 @@ if [ "$OPTION" == "upgrade" ]; then echo "running E2E command to test the network after upgrade..." - zetae2e "$ZETAE2E_CMD" --skip-setup --config deployed.yml + # Run zetae2e again + # When the upgrade height is greater than 100 for upgrade test, the Bitcoin tests have been run once, therefore the Bitcoin wallet is already set up + if [ "$UPGRADE_HEIGHT" -lt 100 ]; then + zetae2e "$ZETAE2E_CMD" --skip-setup --config deployed.yml + else + zetae2e "$ZETAE2E_CMD" --skip-setup --config deployed.yml --skip-bitcoin-setup + fi ZETAE2E_EXIT_CODE=$? if [ $ZETAE2E_EXIT_CODE -eq 0 ]; then diff --git a/contrib/mainnet/zetacored/docker-compose.yml b/contrib/mainnet/zetacored/docker-compose.yml index 6df2a17616..cfc9092361 100644 --- a/contrib/mainnet/zetacored/docker-compose.yml +++ b/contrib/mainnet/zetacored/docker-compose.yml @@ -39,7 +39,7 @@ services: - "9091:9091" volumes: - zetacored_data:/root/.zetacored/ - entrypoint: bash /scripts/start.sh + entrypoint: bash /scripts/start-zetae2e.sh #for debugging #entrypoint: ["/bin/sh", "-c"] #command: ["while true; do sleep 86400; done"] diff --git a/x/observer/migrations/v7/migrate.go b/x/observer/migrations/v7/migrate.go index 2d69977f55..2b2c759e09 100644 --- a/x/observer/migrations/v7/migrate.go +++ b/x/observer/migrations/v7/migrate.go @@ -14,6 +14,7 @@ type observerKeeper interface { // MigrateStore performs in-place store migrations from v6 to v7 func MigrateStore(ctx sdk.Context, observerKeeper observerKeeper) error { + ctx.Logger().Info("Migrating observer store from v6 to v7") return MigratePolicies(ctx, observerKeeper) } From 004da79f625980c868160541511bb984083b77a2 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 7 Mar 2024 12:51:03 +0100 Subject: [PATCH 27/33] add light flag and use it for upgrade tests --- cmd/zetae2e/local/bitcoin.go | 7 +- cmd/zetae2e/local/erc20.go | 10 +-- cmd/zetae2e/local/ethereum.go | 4 +- cmd/zetae2e/local/local.go | 83 ++++++++++++++++--- cmd/zetae2e/local/zeta.go | 7 +- .../localnet/orchestrator/start-zetae2e.sh | 9 +- 6 files changed, 85 insertions(+), 35 deletions(-) diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index dc033177dd..78728a86c1 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -18,6 +18,7 @@ func bitcoinTestRoutine( verbose bool, initBitcoinNetwork bool, testHeader bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -67,11 +68,7 @@ func bitcoinTestRoutine( // to make it faster to catch up with the latest block header testsToRun, err := bitcoinRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestBitcoinWithdrawName, - //e2etests.TestBitcoinWithdrawInvalidAddressName, - //e2etests.TestZetaWithdrawBTCRevertName, - //e2etests.TestCrosschainSwapName, - //e2etests.TestBitcoinWithdrawRestrictedName, + testNames..., ) if err != nil { return fmt.Errorf("bitcoin tests failed: %v", err) diff --git a/cmd/zetae2e/local/erc20.go b/cmd/zetae2e/local/erc20.go index 628529a714..2bf7816d5e 100644 --- a/cmd/zetae2e/local/erc20.go +++ b/cmd/zetae2e/local/erc20.go @@ -16,6 +16,7 @@ func erc20TestRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -56,17 +57,10 @@ func erc20TestRoutine( erc20Runner.WaitForMinedCCTX(txEtherDeposit) erc20Runner.WaitForMinedCCTX(txERC20Deposit) - //erc20Runner.SetupBitcoinAccount() - //erc20Runner.DepositBTC() - // run erc20 test testsToRun, err := erc20Runner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestERC20WithdrawName, - //e2etests.TestMultipleWithdrawsName, - //e2etests.TestERC20DepositAndCallRefundName, - //e2etests.TestZRC20SwapName, - //e2etests.TestERC20DepositRestrictedName, + testNames..., ) if err != nil { return fmt.Errorf("erc20 tests failed: %v", err) diff --git a/cmd/zetae2e/local/ethereum.go b/cmd/zetae2e/local/ethereum.go index 02f8111147..1809af9da2 100644 --- a/cmd/zetae2e/local/ethereum.go +++ b/cmd/zetae2e/local/ethereum.go @@ -17,6 +17,7 @@ func ethereumTestRoutine( deployerRunner *runner.E2ERunner, verbose bool, testHeader bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -56,11 +57,12 @@ func ethereumTestRoutine( // to make it faster to catch up with the latest block header testsToRun, err := ethereumRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestEtherWithdrawName, + //e2etests.TestEtherWithdrawName, //e2etests.TestContextUpgradeName, //e2etests.TestEtherDepositAndCallName, //e2etests.TestDepositAndCallRefundName, //e2etests.TestEtherWithdrawRestrictedName, + testNames..., ) if err != nil { return fmt.Errorf("ethereum tests failed: %v", err) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index ea7c09595d..0d77d89939 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/cobra" zetae2econfig "github.com/zeta-chain/zetacore/cmd/zetae2e/config" "github.com/zeta-chain/zetacore/e2e/config" + "github.com/zeta-chain/zetacore/e2e/e2etests" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" "golang.org/x/sync/errgroup" @@ -19,13 +20,14 @@ const ( flagContractsDeployed = "deployed" flagWaitForHeight = "wait-for" FlagConfigFile = "config" + flagConfigOut = "config-out" flagVerbose = "verbose" flagTestAdmin = "test-admin" flagTestPerformance = "test-performance" flagTestCustom = "test-custom" flagSkipRegular = "skip-regular" + flagLight = "light" flagSetupOnly = "setup-only" - flagConfigOut = "config-out" flagSkipSetup = "skip-setup" flagSkipBitcoinSetup = "skip-bitcoin-setup" ) @@ -57,6 +59,11 @@ func NewLocalCmd() *cobra.Command { "", "config file to use for the tests", ) + cmd.Flags().String( + flagConfigOut, + "", + "config file to write the deployed contracts from the setup", + ) cmd.Flags().Bool( flagVerbose, false, @@ -82,16 +89,16 @@ func NewLocalCmd() *cobra.Command { false, "set to true to skip regular tests", ) + cmd.Flags().Bool( + flagLight, + false, + "run the most basic regular tests, useful for quick checks", + ) cmd.Flags().Bool( flagSetupOnly, false, "set to true to only setup the networks", ) - cmd.Flags().String( - flagConfigOut, - "", - "config file to write the deployed contracts from the setup", - ) cmd.Flags().Bool( flagSkipSetup, false, @@ -120,7 +127,10 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if err != nil { panic(err) } - logger := runner.NewLogger(verbose, color.FgWhite, "setup") + configOut, err := cmd.Flags().GetString(flagConfigOut) + if err != nil { + panic(err) + } testAdmin, err := cmd.Flags().GetBool(flagTestAdmin) if err != nil { panic(err) @@ -137,11 +147,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if err != nil { panic(err) } - setupOnly, err := cmd.Flags().GetBool(flagSetupOnly) + light, err := cmd.Flags().GetBool(flagLight) if err != nil { panic(err) } - configOut, err := cmd.Flags().GetString(flagConfigOut) + setupOnly, err := cmd.Flags().GetBool(flagSetupOnly) if err != nil { panic(err) } @@ -154,6 +164,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) { panic(err) } + logger := runner.NewLogger(verbose, color.FgWhite, "setup") + testStartTime := time.Now() logger.Print("starting E2E tests") @@ -261,10 +273,55 @@ func localE2ETest(cmd *cobra.Command, _ []string) { // run tests var eg errgroup.Group if !skipRegular { - eg.Go(erc20TestRoutine(conf, deployerRunner, verbose)) - eg.Go(zetaTestRoutine(conf, deployerRunner, verbose)) - eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup)) - eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose)) + // defines all tests, if light is enabled, only the most basic tests are run + erc20Tests := []string{ + e2etests.TestERC20WithdrawName, + e2etests.TestMultipleWithdrawsName, + e2etests.TestERC20DepositAndCallRefundName, + e2etests.TestZRC20SwapName, + } + erc20AdvancedTests := []string{ + e2etests.TestERC20DepositRestrictedName, + } + zetaTests := []string{ + e2etests.TestZetaWithdrawName, + e2etests.TestMessagePassingName, + e2etests.TestMessagePassingRevertFailName, + e2etests.TestMessagePassingRevertSuccessName, + } + zetaAdvancedTests := []string{ + e2etests.TestZetaDepositRestrictedName, + } + bitcoinTests := []string{ + e2etests.TestBitcoinWithdrawName, + e2etests.TestBitcoinWithdrawInvalidAddressName, + e2etests.TestZetaWithdrawBTCRevertName, + e2etests.TestCrosschainSwapName, + } + bitcoinAdvancedTests := []string{ + e2etests.TestBitcoinWithdrawRestrictedName, + } + ethereumTests := []string{ + e2etests.TestEtherWithdrawName, + e2etests.TestContextUpgradeName, + e2etests.TestEtherDepositAndCallName, + e2etests.TestDepositAndCallRefundName, + } + ethereumAdvancedTests := []string{ + e2etests.TestEtherWithdrawRestrictedName, + } + + if !light { + erc20Tests = append(erc20Tests, erc20AdvancedTests...) + zetaTests = append(zetaTests, zetaAdvancedTests...) + bitcoinTests = append(bitcoinTests, bitcoinAdvancedTests...) + ethereumTests = append(ethereumTests, ethereumAdvancedTests...) + } + + eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) + eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) + eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, !light, bitcoinTests...)) + eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, !light, ethereumTests...)) } if testAdmin { eg.Go(adminTestRoutine(conf, deployerRunner, verbose)) diff --git a/cmd/zetae2e/local/zeta.go b/cmd/zetae2e/local/zeta.go index 9c8f3d4688..a24ee3872e 100644 --- a/cmd/zetae2e/local/zeta.go +++ b/cmd/zetae2e/local/zeta.go @@ -16,6 +16,7 @@ func zetaTestRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -59,11 +60,7 @@ func zetaTestRoutine( // run zeta test testsToRun, err := zetaRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestZetaWithdrawName, - //e2etests.TestMessagePassingName, - //e2etests.TestMessagePassingRevertFailName, - //e2etests.TestMessagePassingRevertSuccessName, - //e2etests.TestZetaDepositRestrictedName, + testNames..., ) if err != nil { return fmt.Errorf("zeta tests failed: %v", err) diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index 9c0b0c8a4f..129317932d 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -60,7 +60,9 @@ if [ "$OPTION" == "upgrade" ]; then zetae2e "$ZETAE2E_CMD" --setup-only --config-out deployed.yml else echo "running E2E command to setup the networks and populate the state..." - zetae2e "$ZETAE2E_CMD" --config-out deployed.yml + + # Use light flag to ensure tests can complete before the upgrade height + zetae2e "$ZETAE2E_CMD" --config-out deployed.yml --light fi ZETAE2E_EXIT_CODE=$? @@ -82,10 +84,11 @@ if [ "$OPTION" == "upgrade" ]; then # Run zetae2e again # When the upgrade height is greater than 100 for upgrade test, the Bitcoin tests have been run once, therefore the Bitcoin wallet is already set up + # Use light flag to skip advanced tests if [ "$UPGRADE_HEIGHT" -lt 100 ]; then - zetae2e "$ZETAE2E_CMD" --skip-setup --config deployed.yml + zetae2e "$ZETAE2E_CMD" --skip-setup --config deployed.yml --light else - zetae2e "$ZETAE2E_CMD" --skip-setup --config deployed.yml --skip-bitcoin-setup + zetae2e "$ZETAE2E_CMD" --skip-setup --config deployed.yml --skip-bitcoin-setup --light fi ZETAE2E_EXIT_CODE=$? From d69550e78ba817ac1c21d80c5133904bb23aecab Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 7 Mar 2024 13:21:47 +0100 Subject: [PATCH 28/33] fix username --- cmd/zetae2e/config/local.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/zetae2e/config/local.yml b/cmd/zetae2e/config/local.yml index 5c02747766..47b18278f2 100644 --- a/cmd/zetae2e/config/local.yml +++ b/cmd/zetae2e/config/local.yml @@ -7,7 +7,7 @@ rpcs: evm: "http://0.0.0.0:8545" bitcoin: host: "0.0.0.0:18443" - user: "e2e" + user: "smoketest" pass: "123" http_post_mode: true disable_tls: true From 56840956f600d30d08d7255f058aa9b64e45474e Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 7 Mar 2024 13:40:37 +0100 Subject: [PATCH 29/33] add performance test back --- cmd/zetae2e/local/local.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 0d77d89939..eefbcc9657 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -327,7 +327,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { eg.Go(adminTestRoutine(conf, deployerRunner, verbose)) } if testPerformance { - //eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose)) + eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose)) eg.Go(ethereumWithdrawPerformanceRoutine(conf, deployerRunner, verbose)) } if testCustom { From 6be669710f090f78ddee157c75b6cb2b38cd34b0 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Thu, 7 Mar 2024 20:44:33 +0100 Subject: [PATCH 30/33] Update cmd/zetae2e/local/ethereum.go --- cmd/zetae2e/local/ethereum.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/zetae2e/local/ethereum.go b/cmd/zetae2e/local/ethereum.go index 1809af9da2..d5745472bd 100644 --- a/cmd/zetae2e/local/ethereum.go +++ b/cmd/zetae2e/local/ethereum.go @@ -57,11 +57,6 @@ func ethereumTestRoutine( // to make it faster to catch up with the latest block header testsToRun, err := ethereumRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - //e2etests.TestEtherWithdrawName, - //e2etests.TestContextUpgradeName, - //e2etests.TestEtherDepositAndCallName, - //e2etests.TestDepositAndCallRefundName, - //e2etests.TestEtherWithdrawRestrictedName, testNames..., ) if err != nil { From 0694fc532075ba220ad38aefcb87e22ce94b972d Mon Sep 17 00:00:00 2001 From: lumtis Date: Sun, 10 Mar 2024 16:53:35 +0100 Subject: [PATCH 31/33] stefan comments --- changelog.md | 1 - cmd/zetae2e/local/admin.go | 5 ++--- cmd/zetae2e/local/local.go | 14 +++++++++----- cmd/zetae2e/local/misc.go | 4 ++-- cmd/zetae2e/local/performance.go | 10 ++++++---- contrib/localnet/docker-compose-performance.yml | 2 +- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/changelog.md b/changelog.md index 4b2d6e5a1e..c109544cfe 100644 --- a/changelog.md +++ b/changelog.md @@ -25,7 +25,6 @@ * [1767](https://github.com/zeta-chain/node/pull/1767) - add unit tests for emissions module begin blocker * [1816](https://github.com/zeta-chain/node/pull/1816) - add args to e2e tests * [1791](https://github.com/zeta-chain/node/pull/1791) - add e2e tests for feature of restricted address -* [1787](https://github.com/zeta-chain/node/pull/1787) - add unit tests for cross-chain evm hooks and e2e test failed withdraw to BTC legacy address * [1787](https://github.com/zeta-chain/node/pull/1787) - add unit tests for cross-chain evm hooks and e2e test failed withdraw to BTC legacy address * [1840](https://github.com/zeta-chain/node/pull/1840) - fix code coverage test failures ignored in CI diff --git a/cmd/zetae2e/local/admin.go b/cmd/zetae2e/local/admin.go index 7db44c4453..ea33e946a1 100644 --- a/cmd/zetae2e/local/admin.go +++ b/cmd/zetae2e/local/admin.go @@ -17,6 +17,7 @@ func adminTestRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -63,9 +64,7 @@ func adminTestRoutine( // run erc20 advanced test testsToRun, err := adminRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestPauseZRC20Name, - e2etests.TestUpdateBytecodeName, - e2etests.TestDepositEtherLiquidityCapName, + testNames..., ) if err != nil { return fmt.Errorf("admin tests failed: %v", err) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index eefbcc9657..34cb93041b 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -173,7 +173,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { logger.Print("⚠️ admin tests enabled") } - if testPerformance && !skipRegular { + if testPerformance { logger.Print("⚠️ performance tests enabled, regular tests will be skipped") skipRegular = true } @@ -324,14 +324,18 @@ func localE2ETest(cmd *cobra.Command, _ []string) { eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, !light, ethereumTests...)) } if testAdmin { - eg.Go(adminTestRoutine(conf, deployerRunner, verbose)) + eg.Go(adminTestRoutine(conf, deployerRunner, verbose, + e2etests.TestPauseZRC20Name, + e2etests.TestUpdateBytecodeName, + e2etests.TestDepositEtherLiquidityCapName, + )) } if testPerformance { - eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose)) - eg.Go(ethereumWithdrawPerformanceRoutine(conf, deployerRunner, verbose)) + eg.Go(ethereumDepositPerformanceRoutine(conf, deployerRunner, verbose, e2etests.TestStressEtherDepositName)) + eg.Go(ethereumWithdrawPerformanceRoutine(conf, deployerRunner, verbose, e2etests.TestStressEtherWithdrawName)) } if testCustom { - eg.Go(miscTestRoutine(conf, deployerRunner, verbose)) + eg.Go(miscTestRoutine(conf, deployerRunner, verbose, e2etests.TestMyTestName)) } if err := eg.Wait(); err != nil { diff --git a/cmd/zetae2e/local/misc.go b/cmd/zetae2e/local/misc.go index b3f2b90e4b..bc5240e571 100644 --- a/cmd/zetae2e/local/misc.go +++ b/cmd/zetae2e/local/misc.go @@ -17,6 +17,7 @@ func miscTestRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -58,8 +59,7 @@ func miscTestRoutine( // run misc test testsToRun, err := miscRunner.GetE2ETestsToRunByName( e2etests.AllE2ETests, - //e2etests.TestBlockHeadersName, - e2etests.TestMyTestName, + testNames..., ) if err != nil { return fmt.Errorf("misc tests failed: %v", err) diff --git a/cmd/zetae2e/local/performance.go b/cmd/zetae2e/local/performance.go index 69bbd81860..0c15e799db 100644 --- a/cmd/zetae2e/local/performance.go +++ b/cmd/zetae2e/local/performance.go @@ -14,11 +14,12 @@ import ( "github.com/zeta-chain/zetacore/e2e/runner" ) -// ethereumDepositPerformanceRoutine runs Ethereum withdraw stress tests +// ethereumDepositPerformanceRoutine runs performance tests for Ether deposit func ethereumDepositPerformanceRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -51,7 +52,7 @@ func ethereumDepositPerformanceRoutine( tests, err := r.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestStressEtherDepositName, + testNames..., ) if err != nil { return fmt.Errorf("ethereum deposit performance test failed: %v", err) @@ -67,11 +68,12 @@ func ethereumDepositPerformanceRoutine( } } -// ethereumWithdrawPerformanceRoutine runs Ethereum withdraw stress tests +// ethereumWithdrawPerformanceRoutine runs performance tests for Ether withdraw func ethereumWithdrawPerformanceRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, + testNames ...string, ) func() error { return func() (err error) { // return an error on panic @@ -108,7 +110,7 @@ func ethereumWithdrawPerformanceRoutine( tests, err := r.GetE2ETestsToRunByName( e2etests.AllE2ETests, - e2etests.TestStressEtherWithdrawName, + testNames..., ) if err != nil { return fmt.Errorf("ethereum withdraw performance test failed: %v", err) diff --git a/contrib/localnet/docker-compose-performance.yml b/contrib/localnet/docker-compose-performance.yml index e6fd10dd4e..0374d42030 100644 --- a/contrib/localnet/docker-compose-performance.yml +++ b/contrib/localnet/docker-compose-performance.yml @@ -5,5 +5,5 @@ version: "3" services: orchestrator: - entrypoint: ["/work/start-zetae2e.sh", "local --skip-regular --test-performance"] + entrypoint: ["/work/start-zetae2e.sh", "local --test-performance"] From e32ea4821734b0c81dc2b46db91b784735aadf3c Mon Sep 17 00:00:00 2001 From: lumtis Date: Tue, 12 Mar 2024 09:36:40 +0100 Subject: [PATCH 32/33] changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index cadfe718fe..724979f970 100644 --- a/changelog.md +++ b/changelog.md @@ -31,6 +31,7 @@ * [1840](https://github.com/zeta-chain/node/pull/1840) - fix code coverage test failures ignored in CI * [1851](https://github.com/zeta-chain/node/pull/1851) - rename usdt to erc20 in e2e tests * [1872](https://github.com/zeta-chain/node/pull/1872) - remove usage of RPC in unit test +* [1805](https://github.com/zeta-chain/node/pull/1805) - add admin and performance test and fix upgrade test ### Fixes @@ -93,7 +94,6 @@ * [1584](https://github.com/zeta-chain/node/pull/1584) - allow to run E2E tests on any networks * [1746](https://github.com/zeta-chain/node/pull/1746) - rename smoke tests to e2e tests * [1753](https://github.com/zeta-chain/node/pull/1753) - fix gosec errors on usage of rand package - * [1762](https://github.com/zeta-chain/node/pull/1762) - improve coverage for fungibile module * [1782](https://github.com/zeta-chain/node/pull/1782) - improve coverage for fungibile module system contract From 8838ac9aabbc53f814e66091e3e35fb067325442 Mon Sep 17 00:00:00 2001 From: lumtis Date: Tue, 12 Mar 2024 10:25:50 +0100 Subject: [PATCH 33/33] fix --- e2e/e2etests/test_stress_eth_withdraw.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/e2etests/test_stress_eth_withdraw.go b/e2e/e2etests/test_stress_eth_withdraw.go index 286fee59f7..3be0467069 100644 --- a/e2e/e2etests/test_stress_eth_withdraw.go +++ b/e2e/e2etests/test_stress_eth_withdraw.go @@ -31,7 +31,7 @@ func TestStressEtherWithdraw(r *runner.E2ERunner, args []string) { panic("Invalid number of withdrawals specified for TestStressEtherWithdraw.") } - tx, err := r.ETHZRC20.Approve(r.ZevmAuth, r.ETHZRC20Addr, big.NewInt(1e18)) + tx, err := r.ETHZRC20.Approve(r.ZEVMAuth, r.ETHZRC20Addr, big.NewInt(1e18)) if err != nil { panic(err) }