Skip to content

Commit 5e2dd4b

Browse files
committed
don't process scheduled transactions during epoch transitions
1 parent ef14bda commit 5e2dd4b

14 files changed

+142
-8
lines changed

contracts/FlowTransactionScheduler.cdc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import "FlowToken"
33
import "FlowFees"
44
import "FlowStorageFees"
55
import "ViewResolver"
6+
import "FlowEpoch"
67

78
/// FlowTransactionScheduler enables smart contracts to schedule autonomous execution in the future.
89
///
@@ -1284,6 +1285,14 @@ access(all) contract FlowTransactionScheduler {
12841285
return
12851286
}
12861287

1288+
// Skip processing if the epoch is in one of the phase transitions
1289+
// or expensive operations
1290+
// We don't skip if it is running in a test environment,
1291+
// so we check automaticRewardsEnabled() because it is only true on testnet and mainnet.
1292+
if FlowEpoch.isPhaseTransition() && FlowEpoch.automaticRewardsEnabled() {
1293+
return
1294+
}
1295+
12871296
self.removeExecutedTransactions(currentTimestamp)
12881297

12891298
let pendingTransactions = self.pendingQueue()

contracts/epochs/FlowEpoch.cdc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,41 @@ access(all) contract FlowEpoch {
12961296
?? 0.0
12971297
}
12981298

1299+
/// Checks if the current epoch is in a phase transition
1300+
/// so that other system contracts can skip computation-heavy operations
1301+
/// during the phase transition.
1302+
access(all) fun isPhaseTransition(): Bool {
1303+
switch self.currentEpochPhase {
1304+
case EpochPhase.STAKINGAUCTION:
1305+
if let previousEpochMetadata = self.getEpochMetadata(FlowEpoch.currentEpochCounter.saturatingSubtract(1)) {
1306+
// Paying rewards for the previous epoch
1307+
if self.currentEpochCounter > 0 && !previousEpochMetadata.rewardsPaid {
1308+
return true
1309+
}
1310+
}
1311+
let currentBlock = getCurrentBlock()
1312+
let currentEpochMetadata = self.getEpochMetadata(self.currentEpochCounter)!
1313+
// Staking auction is ending
1314+
if currentBlock.view >= currentEpochMetadata.stakingEndView {
1315+
return true
1316+
}
1317+
case EpochPhase.EPOCHSETUP:
1318+
// QC and DKG are completed and will be cleaned up
1319+
if FlowClusterQC.votingCompleted() && (FlowDKG.dkgCompleted() != nil) {
1320+
return true
1321+
}
1322+
case EpochPhase.EPOCHCOMMIT:
1323+
let currentBlock = getCurrentBlock()
1324+
let currentEpochMetadata = FlowEpoch.getEpochMetadata(FlowEpoch.currentEpochCounter)!
1325+
// Epoch is ending
1326+
if currentBlock.view >= currentEpochMetadata.endView {
1327+
return true
1328+
}
1329+
}
1330+
1331+
return false
1332+
}
1333+
12991334
init (currentEpochCounter: UInt64,
13001335
numViewsInEpoch: UInt64,
13011336
numViewsInStakingAuction: UInt64,

flow.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"aliases": {
4646
"emulator": "f8d6e0586b0a20c7",
4747
"mainnet": "8624b52f9ddcd04a",
48-
"testing": "0000000000000007",
48+
"testing": "0000000000000001",
4949
"testnet": "9eca2b38b18b5dfe"
5050
}
5151
},

lib/go/contracts/internal/assets/assets.go

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/go/templates/epoch_templates.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const (
3939
getCurrentViewFilename = "epoch/scripts/get_current_view.cdc"
4040
getFlowTotalSupplyFilename = "flowToken/scripts/get_supply.cdc"
4141
getFlowBonusTokensFilename = "epoch/scripts/get_bonus_tokens.cdc"
42+
isPhaseTransitionFilename = "epoch/scripts/is_phase_transition.cdc"
4243

4344
// test scripts
4445
getRandomizeFilename = "epoch/scripts/get_randomize.cdc"
@@ -238,3 +239,9 @@ func GenerateGetBonusTokensScript(env Environment) []byte {
238239

239240
return []byte(ReplaceAddresses(code, env))
240241
}
242+
243+
func GenerateIsPhaseTransitionScript(env Environment) []byte {
244+
code := assets.MustAssetString(isPhaseTransitionFilename)
245+
246+
return []byte(ReplaceAddresses(code, env))
247+
}

lib/go/templates/internal/assets/assets.go

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/go/test/flow_epoch_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ func TestEpochDeployment(t *testing.T) {
8989
clusterQCs: nil,
9090
dkgKeys: nil})
9191

92+
// Should not be in a phase transition since the epoch is just starting
93+
result := executeScriptAndCheck(t, b, templates.GenerateIsPhaseTransitionScript(env), nil)
94+
assertEqual(t, cadence.NewBool(false), result)
9295
}
9396

9497
func TestEpochClusters(t *testing.T) {
@@ -586,6 +589,10 @@ func TestEpochAdvance(t *testing.T) {
586589
// Advance to epoch Setup and make sure that the epoch cannot be ended
587590
advanceView(t, b, env, idTableAddress, IDTableSigner, 1, "EPOCHSETUP", false)
588591

592+
// Should not be in a phase transition since we already advanced to epoch setup
593+
result := executeScriptAndCheck(t, b, templates.GenerateIsPhaseTransitionScript(env), nil)
594+
assertEqual(t, cadence.NewBool(false), result)
595+
589596
verifyConfigMetadata(t, b, env,
590597
ConfigMetadata{
591598
currentEpochCounter: startEpochCounter,
@@ -638,7 +645,7 @@ func TestEpochAdvance(t *testing.T) {
638645
})
639646

640647
// QC Contract Checks
641-
result := executeScriptAndCheck(t, b, templates.GenerateGetClusterWeightScript(env), [][]byte{jsoncdc.MustEncode(cadence.UInt16(uint16(0)))})
648+
result = executeScriptAndCheck(t, b, templates.GenerateGetClusterWeightScript(env), [][]byte{jsoncdc.MustEncode(cadence.UInt16(uint16(0)))})
642649
assert.Equal(t, cadence.NewUInt64(100), result)
643650

644651
result = executeScriptAndCheck(t, b, templates.GenerateGetNodeWeightScript(env), [][]byte{jsoncdc.MustEncode(cadence.UInt16(uint16(1))), jsoncdc.MustEncode(cadence.String(ids[0]))})
@@ -864,6 +871,10 @@ func TestEpochQCDKG(t *testing.T) {
864871
false,
865872
)
866873

874+
// Should be in a phase transition since we haven't advanced to epoch setup and view is greater than the staking end view (50)
875+
result := executeScriptAndCheck(t, b, templates.GenerateIsPhaseTransitionScript(env), nil)
876+
assertEqual(t, cadence.NewBool(true), result)
877+
867878
// Advance to epoch Setup and make sure that the epoch cannot be ended
868879
advanceView(t, b, env, idTableAddress, IDTableSigner, 1, "EPOCHSETUP", false)
869880

@@ -1001,6 +1012,10 @@ func TestEpochQCDKG(t *testing.T) {
10011012
// Advance to epoch commit
10021013
advanceView(t, b, env, idTableAddress, IDTableSigner, 1, "EPOCHCOMMIT", false)
10031014

1015+
// Should be in a phase transition since we advanced to epoch commit and view is greater than the end view (70)
1016+
result = executeScriptAndCheck(t, b, templates.GenerateIsPhaseTransitionScript(env), nil)
1017+
assertEqual(t, cadence.NewBool(true), result)
1018+
10041019
verifyConfigMetadata(t, b, env,
10051020
ConfigMetadata{
10061021
currentEpochCounter: startEpochCounter,
@@ -1087,6 +1102,10 @@ func TestEpochQCDKG(t *testing.T) {
10871102
rewards: "6571204.6775",
10881103
})
10891104

1105+
// Should be in a phase transition since we just ended the epoch but haven't paid rewards
1106+
result = executeScriptAndCheck(t, b, templates.GenerateIsPhaseTransitionScript(env), nil)
1107+
assertEqual(t, cadence.NewBool(true), result)
1108+
10901109
tx = createTxWithTemplateAndAuthorizer(b, templates.GenerateEpochPayRewardsScript(env), idTableAddress)
10911110

10921111
signAndSubmit(

tests/scheduled_transaction_test_helpers.cdc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,27 @@ access(all) fun upgradeSchedulerUtilsContract() {
499499
Test.expect(upgradeResult, Test.beSucceeded())
500500
}
501501

502+
access(all) fun upgradeEpochContract() {
503+
var epochCode = Test.readFile("../contracts/epochs/FlowEpoch.cdc")
504+
epochCode = epochCode.replaceAll(of: "\"FungibleToken\"", with: "FungibleToken from 0x0000000000000002")
505+
epochCode = epochCode.replaceAll(of: "\"FlowToken\"", with: "FlowToken from 0x0000000000000003")
506+
epochCode = epochCode.replaceAll(of: "\"FlowFees\"", with: "FlowFees from 0x0000000000000004")
507+
epochCode = epochCode.replaceAll(of: "\"FlowIDTableStaking\"", with: "FlowIDTableStaking from 0x0000000000000001")
508+
epochCode = epochCode.replaceAll(of: "\"FlowClusterQC\"", with: "FlowClusterQC from 0x0000000000000001")
509+
epochCode = epochCode.replaceAll(of: "\"FlowDKG\"", with: "FlowDKG from 0x0000000000000001")
510+
511+
var upgradeTx = Test.Transaction(
512+
code: Test.readFile("./transactions/upgrade_contract.cdc"),
513+
authorizers: [serviceAccount.address],
514+
signers: [serviceAccount],
515+
arguments: ["FlowEpoch", epochCode],
516+
)
517+
var upgradeResult = Test.executeTransaction(
518+
upgradeTx,
519+
)
520+
Test.expect(upgradeResult, Test.beSucceeded())
521+
}
522+
502523
access(all) fun getTimestamp(): UFix64 {
503524
var timestamp = _executeScript(
504525
"./scripts/get_timestamp.cdc",

tests/transactionScheduler_events_test.cdc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ access(all) var accountBalanceBefore: UFix64 = 0.0
1717
access(all)
1818
fun setup() {
1919

20+
// upgrade the FlowEpoch contract to the latest version
21+
upgradeEpochContract()
22+
2023
var err = Test.deployContract(
2124
name: "FlowTransactionScheduler",
2225
path: "../contracts/FlowTransactionScheduler.cdc",

tests/transactionScheduler_manager_test.cdc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ access(all) var timeInFuture: UFix64 = 0.0
1717
access(all)
1818
fun setup() {
1919

20+
// upgrade the FlowEpoch contract to the latest version
21+
upgradeEpochContract()
22+
2023
var err = Test.deployContract(
2124
name: "FlowTransactionScheduler",
2225
path: "../contracts/FlowTransactionScheduler.cdc",

0 commit comments

Comments
 (0)