From f7bca021550daae276f0c0cc84146a0229ac1aa9 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Tue, 25 Feb 2025 17:24:18 -0600 Subject: [PATCH] proposervm epoch poc --- upgrade/upgrade.go | 4 +- vms/proposervm/block.go | 69 +++++++++++++++++++++++++++++- vms/proposervm/block/block.go | 16 ++++--- vms/proposervm/block/build.go | 24 ++++++----- vms/proposervm/mocks_test.go | 15 +++++++ vms/proposervm/post_fork_block.go | 4 ++ vms/proposervm/post_fork_option.go | 8 ++++ vms/proposervm/pre_fork_block.go | 5 +++ vms/proposervm/vm.go | 9 ++++ vms/proposervm/vm_test.go | 54 +++++++++++++++++++++++ 10 files changed, 190 insertions(+), 18 deletions(-) diff --git a/upgrade/upgrade.go b/upgrade/upgrade.go index 22818f0add41..c04187a96aa9 100644 --- a/upgrade/upgrade.go +++ b/upgrade/upgrade.go @@ -73,7 +73,8 @@ var ( CortinaXChainStopVertexID: ids.Empty, DurangoTime: InitiallyActiveTime, EtnaTime: InitiallyActiveTime, - FUpgradeTime: UnscheduledActivationTime, + FUpgradeTime: InitiallyActiveTime, + FUpgradeEpochDuration: 30, } ErrInvalidUpgradeTimes = errors.New("invalid upgrade configuration") @@ -95,6 +96,7 @@ type Config struct { DurangoTime time.Time `json:"durangoTime"` EtnaTime time.Time `json:"etnaTime"` FUpgradeTime time.Time `json:"fUpgradeTime"` + FUpgradeEpochDuration uint64 `json:"fUpgradeEpochDuration"` } func (c *Config) Validate() error { diff --git a/vms/proposervm/block.go b/vms/proposervm/block.go index 98f6e8608585..8f9e76f6f3f1 100644 --- a/vms/proposervm/block.go +++ b/vms/proposervm/block.go @@ -59,6 +59,7 @@ type Block interface { buildChild(context.Context) (Block, error) pChainHeight(context.Context) (uint64, error) + pChainEpochHeight(context.Context) (uint64, error) } type PostForkBlock interface { @@ -163,7 +164,9 @@ func (p *postForkCommonComponents) Verify( } var contextPChainHeight uint64 - if p.vm.Upgrades.IsEtnaActivated(childTimestamp) { + if p.vm.Upgrades.IsFUpgradeActivated(childTimestamp) { + contextPChainHeight = child.PChainEpochHeight() + } else if p.vm.Upgrades.IsEtnaActivated(childTimestamp) { contextPChainHeight = childPChainHeight } else { contextPChainHeight = parentPChainHeight @@ -203,6 +206,64 @@ func (p *postForkCommonComponents) buildChild( return nil, err } + var pChainEpochHeight, pChainEpochHeightParent uint64 + if p.vm.Upgrades.IsFUpgradeActivated(newTimestamp) { + parent, err := p.vm.getBlock(ctx, parentID) + if err != nil { + p.vm.ctx.Log.Error("unexpected build block failure", + zap.String("reason", "failed to fetch parent block"), + zap.Stringer("parentID", parentID), + zap.Error(err), + ) + return nil, err + } + parent2, err := p.vm.getBlock(ctx, parent.Parent()) + if err != nil { + p.vm.ctx.Log.Error("unexpected build block failure", + zap.String("reason", "failed to fetch second parent block"), + zap.Stringer("parentID", parentID), + zap.Error(err), + ) + return nil, err + } + + if p.vm.GetEpoch(parent.Timestamp()) == p.vm.GetEpoch(parent2.Timestamp()) { + pChainEpochHeightParent, err = parent.pChainEpochHeight(ctx) + if err != nil { + p.vm.ctx.Log.Error("unexpected build block failure", + zap.String("reason", "failed to get parent epoch height"), + zap.Stringer("parentID", parentID), + zap.Error(err), + ) + return nil, err + } + pChainEpochHeight = pChainEpochHeightParent + + p.vm.ctx.Log.Info( + "continuing epoch", + zap.Uint64("pChainHeight", pChainHeight), + zap.Uint64("pChainEpochHeight", pChainEpochHeight), + zap.Uint64("epoch", p.vm.GetEpoch(newTimestamp)), + ) + } else { + pChainEpochHeight, err = parent.pChainHeight(ctx) + if err != nil { + p.vm.ctx.Log.Error("unexpected build block failure", + zap.String("reason", "failed to get parent p-chain height"), + zap.Stringer("parentID", parentID), + zap.Error(err), + ) + return nil, err + } + p.vm.ctx.Log.Info( + "epoch has advanced", + zap.Uint64("pChainHeight", pChainHeight), + zap.Uint64("pChainEpochHeight", pChainEpochHeight), + zap.Uint64("epoch", p.vm.GetEpoch(newTimestamp)), + ) + } + } + var shouldBuildSignedBlock bool if p.vm.Upgrades.IsDurangoActivated(parentTimestamp) { shouldBuildSignedBlock, err = p.shouldBuildSignedBlockPostDurango( @@ -226,7 +287,9 @@ func (p *postForkCommonComponents) buildChild( } var contextPChainHeight uint64 - if p.vm.Upgrades.IsEtnaActivated(newTimestamp) { + if p.vm.Upgrades.IsFUpgradeActivated(newTimestamp) { + contextPChainHeight = pChainEpochHeight + } else if p.vm.Upgrades.IsEtnaActivated(newTimestamp) { contextPChainHeight = pChainHeight } else { contextPChainHeight = parentPChainHeight @@ -251,6 +314,7 @@ func (p *postForkCommonComponents) buildChild( parentID, newTimestamp, pChainHeight, + pChainEpochHeight, p.vm.StakingCertLeaf, innerBlock.Bytes(), p.vm.ctx.ChainID, @@ -261,6 +325,7 @@ func (p *postForkCommonComponents) buildChild( parentID, newTimestamp, pChainHeight, + pChainEpochHeight, innerBlock.Bytes(), ) } diff --git a/vms/proposervm/block/block.go b/vms/proposervm/block/block.go index 68da910e1dbd..72c15ba29592 100644 --- a/vms/proposervm/block/block.go +++ b/vms/proposervm/block/block.go @@ -35,6 +35,7 @@ type SignedBlock interface { Block PChainHeight() uint64 + PChainEpochHeight() uint64 Timestamp() time.Time // Proposer returns the ID of the node that proposed this block. If no node @@ -43,11 +44,12 @@ type SignedBlock interface { } type statelessUnsignedBlock struct { - ParentID ids.ID `serialize:"true"` - Timestamp int64 `serialize:"true"` - PChainHeight uint64 `serialize:"true"` - Certificate []byte `serialize:"true"` - Block []byte `serialize:"true"` + ParentID ids.ID `serialize:"true"` + Timestamp int64 `serialize:"true"` + PChainHeight uint64 `serialize:"true"` + PChainEpochHeight uint64 `serialize:"true"` + Certificate []byte `serialize:"true"` + Block []byte `serialize:"true"` } type statelessBlock struct { @@ -127,6 +129,10 @@ func (b *statelessBlock) PChainHeight() uint64 { return b.StatelessBlock.PChainHeight } +func (b *statelessBlock) PChainEpochHeight() uint64 { + return b.StatelessBlock.PChainEpochHeight +} + func (b *statelessBlock) Timestamp() time.Time { return b.timestamp } diff --git a/vms/proposervm/block/build.go b/vms/proposervm/block/build.go index 228ab97604da..8fb794357feb 100644 --- a/vms/proposervm/block/build.go +++ b/vms/proposervm/block/build.go @@ -18,15 +18,17 @@ func BuildUnsigned( parentID ids.ID, timestamp time.Time, pChainHeight uint64, + pChainEpochHeight uint64, blockBytes []byte, ) (SignedBlock, error) { var block SignedBlock = &statelessBlock{ StatelessBlock: statelessUnsignedBlock{ - ParentID: parentID, - Timestamp: timestamp.Unix(), - PChainHeight: pChainHeight, - Certificate: nil, - Block: blockBytes, + ParentID: parentID, + Timestamp: timestamp.Unix(), + PChainHeight: pChainHeight, + PChainEpochHeight: pChainEpochHeight, + Certificate: nil, + Block: blockBytes, }, timestamp: timestamp, } @@ -43,6 +45,7 @@ func Build( parentID ids.ID, timestamp time.Time, pChainHeight uint64, + pChainEpochHeight uint64, cert *staking.Certificate, blockBytes []byte, chainID ids.ID, @@ -50,11 +53,12 @@ func Build( ) (SignedBlock, error) { block := &statelessBlock{ StatelessBlock: statelessUnsignedBlock{ - ParentID: parentID, - Timestamp: timestamp.Unix(), - PChainHeight: pChainHeight, - Certificate: cert.Raw, - Block: blockBytes, + ParentID: parentID, + Timestamp: timestamp.Unix(), + PChainHeight: pChainHeight, + PChainEpochHeight: pChainEpochHeight, + Certificate: cert.Raw, + Block: blockBytes, }, timestamp: timestamp, cert: cert, diff --git a/vms/proposervm/mocks_test.go b/vms/proposervm/mocks_test.go index 52633dfbda40..0cd3ba336779 100644 --- a/vms/proposervm/mocks_test.go +++ b/vms/proposervm/mocks_test.go @@ -227,6 +227,21 @@ func (mr *MockPostForkBlockMockRecorder) getStatelessBlk() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getStatelessBlk", reflect.TypeOf((*MockPostForkBlock)(nil).getStatelessBlk)) } +// pChainEpochHeight mocks base method. +func (m *MockPostForkBlock) pChainEpochHeight(arg0 context.Context) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "pChainEpochHeight", arg0) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// pChainEpochHeight indicates an expected call of pChainEpochHeight. +func (mr *MockPostForkBlockMockRecorder) pChainEpochHeight(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "pChainEpochHeight", reflect.TypeOf((*MockPostForkBlock)(nil).pChainEpochHeight), arg0) +} + // pChainHeight mocks base method. func (m *MockPostForkBlock) pChainHeight(arg0 context.Context) (uint64, error) { m.ctrl.T.Helper() diff --git a/vms/proposervm/post_fork_block.go b/vms/proposervm/post_fork_block.go index c815144fd306..86053817bd5b 100644 --- a/vms/proposervm/post_fork_block.go +++ b/vms/proposervm/post_fork_block.go @@ -157,6 +157,10 @@ func (b *postForkBlock) pChainHeight(context.Context) (uint64, error) { return b.PChainHeight(), nil } +func (b *postForkBlock) pChainEpochHeight(context.Context) (uint64, error) { + return b.PChainEpochHeight(), nil +} + func (b *postForkBlock) getStatelessBlk() block.Block { return b.SignedBlock } diff --git a/vms/proposervm/post_fork_option.go b/vms/proposervm/post_fork_option.go index 8be839049f93..93b06c5eb58b 100644 --- a/vms/proposervm/post_fork_option.go +++ b/vms/proposervm/post_fork_option.go @@ -122,6 +122,14 @@ func (b *postForkOption) pChainHeight(ctx context.Context) (uint64, error) { return parent.pChainHeight(ctx) } +func (b *postForkOption) pChainEpochHeight(ctx context.Context) (uint64, error) { + parent, err := b.vm.getBlock(ctx, b.ParentID()) + if err != nil { + return 0, err + } + return parent.pChainEpochHeight(ctx) +} + func (b *postForkOption) getStatelessBlk() block.Block { return b.Block } diff --git a/vms/proposervm/pre_fork_block.go b/vms/proposervm/pre_fork_block.go index 72f2a5a46a26..f82e1186b8ec 100644 --- a/vms/proposervm/pre_fork_block.go +++ b/vms/proposervm/pre_fork_block.go @@ -211,6 +211,7 @@ func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) { parentID, newTimestamp, pChainHeight, + pChainHeight, innerBlock.Bytes(), ) if err != nil { @@ -238,3 +239,7 @@ func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) { func (*preForkBlock) pChainHeight(context.Context) (uint64, error) { return 0, nil } + +func (*preForkBlock) pChainEpochHeight(context.Context) (uint64, error) { + return 0, nil +} diff --git a/vms/proposervm/vm.go b/vms/proposervm/vm.go index d0c21971c80d..b2aea19e807b 100644 --- a/vms/proposervm/vm.go +++ b/vms/proposervm/vm.go @@ -107,6 +107,15 @@ type VM struct { acceptedBlocksSlotHistogram prometheus.Histogram } +// Epochs are numbered starting from 0. +// The 0th epoch is defined as ending at FUpgradeTime + FUpgradeEpochDuration +func (vm *VM) GetEpoch(timestamp time.Time) uint64 { + if timestamp.Before(vm.Upgrades.FUpgradeTime) { + return 0 + } + return uint64(timestamp.Sub(vm.Upgrades.FUpgradeTime).Seconds()) / vm.Upgrades.FUpgradeEpochDuration +} + // New performs best when [minBlkDelay] is whole seconds. This is because block // timestamps are only specific to the second. func New( diff --git a/vms/proposervm/vm_test.go b/vms/proposervm/vm_test.go index bbd41939516a..8c7f4e28b646 100644 --- a/vms/proposervm/vm_test.go +++ b/vms/proposervm/vm_test.go @@ -2530,3 +2530,57 @@ func TestLocalParse(t *testing.T) { }) } } + +func TestGetEpoch(t *testing.T) { + t0 := time.Now() + vm := New( + &fullVM{}, + Config{ + Upgrades: upgrade.Config{ + FUpgradeTime: t0, + FUpgradeEpochDuration: 10, + }, + }, + ) + + testCases := []struct { + timestamp time.Time + epoch uint64 + }{ + { + timestamp: t0.Add(-10 * time.Second), + epoch: 0, + }, + { + timestamp: t0.Add(-1 * time.Second), + epoch: 0, + }, + { + timestamp: t0, + epoch: 0, + }, + { + timestamp: t0.Add(1 * time.Second), + epoch: 0, + }, + { + timestamp: t0.Add(9 * time.Second), + epoch: 0, + }, + { + timestamp: t0.Add(10 * time.Second), + epoch: 1, + }, + { + timestamp: t0.Add(11 * time.Second), + epoch: 1, + }, + { + timestamp: t0.Add(20 * time.Second), + epoch: 2, + }, + } + for i := range testCases { + require.Equal(t, vm.GetEpoch(testCases[i].timestamp), testCases[i].epoch) + } +}