Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FLIP 204] Add TargetEndTime to FlowEpoch smart contract #395

Merged
merged 9 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 62 additions & 5 deletions contracts/epochs/FlowEpoch.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ pub contract FlowEpoch {
/// than the given deadline, it can safely transition to the next phase.
DKGPhase1FinalView: UInt64,
DKGPhase2FinalView: UInt64,
DKGPhase3FinalView: UInt64
DKGPhase3FinalView: UInt64,

/// The target end time for the upcoming epoch, specified in second-precision Unix time
targetEndTime: UInt64
)

/// The EpochCommit service event is emitted when we transition from the Epoch
Expand Down Expand Up @@ -226,7 +229,7 @@ pub contract FlowEpoch {
}
}

/// Metadata that is managed and can be changed by the Admin///
/// Metadata that is managed and can be changed by the Admin
pub struct Config {
/// The number of views in an entire epoch
pub(set) var numViewsInEpoch: UInt64
Expand All @@ -253,6 +256,34 @@ pub contract FlowEpoch {
}
}

/// Configuration for epoch timing.
/// Each epoch is assigned a target end time when it is setup (within the EpochSetup event).
/// The configuration defines a reference epoch counter and timestamp, which defines
/// all future target end times. If `targetEpochCounter` is an upcoming epoch, then
/// its target end time is given by:
///
/// targetEndTime = refTimestamp + duration * (targetEpochCounter-refCounter)
///
pub struct EpochTimingConfig {
/// The duration of each epoch, in seconds
pub let duration: UInt64
Copy link
Member

Choose a reason for hiding this comment

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

Will every epoch always have the same duration? I understand the config can change, but what if the duration changes, but we are using a reference epoch that is before the change? Would we just never do that?

Copy link
Member Author

@jordanschalm jordanschalm Nov 14, 2023

Choose a reason for hiding this comment

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

Right now, all epochs have the same duration (1 week on Mainnet). If/when we want to change the duration, then we would change the timing config.

The reference epoch needs to contain a real reference counter that has been used in the past. But the associated duration can be anything we want (it does not need to match the duration that the reference epoch actually took, when it occurred). So there isn't anything stopping us from using a reference epoch that is before the duration change, if we want to.

/// The counter of the reference epoch
pub let refCounter: UInt64
/// The end time of the reference epoch, specified in second-precision Unix time
pub let refTimestamp: UInt64

/// Compute target switchover time based on offset from reference counter/switchover.
pub fun getTargetEndTimeForEpoch(_ targetEpochCounter: UInt64): UInt64 {
return self.refTimestamp + self.duration * (targetEpochCounter-self.refCounter)
}

init(duration: UInt64, refCounter: UInt64, refTimestamp: UInt64) {
self.duration = duration
self.refCounter = refCounter
self.refTimestamp = refTimestamp
}
}

/// Holds the `FlowEpoch.Config` struct with the configurable metadata
access(contract) let configurableMetadata: Config

Expand Down Expand Up @@ -343,6 +374,14 @@ pub contract FlowEpoch {
FlowEpoch.configurableMetadata.numViewsInDKGPhase = newPhaseViews
}

pub fun updateEpochTimingConfig(_ newConfig: EpochTimingConfig) {
pre {
FlowEpoch.currentEpochCounter >= newConfig.refCounter: "Reference epoch must be before next epoch"
}
FlowEpoch.account.load<EpochTimingConfig>(from: /storage/flowEpochTimingConfig)
FlowEpoch.account.save(newConfig, to: /storage/flowEpochTimingConfig)
}

pub fun updateNumCollectorClusters(_ newNumClusters: UInt16) {
pre {
FlowEpoch.currentEpochPhase == EpochPhase.STAKINGAUCTION: "Can only update fields during the staking auction"
Expand Down Expand Up @@ -641,7 +680,7 @@ pub contract FlowEpoch {

let currentEpochMetadata = self.getEpochMetadata(self.currentEpochCounter)!

// Initialze the metadata for the next epoch
// Initialize the metadata for the next epoch
// QC and DKG metadata will be filled in later
let proposedEpochMetadata = EpochMetadata(counter: self.proposedEpochCounter(),
seed: randomSource,
Expand All @@ -653,6 +692,9 @@ pub contract FlowEpoch {
clusterQCs: [],
dkgKeys: [])

// Compute the target end time for the next epoch
let proposedTargetEndTime = self.getEpochTimingConfig().getTargetEndTimeForEpoch(self.proposedEpochCounter())

self.saveEpochMetadata(proposedEpochMetadata)

self.currentEpochPhase = EpochPhase.EPOCHSETUP
Expand All @@ -665,7 +707,8 @@ pub contract FlowEpoch {
randomSource: randomSource,
DKGPhase1FinalView: proposedEpochMetadata.startView + self.configurableMetadata.numViewsInStakingAuction + self.configurableMetadata.numViewsInDKGPhase - 1 as UInt64,
DKGPhase2FinalView: proposedEpochMetadata.startView + self.configurableMetadata.numViewsInStakingAuction + (2 as UInt64 * self.configurableMetadata.numViewsInDKGPhase) - 1 as UInt64,
DKGPhase3FinalView: proposedEpochMetadata.startView + self.configurableMetadata.numViewsInStakingAuction + (3 as UInt64 * self.configurableMetadata.numViewsInDKGPhase) - 1 as UInt64)
DKGPhase3FinalView: proposedEpochMetadata.startView + self.configurableMetadata.numViewsInStakingAuction + (3 as UInt64 * self.configurableMetadata.numViewsInDKGPhase) - 1 as UInt64,
targetEndTime: proposedTargetEndTime)
}

/// Ends the EpochSetup phase when the QC and DKG are completed
Expand Down Expand Up @@ -853,6 +896,13 @@ pub contract FlowEpoch {
return self.configurableMetadata
}

/// Returns the config for epoch timing, which can be configured by the admin.
/// Conceptually, this should be part of `Config`, however it was added later in an
/// upgrade which requires new fields be specified separately from existing structs.
pub fun getEpochTimingConfig(): EpochTimingConfig {
return self.account.copy<EpochTimingConfig>(from: /storage/flowEpochTimingConfig)!
}

/// The proposed Epoch counter is always the current counter plus 1
pub fun proposedEpochCounter(): UInt64 {
return self.currentEpochCounter + 1 as UInt64
Expand Down Expand Up @@ -894,7 +944,14 @@ pub contract FlowEpoch {
numViewsInDKGPhase: numViewsInDKGPhase,
numCollectorClusters: numCollectorClusters,
FLOWsupplyIncreasePercentage: FLOWsupplyIncreasePercentage)


// Set a reasonable default for the epoch timing config targeting 1 block per second
let defaultEpochTimingConfig = EpochTimingConfig(
duration: numViewsInEpoch,
refCounter: currentEpochCounter,
refTimestamp: UInt64(getCurrentBlock().timestamp))
FlowEpoch.account.save(defaultEpochTimingConfig, to: /storage/flowEpochTimingConfig)

self.currentEpochCounter = currentEpochCounter
self.currentEpochPhase = EpochPhase.STAKINGAUCTION
self.adminStoragePath = /storage/flowEpochAdmin
Expand Down
6 changes: 3 additions & 3 deletions lib/go/contracts/internal/assets/assets.go

Large diffs are not rendered by default.

37 changes: 29 additions & 8 deletions lib/go/templates/epoch_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
updateStakingViewsFilename = "epoch/admin/update_staking_views.cdc"
updateDKGViewsFilename = "epoch/admin/update_dkg_phase_views.cdc"
updateEpochConfigFilename = "epoch/admin/update_epoch_config.cdc"
updateEpochTimingConfigFilename = "epoch/admin/update_epoch_timing_config.cdc"
updateNumClustersFilename = "epoch/admin/update_clusters.cdc"
updateRewardPercentageFilename = "epoch/admin/update_reward.cdc"
advanceViewFilename = "epoch/admin/advance_view.cdc"
Expand All @@ -27,14 +28,16 @@ const (
epochRegisterDKGParticipantFilename = "epoch/node/register_dkg_participant.cdc"

// Scripts
getCurrentEpochCounterFilename = "epoch/scripts/get_epoch_counter.cdc"
getProposedEpochCounterFilename = "epoch/scripts/get_proposed_counter.cdc"
getEpochMetadataFilename = "epoch/scripts/get_epoch_metadata.cdc"
getConfigMetadataFilename = "epoch/scripts/get_config_metadata.cdc"
getEpochPhaseFilename = "epoch/scripts/get_epoch_phase.cdc"
getCurrentViewFilename = "epoch/scripts/get_current_view.cdc"
getFlowTotalSupplyFilename = "flowToken/scripts/get_supply.cdc"
getFlowBonusTokensFilename = "epoch/scripts/get_bonus_tokens.cdc"
getCurrentEpochCounterFilename = "epoch/scripts/get_epoch_counter.cdc"
getProposedEpochCounterFilename = "epoch/scripts/get_proposed_counter.cdc"
getEpochMetadataFilename = "epoch/scripts/get_epoch_metadata.cdc"
getConfigMetadataFilename = "epoch/scripts/get_config_metadata.cdc"
getTimingConfigFilename = "epoch/scripts/get_epoch_timing_config.cdc"
getTargetEndTimeForEpochFilename = "epoch/scripts/get_target_end_time_for_epoch.cdc"
getEpochPhaseFilename = "epoch/scripts/get_epoch_phase.cdc"
getCurrentViewFilename = "epoch/scripts/get_current_view.cdc"
getFlowTotalSupplyFilename = "flowToken/scripts/get_supply.cdc"
getFlowBonusTokensFilename = "epoch/scripts/get_bonus_tokens.cdc"

// test scripts
getRandomizeFilename = "epoch/scripts/get_randomize.cdc"
Expand Down Expand Up @@ -81,6 +84,12 @@ func GenerateUpdateEpochConfigScript(env Environment) []byte {
return []byte(ReplaceAddresses(code, env))
}

func GenerateUpdateEpochTimingConfigScript(env Environment) []byte {
code := assets.MustAssetString(updateEpochTimingConfigFilename)

return []byte(ReplaceAddresses(code, env))
}

func GenerateUpdateNumClustersScript(env Environment) []byte {
code := assets.MustAssetString(updateNumClustersFilename)

Expand Down Expand Up @@ -175,6 +184,18 @@ func GenerateGetEpochConfigMetadataScript(env Environment) []byte {
return []byte(ReplaceAddresses(code, env))
}

func GenerateGetEpochTimingConfigScript(env Environment) []byte {
code := assets.MustAssetString(getTimingConfigFilename)

return []byte(ReplaceAddresses(code, env))
}

func GenerateGetTargetEndTimeForEpochScript(env Environment) []byte {
code := assets.MustAssetString(getTargetEndTimeForEpochFilename)

return []byte(ReplaceAddresses(code, env))
}

func GenerateGetEpochPhaseScript(env Environment) []byte {
code := assets.MustAssetString(getEpochPhaseFilename)

Expand Down
Loading
Loading