test(systemtests): Add double signing test (#21115)
Co-authored-by: marbar3778 <marbar3778@yahoo.com>
This commit is contained in:
parent
43dd23137e
commit
aee9803a0a
@ -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"
|
||||
)
|
||||
|
||||
@ -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)
|
||||
@ -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
|
||||
@ -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 {
|
||||
|
||||
62
tests/systemtests/fraud_test.go
Normal file
62
tests/systemtests/fraud_test.go
Normal file
@ -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
tests/systemtests/node_utils.go
Normal file
46
tests/systemtests/node_utils.go
Normal file
@ -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
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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]),
|
||||
@ -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++
|
||||
@ -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, " "))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user