diff --git a/auth/querier_test.go b/auth/querier_test.go index fbdcd26c..6ba9c765 100644 --- a/auth/querier_test.go +++ b/auth/querier_test.go @@ -157,11 +157,19 @@ func (suite *QuerierTestSuite) TestQueryParams() { require.Equal(t, uint64(8), params.TxSizeCostPerByte) { + // When params are not set, querier should return zero values instead of panicking happ := app.Setup(true) ctx := happ.BaseApp.NewContext(true, abci.Header{}) querier := auth.NewQuerier(happ.AccountKeeper) - require.Panics(t, func() { - querier(ctx, path, req) - }) + res, err = querier(ctx, path, req) + require.NoError(t, err) + require.NotNil(t, res) + + var zeroParams types.Params + err4 := json.Unmarshal(res, &zeroParams) + require.NoError(t, err4) + // Params should be zero values when not initialized + require.Equal(t, uint64(0), zeroParams.MaxMemoCharacters) + require.Equal(t, uint64(0), zeroParams.TxSigLimit) } } diff --git a/bridge/setu/listener/heimdall.go b/bridge/setu/listener/heimdall.go index d9ceaf48..64a19e43 100644 --- a/bridge/setu/listener/heimdall.go +++ b/bridge/setu/listener/heimdall.go @@ -16,7 +16,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" checkpointTypes "github.com/maticnetwork/heimdall/checkpoint/types" clerkTypes "github.com/maticnetwork/heimdall/clerk/types" - featureManagerTypes "github.com/maticnetwork/heimdall/featuremanager/types" slashingTypes "github.com/maticnetwork/heimdall/slashing/types" stakingTypes "github.com/maticnetwork/heimdall/staking/types" htype "github.com/maticnetwork/heimdall/types" @@ -48,8 +47,17 @@ func (hl *HeimdallListener) Start() error { // Heimdall pollIntervall = (minimal pollInterval of rootchain and matichain) pollInterval := helper.GetConfig().EthSyncerPollInterval - if helper.GetConfig().CheckpointerPollInterval < helper.GetConfig().EthSyncerPollInterval { - pollInterval = helper.GetConfig().CheckpointerPollInterval + + checkpointPollInterval := helper.GetConfig().CheckpointerPollInterval + + // fetch initial checkpoint params (will retry up to 10 times or exit service) + checkpointParams := util.GetCheckpointParamsWithRetry(hl.cliCtx) + if checkpointParams.CheckpointPollInterval > 0 { + checkpointPollInterval = checkpointParams.CheckpointPollInterval + } + + if checkpointPollInterval < helper.GetConfig().EthSyncerPollInterval { + pollInterval = checkpointPollInterval } hl.Logger.Info("Start polling for events", "pollInterval", pollInterval) @@ -293,12 +301,12 @@ func (hl *HeimdallListener) StartPollingEventRecord(ctx context.Context, pollInt } func (hl *HeimdallListener) loadEventRecords(ctx context.Context, pollInterval time.Duration) { - targetFeature, err := util.GetTargetFeatureConfig(hl.cliCtx, featureManagerTypes.DynamicCheckpoint) - if err != nil || !targetFeature.IsOpen { - hl.Logger.Info("Feature not supported... goroutine exists") - - return - } + //targetFeature, err := util.GetTargetFeatureConfig(hl.cliCtx, featureManagerTypes.DynamicCheckpoint) + //if err != nil || !targetFeature.IsOpen { + // hl.Logger.Info("Feature not supported... goroutine exists") + // + // return + //} if atomic.LoadUint32(&hl.stateSyncedInitializationRun) == 1 { hl.Logger.Info("Last ProcessEventRecords not finished... goroutine exists") diff --git a/bridge/setu/listener/maticchain.go b/bridge/setu/listener/maticchain.go index 48c1267c..3fbed7a5 100644 --- a/bridge/setu/listener/maticchain.go +++ b/bridge/setu/listener/maticchain.go @@ -5,6 +5,7 @@ import ( "time" "github.com/RichardKnop/machinery/v1/tasks" + "github.com/maticnetwork/heimdall/bridge/setu/util" "github.com/maticnetwork/heimdall/helper" ) @@ -33,9 +34,15 @@ func (ml *MaticChainListener) Start() error { // start header process go ml.StartHeaderProcess(headerCtx) - ml.Logger.Info("Start polling for header blocks", "pollInterval", helper.GetConfig().CheckpointerPollInterval) + pollInterval := helper.GetConfig().CheckpointerPollInterval + params := util.GetCheckpointParamsWithRetry(ml.cliCtx) + if params.CheckpointPollInterval > 0 { + pollInterval = params.CheckpointPollInterval + } + + ml.Logger.Info("Start polling for header blocks", "pollInterval", pollInterval) - go ml.StartPolling(ctx, helper.GetConfig().CheckpointerPollInterval, true, nil) + go ml.StartPolling(ctx, pollInterval, true, nil) // subscribed to new head ml.Logger.Info("Subscribed to new head") diff --git a/bridge/setu/processor/checkpoint.go b/bridge/setu/processor/checkpoint.go index abab9986..4d011c7b 100644 --- a/bridge/setu/processor/checkpoint.go +++ b/bridge/setu/processor/checkpoint.go @@ -99,15 +99,23 @@ func (cp *CheckpointProcessor) startPolling(ctx context.Context) { now := time.Now() baseTime := time.Unix(0, 0) // no-ack ticker interval keep same with checkpoint interval - noAckInterval := helper.GetConfig().CheckpointerPollInterval + checkpointPollInterval := helper.GetConfig().CheckpointerPollInterval + + // fetch initial checkpoint params (will retry up to 10 times or exit service) + checkpointParams := util.GetCheckpointParamsWithRetry(cp.cliCtx) + if checkpointParams.CheckpointPollInterval > 0 { + checkpointPollInterval = checkpointParams.CheckpointPollInterval + } + // adjust no-ack ticker to tick at the middle of checkpoint interval - firstIntervalForNoAck := noAckInterval - (now.UTC().Sub(baseTime) % noAckInterval) - noAckInterval/2 // nolint: gomnd + firstIntervalForNoAck := checkpointPollInterval - (now.UTC().Sub(baseTime) % checkpointPollInterval) - checkpointPollInterval/2 // nolint: gomnd if firstIntervalForNoAck <= 0 { - firstIntervalForNoAck += noAckInterval + firstIntervalForNoAck += checkpointPollInterval } tickerForNoAck := time.NewTicker(firstIntervalForNoAck) - syncInterval := helper.GetConfig().CheckpointerPollInterval / 2 + syncInterval := checkpointPollInterval / 2 + noAckInterval := checkpointPollInterval tickerForSync := time.NewTicker(syncInterval) // stop ticker when everything done defer tickerForNoAck.Stop() @@ -568,10 +576,10 @@ func (cp *CheckpointProcessor) createAndSendCheckpointToHeimdall(checkpointConte latestCheckpoint, err := util.GetlastestCheckpoint(cp.cliCtx, rootChain) // event checkpoint is older than or equal to latest checkpoint if err == nil && latestCheckpoint != nil && latestCheckpoint.EndBlock+1 < start { - cp.Logger.Debug("Need to resubmit Checkpoint ack first", "start", start, "last_end", latestCheckpoint.EndBlock) + cp.Logger.Info("Need to resubmit Checkpoint ack first", "start", start, "last_end", latestCheckpoint.EndBlock) err := cp.resubmitCheckpointAck(checkpointContext, rootChain) if err != nil { - cp.Logger.Info("Error while resubmit checkpoint ack", "root", rootChain, "err", err) + cp.Logger.Error("Error while resubmit checkpoint ack", "root", rootChain, "err", err) return err } return nil @@ -840,16 +848,36 @@ func (cp *CheckpointProcessor) checkIfNoAckIsRequired(checkpointContext *Checkpo currentTime := time.Now().UTC() timeDiff := currentTime.Sub(checkpointCreationTime) - if timeDiff.Seconds() >= helper.GetConfig().CheckpointerPollInterval.Seconds() && index == 0 { - index = math.Floor(timeDiff.Seconds() / helper.GetConfig().CheckpointerPollInterval.Seconds()) + + // checkpoint params + checkpointParams := checkpointContext.CheckpointParams + + var checkpointPollInterval time.Duration + if checkpointParams.CheckpointPollInterval > 0 { + checkpointPollInterval = checkpointParams.CheckpointPollInterval + } else { + checkpointPollInterval = helper.GetConfig().CheckpointerPollInterval } - if index == 0 { + var checkpointTimeout time.Duration + isOpen, tronMaxLength, err := cp.getTronDynamicCheckpointProposalWithErr() + if err != nil { + cp.Logger.Error("failed to check if no ack is required. Error while fetching dynamic checkpoint feature", "error", err) return false, uint64(index) } + if isOpen { + checkpointTimeout, _ = helper.CalcCheckpointTimeout(tronMaxLength, checkpointPollInterval) + } else { + checkpointTimeout = checkpointPollInterval + } - // checkpoint params - checkpointParams := checkpointContext.CheckpointParams + if timeDiff.Seconds() >= checkpointTimeout.Seconds() && index == 0 { + index = math.Floor(timeDiff.Seconds() / checkpointTimeout.Seconds()) + } + + if index == 0 { + return false, uint64(index) + } // check if difference between no-ack time and current time lastNoAck := cp.getLastNoAckTime() @@ -991,9 +1019,7 @@ func (cp *CheckpointProcessor) Stop() { cp.cancelNoACKPolling() } -// // utils -// func (cp *CheckpointProcessor) getCheckpointContext(rootChain string) (*CheckpointContext, error) { // fetch chain params for different root chains chainmanagerParams, err := util.GetNewChainParams(cp.cliCtx, rootChain) @@ -1090,3 +1116,13 @@ func (cp *CheckpointProcessor) getDynamicCheckpointProposal(rootType string) (bo return fea.IsOpen, fea.IntConf[strings.ToLower(rootType)] != 0, fea.IntConf["maxLength"] } + +func (cp *CheckpointProcessor) getTronDynamicCheckpointProposalWithErr() (bool, int, error) { + fea, err := util.GetTronDynamicCheckpointFeature(cp.cliCtx) + if err != nil { + cp.Logger.Error("Error while fetching dynamic checkpoint feature", "error", err) + + return false, 0, err + } + return fea.IsOpen, fea.IntConf["maxLength"], err +} diff --git a/bridge/setu/processor/checkpointsync.go b/bridge/setu/processor/checkpointsync.go index 5309f0b3..0387d994 100644 --- a/bridge/setu/processor/checkpointsync.go +++ b/bridge/setu/processor/checkpointsync.go @@ -147,7 +147,7 @@ func (cp *CheckpointProcessor) sendCheckpointSyncToStakeChain(eventBytes string, } else { txHash := common.FromHex(txHash) if err := cp.createAndSendCheckpointSyncToTron(checkpointContext, number, startBlock, endBlock, rootChain, blockHeight, txHash); err != nil { - cp.Logger.Error("Error sending checkpoint to rootchain", "error", err) + cp.Logger.Error("Error sending checkpoint sync to rootchain", "error", err) return err } } diff --git a/bridge/setu/processor/troncheckpoint.go b/bridge/setu/processor/troncheckpoint.go index 4eeb8e84..854482a0 100644 --- a/bridge/setu/processor/troncheckpoint.go +++ b/bridge/setu/processor/troncheckpoint.go @@ -97,6 +97,12 @@ func (cp *CheckpointProcessor) nextExpectedTronCheckpoint(checkpointContext *Che "end", end, ) } + + // Check cross-chain transactions for Tron dynamic checkpoint feature + if !cp.checkCrossChainForTron(start, end, checkpointParams.MaxCheckpointLength) { + end = start + } + // Handle when block producers go down if end == 0 || end == start || (0 < diff && diff < checkpointParams.AvgCheckpointLength) { cp.Logger.Debug("Fetching last header block to calculate time") @@ -137,10 +143,10 @@ func (cp *CheckpointProcessor) createAndSendTronCheckpointToHeimdall(checkpointC latestCheckpoint, err := util.GetlastestCheckpoint(cp.cliCtx, hmTypes.RootChainTypeTron) // event checkpoint is older than or equal to latest checkpoint if err == nil && latestCheckpoint != nil && latestCheckpoint.EndBlock+1 < start { - cp.Logger.Debug("Need to resubmit Checkpoint ack first", "start", start, "last_end", latestCheckpoint.EndBlock) + cp.Logger.Info("Need to resubmit Checkpoint ack first", "start", start, "last_end", latestCheckpoint.EndBlock) err := cp.resubmitTronCheckpointAck(checkpointContext) if err != nil { - cp.Logger.Info("Error while resubmit checkpoint ack", "root", hmTypes.RootChainTypeTron, "err", err) + cp.Logger.Error("Error while resubmit checkpoint ack", "root", hmTypes.RootChainTypeTron, "err", err) return err } return nil @@ -343,3 +349,29 @@ func (cp *CheckpointProcessor) resubmitTronCheckpointAck(checkpointContext *Chec return nil } + +// checkCrossChainForTron checks if checkpoint should be submitted based on Tron dynamic checkpoint feature +// This method implements the same logic as checkCrossChain but specifically for Tron chain +func (cp *CheckpointProcessor) checkCrossChainForTron(start uint64, end uint64, maxCheckpointLengthParam uint64) bool { + // Get Tron dynamic checkpoint feature configuration + isOpen, maxLength, err := cp.getTronDynamicCheckpointProposalWithErr() + if err != nil || !isOpen { + // If feature is not enabled or error occurred, allow checkpoint submission + return true + } + + // Validate maxLength parameter + if maxCheckpointLengthParam < uint64(maxLength) { + cp.Logger.Error("proposal feature-tron-dynamic-checkpoint maxlength is too long", + "maxLength", maxLength, "MaxCheckpointLength", maxCheckpointLengthParam) + return true + } + + // If checkpoint length already reached maxLength, allow submission + if end-start+1 >= uint64(maxLength) { + return true + } + + // Check if there are cross-chain transactions for Tron + return cp.hasCrossChainTx(start, end, hmTypes.RootChainTypeTron) +} diff --git a/bridge/setu/util/common.go b/bridge/setu/util/common.go index b3fd47e7..48929a41 100644 --- a/bridge/setu/util/common.go +++ b/bridge/setu/util/common.go @@ -426,6 +426,40 @@ func GetCheckpointParams(cliCtx cliContext.CLIContext) (*checkpointTypes.Params, return ¶ms, nil } +// GetCheckpointParamsWithRetry guarantees successful retrieval of checkpoint parameters +// by retrying up to 10 times. If it fails after 10 attempts, the service will exit. +func GetCheckpointParamsWithRetry(cliCtx cliContext.CLIContext) *checkpointTypes.Params { + const maxRetries = 10 + retryDelay := 1 * time.Second + maxRetryDelay := 30 * time.Second + + for attempt := 1; attempt <= maxRetries; attempt++ { + params, err := GetCheckpointParams(cliCtx) + if err == nil { + logger.Info("Successfully fetched checkpoint params", "attempt", attempt) + return params + } + + logger.Error("Failed to fetch checkpoint params, retrying...", + "err", err, "attempt", attempt, "maxRetries", maxRetries, "retryAfter", retryDelay) + + if attempt < maxRetries { + time.Sleep(retryDelay) + + // Exponential backoff with cap + retryDelay *= 2 + if retryDelay > maxRetryDelay { + retryDelay = maxRetryDelay + } + } + } + + logger.Error("Unexpected: exceeded retry loop without returning or exiting") + + panic(errors.New("Failed to fetch checkpoint params")) + return nil +} + // GetBufferedCheckpoint return checkpoint from bueffer func GetBufferedCheckpoint(cliCtx cliContext.CLIContext, rootChain string) (*hmtypes.Checkpoint, error) { response, err := helper.FetchFromAPI( @@ -570,6 +604,10 @@ func GetDynamicCheckpointFeature(cliCtx cliContext.CLIContext) (*featureManagerT return GetTargetFeatureConfig(cliCtx, featureManagerTypes.DynamicCheckpoint) } +func GetTronDynamicCheckpointFeature(cliCtx cliContext.CLIContext) (*featureManagerTypes.PlainFeatureData, error) { + return GetTargetFeatureConfig(cliCtx, featureManagerTypes.TronDynamicCheckpoint) +} + func GetFinalizedEthOpen(cliCtx cliContext.CLIContext) bool { feature, err := GetTargetFeatureConfig(cliCtx, featureManagerTypes.FinalizedEth) if err != nil { diff --git a/chainmanager/querier_test.go b/chainmanager/querier_test.go index e78eb7b7..852fa766 100644 --- a/chainmanager/querier_test.go +++ b/chainmanager/querier_test.go @@ -82,11 +82,18 @@ func (suite *QuerierTestSuite) TestQueryParams() { require.Equal(t, defaultParams.ChainParams, params.ChainParams) { + // When params are not set, querier should return zero values instead of panicking rapp := app.Setup(true) ctx := rapp.BaseApp.NewContext(true, abci.Header{}) querier := chainmanager.NewQuerier(rapp.ChainKeeper) - require.Panics(t, func() { - querier(ctx, path, req) - }) + res, err = querier(ctx, path, req) + require.NoError(t, err) + require.NotNil(t, res) + + var zeroParams types.Params + json.Unmarshal(res, &zeroParams) + // Params should be zero values when not initialized + require.Equal(t, uint64(0), zeroParams.MainchainTxConfirmations) + require.Equal(t, uint64(0), zeroParams.MaticchainTxConfirmations) } } diff --git a/checkpoint/handler.go b/checkpoint/handler.go index 7531b60b..f82eeaf3 100644 --- a/checkpoint/handler.go +++ b/checkpoint/handler.go @@ -225,14 +225,32 @@ func handleMsgCheckpointNoAck(ctx sdk.Context, msg types.MsgCheckpointNoAck, k K // Get buffer time from params bufferTime := k.GetParams(ctx).CheckpointBufferTime + var checkpointPollInterval time.Duration + if k.GetParams(ctx).CheckpointPollInterval > 0 { + checkpointPollInterval = k.GetParams(ctx).CheckpointPollInterval + } else { + checkpointPollInterval = helper.GetConfig().CheckpointerPollInterval + } + + var checkpointTimeout time.Duration + tronDynamicFeature := util.GetFeatureConfig().GetFeature(ctx, featuremanagerTypes.TronDynamicCheckpoint) + if tronDynamicFeature.IsOpen { + tronMaxLength := tronDynamicFeature.IntConf["maxLength"] + checkpointTimeout, _ = helper.CalcCheckpointTimeout(tronMaxLength, checkpointPollInterval) + } else { + checkpointTimeout = bufferTime + } + // Fetch last checkpoint from store // TODO figure out how to handle this error lastCheckpoint, _ := k.GetLastCheckpoint(ctx, hmTypes.RootChainTypeStake) lastCheckpointTime := time.Unix(int64(lastCheckpoint.TimeStamp), 0) // If last checkpoint is not present or last checkpoint happens before checkpoint buffer time -- thrown an error - if lastCheckpointTime.After(currentTime) || (currentTime.Sub(lastCheckpointTime) < bufferTime) { - logger.Debug("Invalid No ACK -- Waiting for last checkpoint ACK") + if lastCheckpointTime.After(currentTime) || (currentTime.Sub(lastCheckpointTime) < checkpointTimeout) { + logger.Debug("Invalid No ACK -- Waiting for last checkpoint ACK", "lastCheckpoint", lastCheckpoint, + "lastCheckpointTime", lastCheckpointTime, "currentTime", currentTime, + "checkpointTimeout", checkpointTimeout) return common.ErrInvalidNoACK(k.Codespace()).Result() } diff --git a/checkpoint/handler_test.go b/checkpoint/handler_test.go index 9ed44aef..d557989a 100644 --- a/checkpoint/handler_test.go +++ b/checkpoint/handler_test.go @@ -15,6 +15,7 @@ import ( "github.com/maticnetwork/heimdall/checkpoint" chSim "github.com/maticnetwork/heimdall/checkpoint/simulation" + featuremanagerTypes "github.com/maticnetwork/heimdall/featuremanager/types" "github.com/maticnetwork/heimdall/helper/mocks" hmTypes "github.com/maticnetwork/heimdall/types" "github.com/stretchr/testify/require" @@ -332,7 +333,6 @@ func (suite *HandlerTestSuite) TestHandleMsgCheckpointNoAck() { start := uint64(0) maxSize := uint64(256) params := keeper.GetParams(ctx) - checkpointBufferTime := params.CheckpointBufferTime dividendAccount := hmTypes.DividendAccount{ User: hmTypes.HexToHeimdallAddress("123"), @@ -359,8 +359,75 @@ func (suite *HandlerTestSuite) TestHandleMsgCheckpointNoAck() { require.True(t, got.IsOK(), "expected send-NoAck to be ok, got %v", got) // set time lastCheckpoint timestamp + checkpointBufferTime - newTime := lastCheckpoint.TimeStamp + uint64(checkpointBufferTime) - suite.ctx = ctx.WithBlockTime(time.Unix(0, int64(newTime))) + checkpointTimeout := 10 * time.Minute + newTime := time.Unix(int64(lastCheckpoint.TimeStamp), int64(checkpointTimeout)) + suite.ctx = ctx.WithBlockTime(newTime) + result := suite.SendNoAck() + require.True(t, result.IsOK(), "expected send-NoAck to be ok, got %v", got) + ackCount := keeper.GetACKCount(ctx, hmTypes.RootChainTypeStake) + require.Equal(t, uint64(0), uint64(ackCount), "Should not update state") +} + +func (suite *HandlerTestSuite) TestHandleMsgCheckpointNoAckWithTronDynamicOpen() { + t, app, ctx := suite.T(), suite.app, suite.ctx + keeper := app.CheckpointKeeper + stakingKeeper := app.StakingKeeper + topupKeeper := app.TopupKeeper + featureKeeper := app.FeatureKeeper + start := uint64(0) + maxSize := uint64(256) + params := keeper.GetParams(ctx) + + open := true + + featureParams := featuremanagerTypes.FeatureParams{ + FeatureParamMap: map[string]featuremanagerTypes.FeatureData{ + featuremanagerTypes.DynamicCheckpoint: {IsOpen: &open, + IntConf: map[string]int{ + "eth": 1, + "maxLength": 1024, + }}, + featuremanagerTypes.FinalizedEth: {IsOpen: &open}, + }, + } + featureData := featuremanagerTypes.FeatureData{ + IsOpen: &open, + IntConf: map[string]int{ + "maxLength": 1024, + }, + } + featureParams.FeatureParamMap[featuremanagerTypes.TronDynamicCheckpoint] = featureData + + featureKeeper.SetFeatureParams(ctx, featureParams) + + dividendAccount := hmTypes.DividendAccount{ + User: hmTypes.HexToHeimdallAddress("123"), + FeeAmount: big.NewInt(0).String(), + } + topupKeeper.AddDividendAccount(ctx, dividendAccount) + + // check valid checkpoint + // generate proposer for validator set + chSim.LoadValidatorSet(2, t, stakingKeeper, ctx, false, 10) + stakingKeeper.IncrementAccum(ctx, 1) + + lastCheckpoint, err := keeper.GetLastCheckpoint(ctx, hmTypes.RootChainTypeStake) + if err == nil { + start = start + lastCheckpoint.EndBlock + 1 + } + + header, err := chSim.GenRandCheckpoint(start, maxSize, params.MaxCheckpointLength) + + // add current proposer to header + header.Proposer = stakingKeeper.GetValidatorSet(ctx).Proposer.Signer + + got := suite.SendCheckpoint(header) + require.True(t, got.IsOK(), "expected send-NoAck to be ok, got %v", got) + + // set time lastCheckpoint timestamp + checkpointBufferTime + checkpointTimeout := 40 * time.Minute + newTime := time.Unix(int64(lastCheckpoint.TimeStamp), int64(checkpointTimeout)) + suite.ctx = ctx.WithBlockTime(newTime) result := suite.SendNoAck() require.True(t, result.IsOK(), "expected send-NoAck to be ok, got %v", got) ackCount := keeper.GetACKCount(ctx, hmTypes.RootChainTypeStake) diff --git a/checkpoint/integration_test.go b/checkpoint/integration_test.go index a1c7140e..5b845432 100644 --- a/checkpoint/integration_test.go +++ b/checkpoint/integration_test.go @@ -26,8 +26,7 @@ func createTestApp(isCheckTx bool) (*app.HeimdallApp, sdk.Context, context.CLICo cliCtx := context.NewCLIContext().WithCodec(app.Codec()) helper.SetTestConfig(helper.GetDefaultHeimdallConfig()) - - params := types.NewParams(5*time.Second, 256, 1024, 10000) + params := types.NewParams(5*time.Second, 256, 1024, 10000, 10*time.Minute) Checkpoints := make([]hmTypes.Checkpoint, 0) diff --git a/checkpoint/types/params.go b/checkpoint/types/params.go index 09222ab8..49e8c9f3 100644 --- a/checkpoint/types/params.go +++ b/checkpoint/types/params.go @@ -11,28 +11,31 @@ import ( // Default parameter values const ( - DefaultCheckpointBufferTime time.Duration = 1000 * time.Second // Time checkpoint is allowed to stay in buffer (1000 seconds ~ 17 mins) - DefaultAvgCheckpointLength uint64 = 256 - DefaultMaxCheckpointLength uint64 = 1024 - DefaultChildBlockInterval uint64 = 10000 + DefaultCheckpointBufferTime time.Duration = 1000 * time.Second // Time checkpoint is allowed to stay in buffer (1000 seconds ~ 17 mins) + DefaultAvgCheckpointLength uint64 = 256 + DefaultMaxCheckpointLength uint64 = 1024 + DefaultChildBlockInterval uint64 = 10000 + DefaultCheckpointPollInterval time.Duration = 30 * time.Minute // Poll interval for checkpoint service to send new checkpoints or missing ACK ) // Parameter keys var ( - KeyCheckpointBufferTime = []byte("CheckpointBufferTime") - KeyAvgCheckpointLength = []byte("AvgCheckpointLength") - KeyMaxCheckpointLength = []byte("MaxCheckpointLength") - KeyChildBlockInterval = []byte("ChildBlockInterval") + KeyCheckpointBufferTime = []byte("CheckpointBufferTime") + KeyAvgCheckpointLength = []byte("AvgCheckpointLength") + KeyMaxCheckpointLength = []byte("MaxCheckpointLength") + KeyChildBlockInterval = []byte("ChildBlockInterval") + KeyCheckpointPollInterval = []byte("CheckpointPollInterval") ) var _ subspace.ParamSet = &Params{} // Params defines the parameters for the auth module. type Params struct { - CheckpointBufferTime time.Duration `json:"checkpoint_buffer_time" yaml:"checkpoint_buffer_time"` - AvgCheckpointLength uint64 `json:"avg_checkpoint_length" yaml:"avg_checkpoint_length"` - MaxCheckpointLength uint64 `json:"max_checkpoint_length" yaml:"max_checkpoint_length"` - ChildBlockInterval uint64 `json:"child_chain_block_interval" yaml:"child_chain_block_interval"` + CheckpointBufferTime time.Duration `json:"checkpoint_buffer_time" yaml:"checkpoint_buffer_time"` + AvgCheckpointLength uint64 `json:"avg_checkpoint_length" yaml:"avg_checkpoint_length"` + MaxCheckpointLength uint64 `json:"max_checkpoint_length" yaml:"max_checkpoint_length"` + ChildBlockInterval uint64 `json:"child_chain_block_interval" yaml:"child_chain_block_interval"` + CheckpointPollInterval time.Duration `json:"checkpoint_poll_interval" yaml:"checkpoint_poll_interval"` } // NewParams creates a new Params object @@ -41,12 +44,14 @@ func NewParams( checkpointLength uint64, maxCheckpointLength uint64, childBlockInterval uint64, + checkpointPollInterval time.Duration, ) Params { return Params{ - CheckpointBufferTime: checkpointBufferTime, - AvgCheckpointLength: checkpointLength, - MaxCheckpointLength: maxCheckpointLength, - ChildBlockInterval: childBlockInterval, + CheckpointBufferTime: checkpointBufferTime, + AvgCheckpointLength: checkpointLength, + MaxCheckpointLength: maxCheckpointLength, + ChildBlockInterval: childBlockInterval, + CheckpointPollInterval: checkpointPollInterval, } } @@ -64,6 +69,7 @@ func (p *Params) ParamSetPairs() subspace.ParamSetPairs { {KeyAvgCheckpointLength, &p.AvgCheckpointLength}, {KeyMaxCheckpointLength, &p.MaxCheckpointLength}, {KeyChildBlockInterval, &p.ChildBlockInterval}, + {KeyCheckpointPollInterval, &p.CheckpointPollInterval}, } } @@ -77,10 +83,11 @@ func (p Params) Equal(p2 Params) bool { // DefaultParams returns a default set of parameters. func DefaultParams() Params { return Params{ - CheckpointBufferTime: DefaultCheckpointBufferTime, - AvgCheckpointLength: DefaultAvgCheckpointLength, - MaxCheckpointLength: DefaultMaxCheckpointLength, - ChildBlockInterval: DefaultChildBlockInterval, + CheckpointBufferTime: DefaultCheckpointBufferTime, + AvgCheckpointLength: DefaultAvgCheckpointLength, + MaxCheckpointLength: DefaultMaxCheckpointLength, + ChildBlockInterval: DefaultChildBlockInterval, + CheckpointPollInterval: DefaultCheckpointPollInterval, } } @@ -92,6 +99,7 @@ func (p Params) String() string { sb.WriteString(fmt.Sprintf("AvgCheckpointLength: %d\n", p.AvgCheckpointLength)) sb.WriteString(fmt.Sprintf("MaxCheckpointLength: %d\n", p.MaxCheckpointLength)) sb.WriteString(fmt.Sprintf("ChildBlockInterval: %d\n", p.ChildBlockInterval)) + sb.WriteString(fmt.Sprintf("CheckpointPollInterval: %s\n", p.CheckpointPollInterval)) return sb.String() } @@ -109,5 +117,9 @@ func (p Params) Validate() error { return fmt.Errorf("ChildBlockInterval should be greater than zero") } + if p.CheckpointPollInterval == 0 { + return fmt.Errorf("CheckpointPollInterval should be greater than zero") + } + return nil } diff --git a/featuremanager/keeper.go b/featuremanager/keeper.go index 93b3ef69..68f44a53 100644 --- a/featuremanager/keeper.go +++ b/featuremanager/keeper.go @@ -66,6 +66,7 @@ func (k Keeper) RegisterFeature() { k.addFeature(types.SupportMapMarshaling) k.addFeature(types.FinalizedEth) k.addFeature(types.NoAckValidatorCheck) + k.addFeature(types.TronDynamicCheckpoint) } func (k Keeper) HasFeature(feature string) bool { diff --git a/featuremanager/types/keys.go b/featuremanager/types/keys.go index b0b6dfb5..1bd65ef6 100644 --- a/featuremanager/types/keys.go +++ b/featuremanager/types/keys.go @@ -18,8 +18,9 @@ const ( ) const ( - SupportMapMarshaling = "SupportMapMarshaling" - DynamicCheckpoint = "DynamicCheckpoint" - FinalizedEth = "FinalizedEth" - NoAckValidatorCheck = "NoAckValidatorCheck" + SupportMapMarshaling = "SupportMapMarshaling" + DynamicCheckpoint = "DynamicCheckpoint" + FinalizedEth = "FinalizedEth" + NoAckValidatorCheck = "NoAckValidatorCheck" + TronDynamicCheckpoint = "TronDynamicCheckpoint" ) diff --git a/helper/util.go b/helper/util.go index 035d83fc..dd357092 100644 --- a/helper/util.go +++ b/helper/util.go @@ -16,6 +16,7 @@ import ( "os" "path" "sort" + "time" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" @@ -51,6 +52,10 @@ var ZeroAddress = common.Address{} // ZeroPubKey represents empty pub key var ZeroPubKey = hmTypes.PubKey{} +const BttcBlockInterval = 2 * time.Second +const BttcFirstBlockIntervalInSprint = 6 * time.Second +const BttcSprintLength = 64 + // GetFromAddress get from address func GetFromAddress(cliCtx context.CLIContext) types.HeimdallAddress { fromAddress := cliCtx.GetFromAddress() @@ -888,3 +893,14 @@ func Hash(s []byte) ([]byte, error) { bs := h.Sum(nil) return bs, nil } + +func CalcCheckpointTimeout(tronMaxLength int, pollTime time.Duration) (time.Duration, error) { + if pollTime <= 0 { + return 0, errors.New("pollTime must be greater than 0") + } + + timeForBttcBlocks := time.Duration(tronMaxLength)*BttcBlockInterval + time.Duration(tronMaxLength)/BttcSprintLength*(BttcFirstBlockIntervalInSprint-BttcBlockInterval) + + checkpointTimeout := ((timeForBttcBlocks + pollTime - 1) / pollTime) * pollTime + return checkpointTimeout, nil +} diff --git a/helper/util_test.go b/helper/util_test.go index 2ac50e2d..91b73fe0 100644 --- a/helper/util_test.go +++ b/helper/util_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "math/big" "testing" + "time" "github.com/stretchr/testify/require" @@ -63,3 +64,17 @@ func TestGetPowerFromAmount(t *testing.T) { require.Equal(t, p.String(), v, "Power must match") } } + +func TestCalcCheckpointTimeout(t *testing.T) { + tronMaxLength := 1024 + pollTime := 10 * time.Minute + checkpointTimeout, _ := CalcCheckpointTimeout(tronMaxLength, pollTime) + require.Equal(t, 40*time.Minute, checkpointTimeout, "checkpointTimeout should match") +} + +func TestCalcCheckpointTimeout2(t *testing.T) { + tronMaxLength := 1024 + pollTime := 5 * time.Minute + checkpointTimeout, _ := CalcCheckpointTimeout(tronMaxLength, pollTime) + require.Equal(t, 40*time.Minute, checkpointTimeout, "checkpointTimeout should match") +} diff --git a/params/subspace/subspace.go b/params/subspace/subspace.go index ea26a947..e8911fa5 100644 --- a/params/subspace/subspace.go +++ b/params/subspace/subspace.go @@ -2,9 +2,10 @@ package subspace import ( "encoding/json" - "github.com/ethereum/go-ethereum/log" "reflect" + "github.com/ethereum/go-ethereum/log" + "github.com/maticnetwork/heimdall/helper/fork" "github.com/cosmos/cosmos-sdk/codec" @@ -123,6 +124,13 @@ func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { store := s.kvStore(ctx) bz := store.Get(key) + // If the key does not exist in store, return early without modifying ptr + // This allows ptr to keep its zero value (e.g., 0 for time.Duration) + // Callers can check if the value > 0 to determine if it was set + if bz == nil { + return + } + var err error if hasMap(ctx, s, string(key)) {