From ce70a337d0e0f51655f5b4dd4918c21ad276e04e Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 24 Oct 2023 10:27:49 +0800 Subject: [PATCH 1/3] implement block time calculator --- pkg/util/blockutil/block_time_calculator.go | 43 +++++++++++++++++ .../blockutil/block_time_calculator_test.go | 46 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 pkg/util/blockutil/block_time_calculator.go create mode 100644 pkg/util/blockutil/block_time_calculator_test.go diff --git a/pkg/util/blockutil/block_time_calculator.go b/pkg/util/blockutil/block_time_calculator.go new file mode 100644 index 0000000000..5ac8675791 --- /dev/null +++ b/pkg/util/blockutil/block_time_calculator.go @@ -0,0 +1,43 @@ +package blockutil + +import "time" + +type ( + // BlockTimeCalculator calculates block time of a given height. + BlockTimeCalculator struct { + getBlockInterval getBlockIntervalFn + getTipHeight getTipHeightFn + getHistoryBlockTime getHistoryblockTimeFn + } + + getBlockIntervalFn func(uint64) time.Duration + getTipHeightFn func() uint64 + getHistoryblockTimeFn func(uint64) (time.Time, error) +) + +// NewBlockTimeCalculator creates a new BlockTimeCalculator. +func NewBlockTimeCalculator(getBlockInterval getBlockIntervalFn, getTipHeight getTipHeightFn, getHistoryBlockTime getHistoryblockTimeFn) *BlockTimeCalculator { + return &BlockTimeCalculator{ + getBlockInterval: getBlockInterval, + getTipHeight: getTipHeight, + getHistoryBlockTime: getHistoryBlockTime, + } +} + +// CalculateBlockTime returns the block time of the given height. +// If the height is in the future, it will predict the block time according to the tip block time and interval. +// If the height is in the past, it will get the block time from indexer. +func (btc *BlockTimeCalculator) CalculateBlockTime(height uint64) (time.Time, error) { + // get block time from indexer if height is in the past + tipHeight := btc.getTipHeight() + if height <= tipHeight { + return btc.getHistoryBlockTime(height) + } + + // predict block time according to tip block time and interval + tipBlockTime, err := btc.getHistoryBlockTime(tipHeight) + if err != nil { + return time.Time{}, err + } + return tipBlockTime.Add(time.Duration(height-tipHeight) * btc.getBlockInterval(height)), nil +} diff --git a/pkg/util/blockutil/block_time_calculator_test.go b/pkg/util/blockutil/block_time_calculator_test.go new file mode 100644 index 0000000000..203af1206e --- /dev/null +++ b/pkg/util/blockutil/block_time_calculator_test.go @@ -0,0 +1,46 @@ +package blockutil + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestBlockTimeCalculator_CalculateBlockTime(t *testing.T) { + r := require.New(t) + interval := 5 * time.Second + intervalFn := func(h uint64) time.Duration { + return 5 * time.Second + } + tipHeight := uint64(100) + tipHeightF := func() uint64 { return tipHeight } + baseTime, err := time.Parse("2006-01-02T15:04:05.000Z", "2022-01-01T00:00:00.000Z") + r.NoError(err) + historyBlockTimeF := func(height uint64) (time.Time, error) { return baseTime.Add(time.Hour * time.Duration(height)), nil } + btc := NewBlockTimeCalculator(intervalFn, tipHeightF, historyBlockTimeF) + + historyWrapper := func(height uint64) time.Time { + t, err := historyBlockTimeF(height) + r.NoError(err) + return t + } + cases := []struct { + name string + height uint64 + want time.Time + }{ + {"height is in the past", tipHeight - 1, historyWrapper(tipHeight - 1)}, + {"height is in the past I", tipHeight, historyWrapper(tipHeight)}, + {"height is in the future", tipHeight + 1, historyWrapper(tipHeight).Add(interval)}, + {"height is in the future I", tipHeight + 2, historyWrapper(tipHeight).Add(2 * interval)}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got, err := btc.CalculateBlockTime(c.height) + r.NoError(err) + r.Equal(c.want, got) + }) + } +} From ef356cce12158e4077daab57efc297b76aa1033e Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 1 Nov 2023 06:53:52 +0800 Subject: [PATCH 2/3] address comments --- pkg/util/blockutil/block_time_calculator.go | 20 +++++++++++++++---- .../blockutil/block_time_calculator_test.go | 3 ++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pkg/util/blockutil/block_time_calculator.go b/pkg/util/blockutil/block_time_calculator.go index 5ac8675791..fd783f0c9e 100644 --- a/pkg/util/blockutil/block_time_calculator.go +++ b/pkg/util/blockutil/block_time_calculator.go @@ -1,6 +1,9 @@ package blockutil -import "time" +import ( + "errors" + "time" +) type ( // BlockTimeCalculator calculates block time of a given height. @@ -16,12 +19,21 @@ type ( ) // NewBlockTimeCalculator creates a new BlockTimeCalculator. -func NewBlockTimeCalculator(getBlockInterval getBlockIntervalFn, getTipHeight getTipHeightFn, getHistoryBlockTime getHistoryblockTimeFn) *BlockTimeCalculator { +func NewBlockTimeCalculator(getBlockInterval getBlockIntervalFn, getTipHeight getTipHeightFn, getHistoryBlockTime getHistoryblockTimeFn) (*BlockTimeCalculator, error) { + if getBlockInterval == nil { + return nil, errors.New("nil getBlockInterval") + } + if getTipHeight == nil { + return nil, errors.New("nil getTipHeight") + } + if getHistoryBlockTime == nil { + return nil, errors.New("nil getHistoryBlockTime") + } return &BlockTimeCalculator{ getBlockInterval: getBlockInterval, getTipHeight: getTipHeight, getHistoryBlockTime: getHistoryBlockTime, - } + }, nil } // CalculateBlockTime returns the block time of the given height. @@ -39,5 +51,5 @@ func (btc *BlockTimeCalculator) CalculateBlockTime(height uint64) (time.Time, er if err != nil { return time.Time{}, err } - return tipBlockTime.Add(time.Duration(height-tipHeight) * btc.getBlockInterval(height)), nil + return tipBlockTime.Add(time.Duration(height-tipHeight) * btc.getBlockInterval(tipHeight)), nil } diff --git a/pkg/util/blockutil/block_time_calculator_test.go b/pkg/util/blockutil/block_time_calculator_test.go index 203af1206e..1ce2f7b2e5 100644 --- a/pkg/util/blockutil/block_time_calculator_test.go +++ b/pkg/util/blockutil/block_time_calculator_test.go @@ -18,7 +18,8 @@ func TestBlockTimeCalculator_CalculateBlockTime(t *testing.T) { baseTime, err := time.Parse("2006-01-02T15:04:05.000Z", "2022-01-01T00:00:00.000Z") r.NoError(err) historyBlockTimeF := func(height uint64) (time.Time, error) { return baseTime.Add(time.Hour * time.Duration(height)), nil } - btc := NewBlockTimeCalculator(intervalFn, tipHeightF, historyBlockTimeF) + btc, err := NewBlockTimeCalculator(intervalFn, tipHeightF, historyBlockTimeF) + r.NoError(err) historyWrapper := func(height uint64) time.Time { t, err := historyBlockTimeF(height) From b547d27cc3762bee4fa32d1e5882058666047389 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 1 Nov 2023 20:43:37 +0800 Subject: [PATCH 3/3] overflow check --- pkg/util/blockutil/block_time_calculator.go | 8 +++++++- pkg/util/blockutil/block_time_calculator_test.go | 15 +++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pkg/util/blockutil/block_time_calculator.go b/pkg/util/blockutil/block_time_calculator.go index fd783f0c9e..71726fcb14 100644 --- a/pkg/util/blockutil/block_time_calculator.go +++ b/pkg/util/blockutil/block_time_calculator.go @@ -2,6 +2,7 @@ package blockutil import ( "errors" + "math" "time" ) @@ -47,9 +48,14 @@ func (btc *BlockTimeCalculator) CalculateBlockTime(height uint64) (time.Time, er } // predict block time according to tip block time and interval + blockInterval := btc.getBlockInterval(tipHeight) + blockNumer := time.Duration(height - tipHeight) + if blockNumer > math.MaxInt64/blockInterval { + return time.Time{}, errors.New("height overflow") + } tipBlockTime, err := btc.getHistoryBlockTime(tipHeight) if err != nil { return time.Time{}, err } - return tipBlockTime.Add(time.Duration(height-tipHeight) * btc.getBlockInterval(tipHeight)), nil + return tipBlockTime.Add(blockNumer * blockInterval), nil } diff --git a/pkg/util/blockutil/block_time_calculator_test.go b/pkg/util/blockutil/block_time_calculator_test.go index 1ce2f7b2e5..6cdbdbff3a 100644 --- a/pkg/util/blockutil/block_time_calculator_test.go +++ b/pkg/util/blockutil/block_time_calculator_test.go @@ -30,16 +30,23 @@ func TestBlockTimeCalculator_CalculateBlockTime(t *testing.T) { name string height uint64 want time.Time + errMsg string }{ - {"height is in the past", tipHeight - 1, historyWrapper(tipHeight - 1)}, - {"height is in the past I", tipHeight, historyWrapper(tipHeight)}, - {"height is in the future", tipHeight + 1, historyWrapper(tipHeight).Add(interval)}, - {"height is in the future I", tipHeight + 2, historyWrapper(tipHeight).Add(2 * interval)}, + {"height is in the past", tipHeight - 1, historyWrapper(tipHeight - 1), ""}, + {"height is in the past I", tipHeight, historyWrapper(tipHeight), ""}, + {"height is in the future", tipHeight + 1, historyWrapper(tipHeight).Add(interval), ""}, + {"height is in the future I", tipHeight + 2, historyWrapper(tipHeight).Add(2 * interval), ""}, + {"height is not overflow", tipHeight + (1<<63-1)/uint64(interval), historyWrapper(tipHeight).Add((1<<63 - 1) / interval * interval), ""}, + {"height is overflow", tipHeight + (1<<63-1)/uint64(interval) + 1, time.Time{}, "height overflow"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got, err := btc.CalculateBlockTime(c.height) + if c.errMsg != "" { + r.ErrorContains(err, c.errMsg) + return + } r.NoError(err) r.Equal(c.want, got) })