Skip to content

Commit

Permalink
add initial double signing simulation (#402)
Browse files Browse the repository at this point in the history
* add initial double signing simulation

* wip

* update double sign tests

* WIP: play with hermes builds

* add unofficial hermes image in build

* add provider double sign

* run all tests in parallel

* add new container for double sign tests

* rm unnecessary default value in doublesign script

* update after reviews

* bump double sign timeout

Co-authored-by: Jehan <[email protected]>
Co-authored-by: Daniel T <[email protected]>
Co-authored-by: Jehan Tremback <[email protected]>
  • Loading branch information
4 people authored Nov 2, 2022
1 parent 80b751c commit a2536c1
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 9 deletions.
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOFLAGS="-buildvcs=false"

WORKDIR /downloads

# Copy in the repo under test
ADD . /interchain-security

Expand All @@ -24,11 +22,13 @@ RUN go mod tidy
RUN make install

# Get Hermes build
FROM informalsystems/hermes:1.0.0 AS hermes-builder
# FROM informalsystems/hermes:1.0.0 AS hermes-builder
# remove line below and use the line above once hermes is updated
FROM majita/hermes-unofficial:latest AS hermes-builder

FROM --platform=linux/amd64 fedora:36
RUN dnf update -y
RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq
RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq openssl1.1
USER root

# Copy Hermes and IS binaries to final image
Expand Down
32 changes: 32 additions & 0 deletions tests/integration/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -962,3 +962,35 @@ func (tr TestRun) registerRepresentative(

wg.Wait()
}

// Creates an additional node on selected chain
// by copying an existing validator's home folder
//
// Steps needed to double sign:
// - copy existing validator's state and configs
// - use existing priv_validator_key.json
// - use new node_key.json (otherwise node gets rejected)
// - reset priv_validator_state.json to initial values
// - start the new node
// Double sign should be registered within couple blocks.
type doublesignSlashAction struct {
// start another node for this validator
validator validatorID
chain chainID
}

func (tr TestRun) invokeDoublesignSlash(
action doublesignSlashAction,
verbose bool,
) {
chainConfig := tr.chainConfigs[action.chain]
//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments.
bz, err := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash",
"/testnet-scripts/cause-doublesign.sh", chainConfig.binaryName, string(action.validator),
string(chainConfig.chainId), chainConfig.ipPrefix).CombinedOutput()

if err != nil {
log.Fatal(err, "\n", string(bz))
}
tr.waitBlocks("provi", 10, 2*time.Minute)
}
46 changes: 45 additions & 1 deletion tests/integration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,46 @@ func getDefaultValidators() map[validatorID]ValidatorConfig {
}
}

func DoubleSignTestRun() TestRun {
return TestRun{
name: "double-sign",
containerConfig: ContainerConfig{
containerName: "interchain-security-ds-container",
instanceName: "interchain-security-ds-instance",
ccvVersion: "1",
now: time.Now(),
},
validatorConfigs: getDefaultValidators(),
chainConfigs: map[chainID]ChainConfig{
chainID("provi"): {
chainId: chainID("provi"),
binaryName: "interchain-security-pd",
ipPrefix: "7.7.7",
votingWaitTime: 5,
genesisChanges: ".app_state.gov.voting_params.voting_period = \"5s\" | " +
// Custom slashing parameters for testing validator downtime functionality
// See https://docs.cosmos.network/main/modules/slashing/04_begin_block.html#uptime-tracking
".app_state.slashing.params.signed_blocks_window = \"2\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"2s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.slashing.params.slash_fraction_double_sign = \"0.100000000000000000\"",
},
chainID("consu"): {
chainId: chainID("consu"),
binaryName: "interchain-security-cd",
ipPrefix: "7.7.8",
votingWaitTime: 10,
genesisChanges: ".app_state.gov.voting_params.voting_period = \"10s\" | " +
".app_state.slashing.params.signed_blocks_window = \"2\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"2s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"",
},
},
}
}

func DefaultTestRun() TestRun {
return TestRun{
name: "default",
Expand Down Expand Up @@ -196,7 +236,11 @@ func (s *TestRun) ValidateStringLiterals() {
panic("ip suffix 253 is reserved for query node")
}

if ipSuffix < 1 || ipSuffix > 252 {
if ipSuffix == 252 {
panic("ip suffix 252 is reserved for double signing node")
}

if ipSuffix < 1 || 251 < ipSuffix {
panic("ip suffix out of range, need to change config")
}
}
Expand Down
16 changes: 13 additions & 3 deletions tests/integration/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,20 @@ func main() {
dmc.ValidateStringLiterals()
dmc.startDocker()

ds := DoubleSignTestRun()
ds.SetLocalSDKPath(*localSdkPath)
ds.ValidateStringLiterals()
ds.startDocker()

wg.Add(1)
go tr.ExecuteSteps(&wg, happyPathSteps)

wg.Add(1)
go dmc.ExecuteSteps(&wg, democracySteps)

wg.Add(1)
go ds.ExecuteSteps(&wg, doubleSignProviderSteps)

wg.Wait()
fmt.Printf("TOTAL TIME ELAPSED: %v\n", time.Since(start))
}
Expand Down Expand Up @@ -85,6 +93,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) {
tr.invokeDowntimeSlash(action, verbose)
case unjailValidatorAction:
tr.unjailValidator(action, verbose)
case doublesignSlashAction:
tr.invokeDoublesignSlash(action, verbose)
case registerRepresentativeAction:
tr.registerRepresentative(action, verbose)
default:
Expand All @@ -96,6 +106,7 @@ func (tr *TestRun) runStep(step Step, verbose bool) {

// Check state
if !reflect.DeepEqual(actualState, modelState) {
fmt.Println("action", reflect.TypeOf(step.action).Name())
pretty.Print("actual state", actualState)
pretty.Print("model state", modelState)
log.Fatal(`actual state (-) not equal to model state (+): ` + pretty.Compare(actualState, modelState))
Expand All @@ -110,9 +121,8 @@ func (tr *TestRun) ExecuteSteps(wg *sync.WaitGroup, steps []Step) {
start := time.Now()
for i, step := range steps {
// print something the show the test is alive
if i%10 == 0 {
fmt.Printf("running %s: step %d\n", tr.name, i+1)
}
fmt.Printf("running %s: step %d == %s \n",
tr.name, i+1, reflect.TypeOf(step.action).Name())
tr.runStep(step, *verbose)
}

Expand Down
5 changes: 5 additions & 0 deletions tests/integration/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ var democracySteps = concatSteps(
stepsDelegate("democ"),
stepsDemocracy("democ"),
)

var doubleSignProviderSteps = concatSteps(
stepsStartChains("consu", false),
stepsDoubleSign("consu", "provi", "carol"),
)
125 changes: 125 additions & 0 deletions tests/integration/steps_double_sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package main

// simulates double signing on provider and consumer chains
func stepsDoubleSign(consumerName, doubleSignChain, doubleSignValidator string) []Step {
return []Step{
// provider double sign
{
action: doublesignSlashAction{
chain: chainID("provi"),
validator: validatorID("carol"),
},
state: State{
// slash on provider
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
// not slashed on consumer yet
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 500,
validatorID("carol"): 500,
},
},
},
},
{
// relay power change to consumer
action: relayPacketsAction{
chain: chainID("provi"),
port: "provider",
channel: 0,
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
},
},
{
action: doublesignSlashAction{
chain: chainID("consu"),
validator: validatorID("bob"),
},
state: State{
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
},
},
{
action: relayPacketsAction{
chain: chainID("provi"),
port: "provider",
channel: 0,
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 0,
validatorID("carol"): 0,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 500,
validatorID("carol"): 0,
},
},
},
},
{
// relay power change to consumer
action: relayPacketsAction{
chain: chainID("provi"),
port: "provider",
channel: 0,
},
state: State{
chainID("provi"): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 0,
validatorID("carol"): 0,
},
},
chainID(consumerName): ChainState{
ValPowers: &map[validatorID]uint{
validatorID("alice"): 500,
validatorID("bob"): 0,
validatorID("carol"): 0,
},
},
},
},
}
}
36 changes: 36 additions & 0 deletions tests/integration/testnet-scripts/cause-doublesign.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
set -eux

# The gaiad binary
BIN=$1

VAL_ID=$2

# The chain ID
CHAIN_ID=$3

# chain's IP address prefix; 7.7.7, 7.7.8...
# see chain config for details
CHAIN_PREFIX=$4

# create directory for double signing node
mkdir /$CHAIN_ID/validatorsybil
cp -r /$CHAIN_ID/validator$VAL_ID/* /$CHAIN_ID/validatorsybil

# clear state in validatorsybil
echo '{"height": "0","round": 0,"step": 0,"signature":"","signbytes":""}' > /$CHAIN_ID/validatorsybil/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=="}}' > /$CHAIN_ID/validatorsybil/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 $CHAIN_ID-sybil $BIN $ARGS --home /$CHAIN_ID/validatorsybil start &> /$CHAIN_ID/validatorsybil/logs &

22 changes: 21 additions & 1 deletion tests/integration/testnet-scripts/start-chain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ done

# COLLECT GENTXS IF WE ARE STARTING A NEW CHAIN

if [ "$SKIP_GENTX" = "false" ] ; then
if [ "$SKIP_GENTX" = "false" ] ; then
# make the final genesis.json
$BIN collect-gentxs --home /$CHAIN_ID/validator$FIRST_VAL_ID

Expand Down Expand Up @@ -230,6 +230,26 @@ do
ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/validator$VAL_ID/logs &
done

## SETUP DOUBLE SIGNING NODE NETWORK NAMESPACE
# double signing node is started using cause-doublesign.sh
# here we just allocate a network namespace so it can be used later
SYBIL_NODE_ID="sybil"
SYBIL_IP_SUFFIX="252"
SYBIL_NET_NAMESPACE_NAME="$CHAIN_ID-$SYBIL_NODE_ID"
SYBIL_IP_ADDR="$CHAIN_IP_PREFIX.$SYBIL_IP_SUFFIX/24"

ip netns add $SYBIL_NET_NAMESPACE_NAME
ip link add $SYBIL_NET_NAMESPACE_NAME-in type veth peer name $SYBIL_NET_NAMESPACE_NAME-out
ip link set $SYBIL_NET_NAMESPACE_NAME-in netns $SYBIL_NET_NAMESPACE_NAME
ip netns exec $SYBIL_NET_NAMESPACE_NAME ip addr add $SYBIL_IP_ADDR dev $SYBIL_NET_NAMESPACE_NAME-in
ip link set $SYBIL_NET_NAMESPACE_NAME-out master virtual-bridge

## DOUBLE SIGNING ENABLE DEVICE
ip link set $SYBIL_NET_NAMESPACE_NAME-out up
ip netns exec $SYBIL_NET_NAMESPACE_NAME ip link set dev $SYBIL_NET_NAMESPACE_NAME-in up
ip netns exec $SYBIL_NET_NAMESPACE_NAME ip link set dev lo up
## DONE DOUBLE SIGNING NODE ENABLE DEVICE

## SETUP QUERY NODE NETWORK NAMESPACE
QUERY_NODE_ID="query"
QUERY_IP_SUFFIX="253"
Expand Down

0 comments on commit a2536c1

Please sign in to comment.