Skip to content

FEATURE: Callback scheduling #489

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

Open
wants to merge 68 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
3df0e1d
add safe scheduler contract
devbugging Jun 25, 2025
6dd02f7
adding tests for estimate
devbugging Jun 25, 2025
618c07b
remove some tests
devbugging Jun 25, 2025
352c6c6
fix test issues
devbugging Jun 25, 2025
a31805e
add estiamted callback error
devbugging Jun 25, 2025
2e141a4
update tests to check estiamte error
devbugging Jun 25, 2025
dce16ac
prune statuses
devbugging Jun 25, 2025
a3462c9
add todo
devbugging Jun 25, 2025
556e3bb
add schedule tests
devbugging Jun 27, 2025
3b38e5c
clean up test
devbugging Jun 30, 2025
b352a51
pr review feedback
devbugging Jun 30, 2025
8fc371f
todo tests
devbugging Jun 30, 2025
fd9db06
address other PR comments
joshuahannan Jul 3, 2025
ab6b82d
fix priority enum, add fees deposit, and revert pre-conditions change
joshuahannan Jul 7, 2025
a6db6b4
make ci
joshuahannan Jul 7, 2025
8c87d0a
rearrange error messages in estimate and update tests
joshuahannan Jul 8, 2025
81c74f4
change EstimatedCallback to non-optional
joshuahannan Jul 8, 2025
e343b96
move transaction and update test
joshuahannan Jul 8, 2025
898a583
more schedule tests and scripts
joshuahannan Jul 9, 2025
8bff865
revert setStatus
joshuahannan Jul 9, 2025
7ba2096
update error handling for low priority callbacks
joshuahannan Jul 9, 2025
49b3a1d
add comments and update status constants in tests
joshuahannan Jul 9, 2025
b8e2fce
add first draft of go package for callback scheduling
joshuahannan Jul 9, 2025
d4a3e56
update pkg versions so tests pass
joshuahannan Jul 9, 2025
171a103
add schedule utility function
joshuahannan Jul 9, 2025
763b332
add support for adding low priority callbacks to slots during processing
joshuahannan Jul 10, 2025
b17ac2e
add additional event arguments
joshuahannan Jul 10, 2025
274a4fe
fix timestamp and slotqueue naming
joshuahannan Jul 11, 2025
cf59ea0
Merge branch 'master' into feature/callback-scheduling
janezpodhostnik Jul 17, 2025
d4a1f36
Fix initialization issue with low priority slot (#496)
devbugging Jul 28, 2025
6f5e1dd
add cancel logic and tests
joshuahannan Jul 14, 2025
00a51dc
first draft of priority process ordering
joshuahannan Jul 22, 2025
d0868d3
initialization of effort
joshuahannan Jul 28, 2025
f626531
add cleanup for failed transactions, reorganize code, and add tests
joshuahannan Jul 29, 2025
2a039b3
remove mark failed callback
joshuahannan Jul 29, 2025
ce36e22
add calculation for storage fees
joshuahannan Jul 30, 2025
07ebd78
add underflow check
joshuahannan Jul 31, 2025
6ab2c6b
don't clean up failed callbacks
joshuahannan Jul 31, 2025
291fa9a
add ability for owner to configure metadata
joshuahannan Aug 1, 2025
e280f27
adds underflow checks
joshuahannan Aug 1, 2025
5c2156a
use saturating subtract
joshuahannan Aug 6, 2025
fd2a5d0
make assets
joshuahannan Aug 6, 2025
9a74f0f
add more event parameters and tests
joshuahannan Aug 1, 2025
4b3f275
rename entitlements and events and edit some comments
joshuahannan Aug 1, 2025
f776b49
address more PR comments
joshuahannan Aug 5, 2025
042cf6a
return 0 for size of some primitive types
joshuahannan Aug 6, 2025
9ceefaa
rename config metadata to config details
joshuahannan Aug 7, 2025
93c7f82
sanitize timestamp
joshuahannan Aug 5, 2025
00118c4
add 0.0 multiplier check
joshuahannan Aug 6, 2025
8ec6ea9
remove optional from scheduled event
joshuahannan Aug 6, 2025
29940d8
rebase and make assets
joshuahannan Aug 7, 2025
e94301b
use interface type for config storage
joshuahannan Aug 7, 2025
9f760a6
handle failed callbacks and store failed statuses
joshuahannan Aug 7, 2025
9897d19
make assets
joshuahannan Aug 7, 2025
52ab078
remove succeeded status from get status
joshuahannan Aug 8, 2025
309bc9e
keep succeeded status
joshuahannan Aug 11, 2025
68fecdd
rename historic timestamp and use earliest historic ID
joshuahannan Aug 11, 2025
224ca21
fix assets
joshuahannan Aug 11, 2025
9ae4443
add unknown status
joshuahannan Aug 11, 2025
f96494a
General callback scheduler improvements (#508)
devbugging Aug 12, 2025
5d78437
address Janez and Josh Pr comments
joshuahannan Aug 12, 2025
c17f3ae
remove test code
joshuahannan Aug 12, 2025
926eff2
more concise names
joshuahannan Aug 13, 2025
203b43f
Remove shared state access
devbugging Aug 12, 2025
7a4d679
do cleanup in process
joshuahannan Aug 12, 2025
27147d6
refactor finalize and only call after execution
joshuahannan Aug 12, 2025
c6aea32
fix initial tests
joshuahannan Aug 13, 2025
e0a2950
fix low priority bug and initialize canceled callbacks to 0
joshuahannan Aug 14, 2025
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ coverage.lcov
lcov.info
*.pkey

imports*

# Private flow.jsons

private.flow.json
1,097 changes: 1,097 additions & 0 deletions contracts/FlowCallbackScheduler.cdc

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions contracts/testContracts/TestFlowCallbackHandler.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import "FlowCallbackScheduler"
import "FlowToken"
import "FungibleToken"

// TestFlowCallbackHandler is a simplified test contract for testing CallbackScheduler
access(all) contract TestFlowCallbackHandler {
access(all) var scheduledCallbacks: {UInt64: FlowCallbackScheduler.ScheduledCallback}
access(all) var succeededCallbacks: [UInt64]

access(all) let HandlerStoragePath: StoragePath
access(all) let HandlerPublicPath: PublicPath

access(all) resource Handler: FlowCallbackScheduler.CallbackHandler {

access(FlowCallbackScheduler.Execute)
fun executeCallback(id: UInt64, data: AnyStruct?) {
// Most callbacks will have string data
if let dataString = data as? String {
// intentional failure test case
if dataString == "fail" {
panic("Callback \(id) failed")
} else {
// All other regular test cases should succeed
TestFlowCallbackHandler.succeededCallbacks.append(id)
}
} else if let dataCap = data as? Capability<auth(FlowCallbackScheduler.Execute) &{FlowCallbackScheduler.CallbackHandler}> {
// Testing scheduling a callback with a callback
let scheduledCallback = FlowCallbackScheduler.schedule(
callback: dataCap,
data: "test data",
timestamp: getCurrentBlock().timestamp + 10.0,
priority: FlowCallbackScheduler.Priority.High,
executionEffort: UInt64(1000),
fees: <-TestFlowCallbackHandler.getFeeFromVault(amount: 1.0)
)
TestFlowCallbackHandler.addScheduledCallback(callback: scheduledCallback)
} else {
panic("TestFlowCallbackHandler.executeCallback: Invalid data type for callback with id \(id). Type is \(data.getType().identifier)")
}
}
}

access(all) fun createHandler(): @Handler {
return <- create Handler()
}

access(all) fun addScheduledCallback(callback: FlowCallbackScheduler.ScheduledCallback) {
self.scheduledCallbacks[callback.id] = callback
}

access(all) fun cancelCallback(id: UInt64): @FlowToken.Vault {
let callback = self.scheduledCallbacks[id]
?? panic("Invalid ID: \(id) callback not found")
self.scheduledCallbacks[id] = nil
return <-FlowCallbackScheduler.cancel(callback: callback)
}

access(all) fun getSucceededCallbacks(): [UInt64] {
return self.succeededCallbacks
}

access(contract) fun getFeeFromVault(amount: UFix64): @FlowToken.Vault {
// borrow a reference to the vault that will be used for fees
let vault = self.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
?? panic("Could not borrow FlowToken vault")

return <- vault.withdraw(amount: amount) as! @FlowToken.Vault
}

access(all) init() {
self.scheduledCallbacks = {}
self.succeededCallbacks = []

self.HandlerStoragePath = /storage/testCallbackHandler
self.HandlerPublicPath = /public/testCallbackHandler
}
}
51 changes: 36 additions & 15 deletions flow.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
"testnet": "8c5303eaa26202d6"
}
},
"FlowCallbackScheduler": {
"source": "./contracts/FlowCallbackScheduler.cdc",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"testing": "0000000000000001"
}
},
"FlowClusterQC": {
"source": "./contracts/epochs/FlowClusterQC.cdc",
"aliases": {
Expand Down Expand Up @@ -44,12 +51,21 @@
"testnet": "9eca2b38b18b5dfe"
}
},
"FlowExecutionParameters": {
"source": "./contracts/FlowExecutionParameters.cdc",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "f426ff57ee8f6110",
"testing": "0000000000000007",
"testnet": "6997a2f2cf57b73a"
}
},
"FlowFees": {
"source": "./contracts/FlowFees.cdc",
"aliases": {
"emulator": "e5a8b7f23e8b548f",
"mainnet": "f919ee77447b7497",
"testing": "0000000000000007",
"testing": "0000000000000004",
"testnet": "912d5440f7e3769e"
}
},
Expand All @@ -62,15 +78,6 @@
"testnet": "9eca2b38b18b5dfe"
}
},
"FlowExecutionParameters": {
"source": "./contracts/FlowExecutionParameters.cdc",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "f426ff57ee8f6110",
"testing": "0000000000000007",
"testnet": "6997a2f2cf57b73a"
}
},
"FlowServiceAccount": {
"source": "./contracts/FlowServiceAccount.cdc",
"aliases": {
Expand All @@ -94,7 +101,7 @@
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "e467b9dd11fa00df",
"testing": "0000000000000007",
"testing": "0000000000000001",
"testnet": "8c5303eaa26202d6"
}
},
Expand All @@ -103,7 +110,7 @@
"aliases": {
"emulator": "0ae53cb6e3f42a79",
"mainnet": "1654653399040a61",
"testing": "0000000000000007",
"testing": "0000000000000003",
"testnet": "7e60df042a9c0868"
}
},
Expand All @@ -112,14 +119,14 @@
"aliases": {
"emulator": "ee82856bf20e2aa6",
"mainnet": "f233dcee88fe0abe",
"testing": "0000000000000007",
"testing": "0000000000000002",
"testnet": "9a0766d93b6608b7"
}
},
"LinearCodeAddressGenerator": {
"source": "./contracts/LinearCodeAddressGenerator.cdc",
"aliases": {
"testing": "0x0000000000000007"
"testing": "0000000000000007"
}
},
"LockedTokens": {
Expand Down Expand Up @@ -157,6 +164,13 @@
"testing": "0000000000000007",
"testnet": "7aad92e5a0715d21"
}
},
"TestFlowCallbackHandler": {
"source": "./contracts/testContracts/TestFlowCallbackHandler.cdc",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"testing": "0000000000000001"
}
}
},
"networks": {
Expand All @@ -168,7 +182,14 @@
"accounts": {
"emulator-account": {
"address": "f8d6e0586b0a20c7",
"key": "7677f7c9410f8773b482737c778b5d7c6acfdbbae718d61e4727a07667f66004"
"key": "aff3a277caf2bdd6582c156ae7b07dbca537da7833309de88e56987faa2c0f1b"
}
},
"deployments": {
"emulator": {
"emulator-account": [
"TestFlowCallbackHandler"
]
}
}
}
53 changes: 37 additions & 16 deletions lib/go/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,31 @@ const (
flowRandomBeaconHistoryFilename = "RandomBeaconHistory.cdc"
cryptoFilename = "Crypto.cdc"
linearCodeAddressGeneratorFilename = "LinearCodeAddressGenerator.cdc"
flowCallbackSchedulerFilename = "FlowCallbackScheduler.cdc"

// Test contracts
// only used for testing
TESTFlowIdentityTableFilename = "testContracts/TestFlowIDTableStaking.cdc"
TESTFlowIdentityTableFilename = "testContracts/TestFlowIDTableStaking.cdc"
TESTflowCallbackHandlerFilename = "testContracts/TestFlowCallbackHandler.cdc"

// Each contract has placeholder addresses that need to be replaced
// depending on which network they are being used with
placeholderFungibleTokenAddress = "\"FungibleToken\""
placeholderFungibleTokenMVAddress = "\"FungibleTokenMetadataViews\""
placeholderMetadataViewsAddress = "\"MetadataViews\""
placeholderFlowTokenAddress = "\"FlowToken\""
placeholderIDTableAddress = "\"FlowIDTableStaking\""
placeholderBurnerAddress = "\"Burner\""
placeholderStakingProxyAddress = "\"StakingProxy\""
placeholderQCAddr = "\"FlowClusterQC\""
placeholderDKGAddr = "\"FlowDKG\""
placeholderEpochAddr = "\"FlowEpoch\""
placeholderFlowFeesAddress = "\"FlowFees\""
placeholderStorageFeesAddress = "\"FlowStorageFees\""
placeholderLockedTokensAddress = "\"LockedTokens\""
placeholderStakingCollectionAddress = "\"FlowStakingCollection\""
placeholderNodeVersionBeaconAddress = "\"NodeVersionBeacon\""
placeholderFungibleTokenAddress = "\"FungibleToken\""
placeholderFungibleTokenMVAddress = "\"FungibleTokenMetadataViews\""
placeholderMetadataViewsAddress = "\"MetadataViews\""
placeholderFlowTokenAddress = "\"FlowToken\""
placeholderIDTableAddress = "\"FlowIDTableStaking\""
placeholderBurnerAddress = "\"Burner\""
placeholderStakingProxyAddress = "\"StakingProxy\""
placeholderQCAddr = "\"FlowClusterQC\""
placeholderDKGAddr = "\"FlowDKG\""
placeholderEpochAddr = "\"FlowEpoch\""
placeholderFlowFeesAddress = "\"FlowFees\""
placeholderStorageFeesAddress = "\"FlowStorageFees\""
placeholderLockedTokensAddress = "\"LockedTokens\""
placeholderStakingCollectionAddress = "\"FlowStakingCollection\""
placeholderNodeVersionBeaconAddress = "\"NodeVersionBeacon\""
placeholderFlowCallbackSchedulerAddress = "\"FlowCallbackScheduler\""
)

// Adds a `0x` prefix to the provided address string
Expand Down Expand Up @@ -293,6 +296,15 @@ func RandomBeaconHistory() []byte {
return assets.MustAsset(flowRandomBeaconHistoryFilename)
}

// FlowCallbackScheduler returns the FlowCallbackScheduler contract.
func FlowCallbackScheduler(env templates.Environment) []byte {
code := assets.MustAssetString(flowCallbackSchedulerFilename)

code = templates.ReplaceAddresses(code, env)

return []byte(code)
}

// FlowContractAudits returns the deprecated FlowContractAudits contract.
// This contract is no longer used on any network
func FlowContractAudits() []byte {
Expand Down Expand Up @@ -368,6 +380,15 @@ func TestFlowFees(fungibleTokenAddress, flowTokenAddress, storageFeesAddress str
return []byte(code)
}

// TestFlowCallbackHandler returns the TestFlowCallbackHandler contract.
func TestFlowCallbackHandler(env templates.Environment) []byte {
code := assets.MustAssetString(TESTflowCallbackHandlerFilename)

code = templates.ReplaceAddresses(code, env)

return []byte(code)
}

func ExampleToken(env templates.Environment) []byte {
return ftcontracts.ExampleToken(env.FungibleTokenAddress, env.MetadataViewsAddress, env.FungibleTokenMetadataViewsAddress)
}
Expand Down
18 changes: 18 additions & 0 deletions lib/go/contracts/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func SetAllAddresses(env *templates.Environment) {
env.ServiceAccountAddress = fakeAddr
env.NodeVersionBeaconAddress = fakeAddr
env.RandomBeaconHistoryAddress = fakeAddr
env.FlowCallbackSchedulerAddress = fakeAddr
}

// Tests that a specific contract path should succeed when retrieving it
Expand Down Expand Up @@ -164,6 +165,23 @@ func TestStakingCollection(t *testing.T) {
assert.Contains(t, contract, "import LockedTokens from 0x")
}

func TestFlowCallbackScheduler(t *testing.T) {
env := templates.Environment{}
SetAllAddresses(&env)
contract := string(contracts.FlowCallbackScheduler(env))
GetCadenceContractShouldSucceed(t, contract)
assert.Contains(t, contract, "import FlowToken from 0x")
assert.Contains(t, contract, "import FlowFees from 0x")
}

func TestFlowCallbackHandler(t *testing.T) {
env := templates.Environment{}
SetAllAddresses(&env)
contract := string(contracts.TestFlowCallbackHandler(env))
GetCadenceContractShouldSucceed(t, contract)
assert.Contains(t, contract, "import FlowCallbackScheduler from 0x")
}

func TestNodeVersionBeacon(t *testing.T) {
contract := string(contracts.NodeVersionBeacon())
GetCadenceContractShouldSucceed(t, contract)
Expand Down
12 changes: 6 additions & 6 deletions lib/go/contracts/go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module github.com/onflow/flow-core-contracts/lib/go/contracts

go 1.22
go 1.23.0

toolchain go1.22.4
toolchain go1.23.1

require (
github.com/kevinburke/go-bindata v3.24.0+incompatible
github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1-0.20250226163127-3c9723416637
github.com/onflow/flow-core-contracts/lib/go/templates v1.7.2-0.20250709181500-7b276f4ca1c8
github.com/onflow/flow-ft/lib/go/contracts v1.0.1
github.com/onflow/flow-go-sdk v1.0.0-preview.54
github.com/onflow/flow-nft/lib/go/contracts v1.2.4
Expand Down Expand Up @@ -59,10 +59,10 @@ require (
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gonum.org/v1/gonum v0.14.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
Expand Down
Loading
Loading