-
Notifications
You must be signed in to change notification settings - Fork 122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat!: Cryptographic verification of equivocation #1287
Changes from all commits
a77eea1
21e3d83
292ad75
f168b9b
f12a5c0
2501e83
eb6a079
98af9c0
c881a1a
a71f1fe
3be76ad
691d206
88e0717
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,15 @@ import "google/api/annotations.proto"; | |
import "gogoproto/gogo.proto"; | ||
import "cosmos_proto/cosmos.proto"; | ||
import "google/protobuf/any.proto"; | ||
import "ibc/lightclients/tendermint/v1/tendermint.proto"; | ||
import "tendermint/types/evidence.proto"; | ||
|
||
|
||
// Msg defines the Msg service. | ||
service Msg { | ||
rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse); | ||
rpc SubmitConsumerMisbehaviour(MsgSubmitConsumerMisbehaviour) returns (MsgSubmitConsumerMisbehaviourResponse); | ||
rpc SubmitConsumerDoubleVoting(MsgSubmitConsumerDoubleVoting) returns (MsgSubmitConsumerDoubleVotingResponse); | ||
} | ||
|
||
message MsgAssignConsumerKey { | ||
|
@@ -28,3 +33,33 @@ message MsgAssignConsumerKey { | |
} | ||
|
||
message MsgAssignConsumerKeyResponse {} | ||
|
||
|
||
// MsgSubmitConsumerMisbehaviour defines a message that reports a light client attack, | ||
// also known as a misbehaviour, observed on a consumer chain | ||
message MsgSubmitConsumerMisbehaviour { | ||
option (gogoproto.equal) = false; | ||
option (gogoproto.goproto_getters) = false; | ||
string submitter = 1; | ||
// The Misbehaviour of the consumer chain wrapping | ||
// two conflicting IBC headers | ||
ibc.lightclients.tendermint.v1.Misbehaviour misbehaviour = 2; | ||
} | ||
|
||
message MsgSubmitConsumerMisbehaviourResponse {} | ||
|
||
|
||
// MsgSubmitConsumerDoubleVoting defines a message that reports | ||
// a double signing infraction observed on a consumer chain | ||
message MsgSubmitConsumerDoubleVoting { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. emphasize it's about double signing attacks |
||
option (gogoproto.equal) = false; | ||
option (gogoproto.goproto_getters) = false; | ||
string submitter = 1; | ||
// The equivocation of the consumer chain wrapping | ||
// an evidence of a validator that signed two conflicting votes | ||
tendermint.types.DuplicateVoteEvidence duplicate_vote_evidence = 2; | ||
// The light client header of the infraction block | ||
ibc.lightclients.tendermint.v1.Header infraction_block_header = 3; | ||
} | ||
|
||
message MsgSubmitConsumerDoubleVotingResponse {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,7 +63,7 @@ type StartChainAction struct { | |
validators []StartChainValidator | ||
// Genesis changes specific to this action, appended to genesis changes defined in chain config | ||
genesisChanges string | ||
skipGentx bool | ||
isConsumer bool | ||
} | ||
|
||
type StartChainValidator struct { | ||
|
@@ -133,7 +133,7 @@ func (tr TestRun) startChain( | |
cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", | ||
"/testnet-scripts/start-chain.sh", chainConfig.binaryName, string(vals), | ||
string(chainConfig.chainId), chainConfig.ipPrefix, genesisChanges, | ||
fmt.Sprint(action.skipGentx), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check that the sovereign tests are not broken @MSalopek |
||
fmt.Sprint(action.isConsumer), | ||
// override config/config.toml for each node on chain | ||
// usually timeout_commit and peer_gossip_sleep_duration are changed to vary the test run duration | ||
// lower timeout_commit means the blocks are produced faster making the test run shorter | ||
|
@@ -170,6 +170,7 @@ func (tr TestRun) startChain( | |
tr.addChainToRelayer(addChainToRelayerAction{ | ||
chain: action.chain, | ||
validator: action.validators[0].id, | ||
consumer: action.isConsumer, | ||
}, verbose) | ||
} | ||
|
||
|
@@ -280,6 +281,8 @@ func (tr TestRun) submitConsumerAdditionProposal( | |
if err != nil { | ||
log.Fatal(err, "\n", string(bz)) | ||
} | ||
|
||
tr.waitBlocks(action.chain, 1, 5*time.Second) | ||
} | ||
|
||
type submitConsumerRemovalProposalAction struct { | ||
|
@@ -565,7 +568,7 @@ func (tr TestRun) startConsumerChain( | |
chain: action.consumerChain, | ||
validators: action.validators, | ||
genesisChanges: consumerGenesis, | ||
skipGentx: true, | ||
isConsumer: true, | ||
}, verbose) | ||
} | ||
|
||
|
@@ -699,6 +702,7 @@ func (tr TestRun) startChangeover( | |
type addChainToRelayerAction struct { | ||
chain chainID | ||
validator validatorID | ||
consumer bool | ||
} | ||
|
||
const hermesChainConfigTemplate = ` | ||
|
@@ -715,7 +719,8 @@ rpc_addr = "%s" | |
rpc_timeout = "10s" | ||
store_prefix = "ibc" | ||
trusting_period = "14days" | ||
websocket_addr = "%s" | ||
event_source = { mode = "push", url = "%s", batch_delay = "50ms" } | ||
ccv_consumer_chain = %v | ||
|
||
[chains.gas_price] | ||
denom = "stake" | ||
|
@@ -814,7 +819,7 @@ func (tr TestRun) addChainToHermes( | |
keyName, | ||
rpcAddr, | ||
wsAddr, | ||
// action.consumer, | ||
action.consumer, | ||
) | ||
|
||
bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, "/root/.hermes/config.toml") | ||
|
@@ -828,7 +833,15 @@ func (tr TestRun) addChainToHermes( | |
} | ||
|
||
// Save mnemonic to file within container | ||
saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, tr.validatorConfigs[action.validator].mnemonic, "/root/.hermes/mnemonic.txt") | ||
var mnemonic string | ||
if tr.validatorConfigs[action.validator].useConsumerKey && action.consumer { | ||
mnemonic = tr.validatorConfigs[action.validator].consumerMnemonic | ||
} else { | ||
mnemonic = tr.validatorConfigs[action.validator].mnemonic | ||
} | ||
|
||
saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, mnemonic, "/root/.hermes/mnemonic.txt") | ||
fmt.Println("Add to hermes", action.validator) | ||
//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. | ||
bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, "bash", "-c", | ||
saveMnemonicCommand, | ||
|
@@ -1716,7 +1729,7 @@ func (tr TestRun) submitChangeRewardDenomsProposal(action submitChangeRewardDeno | |
//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. | ||
// CHANGE REWARDS DENOM PROPOSAL | ||
bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, providerChain.binaryName, | ||
"tx", "gov", "submit-legacy-proposal", "change-reward-denoms", "/change-reward-denoms-proposal.json", | ||
"tx", "gov", "submit-proposal", "change-reward-denoms", "/change-reward-denoms-proposal.json", | ||
`--from`, `validator`+fmt.Sprint(action.from), | ||
`--chain-id`, string(providerChain.chainId), | ||
`--home`, tr.getValidatorHome(providerChain.chainId, action.from), | ||
|
@@ -1864,6 +1877,8 @@ func (tr TestRun) assignConsumerPubKey(action assignConsumerPubKeyAction, verbos | |
valCfg.useConsumerKey = true | ||
tr.validatorConfigs[action.validator] = valCfg | ||
} | ||
|
||
time.Sleep(1 * time.Second) | ||
} | ||
|
||
// slashThrottleDequeue polls slash queue sizes until nextQueueSize is achieved | ||
|
@@ -1925,3 +1940,26 @@ func (tr TestRun) GetPathNameForGorelayer(chainA, chainB chainID) string { | |
|
||
return pathName | ||
} | ||
|
||
// Run an instance of the Hermes relayer using the "evidence" command, | ||
// which detects evidences committed to the blocks of a consumer chain. | ||
// Each infraction detected is reported to the provider chain using | ||
// either a SubmitConsumerDoubleVoting or a SubmitConsumerMisbehaviour message. | ||
type startConsumerEvidenceDetectorAction struct { | ||
chain chainID | ||
} | ||
|
||
func (tr TestRun) startConsumerEvidenceDetector( | ||
action startConsumerEvidenceDetectorAction, | ||
verbose bool, | ||
) { | ||
chainConfig := tr.chainConfigs[action.chain] | ||
// run in detached mode so it will keep running in the background | ||
//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. | ||
bz, err := exec.Command("docker", "exec", "-d", tr.containerConfig.instanceName, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a comment that it will keep running on the background |
||
"hermes", "evidence", "--chain", string(chainConfig.chainId)).CombinedOutput() | ||
if err != nil { | ||
log.Fatal(err, "\n", string(bz)) | ||
} | ||
tr.waitBlocks("provi", 10, 2*time.Minute) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"log" | ||
"os/exec" | ||
"strconv" | ||
"time" | ||
) | ||
|
||
// forkConsumerChainAction forks the consumer chain by cloning of a validator node | ||
// Note that the chain fork is running in an different network | ||
type forkConsumerChainAction struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you comment somewhere what we can expect when calling this? |
||
consumerChain chainID | ||
providerChain chainID | ||
validator validatorID | ||
relayerConfig string | ||
} | ||
|
||
func (tr TestRun) forkConsumerChain(action forkConsumerChainAction, verbose bool) { | ||
valCfg := tr.validatorConfigs[action.validator] | ||
|
||
//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. | ||
configureNodeCmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", | ||
"/testnet-scripts/fork-consumer.sh", tr.chainConfigs[action.consumerChain].binaryName, | ||
string(action.validator), string(action.consumerChain), | ||
tr.chainConfigs[action.consumerChain].ipPrefix, | ||
tr.chainConfigs[action.providerChain].ipPrefix, | ||
valCfg.mnemonic, | ||
action.relayerConfig, | ||
) | ||
|
||
if verbose { | ||
log.Println("forkConsumerChain - reconfigure node cmd:", configureNodeCmd.String()) | ||
} | ||
|
||
cmdReader, err := configureNodeCmd.StdoutPipe() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
configureNodeCmd.Stderr = configureNodeCmd.Stdout | ||
|
||
if err := configureNodeCmd.Start(); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
scanner := bufio.NewScanner(cmdReader) | ||
|
||
for scanner.Scan() { | ||
out := scanner.Text() | ||
if verbose { | ||
log.Println("fork consumer validator : " + out) | ||
} | ||
if out == done { | ||
break | ||
} | ||
} | ||
if err := scanner.Err(); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
time.Sleep(5 * time.Second) | ||
} | ||
|
||
type updateLightClientAction struct { | ||
chain chainID | ||
hostChain chainID | ||
relayerConfig string | ||
clientID string | ||
} | ||
|
||
func (tr TestRun) updateLightClient( | ||
action updateLightClientAction, | ||
verbose bool, | ||
) { | ||
// retrieve a trusted height of the consumer light client | ||
trustedHeight := tr.getTrustedHeight(action.hostChain, action.clientID, 2) | ||
|
||
//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. | ||
cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", | ||
"--config", action.relayerConfig, | ||
"update", | ||
"client", | ||
"--client", action.clientID, | ||
"--host-chain", string(action.hostChain), | ||
"--trusted-height", strconv.Itoa(int(trustedHeight.RevisionHeight)), | ||
) | ||
if verbose { | ||
log.Println("updateLightClientAction cmd:", cmd.String()) | ||
} | ||
|
||
bz, err := cmd.CombinedOutput() | ||
if err != nil { | ||
log.Fatal(err, "\n", string(bz)) | ||
} | ||
|
||
tr.waitBlocks(action.hostChain, 5, 30*time.Second) | ||
} | ||
|
||
type assertChainIsHaltedAction struct { | ||
chain chainID | ||
} | ||
|
||
// assertChainIsHalted verifies that the chain isn't producing blocks | ||
// by checking that the block height is still the same after 20 seconds | ||
func (tr TestRun) assertChainIsHalted( | ||
action assertChainIsHaltedAction, | ||
verbose bool, | ||
) { | ||
blockHeight := tr.getBlockHeight(action.chain) | ||
time.Sleep(20 * time.Second) | ||
if blockHeight != tr.getBlockHeight(action.chain) { | ||
panic(fmt.Sprintf("chain %v isn't expected to produce blocks", action.chain)) | ||
} | ||
if verbose { | ||
log.Printf("assertChainIsHalted - chain %v was successfully halted\n", action.chain) | ||
} | ||
} |
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.
explain it's about light client attacks