-
Notifications
You must be signed in to change notification settings - Fork 5
feat: implement misbehaviour program #103
feat: implement misbehaviour program #103
Conversation
with: | ||
version: v1.59 | ||
version: v1.60 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed to use 1.60 because there is a memory bug in 1.59 that causes a lot of problems on my machine.
The version bumps in this file is just because I thought it was good to have latest version 🤷
@@ -61,13 +63,15 @@ contract SP1TendermintScript is Script, IICS07TendermintMsgs { | |||
bytes32 updateClientVkey = json.readBytes32(".updateClientVkey"); | |||
bytes32 membershipVkey = json.readBytes32(".membershipVkey"); | |||
bytes32 ucAndMembershipVkey = json.readBytes32(".ucAndMembershipVkey"); | |||
bytes32 misbehaviourVkey = json.readBytes32(".misbehaviourVkey"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is a new program, it means we need a new program key to verify it in the proof verification process in Solidity.
} | ||
validateEnv(output.env); | ||
|
||
bytes32 outputConsensusStateHash1 = keccak256(abi.encode(output.trustedConsensusState1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we basically verify that the trusted consensus state in both headers are known (trusted) by us already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In code docs will make it clearer for new readers why this is needed
@@ -51,6 +51,10 @@ issues: | |||
max-same-issues: 10000 | |||
|
|||
linters-settings: | |||
gosec: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new version of golangci-lint has a really aggressive check for casting of ints, which we have to do a lot with heights (and there is no "safe" way that will shut it up)
(For context, I think this check has removed from the default set in for the next release of golangci-lint, so others are also complaining about it)
@@ -34,6 +38,9 @@ import ( | |||
type SP1ICS07TendermintTestSuite struct { | |||
e2esuite.TestSuite | |||
|
|||
// Whether to generate fixtures for the solidity tests | |||
generateFixtures bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this to be able to generate fixtures from the e2e tests for misbehaviour. We had already talked about potentially doing this for all fixtures, so we don't depend on getting state from the mocha testnet as we do now. Instead we could generate them from the e2e tests which should be perfectly valid. We already do this for benchmarks in the solidity-ibc-eureka repo.
justfile
Outdated
"RUST_LOG=info SP1_PROVER={{prover}} ./target/release/operator fixtures update-client --trusted-block $TRUSTED_HEIGHT --target-block $TARGET_HEIGHT -o 'contracts/fixtures/update_client_fixture.json'" \ | ||
"sleep 15 && RUST_LOG=info SP1_PROVER={{prover}} ./target/release/operator fixtures update-client-and-membership --key-paths clients/07-tendermint-0/clientState,clients/07-tendermint-001/clientState --trusted-block $TRUSTED_HEIGHT --target-block $TARGET_HEIGHT -o 'contracts/fixtures/uc_and_memberships_fixture.json'" \ | ||
"sleep 30 && RUST_LOG=info SP1_PROVER={{prover}} ./target/release/operator fixtures membership --key-paths clients/07-tendermint-0/clientState,clients/07-tendermint-001/clientState --trusted-block $TRUSTED_HEIGHT -o 'contracts/fixtures/memberships_fixture.json'" | ||
cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/TestMisbehaviour$' -timeout 40m |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into some issues when trying to run this one in parallel, but it is quite fast, so not as big of a deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work!! It took me a while to understand what was happening and to dive into the ibc-rs code as well to see how it all fit together.
Think some code docs will help a lot and i left a few suggestions. Some may be obvious to rust readers which can be omitted in that case.
I will review the tests in a second pass but the implementation code lgtm!
programs/misbehaviour/src/lib.rs
Outdated
|
||
assert!(is_misbehaviour, "Misbehaviour is not detected"); | ||
|
||
let output_trusted_header_1 = sp1_ics07_tendermint::Height { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we name this variable output_trusted_height_1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would make more sense 🙈
programs/misbehaviour/src/lib.rs
Outdated
.try_into() | ||
.unwrap(), | ||
}; | ||
let output_trusted_header_2 = sp1_ics07_tendermint::Height { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
args.trust_options.trust_level, | ||
) | ||
.await?; | ||
let genesis_2 = SP1ICS07TendermintGenesis::from_env( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use trusted light block 2 to instantiate a new SP1 tendermint client with light block 2 as initial trusted consensus state
) | ||
.await?; | ||
|
||
let trusted_consensus_state_1 = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the clients to convert the Tendermint light blocks into the IBC Tendermint trusted consensus states
ConsensusState::abi_decode(&genesis_1.trusted_consensus_state, false)?; | ||
let trusted_consensus_state_2 = | ||
ConsensusState::abi_decode(&genesis_2.trusted_consensus_state, false)?; | ||
let trusted_client_state_2 = ClientState::abi_decode(&genesis_2.trusted_client_state, false)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the client state from genesis_2 as the client state since they will both be the same
|
||
let verify_misbehaviour_prover = SP1ICS07TendermintProver::<MisbehaviourProgram>::default(); | ||
|
||
let contract_env = Env { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
construct contract env from the client state
} | ||
validateEnv(output.env); | ||
|
||
bytes32 outputConsensusStateHash1 = keccak256(abi.encode(output.trustedConsensusState1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In code docs will make it clearer for new readers why this is needed
Co-authored-by: Aditya <[email protected]>
Co-authored-by: Aditya <[email protected]>
Co-authored-by: Aditya <[email protected]>
Co-authored-by: Aditya <[email protected]>
Thank you! Really good suggestions! I have added all the requested comment points. Let me know if you would like any other places documented better or any other changes you would like to see :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super super impressed with the testing setup you guys have built!!! I have a couple nits and one more test case to check but everything else looks great
// It uses the given oldHeader as a base to create the new header with a new hash, and sign using the existing validators | ||
// It can be used to for instance test misbehaviour | ||
// Partially based on https://github.com/cosmos/relayer/blob/f9aaf3dd0ebfe99fbe98d190a145861d7df93804/interchaintest/misbehaviour_test.go#L38 | ||
func (s *TestSuite) CreateTMClientHeader( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
impressive work!
|
||
validators1 := jsonIntermediary["header_1"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | ||
validators2 := jsonIntermediary["header_2"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | ||
validators3 := jsonIntermediary["header_1"].(map[string]interface{})["trusted_validators"].(map[string]interface{})["validators"].([]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these names should trustedValidators1 or similar
s.Require().ErrorContains(err, "Misbehaviour is not detected") | ||
})) | ||
|
||
s.Require().True(s.Run("Valid misbehaviour", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a test with time montonicity breaking. ie, next header has a lower time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that makes perfect sense!
Co-authored-by: Aditya <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot to unpack, but this is fantastic work. Well done, @gjermundgaraba!
@@ -0,0 +1,9 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename file to misbehaviour_fixture.json
? (to keep naming consistent with the other files in the folder - alternatively rename the other files to prepend e2e
😄)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it makes sense. It was just to signify a difference as the other ones are currently generated from Celestia mocha testnet. But I think it still makes sense to rename this one to misbehaviour_fixture.json to keep naming consistent 👍
contracts/test/Misbehaviour.t.sol
Outdated
|
||
function test_ValidMisbehaviour() public { | ||
// set a correct timestamp | ||
vm.warp(env.now + 300); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why adding 300 sets the correct timestamp?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
300 is slightly arbitrary, but the idea was that slightly in the future is more realistic as the env.now is the timestamp when the proof was generated, and the misbehaviour will be submitted later.
@@ -133,6 +145,132 @@ func (s *TestSuite) GetTxReciept(ctx context.Context, chain *ethereum.EthereumCh | |||
|
|||
return receipt != nil, nil | |||
}) | |||
|
|||
// TODO: This should check if the tx was actually successful and return a bool or err on that basis |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To address this TODO, do we basically need to check if receipt.Status == ethtypes.ReceiptStatusSuccessful
? (can be done in a separate PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much!
_ = eth | ||
|
||
var height clienttypes.Height | ||
var oldHeader tmclient.Header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var oldHeader tmclient.Header | |
var trustedHeader tmclient.Header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed! Fixing this
|
||
var height clienttypes.Height | ||
var oldHeader tmclient.Header | ||
s.Require().True(s.Run("Get old header", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are basically getting a trusted header, right?
s.Require().True(s.Run("Get old header", func() { | |
s.Require().True(s.Run("Get trusted header", func() { |
@@ -29,40 +31,40 @@ install-operator: | |||
@echo "Installed the operator executable" | |||
|
|||
# Run the Solidity tests using `forge test` command | |||
test-foundry: | |||
forge test -vvv | |||
test-foundry testname=".\\*": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we still have a run all tests command anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If no name is passed in it will use the default value which runs all tests
trust_threshold: env.trustThreshold.clone().into(), | ||
trusting_period: Duration::from_secs(env.trustingPeriod.into()), | ||
clock_drift: Duration::default(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In production environments, these values should come form the client state, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but in the proof program all values need to be passed in. So the operator/relayer is responsible for setting these values. The operator currently gets this by querying a light block of the trusted height.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your new test looks good. Think you may have misunderstood my nit. Fix or leave as you see fit
trustedValidators1 := jsonIntermediary["header_1"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | ||
trustedValidators2 := jsonIntermediary["header_2"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | ||
trustedValidators3 := jsonIntermediary["header_1"].(map[string]interface{})["trusted_validators"].(map[string]interface{})["validators"].([]interface{}) | ||
trustedValidators4 := jsonIntermediary["header_2"].(map[string]interface{})["trusted_validators"].(map[string]interface{})["validators"].([]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trustedValidators1 := jsonIntermediary["header_1"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | |
trustedValidators2 := jsonIntermediary["header_2"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | |
trustedValidators3 := jsonIntermediary["header_1"].(map[string]interface{})["trusted_validators"].(map[string]interface{})["validators"].([]interface{}) | |
trustedValidators4 := jsonIntermediary["header_2"].(map[string]interface{})["trusted_validators"].(map[string]interface{})["validators"].([]interface{}) | |
validators1 := jsonIntermediary["header_1"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | |
validators2 := jsonIntermediary["header_2"].(map[string]interface{})["validator_set"].(map[string]interface{})["validators"].([]interface{}) | |
trustedValidators1 := jsonIntermediary["header_1"].(map[string]interface{})["trusted_validators"].(map[string]interface{})["validators"].([]interface{}) | |
trustedValidators2 := jsonIntermediary["header_2"].(map[string]interface{})["trusted_validators"].(map[string]interface{})["validators"].([]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! Yeah of course, that makes sense 🙈 Fixing it!
Closes #56
There are a couple of only semi-related changes that I will comment on in the changes.