diff --git a/.changelog/unreleased/improvements/ibc-relayer/4153-update-misbehaviour-to-permissionless.md b/.changelog/unreleased/improvements/ibc-relayer/4153-update-misbehaviour-to-permissionless.md new file mode 100644 index 0000000000..65bbfe1c77 --- /dev/null +++ b/.changelog/unreleased/improvements/ibc-relayer/4153-update-misbehaviour-to-permissionless.md @@ -0,0 +1,2 @@ +- Use CCV consumer ID to submit misbehaviour messages + ([\#4153](https://github.com/informalsystems/hermes/issues/4153)) \ No newline at end of file diff --git a/.github/workflows/misbehaviour.yml b/.github/workflows/misbehaviour.yml index 43dfc6635d..135ef7c9a1 100644 --- a/.github/workflows/misbehaviour.yml +++ b/.github/workflows/misbehaviour.yml @@ -43,20 +43,20 @@ jobs: fail-fast: false matrix: chain: - - package: gaia18 + - package: gaia20 command: gaiad account_prefix: cosmos steps: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -102,13 +102,13 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -154,13 +154,13 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -193,7 +193,6 @@ jobs: run: | nix shell .#${{ matrix.chain.package }} -c bash light_client_attack_freeze_test.sh - ics-double-sign: runs-on: ubuntu-20.04 timeout-minutes: 20 @@ -207,13 +206,13 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -245,4 +244,3 @@ jobs: working-directory: ci/misbehaviour-ics run: | nix shell .#${{ matrix.chain.package }} -c bash double_sign_test.sh - diff --git a/Cargo.lock b/Cargo.lock index 3b69c2fe11..72866405bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,6 +639,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cosmos-sdk-proto" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c1a5856db92cd90dddc955bce308703d3519fb33ae3d0b8f3658e9cfd05c3f" +dependencies = [ + "informalsystems-pbjson", + "prost", + "serde", + "tendermint-proto", + "tonic", +] + [[package]] name = "cpufeatures" version = "0.2.13" @@ -1549,12 +1562,13 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.47.1" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c852d22b782d2d793f4a646f968de419be635e02bc8798d5d74a6e44eef27733" +checksum = "07b9db9a33cb15d6eb56105cd9b70db016e56bcae74a8adaa1627e5b24d1f1d2" dependencies = [ "base64 0.22.1", "bytes", + "cosmos-sdk-proto", "flex-error", "ics23", "informalsystems-pbjson", @@ -2400,15 +2414,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "prost-types" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" -dependencies = [ - "prost", -] - [[package]] name = "protobuf" version = "2.28.0" @@ -2723,6 +2728,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3323,9 +3341,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d9d6ffeb83b1de47c307c6e0d2dff56c6256989299010ad03cd80a8491e97" +checksum = "37d513ce7f9e41c67ab2dd3d554ef65f36fbcc61745af1e1f93eafdeefa1ce37" dependencies = [ "bytes", "digest 0.10.7", @@ -3337,7 +3355,6 @@ dependencies = [ "num-traits", "once_cell", "prost", - "prost-types", "ripemd", "serde", "serde_bytes", @@ -3354,9 +3371,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de111ea653b2adaef627ac2452b463c77aa615c256eaaddf279ec5a1cf9775f" +checksum = "4de4e66e78c6bfb768993e69c4fc5333dbc863f6d54ebd7a5d08d91556768087" dependencies = [ "flex-error", "serde", @@ -3368,9 +3385,9 @@ dependencies = [ [[package]] name = "tendermint-light-client" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91e5abb448c65e8abdfe0e17a3a189e005a71b4169b89f36aaa2053ff239577" +checksum = "3e88c08a112db05101396a79f71c017d7dbf548dc21614f82251f17ecbe5d5e8" dependencies = [ "contracts", "crossbeam-channel", @@ -3393,9 +3410,9 @@ dependencies = [ [[package]] name = "tendermint-light-client-detector" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1ac1607eb7a3393313558b339c36eebeba15aa7f2d101d1d47299e65825152" +checksum = "d48a431ea923182c37ca9f3cc8333490ac6746a64520d1c4a3dd18c08b0806ac" dependencies = [ "crossbeam-channel", "derive_more", @@ -3416,9 +3433,9 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2674adbf0dc51aa0c8eaf8462c7d6692ec79502713e50ed5432a442002be90" +checksum = "7affc5fffe9df158185e15bce3e47fc3a0c901e6708f3b7d33f0867d7aef8ce1" dependencies = [ "derive_more", "flex-error", @@ -3429,14 +3446,13 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed14abe3b0502a3afe21ca74ca5cdd6c7e8d326d982c26f98a394445eb31d6e" +checksum = "c81ba1b023ec00763c3bc4f4376c67c0047f185cccf95c416c7a2f16272c4cbb" dependencies = [ "bytes", "flex-error", "prost", - "prost-types", "serde", "serde_bytes", "subtle-encoding", @@ -3445,9 +3461,9 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f96a2b8a0d3d0b59e4024b1a6bdc1589efc6af4709d08a480a20cc4ba90f63" +checksum = "4d3ec9d6a266cb079a44272189b5a033227d058ab28659722557c1f7fed6b83c" dependencies = [ "async-trait", "async-tungstenite", @@ -3479,9 +3495,9 @@ dependencies = [ [[package]] name = "tendermint-testgen" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae007e2918414ae96e4835426aace7538d23b8ddf96d71e23d241f58f386e877" +checksum = "7d97c36f54bf8754292166604e0c1a16cdbac3c7a2b59cb866f068b25fb0c811" dependencies = [ "ed25519-consensus", "gumdrop", @@ -3763,9 +3779,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", @@ -3782,7 +3798,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "rustls-native-certs 0.7.3", + "rustls-native-certs 0.8.0", "rustls-pemfile 2.1.3", "socket2", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 95c0b27bde..b69d6c052e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,14 +29,19 @@ ibc-telemetry = { version = "0.29.3", path = "crates/telemetry" } ibc-test-framework = { version = "0.29.3", path = "tools/test-framework" } ibc-integration-test = { version = "0.29.3", path = "tools/integration-test" } +# IBC dependencies +ibc-proto = "0.51.0" +ics23 = "0.12.0" + # Tendermint dependencies -tendermint = { version = "0.38.1", default-features = false } -tendermint-light-client = { version = "0.38.1", default-features = false } -tendermint-light-client-detector = { version = "0.38.1", default-features = false } -tendermint-light-client-verifier = { version = "0.38.1", default-features = false } -tendermint-proto = { version = "0.38.1" } -tendermint-rpc = { version = "0.38.1" } -tendermint-testgen = { version = "0.38.1" } +tendermint = { version = "0.40.0", default-features = false } +tendermint-light-client = { version = "0.40.0", default-features = false } +tendermint-light-client-detector = { version = "0.40.0", default-features = false } +tendermint-light-client-verifier = { version = "0.40.0", default-features = false } +tendermint-proto = { version = "0.40.0" } +tendermint-rpc = { version = "0.40.0" } +tendermint-testgen = { version = "0.40.0" } + # Other dependencies abscissa_core = "=0.6.0" @@ -72,8 +77,6 @@ hex = "0.4.3" http = "1.0.0" humantime = "2.1.0" humantime-serde = "1.1.1" -ibc-proto = "0.47.1" -ics23 = "0.12.0" itertools = "0.13.0" moka = "0.12.8" num-bigint = "0.4" diff --git a/ci/misbehaviour-ics/double_sign_test.sh b/ci/misbehaviour-ics/double_sign_test.sh index 3685868433..4829f59c0f 100644 --- a/ci/misbehaviour-ics/double_sign_test.sh +++ b/ci/misbehaviour-ics/double_sign_test.sh @@ -1,6 +1,10 @@ #!/bin/bash # shellcheck disable=2086,2004 +## Prerequisites: +# * ICS v6.x +# * Hermes v1.10.3+45a29cc00 + set -eu DEBUG=${DEBUG:-false} @@ -9,13 +13,6 @@ if [ "$DEBUG" = true ]; then set -x fi -# User balance of stake tokens -USER_COINS="100000000000stake" -# Amount of stake tokens staked -STAKE="100000000stake" -# Node IP address -NODE_IP="127.0.0.1" - # Home directory HOME_DIR="/tmp/hermes-ics-double-sign" @@ -25,11 +22,19 @@ if [ "$DEBUG" = true ]; then else HERMES_DEBUG="" fi + # Hermes config HERMES_CONFIG="$HOME_DIR/hermes.toml" # Hermes binary HERMES_BIN="cargo run -q --bin hermes -- $HERMES_DEBUG --config $HERMES_CONFIG" +# User balance of stake tokens +USER_COINS="100000000000stake" +# Amount of stake tokens staked +STAKE="100000000stake" +# Node IP address +NODE_IP="127.0.0.1" + # Validator moniker MONIKERS=("coordinator" "alice" "bob") LEAD_VALIDATOR_MONIKER="coordinator" @@ -51,13 +56,8 @@ CLIENT_BASEPORT=29220 # Clean start pkill -f interchain-security-pd &> /dev/null || true -pkill -f interchain-security-cd &> /dev/null || true -pkill -f hermes &> /dev/null || true sleep 1 - -mkdir -p "${HOME_DIR}" -rm -rf "${PROV_NODES_ROOT_DIR}" -rm -rf "${CONS_NODES_ROOT_DIR}" +rm -rf ${PROV_NODES_ROOT_DIR} # Let lead validator create genesis file LEAD_VALIDATOR_PROV_DIR=${PROV_NODES_ROOT_DIR}/provider-${LEAD_VALIDATOR_MONIKER} @@ -70,7 +70,6 @@ do MONIKER=${MONIKERS[$index]} # validator key PROV_KEY=${MONIKER}-key - PROV_KEY2=${MONIKER}-key2 # home directory of this validator on provider PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${MONIKER} @@ -80,16 +79,17 @@ do # Build genesis file and node directory structure interchain-security-pd init $MONIKER --chain-id provider --home ${PROV_NODE_DIR} - jq ".app_state.gov.params.voting_period = \"5s\" | .app_state.staking.params.unbonding_time = \"86400s\"" \ + jq ".app_state.gov.params.voting_period = \"10s\" \ + | .app_state.gov.params.expedited_voting_period = \"9s\" \ + | .app_state.staking.params.unbonding_time = \"86400s\" \ + | .app_state.provider.params.blocks_per_epoch = \"5\"" \ ${PROV_NODE_DIR}/config/genesis.json > \ ${PROV_NODE_DIR}/edited_genesis.json && mv ${PROV_NODE_DIR}/edited_genesis.json ${PROV_NODE_DIR}/config/genesis.json - sleep 1 # Create account keypair - interchain-security-pd keys add $PROV_KEY --home ${PROV_NODE_DIR} --keyring-backend test --output json > ${PROV_NODE_DIR}/${PROV_KEY}.json 2>&1 - interchain-security-pd keys add $PROV_KEY2 --home ${PROV_NODE_DIR} --keyring-backend test --output json > ${PROV_NODE_DIR}/${PROV_KEY2}.json 2>&1 + interchain-security-pd keys add $PROV_KEY --home ${PROV_NODE_DIR} --keyring-backend test --output json > ${PROV_NODE_DIR}/${PROV_KEY}.json 2>&1 sleep 1 # copy genesis in, unless this validator is the lead validator @@ -100,9 +100,6 @@ do # Add stake to user PROV_ACCOUNT_ADDR=$(jq -r '.address' ${PROV_NODE_DIR}/${PROV_KEY}.json) interchain-security-pd genesis add-genesis-account $PROV_ACCOUNT_ADDR $USER_COINS --home ${PROV_NODE_DIR} --keyring-backend test - - PROV_ACCOUNT_ADDR2=$(jq -r '.address' ${PROV_NODE_DIR}/${PROV_KEY2}.json) - interchain-security-pd genesis add-genesis-account $PROV_ACCOUNT_ADDR2 $USER_COINS --home ${PROV_NODE_DIR} --keyring-backend test sleep 1 # copy genesis out, unless this validator is the lead validator @@ -141,10 +138,10 @@ do fi # Stake 1/1000 user's coins - interchain-security-pd genesis gentx $PROV_KEY $STAKE --chain-id provider --home ${PROV_NODE_DIR} --keyring-backend test --moniker $MONIKER + interchain-security-pd genesis gentx $PROV_KEY $STAKE --chain-id provider --home ${PROV_NODE_DIR} --keyring-backend test --moniker $MONIKER sleep 1 - # Copy gentxs to the lead validator for possible future collection. + # Copy gentxs to the lead validator for possible future collection. # Obviously we don't need to copy the first validator's gentx to itself if [ $MONIKER != $LEAD_VALIDATOR_MONIKER ]; then cp ${PROV_NODE_DIR}/config/gentx/* ${LEAD_VALIDATOR_PROV_DIR}/config/gentx/ @@ -183,7 +180,6 @@ do # validator key PROV_KEY=${MONIKER}-key - PROV_KEY2=${MONIKER}-key2 # home directory of this validator on provider PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${MONIKER} @@ -219,89 +215,217 @@ do done # Build consumer chain proposal file -tee ${LEAD_VALIDATOR_PROV_DIR}/consumer-proposal.json< /dev/null || true +sleep 1 +rm -rf ${CONS_NODES_ROOT_DIR} + for index in "${!MONIKERS[@]}" do MONIKER=${MONIKERS[$index]} # validator key PROV_KEY=${MONIKER}-key - PROV_KEY2=${MONIKER}-key2 PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${MONIKER} @@ -314,8 +438,7 @@ do sleep 1 # Create account keypair - interchain-security-cd keys add $PROV_KEY --home ${CONS_NODE_DIR} --keyring-backend test --output json > ${CONS_NODE_DIR}/${PROV_KEY}.json 2>&1 - interchain-security-cd keys add $PROV_KEY2 --home ${CONS_NODE_DIR} --keyring-backend test --output json > ${CONS_NODE_DIR}/${PROV_KEY2}.json 2>&1 + interchain-security-cd keys add $PROV_KEY --home ${CONS_NODE_DIR} --keyring-backend test --output json > ${CONS_NODE_DIR}/${PROV_KEY}.json 2>&1 sleep 1 # copy genesis in, unless this validator is the lead validator @@ -326,15 +449,15 @@ do # Add stake to user CONS_ACCOUNT_ADDR=$(jq -r '.address' ${CONS_NODE_DIR}/${PROV_KEY}.json) interchain-security-cd genesis add-genesis-account $CONS_ACCOUNT_ADDR $USER_COINS --home ${CONS_NODE_DIR} - CONS_ACCOUNT_ADDR2=$(jq -r '.address' ${CONS_NODE_DIR}/${PROV_KEY2}.json) - interchain-security-cd genesis add-genesis-account $CONS_ACCOUNT_ADDR2 $USER_COINS --home ${CONS_NODE_DIR} - sleep 10 ### this probably does not have to be done for each node # Add consumer genesis states to genesis file RPC_LADDR_PORT=$(($RPC_LADDR_BASEPORT + $index)) RPC_LADDR=tcp://${NODE_IP}:${RPC_LADDR_PORT} - interchain-security-pd query provider consumer-genesis consumer --home ${PROV_NODE_DIR} --node ${RPC_LADDR} -o json > consumer_gen.json + interchain-security-pd query provider consumer-genesis $CONSUMER_ID \ + --home ${PROV_NODE_DIR} \ + --node ${RPC_LADDR} -o json > consumer_gen.json + jq -s '.[0].app_state.ccvconsumer = .[1] | .[0]' ${CONS_NODE_DIR}/config/genesis.json consumer_gen.json > ${CONS_NODE_DIR}/edited_genesis.json \ && mv ${CONS_NODE_DIR}/edited_genesis.json ${CONS_NODE_DIR}/config/genesis.json rm consumer_gen.json @@ -376,7 +499,6 @@ done sleep 1 - for index in "${!MONIKERS[@]}" do MONIKER=${MONIKERS[$index]} @@ -437,42 +559,13 @@ do sleep 6 done -## Cause double signing - -# create directory for double signing node -mkdir $CONS_NODES_ROOT_DIR/consumer-bob-sybil/ -cp -r $CONS_NODES_ROOT_DIR/consumer-bob/* $CONS_NODES_ROOT_DIR/consumer-bob-sybil - -# clear state in consumer-bob-sybil -echo '{"height": "0","round": 0,"step": 0,"signature":"","signbytes":""}' > $CONS_NODES_ROOT_DIR/consumer-bob-sybil/data/priv_validator_state.json - -# add new node key to sybil -# key was generated using gaiad init -# if the node key is not unique, double signing cannot be achieved -# and errors such as this can be seen in the terminal -# 5:54PM ERR found conflicting vote from ourselves; did you unsafe_reset a validator? height=1961 module=consensus round=0 type=2 -# 5:54PM ERR failed to process message err="conflicting votes from validator C888306A908A217B9A943D1DAD8790044D0947A4" -echo '{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"tj55by/yYwruSz4NxsOG9y9k2WrPvKLXKQdz/9jL9Uptmi647OYpcisjwf92TyA+wCUYVDOgW7D53Q+638l9/w=="}}' > $CONS_NODES_ROOT_DIR/consumer-bob-sybil/config/node_key.json - -# does not use persistent peers; will do a lookup in genesis.json to find peers -#ARGS="--address tcp://$CHAIN_PREFIX.252:26655 --rpc.laddr tcp://$CHAIN_PREFIX.252:26658 --grpc.address $CHAIN_PREFIX.252:9091 --log_level trace --p2p.laddr tcp://$CHAIN_PREFIX.252:26656 --grpc-web.enable=false" - -# start double signing node - it should not talk to the node with the same key -#ip netns exec $HOME/nodes/consumer/consumer-bob-sybil $BIN $ARGS --home $HOME/nodes/consumer/consumer-bob-sybil start &> $HOME/nodes/consumer/consumer-bob-sybil/logs & - -# Start gaia -interchain-security-cd start \ - --home $CONS_NODES_ROOT_DIR/consumer-bob-sybil \ - --p2p.persistent_peers ${PERSISTENT_PEERS} \ - --rpc.laddr tcp://${NODE_IP}:29179 \ - --grpc.address ${NODE_IP}:29199 \ - --address tcp://${NODE_IP}:29209 \ - --p2p.laddr tcp://${NODE_IP}:29189 \ - --grpc-web.enable=false &> $CONS_NODES_ROOT_DIR/consumer-bob-sybil/logs & +## Setup Hermes -# Setup Hermes config file +HERMES_PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${HERMES_VALIDATOR_MONIKER} +HERMES_KEY=${HERMES_VALIDATOR_MONIKER}-key +HERMES_CONS_NODE_DIR=${CONS_NODES_ROOT_DIR}/consumer-${HERMES_VALIDATOR_MONIKER} -tee $HERMES_CONFIG< $HOME_DIR/hermes-start-logs.txt & +sleep 5 + +## Cause double signing + +CONS_NODE_SYBIL_DIR=${CONS_NODES_ROOT_DIR}/consumer-${MONIKER}-sybil + +# create directory for double signing node +mkdir $CONS_NODE_SYBIL_DIR +cp -r $CONS_NODE_DIR/* $CONS_NODE_SYBIL_DIR + +# clear state in sybil node directory +echo '{"height": "0","round": 0,"step": 0,"signature":"","signbytes":""}' \ + > $CONS_NODE_SYBIL_DIR/data/priv_validator_state.json + +# add new node key to sybil +# key was generated using gaiad init +# if the node key is not unique, double signing cannot be achieved +# and errors such as this can be seen in the terminal +# 5:54PM ERR found conflicting vote from ourselves; did you unsafe_reset a validator? height=1961 module=consensus round=0 type=2 +# 5:54PM ERR failed to process message err="conflicting votes from validator C888306A908A217B9A943D1DAD8790044D0947A4" +echo '{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"tj55by/yYwruSz4NxsOG9y9k2WrPvKLXKQdz/9jL9Uptmi647OYpcisjwf92TyA+wCUYVDOgW7D53Q+638l9/w=="}}' \ + > $CONS_NODE_SYBIL_DIR/config/node_key.json + +# does not use persistent peers; will do a lookup in genesis.json to find peers +# start double signing node - it should not talk to the node with the same key + +# Start gaia +interchain-security-cd start \ + --home ${CONS_NODE_SYBIL_DIR} \ + --rpc.laddr tcp://${NODE_IP}:$((RPC_LADDR_PORT+1)) \ + --grpc.address ${NODE_IP}:$((GRPC_LADDR_PORT+1)) \ + --address tcp://${NODE_IP}:$((NODE_ADDRESS_PORT+1)) \ + --p2p.laddr tcp://${NODE_IP}:$((P2P_LADDR_PORT+1)) \ + --grpc-web.enable=false &> ${CONS_NODE_SYBIL_DIR}/logs & + +sleep 5 + +## start Hermes in evidence mode +$HERMES_BIN evidence --chain consumer --check-past-blocks 0 &> $HOME_DIR/hermes-evidence-logs.txt & -$HERMES_BIN evidence --chain consumer --key-name evidence &> $HOME_DIR/hermes-evidence-logs.txt & +sleep 1 + +# Wait for Hermes to submit double signing evidence +$HERMES_BIN update client --host-chain consumer --client 07-tendermint-0 for _ in $(seq 1 10) do sleep 5 MSG="successfully submitted double voting evidence to chain" - + if grep -c "$MSG" $HOME_DIR/hermes-evidence-logs.txt; then echo "[SUCCESS] Successfully submitted double voting evidence to provider chain" exit 0 @@ -600,9 +717,6 @@ done echo "[ERROR] Failed to submit double voting evidence to provider chain" echo "" echo "---------------------------------------------------------------" -echo "Hermes start logs:" -cat $HOME_DIR/hermes-start-logs.txt -echo "---------------------------------------------------------------" echo "Hermes evidence logs:" cat $HOME_DIR/hermes-evidence-logs.txt echo "---------------------------------------------------------------" diff --git a/ci/misbehaviour-ics/light_client_attack_freeze_test.sh b/ci/misbehaviour-ics/light_client_attack_freeze_test.sh index 24661ace22..2cd1b50f58 100644 --- a/ci/misbehaviour-ics/light_client_attack_freeze_test.sh +++ b/ci/misbehaviour-ics/light_client_attack_freeze_test.sh @@ -85,7 +85,10 @@ rm -rf "${CONS_FORK_NODE_DIR}" # Build genesis file and node directory structure interchain-security-pd init $MONIKER --chain-id provider --home ${PROV_NODE_DIR} -jq ".app_state.gov.params.voting_period = \"5s\" | .app_state.staking.params.unbonding_time = \"86400s\"" \ +jq ".app_state.gov.params.voting_period = \"5s\" \ +| .app_state.gov.params.expedited_voting_period = \"4s\" \ +| .app_state.staking.params.unbonding_time = \"86400s\" \ +| .app_state.provider.params.blocks_per_epoch = \"5\"" \ ${PROV_NODE_DIR}/config/genesis.json > \ ${PROV_NODE_DIR}/edited_genesis.json && mv ${PROV_NODE_DIR}/edited_genesis.json ${PROV_NODE_DIR}/config/genesis.json @@ -160,31 +163,171 @@ interchain-security-pd start \ waiting 10 "for provider sub-node to start" # Build consumer chain proposal file -tee ${PROV_NODE_DIR}/consumer-proposal.json< consumer_gen.json +interchain-security-pd query provider consumer-genesis $CONSUMER_ID --home ${PROV_NODE_DIR} -o json > consumer_gen.json jq -s '.[0].app_state.ccvconsumer = .[1] | .[0]' ${CONS_NODE_DIR}/config/genesis.json consumer_gen.json > ${CONS_NODE_DIR}/edited_genesis.json \ && mv ${CONS_NODE_DIR}/edited_genesis.json ${CONS_NODE_DIR}/config/genesis.json rm consumer_gen.json @@ -296,7 +439,7 @@ rpc_addr = "http://${NODE_IP}:26648" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26648/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -319,7 +462,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -419,7 +562,7 @@ rpc_addr = "http://${NODE_IP}:26638" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26638/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -442,7 +585,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -506,6 +649,7 @@ if [ "$FROZEN_HEIGHT" != "null" ]; then diag "Client is frozen, as expected." else diag "Client is not frozen, aborting." + ${HOME_DIR}/hermes-evidence-logs.txt exit 1 fi @@ -513,6 +657,7 @@ if grep -q "found light client attack evidence" ${HOME_DIR}/hermes-evidence-logs diag "Evidence found, proceeding." else diag "Evidence not found, aborting." + cat ${HOME_DIR}/hermes-evidence-logs.txt exit 1 fi diff --git a/ci/misbehaviour-ics/light_client_attack_test.sh b/ci/misbehaviour-ics/light_client_attack_test.sh index f42982c353..9d5d93c472 100644 --- a/ci/misbehaviour-ics/light_client_attack_test.sh +++ b/ci/misbehaviour-ics/light_client_attack_test.sh @@ -85,7 +85,10 @@ rm -rf "${CONS_FORK_NODE_DIR}" # Build genesis file and node directory structure interchain-security-pd init $MONIKER --chain-id provider --home ${PROV_NODE_DIR} -jq ".app_state.gov.params.voting_period = \"5s\" | .app_state.staking.params.unbonding_time = \"86400s\"" \ +jq ".app_state.gov.params.voting_period = \"5s\" \ +| .app_state.gov.params.expedited_voting_period = \"4s\" \ +| .app_state.staking.params.unbonding_time = \"86400s\" \ +| .app_state.provider.params.blocks_per_epoch = \"5\"" \ ${PROV_NODE_DIR}/config/genesis.json > \ ${PROV_NODE_DIR}/edited_genesis.json && mv ${PROV_NODE_DIR}/edited_genesis.json ${PROV_NODE_DIR}/config/genesis.json @@ -169,33 +172,173 @@ interchain-security-pd start \ waiting 5 "for provider sub-node to start" # Build consumer chain proposal file -tee ${PROV_NODE_DIR}/consumer-proposal.json< consumer_gen.json +interchain-security-pd query provider consumer-genesis $CONSUMER_ID --home ${PROV_NODE_DIR} -o json > consumer_gen.json jq -s '.[0].app_state.ccvconsumer = .[1] | .[0]' ${CONS_NODE_DIR}/config/genesis.json consumer_gen.json > ${CONS_NODE_DIR}/edited_genesis.json \ && mv ${CONS_NODE_DIR}/edited_genesis.json ${CONS_NODE_DIR}/config/genesis.json rm consumer_gen.json @@ -305,7 +448,7 @@ rpc_addr = "http://${NODE_IP}:26648" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26648/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -328,7 +471,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -445,7 +588,7 @@ rpc_addr = "http://${NODE_IP}:26638" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26638/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -467,7 +610,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -479,6 +622,7 @@ event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_ EOF waiting 10 "for a couple blocks" +sleep 5 read -r height hash < <( curl -s "localhost:26648"/commit \ @@ -512,7 +656,7 @@ waiting 10 "for Hermes relayer to start" diag "Running Hermes relayer evidence command" # Run hermes in evidence mode -$HERMES_BIN evidence --chain consumer &> ${HOME_DIR}/hermes-evidence-logs.txt & +$HERMES_BIN --debug=rpc evidence --chain consumer &> ${HOME_DIR}/hermes-evidence-logs.txt & # If we sleep 5 here and above, we end up on the forked block later waiting 10 "for Hermes evidence monitor to start" @@ -555,6 +699,7 @@ if grep -q "found light client attack evidence" ${HOME_DIR}/hermes-evidence-logs diag "Evidence found, proceeding!" else diag "Evidence not found, aborting." + cat ${HOME_DIR}/hermes-evidence-logs.txt exit 1 fi diff --git a/crates/relayer-cli/src/commands/evidence.rs b/crates/relayer-cli/src/commands/evidence.rs index fa54be707b..c74c1286e3 100644 --- a/crates/relayer-cli/src/commands/evidence.rs +++ b/crates/relayer-cli/src/commands/evidence.rs @@ -18,7 +18,7 @@ use ibc_relayer::chain::endpoint::ChainEndpoint; use ibc_relayer::chain::handle::{BaseChainHandle, ChainHandle}; use ibc_relayer::chain::requests::{IncludeProof, PageRequest, QueryHeight}; use ibc_relayer::chain::tracking::TrackedMsgs; -use ibc_relayer::foreign_client::ForeignClient; +use ibc_relayer::foreign_client::{fetch_ccv_consumer_id, ForeignClient}; use ibc_relayer::spawn::spawn_chain_runtime_with_modified_config; use ibc_relayer_types::applications::ics28_ccv::msgs::ccv_double_voting::MsgSubmitIcsConsumerDoubleVoting; use ibc_relayer_types::applications::ics28_ccv::msgs::ccv_misbehaviour::MsgSubmitIcsConsumerMisbehaviour; @@ -318,10 +318,15 @@ fn submit_duplicate_vote_evidence( let signer = counterparty_chain_handle.get_signer()?; - if !is_counterparty_provider(chain, counterparty_chain_handle, counterparty_client_id) { - debug!("counterparty client `{counterparty_client_id}` on chain `{counterparty_chain_id}` is not a CCV client, skipping..."); - return Ok(ControlFlow::Continue(())); - } + let consumer_id = match fetch_ccv_consumer_id(counterparty_chain_handle, counterparty_client_id) + { + Ok(consumer_id) => consumer_id, + Err(e) => { + info!("Failed to query Consumer ID: {e}. \ + Counterparty client `{counterparty_client_id}` on chain `{counterparty_chain_id}` might not be a CCV client, skipping..."); + return Ok(ControlFlow::Continue(())); + } + }; let infraction_height = evidence.vote_a.height; @@ -359,6 +364,7 @@ fn submit_duplicate_vote_evidence( submitter: signer.clone(), duplicate_vote_evidence: evidence.clone(), infraction_block_header, + consumer_id, } .to_any(); @@ -507,42 +513,14 @@ fn submit_light_client_attack_evidence( counterparty.id(), ); - let counterparty_is_provider = - is_counterparty_provider(chain, counterparty, &counterparty_client_id); - let counterparty_client_is_frozen = counterparty_client.is_frozen(); - if !counterparty_is_provider && counterparty_client_is_frozen { - warn!( - "cannot submit light client attack evidence to client `{}` on counterparty chain `{}`", - counterparty_client_id, - counterparty.id() - ); - warn!("reason: client is frozen and chain is not a CCV provider chain"); - - return Ok(()); - } - let signer = counterparty.get_signer()?; let common_height = Height::from_tm(evidence.common_height, chain.id()); let counterparty_has_common_consensus_state = has_consensus_state(counterparty, &counterparty_client_id, common_height); - if counterparty_is_provider - && counterparty_client_is_frozen - && !counterparty_has_common_consensus_state - { - warn!( - "cannot submit light client attack evidence to client `{}` on provider chain `{}`", - counterparty_client_id, - counterparty.id() - ); - warn!("reason: client is frozen and does not have a consensus state at height {common_height}"); - - return Ok(()); - } - let mut msgs = if counterparty_has_common_consensus_state { info!( "skip building update client message for client `{}` on counterparty chain `{}`", @@ -550,8 +528,8 @@ fn submit_light_client_attack_evidence( counterparty.id() ); info!( - "reason: counterparty chain already has consensus state at common height {common_height}" - ); + "reason: counterparty chain already has consensus state at common height {common_height}" + ); Vec::new() } else { @@ -571,21 +549,31 @@ fn submit_light_client_attack_evidence( } }; - if counterparty_is_provider { + if let Ok(consumer_id) = fetch_ccv_consumer_id(counterparty, &counterparty_client_id) { + if counterparty_client_is_frozen && !counterparty_has_common_consensus_state { + warn!( + "cannot submit light client attack evidence to client `{}` on provider chain `{}`", + counterparty_client_id, + counterparty.id() + ); + warn!("reason: client is frozen and does not have a consensus state at height {common_height}"); + + return Ok(()); + } info!( "will submit consumer light client attack evidence to client `{}` on provider chain `{}`", counterparty_client_id, counterparty.id(), ); - - let msg = MsgSubmitIcsConsumerMisbehaviour { - submitter: signer.clone(), - misbehaviour: misbehaviour.clone(), - } - .to_any(); - - msgs.push(msg); - }; + msgs.push( + MsgSubmitIcsConsumerMisbehaviour { + submitter: signer.clone(), + misbehaviour: misbehaviour.clone(), + consumer_id, + } + .to_any(), + ); + } // We do not need to submit the misbehaviour if the client is already frozen. if !counterparty_client_is_frozen { @@ -662,29 +650,6 @@ fn has_consensus_state( res.is_ok() } -/// If the misbehaving chain is a CCV consumer chain, -/// then try fetch the consumer chains of the counterparty chains. -/// If that fails, then the counterparty chain is not a provider chain. -/// Otherwise, check if the misbehaving chain is a consumer of the counterparty chain, -/// which is then definitely a provider. -fn is_counterparty_provider( - chain: &CosmosSdkChain, - counterparty_chain_handle: &BaseChainHandle, - counterparty_client_id: &ClientId, -) -> bool { - if chain.config().ccv_consumer_chain { - let consumer_chains = counterparty_chain_handle - .query_consumer_chains() - .unwrap_or_default(); // If the query fails, use an empty list of consumers - - consumer_chains.iter().any(|(chain_id, client_id)| { - chain_id == chain.id() && client_id == counterparty_client_id - }) - } else { - false - } -} - /// Fetch all the counterparty clients of the given chain. /// A counterparty client is a client that has a connection with that chain. /// @@ -716,6 +681,11 @@ fn fetch_all_counterparty_clients( connection.connection_id ); + if client_id.as_str() == "09-localhost" { + debug!("skipping localhost client `{client_id}`..."); + continue; + } + debug!( "fetching client state for client `{client_id}` on connection `{}`", connection.connection_id diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs index a1ce4291d9..9f07f31539 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs @@ -9,6 +9,7 @@ use crate::signer::Signer; use crate::tx_msg::Msg; use super::error::Error; +use super::ConsumerId; pub const ICS_DOUBLE_VOTING_TYPE_URL: &str = "/interchain_security.ccv.provider.v1.MsgSubmitConsumerDoubleVoting"; @@ -18,6 +19,7 @@ pub struct MsgSubmitIcsConsumerDoubleVoting { pub submitter: Signer, pub duplicate_vote_evidence: DuplicateVoteEvidence, pub infraction_block_header: Header, + pub consumer_id: ConsumerId, } impl Msg for MsgSubmitIcsConsumerDoubleVoting { @@ -57,6 +59,7 @@ impl TryFrom for MsgSubmitIcsConsumerDoubleVoting { .map_err(|e| { Error::invalid_raw_double_voting(format!("cannot convert header: {e}")) })?, + consumer_id: ConsumerId::new(raw.consumer_id), }) } } @@ -67,6 +70,7 @@ impl From for RawIcsDoubleVoting { submitter: value.submitter.to_string(), duplicate_vote_evidence: Some(value.duplicate_vote_evidence.into()), infraction_block_header: Some(value.infraction_block_header.into()), + consumer_id: value.consumer_id.to_string(), } } } diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs index 8b5c6c2750..67a7d20102 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs @@ -10,6 +10,7 @@ use crate::signer::Signer; use crate::tx_msg::Msg; use super::error::Error; +use super::ConsumerId; pub const ICS_MISBEHAVIOR_TYPE_URL: &str = "/interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour"; @@ -18,6 +19,7 @@ pub const ICS_MISBEHAVIOR_TYPE_URL: &str = pub struct MsgSubmitIcsConsumerMisbehaviour { pub submitter: Signer, pub misbehaviour: Misbehaviour, + pub consumer_id: ConsumerId, } impl Msg for MsgSubmitIcsConsumerMisbehaviour { @@ -48,6 +50,7 @@ impl TryFrom for MsgSubmitIcsConsumerMisbehaviour { .map_err(|_e| { Error::invalid_raw_misbehaviour("cannot convert misbehaviour".into()) })?, + consumer_id: ConsumerId::new(raw.consumer_id), }) } } @@ -57,6 +60,7 @@ impl From for RawIcsMisbehaviour { RawIcsMisbehaviour { submitter: value.submitter.to_string(), misbehaviour: Some(value.misbehaviour.into()), + consumer_id: value.consumer_id.to_string(), } } } diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs index df28e4a73a..6f992ae0f7 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs @@ -1,3 +1,52 @@ pub mod ccv_double_voting; pub mod ccv_misbehaviour; pub mod error; + +use std::convert::Infallible; + +use derive_more::Display; +use ibc_proto::interchain_security::ccv::provider::v1::Chain; +use serde::{Deserialize, Serialize}; + +use crate::core::ics24_host; +use crate::core::ics24_host::identifier::{ChainId, ClientId}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, Serialize, Deserialize)] +pub struct ConsumerId(String); + +impl ConsumerId { + pub const fn new(id: String) -> Self { + Self(id) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl std::str::FromStr for ConsumerId { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_string())) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ConsumerChain { + pub chain_id: ChainId, + pub consumer_id: ConsumerId, + pub client_id: ClientId, +} + +impl TryFrom for ConsumerChain { + type Error = ics24_host::error::ValidationError; + + fn try_from(value: Chain) -> Result { + Ok(Self { + chain_id: ChainId::from_string(&value.chain_id), + consumer_id: ConsumerId::new(value.consumer_id), + client_id: value.client_id.parse()?, + }) + } +} diff --git a/crates/relayer-types/src/applications/ics31_icq/response.rs b/crates/relayer-types/src/applications/ics31_icq/response.rs index 98b45acf06..7a5c9bb638 100644 --- a/crates/relayer-types/src/applications/ics31_icq/response.rs +++ b/crates/relayer-types/src/applications/ics31_icq/response.rs @@ -52,11 +52,13 @@ impl CrossChainQueryResponse { pub fn try_to_any(&self, signer: Signer) -> Result { let mut encoded = vec![]; + let proof_ops = into_proof_ops(self.proof.clone()); + let msg_submit_cross_chain_query_result = MsgSubmitQueryResponse { chain_id: self.chain_id.to_string(), query_id: self.query_id.to_string(), result: self.result.clone(), - proof_ops: Some(into_proof_ops(self.proof.clone())), + proof_ops: Some(proof_ops), height: self.height, from_address: signer.as_ref().to_string(), }; diff --git a/crates/relayer-types/src/applications/transfer/error.rs b/crates/relayer-types/src/applications/transfer/error.rs index f0e52d946d..71c8d04b0f 100644 --- a/crates/relayer-types/src/applications/transfer/error.rs +++ b/crates/relayer-types/src/applications/transfer/error.rs @@ -4,7 +4,6 @@ use std::string::FromUtf8Error; use flex_error::{define_error, DisplayOnly, TraceError}; use subtle_encoding::Error as EncodingError; -use tendermint_proto::Error as TendermintProtoError; use uint::FromDecStrErr; use crate::core::ics04_channel::channel::Ordering; @@ -127,7 +126,7 @@ define_error! { | _ | { "no trace associated with specified hash" }, DecodeRawMsg - [ TraceError ] + [ TraceError ] | _ | { "error decoding raw msg" }, UnknownMsgType diff --git a/crates/relayer-types/src/applications/transfer/msgs/send.rs b/crates/relayer-types/src/applications/transfer/msgs/send.rs index 36e71dc66b..fcbb34a6bb 100644 --- a/crates/relayer-types/src/applications/transfer/msgs/send.rs +++ b/crates/relayer-types/src/applications/transfer/msgs/send.rs @@ -43,11 +43,11 @@ where type Error = Error; fn try_from(value: RawMsgSend) -> Result { - let amount: Vec> = value + let amount = value .amount .into_iter() - .map(Coin::try_from) - .collect::>, _>>()?; + .map(Coin::::try_from) + .collect::, _>>()?; Ok(MsgSend { from_address: value.from_address, to_address: value.to_address, diff --git a/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs b/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs index 818cf13347..44888d8afa 100644 --- a/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs +++ b/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs @@ -305,9 +305,9 @@ impl From for RawTmClientState { Self { chain_id: value.chain_id.to_string(), trust_level: Some(value.trust_threshold.into()), - trusting_period: Some(value.trusting_period.into()), - unbonding_period: Some(value.unbonding_period.into()), - max_clock_drift: Some(value.max_clock_drift.into()), + trusting_period: Some(value.trusting_period.try_into().unwrap()), + unbonding_period: Some(value.unbonding_period.try_into().unwrap()), + max_clock_drift: Some(value.max_clock_drift.try_into().unwrap()), frozen_height: Some(value.frozen_height.map(|height| height.into()).unwrap_or( RawHeight { revision_number: 0, diff --git a/crates/relayer-types/src/core/ics02_client/error.rs b/crates/relayer-types/src/core/ics02_client/error.rs index a560a499f1..27316e2747 100644 --- a/crates/relayer-types/src/core/ics02_client/error.rs +++ b/crates/relayer-types/src/core/ics02_client/error.rs @@ -1,5 +1,4 @@ use flex_error::{define_error, TraceError}; -use tendermint_proto::Error as TendermintProtoError; use crate::core::ics02_client::client_type::ClientType; use crate::core::ics02_client::height::HeightError; @@ -109,14 +108,14 @@ define_error! { }, DecodeRawClientState - [ TraceError ] + [ TraceError ] | _ | { "error decoding raw client state" }, MissingRawClientState | _ | { "missing raw client state" }, InvalidRawConsensusState - [ TraceError ] + [ TraceError ] | _ | { "invalid raw client consensus state" }, MissingRawConsensusState @@ -138,7 +137,7 @@ define_error! { | _ | { "invalid client identifier" }, InvalidRawHeader - [ TraceError ] + [ TraceError ] | _ | { "invalid raw header" }, MalformedHeader @@ -148,7 +147,7 @@ define_error! { | _ | { "missing raw header" }, DecodeRawMisbehaviour - [ TraceError ] + [ TraceError ] | _ | { "invalid raw misbehaviour" }, InvalidRawMisbehaviour @@ -254,19 +253,19 @@ define_error! { | e | { format_args!("the local consensus state could not be retrieved for height {}", e.height) }, InvalidConnectionEnd - [ TraceError] + [ TraceError] | _ | { "invalid connection end" }, InvalidChannelEnd - [ TraceError] + [ TraceError] | _ | { "invalid channel end" }, InvalidAnyClientState - [ TraceError] + [ TraceError] | _ | { "invalid any client state" }, InvalidAnyConsensusState - [ TraceError ] + [ TraceError ] | _ | { "invalid any client consensus state" }, Signer diff --git a/crates/relayer-types/src/core/ics02_client/events.rs b/crates/relayer-types/src/core/ics02_client/events.rs index 3abe9b7b8d..da295833ef 100644 --- a/crates/relayer-types/src/core/ics02_client/events.rs +++ b/crates/relayer-types/src/core/ics02_client/events.rs @@ -1,9 +1,10 @@ //! Types for the IBC events emitted from Tendermint Websocket by the client module. -use serde_derive::{Deserialize, Serialize}; use std::fmt::{Display, Error as FmtError, Formatter}; + +use ibc_proto::Protobuf; +use serde_derive::{Deserialize, Serialize}; use tendermint::abci; -use tendermint_proto::Protobuf; use super::header::AnyHeader; use crate::core::ics02_client::client_type::ClientType; diff --git a/crates/relayer-types/src/core/ics26_routing/error.rs b/crates/relayer-types/src/core/ics26_routing/error.rs index f51581b6e0..5a4773e5af 100644 --- a/crates/relayer-types/src/core/ics26_routing/error.rs +++ b/crates/relayer-types/src/core/ics26_routing/error.rs @@ -29,7 +29,7 @@ define_error! { | e | { format_args!("unknown type URL {0}", e.url) }, MalformedMessageBytes - [ TraceError ] + [ TraceError ] | _ | { "the message is malformed and cannot be decoded" }, } } diff --git a/crates/relayer/src/chain/cosmos.rs b/crates/relayer/src/chain/cosmos.rs index e7729c7382..1dc99d6923 100644 --- a/crates/relayer/src/chain/cosmos.rs +++ b/crates/relayer/src/chain/cosmos.rs @@ -4,6 +4,7 @@ use bytes::Bytes; use config::CosmosSdkConfig; use core::{future::Future, str::FromStr, time::Duration}; use futures::future::join_all; +use ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerIdFromClientIdRequest; use itertools::Itertools; use num_bigint::BigInt; use prost::Message; @@ -22,6 +23,7 @@ use ibc_proto::ibc::apps::fee::v1::{ use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_proto::interchain_security::ccv::v1::ConsumerParams as CcvConsumerParams; use ibc_proto::Protobuf; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; use ibc_relayer_types::clients::ics07_tendermint::client_state::{ AllowUpdate, ClientState as TmClientState, @@ -367,6 +369,7 @@ impl CosmosSdkChain { } /// Performs a gRPC query to fetch CCV Consumer chain staking parameters. + /// Assumes we are the consumer chain. pub fn query_ccv_consumer_chain_params(&self) -> Result { crate::time!( "query_ccv_consumer_chain_params", @@ -2542,7 +2545,10 @@ impl ChainEndpoint for CosmosSdkChain { Ok(incentivized_response) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { + use ibc_proto::interchain_security::ccv::provider::v1::ConsumerPhase; + use ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerChainsRequest; + crate::time!( "query_consumer_chains", { @@ -2556,9 +2562,10 @@ impl ChainEndpoint for CosmosSdkChain { ibc_proto::interchain_security::ccv::provider::v1::query_client::QueryClient::new, ))?; - let request = tonic::Request::new( - ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerChainsRequest {}, - ); + let request = tonic::Request::new(QueryConsumerChainsRequest { + phase: ConsumerPhase::Launched as i32, + pagination: Some(PageRequest::all().into()), + }); let response = self .block_on(client.query_consumer_chains(request)) @@ -2568,8 +2575,8 @@ impl ChainEndpoint for CosmosSdkChain { let result = response .chains .into_iter() - .map(|c| (c.chain_id.parse().unwrap(), c.client_id.parse().unwrap())) - .collect(); + .map(|c| ConsumerChain::try_from(c).map_err(Error::ics24_host_validation_error)) + .collect::, _>>()?; Ok(result) } @@ -2631,6 +2638,40 @@ impl ChainEndpoint for CosmosSdkChain { IncludeProof::No => Ok((error_receipt, None)), } } + + /// Performs a gRPC query to fetch the CCV ConsumerID corresponding + /// to the given ClientID. + /// + /// Assumes we are the provider chain. + fn query_ccv_consumer_id(&self, client_id: ClientId) -> Result { + use ibc_proto::interchain_security::ccv::provider::v1::query_client::QueryClient; + + crate::telemetry!(query, &self.config.id, "query_ccv_consumer_id"); + crate::time!( + "query_ccv_consumer_id", + { + "src_chain": &self.config.id, + } + ); + + let grpc_addr = Uri::from_str(&self.config.grpc_addr.to_string()) + .map_err(|e| Error::invalid_uri(self.config.grpc_addr.to_string(), e))?; + + let mut client = self + .block_on(create_grpc_client(&grpc_addr, QueryClient::new))? + .max_decoding_message_size(self.config.max_grpc_decoding_size.get_bytes() as usize); + + let request = tonic::Request::new(QueryConsumerIdFromClientIdRequest { + client_id: client_id.to_string(), + }); + + let response = self + .block_on(client.query_consumer_id_from_client_id(request)) + .map_err(|e| Error::grpc_status(e, "query_ccv_consumer_id".to_owned()))?; + + let consumer_id = response.into_inner().consumer_id; + Ok(ConsumerId::new(consumer_id)) + } } fn sort_events_by_sequence(events: &mut [IbcEventWithHeight]) { @@ -2814,7 +2855,7 @@ pub async fn fetch_compat_mode( } }?; - Ok(compat_mode.into()) + Ok(compat_mode) } #[cfg(test)] diff --git a/crates/relayer/src/chain/cosmos/encode.rs b/crates/relayer/src/chain/cosmos/encode.rs index 447785f97e..a64ebd51e8 100644 --- a/crates/relayer/src/chain/cosmos/encode.rs +++ b/crates/relayer/src/chain/cosmos/encode.rs @@ -185,6 +185,7 @@ pub fn encode_to_bech32(address: &str, account_prefix: &str) -> Result Result<(AuthInfo, Vec), Error> { + #[allow(deprecated)] let auth_info = AuthInfo { signer_infos: vec![signer_info], fee: Some(fee), diff --git a/crates/relayer/src/chain/cosmos/query/account.rs b/crates/relayer/src/chain/cosmos/query/account.rs index 19d9ac5b34..eb8e1dc8b8 100644 --- a/crates/relayer/src/chain/cosmos/query/account.rs +++ b/crates/relayer/src/chain/cosmos/query/account.rs @@ -1,6 +1,6 @@ use http::uri::Uri; use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; -use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest}; +use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; use prost::Message; use tracing::info; @@ -9,6 +9,19 @@ use crate::config::default::max_grpc_decoding_size; use crate::error::Error; use crate::util::create_grpc_client; +/// EthAccount defines an Ethermint account. +/// TODO: remove when/if a canonical `EthAccount` +/// lands in the next Cosmos SDK release +/// (note +/// only adds the PubKey type) +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthAccount { + #[prost(message, optional, tag = "1")] + pub base_account: ::core::option::Option, + #[prost(bytes = "vec", tag = "2")] + pub code_hash: ::prost::alloc::vec::Vec, +} + /// Get a `&mut Account` from an `&mut Option` if it is `Some(Account)`. /// Otherwise query for the account information, update the `Option` to `Some`, /// and return the underlying `&mut` reference. diff --git a/crates/relayer/src/chain/cosmos/query/balance.rs b/crates/relayer/src/chain/cosmos/query/balance.rs index 2b8a93e6de..23053fd807 100644 --- a/crates/relayer/src/chain/cosmos/query/balance.rs +++ b/crates/relayer/src/chain/cosmos/query/balance.rs @@ -53,6 +53,7 @@ pub async fn query_all_balances( let request = tonic::Request::new(QueryAllBalancesRequest { address: account_address.to_string(), pagination: None, + resolve_denom: false, // TODO: Correctly handle resolve_denom argument }); let response = client diff --git a/crates/relayer/src/chain/endpoint.rs b/crates/relayer/src/chain/endpoint.rs index 0a12e73341..387ff0b8d6 100644 --- a/crates/relayer/src/chain/endpoint.rs +++ b/crates/relayer/src/chain/endpoint.rs @@ -1,6 +1,7 @@ use alloc::sync::Arc; use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::core::ics02_client::height::Height; use tokio::runtime::Runtime as TokioRuntime; @@ -688,7 +689,7 @@ pub trait ChainEndpoint: Sized { request: QueryIncentivizedPacketRequest, ) -> Result; - fn query_consumer_chains(&self) -> Result, Error>; + fn query_consumer_chains(&self) -> Result, Error>; fn query_upgrade( &self, @@ -703,4 +704,6 @@ pub trait ChainEndpoint: Sized { height: Height, include_proof: IncludeProof, ) -> Result<(ErrorReceipt, Option), Error>; + + fn query_ccv_consumer_id(&self, client_id: ClientId) -> Result; } diff --git a/crates/relayer/src/chain/handle.rs b/crates/relayer/src/chain/handle.rs index 2137821e61..b4aae48916 100644 --- a/crates/relayer/src/chain/handle.rs +++ b/crates/relayer/src/chain/handle.rs @@ -8,6 +8,7 @@ use ibc_proto::ibc::apps::fee::v1::{ QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse, }; use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::{ applications::ics31_icq::response::CrossChainQueryResponse, core::{ @@ -371,7 +372,7 @@ pub enum ChainRequest { }, QueryConsumerChains { - reply_to: ReplyTo>, + reply_to: ReplyTo>, }, QueryUpgrade { @@ -387,6 +388,11 @@ pub enum ChainRequest { include_proof: IncludeProof, reply_to: ReplyTo<(ErrorReceipt, Option)>, }, + + QueryConsumerId { + client_id: ClientId, + reply_to: ReplyTo, + }, } pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { @@ -699,7 +705,7 @@ pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { request: QueryIncentivizedPacketRequest, ) -> Result; - fn query_consumer_chains(&self) -> Result, Error>; + fn query_consumer_chains(&self) -> Result, Error>; fn query_upgrade( &self, @@ -714,4 +720,6 @@ pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { height: Height, include_proof: IncludeProof, ) -> Result<(ErrorReceipt, Option), Error>; + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result; } diff --git a/crates/relayer/src/chain/handle/base.rs b/crates/relayer/src/chain/handle/base.rs index 0898269281..64d7d7d30e 100644 --- a/crates/relayer/src/chain/handle/base.rs +++ b/crates/relayer/src/chain/handle/base.rs @@ -8,20 +8,23 @@ use ibc_proto::ibc::{ core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}, }; use ibc_relayer_types::{ - applications::ics31_icq::response::CrossChainQueryResponse, + applications::{ + ics28_ccv::msgs::{ConsumerChain, ConsumerId}, + ics31_icq::response::CrossChainQueryResponse, + }, core::{ ics02_client::{events::UpdateClient, header::AnyHeader}, - ics03_connection::connection::{ConnectionEnd, IdentifiedConnectionEnd}, - ics03_connection::version::Version, - ics04_channel::channel::{ChannelEnd, IdentifiedChannelEnd}, + ics03_connection::{ + connection::{ConnectionEnd, IdentifiedConnectionEnd}, + version::Version, + }, ics04_channel::{ + channel::{ChannelEnd, IdentifiedChannelEnd}, packet::{PacketMsgType, Sequence}, upgrade::{ErrorReceipt, Upgrade}, }, ics23_commitment::{commitment::CommitmentPrefix, merkle::MerkleProof}, - ics24_host::identifier::ChainId, - ics24_host::identifier::ChannelId, - ics24_host::identifier::{ClientId, ConnectionId, PortId}, + ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, }, proofs::Proofs, signer::Signer, @@ -522,7 +525,7 @@ impl ChainHandle for BaseChainHandle { self.send(|reply_to| ChainRequest::QueryIncentivizedPacket { request, reply_to }) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.send(|reply_to| ChainRequest::QueryConsumerChains { reply_to }) } @@ -553,4 +556,11 @@ impl ChainHandle for BaseChainHandle { reply_to, }) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.send(|reply_to| ChainRequest::QueryConsumerId { + client_id: client_id.clone(), + reply_to, + }) + } } diff --git a/crates/relayer/src/chain/handle/cache.rs b/crates/relayer/src/chain/handle/cache.rs index 6a1731f903..403fee0bb2 100644 --- a/crates/relayer/src/chain/handle/cache.rs +++ b/crates/relayer/src/chain/handle/cache.rs @@ -1,20 +1,22 @@ use core::fmt::{Display, Error as FmtError, Formatter}; use crossbeam_channel as channel; -use ibc_relayer_types::core::ics02_client::header::AnyHeader; -use ibc_relayer_types::core::ics04_channel::upgrade::ErrorReceipt; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerId; use tracing::Span; use ibc_proto::ibc::apps::fee::v1::QueryIncentivizedPacketRequest; use ibc_proto::ibc::apps::fee::v1::QueryIncentivizedPacketResponse; use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerChain; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; use ibc_relayer_types::core::ics02_client::events::UpdateClient; +use ibc_relayer_types::core::ics02_client::header::AnyHeader; use ibc_relayer_types::core::ics03_connection::connection::ConnectionEnd; use ibc_relayer_types::core::ics03_connection::connection::IdentifiedConnectionEnd; use ibc_relayer_types::core::ics03_connection::version::Version; use ibc_relayer_types::core::ics04_channel::channel::ChannelEnd; use ibc_relayer_types::core::ics04_channel::channel::IdentifiedChannelEnd; use ibc_relayer_types::core::ics04_channel::packet::{PacketMsgType, Sequence}; +use ibc_relayer_types::core::ics04_channel::upgrade::ErrorReceipt; use ibc_relayer_types::core::ics04_channel::upgrade::Upgrade; use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentPrefix; use ibc_relayer_types::core::ics23_commitment::merkle::MerkleProof; @@ -515,7 +517,7 @@ impl ChainHandle for CachingChainHandle { self.inner.query_incentivized_packet(request) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.inner.query_consumer_chains() } @@ -537,4 +539,8 @@ impl ChainHandle for CachingChainHandle { self.inner .query_upgrade_error(request, height, include_proof) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.inner.query_ccv_consumer_id(client_id) + } } diff --git a/crates/relayer/src/chain/handle/counting.rs b/crates/relayer/src/chain/handle/counting.rs index 1096e1b0bf..4fe7685a9e 100644 --- a/crates/relayer/src/chain/handle/counting.rs +++ b/crates/relayer/src/chain/handle/counting.rs @@ -3,13 +3,13 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock, RwLockReadGuard}; use crossbeam_channel as channel; -use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; -use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use tracing::{debug, Span}; use ibc_proto::ibc::apps::fee::v1::{ QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse, }; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; use ibc_relayer_types::core::ics02_client::events::UpdateClient; use ibc_relayer_types::core::ics02_client::header::AnyHeader; @@ -19,6 +19,7 @@ use ibc_relayer_types::core::ics03_connection::version::Version; use ibc_relayer_types::core::ics04_channel::channel::ChannelEnd; use ibc_relayer_types::core::ics04_channel::channel::IdentifiedChannelEnd; use ibc_relayer_types::core::ics04_channel::packet::{PacketMsgType, Sequence}; +use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentPrefix; use ibc_relayer_types::core::ics23_commitment::merkle::MerkleProof; use ibc_relayer_types::core::ics24_host::identifier::{ @@ -507,7 +508,7 @@ impl ChainHandle for CountingChainHandle { self.inner.query_incentivized_packet(request) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.inc_metric("query_consumer_chains"); self.inner.query_consumer_chains() } @@ -532,4 +533,9 @@ impl ChainHandle for CountingChainHandle { self.inner .query_upgrade_error(request, height, include_proof) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.inc_metric("query_ccv_consumer_id"); + self.inner.query_ccv_consumer_id(client_id) + } } diff --git a/crates/relayer/src/chain/runtime.rs b/crates/relayer/src/chain/runtime.rs index 1422f386b3..0aff1cb248 100644 --- a/crates/relayer/src/chain/runtime.rs +++ b/crates/relayer/src/chain/runtime.rs @@ -10,10 +10,12 @@ use ibc_proto::ibc::{ core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}, }; use ibc_relayer_types::{ - applications::ics31_icq::response::CrossChainQueryResponse, + applications::{ + ics28_ccv::msgs::{ConsumerChain, ConsumerId}, + ics31_icq::response::CrossChainQueryResponse, + }, core::{ - ics02_client::events::UpdateClient, - ics02_client::header::AnyHeader, + ics02_client::{events::UpdateClient, header::AnyHeader}, ics03_connection::{ connection::{ConnectionEnd, IdentifiedConnectionEnd}, version::Version, @@ -24,7 +26,7 @@ use ibc_relayer_types::{ upgrade::{ErrorReceipt, Upgrade}, }, ics23_commitment::{commitment::CommitmentPrefix, merkle::MerkleProof}, - ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, + ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}, }, proofs::Proofs, signer::Signer, @@ -363,6 +365,10 @@ where ChainRequest::QueryUpgradeError { request, height, include_proof, reply_to } => { self.query_upgrade_error(request, height, include_proof, reply_to)? }, + + ChainRequest::QueryConsumerId { client_id, reply_to } => { + self.query_ccv_consumer_id(client_id, reply_to)? + }, } }, } @@ -865,10 +871,7 @@ where Ok(()) } - fn query_consumer_chains( - &self, - reply_to: ReplyTo>, - ) -> Result<(), Error> { + fn query_consumer_chains(&self, reply_to: ReplyTo>) -> Result<(), Error> { let result = self.chain.query_consumer_chains(); reply_to.send(result).map_err(Error::send)?; @@ -902,4 +905,15 @@ where Ok(()) } + + fn query_ccv_consumer_id( + &self, + client_id: ClientId, + reply_to: ReplyTo, + ) -> Result<(), Error> { + let result = self.chain.query_ccv_consumer_id(client_id); + reply_to.send(result).map_err(Error::send)?; + + Ok(()) + } } diff --git a/crates/relayer/src/config/compat_mode.rs b/crates/relayer/src/config/compat_mode.rs index cb2a035855..795081ea6f 100644 --- a/crates/relayer/src/config/compat_mode.rs +++ b/crates/relayer/src/config/compat_mode.rs @@ -1,95 +1 @@ -use core::fmt::{Display, Error as FmtError, Formatter}; -use core::str::FromStr; -use serde::Deserialize; -use serde::Deserializer; -use serde::Serialize; -use serde::Serializer; - -use tendermint_rpc::client::CompatMode as TmCompatMode; - -use crate::config::Error; - -/// CometBFT RPC compatibility mode -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum CompatMode { - /// Use version 0.34 of the protocol. - V0_34, - /// Use version 0.37+ of the protocol. - V0_37, -} - -impl Display for CompatMode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - match self { - Self::V0_34 => write!(f, "v0.34"), - Self::V0_37 => write!(f, "v0.37"), - } - } -} - -impl FromStr for CompatMode { - type Err = Error; - - fn from_str(s: &str) -> Result { - const VALID_COMPAT_MODES: &str = "0.34, 0.37, 0.38"; - - // Trim leading 'v', if present - match s.trim_start_matches('v') { - "0.34" => Ok(CompatMode::V0_34), - "0.37" => Ok(CompatMode::V0_37), - "0.38" => Ok(CompatMode::V0_37), // v0.38 is compatible with v0.37 - _ => Err(Error::invalid_compat_mode( - s.to_string(), - VALID_COMPAT_MODES, - )), - } - } -} - -impl<'de> Deserialize<'de> for CompatMode { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use serde::de; - - let s = String::deserialize(deserializer)?; - FromStr::from_str(&s).map_err(de::Error::custom) - } -} - -impl Serialize for CompatMode { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl From for CompatMode { - fn from(value: TmCompatMode) -> Self { - match value { - TmCompatMode::V0_34 => Self::V0_34, - TmCompatMode::V0_37 => Self::V0_37, - } - } -} - -impl From for TmCompatMode { - fn from(value: CompatMode) -> Self { - match value { - CompatMode::V0_34 => Self::V0_34, - CompatMode::V0_37 => Self::V0_37, - } - } -} - -impl CompatMode { - pub fn equal_to_tm_compat_mode(&self, tm_compat_mode: TmCompatMode) -> bool { - match self { - Self::V0_34 => tm_compat_mode == TmCompatMode::V0_34, - Self::V0_37 => tm_compat_mode == TmCompatMode::V0_37, - } - } -} +pub use tendermint_rpc::client::CompatMode; diff --git a/crates/relayer/src/error.rs b/crates/relayer/src/error.rs index 6d46f90fe0..8fa5bd4847 100644 --- a/crates/relayer/src/error.rs +++ b/crates/relayer/src/error.rs @@ -30,6 +30,7 @@ use ibc_relayer_types::clients::ics07_tendermint::error as tendermint_error; use ibc_relayer_types::core::ics02_client::{client_type::ClientType, error as client_error}; use ibc_relayer_types::core::ics03_connection::error as connection_error; use ibc_relayer_types::core::ics23_commitment::error as commitment_error; +use ibc_relayer_types::core::ics24_host::error::ValidationError; use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, ConnectionId}; use ibc_relayer_types::proofs::ProofError; @@ -633,6 +634,10 @@ define_error! { InvalidChannelString { channel: String } |e| { format!("invalid channel string {}", e.channel) }, + + Ics24HostValidationError + [ ValidationError ] + |_| { "ICS24 host validation error" }, } } diff --git a/crates/relayer/src/foreign_client.rs b/crates/relayer/src/foreign_client.rs index c05a5739f4..1120ee2dda 100644 --- a/crates/relayer/src/foreign_client.rs +++ b/crates/relayer/src/foreign_client.rs @@ -9,6 +9,7 @@ use std::thread; use std::time::Instant; use ibc_proto::google::protobuf::Any; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerId; use itertools::Itertools; use tracing::{debug, error, info, instrument, trace, warn}; @@ -365,7 +366,7 @@ pub enum ConsensusStateTrusted { NotTrusted { elapsed: Duration, network_timestamp: Timestamp, - consensus_state_timestmap: Timestamp, + consensus_state_timestamp: Timestamp, }, Trusted { elapsed: Duration, @@ -767,12 +768,12 @@ impl ForeignClient { error!( latest_height = %client_state.latest_height(), - network_timestmap = %network_timestamp, - consensus_state_timestamp = %consensus_state_timestmap, + network_timestamp = %network_timestamp, + consensus_state_timestamp = %consensus_state_timestamp, elapsed = ?elapsed, "client state is not valid: latest height is outside of trusting period!", ); @@ -829,7 +830,7 @@ impl ForeignClient ForeignClient ForeignClient { + msgs.push( + MsgSubmitIcsConsumerMisbehaviour { + submitter: signer.clone(), + misbehaviour: tm_misbehaviour, + consumer_id, + } + .to_any(), + ); } - .to_any(), - ); + Err(e) => { + error!( + "cannot build CCV misbehaviour evidence: failed to fetch CCV consumer id for client {}: {}", + self.id, e + ); + } + } } msgs.push( @@ -1931,3 +1943,21 @@ pub fn extract_client_id(event: &IbcEvent) -> Result<&ClientId, ForeignClientErr )), } } + +pub fn fetch_ccv_consumer_id( + provider: &impl ChainHandle, + client_id: &ClientId, +) -> Result { + let consumer_id = provider.query_ccv_consumer_id(client_id).map_err(|e| { + ForeignClientError::misbehaviour( + format!( + "failed to query CCV consumer id corresponding to client {} from provider {}", + client_id, + provider.id() + ), + e, + ) + })?; + + Ok(consumer_id) +} diff --git a/crates/relayer/src/upgrade_chain.rs b/crates/relayer/src/upgrade_chain.rs index 2889d7ab62..318f35130b 100644 --- a/crates/relayer/src/upgrade_chain.rs +++ b/crates/relayer/src/upgrade_chain.rs @@ -296,6 +296,7 @@ fn build_upgrade_proposal( metadata: "".to_string(), title: "proposal 0".to_string(), summary: "upgrade the chain software and unbonding period".to_string(), + expedited: false, }; let mut buf_msg = Vec::new(); diff --git a/crates/relayer/src/util/compat_mode.rs b/crates/relayer/src/util/compat_mode.rs index 716534bb95..c637d5a247 100644 --- a/crates/relayer/src/util/compat_mode.rs +++ b/crates/relayer/src/util/compat_mode.rs @@ -1,7 +1,6 @@ use tracing::warn; use tendermint::Version; -use tendermint_rpc::client::CompatMode as TmCompatMode; use crate::chain::cosmos::version::ConsensusVersion; use crate::config::compat_mode::CompatMode; @@ -11,21 +10,21 @@ pub fn compat_mode_from_node_version( configured_version: &Option, version: Version, ) -> Result { - let queried_version = TmCompatMode::from_version(version); + let queried_version = CompatMode::from_version(version); // This will prioritize the use of the CompatMode specified in Hermes configuration file match (configured_version, queried_version) { - (Some(configured), Ok(queried)) if !configured.equal_to_tm_compat_mode(queried) => { + (Some(configured), Ok(queried)) if configured != &queried => { warn!( "potential `compat_mode` misconfiguration! Configured version '{configured}' does not match chain version '{queried}'. \ Hermes will use the configured `compat_mode` version '{configured}'. \ If this configuration is done on purpose this message can be ignored.", ); - Ok(configured.clone()) + Ok(*configured) } - (Some(configured), _) => Ok(configured.clone()), - (_, Ok(queried)) => Ok(queried.into()), + (Some(configured), _) => Ok(*configured), + (_, Ok(queried)) => Ok(queried), (_, Err(e)) => Err(Error::invalid_compat_mode(e)), } } @@ -50,7 +49,7 @@ pub fn compat_mode_from_version_specs( If this configuration is done on purpose this message can be ignored." ); - Ok(configured.clone()) + Ok(*configured) } (Some(configured), None) => { warn!( @@ -58,7 +57,7 @@ pub fn compat_mode_from_version_specs( and will use the configured `compat_mode` version `{configured}`." ); - Ok(configured.clone()) + Ok(*configured) } (None, Some(queried)) => Ok(queried), (None, None) => { @@ -77,7 +76,7 @@ fn compat_mode_from_semver(v: semver::Version) -> Option { match (v.major, v.minor) { (0, 34) => Some(CompatMode::V0_34), (0, 37) => Some(CompatMode::V0_37), - (0, 38) => Some(CompatMode::V0_37), + (0, 38) => Some(CompatMode::V0_38), _ => None, } } diff --git a/flake.lock b/flake.lock index d78d62da6b..6b423bbc94 100644 --- a/flake.lock +++ b/flake.lock @@ -101,15 +101,16 @@ "cometbft-src": { "flake": false, "locked": { - "narHash": "sha256-G5gchJMn/BFzwYx8/ikPDL5fS/TuFIBF4DKJbkalp/M=", + "lastModified": 1723450629, + "narHash": "sha256-2QO4KeEUX4HHT1AKhEdPplJHjBhalfM11Dn3/urIVig=", "owner": "cometbft", "repo": "cometbft", - "rev": "66a5a9da9f7a3306f382eb9142ccb9c9f7997d3f", + "rev": "e1b4453baf0af6487ad187c7f17dc50517126673", "type": "github" }, "original": { "owner": "cometbft", - "ref": "v0.38.0", + "ref": "v0.38.11", "repo": "cometbft", "type": "github" } @@ -158,6 +159,7 @@ "gaia17-src": "gaia17-src", "gaia18-src": "gaia18-src", "gaia19-src": "gaia19-src", + "gaia20-src": "gaia20-src", "gaia5-src": "gaia5-src", "gaia6-ordered-src": "gaia6-ordered-src", "gaia6-src": "gaia6-src", @@ -218,6 +220,7 @@ "wasmvm_1_5_0-src": "wasmvm_1_5_0-src", "wasmvm_1_5_2-src": "wasmvm_1_5_2-src", "wasmvm_1_5_4-src": "wasmvm_1_5_4-src", + "wasmvm_1_5_5-src": "wasmvm_1_5_5-src", "wasmvm_1_beta7-src": "wasmvm_1_beta7-src", "wasmvm_2_0_0-src": "wasmvm_2_0_0-src", "wasmvm_2_0_3-src": "wasmvm_2_0_3-src", @@ -225,11 +228,11 @@ "wasmvm_2_1_2-src": "wasmvm_2_1_2-src" }, "locked": { - "lastModified": 1727709426, - "narHash": "sha256-Q+uchaRWUNGOksfHY8x6HAz7nADkRtLHUl11A0mBc7Y=", + "lastModified": 1729681912, + "narHash": "sha256-Ym4WfC/Iogqft2KOoHEv2FlWlgsxOAA5CZCbGXPf65o=", "owner": "informalsystems", "repo": "cosmos.nix", - "rev": "706d3206feb0c816765768dd164790874c221bd3", + "rev": "04a2efd0e01206b5df601b205d7662db79a765f0", "type": "github" }, "original": { @@ -673,6 +676,23 @@ "type": "github" } }, + "gaia20-src": { + "flake": false, + "locked": { + "lastModified": 1726853009, + "narHash": "sha256-N7x3k56AtPbIbbJjqKmlEJIytKElALJwj14lZ2pewZg=", + "owner": "cosmos", + "repo": "gaia", + "rev": "2dba9d471ef73b0a99e844bf55a44ddae700ea06", + "type": "github" + }, + "original": { + "owner": "cosmos", + "ref": "v20.0.0", + "repo": "gaia", + "type": "github" + } + }, "gaia5-src": { "flake": false, "locked": { @@ -1131,15 +1151,16 @@ "interchain-security-src": { "flake": false, "locked": { - "narHash": "sha256-adBzn51PKoRsCL9gIzC5Tcqmu7u3GjxTcDj2jpZ/da8=", + "lastModified": 1726849313, + "narHash": "sha256-1WEvV3LoXfGvZC9fXOb8mBLKVGCVBiXZcwUewSPit+8=", "owner": "cosmos", "repo": "interchain-security", - "rev": "03aada4af3243dbf739a12adfacc7b37232df694", + "rev": "1e60637f9d8f3505208282416abfbb87fabc4795", "type": "github" }, "original": { "owner": "cosmos", - "ref": "feat/ics-misbehaviour-handling", + "ref": "v6.1.0", "repo": "interchain-security", "type": "github" } @@ -1179,16 +1200,16 @@ "juno-src": { "flake": false, "locked": { - "lastModified": 1724278445, - "narHash": "sha256-XvJqp36HSYqm6OMLQqJPX5sCT52GxSLFDMO+fJovh+0=", + "lastModified": 1727102451, + "narHash": "sha256-UaTCcK+I6Wl4yCpbNckx+lRi55kTSucJxzw5irJOVh4=", "owner": "CosmosContracts", "repo": "juno", - "rev": "2f119adacca3a1668ff150c225a3f423501e748c", + "rev": "de3c4d145c7a96c31e3fca6fe8850ce4ab559e33", "type": "github" }, "original": { "owner": "CosmosContracts", - "ref": "v24.0.0", + "ref": "v25.0.0", "repo": "juno", "type": "github" } @@ -1444,11 +1465,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1727648392, - "narHash": "sha256-VTlVv1nSxImFxY6RPQpNZxvEOQ0u5s1wBFDgixySNDo=", + "lastModified": 1729788628, + "narHash": "sha256-3suayUinicnvE/4shMZwp9FHT5izUM8gMpdEO/NHBTo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4e0c36e4dd53f35d5a6385bdae88895ec5832f70", + "rev": "63487b2f26fa065cfeeaa47dddb08e2856ba53e8", "type": "github" }, "original": { @@ -2010,6 +2031,23 @@ "type": "github" } }, + "wasmvm_1_5_5-src": { + "flake": false, + "locked": { + "lastModified": 1727088523, + "narHash": "sha256-ysS2pMMm+s1JsHVv9RhiMHt5g4UGcE5jqOI5YKdC4vU=", + "owner": "CosmWasm", + "repo": "wasmvm", + "rev": "0c5b9ce8446189f07d2bf65fbb902817cf57a563", + "type": "github" + }, + "original": { + "owner": "CosmWasm", + "ref": "v1.5.5", + "repo": "wasmvm", + "type": "github" + } + }, "wasmvm_1_beta7-src": { "flake": false, "locked": { diff --git a/flake.nix b/flake.nix index 1a9ae4229a..ce0e9b787d 100644 --- a/flake.nix +++ b/flake.nix @@ -2,9 +2,9 @@ description = "Nix development dependencies for ibc-rs"; inputs = { - nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable; - flake-utils.url = github:numtide/flake-utils; - cosmos-nix.url = github:informalsystems/cosmos.nix; + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + cosmos-nix.url = "github:informalsystems/cosmos.nix"; }; outputs = inputs: let @@ -33,6 +33,7 @@ evmos gaia6-ordered gaia18 + gaia20 ibc-go-v2-simapp ibc-go-v3-simapp ibc-go-v4-simapp diff --git a/tools/test-framework/src/chain/driver.rs b/tools/test-framework/src/chain/driver.rs index ace13e8f5f..bd4aba6543 100644 --- a/tools/test-framework/src/chain/driver.rs +++ b/tools/test-framework/src/chain/driver.rs @@ -5,11 +5,11 @@ use alloc::sync::Arc; use core::time::Duration; use eyre::eyre; -use ibc_relayer::config::compat_mode::CompatMode; use std::cmp::max; use tokio::runtime::Runtime; use ibc_relayer::chain::cosmos::types::config::TxConfig; +use ibc_relayer::config::compat_mode::CompatMode; use ibc_relayer_types::applications::transfer::amount::Amount; use ibc_relayer_types::core::ics24_host::identifier::ChainId; diff --git a/tools/test-framework/src/chain/tagged.rs b/tools/test-framework/src/chain/tagged.rs index b574c6eef1..af7a73c06a 100644 --- a/tools/test-framework/src/chain/tagged.rs +++ b/tools/test-framework/src/chain/tagged.rs @@ -132,7 +132,7 @@ impl<'a, Chain: Send> TaggedChainDriverExt for MonoTagged Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.value().query_consumer_chains() } @@ -456,4 +457,8 @@ where self.value() .query_upgrade_error(request, height, include_proof) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.value().query_ccv_consumer_id(client_id) + } }