Skip to content

Commit

Permalink
Added a new genesisSlot parameter to the TimeProvider to be able to o…
Browse files Browse the repository at this point in the history
…ffset the genesis
  • Loading branch information
alexsporn committed Oct 31, 2023
1 parent c52290b commit 578f582
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 62 deletions.
2 changes: 1 addition & 1 deletion api_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ func (v *v3api) Decode(b []byte, obj interface{}, opts ...serix.Option) (int, er
func V3API(protoParams ProtocolParameters) API {
api := CommonSerixAPI()

timeProvider := NewTimeProvider(protoParams.GenesisUnixTimestamp(), int64(protoParams.SlotDurationInSeconds()), protoParams.SlotsPerEpochExponent())
timeProvider := NewTimeProvider(0, protoParams.GenesisUnixTimestamp(), int64(protoParams.SlotDurationInSeconds()), protoParams.SlotsPerEpochExponent())

maxBlockWork, err := protoParams.WorkScoreParameters().MaxBlockWork()
must(err)
Expand Down
2 changes: 1 addition & 1 deletion builder/output_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestBasicOutputBuilder(t *testing.T) {
nativeTokenFeature = tpkg.RandNativeTokenFeature()
expirationTarget = tpkg.RandEd25519Address()
metadata = []byte("123456")
slotTimeProvider = iotago.NewTimeProvider(time.Now().Unix(), 10, 10)
slotTimeProvider = iotago.NewTimeProvider(0, time.Now().Unix(), 10, 10)
)
timelock := slotTimeProvider.SlotFromTime(time.Now().Add(5 * time.Minute))
expiration := slotTimeProvider.SlotFromTime(time.Now().Add(10 * time.Minute))
Expand Down
2 changes: 1 addition & 1 deletion mana_decay_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestMain(m *testing.M) {
testManaDecayFactors = tpkg.ManaDecayFactors(betaPerYear, 1<<slotsPerEpochExponent, slotDurationSeconds, decayFactorsExponent)
testManaDecayFactorEpochsSum = tpkg.ManaDecayFactorEpochsSum(betaPerYear, 1<<slotsPerEpochExponent, slotDurationSeconds, decayFactorEpochsSumExponent)

testTimeProvider = iotago.NewTimeProvider(0, slotDurationSeconds, slotsPerEpochExponent)
testTimeProvider = iotago.NewTimeProvider(0, 0, slotDurationSeconds, slotsPerEpochExponent)
manaStruct := &iotago.ManaParameters{
BitsCount: bitsCount,
GenerationRate: generationRate,
Expand Down
44 changes: 32 additions & 12 deletions timeprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
// epoch 2: [slot 16; slot 24)
// ...
type TimeProvider struct {
genesisSlot SlotIndex

// genesisUnixTime is the time (Unix in seconds) of the genesis.
genesisUnixTime int64

Expand All @@ -40,8 +42,9 @@ type TimeProvider struct {
}

// NewTimeProvider creates a new time provider.
func NewTimeProvider(genesisUnixTime int64, slotDurationSeconds int64, slotsPerEpochExponent uint8) *TimeProvider {
func NewTimeProvider(genesisSlot SlotIndex, genesisUnixTime int64, slotDurationSeconds int64, slotsPerEpochExponent uint8) *TimeProvider {
return &TimeProvider{
genesisSlot: genesisSlot,
genesisUnixTime: genesisUnixTime,
genesisTime: time.Unix(genesisUnixTime, 0),
slotDurationSeconds: slotDurationSeconds,
Expand All @@ -51,6 +54,11 @@ func NewTimeProvider(genesisUnixTime int64, slotDurationSeconds int64, slotsPerE
}
}

// GenesisSlot is the slot of the genesis.
func (t *TimeProvider) GenesisSlot() SlotIndex {
return t.genesisSlot
}

// GenesisUnixTime is the time (Unix in seconds) of the genesis.
func (t *TimeProvider) GenesisUnixTime() int64 {
return t.genesisUnixTime
Expand Down Expand Up @@ -85,55 +93,67 @@ func (t *TimeProvider) SlotsPerEpochExponent() uint8 {
func (t *TimeProvider) SlotFromTime(targetTime time.Time) SlotIndex {
elapsed := targetTime.Sub(t.genesisTime)
if elapsed < 0 {
return 0
return t.genesisSlot
}

return SlotIndex(int64(elapsed/time.Second)/t.slotDurationSeconds) + 1
return t.genesisSlot + SlotIndex(int64(elapsed/time.Second)/t.slotDurationSeconds) + 1
}

// SlotStartTime calculates the start time of the given slot.
func (t *TimeProvider) SlotStartTime(slot SlotIndex) time.Time {
if slot == 0 {
if slot <= t.genesisSlot {
return t.genesisTime.Add(-time.Nanosecond)
}

startUnix := t.genesisUnixTime + int64(slot-1)*t.slotDurationSeconds
startUnix := t.genesisUnixTime + int64(slot-t.genesisSlot-1)*t.slotDurationSeconds

return time.Unix(startUnix, 0)
}

// SlotEndTime returns the latest possible timestamp for a slot. Anything with higher timestamp will belong to the next slot.
func (t *TimeProvider) SlotEndTime(slot SlotIndex) time.Time {
if slot == 0 {
if slot <= t.genesisSlot {
return t.genesisTime.Add(-time.Nanosecond)
}

endUnix := t.genesisUnixTime + int64(slot)*t.slotDurationSeconds
endUnix := t.genesisUnixTime + int64(slot-t.genesisSlot)*t.slotDurationSeconds
// we subtract 1 nanosecond from the next slot to get the latest possible timestamp for slot i
return time.Unix(endUnix, 0).Add(-time.Nanosecond)
}

// EpochFromSlot calculates the EpochIndex from the given slot.
func (t *TimeProvider) EpochFromSlot(slot SlotIndex) EpochIndex {
return EpochIndex(slot >> t.slotsPerEpochExponent)
if slot <= t.genesisSlot {
return 0
}

return EpochIndex((slot - t.genesisSlot) >> t.slotsPerEpochExponent)
}

// EpochStart calculates the start slot of the given epoch.
func (t *TimeProvider) EpochStart(epoch EpochIndex) SlotIndex {
return SlotIndex(epoch << t.slotsPerEpochExponent)
return t.genesisSlot + SlotIndex(epoch<<t.slotsPerEpochExponent)
}

// EpochEnd calculates the end included slot of the given epoch.
func (t *TimeProvider) EpochEnd(epoch EpochIndex) SlotIndex {
return SlotIndex((epoch+1)<<t.slotsPerEpochExponent - 1)
return t.genesisSlot + SlotIndex((epoch+1)<<t.slotsPerEpochExponent-1)
}

// SlotsBeforeNextEpoch calculates the slots before the start of the next epoch.
func (t *TimeProvider) SlotsBeforeNextEpoch(slot SlotIndex) SlotIndex {
return t.EpochStart(t.EpochFromSlot(slot)+1) - slot
if slot < t.genesisSlot {
return 0
}

return t.genesisSlot + t.EpochStart(t.EpochFromSlot(slot)+1) - slot
}

// SlotsSinceEpochStart calculates the slots since the start of the epoch.
func (t *TimeProvider) SlotsSinceEpochStart(slot SlotIndex) SlotIndex {
return slot - t.EpochStart(t.EpochFromSlot(slot))
if slot < t.genesisSlot {
return 0
}

return t.genesisSlot + slot - t.EpochStart(t.EpochFromSlot(slot))
}
126 changes: 79 additions & 47 deletions timeprovider_test.go
Original file line number Diff line number Diff line change
@@ -1,110 +1,142 @@
package iotago_test

import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/require"

iotago "github.com/iotaledger/iota.go/v4"
"github.com/iotaledger/iota.go/v4/tpkg"
)

func TestTimeProvider(t *testing.T) {
testTimeProviderWithGenesisSlot(t, 0)
testTimeProviderWithGenesisSlot(t, 102848302)
testTimeProviderWithGenesisSlot(t, tpkg.RandSlot())
}

func testTimeProviderWithGenesisSlot(t *testing.T, genesisSlot iotago.SlotIndex) {
genesisUnixTime := int64(1630000000) // Replace with an appropriate Unix timestamp
genesisTime := time.Unix(genesisUnixTime, 0)
slotDurationSeconds := int64(10)
slotsPerEpochExponent := uint8(3) // 2^3 = 8 slots per epoch
slotsPerEpoch := 1 << slotsPerEpochExponent

tp := iotago.NewTimeProvider(genesisUnixTime, slotDurationSeconds, slotsPerEpochExponent)
tp := iotago.NewTimeProvider(genesisSlot, genesisUnixTime, slotDurationSeconds, slotsPerEpochExponent)

t.Run("Test Getters", func(t *testing.T) {
t.Run(fmt.Sprintf("Test Getters %d", genesisSlot), func(t *testing.T) {
require.EqualValues(t, genesisUnixTime, tp.GenesisUnixTime())
require.EqualValues(t, genesisTime, tp.GenesisTime())
require.EqualValues(t, slotDurationSeconds, tp.SlotDurationSeconds())
require.EqualValues(t, slotsPerEpoch, tp.EpochDurationSlots())
require.EqualValues(t, slotDurationSeconds*int64(slotsPerEpoch), tp.EpochDurationSeconds())
})

t.Run("Test SlotFromTime", func(t *testing.T) {
if genesisSlot > 0 {
t.Run(fmt.Sprintf("Test Below Genesis %d", genesisSlot), func(t *testing.T) {
firstEpoch := iotago.EpochIndex(0)
belowGenesisTime := genesisTime.Add(-time.Nanosecond)

require.EqualValues(t, genesisSlot, tp.SlotFromTime(belowGenesisTime))

require.EqualValues(t, belowGenesisTime, tp.SlotStartTime(genesisSlot-1))
require.EqualValues(t, belowGenesisTime, tp.SlotStartTime(0))

require.EqualValues(t, belowGenesisTime, tp.SlotEndTime(genesisSlot-1))
require.EqualValues(t, belowGenesisTime, tp.SlotEndTime(0))

require.EqualValues(t, firstEpoch, tp.EpochFromSlot(genesisSlot-1))
require.EqualValues(t, firstEpoch, tp.EpochFromSlot(0))

require.EqualValues(t, 0, tp.SlotsBeforeNextEpoch(genesisSlot-1))
require.EqualValues(t, 0, tp.SlotsBeforeNextEpoch(0))

require.EqualValues(t, 0, tp.SlotsSinceEpochStart(genesisSlot-1))
require.EqualValues(t, 0, tp.SlotsSinceEpochStart(0))
})
}

t.Run(fmt.Sprintf("Test SlotFromTime %d", genesisSlot), func(t *testing.T) {
slot0StartTime := genesisTime.Add(-time.Nanosecond)
require.EqualValues(t, 0, tp.SlotFromTime(slot0StartTime))
require.EqualValues(t, genesisSlot, tp.SlotFromTime(slot0StartTime))

slot1StartTime := genesisTime
require.EqualValues(t, 1, tp.SlotFromTime(slot1StartTime))
require.EqualValues(t, genesisSlot+1, tp.SlotFromTime(slot1StartTime))

slot2StartTime := genesisTime.Add(time.Duration(slotDurationSeconds) * time.Second)
require.EqualValues(t, 2, tp.SlotFromTime(slot2StartTime))
require.EqualValues(t, genesisSlot+2, tp.SlotFromTime(slot2StartTime))

arbitraryTime := genesisTime.Add(time.Duration(slotDurationSeconds*3)*time.Second + 5*time.Second + 300*time.Millisecond)
require.EqualValues(t, 4, tp.SlotFromTime(arbitraryTime))
require.EqualValues(t, genesisSlot+4, tp.SlotFromTime(arbitraryTime))
})

t.Run("Test SlotStartTime", func(t *testing.T) {
t.Run(fmt.Sprintf("Test SlotStartTime %d", genesisSlot), func(t *testing.T) {
slot0StartTime := genesisTime.Add(-time.Nanosecond)
require.EqualValues(t, slot0StartTime, tp.SlotStartTime(0))
require.EqualValues(t, slot0StartTime, tp.SlotStartTime(genesisSlot))

slot1StartTime := genesisTime
require.EqualValues(t, slot1StartTime, tp.SlotStartTime(1))
require.EqualValues(t, slot1StartTime, tp.SlotStartTime(genesisSlot+1))

slot2StartTime := genesisTime.Add(time.Duration(slotDurationSeconds) * time.Second)
require.EqualValues(t, slot2StartTime, tp.SlotStartTime(2))
require.EqualValues(t, slot2StartTime, tp.SlotStartTime(genesisSlot+2))

slot4000StartTime := genesisTime.Add(time.Duration(slotDurationSeconds*3999) * time.Second)
require.EqualValues(t, slot4000StartTime, tp.SlotStartTime(4000))
require.EqualValues(t, slot4000StartTime, tp.SlotStartTime(genesisSlot+4000))
})

t.Run("Test SlotEndTime", func(t *testing.T) {
t.Run(fmt.Sprintf("Test SlotEndTime %d", genesisSlot), func(t *testing.T) {
slot0EndTime := genesisTime.Add(-time.Nanosecond)
require.EqualValues(t, slot0EndTime, tp.SlotEndTime(0))
require.EqualValues(t, slot0EndTime, tp.SlotEndTime(genesisSlot))

slot1EndTime := genesisTime.Add(time.Duration(slotDurationSeconds) * time.Second).Add(-time.Nanosecond)
require.EqualValues(t, slot1EndTime, tp.SlotEndTime(1))
require.EqualValues(t, slot1EndTime, tp.SlotEndTime(genesisSlot+1))

slot2EndTime := genesisTime.Add(time.Duration(slotDurationSeconds*2) * time.Second).Add(-time.Nanosecond)
require.EqualValues(t, slot2EndTime, tp.SlotEndTime(2))
require.EqualValues(t, slot2EndTime, tp.SlotEndTime(genesisSlot+2))

slot4000EndTime := genesisTime.Add(time.Duration(slotDurationSeconds*4000) * time.Second).Add(-time.Nanosecond)
require.EqualValues(t, slot4000EndTime, tp.SlotEndTime(4000))
require.EqualValues(t, slot4000EndTime, tp.SlotEndTime(genesisSlot+4000))
})

t.Run("Test EpochFromSlot", func(t *testing.T) {
require.EqualValues(t, 0, tp.EpochFromSlot(0))
require.EqualValues(t, 0, tp.EpochFromSlot(7))
require.EqualValues(t, 1, tp.EpochFromSlot(8))
require.EqualValues(t, 1, tp.EpochFromSlot(15))
require.EqualValues(t, 4000, tp.EpochFromSlot(32000))
require.EqualValues(t, 4000, tp.EpochFromSlot(32007))
t.Run(fmt.Sprintf("Test EpochFromSlot %d", genesisSlot), func(t *testing.T) {
require.EqualValues(t, 0, tp.EpochFromSlot(genesisSlot))
require.EqualValues(t, 0, tp.EpochFromSlot(genesisSlot+7))
require.EqualValues(t, 1, tp.EpochFromSlot(genesisSlot+8))
require.EqualValues(t, 1, tp.EpochFromSlot(genesisSlot+15))
require.EqualValues(t, 4000, tp.EpochFromSlot(genesisSlot+32000))
require.EqualValues(t, 4000, tp.EpochFromSlot(genesisSlot+32007))
})

t.Run("Test EpochStart", func(t *testing.T) {
require.EqualValues(t, 0, tp.EpochStart(0))
require.EqualValues(t, 8, tp.EpochStart(1))
require.EqualValues(t, 16, tp.EpochStart(2))
require.EqualValues(t, 32000, tp.EpochStart(4000))
t.Run(fmt.Sprintf("Test EpochStart %d", genesisSlot), func(t *testing.T) {
require.EqualValues(t, genesisSlot, tp.EpochStart(0))
require.EqualValues(t, genesisSlot+8, tp.EpochStart(1))
require.EqualValues(t, genesisSlot+16, tp.EpochStart(2))
require.EqualValues(t, genesisSlot+32000, tp.EpochStart(4000))
})

t.Run("Test EpochEnd", func(t *testing.T) {
require.EqualValues(t, 7, tp.EpochEnd(0))
require.EqualValues(t, 15, tp.EpochEnd(1))
require.EqualValues(t, 23, tp.EpochEnd(2))
require.EqualValues(t, 32007, tp.EpochEnd(4000))
t.Run(fmt.Sprintf("Test EpochEnd %d", genesisSlot), func(t *testing.T) {
require.EqualValues(t, genesisSlot+7, tp.EpochEnd(0))
require.EqualValues(t, genesisSlot+15, tp.EpochEnd(1))
require.EqualValues(t, genesisSlot+23, tp.EpochEnd(2))
require.EqualValues(t, genesisSlot+32007, tp.EpochEnd(4000))
})

t.Run("Test SlotsBeforeNextEpoch", func(t *testing.T) {
require.EqualValues(t, 8, tp.SlotsBeforeNextEpoch(0))
require.EqualValues(t, 1, tp.SlotsBeforeNextEpoch(7))
require.EqualValues(t, 1, tp.SlotsBeforeNextEpoch(15))
require.EqualValues(t, 8, tp.SlotsBeforeNextEpoch(32000))
require.EqualValues(t, 1, tp.SlotsBeforeNextEpoch(32007))
t.Run(fmt.Sprintf("Test SlotsBeforeNextEpoch %d", genesisSlot), func(t *testing.T) {
require.EqualValues(t, genesisSlot+8, tp.SlotsBeforeNextEpoch(genesisSlot))
require.EqualValues(t, genesisSlot+1, tp.SlotsBeforeNextEpoch(genesisSlot+7))
require.EqualValues(t, genesisSlot+1, tp.SlotsBeforeNextEpoch(genesisSlot+15))
require.EqualValues(t, genesisSlot+8, tp.SlotsBeforeNextEpoch(genesisSlot+32000))
require.EqualValues(t, genesisSlot+1, tp.SlotsBeforeNextEpoch(genesisSlot+32007))
})

t.Run("Test SlotsSinceEpochStart", func(t *testing.T) {
require.EqualValues(t, 0, tp.SlotsSinceEpochStart(0))
require.EqualValues(t, 7, tp.SlotsSinceEpochStart(7))
require.EqualValues(t, 0, tp.SlotsSinceEpochStart(8))
require.EqualValues(t, 7, tp.SlotsSinceEpochStart(15))
require.EqualValues(t, 0, tp.SlotsSinceEpochStart(32000))
require.EqualValues(t, 7, tp.SlotsSinceEpochStart(32007))
t.Run(fmt.Sprintf("Test SlotsSinceEpochStart %d", genesisSlot), func(t *testing.T) {
require.EqualValues(t, genesisSlot, tp.SlotsSinceEpochStart(genesisSlot))
require.EqualValues(t, genesisSlot+7, tp.SlotsSinceEpochStart(genesisSlot+7))
require.EqualValues(t, genesisSlot, tp.SlotsSinceEpochStart(genesisSlot+8))
require.EqualValues(t, genesisSlot+7, tp.SlotsSinceEpochStart(genesisSlot+15))
require.EqualValues(t, genesisSlot+0, tp.SlotsSinceEpochStart(genesisSlot+32000))
require.EqualValues(t, genesisSlot+7, tp.SlotsSinceEpochStart(genesisSlot+32007))
})
}

0 comments on commit 578f582

Please sign in to comment.