diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7886a368ce..9632964761 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 240 strategy: matrix: - tests: [unmarked, ibc, ibc_rly_evm, ibc_rly_gas, ibc_timeout, ibc_update_client, ica, gov, upgrade, slow, gas, mint] + tests: [unmarked, ibc, ibc_rly_evm, ibc_rly_gas, ibc_timeout, ibc_update_client, ica, gov, upgrade, slow, gas, mint, staking] env: TESTS_TO_RUN: ${{ matrix.tests }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 26734202ca..94b0213a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ ## UNRELEASED +* [#1907](https://github.com/crypto-org-chain/cronos/pull/1907) fix: Optimize staking endblocker with an in-memory KV store and standardize gas consumption for staking related messages + +### Improvements + +* [#1903](https://github.com/crypto-org-chain/cronos/pull/1903) Feat: check authorization list in e2ee. +* [#1922](https://github.com/crypto-org-chain/cronos/pull/1922) Feat: check destination address in the blocklist +* [#1904](https://github.com/crypto-org-chain/cronos/pull/1904) Test: add eip-7702 tests + +### Bug fixes + +* [#1918](https://github.com/crypto-org-chain/cronos/pull/1918) Chore: cleanup and improve x/mint params validation and test in cosmos-sdk + +*Nov 30, 2025* + +## v1.5.4 + +### Improvements + +* [#1898](https://github.com/crypto-org-chain/cronos/pull/1898) Chore: cleanup release by reverting #1892, #1893 and #1850. +* [#1901](https://github.com/crypto-org-chain/cronos/pull/1901) Feat: add mempool.feebump and disable-tx-replacement flags. +* [#1911](https://github.com/crypto-org-chain/cronos/pull/1911) Fix: bug on multiple tx replacements + + +*Oct 30, 2025* + +## v1.5.3 + +### Bug fixes +* [#1898](https://github.com/crypto-org-chain/cronos/pull/1898) Check authorisation list for blocklisted address. + +## v1.5.2 + +* [#1892](https://github.com/crypto-org-chain/cronos/pull/1892) fix: disable memiavl cache when optimistic execution is enabled. +* [#1893](https://github.com/crypto-org-chain/cronos/pull/1893) Normalize cache validator queue key to be UTC. +* [#1850](https://github.com/crypto-org-chain/cronos/pull/1850) Optimize staking endblocker execution by caching queue entries from iterators. Upgrade RocksDB to `v10.4.2` and enable asyncIO. + +*Oct 15, 2025* + +## v1.5.1 + * [#1869](https://github.com/crypto-org-chain/cronos/pull/1869) Add missing tx context during vm initialisation * [#1872](https://github.com/crypto-org-chain/cronos/pull/1872) Support 4byteTracer for tracer * [#1875](https://github.com/crypto-org-chain/cronos/pull/1875) Support for preinstalls diff --git a/app/app.go b/app/app.go index 0e59acaf18..78f306bbba 100644 --- a/app/app.go +++ b/app/app.go @@ -229,6 +229,7 @@ func StoreKeys() ( map[string]*storetypes.KVStoreKey, map[string]*storetypes.TransientStoreKey, map[string]*storetypes.ObjectStoreKey, + map[string]*storetypes.MemoryStoreKey, ) { storeKeys := []string{ authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, @@ -250,9 +251,10 @@ func StoreKeys() ( } keys := storetypes.NewKVStoreKeys(storeKeys...) tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := storetypes.NewMemoryStoreKeys(stakingtypes.CacheStoreKey, cronostypes.MemStoreKey) okeys := storetypes.NewObjectStoreKeys(banktypes.ObjectStoreKey, evmtypes.ObjectStoreKey) - return keys, tkeys, okeys + return keys, tkeys, okeys, memKeys } var ( @@ -280,10 +282,10 @@ type App struct { pendingTxListeners []evmante.PendingTxListener // keys to access the substores - keys map[string]*storetypes.KVStoreKey - tkeys map[string]*storetypes.TransientStoreKey - okeys map[string]*storetypes.ObjectStoreKey - + keys map[string]*storetypes.KVStoreKey + tkeys map[string]*storetypes.TransientStoreKey + okeys map[string]*storetypes.ObjectStoreKey + memKeys map[string]*storetypes.MemoryStoreKey // keepers AccountKeeper authkeeper.AccountKeeper BankKeeper bankkeeper.Keeper @@ -429,7 +431,7 @@ func New( bApp.SetInterfaceRegistry(interfaceRegistry) bApp.SetTxEncoder(txConfig.TxEncoder()) - keys, tkeys, okeys := StoreKeys() + keys, tkeys, okeys, memKeys := StoreKeys() invCheckPeriod := cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)) app := &App{ @@ -443,6 +445,7 @@ func New( keys: keys, tkeys: tkeys, okeys: okeys, + memKeys: memKeys, blockProposalHandler: blockProposalHandler, dummyCheckTx: cast.ToBool(appOpts.Get(FlagUnsafeDummyCheckTx)), } @@ -498,14 +501,17 @@ func New( panic(err) } app.txConfig = txConfig + stakingCacheSize := cast.ToInt(appOpts.Get(server.FlagStakingCacheSize)) app.StakingKeeper = stakingkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), + runtime.NewMemStoreService(memKeys[stakingtypes.CacheStoreKey]), app.AccountKeeper, app.BankKeeper, authAddr, address.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()), address.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()), + stakingCacheSize, ) app.MintKeeper = mintkeeper.NewKeeper( appCodec, @@ -648,7 +654,7 @@ func New( app.CronosKeeper = *cronoskeeper.NewKeeper( appCodec, keys[cronostypes.StoreKey], - keys[cronostypes.MemStoreKey], + memKeys[cronostypes.MemStoreKey], app.BankKeeper, app.TransferKeeper, app.EvmKeeper, @@ -957,7 +963,7 @@ func New( app.MountKVStores(keys) app.MountTransientStores(tkeys) app.MountObjectStores(okeys) - + app.MountMemoryStores(memKeys) // initialize BaseApp app.SetInitChainer(app.InitChainer) app.SetPreBlocker(app.PreBlocker) diff --git a/cmd/cronosd/cmd/versiondb.go b/cmd/cronosd/cmd/versiondb.go index eabda29d21..c615321002 100644 --- a/cmd/cronosd/cmd/versiondb.go +++ b/cmd/cronosd/cmd/versiondb.go @@ -13,7 +13,7 @@ import ( ) func ChangeSetCmd() *cobra.Command { - keys, _, _ := app.StoreKeys() + keys, _, _, _ := app.StoreKeys() storeNames := make([]string, 0, len(keys)) for name := range keys { storeNames = append(storeNames, name) diff --git a/go.mod b/go.mod index 7e652759aa..1d4992bcf4 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/cosmos/cosmos-db v1.1.3 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.53.0 - github.com/cosmos/gogoproto v1.7.0 + github.com/cosmos/gogoproto v1.7.2 // release/v10.0.x github.com/cosmos/ibc-go/v10 v10.1.1 github.com/cosmos/rosetta v0.50.12 @@ -77,8 +77,8 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.2.0 // indirect github.com/bits-and-blooms/bitset v1.22.0 // indirect - github.com/btcsuite/btcd v0.24.2 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd v0.25.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect @@ -269,7 +269,7 @@ require ( golang.org/x/time v0.10.0 // indirect google.golang.org/api v0.222.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect nhooyr.io/websocket v1.8.11 // indirect @@ -281,7 +281,8 @@ require ( replace ( cosmossdk.io/store => github.com/crypto-org-chain/cosmos-sdk/store v0.0.0-20241217090828-cfbca9fe8254 cosmossdk.io/x/tx => github.com/crypto-org-chain/cosmos-sdk/x/tx v0.0.0-20241217090828-cfbca9fe8254 - github.com/cosmos/cosmos-sdk => github.com/crypto-org-chain/cosmos-sdk v0.50.6-0.20251119062431-8d0a31ef043d + // release/v0.50-cronosv1.6.x + github.com/cosmos/cosmos-sdk => github.com/crypto-org-chain/cosmos-sdk v0.0.0-20251121110054-d5e74b9954c1 ) replace ( @@ -303,7 +304,7 @@ replace ( // release/v1.15 github.com/ethereum/go-ethereum => github.com/crypto-org-chain/go-ethereum v1.10.20-0.20250815065500-a4fbafcae0dd // develop - github.com/evmos/ethermint => github.com/crypto-org-chain/ethermint v0.22.1-0.20251007011737-164da0caf703 + github.com/evmos/ethermint => github.com/randy-cro/ethermint v0.0.0-20251121082919-46c057ac4dde // Fix upstream GHSA-h395-qcrw-5vmq and GHSA-3vp4-m3rf-835h vulnerabilities. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.0 diff --git a/go.sum b/go.sum index 516fa542d5..bbb065e1ff 100644 --- a/go.sum +++ b/go.sum @@ -744,12 +744,13 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd v0.25.0 h1:JPbjwvHGpSywBRuorFFqTjaVP4y6Qw69XJ1nQ6MyWJM= +github.com/btcsuite/btcd v0.25.0/go.mod h1:qbPE+pEiR9643E1s1xu57awsRhlCIm1ZIi6FfeRA4KE= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= -github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= +github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= @@ -869,8 +870,8 @@ github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4x github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= -github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= -github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/cosmos/gogoproto v1.7.2 h1:5G25McIraOC0mRFv9TVO139Uh3OklV2hczr13KKVHCA= +github.com/cosmos/gogoproto v1.7.2/go.mod h1:8S7w53P1Y1cHwND64o0BnArT6RmdgIvsBuco6uTllsk= github.com/cosmos/iavl v1.2.6 h1:Hs3LndJbkIB+rEvToKJFXZvKo6Vy0Ex1SJ54hhtioIs= github.com/cosmos/iavl v1.2.6/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= github.com/cosmos/ibc-go/v10 v10.1.1 h1:Mtl0Ydr9dVdOrPqmxCAG49RmX2/VDYeKYdwv3G2y0g8= @@ -904,14 +905,12 @@ github.com/crypto-org-chain/btree v0.0.0-20240406140148-2687063b042c h1:MOgfS4+F github.com/crypto-org-chain/btree v0.0.0-20240406140148-2687063b042c/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/crypto-org-chain/cometbft v0.0.0-20251014161156-b0e778b18408 h1:7dfWkDRYCsguKrpd0t14nrZ3Xf/9aVHiQrWx5o0DCdo= github.com/crypto-org-chain/cometbft v0.0.0-20251014161156-b0e778b18408/go.mod h1:khbgmtxbgwJfMqDmnGY4rl2sQpTdzpPb1f9nqnfpy1o= -github.com/crypto-org-chain/cosmos-sdk v0.50.6-0.20251119062431-8d0a31ef043d h1:ffzsdKbhbSSBIMBAGJGjezjEr60A/JgpznOJhMUMbfE= -github.com/crypto-org-chain/cosmos-sdk v0.50.6-0.20251119062431-8d0a31ef043d/go.mod h1:8/AdT5lF3ILCCl/sDQXyBgzWGtcmD1tInWyhYeREVPA= +github.com/crypto-org-chain/cosmos-sdk v0.0.0-20251121110054-d5e74b9954c1 h1:4aMpMx19bBo0vXDLx9jBtBW/BvWyOVW5SN4ciC7WhDc= +github.com/crypto-org-chain/cosmos-sdk v0.0.0-20251121110054-d5e74b9954c1/go.mod h1:8/AdT5lF3ILCCl/sDQXyBgzWGtcmD1tInWyhYeREVPA= github.com/crypto-org-chain/cosmos-sdk/store v0.0.0-20241217090828-cfbca9fe8254 h1:NEgy0r3otU/O+0OAjMdEhbn4VotQlg+98hHbD7M23wU= github.com/crypto-org-chain/cosmos-sdk/store v0.0.0-20241217090828-cfbca9fe8254/go.mod h1:8DwVTz83/2PSI366FERGbWSH7hL6sB7HbYp8bqksNwM= github.com/crypto-org-chain/cosmos-sdk/x/tx v0.0.0-20241217090828-cfbca9fe8254 h1:JzLOFRiKsDtLJt5h0M0jkEIPDKvFFyja7VEp7gG6O9U= github.com/crypto-org-chain/cosmos-sdk/x/tx v0.0.0-20241217090828-cfbca9fe8254/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w= -github.com/crypto-org-chain/ethermint v0.22.1-0.20251007011737-164da0caf703 h1:O0DF++IbEl5TAknuXtAcxFTocZ8zO8DlZRUMIo9HnLA= -github.com/crypto-org-chain/ethermint v0.22.1-0.20251007011737-164da0caf703/go.mod h1:GVopiVE4ftDRfAm3e6qj7URhNTa3Tv3JrCbfO/s8P/I= github.com/crypto-org-chain/go-block-stm v0.0.0-20241213061541-7afe924fb4a6 h1:6KPEi8dWkDSBddQb4NAvEXmNnTXymF3yVeTaT4Hz1iU= github.com/crypto-org-chain/go-block-stm v0.0.0-20241213061541-7afe924fb4a6/go.mod h1:iwQTX9xMX8NV9k3o2BiWXA0SswpsZrDk5q3gA7nWYiE= github.com/crypto-org-chain/go-ethereum v1.10.20-0.20250815065500-a4fbafcae0dd h1:ebSnzvM9yKVGFjvoGly7LFQQCS2HuOWMCvQyByJ52Gs= @@ -1501,8 +1500,8 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE= -github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw= +github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= +github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -1609,6 +1608,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/randy-cro/ethermint v0.0.0-20251121082919-46c057ac4dde h1:6aXJ2U+NOAAaetAuPJ9ixLCtByn+hqC/MwnnafrpjUQ= +github.com/randy-cro/ethermint v0.0.0-20251121082919-46c057ac4dde/go.mod h1:ZLSoAlnXOn5fiK1+BZSEi7xeuXU5W2SSEU1UeGVNwgY= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -1807,8 +1808,6 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -2514,8 +2513,8 @@ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= diff --git a/gomod2nix.toml b/gomod2nix.toml index 0f632cdb93..295dab7289 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -137,11 +137,11 @@ schema = 3 version = "v1.22.0" hash = "sha256-lY1K29h4vlAmJVvwKgbTG8BTACYGjFaginCszN+ST6w=" [mod."github.com/btcsuite/btcd"] - version = "v0.24.2" - hash = "sha256-ahlpwEr4KfyrEA899X07QtuSDnC8U+SnwL+z72DiK5E=" + version = "v0.25.0" + hash = "sha256-Yh3UJ8HmzY+5WXZHhcl3oFcXl2PkBTd4O4s8FYyNbos=" [mod."github.com/btcsuite/btcd/btcec/v2"] - version = "v2.3.4" - hash = "sha256-9fV41jYeTUrpoyu19LPuGBG/N9wFv6D6wVBE8R5WzRI=" + version = "v2.3.5" + hash = "sha256-stpoaGQ1PNPqtLYIQc96YH24s8owcV+PoSo6xREi9LI=" [mod."github.com/btcsuite/btcd/btcutil"] version = "v1.1.6" hash = "sha256-TYbwJLNX/+63nm+b3RqPH3ZIvTBnsm9peqJP05v9Z90=" @@ -213,8 +213,8 @@ schema = 3 version = "v1.0.0-beta.5" hash = "sha256-Fy/PbsOsd6iq0Njy3DVWK6HqWsogI+MkE8QslHGWyVg=" [mod."github.com/cosmos/cosmos-sdk"] - version = "v0.50.6-0.20251119062431-8d0a31ef043d" - hash = "sha256-VxQus9ynUK8nAZh3ubNXRcxJsITzgndjd7UYYgMt6C0=" + version = "v0.0.0-20251121110054-d5e74b9954c1" + hash = "sha256-XzBX/BIFpKZWMBqyGML0RpSBHMnQu1QVY9+dMi85mws=" replaced = "github.com/crypto-org-chain/cosmos-sdk" [mod."github.com/cosmos/go-bip39"] version = "v1.0.0" @@ -223,8 +223,8 @@ schema = 3 version = "v1.2.0" hash = "sha256-Hd19V0RCiMoCL67NsqvWIsvWF8KM3LnuJTbYjWtQkEo=" [mod."github.com/cosmos/gogoproto"] - version = "v1.7.0" - hash = "sha256-ZkEUImxBBo8Q/6c7tVR0rybpLbtlplzvgfLl5xvtV00=" + version = "v1.7.2" + hash = "sha256-L9sJZoQGWaix43AJ7rsm1DUng8uoD8HJ6Mb92Ynq8+s=" [mod."github.com/cosmos/iavl"] version = "v1.2.6" hash = "sha256-9kLtVepU5b3m2Sne8pBQNvF9LxM374LEmvuLWeYBfFU=" @@ -312,9 +312,9 @@ schema = 3 version = "v0.2.2" hash = "sha256-0MLfSJKdeK3Z7tWAXTdzwB4091dmyxIX38S5SKH5QAw=" [mod."github.com/evmos/ethermint"] - version = "v0.22.1-0.20251007011737-164da0caf703" - hash = "sha256-lWvRrVuhssOIMCv07iXwJLfN2gFRgsbK8HBiqMBTMew=" - replaced = "github.com/crypto-org-chain/ethermint" + version = "v0.0.0-20251121082919-46c057ac4dde" + hash = "sha256-8eJk4xUTm1aRKJiuHdTv0ZGg4kwTUrTiO8E/NyHlU8A=" + replaced = "github.com/randy-cro/ethermint" [mod."github.com/fatih/color"] version = "v1.17.0" hash = "sha256-QsKMy3MsvjbYNcA9jP8w6c3wpmWDZ0079bybAEzmXR0=" @@ -780,7 +780,7 @@ schema = 3 version = "v0.0.0-20250707201910-8d1bb00bc6a7" hash = "sha256-xtTBmzlyynWQa0KtuQpNZ4fzSTB/5ozXclE3SuP3naI=" [mod."google.golang.org/genproto/googleapis/rpc"] - version = "v0.0.0-20250707201910-8d1bb00bc6a7" + version = "v0.0.0-20250804133106-a7a43d27e69b" hash = "sha256-WK7iDtAhH19NPe3TywTQlGjDawNaDKWnxhFL9PgVUwM=" [mod."google.golang.org/grpc"] version = "v1.75.1" diff --git a/integration_tests/configs/staking_cache.jsonnet b/integration_tests/configs/staking_cache.jsonnet new file mode 100644 index 0000000000..804f0c3cab --- /dev/null +++ b/integration_tests/configs/staking_cache.jsonnet @@ -0,0 +1,270 @@ +{ + 'cronos_777-1': { + 'start-flags': '--trace', + cmd: 'cronosd', + + local _1mil_tcro = '1000000000000000000000000basetcro', + local _10quintillion_stake = '10000000000000000000stake', + local _1quintillion_stake = '1000000000000000000stake', + local _1mil_qatest = '1000000qatest', + + validators: [ + { + coins: std.join(',', [_1mil_tcro, _10quintillion_stake]), + staked: _1quintillion_stake, + mnemonic: 'elbow flight coast travel move behind sister tell avocado road wait above', + gas_prices: '100000000000000000basetcro', + base_port: 26650, + 'app-config': { + staking: { + 'cache-size': -1, // disabled + }, + }, + }, + { + coins: std.join(',', [_1mil_tcro, _10quintillion_stake]), + staked: _1quintillion_stake, + mnemonic: 'nasty large defy garage violin casual alarm blue marble industry infant inside', + gas_prices: '100000000000000000basetcro', + base_port: 26660, + 'app-config': { + staking: { + 'cache-size': 0, // unlimited + }, + }, + }, + { + coins: std.join(',', [_1mil_tcro, _10quintillion_stake]), + staked: _1quintillion_stake, + mnemonic: 'lobster culture confirm twist oak sock lucky core kiss echo term faint robot purity fluid mix rescue music drive spot term pistol feed abuse', + gas_prices: '100000000000000000basetcro', + base_port: 26670, + 'app-config': { + staking: { + 'cache-size': 1, // size limit 1 + }, + }, + }, + { + coins: std.join(',', [_1mil_tcro, _10quintillion_stake]), + staked: _1quintillion_stake, + mnemonic: 'wonder grocery sing soccer two portion shift science gain tuition mean garbage feed execute brush civil buddy filter mandate aunt rocket quarter aim first', + gas_prices: '100000000000000000basetcro', + base_port: 26680, + 'app-config': { + staking: { + 'cache-size': 2, // size limit 2 + }, + }, + }, + { + coins: std.join(',', [_1mil_tcro, _10quintillion_stake]), + staked: _1quintillion_stake, + mnemonic: 'super develop desert oak load field ring jazz tray spray found novel', + gas_prices: '100000000000000000basetcro', + base_port: 26690, + 'app-config': { + staking: { + 'cache-size': 3, // size limit 3 + }, + }, + }, + { + coins: std.join(',', [_1mil_tcro, _10quintillion_stake]), + staked: _1quintillion_stake, + mnemonic: 'author satoshi neck arm afraid route carbon invite frozen drink upon point devote slow chase', + gas_prices: '100000000000000000basetcro', + base_port: 26700, + 'app-config': { + staking: { + 'cache-size': 100, // size limit 100 + }, + }, + }, + { + coins: std.join(',', [_1mil_tcro, _10quintillion_stake]), + staked: _1quintillion_stake, + mnemonic: 'visual loyal reward cloud other remember sting control half flight maze unveil cherry elite carry', + gas_prices: '100000000000000000basetcro', + base_port: 26710, + 'app-config': { + staking: { + 'cache-size': 1000, // size limit 1000 + }, + }, + }, + ], + accounts: [ + { + name: 'rich', + coins: std.join(',', [_1mil_tcro, _1mil_qatest, _10quintillion_stake]), + mnemonic: 'loyal legend allow glow wheel heavy pretty example tell peasant myself garlic battle bachelor buddy stand true grit manual letter wire alone polar glove', + }, + { + name: 'alice', + coins: std.join(',', [_1mil_tcro, _1mil_qatest, _10quintillion_stake]), + mnemonic: 'style recipe economy valve curtain raw scare unable chair silly impact thrive moment copy able voyage slush diary adjust boss smile finger volume reward', + }, + { + name: 'bob', + coins: std.join(',', [_1mil_tcro, _1mil_qatest, _10quintillion_stake]), + mnemonic: 'frost worth crisp gasp this waste harbor able ethics raise december tent kid brief banner frame absent fragile police garage remind stomach side midnight', + }, + { + name: 'charlie', + coins: std.join(',', [_1mil_tcro, _1mil_qatest, _10quintillion_stake]), + mnemonic: 'worth lounge teach critic forward disease shy genuine rain gorilla end depth sort clutch museum festival stay joke custom anchor seven outside equip crawl', + }, + ], + + config: { + 'unsafe-ignore-block-list-failure': true, + consensus: { + timeout_commit: '1s', + create_empty_blocks_interval: '1s', + }, + }, + + 'app-config': { + 'minimum-gas-prices': '5000000000000basetcro', + 'app-db-backend': 'goleveldb', + pruning: 'nothing', + rosetta: { + 'denom-to-suggest': 'basetcro', + }, + evm: { + 'max-tx-gas-wanted': 0, + }, + 'json-rpc': { + address: '0.0.0.0:{EVMRPC_PORT}', + 'ws-address': '0.0.0.0:{EVMRPC_PORT_WS}', + api: 'eth,net,web3,debug,cronos', + 'block-range-cap': 30, + 'evm-timeout': '10s', + }, + 'blocked-addresses': [], + mempool: { + 'max-txs': 0, + }, + }, + genesis: { + consensus: { + params: { + block: { + max_bytes: '1048576', + max_gas: '81500000', + }, + evidence: { + max_age_num_blocks: '403200', + max_age_duration: '2419200000000000', + max_bytes: '150000', + }, + }, + }, + app_state: { + bank: { + send_enabled: [ + { + denom: 'stake', + enabled: true, + }, + { + denom: 'basetcro', + enabled: false, + }, + ], + }, + cronos: { + params: { + cronos_admin: 'crc12luku6uxehhak02py4rcz65zu0swh7wjsrw0pp', + ibc_cro_denom: 'ibc/6B5A664BF0AF4F71B2F0BAA33141E2F1321242FBD5D19762F541EC971ACB0865', + }, + }, + distribution: { + params: { + community_tax: '0', + base_proposer_reward: '0', + bonus_proposer_reward: '0', + }, + }, + evm: { + params: { + evm_denom: 'basetcro', + }, + }, + gov: { + params: { + min_deposit: [ + { + denom: 'basetcro', + amount: '5', + }, + ], + max_deposit_period: '30s', + voting_period: '30s', + expedited_voting_period: '15s', + expedited_min_deposit: [ + { + denom: 'basetcro', + amount: '25', + }, + ], + }, + }, + ibc: { + client_genesis: { + params: { + allowed_clients: [ + '06-solomachine', + '07-tendermint', + '09-localhost', + ], + }, + }, + }, + mint: { + minter: { + inflation: '0.000000000000000000', + annual_provisions: '0.000000000000000000', + }, + params: { + inflation_rate_change: '0', + inflation_max: '0', + inflation_min: '0', + goal_bonded: '1', + }, + }, + slashing: { + params: { + downtime_jail_duration: '60s', + min_signed_per_window: '0.5', + signed_blocks_window: '10', + slash_fraction_double_sign: '0', + slash_fraction_downtime: '0', + }, + }, + staking: { + params: { + unbonding_time: '60s', + max_validators: '50', + }, + }, + feemarket: { + // from https://rest-t3.cronos.org/ethermint/feemarket/v1/params + params: { + no_base_fee: false, + base_fee_change_denominator: 100, + elasticity_multiplier: 4, + // enabled at genesis, different from testnet + enable_height: '0', + // initial base fee at genesis, testnet shows the current base fee, hence different + base_fee: '1000000000', + min_gas_price: '1000000000', + min_gas_multiplier: '0.500000000000000000', + }, + }, + }, + }, + }, +} + diff --git a/integration_tests/cosmoscli.py b/integration_tests/cosmoscli.py index 04ab825984..9bc9528e11 100644 --- a/integration_tests/cosmoscli.py +++ b/integration_tests/cosmoscli.py @@ -19,6 +19,7 @@ # the default initial base fee used by integration tests DEFAULT_GAS_PRICE = "100000000000basetcro" DEFAULT_GAS = "250000" +STAKING_DEFAULT_GAS = "1000000" class ModuleAccount(enum.Enum): @@ -409,40 +410,71 @@ def get_delegated_amount(self, which_addr): ) ) - def delegate_amount(self, to_addr, amount, from_addr, gas_price=None): - if gas_price is None: - return json.loads( - self.raw( - "tx", - "staking", - "delegate", - to_addr, - amount, - "-y", - home=self.data_dir, - from_=from_addr, - keyring_backend="test", - chain_id=self.chain_id, - node=self.node_rpc, - ) + def get_delegations(self, which_addr): + """Query all delegations made from one delegator.""" + return json.loads( + self.raw( + "query", + "staking", + "delegations", + which_addr, + home=self.data_dir, + chain_id=self.chain_id, + node=self.node_rpc, + output="json", ) - else: - return json.loads( - self.raw( - "tx", - "staking", - "delegate", - to_addr, - amount, - "-y", - home=self.data_dir, - from_=from_addr, - keyring_backend="test", - chain_id=self.chain_id, - node=self.node_rpc, - gas_prices=gas_price, - ) + ) + + def get_unbonding_delegations(self, which_addr): + """Query all unbonding delegations from a delegator.""" + return json.loads( + self.raw( + "query", + "staking", + "unbonding-delegations", + which_addr, + home=self.data_dir, + chain_id=self.chain_id, + node=self.node_rpc, + output="json", + ) + ).get("unbonding_responses", []) + + def get_redelegations(self, delegator_addr, src_validator_addr, dst_validator_addr): + """Query all redelegations from a delegator.""" + return json.loads( + self.raw( + "query", + "staking", + "redelegation", + delegator_addr, + src_validator_addr, + dst_validator_addr, + home=self.data_dir, + chain_id=self.chain_id, + node=self.node_rpc, + output="json", + ) + ).get("redelegation_responses", []) + + def delegate_amount(self, to_addr, amount, from_addr): + return json.loads( + self.raw( + "tx", + "staking", + "delegate", + to_addr, + amount, + "-y", + home=self.data_dir, + from_=from_addr, + keyring_backend="test", + chain_id=self.chain_id, + node=self.node_rpc, + gas_prices=DEFAULT_GAS_PRICE, + gas=STAKING_DEFAULT_GAS, ) + ) # to_addr: croclcl1... , from_addr: cro1... def unbond_amount(self, to_addr, amount, from_addr): @@ -459,6 +491,8 @@ def unbond_amount(self, to_addr, amount, from_addr): keyring_backend="test", chain_id=self.chain_id, node=self.node_rpc, + gas=STAKING_DEFAULT_GAS, + gas_prices=DEFAULT_GAS_PRICE, ) ) @@ -480,6 +514,8 @@ def redelegate_amount( keyring_backend="test", chain_id=self.chain_id, node=self.node_rpc, + gas=STAKING_DEFAULT_GAS, + gas_prices=DEFAULT_GAS_PRICE, ) ) @@ -693,6 +729,7 @@ def edit_validator( website=None, security_contact=None, details=None, + min_self_delegation=None, ): """MsgEditValidator""" options = dict( @@ -703,6 +740,7 @@ def edit_validator( website=website, security_contact=security_contact, details=details, + min_self_delegation=min_self_delegation, ) return json.loads( self.raw( @@ -715,6 +753,8 @@ def edit_validator( node=self.node_rpc, keyring_backend="test", chain_id=self.chain_id, + gas_prices=DEFAULT_GAS_PRICE, + gas=STAKING_DEFAULT_GAS, **{k: v for k, v in options.items() if v is not None}, ) ) diff --git a/integration_tests/test_staking_cache.py b/integration_tests/test_staking_cache.py new file mode 100644 index 0000000000..7414e8a673 --- /dev/null +++ b/integration_tests/test_staking_cache.py @@ -0,0 +1,977 @@ +""" +Test staking cache functionality with different cache sizes. + +This test verifies that staking operations work correctly across nodes +with different cache size configurations: +- Node 0: cache-size = -1 (disabled) +- Node 1: cache-size = 0 (unlimited) +- Node 2: cache-size = 1 +- Node 3: cache-size = 2 +- Node 4: cache-size = 3 +- Node 5: cache-size = 100 +- Node 6: cache-size = 1000 + +Test scenarios: +1. Multiple unbonding operations from different delegators +2. Multiple redelegations between validators +3. Unbonding validators by removing all self-delegation +""" + +import pytest + +from .network import setup_custom_cronos +from .utils import wait_for_new_blocks + +pytestmark = pytest.mark.staking + + +@pytest.fixture(scope="function") +def cronos_staking_cache(tmp_path_factory): + """Setup cronos cluster with different staking cache sizes per node.""" + from pathlib import Path + + path = tmp_path_factory.mktemp("staking_cache") + yield from setup_custom_cronos( + path, + 26650, + Path(__file__).parent / "configs/staking_cache.jsonnet", + ) + + +def get_delegator_address(cli, account_name): + """Get delegator address for a specific account.""" + return cli.address(account_name) + + +def test_staking_cache_multiple_unbonding(cronos_staking_cache): + """ + Test multiple unbonding operations across nodes with different cache sizes. + + This test performs multiple unbonding operations from different accounts + to different validators and verifies that all nodes maintain consistent state + regardless of their cache configuration. + """ + cronos = cronos_staking_cache + + # Get CLI instances for different nodes + cli = cronos.cosmos_cli() + + # Get validator addresses + validators = [] + for i in range(7): + val_addr = cronos.cosmos_cli(i).address("validator", bech="val") + validators.append(val_addr) + + print(f"Validators: {validators}") + + # Delegate some tokens from rich account to all validators + rich_addr = get_delegator_address(cli, "rich") + delegation_amount = "1000000000000000000stake" # 1 stake token + + print("\n=== Phase 1: Delegating to validators ===") + for i, val_addr in enumerate(validators): + print(f"Delegating to validator {i}: {val_addr}") + rsp = cli.delegate_amount( + val_addr, + delegation_amount, + "rich", + ) + assert rsp["code"] == 0, f"Delegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Verify delegations were successful on all nodes + print("\n=== Verifying delegations on all nodes ===") + delegation_counts = [] + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + delegations = node_cli.get_delegated_amount(rich_addr) + delegation_responses = ( + delegations.get("delegation_responses", []) if delegations else [] + ) + count = len(delegation_responses) + delegation_counts.append(count) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + print(f"Node {node_idx} (cache-size={cache_size}): " f"{count} delegations") + + # Verify all nodes have the same number of delegations + assert ( + len(set(delegation_counts)) == 1 + ), f"Nodes have different delegation counts: {delegation_counts}" + print(f"✓ All nodes consistent: {delegation_counts[0]} delegations each") + + # Perform multiple unbonding operations from different accounts + print("\n=== Phase 2: Multiple unbonding operations ===") + + # Unbond from rich account + unbond_amount = "500000000000000000stake" # 0.5 stake token + unbonding_ops = [] + + for i in range(3): # Unbond from first 3 validators + val_addr = validators[i] + print(f"Unbonding from validator {i}: {val_addr}") + rsp = cli.unbond_amount(val_addr, unbond_amount, "rich") + assert rsp["code"] == 0, f"Unbonding failed: {rsp.get('raw_log', rsp)}" + unbonding_ops.append((rich_addr, val_addr, unbond_amount)) + wait_for_new_blocks(cli, 2) + + # Delegate from alice and then unbond + alice_addr = get_delegator_address(cli, "alice") + print("\nDelegating from alice to validator 3") + rsp = cli.delegate_amount(validators[3], delegation_amount, "alice") + assert rsp["code"] == 0, f"Alice delegation failed: {rsp.get('raw_log', rsp)}" + + wait_for_new_blocks(cli, 2) + + print("Unbonding from alice") + rsp = cli.unbond_amount(validators[3], unbond_amount, "alice") + assert rsp["code"] == 0, f"Alice unbonding failed: {rsp.get('raw_log', rsp)}" + unbonding_ops.append((alice_addr, validators[3], unbond_amount)) + + # Delegate from bob and then unbond + bob_addr = get_delegator_address(cli, "bob") + print("\nDelegating from bob to validator 4") + rsp = cli.delegate_amount(validators[4], delegation_amount, "bob") + assert rsp["code"] == 0, f"Bob delegation failed: {rsp.get('raw_log', rsp)}" + + wait_for_new_blocks(cli, 2) + + print("Unbonding from bob") + rsp = cli.unbond_amount(validators[4], unbond_amount, "bob") + assert rsp["code"] == 0, f"Bob unbonding failed: {rsp.get('raw_log', rsp)}" + unbonding_ops.append((bob_addr, validators[4], unbond_amount)) + + wait_for_new_blocks(cli, 2) + + # Verify unbonding entries exist on all nodes + print("\n=== Phase 3: Verifying unbonding entries on all nodes ===") + + # Get unique delegator addresses from unbonding operations + unique_delegators = set(delegator_addr for delegator_addr, _, _ in unbonding_ops) + + # Collect total unbonding delegation counts from each node + total_unbonding_counts = [] + + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + # Get unbonding delegations for all delegators + total_count = 0 + for delegator_addr in unique_delegators: + unbonding = node_cli.get_unbonding_delegations(delegator_addr) + count = len(unbonding) if unbonding else 0 + total_count += count + + total_unbonding_counts.append(total_count) + msg = ( + f"Node {node_idx} (cache-size={cache_size}): " + f"{total_count} total unbonding delegations" + ) + print(msg) + + # Verify all nodes have the same total count + assert ( + len(set(total_unbonding_counts)) == 1 + ), f"Nodes have different unbonding delegation counts: {total_unbonding_counts}" + msg = ( + f"Node {node_idx} (cache-size={cache_size}): " + f"{total_count} total unbonding delegations" + ) + print(msg) + + # Wait for unbonding period to complete (20 seconds, 20 blocks) + print("\n=== Phase 4: Waiting for unbonding period to complete ===") + print("Waiting 60 seconds for unbonding delegations to mature...") + wait_for_new_blocks(cli, 60) + + # Verify unbonding delegations are now empty/matured on all nodes + print( + "\n=== Phase 5: Verifying unbonding delegations matured " + "(should be empty) ===" + ) + matured_unbonding_counts = [] + + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + # Get unbonding delegations for all delegators + total_count = 0 + for delegator_addr in unique_delegators: + unbonding = node_cli.get_unbonding_delegations(delegator_addr) + count = len(unbonding) if unbonding else 0 + total_count += count + + matured_unbonding_counts.append(total_count) + msg = ( + f"Node {node_idx} (cache-size={cache_size}): " + f"{total_count} unbonding delegations remaining" + ) + print(msg) + + # Verify all nodes agree that unbonding delegations are empty + assert ( + len(set(matured_unbonding_counts)) == 1 + ), f"Nodes have different matured unbonding counts: {matured_unbonding_counts}" + assert matured_unbonding_counts[0] == 0, ( + f"Expected 0 unbonding delegations after maturation " + f"but got {matured_unbonding_counts[0]}" + ) + msg = ( + f"✓ All nodes consistent: {matured_unbonding_counts[0]} " + f"unbonding delegations (all matured)" + ) + print(msg) + + print("\n=== Test completed successfully ===") + + +def test_staking_cache_multiple_redelegations(cronos_staking_cache): + """ + Test multiple redelegation operations across nodes with different cache sizes. + + This test performs multiple redelegations between validators and verifies + that all nodes maintain consistent state regardless of cache configuration. + """ + cronos = cronos_staking_cache + cli = cronos.cosmos_cli() + + # Get validator addresses + validators = [] + for i in range(7): + val_addr = cronos.cosmos_cli(i).address("validator", bech="val") + validators.append(val_addr) + + print(f"Validators: {validators}") + + # Get delegator addresses + charlie_addr = get_delegator_address(cli, "charlie") + alice_addr = get_delegator_address(cli, "alice") + + delegation_amount = "2000000000000000000stake" # 2 stake tokens + + print("\n=== Phase 1: Initial delegations ===") + + # Charlie delegates to first 3 validators + print("Charlie delegating to validators 0, 1, 2") + for i in range(3): + print(f" Delegating to validator {i}") + rsp = cli.delegate_amount(validators[i], delegation_amount, "charlie") + assert rsp["code"] == 0, f"Charlie delegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Alice delegates to validators 1, 2, 3 + print("Alice delegating to validators 1, 2, 3") + for i in range(1, 4): + print(f" Delegating to validator {i}") + rsp = cli.delegate_amount(validators[i], delegation_amount, "alice") + assert rsp["code"] == 0, f"Alice delegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Perform multiple redelegations + print("\n=== Phase 2: Redelegations ===") + redelegate_amount = "1000000000000000000stake" # 1 stake token + + # Charlie: Redelegate once (from validator 0 to validator 3) + print("Charlie redelegating from validator 0 to validator 3") + rsp = cli.redelegate_amount( + validators[3], validators[0], redelegate_amount, "charlie" # to # from + ) + assert rsp["code"] == 0, f"Charlie redelegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Alice: First redelegation (from validator 1 to validator 0) + print("Alice redelegating from validator 1 to validator 0") + rsp = cli.redelegate_amount( + validators[0], validators[1], redelegate_amount, "alice" # to # from + ) + assert rsp["code"] == 0, f"Alice redelegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Alice: Second redelegation (from validator 2 to validator 4) + print("Alice redelegating from validator 2 to validator 4") + rsp = cli.redelegate_amount( + validators[4], validators[2], redelegate_amount, "alice" # to # from + ) + assert rsp["code"] == 0, f"Alice redelegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Verify redelegation consistency across all nodes + print("\n=== Phase 3: Verifying redelegation consistency on all nodes ===") + + # Expected redelegations: (src_validator, dst_validator, delegator_name) + expected_redelegations = [ + # Charlie: validator 0 -> 3 + (validators[0], validators[3], charlie_addr, "Charlie"), + # Alice: validator 1 -> 0 + (validators[1], validators[0], alice_addr, "Alice"), + # Alice: validator 2 -> 4 + (validators[2], validators[4], alice_addr, "Alice"), + ] + + for src_val, dst_val, delegator_addr, delegator_name in expected_redelegations: + redelegation_counts = [] + + msg = ( + f"\nChecking {delegator_name}'s redelegation from " + f"{src_val}... to {dst_val}...:" + ) + print(msg) + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + redelegations = node_cli.get_redelegations(delegator_addr, src_val, dst_val) + count = len(redelegations) if redelegations else 0 + redelegation_counts.append(count) + msg = ( + f" Node {node_idx} (cache-size={cache_size}): " + f"{count} redelegation(s)" + ) + print(msg) + + # Verify consistency across nodes + assert len(set(redelegation_counts)) == 1, ( + f"{delegator_name}'s redelegation has different counts " + f"across nodes: {redelegation_counts}" + ) + + # Verify we have exactly 1 redelegation entry + assert redelegation_counts[0] == 1, ( + f"{delegator_name}'s redelegation expected 1 entry " + f"but got {redelegation_counts[0]}" + ) + + print(f" ✓ All nodes consistent: {redelegation_counts[0]} redelegation entry") + + # Wait for redelegation completion period (20 seconds, 20 blocks) + print("\n=== Phase 4: Waiting for redelegation completion period ===") + print("Waiting 60 seconds for redelegations to complete...") + wait_for_new_blocks(cli, 60) + + # Verify redelegations are now empty/completed on all nodes + print("\n=== Phase 5: Verifying redelegations completed (should be empty) ===") + + for src_val, dst_val, delegator_addr, delegator_name in expected_redelegations: + matured_redelegation_counts = [] + + msg = ( + f"\nChecking {delegator_name}'s redelegation from " + f"{src_val}... to {dst_val}... (after completion):" + ) + print(msg) + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + try: + redelegations = node_cli.get_redelegations( + delegator_addr, src_val, dst_val + ) + count = len(redelegations) if redelegations else 0 + matured_redelegation_counts.append(count) + msg = ( + f" Node {node_idx} (cache-size={cache_size}): " + f"{count} redelegation(s) remaining" + ) + print(msg) + except Exception as e: + # "not found" errors are expected when redelegations have matured + error_str = str(e).lower() + if "not found" in error_str: + matured_redelegation_counts.append(0) + msg = ( + f" Node {node_idx} (cache-size={cache_size}): " + f"0 redelegation(s) remaining" + ) + print(msg) + else: + # Unexpected error - fail the test + raise + + # Verify consistency across nodes + assert len(set(matured_redelegation_counts)) == 1, ( + f"{delegator_name}'s matured redelegation has different " + f"counts across nodes: {matured_redelegation_counts}" + ) + + # Verify redelegation is now complete (count should be 0) + assert matured_redelegation_counts[0] == 0, ( + f"{delegator_name}'s redelegation expected 0 entries " + f"after completion but got {matured_redelegation_counts[0]}" + ) + + msg = ( + f" ✓ All nodes consistent: {matured_redelegation_counts[0]} " + f"redelegation entries (completed)" + ) + print(msg) + + print("\n=== Test completed successfully ===") + + +def test_staking_cache_unbonding_validator(cronos_staking_cache): + """ + Test unbonding validators by removing all self-delegation. + + This test verifies that when validators unbond all their self-delegation, + they transition to unbonding state correctly across all nodes + with different cache configurations. + + Tests 3 validators: nodes 4, 5, and 6 (cache-size=3, 100, 1000) + """ + cronos = cronos_staking_cache + + # We'll unbond validators from nodes 4, 5, and 6 + test_node_indices = [4, 5, 6] + test_validators = [] + + print("\n=== Testing validator unbonding (3 validators) ===") + + # Collect validator info for all test nodes + for test_node_idx in test_node_indices: + cli = cronos.cosmos_cli(test_node_idx) + val_addr = cli.address("validator", bech="val") + val_account = cli.address("validator") + test_validators.append( + { + "node_idx": test_node_idx, + "cli": cli, + "val_addr": val_addr, + "val_account": val_account, + } + ) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][test_node_idx] + print( + f"Node {test_node_idx} (cache-size={cache_size}): " + f"Validator address: {val_addr}" + ) + + # Get initial validator status on all nodes + print("\n=== Phase 1: Initial validator status ===") + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + validators = node_cli.validators() + + print( + f"Node {node_idx} (cache-size={cache_size}): {len(validators)} validators" + ) + + # Verify all test validators are present + for test_val_info in test_validators: + test_val = None + for v in validators: + if v["operator_address"] == test_val_info["val_addr"]: + test_val = v + break + + assert test_val is not None, ( + f"Node {node_idx} (cache-size={cache_size}): " + f"Validator {test_val_info['val_addr']} not found in " + f"initial status check" + ) + + # Query each validator's actual total tokens and set min_self_delegation + print( + "\n=== Phase 2: Query validators' actual tokens and set min_self_delegation ===" + ) + + for test_val_info in test_validators: + cli = test_val_info["cli"] + val_addr = test_val_info["val_addr"] + node_idx = test_val_info["node_idx"] + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + print( + f"\nNode {node_idx} (cache-size={cache_size}): " + f"Processing validator {val_addr}" + ) + + validator_info = cli.validator(val_addr) + assert ( + validator_info and "validator" in validator_info + ), f"Failed to query validator {val_addr}" + + actual_tokens = int(validator_info["validator"].get("tokens", "0")) + print(f" Validator's actual total tokens: {actual_tokens}") + test_val_info["actual_tokens"] = actual_tokens + + # Set min_self_delegation to current tokens to trigger jailing on any unbond + print(f" Setting min_self_delegation to {actual_tokens}") + rsp = cli.edit_validator(min_self_delegation=str(actual_tokens)) + assert rsp["code"] == 0, ( + f"Edit validator failed with code {rsp['code']}: " + f"{rsp.get('raw_log', rsp)}" + ) + print(" Successfully set min_self_delegation") + wait_for_new_blocks(cli, 2) + + # Unbond from each validator to trigger the min_self_delegation check + unbond_amount = "1000000000000000000stake" # 1 stake token + + print("\n=== Phase 3: Unbonding to trigger min_self_delegation violation ===") + + for test_val_info in test_validators: + cli = test_val_info["cli"] + val_addr = test_val_info["val_addr"] + node_idx = test_val_info["node_idx"] + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + print( + f"\nNode {node_idx} (cache-size={cache_size}): " + f"Unbonding {unbond_amount} from validator" + ) + rsp = cli.unbond_amount(val_addr, unbond_amount, "validator") + + assert rsp["code"] == 0, ( + f"Validator self-unbond returned code {rsp['code']}: " + f"{rsp.get('raw_log', rsp)}" + ) + print(" Unbonding transaction successful") + wait_for_new_blocks(cli, 2) + + # Wait for 3 more blocks to ensure state propagation and jailing across all nodes + cli = cronos.cosmos_cli() + wait_for_new_blocks(cli, 3) + + # Check validator status on all nodes after unbonding + print( + "\n=== Phase 4: Validator status after unbonding " "(should be UNBONDING) ===" + ) + + for test_val_info in test_validators: + val_addr = test_val_info["val_addr"] + node_idx = test_val_info["node_idx"] + + print(f"\nChecking validator from node {node_idx}: {val_addr}") + unbonding_statuses = [] + + for check_node_idx in range(7): + node_cli = cronos.cosmos_cli(check_node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][check_node_idx] + validator = node_cli.validator(val_addr) + + assert validator and "validator" in validator, ( + f"Node {check_node_idx} (cache-size={cache_size}): " + f"Failed to query validator {val_addr} after unbonding" + ) + + val_info = validator["validator"] + status = val_info.get("status", "unknown") + tokens = val_info.get("tokens", "0") + jailed = val_info.get("jailed", False) + unbonding_statuses.append(status) + print( + f" Node {check_node_idx} (cache-size={cache_size}): " + f"Status={status}, Tokens={tokens}, Jailed={jailed}" + ) + + # Assert validator is in BOND_STATUS_UNBONDING on all nodes + assert len(set(unbonding_statuses)) == 1, ( + f"Validator has different statuses across nodes: " f"{unbonding_statuses}" + ) + assert ( + unbonding_statuses[0] == "BOND_STATUS_UNBONDING" + ), f"Expected BOND_STATUS_UNBONDING but got {unbonding_statuses[0]}" + print(" ✓ Validator is in BOND_STATUS_UNBONDING on all nodes") + + # Wait for unbonding period to complete (60 seconds, 60 blocks) + print("\n=== Phase 5: Waiting for unbonding period (60 seconds) ===") + wait_for_new_blocks(cli, 60) + + # Check validator count after unbonding period + print("\n=== Phase 6: Checking validator count " "after unbonding period ===") + initial_validator_count = 7 # We started with 7 validators + validator_counts = [] + + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + validators = node_cli.validators() + count = len(validators) + validator_counts.append(count) + msg = f"Node {node_idx} (cache-size={cache_size}): " f"{count} validators" + print(msg) + + # Assert all nodes have consistent validator count + assert ( + len(set(validator_counts)) == 1 + ), f"Nodes have different validator counts: {validator_counts}" + + # Assert validator count reduced by 3 (we unbonded 3 validators) + expected_count = initial_validator_count - 3 + assert validator_counts[0] == expected_count, ( + f"Expected {expected_count} validators but got " f"{validator_counts[0]}" + ) + msg = f"✓ All nodes consistent: {validator_counts[0]} " f"validators (reduced by 3)" + print(msg) + + print("\n=== Test completed successfully ===") + + +def test_staking_cache_consistency(cronos_staking_cache): + """ + Comprehensive test combining delegations, redelegations, and unbonding operations. + + This test performs a complete lifecycle of staking operations and verifies + consistency across all nodes before, during, and after the unbonding period: + 1. Initial delegations with consistency checks + 2. Redelegations with consistency checks (before maturation) + 3. Unbonding delegations with consistency checks (before maturation) + 4. Validator unbonding with status verification + 5. Wait for unbonding period + 6. Verify all unbonding delegations and redelegations have matured (empty) + """ + cronos = cronos_staking_cache + cli = cronos.cosmos_cli() + + print("\n=== Comprehensive Staking Cache Consistency Test ===") + + # Get all validator addresses + validators = [] + for i in range(7): + val_addr = cronos.cosmos_cli(i).address("validator", bech="val") + validators.append(val_addr) + + print(f"Validators: {validators}") + + # Get delegator addresses + rich_addr = cli.address("rich") + alice_addr = cli.address("alice") + bob_addr = cli.address("bob") + charlie_addr = cli.address("charlie") + + # ========== PHASE 1: Initial Delegations ========== + print("\n=== Phase 1: Initial delegations ===") + delegation_amount = "2000000000000000000stake" # 2 stake tokens + + # Rich delegates to validators 0 and 1 + print("Rich delegating to validators 0, 1") + for i in range(2): + print(f" Delegating to validator {i}") + rsp = cli.delegate_amount(validators[i], delegation_amount, "rich") + assert rsp["code"] == 0, f"Rich delegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Alice delegates to validators 1 and 2 + print("Alice delegating to validators 1, 2") + for i in range(1, 3): + print(f" Delegating to validator {i}") + rsp = cli.delegate_amount(validators[i], delegation_amount, "alice") + assert rsp["code"] == 0, f"Alice delegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Bob delegates to validator 3 + print("Bob delegating to validator 3") + rsp = cli.delegate_amount(validators[3], delegation_amount, "bob") + assert rsp["code"] == 0, f"Bob delegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Charlie delegates to validator 0 + print("Charlie delegating to validator 0") + rsp = cli.delegate_amount(validators[0], delegation_amount, "charlie") + assert rsp["code"] == 0, f"Charlie delegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Verify delegations consistency + print("\n=== Verifying initial delegations consistency ===") + for delegator_name, delegator_addr in [ + ("Rich", rich_addr), + ("Alice", alice_addr), + ("Bob", bob_addr), + ("Charlie", charlie_addr), + ]: + delegation_counts = [] + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + delegations = node_cli.get_delegated_amount(delegator_addr) + delegation_responses = ( + delegations.get("delegation_responses", []) if delegations else [] + ) + count = len(delegation_responses) + delegation_counts.append(count) + + assert len(set(delegation_counts)) == 1, ( + f"{delegator_name}'s delegations inconsistent " + f"across nodes: {delegation_counts}" + ) + msg = f"✓ {delegator_name}: {delegation_counts[0]} delegations across all nodes" + print(msg) + + # ========== PHASE 2: Redelegations ========== + print("\n=== Phase 2: Redelegations ===") + redelegate_amount = "1000000000000000000stake" # 1 stake token + + # Rich: Redelegate from validator 0 to validator 2 + print("Rich redelegating from validator 0 to validator 2") + rsp = cli.redelegate_amount(validators[2], validators[0], redelegate_amount, "rich") + assert rsp["code"] == 0, f"Rich redelegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Charlie: Redelegate from validator 0 to validator 3 + print("Charlie redelegating from validator 0 to validator 3") + rsp = cli.redelegate_amount( + validators[3], validators[0], redelegate_amount, "charlie" + ) + assert rsp["code"] == 0, f"Charlie redelegation failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Verify redelegations consistency (before maturation) + print("\n=== Verifying redelegations consistency (before maturation) ===") + expected_redelegations = [ + (validators[0], validators[2], rich_addr, "Rich"), + (validators[0], validators[3], charlie_addr, "Charlie"), + ] + + for src_val, dst_val, delegator_addr, delegator_name in expected_redelegations: + redelegation_counts = [] + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + redelegations = node_cli.get_redelegations(delegator_addr, src_val, dst_val) + count = len(redelegations) if redelegations else 0 + redelegation_counts.append(count) + + assert len(set(redelegation_counts)) == 1, ( + f"{delegator_name}'s redelegation inconsistent: " f"{redelegation_counts}" + ) + assert redelegation_counts[0] == 1, ( + f"{delegator_name}'s redelegation expected 1 entry " + f"but got {redelegation_counts[0]}" + ) + msg = ( + f"✓ {delegator_name}: {redelegation_counts[0]} " + f"redelegation across all nodes" + ) + print(msg) + + # ========== PHASE 3: Unbonding Delegations ========== + print("\n=== Phase 3: Unbonding delegations ===") + unbond_amount = "500000000000000000stake" # 0.5 stake token + + # Alice unbonds from validator 1 + print("Alice unbonding from validator 1") + rsp = cli.unbond_amount(validators[1], unbond_amount, "alice") + assert rsp["code"] == 0, f"Alice unbonding failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Bob unbonds from validator 3 + print("Bob unbonding from validator 3") + rsp = cli.unbond_amount(validators[3], unbond_amount, "bob") + assert rsp["code"] == 0, f"Bob unbonding failed: {rsp.get('raw_log', rsp)}" + wait_for_new_blocks(cli, 2) + + # Verify unbonding delegations consistency (before maturation) + print("\n=== Verifying unbonding delegations consistency (before maturation) ===") + unbonding_delegators = [("Alice", alice_addr), ("Bob", bob_addr)] + + for delegator_name, delegator_addr in unbonding_delegators: + unbonding_counts = [] + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + unbonding = node_cli.get_unbonding_delegations(delegator_addr) + count = len(unbonding) if unbonding else 0 + unbonding_counts.append(count) + + assert ( + len(set(unbonding_counts)) == 1 + ), f"{delegator_name}'s unbonding inconsistent: {unbonding_counts}" + msg = ( + f"✓ {delegator_name}: {unbonding_counts[0]} " + f"unbonding delegations across all nodes" + ) + print(msg) + + # ========== PHASE 4: Validator Unbonding ========== + print("\n=== Phase 4: Validator unbonding (3 validators) ===") + + # Use validators 4, 5, and 6 for unbonding test + test_node_indices = [4, 5, 6] + test_validators = [] + + # Get initial validator count + initial_validators = cli.validators() + initial_count = len(initial_validators) + print(f"Initial validator count: {initial_count}") + + # Collect validator info for all test nodes + for test_node_idx in test_node_indices: + val_cli = cronos.cosmos_cli(test_node_idx) + val_addr = val_cli.address("validator", bech="val") + test_validators.append( + {"node_idx": test_node_idx, "cli": val_cli, "val_addr": val_addr} + ) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][test_node_idx] + print( + f"Will unbond validator from node {test_node_idx} " + f"(cache-size={cache_size}): {val_addr}" + ) + + # Query each validator's actual total tokens and set min_self_delegation + print("\nQuerying validators' actual tokens and setting min_self_delegation") + + for test_val_info in test_validators: + val_cli = test_val_info["cli"] + val_addr = test_val_info["val_addr"] + node_idx = test_val_info["node_idx"] + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + print( + f"\nNode {node_idx} (cache-size={cache_size}): " + f"Processing validator {val_addr}" + ) + + validator_info = val_cli.validator(val_addr) + assert ( + validator_info and "validator" in validator_info + ), f"Failed to query validator {val_addr}" + + actual_tokens = int(validator_info["validator"].get("tokens", "0")) + print(f" Validator's actual total tokens: {actual_tokens}") + test_val_info["actual_tokens"] = actual_tokens + + # Set min_self_delegation to current tokens to trigger jailing on any unbond + print(f" Setting min_self_delegation to {actual_tokens}") + rsp = val_cli.edit_validator(min_self_delegation=str(actual_tokens)) + assert rsp["code"] == 0, ( + f"Edit validator failed with code {rsp['code']}: " + f"{rsp.get('raw_log', rsp)}" + ) + print(" Successfully set min_self_delegation") + wait_for_new_blocks(cli, 2) + + # Unbond from each validator to trigger the min_self_delegation check + unbond_val_amount = "1000000000000000000stake" # 1 stake token + print( + f"\nUnbonding {unbond_val_amount} from each validator to trigger " + f"min_self_delegation violation" + ) + + for test_val_info in test_validators: + val_cli = test_val_info["cli"] + val_addr = test_val_info["val_addr"] + node_idx = test_val_info["node_idx"] + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + + print(f"\nNode {node_idx} (cache-size={cache_size}): Unbonding from validator") + rsp = val_cli.unbond_amount(val_addr, unbond_val_amount, "validator") + assert ( + rsp["code"] == 0 + ), f"Validator unbonding failed: {rsp.get('raw_log', rsp)}" + print(" Unbonding transaction successful") + wait_for_new_blocks(cli, 2) + + # Wait for 3 more blocks to ensure state propagation and jailing across all nodes + wait_for_new_blocks(cli, 3) + + # Verify validators are in UNBONDING status across all nodes + print("\n=== Verifying validator UNBONDING status ===") + + for test_val_info in test_validators: + val_addr = test_val_info["val_addr"] + node_idx = test_val_info["node_idx"] + + print(f"\nChecking validator from node {node_idx}: {val_addr}") + unbonding_statuses = [] + + for check_node_idx in range(7): + node_cli = cronos.cosmos_cli(check_node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][check_node_idx] + validator = node_cli.validator(val_addr) + + assert validator and "validator" in validator, ( + f"Node {check_node_idx} (cache-size={cache_size}): " + f"Failed to query validator {val_addr} after unbonding" + ) + + val_info = validator["validator"] + status = val_info.get("status", "unknown") + unbonding_statuses.append(status) + print(f" Node {check_node_idx} (cache-size={cache_size}): Status={status}") + + assert ( + len(set(unbonding_statuses)) == 1 + ), f"Validator has different statuses across nodes: {unbonding_statuses}" + assert ( + unbonding_statuses[0] == "BOND_STATUS_UNBONDING" + ), f"Expected BOND_STATUS_UNBONDING but got {unbonding_statuses[0]}" + print(" ✓ Validator is in BOND_STATUS_UNBONDING on all nodes") + + # ========== PHASE 5: Wait for Unbonding Period ========== + print("\n=== Phase 5: Waiting for unbonding period (60 blocks ≈ 60 seconds) ===") + wait_for_new_blocks(cli, 60) + + # ========== PHASE 6: Verify All Matured ========== + print("\n=== Phase 6: Verifying all operations matured ===") + + # Check redelegations are now empty + print("\n--- Checking redelegations matured (should be empty) ---") + for src_val, dst_val, delegator_addr, delegator_name in expected_redelegations: + matured_counts = [] + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + try: + redelegations = node_cli.get_redelegations( + delegator_addr, src_val, dst_val + ) + count = len(redelegations) if redelegations else 0 + matured_counts.append(count) + except Exception as e: + # "not found" errors are expected when redelegations have matured + error_str = str(e).lower() + if "not found" in error_str: + matured_counts.append(0) + else: + # Unexpected error - fail the test + raise + + assert ( + len(set(matured_counts)) == 1 + ), f"{delegator_name}'s matured redelegation inconsistent: {matured_counts}" + assert matured_counts[0] == 0, ( + f"{delegator_name}'s redelegation expected 0 " + f"after maturation but got {matured_counts[0]}" + ) + print(f"✓ {delegator_name}: 0 redelegations (matured)") + + # Check unbonding delegations are now empty + print("\n--- Checking unbonding delegations matured (should be empty) ---") + for delegator_name, delegator_addr in unbonding_delegators: + matured_counts = [] + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + unbonding = node_cli.get_unbonding_delegations(delegator_addr) + count = len(unbonding) if unbonding else 0 + matured_counts.append(count) + + assert ( + len(set(matured_counts)) == 1 + ), f"{delegator_name}'s matured unbonding inconsistent: {matured_counts}" + assert matured_counts[0] == 0, ( + f"{delegator_name}'s unbonding expected 0 " + f"after maturation but got {matured_counts[0]}" + ) + print(f"✓ {delegator_name}: 0 unbonding delegations (matured)") + + # Check validator count reduced by 3 + print("\n--- Checking validator count after unbonding period ---") + validator_counts = [] + for node_idx in range(7): + node_cli = cronos.cosmos_cli(node_idx) + cache_size = [-1, 0, 1, 2, 3, 100, 1000][node_idx] + validators_list = node_cli.validators() + count = len(validators_list) + validator_counts.append(count) + print(f" Node {node_idx} (cache-size={cache_size}): {count} validators") + + assert ( + len(set(validator_counts)) == 1 + ), f"Nodes have different validator counts: {validator_counts}" + expected_count = initial_count - 3 + assert ( + validator_counts[0] == expected_count + ), f"Expected {expected_count} validators but got {validator_counts[0]}" + msg = f"✓ All nodes consistent: {validator_counts[0]} validators (reduced by 3)" + print(msg) + + print("\n=== Test completed successfully ===")