Skip to content

Commit

Permalink
fix: prevent stuck SLCs waiting for valset
Browse files Browse the repository at this point in the history
  • Loading branch information
maharifu committed Aug 24, 2024
1 parent e817d16 commit c32b14a
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 0 deletions.
52 changes: 52 additions & 0 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,3 +931,55 @@ func (k Keeper) GetValidatorAddressByEthAddress(ctx context.Context, ethAddr sky
}
return
}

func (k Keeper) AddJustInTimeValsetUpdates(ctx context.Context) {
chainInfos, err := k.GetAllChainInfos(ctx)
if err != nil {
k.Logger(ctx).Error("failed to get chains infos", "err", err)
return
}

for _, chainInfo := range chainInfos {
queueName := consensustypes.Queue(
types.ConsensusTurnstoneMessage,
xchainType,
xchain.ReferenceID(chainInfo.GetChainReferenceID()),
)

messages, err := k.ConsensusKeeper.GetMessagesFromQueue(ctx, queueName, 0)
if err != nil {
k.Logger(ctx).Error("failed to get messages from queue", "err", err)
return
}

var hasUpdateValset, hasFeePayer bool

for _, msg := range messages {
consMsg, err := msg.ConsensusMsg(k.cdc)
if err != nil {
continue
}

mmsg, ok := consMsg.(*types.Message)
if !ok {
continue
}

switch mmsg.Action.(type) {
case *types.Message_UpdateValset:
hasUpdateValset = true
case types.FeePayer:
hasFeePayer = true
}
}

if hasFeePayer && !hasUpdateValset {
// Check if we need the valset update and add it to the queue
err = k.justInTimeValsetUpdate(ctx, chainInfo)
if err != nil {
k.Logger(ctx).Error("failed to issue valset update", "err", err)
return
}
}
}
}
254 changes: 254 additions & 0 deletions x/evm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"testing"
"time"

"cosmossdk.io/math"
sdkmath "cosmossdk.io/math"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/palomachain/paloma/util/slice"
consensustypes "github.com/palomachain/paloma/x/consensus/types"
"github.com/palomachain/paloma/x/evm/types"
evmtypes "github.com/palomachain/paloma/x/evm/types"
Expand Down Expand Up @@ -711,3 +713,255 @@ func TestKeeper_SendValsetMsgForChain(t *testing.T) {
assert.NoError(t, err)
})
}

func TestKeeper_AddJustInTimeValsetUpdates(t *testing.T) {
chainInfo := &types.ChainInfo{
ChainID: 100,
ChainReferenceID: "test-chain",
ReferenceBlockHeight: 1000,
ReferenceBlockHash: "0x00",
MinOnChainBalance: "100",
SmartContractUniqueID: []byte("abc"),
SmartContractAddr: "0x01",
RelayWeights: &types.RelayWeights{
Fee: "1.0",
Uptime: "1.0",
SuccessRate: "1.0",
ExecutionTime: "1.0",
FeatureSet: "1.0",
},
}

type valpower struct {
valAddr sdk.ValAddress
power int64
externalChain []*valsettypes.ExternalChainInfo
}

var totalPower int64 = 20
valpowers := []valpower{
{
valAddr: sdk.ValAddress("validator-1"),
power: 15,
externalChain: []*valsettypes.ExternalChainInfo{
{
ChainType: "evm",
ChainReferenceID: chainInfo.GetChainReferenceID(),
Address: "addr1",
Pubkey: []byte("1"),
},
},
},
{
valAddr: sdk.ValAddress("validator-2"),
power: 5,
externalChain: []*valsettypes.ExternalChainInfo{
{
ChainType: "evm",
ChainReferenceID: chainInfo.GetChainReferenceID(),
Address: "addr1",
Pubkey: []byte("1"),
},
},
},
}

currentSnapshot := &valsettypes.Snapshot{
Id: 5,
Validators: slice.Map(valpowers, func(p valpower) valsettypes.Validator {
return valsettypes.Validator{
ShareCount: sdkmath.NewInt(p.power),
Address: p.valAddr,
ExternalChainInfos: p.externalChain,
}
}),
TotalShares: sdkmath.NewInt(totalPower),
}

validatorMetrics := &metrixtypes.QueryValidatorsResponse{
ValMetrics: []metrixtypes.ValidatorMetrics{
{
ValAddress: sdk.ValAddress("validator-1").String(),
Uptime: sdkmath.LegacyOneDec(),
SuccessRate: sdkmath.LegacyOneDec(),
ExecutionTime: sdkmath.NewInt(0),
Fee: sdkmath.NewInt(0),
FeatureSet: sdkmath.LegacyOneDec(),
},
},
}

fee, _ := sdkmath.LegacyNewDecFromStr("1.1")
relayerFees := map[string]math.LegacyDec{
sdk.ValAddress("validator-1").String(): fee,
}

t.Run("Should add valset update with SLC", func(t *testing.T) {
k, ms, ctx := NewEvmKeeper(t)
err := k.updateChainInfo(ctx, chainInfo)
require.NoError(t, err)

qMsg, _ := codectypes.NewAnyWithValue(&evmtypes.Message{
TurnstoneID: "abc",
ChainReferenceID: "test-chain",
Assignee: "addr4",
Action: &evmtypes.Message_SubmitLogicCall{
SubmitLogicCall: &evmtypes.SubmitLogicCall{
SenderAddress: sdk.ValAddress("sender"),
},
},
})

msgs := []consensustypes.QueuedSignedMessageI{
&consensustypes.QueuedSignedMessage{
Id: 1,
Msg: qMsg,
},
}

ms.ConsensusKeeper.On("GetMessagesFromQueue", mock.Anything,
mock.Anything, mock.Anything).
Return(msgs, nil).
Once()

ms.ValsetKeeper.On("GetCurrentSnapshot", mock.Anything).
Return(currentSnapshot, nil)

ms.ValsetKeeper.On("GetLatestSnapshotOnChain", mock.Anything, mock.Anything).
Return(&valsettypes.Snapshot{Id: 1}, nil)

ms.MetrixKeeper.On("Validators", mock.Anything, mock.Anything).
Return(validatorMetrics, nil)

ms.TreasuryKeeper.On("GetRelayerFeesByChainReferenceID", mock.Anything,
chainInfo.ChainReferenceID).
Return(relayerFees, nil)

ms.MsgSender.On("SendValsetMsgForChain", mock.Anything, mock.Anything,
mock.Anything, mock.Anything, mock.Anything).
Return(nil)

k.AddJustInTimeValsetUpdates(ctx)
})

t.Run("Should do nothing if no SLC in queue", func(t *testing.T) {
k, ms, ctx := NewEvmKeeper(t)
err := k.updateChainInfo(ctx, chainInfo)
require.NoError(t, err)

qMsg, _ := codectypes.NewAnyWithValue(&evmtypes.Message{
TurnstoneID: "abc",
ChainReferenceID: "test-chain",
Assignee: "addr4",
Action: &evmtypes.Message_UploadSmartContract{
UploadSmartContract: &evmtypes.UploadSmartContract{},
},
})

msgs := []consensustypes.QueuedSignedMessageI{
&consensustypes.QueuedSignedMessage{
Id: 1,
Msg: qMsg,
},
}

ms.ConsensusKeeper.On("GetMessagesFromQueue", mock.Anything, mock.Anything, mock.Anything).
Return(msgs, nil).
Once()

k.AddJustInTimeValsetUpdates(ctx)
})

t.Run("Should do nothing if valset update is already scheduled", func(t *testing.T) {
k, ms, ctx := NewEvmKeeper(t)
err := k.updateChainInfo(ctx, chainInfo)
require.NoError(t, err)

qMsg, _ := codectypes.NewAnyWithValue(&evmtypes.Message{
TurnstoneID: "abc",
ChainReferenceID: "test-chain",
Assignee: "addr4",
Action: &evmtypes.Message_UpdateValset{
UpdateValset: &evmtypes.UpdateValset{
Valset: &evmtypes.Valset{
ValsetID: 2,
},
},
},
})

slcMsg, _ := codectypes.NewAnyWithValue(&evmtypes.Message{
TurnstoneID: "abc",
ChainReferenceID: "test-chain",
Assignee: "addr4",
Action: &evmtypes.Message_SubmitLogicCall{
SubmitLogicCall: &evmtypes.SubmitLogicCall{
SenderAddress: sdk.ValAddress("sender"),
},
},
})

msgs := []consensustypes.QueuedSignedMessageI{
&consensustypes.QueuedSignedMessage{
Id: 1,
Msg: qMsg,
},
&consensustypes.QueuedSignedMessage{
Id: 2,
Msg: slcMsg,
},
}

ms.ConsensusKeeper.On("GetMessagesFromQueue", mock.Anything, mock.Anything, mock.Anything).
Return(msgs, nil).
Once()

k.AddJustInTimeValsetUpdates(ctx)
})

t.Run("Should add valset update with UploadUserSmartContract", func(t *testing.T) {
k, ms, ctx := NewEvmKeeper(t)
err := k.updateChainInfo(ctx, chainInfo)
require.NoError(t, err)

qMsg, _ := codectypes.NewAnyWithValue(&evmtypes.Message{
TurnstoneID: "abc",
ChainReferenceID: "test-chain",
Assignee: "addr4",
Action: &evmtypes.Message_UploadUserSmartContract{
UploadUserSmartContract: &evmtypes.UploadUserSmartContract{},
},
})

msgs := []consensustypes.QueuedSignedMessageI{
&consensustypes.QueuedSignedMessage{
Id: 1,
Msg: qMsg,
},
}

ms.ConsensusKeeper.On("GetMessagesFromQueue", mock.Anything,
mock.Anything, mock.Anything).
Return(msgs, nil).
Once()

ms.ValsetKeeper.On("GetCurrentSnapshot", mock.Anything).
Return(currentSnapshot, nil)

ms.ValsetKeeper.On("GetLatestSnapshotOnChain", mock.Anything, mock.Anything).
Return(&valsettypes.Snapshot{Id: 1}, nil)

ms.MetrixKeeper.On("Validators", mock.Anything, mock.Anything).
Return(validatorMetrics, nil)

ms.TreasuryKeeper.On("GetRelayerFeesByChainReferenceID", mock.Anything,
chainInfo.ChainReferenceID).
Return(relayerFees, nil)

ms.MsgSender.On("SendValsetMsgForChain", mock.Anything, mock.Anything,
mock.Anything, mock.Anything, mock.Anything).
Return(nil)

k.AddJustInTimeValsetUpdates(ctx)
})
}
4 changes: 4 additions & 0 deletions x/evm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ func (am AppModule) EndBlock(ctx context.Context) error {
sdkCtx := sdk.UnwrapSDKContext(ctx)
am.keeper.TryDeployingLastCompassContractToAllChains(sdkCtx)

// Add valset updates to queue if we have SLCs in the queue and a valset
// mismatch. This should prevent SLCs from getting stuck.
am.keeper.AddJustInTimeValsetUpdates(sdkCtx)

if sdkCtx.BlockHeight()%300 == 0 {
if err := am.scheduleExternalBalances(sdkCtx); err != nil {
liblog.FromSDKLogger(sdkCtx.Logger()).WithError(err).
Expand Down

0 comments on commit c32b14a

Please sign in to comment.