Skip to content
This repository has been archived by the owner on Nov 29, 2024. It is now read-only.

feat: implement misbehaviour program #103

Merged
merged 23 commits into from
Sep 4, 2024

Conversation

gjermundgaraba
Copy link
Contributor

Closes #56

There are a couple of only semi-related changes that I will comment on in the changes.

@gjermundgaraba gjermundgaraba requested a review from srdtrk as a code owner August 28, 2024 13:55
@gjermundgaraba gjermundgaraba linked an issue Aug 28, 2024 that may be closed by this pull request
with:
version: v1.59
version: v1.60
Copy link
Contributor Author

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");
Copy link
Contributor Author

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));
Copy link
Contributor Author

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.

Copy link
Member

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:
Copy link
Contributor Author

@gjermundgaraba gjermundgaraba Aug 28, 2024

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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.

Copy link
Member

@AdityaSripal AdityaSripal left a 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 Show resolved Hide resolved
programs/misbehaviour/src/lib.rs Show resolved Hide resolved
programs/misbehaviour/src/lib.rs Show resolved Hide resolved

assert!(is_misbehaviour, "Misbehaviour is not detected");

let output_trusted_header_1 = sp1_ics07_tendermint::Height {
Copy link
Member

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

Copy link
Contributor Author

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 🙈

.try_into()
.unwrap(),
};
let output_trusted_header_2 = sp1_ics07_tendermint::Height {
Copy link
Member

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(
Copy link
Member

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 =
Copy link
Member

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)?;
Copy link
Member

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 {
Copy link
Member

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));
Copy link
Member

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

@gjermundgaraba
Copy link
Contributor Author

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!

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 :)

Copy link
Member

@AdityaSripal AdityaSripal left a 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(
Copy link
Member

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{})
Copy link
Member

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() {
Copy link
Member

@AdityaSripal AdityaSripal Sep 2, 2024

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

Copy link
Contributor Author

@gjermundgaraba gjermundgaraba Sep 3, 2024

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!

operator/src/runners/fixtures/misbehaviour.rs Outdated Show resolved Hide resolved
Copy link

@crodriguezvega crodriguezvega left a 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!

contracts/src/SP1ICS07Tendermint.sol Show resolved Hide resolved
@@ -0,0 +1,9 @@
{
Copy link

@crodriguezvega crodriguezvega Sep 3, 2024

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 😄)

Copy link
Contributor Author

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 👍


function test_ValidMisbehaviour() public {
// set a correct timestamp
vm.warp(env.now + 300);

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?

Copy link
Contributor Author

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.

contracts/test/Misbehaviour.t.sol Show resolved Hide resolved
@@ -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

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much!

e2e/interchaintestv8/sp1_ics07_test.go Outdated Show resolved Hide resolved
_ = eth

var height clienttypes.Height
var oldHeader tmclient.Header

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var oldHeader tmclient.Header
var trustedHeader tmclient.Header

Copy link
Contributor Author

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() {

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?

Suggested change
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=".\\*":

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?

Copy link
Contributor Author

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

Comment on lines +55 to +57
trust_threshold: env.trustThreshold.clone().into(),
trusting_period: Duration::from_secs(env.trustingPeriod.into()),
clock_drift: Duration::default(),

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?

Copy link
Contributor Author

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.

Copy link
Member

@AdityaSripal AdityaSripal left a 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

Comment on lines 230 to 233
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{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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{})

Copy link
Contributor Author

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!

@gjermundgaraba gjermundgaraba merged commit 8102e5f into main Sep 4, 2024
16 checks passed
@gjermundgaraba gjermundgaraba deleted the gjermund/56-implement-misbehavior-program branch September 4, 2024 14:20
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Implement misbehavior program
3 participants