Skip to content

Conversation

cam-schultz
Copy link
Contributor

Why this should be merged

Implements ProposerVM epochs as described in avalanche-foundation/ACPs#181

How this works

Adds a field PChainEpochHeight to proposervm blocks that remains fixed for the epoch's duration. This height is passed to the innerVM block, rather than the windowed P-Chain height.

How this was tested

Added unit test for the epoch calculation, but unit/integration tests for the rest of the changed are still TODO

Need to be documented in RELEASES.md?

Yes

Copy link

This PR has become stale because it has been open for 30 days with no activity. Adding the lifecycle/frozen label will cause this PR to ignore lifecycle events.

EtnaTime time.Time `json:"etnaTime"`
FortunaTime time.Time `json:"fortunaTime"`
GraniteTime time.Time `json:"graniteTime"`
GraniteEpochDuration time.Duration `json:"graniteEpochDuration"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we add this to Mainnet and Fuji too?

@@ -59,6 +59,9 @@ type Block interface {
buildChild(context.Context) (Block, error)

pChainHeight(context.Context) (uint64, error)
pChainEpochHeight(context.Context) (uint64, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the use of the context here? Isn't the implementation just memory access?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Block interface ends up getting used in postForkBlock and postForkOption. postForkOption needs to get the parent block of the "option", which requires the context.

@@ -79,6 +82,51 @@ func (p *postForkCommonComponents) Height() uint64 {
return p.innerBlk.Height()
}

// Calculates a block's P-Chain epoch height based on its ancestor's epoch membership
func (p *postForkCommonComponents) getPChainEpoch(ctx context.Context, parentID ids.ID) (uint64, uint64, time.Time, error) {
parent, err := p.vm.getBlock(ctx, parentID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if we call this on the genesis block?

@@ -163,9 +212,23 @@ func (p *postForkCommonComponents) Verify(
}

var contextPChainHeight uint64
if p.vm.Upgrades.IsEtnaActivated(childTimestamp) {
switch {
case p.vm.Upgrades.IsGraniteActivated(childTimestamp):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if the parent doesn't have granite activated but the child did? How should the system behave?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the epochStartTime.IsZero() case inside getPChainEpoch right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct. We've change the behaviour to actually check for epoch 0 instead of checking the starttime

switch {
case p.vm.Upgrades.IsGraniteActivated(childTimestamp):
pChainEpochHeight, _, _, err := p.getPChainEpoch(ctx, child.Parent())
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're checking the PchainEpochHeight is what it should be, what about the EpochNumber and EpochStartTime? shouldn't we check that too?

Copy link
Contributor

@yacovm yacovm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not done yet but left some comments in the meantime

const txAmount = 10 * units.Avax // Arbitrary amount to send and transfer

ginkgo.It("should advance the proposervm epoch according to the upgrade config epoch duration", func() {
// TODO: Skip this test if Granite is not activated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// TODO: Skip this test if Granite is not activated

Why? Can't we force its activation in the test?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just do upgrades.GraniteTime = upgrade.InitiallyActiveTime and it will make it activated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upgrades.GraniteTime gets set outside of the test. The current test suite runs all the tests once without granite activated, and once with them activated.

@@ -79,6 +82,59 @@ func (p *postForkCommonComponents) Height() uint64 {
return p.innerBlk.Height()
}

// Calculates a block's P-Chain epoch height based on its ancestor's epoch membership
func (p *postForkCommonComponents) getPChainEpoch(ctx context.Context, parentID ids.ID) (uint64, uint64, time.Time, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we:

  1. add the parent block as a parameter to this function instead of retrieving it inside the method
  2. move the logger to be a parameter to the method
  3. make this a function and not an instance method, so it can be easily tested
  4. make a unit test that tests this method directly

?

@@ -89,6 +145,7 @@ func (p *postForkCommonComponents) Height() uint64 {
// 7) [child]'s timestamp is within its proposer's window
// 8) [child] has a valid signature from its proposer
// 9) [child]'s inner block is valid
// 10) [child] has the expected P-Chain epoch height
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about child has the expected epoch number and epoch start time?

We need to verify every field we encode.

@@ -163,9 +212,23 @@ func (p *postForkCommonComponents) Verify(
}

var contextPChainHeight uint64
if p.vm.Upgrades.IsEtnaActivated(childTimestamp) {
switch {
case p.vm.Upgrades.IsGraniteActivated(childTimestamp):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the epochStartTime.IsZero() case inside getPChainEpoch right?

p.vm.ctx.Log.Error("unexpected build verification failure",
zap.String("reason", "failed to get P-Chain epoch height"),
zap.Stringer("parentID", child.Parent()),
zap.Error(err),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we need to fail fast and return an error here?

ParentID: parentID,
Timestamp: timestamp.Unix(),
PChainHeight: pChainHeight,
PChainEpochHeight: pChainEpochHeight,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new fields are being serialized even if we haven't activated granite yet (they'll be zero), right?

Since the validators don't all upgrade the software version at the same second - how would it work? how can a validator in the current avalanchego version deserialize a block built by a validator in the new avalanchego version?

zap.Error(err),
)
}
if childHeight := child.PChainEpochHeight(); pChainEpochHeight != childHeight {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we extract the ICM epoch attribute validation into a separate function and then just check everything we need there, and not in this function?

zap.String("method", "getEpoch"),
)

lastAccepted, err := p.vm.LastAccepted(r.Context())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this struct supposed to get accessed concurrently? I think we need some kind of synchronization if so.

zap.String("encoding", args.Encoding.String()),
)

block, err := p.vm.GetBlock(r.Context(), args.ProposerID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure VM access is thread safe

import (
"testing"
"time"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the tests are missing here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants