Skip to content

Commit

Permalink
vocone: fix dateToBlock estimation endpoint
Browse files Browse the repository at this point in the history
* vocinfo.BlockTimes() now returns [5]time.Duration
* during startup, endpoint /chain/info now returns BlockTimeTarget instead of `[0, 0, 0, 0, 0]`
* vochain/app now has BlockTimeTarget and SetBlockTimeTarget
* types.DefaultBlockTime fixed (10s instead of 12s)
* vi.EstimateBlockHeight and vi.HeightTime refactored and fixed
* vocone now keeps track of blockTimestamps
* improved vocone.getBlock to fill in blk.Time, blk.Height and blk.ChainID
  • Loading branch information
altergui committed Dec 13, 2023
1 parent a8b87d0 commit f432284
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 104 deletions.
10 changes: 9 additions & 1 deletion api/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,17 @@ func (a *API) chainInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
return err
}

var blockTimesInMs [5]uint64
for i, v := range a.vocinfo.BlockTimes() {
blockTimesInMs[i] = uint64(v.Milliseconds())
}
if blockTimesInMs[0] == 0 {
blockTimesInMs[0] = uint64(a.vocapp.BlockTimeTarget().Milliseconds())
}

data, err := json.Marshal(&ChainInfo{
ID: a.vocapp.ChainID(),
BlockTime: a.vocinfo.BlockTimes(),
BlockTime: blockTimesInMs,
ElectionCount: a.indexer.CountTotalProcesses(),
OrganizationCount: a.indexer.CountTotalEntities(),
Height: a.vocapp.Height(),
Expand Down
2 changes: 1 addition & 1 deletion cmd/voconed/voconed.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func main() {
}
}

vc.SetBlockTimeTarget(time.Second * time.Duration(config.blockSeconds))
vc.App.SetBlockTimeTarget(time.Second * time.Duration(config.blockSeconds))
vc.SetBlockSize(config.blockSize)

go vc.Start()
Expand Down
6 changes: 3 additions & 3 deletions service/vochain.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,13 @@ func VochainPrintInfo(interval time.Duration, vi *vochaininfo.VochainInfo) {
b.Reset()
a := vi.BlockTimes()
if a[1] > 0 {
fmt.Fprintf(&b, "10m:%.2f", float32(a[1])/1000)
fmt.Fprintf(&b, "10m:%s", a[1].Truncate(time.Millisecond))
}
if a[2] > 0 {
fmt.Fprintf(&b, " 1h:%.2f", float32(a[2])/1000)
fmt.Fprintf(&b, " 1h:%s", a[2].Truncate(time.Millisecond))
}
if a[4] > 0 {
fmt.Fprintf(&b, " 24h:%.2f", float32(a[4])/1000)
fmt.Fprintf(&b, " 24h:%s", a[4].Truncate(time.Millisecond))
}
h = vi.Height()
m = vi.MempoolSize()
Expand Down
2 changes: 1 addition & 1 deletion types/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const (
ArchiveURL = "/ipns/k2k4r8otxrf176h1i08txap0ep6ynr1jac0vymozi068eedml7gk1595"

// DefaultBlockTime is the default block time in seconds.
DefaultBlockTime = 12 * time.Second
DefaultBlockTime = 10 * time.Second

// KeyKeeperMaxKeyIndex is the maxim number of allowed encryption keys.
KeyKeeperMaxKeyIndex = 16
Expand Down
17 changes: 17 additions & 0 deletions vochain/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"go.vocdoni.io/dvote/config"
"go.vocdoni.io/dvote/crypto/zk/circuit"
"go.vocdoni.io/dvote/test/testcommon/testutil"
"go.vocdoni.io/dvote/types"
"go.vocdoni.io/dvote/vochain/ist"
vstate "go.vocdoni.io/dvote/vochain/state"
"go.vocdoni.io/dvote/vochain/transaction"
Expand Down Expand Up @@ -72,6 +73,9 @@ type BaseApplication struct {
// was seen frist time and the number of attempts failed for including it into a block.
txReferences sync.Map

// blockTime is the target block time that miners use
blockTime time.Duration

// endBlockTimestamp is the last block end timestamp calculated from local time.
endBlockTimestamp atomic.Int64
// startBlockTimestamp is the current block timestamp from tendermint's
Expand Down Expand Up @@ -413,3 +417,16 @@ func (app *BaseApplication) TimestampFromBlock(height int64) *time.Time {
func (app *BaseApplication) MempoolSize() int {
return app.fnMempoolSize()
}

// BlockTimeTarget returns the current block time target
func (app *BaseApplication) BlockTimeTarget() time.Duration {
if app.blockTime == 0 {
return types.DefaultBlockTime
}
return app.blockTime
}

// SetBlockTimeTarget sets the current block time target
func (app *BaseApplication) SetBlockTimeTarget(d time.Duration) {
app.blockTime = d
}
142 changes: 59 additions & 83 deletions vochain/vochaininfo/vochaininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ package vochaininfo
import (
"context"
"fmt"
"math"
"sync"
"time"

"github.com/VictoriaMetrics/metrics"
coretypes "github.com/cometbft/cometbft/rpc/core/types"
"go.vocdoni.io/dvote/log"
"go.vocdoni.io/dvote/types"
"go.vocdoni.io/dvote/vochain"
"go.vocdoni.io/dvote/vochain/state"
)
Expand All @@ -19,7 +17,7 @@ import (
// Avg1/10/60/360 are the block time average for 1 minute, 10 minutes, 1 hour and 6 hours
type VochainInfo struct {
votesPerMinute uint64
blockTimes [5]uint64
blockTimes [5]time.Duration
blocksMinute float64
vnode *vochain.BaseApplication
close chan bool
Expand Down Expand Up @@ -63,7 +61,7 @@ func (vi *VochainInfo) updateCounters() {

voteCacheSize.Set(uint64(vi.vnode.State.CacheSize()))
mempoolSize.Set(uint64(vi.vnode.MempoolSize()))
blockPeriodMinute.Set(vi.BlockTimes()[0])
blockPeriodMinute.Set(uint64(vi.BlockTimes()[0].Milliseconds()))
blocksSyncLastMinute.Set(uint64(vi.BlocksLastMinute()))
}

Expand All @@ -74,7 +72,7 @@ func (vi *VochainInfo) Height() uint64 {

// averageBlockTime calculates the average block time for the last intervalBlocks blocks.
// The timestamp information is taken from the block headers.
func (vi *VochainInfo) averageBlockTime(intervalBlocks int64) float64 {
func (vi *VochainInfo) averageBlockTime(intervalBlocks int64) time.Duration {
if intervalBlocks == 0 {
return 0
}
Expand All @@ -91,93 +89,79 @@ func (vi *VochainInfo) averageBlockTime(intervalBlocks int64) float64 {
startTime := vi.vnode.TimestampFromBlock(startBlockHeight)
currentTime := vi.vnode.TimestampFromBlock(currentHeight)

if startTime == nil || currentTime == nil {
if startTime == nil || currentTime == nil || startTime.Equal(time.Time{}) {
return 0
}

// Calculate the time frame in seconds
timeFrameSeconds := currentTime.Sub(*startTime).Seconds()
timeFrame := currentTime.Sub(*startTime)

// Adjust the average block time based on the actual time frame
return timeFrameSeconds / float64(intervalBlocks)
return timeFrame / time.Duration(intervalBlocks)
}

func (vi *VochainInfo) updateBlockTimes() {
vi.blockTimes = [5]uint64{
uint64(1000 * vi.averageBlockTime(60/int64(types.DefaultBlockTime.Seconds())*1)),
uint64(1000 * vi.averageBlockTime(60/int64(types.DefaultBlockTime.Seconds())*10)),
uint64(1000 * vi.averageBlockTime(60/int64(types.DefaultBlockTime.Seconds())*60)),
uint64(1000 * vi.averageBlockTime(60/int64(types.DefaultBlockTime.Seconds())*360)),
uint64(1000 * vi.averageBlockTime(60/int64(types.DefaultBlockTime.Seconds())*1440)),
vi.blockTimes = [5]time.Duration{
vi.averageBlockTime(int64(1 * time.Minute / vi.vnode.BlockTimeTarget())),
vi.averageBlockTime(int64(10 * time.Minute / vi.vnode.BlockTimeTarget())),
vi.averageBlockTime(int64(1 * time.Hour / vi.vnode.BlockTimeTarget())),
vi.averageBlockTime(int64(6 * time.Hour / vi.vnode.BlockTimeTarget())),
vi.averageBlockTime(int64(24 * time.Hour / vi.vnode.BlockTimeTarget())),
}
if vi.blockTimes[0] == 0 {
vi.blockTimes[0] = vi.vnode.BlockTimeTarget()
}
}

// BlockTimes returns the average block time in milliseconds for 1, 10, 60, 360 and 1440 minutes.
// BlockTimes returns the average block time for 1, 10, 60, 360 and 1440 minutes.
// Value 0 means there is not yet an average.
func (vi *VochainInfo) BlockTimes() [5]uint64 {
func (vi *VochainInfo) BlockTimes() [5]time.Duration {
vi.lock.RLock()
defer vi.lock.RUnlock()
return vi.blockTimes
}

// EstimateBlockHeight provides an estimation time for a future blockchain height number.
func (vi *VochainInfo) EstimateBlockHeight(target time.Time) (uint64, error) {
currentTime := time.Now()
// diff time in seconds
diffTime := target.Unix() - currentTime.Unix()

// block time in ms
func (vi *VochainInfo) getBlockTimeBestEstimate(i int) time.Duration {
times := vi.BlockTimes()
getMaxTimeFrom := func(i int) uint64 {
for ; i >= 0; i-- {
if times[i] != 0 {
return uint64(times[i])
}
for ; i >= 0; i-- {
if times[i] != 0 {
return times[i]
}
return 10000 // fallback
}
inPast := diffTime < 0
absDiff := diffTime
// check diff is not too big
if absDiff > math.MaxUint64/1000 {
return 0, fmt.Errorf("target time %v is too far in the future", target)
}
if inPast {
absDiff = -absDiff
}
t := uint64(0)
switch {
// if less than around 15 minutes missing
case absDiff < 900:
t = getMaxTimeFrom(1)
// if less than around 6 hours missing
case absDiff < 21600:
t = getMaxTimeFrom(3)
// if more than around 6 hours missing
default:
t = getMaxTimeFrom(4)
}
// Multiply by 1000 because t is represented in seconds, not ms.
// Dividing t first can floor the integer, leading to divide-by-zero
currentHeight := uint64(vi.Height())
blockDiff := uint64(absDiff*1000) / t
if inPast {
if blockDiff > currentHeight {
return 0, fmt.Errorf("target time %v is before origin", target)
return vi.vnode.BlockTimeTarget() // fallback
}

// EstimateBlockHeight provides an estimated blockchain height for a future or past date.
func (vi *VochainInfo) EstimateBlockHeight(target time.Time) (uint64, error) {
timeDiff := time.Until(target)
timeBetweenBlocks := func() time.Duration {
switch {
// if less than around 15 minutes missing
case timeDiff.Abs().Minutes() < 15:
return vi.getBlockTimeBestEstimate(1)
// if less than around 6 hours missing
case timeDiff.Abs().Hours() < 6:
return vi.getBlockTimeBestEstimate(3)
// if more than around 6 hours missing
default:
return vi.getBlockTimeBestEstimate(4)
}
return currentHeight - blockDiff, nil
}()
blockDiff := uint64(timeDiff / timeBetweenBlocks)
currentHeight := uint64(vi.vnode.Height())
// timeDiff is negative if target is in the past
if timeDiff < 0 && currentHeight+blockDiff <= 0 {
return 0, fmt.Errorf("target time %v is before genesis", target)
}
return currentHeight + blockDiff, nil
}

// HeightTime estimates the UTC time for a future height or returns the
// block timestamp if height is in the past.
func (vi *VochainInfo) HeightTime(height uint64) time.Time {
times := vi.BlockTimes()
currentHeight := vi.Height()
diffHeight := int64(height - currentHeight)
diffHeight := int64(height - uint64(vi.vnode.Height()))

if diffHeight < 0 {
if diffHeight < 0 { // height is in the past
blk := vi.vnode.GetBlockByHeight(int64(height))
if blk == nil {
log.Errorf("cannot get block height %d", height)
Expand All @@ -186,28 +170,20 @@ func (vi *VochainInfo) HeightTime(height uint64) time.Time {
return blk.Header.Time
}

getMaxTimeFrom := func(i int) uint64 {
for ; i >= 0; i-- {
if times[i] != 0 {
return times[i]
}
timeBetweenBlocks := func() time.Duration {
switch {
// if less than around 15 minutes missing
case diffHeight < int64(15*time.Minute/vi.vnode.BlockTimeTarget()):
return vi.getBlockTimeBestEstimate(1)
// if less than around 6 hours missing
case diffHeight < int64(6*time.Hour/vi.vnode.BlockTimeTarget()):
return vi.getBlockTimeBestEstimate(3)
// if more than around 6 hours missing
default:
return vi.getBlockTimeBestEstimate(4)
}
return 10000 // fallback
}

t := uint64(0)
switch {
// if less than around 15 minutes missing
case diffHeight < 100:
t = getMaxTimeFrom(1)
// if less than around 6 hours missing
case diffHeight < 1000:
t = getMaxTimeFrom(3)
// if less than around 6 hours missing
case diffHeight >= 1000:
t = getMaxTimeFrom(4)
}
return time.Now().Add(time.Duration(diffHeight*int64(t)) * time.Millisecond)
}()
return time.Now().Add(time.Duration(diffHeight) * timeBetweenBlocks)
}

// TreeSizes returns the current size of the ProcessTree, VoteTree and the votes per minute
Expand Down
Loading

0 comments on commit f432284

Please sign in to comment.