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

Add warnings when provider unbonding is shorter than consumer unbonding #858

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.14.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
Expand Down
256 changes: 256 additions & 0 deletions testutil/mock_rpc_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package testutil

import (
"context"

"github.com/stretchr/testify/mock"
"github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/libs/log"
rpcClient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/types"
)

p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
// A mock client to be used in tests that want to mock out calls to the CometBFT CLI.
// The client implements the interface "github.com/tendermint/tendermint/rpc/client".Client
// Example Usage:
//
// mockClient := new(testutil.MockClient)
// clientCtx := client.Context{}.WithClient(mockClient)
// mockClient.On("ConsensusParams", mock.Anything, mock.Anything).Return(&resConsensParams, nil)
// {code using clientCtx}
type MockClient struct {
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
mock.Mock
}

// IsRunning implements client.Client
func (m MockClient) IsRunning() bool {
args := m.Called()
return args.Bool(0)
}

// OnReset implements client.Client
func (m MockClient) OnReset() error {
args := m.Called()
return args.Error(0)
}

// OnStart implements client.Client
func (m MockClient) OnStart() error {
args := m.Called()
return args.Error(0)
}

// OnStop implements client.Client
func (m MockClient) OnStop() {
m.Called()
}

// Quit implements client.Client
func (m MockClient) Quit() <-chan struct{} {
args := m.Called()
return args.Get(0).(<-chan struct{})
}

// Reset implements client.Client
func (m MockClient) Reset() error {
args := m.Called()
return args.Error(0)
}

// SetLogger implements client.Client
func (m MockClient) SetLogger(logger log.Logger) {
m.Called(logger)
}

// Start implements client.Client
func (m MockClient) Start() error {
args := m.Called()
return args.Error(0)
}

// Stop implements client.Client
func (m MockClient) Stop() error {
args := m.Called()
return args.Error(0)
}

// ABCIInfo implements client.Client
func (m MockClient) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultABCIInfo), args.Error(1)
}

// ABCIQuery implements client.Client
func (m MockClient) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) {
args := m.Called(ctx, path, data)
return args.Get(0).(*ctypes.ResultABCIQuery), args.Error(1)
}

// ABCIQueryWithOptions implements client.Client
func (m MockClient) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcClient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
args := m.Called(ctx, path, data, opts)
return args.Get(0).(*ctypes.ResultABCIQuery), args.Error(1)
}

// BroadcastTxAsync implements client.Client
func (m MockClient) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
args := m.Called(ctx, tx)
return args.Get(0).(*ctypes.ResultBroadcastTx), args.Error(1)
}

// BroadcastTxCommit implements client.Client
func (m MockClient) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
args := m.Called(ctx, tx)
return args.Get(0).(*ctypes.ResultBroadcastTxCommit), args.Error(1)
}

// BroadcastTxSync implements client.Client
func (m MockClient) BroadcastTxSync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
args := m.Called(ctx, tx)
return args.Get(0).(*ctypes.ResultBroadcastTx), args.Error(1)
}

// Subscribe implements client.Client
func (m MockClient) Subscribe(ctx context.Context, subscriber string, query string, outCapacity ...int) (<-chan ctypes.ResultEvent, error) {
args := m.Called(ctx, subscriber, query, outCapacity)
return args.Get(0).(<-chan ctypes.ResultEvent), args.Error(1)
}

// Unsubscribe implements client.Client
func (m *MockClient) Unsubscribe(ctx context.Context, subscriber string, query string) error {
args := m.Called(ctx, subscriber, query)
return args.Error(0)
}

// UnsubscribeAll implements client.Client
func (m *MockClient) UnsubscribeAll(ctx context.Context, subscriber string) error {
args := m.Called(ctx, subscriber)
return args.Error(0)
}

// BlockchainInfo implements client.Client
func (m *MockClient) BlockchainInfo(ctx context.Context, minHeight int64, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
args := m.Called(ctx, minHeight, maxHeight)
return args.Get(0).(*ctypes.ResultBlockchainInfo), args.Error(1)
}

// Genesis implements client.Client
func (m *MockClient) Genesis(ctx context.Context) (*ctypes.ResultGenesis, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultGenesis), args.Error(1)
}

// GenesisChunked implements client.Client
func (m *MockClient) GenesisChunked(ctx context.Context, chunkSize uint) (*ctypes.ResultGenesisChunk, error) {
args := m.Called(ctx, chunkSize)
return args.Get(0).(*ctypes.ResultGenesisChunk), args.Error(1)
}

// ConsensusParams implements client.Client
func (m *MockClient) ConsensusParams(ctx context.Context, height *int64) (*ctypes.ResultConsensusParams, error) {
args := m.Called(ctx, height)
return args.Get(0).(*ctypes.ResultConsensusParams), args.Error(1)
}

// ConsensusState implements client.Client
func (m MockClient) ConsensusState(ctx context.Context) (*ctypes.ResultConsensusState, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultConsensusState), args.Error(1)
}

// DumpConsensusState implements client.Client
func (m MockClient) DumpConsensusState(ctx context.Context) (*ctypes.ResultDumpConsensusState, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultDumpConsensusState), args.Error(1)
}

// Health implements client.Client
func (m MockClient) Health(ctx context.Context) (*ctypes.ResultHealth, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultHealth), args.Error(1)
}

// NetInfo implements client.Client
func (m MockClient) NetInfo(ctx context.Context) (*ctypes.ResultNetInfo, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultNetInfo), args.Error(1)
}

// Block implements client.Client
func (m MockClient) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) {
args := m.Called(ctx, height)
return args.Get(0).(*ctypes.ResultBlock), args.Error(1)
}

// BlockByHash implements client.Client
func (m MockClient) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) {
args := m.Called(ctx, hash)
return args.Get(0).(*ctypes.ResultBlock), args.Error(1)
}

// BlockResults implements client.Client
func (m MockClient) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) {
args := m.Called(ctx, height)
return args.Get(0).(*ctypes.ResultBlockResults), args.Error(1)
}

// BlockSearch implements client.Client
func (m MockClient) BlockSearch(ctx context.Context, query string, page *int, perPage *int, orderBy string) (*ctypes.ResultBlockSearch, error) {
args := m.Called(ctx, query, page, perPage, orderBy)
return args.Get(0).(*ctypes.ResultBlockSearch), args.Error(1)
}

// Commit implements client.Client
func (m MockClient) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) {
args := m.Called(ctx, height)
return args.Get(0).(*ctypes.ResultCommit), args.Error(1)
}

// Tx implements client.Client
func (m MockClient) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
args := m.Called(ctx, hash, prove)
return args.Get(0).(*ctypes.ResultTx), args.Error(1)
}

// TxSearch implements client.Client
func (m MockClient) TxSearch(ctx context.Context, query string, prove bool, page *int, perPage *int, orderBy string) (*ctypes.ResultTxSearch, error) {
args := m.Called(ctx, query, prove, page, perPage, orderBy)
return args.Get(0).(*ctypes.ResultTxSearch), args.Error(1)
}

// Validators implements client.Client
func (m MockClient) Validators(ctx context.Context, height *int64, page *int, perPage *int) (*ctypes.ResultValidators, error) {
args := m.Called(ctx, height, page, perPage)
return args.Get(0).(*ctypes.ResultValidators), args.Error(1)
}

// Status implements client.Client
func (m *MockClient) Status(ctx context.Context) (*ctypes.ResultStatus, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultStatus), args.Error(1)
}

// BroadcastEvidence implements client.Client
func (m *MockClient) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
args := m.Called(ctx, ev)
return args.Get(0).(*ctypes.ResultBroadcastEvidence), args.Error(1)
}

// CheckTx implements client.Client
func (m *MockClient) CheckTx(ctx context.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) {
args := m.Called(ctx, tx)
return args.Get(0).(*ctypes.ResultCheckTx), args.Error(1)
}

// NumUnconfirmedTxs implements client.Client
func (m *MockClient) NumUnconfirmedTxs(ctx context.Context) (*ctypes.ResultUnconfirmedTxs, error) {
args := m.Called(ctx)
return args.Get(0).(*ctypes.ResultUnconfirmedTxs), args.Error(1)
}

// UnconfirmedTxs implements client.Client
func (m *MockClient) UnconfirmedTxs(ctx context.Context, limit *int) (*ctypes.ResultUnconfirmedTxs, error) {
args := m.Called(ctx, limit)
return args.Get(0).(*ctypes.ResultUnconfirmedTxs), args.Error(1)
}
37 changes: 37 additions & 0 deletions x/ccv/provider/client/proposal_handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package client

import (
"context"
"encoding/json"
"fmt"
"net/http"
Expand Down Expand Up @@ -74,6 +75,16 @@ Where proposal.json contains:
return err
}

// do not fail for errors regarding the unbonding period, but just log a warning
err = CheckPropUnbondingPeriod(clientCtx, proposal.UnbondingPeriod)
if err != nil {
fmt.Fprintf(
MSalopek marked this conversation as resolved.
Show resolved Hide resolved
os.Stderr,
"Warning: Could not assure that Proposal Unbonding Period is shorter than Provider Unbonding Period. Error message: %s",
err.Error(),
)
}

content := types.NewConsumerAdditionProposal(
proposal.Title, proposal.Description, proposal.ChainId, proposal.InitialHeight,
proposal.GenesisHash, proposal.BinaryHash, proposal.SpawnTime,
Expand Down Expand Up @@ -441,3 +452,29 @@ func postEquivocationProposalHandlerFn(clientCtx client.Context) http.HandlerFun
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
}
}

func CheckPropUnbondingPeriod(clientCtx client.Context, propUnbondingPeriod time.Duration) error {
client, err := clientCtx.GetNode()
if err != nil {
return err
}

consensusParams, err := client.ConsensusParams(context.Background(), nil)
if err != nil {
return err
}

providerUnbondingTime := consensusParams.ConsensusParams.Evidence.MaxAgeDuration
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved

if providerUnbondingTime <= propUnbondingPeriod {
return fmt.Errorf(
"consumer unbonding period should be smaller than provider unbonding period, but is longer: \n"+
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
"consumer unbonding: %s \n provider unbonding: %s \n"+
"See the Consumer onboarding documentation for more information:\n"+
"https://cosmos.github.io/interchain-security/consumer-development/onboarding#3-submit-a-governance-proposal",
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
propUnbondingPeriod,
providerUnbondingTime)
}

return nil
}
68 changes: 68 additions & 0 deletions x/ccv/provider/client/proposal_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package client_test

import (
"testing"
"time"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/interchain-security/testutil"
providerClient "github.com/cosmos/interchain-security/x/ccv/provider/client"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
cmtproto "github.com/tendermint/tendermint/proto/tendermint/types"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
)

func ConsumerAdditionProposalCLIDriver(providerUnbondingTime, consumerUnbondingTime time.Duration) error {
mockClient := new(testutil.MockClient)

resConsensParams := coretypes.ResultConsensusParams{
BlockHeight: 5,
ConsensusParams: cmtproto.ConsensusParams{
Evidence: cmtproto.EvidenceParams{
MaxAgeDuration: providerUnbondingTime,
},
},
}
mockClient.On("ConsensusParams", mock.Anything, mock.Anything).Return(&resConsensParams, nil)
clientCtx := client.Context{}.WithClient(mockClient)

return providerClient.CheckPropUnbondingPeriod(clientCtx, consumerUnbondingTime)
}

func TestConsumerAdditionProposalSuite(t *testing.T) {
testCases := []struct {
name string
providerUnbonding time.Duration
consumerUnbonding time.Duration
errorExpected bool
}{
{
name: "provider unbonding longer",
providerUnbonding: time.Hour * 5,
consumerUnbonding: time.Hour * 4,
errorExpected: false,
},
{
name: "consumer unbonding longer",
providerUnbonding: time.Hour * 5,
consumerUnbonding: time.Hour * 6,
errorExpected: true,
},
{
name: "unbondings equal",
providerUnbonding: time.Hour * 5,
consumerUnbonding: time.Hour * 5,
errorExpected: true,
},
}

for _, tc := range testCases {
res := ConsumerAdditionProposalCLIDriver(tc.providerUnbonding, tc.consumerUnbonding)
if tc.errorExpected {
require.Error(t, res)
} else {
require.NoError(t, res)
}
}
}