Skip to content
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

test: Add light client attack to e2e tests #1249

Merged
merged 6 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions tests/e2e/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,73 @@ func (tr TestRun) invokeDoublesignSlash(
}
}

// Cause light client attack evidence for a certain validator to appear on the given chain.
// The evidence will look like the validator equivocated to a light client.
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
type lightClientEquivocationAttackAction struct {
validator validatorID
chain chainID
}

func (tr TestRun) lightClientEquivocationAttack(
action lightClientEquivocationAttackAction,
verbose bool,
) {
tr.lightClientAttack(action.validator, action.chain, LightClientEquivocationAttack)
}

// Cause light client attack evidence for a certain validator to appear on the given chain.
// The evidence will look like the validator tried to perform an amnesia attack.
type lightClientAmnesiaAttackAction struct {
validator validatorID
chain chainID
}

func (tr TestRun) lightClientAmnesiaAttack(
action lightClientAmnesiaAttackAction,
verbose bool,
) {
tr.lightClientAttack(action.validator, action.chain, LightClientAmnesiaAttack)
}

type lightClientLunaticAttackAction struct {
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
validator validatorID
chain chainID
}

func (tr TestRun) lightClientLunaticAttack(
action lightClientLunaticAttackAction,
verbose bool,
) {
tr.lightClientAttack(action.validator, action.chain, LightClientLunaticAttack)
}

type LightClientAttackType string

const (
LightClientEquivocationAttack LightClientAttackType = "Equivocation"
LightClientAmnesiaAttack LightClientAttackType = "Amnesia"
LightClientLunaticAttack LightClientAttackType = "Lunatic"
)

func (tr TestRun) lightClientAttack(
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
validator validatorID,
chain chainID,
attackType LightClientAttackType,
) {
if !tr.useCometmock {
log.Fatal("light client attack is only supported with CometMock")
}
validatorAddress := tr.GetValidatorAddress(chain, validator)

method := "cause_light_client_attack"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we somehow get the information about cometmock methods into the interchain-security repo?

Maybe just keep a .md with all the available methods?

I'm trying to think how we could check what the invoked method actually does and need some advice on how to go about it

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 on this, an easily accessible doc outlining what's going on under the hood for each method would be nice

Copy link
Contributor Author

@p-offtermatt p-offtermatt Sep 6, 2023

Choose a reason for hiding this comment

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

Great suggestion, though I am a bit worried about these things going out of date if they are not linked to anything. wdyt about just linking that part of the CometMock repo? It documents each method. I've added a link next to the CometMock flag, but not sure where else this would be good to have.

Copy link
Contributor

Choose a reason for hiding this comment

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

Linking to the cometmock repo seems solid to me 👍

Might be helpful to have a methods.md file or similar where you can link each method used in this repo to the appropriate md section in the cometmock repo

params := fmt.Sprintf(`{"private_key_address":"%s", "misbehaviour_type": "%s"}`, validatorAddress, attackType)
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved

address := tr.getQueryNodeRPCAddress(chain)

tr.curlJsonRPCRequest(method, params, address)
tr.waitBlocks(chain, 1, 10*time.Second)
}

type assignConsumerPubKeyAction struct {
chain chainID
validator validatorID
Expand Down
12 changes: 12 additions & 0 deletions tests/e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ var (
description: `This is like the happy path, but skips steps
that involve starting or stopping nodes for the same chain outside of the chain setup or teardown.
This is suited for CometMock+Gorelayer testing`,
},
"light-client-attack": {
testRun: DefaultTestRun(), steps: lightClientAttackSteps,
description: `This is like the short happy path, but uses light client attacks instead of double sign slashing.
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
This is suited for CometMock+Gorelayer testing, but currently does not work with CometBFT,
since causing light client attacks is not implemented.`,
},
"happy-path": {testRun: DefaultTestRun(), steps: happyPathSteps, description: "happy path tests"},
"changeover": {testRun: ChangeoverTestRun(), steps: changeoverSteps, description: "changeover tests"},
Expand Down Expand Up @@ -238,6 +244,12 @@ func (tr *TestRun) runStep(step Step, verbose bool) {
tr.unjailValidator(action, verbose)
case doublesignSlashAction:
tr.invokeDoublesignSlash(action, verbose)
case lightClientAmnesiaAttackAction:
tr.lightClientAmnesiaAttack(action, verbose)
case lightClientEquivocationAttackAction:
tr.lightClientEquivocationAttack(action, verbose)
case lightClientLunaticAttackAction:
tr.lightClientLunaticAttack(action, verbose)
case registerRepresentativeAction:
tr.registerRepresentative(action, verbose)
case assignConsumerPubKeyAction:
Expand Down
13 changes: 13 additions & 0 deletions tests/e2e/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ var shortHappyPathSteps = concatSteps(
stepsStopChain("consu", 3), // stop chain
)

var lightClientAttackSteps = concatSteps(
stepsStartChains([]string{"consu"}, false),
stepsDelegate("consu"),
stepsUnbond("consu"),
stepsRedelegateShort("consu"),
stepsDowntime("consu"),
stepsRejectEquivocationProposal("consu", 2), // prop to tombstone bob is rejected
stepsLightClientAttackOnProviderAndConsumer("consu"), // carol double signs on provider, bob double signs on consumer
stepsStartRelayer(),
stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay
stepsStopChain("consu", 3), // stop chain
)

var slashThrottleSteps = concatSteps(
stepsStartChains([]string{"consu"}, false),
stepsDelegate("consu"),
Expand Down
130 changes: 130 additions & 0 deletions tests/e2e/steps_light_client_attack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

// Steps that make carol double sign on the provider, and bob double sign on a single consumer
func stepsLightClientAttackOnProviderAndConsumer(consumerName string) []Step {
return []Step{
{
// provider double sign
action: lightClientEquivocationAttackAction{
chain: chainID("provi"),
validator: validatorID("carol"),
},
state: State{
// slash on provider
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500,
validatorID("carol"): 0, // from 500 to 0
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500,
validatorID("carol"): 495, // not tombstoned on consumerName yet
},
},
},
},
{
// relay power change to consumerName
action: relayPacketsAction{
chainA: chainID("provi"),
chainB: chainID(consumerName),
port: "provider",
channel: 0, // consumerName channel
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500,
validatorID("carol"): 0, // tombstoning visible on consumerName
},
},
},
},
{
// consumer double sign
// provider will only log the double sign slash
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
// stepsSubmitEquivocationProposal will cause the double sign slash to be executed
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
action: lightClientEquivocationAttackAction{
chain: chainID("consu"),
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
validator: validatorID("bob"),
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
},
},
{
action: relayPacketsAction{
chainA: chainID("provi"),
chainB: chainID(consumerName),
port: "provider",
channel: 0,
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500, // not tombstoned
validatorID("carol"): 0,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500, // not tombstoned
validatorID("carol"): 0,
},
},
},
},
{
// consumer learns about the double sign
action: relayPacketsAction{
chainA: chainID("provi"),
chainB: chainID(consumerName),
port: "provider",
channel: 0,
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 509,
validatorID("bob"): 500, // not tombstoned
validatorID("carol"): 0,
},
},
},
},
}
}