Skip to content

Commit

Permalink
RandomBeaconHistory contract (#375)
Browse files Browse the repository at this point in the history
* Minimal source of randomness contract

* Add call to entropy

* Source of randomness history suggestions (#377)

* change sor heartbeat signature

* RandomBeaconHistory impl + transactions & scripts

* simplify RandomBeaconHistory interface

* rm RandomBeaconHistory.HeartbeatPublicPath

* cleanup RandomBeaconHistory formatting & naming

* update go assets

* update RandomBeaconHistory events & err messages

* update go assets

* Update contracts/RandomBeaconHistory.cdc

Co-authored-by: Tarak Ben Youssef <[email protected]>

* update go assets

* rename RandomBeaconHistory.initHeight -> .lowestHeight

* add RandomBeaconHistory.getRandomSourceHistoryPage()

* add RandomBeaconHistory scripts

* update go assets

* fix RandomBeaconHistory typo

* update go assets

* update RandomBeacon naming

* remove unused transaction and rename

* fix get_source_of_randomness script

* Adress review comments

* change signature of sourceOfRandomness

* fix tidy

---------

Co-authored-by: Tarak Ben Youssef <[email protected]>
Co-authored-by: Giovanni Sanchez <[email protected]>
  • Loading branch information
3 people authored Oct 16, 2023
1 parent dac80c4 commit c7ba90a
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 50 deletions.
168 changes: 168 additions & 0 deletions contracts/RandomBeaconHistory.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/// RandomBeaconHistory (FLIP 123)
///
/// This contract stores the history of random sources generated by the Flow network. The defined Heartbeat resource is
/// updated by the Flow Service Account at the end of every block with that block's source of randomness.
///
/// While the source values are safely generated by the Random Beacon (non-predictable, unbiasable, verifiable) and transmitted into the execution
/// environment via the committing transaction, using the raw values from this contract does not guarantee non-revertible
/// randomness. The Hearbeat is intended to be used in conjunction with a
/// commit-reveal mechanism to provide an onchain source of non-revertible randomness.
// It is also recommended to use the source values with a pseudo-random number
// generator (PRNG) to generate an arbitrary-long sequence of random values.
//
// For usage of randomness where result abortion is not an issue, it is recommended
// to use the Cadence built-in function `revertibleRandom`, which is also based on
// the safe Random Beacon.
///
/// Read the full FLIP here: https://github.com/onflow/flips/pull/123
///
access(all) contract RandomBeaconHistory {

/// The height at which the first source of randomness was recorded
access(contract) var lowestHeight: UInt64?
/// Sequence of random sources recorded by the Heartbeat, stored as an array over a mapping to reduce storage
access(contract) let randomSourceHistory: [[UInt8]]

/// The path of the Heartbeat resource in the deployment account
access(all) let HeartbeatStoragePath: StoragePath

/* --- Hearbeat --- */
//
/// The Heartbeat resource containing each block's source of randomness in sequence
///
access(all) resource Heartbeat {

/// Callable by owner of the Heartbeat resource, Flow Service Account, records the provided random source
///
/// @param randomSourceHistory The random source to record
///
access(all) fun heartbeat(randomSourceHistory: [UInt8]) {

let currentBlockHeight = getCurrentBlock().height
if RandomBeaconHistory.lowestHeight == nil {
RandomBeaconHistory.lowestHeight = currentBlockHeight
}

RandomBeaconHistory.randomSourceHistory.append(randomSourceHistory)
}
}

/* --- RandomSourceHistory --- */
//
/// Represents a random source value for a given block height
///
access(all) struct RandomSource {
access(all) let blockHeight: UInt64
access(all) let value: [UInt8]

init(blockHeight: UInt64, value: [UInt8]) {
self.blockHeight = blockHeight
self.value = value
}
}

/* --- RandomSourceHistoryPage --- */
//
/// Contains RandomSource values ordered chronologically according to associated block height
///
access(all) struct RandomSourceHistoryPage {
access(all) let page: UInt64
access(all) let perPage: UInt64
access(all) let totalLength: UInt64
access(all) let values: [RandomSource]

init(page: UInt64, perPage: UInt64, totalLength: UInt64, values: [RandomSource]) {
self.page = page
self.perPage = perPage
self.totalLength = totalLength
self.values = values
}
}

/* --- Contract Methods --- */
//
/// Getter for the source of randomness at a given block height. Panics if the requested block height either
/// precedes or exceeds the recorded history. Note that a source of randomness for block n will not be accessible
/// until block n+1.
///
/// @param atBlockHeight The block height at which to retrieve the source of randomness
///
/// @return The source of randomness at the given block height as RandomSource struct
///
access(all) fun sourceOfRandomness(atBlockHeight blockHeight: UInt64): RandomSource {
pre {
self.lowestHeight != nil: "History has not yet been initialized"
blockHeight >= self.lowestHeight!: "Requested block height precedes recorded history"
blockHeight < getCurrentBlock().height: "Source of randomness not yet recorded"
}
let index = blockHeight - self.lowestHeight!
assert(
index >= 0 && index < UInt64(self.randomSourceHistory.length),
message: "Problem finding random source history index"
)
return RandomSource(blockHeight: blockHeight, value: self.randomSourceHistory[index])
}

/// Retrieves a page from the history of random sources, ordered chronologically
///
/// @param page: The page number to retrieve, 0-indexed
/// @param perPage: The number of random sources to include per page
///
/// @return A RandomSourceHistoryPage containing RandomSource values in choronological order according to
/// associated block height
///
access(all) view fun getRandomSourceHistoryPage(_ page: UInt64, perPage: UInt64): RandomSourceHistoryPage {
pre {
self.lowestHeight != nil: "History has not yet been initialized"
}
let values: [RandomSource] = []
let totalLength = UInt64(self.randomSourceHistory.length)

var startIndex = page * perPage
if startIndex > totalLength {
startIndex = totalLength
}
var endIndex = startIndex + perPage
if endIndex > totalLength {
endIndex = totalLength
}
// Return empty page if request exceeds last page
if startIndex == endIndex {
return RandomSourceHistoryPage(page: page, perPage: perPage, totalLength: totalLength, values: values)
}

// Iterate over history and construct page RandomSource values
let lowestHeight = self.lowestHeight!
for i, block in self.randomSourceHistory.slice(from: Int(startIndex), upTo: Int(endIndex)) {
values.append(
RandomSource(
blockHeight: lowestHeight + startIndex + UInt64(i),
value: self.randomSourceHistory[startIndex + UInt64(i)]
)
)
}

return RandomSourceHistoryPage(
page: page,
perPage: perPage,
totalLength: totalLength,
values: values
)
}

/// Getter for the block height at which the first source of randomness was recorded
///
/// @return The block height at which the first source of randomness was recorded
///
access(all) view fun getLowestHeight(): UInt64 {
return self.lowestHeight ?? panic("History has not yet been initialized")
}

init() {
self.lowestHeight = nil
self.randomSourceHistory = []
self.HeartbeatStoragePath = /storage/FlowRandomBeaconHistoryHeartbeat

self.account.save(<-create Heartbeat(), to: self.HeartbeatStoragePath)
}
}
16 changes: 5 additions & 11 deletions flow.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
{
"emulators": {
"default": {
"port": 3569,
"serviceAccount": "emulator-account"
}
},
"contracts": {
"FlowClusterQC": "./contracts/epochs/FlowClusterQC.cdc",
"FlowEpoch": "./contracts/epochs/FlowEpoch.cdc",
"FlowDKG": "./contracts/epochs/FlowEpoch.cdc",
"NodeVersionBeacon": "./contracts/NodeVersionBeacon.cdc",
"FlowContractAudits": "./contracts/FlowContractAudits.cdc",
"FlowDKG": "./contracts/epochs/FlowEpoch.cdc",
"FlowEpoch": "./contracts/epochs/FlowEpoch.cdc",
"FlowFees": "./contracts/FlowFees.cdc",
"FlowIDTableStaking": "./contracts/FlowIDTableStaking.cdc",
"FlowIDTableStaking_old": "./contracts/FlowIDTableStaking_old.cdc",
Expand All @@ -19,6 +12,8 @@
"FlowStorageFees": "./contracts/FlowStorageFees.cdc",
"FlowToken": "./contracts/FlowToken.cdc",
"LockedTokens": "./contracts/LockedTokens.cdc",
"NodeVersionBeacon": "./contracts/NodeVersionBeacon.cdc",
"RandomBeaconHistory": "./contracts/RandomBeaconHistory.cdc",
"StakingProxy": "./contracts/StakingProxy.cdc"
},
"networks": {
Expand All @@ -31,6 +26,5 @@
"address": "f8d6e0586b0a20c7",
"key": "7677f7c9410f8773b482737c778b5d7c6acfdbbae718d61e4727a07667f66004"
}
},
"deployments": {}
}
}
33 changes: 20 additions & 13 deletions lib/go/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@ import (
///

const (
flowFeesFilename = "FlowFees.cdc"
storageFeesFilename = "FlowStorageFees.cdc"
flowServiceAccountFilename = "FlowServiceAccount.cdc"
flowTokenFilename = "FlowToken.cdc"
flowIdentityTableFilename = "FlowIDTableStaking.cdc"
flowQCFilename = "epochs/FlowClusterQC.cdc"
flowDKGFilename = "epochs/FlowDKG.cdc"
flowEpochFilename = "epochs/FlowEpoch.cdc"
flowLockedTokensFilename = "LockedTokens.cdc"
flowStakingProxyFilename = "StakingProxy.cdc"
flowStakingCollectionFilename = "FlowStakingCollection.cdc"
flowContractAuditsFilename = "FlowContractAudits.cdc"
flowNodeVersionBeaconFilename = "NodeVersionBeacon.cdc"
flowFeesFilename = "FlowFees.cdc"
storageFeesFilename = "FlowStorageFees.cdc"
flowServiceAccountFilename = "FlowServiceAccount.cdc"
flowTokenFilename = "FlowToken.cdc"
flowIdentityTableFilename = "FlowIDTableStaking.cdc"
flowQCFilename = "epochs/FlowClusterQC.cdc"
flowDKGFilename = "epochs/FlowDKG.cdc"
flowEpochFilename = "epochs/FlowEpoch.cdc"
flowLockedTokensFilename = "LockedTokens.cdc"
flowStakingProxyFilename = "StakingProxy.cdc"
flowStakingCollectionFilename = "FlowStakingCollection.cdc"
flowContractAuditsFilename = "FlowContractAudits.cdc"
flowNodeVersionBeaconFilename = "NodeVersionBeacon.cdc"
flowRandomBeaconHistoryFilename = "RandomBeaconHistory.cdc"

// Test contracts
// only used for testing
Expand Down Expand Up @@ -342,6 +343,12 @@ func NodeVersionBeacon() []byte {
return []byte(code)
}

func RandomBeaconHistory() []byte {
code := assets.MustAssetString(flowRandomBeaconHistoryFilename)

return []byte(code)
}

// FlowContractAudits returns the deprecated FlowContractAudits contract.
// This contract is no longer used on any network
func FlowContractAudits() []byte {
Expand Down
Loading

0 comments on commit c7ba90a

Please sign in to comment.