Skip to content

Commit

Permalink
test(systemtests): Add double signing test (#21115)
Browse files Browse the repository at this point in the history
Co-authored-by: marbar3778 <[email protected]>
  • Loading branch information
alpe and tac0turtle authored Jul 31, 2024
1 parent 43dd231 commit aee9803
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 39 deletions.
43 changes: 10 additions & 33 deletions tests/systemtests/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import (
"github.com/tidwall/gjson"
"golang.org/x/exp/slices"

"github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/std"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -287,7 +282,7 @@ func (c CLIWrapper) AddKeyFromSeed(name, mnemoic string) string {
return addr
}

// GetKeyAddr returns address
// GetKeyAddr returns Acc address
func (c CLIWrapper) GetKeyAddr(name string) string {
cmd := c.withKeyringFlags("keys", "show", name, "-a")
out, _ := c.run(cmd)
Expand All @@ -296,6 +291,15 @@ func (c CLIWrapper) GetKeyAddr(name string) string {
return addr
}

// GetKeyAddrPrefix returns key address with Beach32 prefix encoding for a key (acc|val|cons)
func (c CLIWrapper) GetKeyAddrPrefix(name, prefix string) string {
cmd := c.withKeyringFlags("keys", "show", name, "-a", "--bech="+prefix)
out, _ := c.run(cmd)
addr := strings.Trim(out, "\n")
require.NotEmpty(c.t, addr, "got %q", out)
return addr
}

const defaultSrcAddr = "node0"

// FundAddress sends the token amount to the destination address
Expand Down Expand Up @@ -330,33 +334,6 @@ func (c CLIWrapper) QueryTotalSupply(denom string) int64 {
return gjson.Get(raw, fmt.Sprintf("supply.#(denom==%q).amount", denom)).Int()
}

func (c CLIWrapper) GetCometBFTValidatorSet() cmtservice.GetLatestValidatorSetResponse {
args := []string{"q", "comet-validator-set"}
got := c.CustomQuery(args...)

// still using amino here as the SDK
amino := codec.NewLegacyAmino()
std.RegisterLegacyAminoCodec(amino)
std.RegisterInterfaces(codectypes.NewInterfaceRegistry())

var res cmtservice.GetLatestValidatorSetResponse
require.NoError(c.t, amino.UnmarshalJSON([]byte(got), &res), got)
return res
}

// IsInCometBftValset returns true when the given pub key is in the current active tendermint validator set
func (c CLIWrapper) IsInCometBftValset(valPubKey cryptotypes.PubKey) (cmtservice.GetLatestValidatorSetResponse, bool) {
valResult := c.GetCometBFTValidatorSet()
var found bool
for _, v := range valResult.Validators {
if v.PubKey.Equal(valPubKey) {
found = true
break
}
}
return valResult, found
}

// SubmitGovProposal submit a gov v1 proposal
func (c CLIWrapper) SubmitGovProposal(proposalJson string, args ...string) string {
if len(args) == 0 {
Expand Down
62 changes: 62 additions & 0 deletions tests/systemtests/fraud_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//go:build system_test

package systemtests

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)

func TestValidatorDoubleSign(t *testing.T) {
// Scenario:
// given: a running chain
// when: a second instance with the same val key signs a block
// then: the validator is removed from the active set and jailed forever
sut.ResetChain(t)
cli := NewCLIWrapper(t, sut, verbose)
sut.StartChain(t)

// Check the validator is in the active set
rsp := cli.CustomQuery("q", "staking", "validators")
t.Log(rsp)
nodePowerBefore := QueryCometValidatorPowerForNode(t, sut, 0)
require.NotEmpty(t, nodePowerBefore)

var validatorPubKey cryptotypes.PubKey
newNode := sut.AddFullnode(t, "0.001stake", func(nodeNumber int, nodePath string) {
valKeyFile := filepath.Join(WorkDir, nodePath, "config", "priv_validator_key.json")
_ = os.Remove(valKeyFile)
_, err := copyFile(filepath.Join(WorkDir, sut.nodePath(0), "config", "priv_validator_key.json"), valKeyFile)
require.NoError(t, err)
validatorPubKey = LoadValidatorPubKeyForNode(t, sut, nodeNumber)
})
sut.AwaitNodeUp(t, fmt.Sprintf("http://%s:%d", newNode.IP, newNode.RPCPort))

// let's wait some blocks to have evidence and update persisted
rpc := sut.RPCClient(t)
pkBz := validatorPubKey.Bytes()
for i := 0; i < 20; i++ {
sut.AwaitNextBlock(t)
if QueryCometValidatorPower(rpc, pkBz) == 0 {
break
}
}
sut.AwaitNextBlock(t)

// then comet status updated
nodePowerAfter := QueryCometValidatorPowerForNode(t, sut, 0)
require.Empty(t, nodePowerAfter)

// and sdk status updated
byzantineOperatorAddr := cli.GetKeyAddrPrefix("node0", "val")
rsp = cli.CustomQuery("q", "staking", "validator", byzantineOperatorAddr)
assert.True(t, gjson.Get(rsp, "validator.jailed").Bool(), rsp)
}
46 changes: 46 additions & 0 deletions tests/systemtests/node_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package systemtests

import (
"bytes"
"path/filepath"
"testing"

"github.com/cometbft/cometbft/privval"
"github.com/stretchr/testify/require"

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)

// LoadValidatorPubKeyForNode load validator nodes consensus pub key for given node number
func LoadValidatorPubKeyForNode(t *testing.T, sut *SystemUnderTest, nodeNumber int) cryptotypes.PubKey {
t.Helper()
return LoadValidatorPubKey(t, filepath.Join(WorkDir, sut.nodePath(nodeNumber), "config", "priv_validator_key.json"))
}

// LoadValidatorPubKey load validator nodes consensus pub key from disk
func LoadValidatorPubKey(t *testing.T, keyFile string) cryptotypes.PubKey {
t.Helper()
filePV := privval.LoadFilePVEmptyState(keyFile, "")
pubKey, err := filePV.GetPubKey()
require.NoError(t, err)
valPubKey, err := cryptocodec.FromCmtPubKeyInterface(pubKey)
require.NoError(t, err)
return valPubKey
}

// QueryCometValidatorPowerForNode returns the validator's power from tendermint RPC endpoint. 0 when not found
func QueryCometValidatorPowerForNode(t *testing.T, sut *SystemUnderTest, nodeNumber int) int64 {
t.Helper()
pubKebBz := LoadValidatorPubKeyForNode(t, sut, nodeNumber).Bytes()
return QueryCometValidatorPower(sut.RPCClient(t), pubKebBz)
}

func QueryCometValidatorPower(c RPCClient, pubKebBz []byte) int64 {
for _, v := range c.Validators() {
if bytes.Equal(v.PubKey.Bytes(), pubKebBz) {
return v.VotingPower
}
}
return 0
}
17 changes: 11 additions & 6 deletions tests/systemtests/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (s *SystemUnderTest) StartChain(t *testing.T, xargs ...string) {
t.Helper()
s.Log("Start chain\n")
s.ChainStarted = true
s.startNodesAsync(t, append([]string{"start", "--trace", "--log_level=info"}, xargs...)...)
s.startNodesAsync(t, append([]string{"start", "--trace", "--log_level=info", "--log_no_color"}, xargs...)...)

s.AwaitNodeUp(t, s.rpcAddr)

Expand Down Expand Up @@ -635,9 +635,12 @@ func AllNodes(t *testing.T, s *SystemUnderTest) []Node {
t.Helper()
result := make([]Node, s.nodesCount)
outs := s.ForEachNodeExecAndWait(t, []string{"comet", "show-node-id"})
ip, err := server.ExternalIP()
require.NoError(t, err)

ip := "127.0.0.1"
if false { // is there still a use case for external ip?
var err error
ip, err = server.ExternalIP()
require.NoError(t, err)
}
for i, out := range outs {
result[i] = Node{
ID: strings.TrimSpace(out[0]),
Expand All @@ -655,7 +658,7 @@ func (s *SystemUnderTest) resetBuffers() {
}

// AddFullnode starts a new fullnode that connects to the existing chain but is not a validator.
func (s *SystemUnderTest) AddFullnode(t *testing.T, beforeStart ...func(nodeNumber int, nodePath string)) Node {
func (s *SystemUnderTest) AddFullnode(t *testing.T, minGasPrices string, beforeStart ...func(nodeNumber int, nodePath string)) Node {
t.Helper()
s.MarkDirty()
s.nodesCount++
Expand Down Expand Up @@ -698,9 +701,11 @@ func (s *SystemUnderTest) AddFullnode(t *testing.T, beforeStart ...func(nodeNumb
fmt.Sprintf("--p2p.laddr=tcp://localhost:%d", node.P2PPort),
fmt.Sprintf("--rpc.laddr=tcp://localhost:%d", node.RPCPort),
fmt.Sprintf("--grpc.address=localhost:%d", 9090+nodeNumber),
fmt.Sprintf("--grpc-web.address=localhost:%d", 8090+nodeNumber),
fmt.Sprintf("--minimum-gas-prices=%s", minGasPrices),
"--p2p.pex=false",
"--moniker=" + moniker,
"--log_level=info",
"--log_no_color",
"--home", nodePath,
}
s.Logf("Execute `%s %s`\n", s.execBinary, strings.Join(args, " "))
Expand Down

0 comments on commit aee9803

Please sign in to comment.