Skip to content

Commit

Permalink
PR review: use array length to compare with empty array and minor cha…
Browse files Browse the repository at this point in the history
…nges
  • Loading branch information
tarakby committed Apr 10, 2024
1 parent bd2265f commit 9e5d257
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 68 deletions.
128 changes: 63 additions & 65 deletions contracts/RandomBeaconHistory.cdc
Expand Up @@ -3,16 +3,16 @@
/// 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.
/// 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
///
Expand Down Expand Up @@ -49,13 +49,13 @@ access(all) contract RandomBeaconHistory {
///
/// @param randomSourceHistory The random source to record
///
/// The Flow protocol makes sure to call this function once per block as a system call. The transaction
/// comes at the end of each block so that the current block's entry becomes available only in the child
/// The Flow protocol makes sure to call this function once per block as a system call. The transaction
/// comes at the end of each block so that the current block's entry becomes available only in the child
/// block.
///
access(all) fun heartbeat(randomSourceHistory: [UInt8]) {
access(all) fun heartbeat(randomSourceHistory: [UInt8]) {

assert (
assert(
// random source must be at least 128 bits
randomSourceHistory.length >= 128 / 8,
message: "Random source must be at least 128 bits"
Expand All @@ -72,8 +72,23 @@ access(all) contract RandomBeaconHistory {
}
let backfiller = RandomBeaconHistory.borrowBackfiller() ?? panic("Problem borrowing backfiller")

// correct index to fill with the new random source
// so that eventually randomSourceHistory[correctIndex] = inputRandom
let lowestHeight = RandomBeaconHistory.lowestHeight!
let correctIndex = currentBlockHeight - lowestHeight

// cache the array length in a local variable to avoid multiple calls to `length`
// when the array is too large
var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length)

// if a new gap is detected, emit an event
if correctIndex > arrayLength {
let gapStartHeight = lowestHeight + arrayLength
emit RandomHistoryMissing(blockHeight: currentBlockHeight, gapStartHeight: gapStartHeight)
}

// check for any existing gap and backfill using the input random source if needed.
backfiller.backfill(randomSource: randomSourceHistory)
backfiller.backfill(randomSource: randomSourceHistory, currentArrayLength: arrayLength, correctIndex: correctIndex)

// we are now at the correct index to record the source of randomness
// created by the protocol for the current block
Expand All @@ -87,7 +102,7 @@ access(all) contract RandomBeaconHistory {
/// to a system transaction failure.
///
access(all) resource Backfiller {
/// Start index of the first gap in the `randomSourceHistory` array where random sources were not recorded,
/// Start index of the first gap in the `randomSourceHistory` array where random sources were not recorded,
/// because of a heartbeat failure.
/// There may be non contiguous gaps in the history, `gapStartIndex` is the start index of the lowest-height
/// gap.
Expand All @@ -102,37 +117,6 @@ access(all) contract RandomBeaconHistory {
self.maxEntriesPerCall = 100
}

access(all) view fun getMaxEntriesPerCall() : UInt64 {
return self.maxEntriesPerCall
}

access(all) fun setMaxEntriesPerCall(max: UInt64) {
assert (
max > 0,
message : "the maximum entry per call must be strictly positive"
)
self.maxEntriesPerCall = max
}

/// Finds the correct index to fill with the new random source. If a gap is detected, emits the
/// RandomHistoryMissing event.
///
access(contract) view fun findGapAndReturnCorrectIndex(): UInt64 {

let currentBlockHeight = getCurrentBlock().height
// correct index to fill with the new random source
// so that eventually randomSourceHistory[correctIndex] = inputRandom
let correctIndex = currentBlockHeight - RandomBeaconHistory.lowestHeight!

// if a new gap is detected, emit an event
var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length)
if correctIndex > arrayLength {
let gapStartHeight = RandomBeaconHistory.lowestHeight! + arrayLength
emit RandomHistoryMissing(blockHeight: currentBlockHeight, gapStartHeight: gapStartHeight)
}
return correctIndex
}

/// Backfills possible empty entries (gaps) in the history array starting from the stored `gapStartIndex`,
/// using `randomSource` as a seed for all entries.
/// If there are no gaps, `gapStartIndex` is just updated to `RandomBeaconHistory`'s length.
Expand All @@ -143,30 +127,28 @@ access(all) contract RandomBeaconHistory {
//
/// gaps only occur in the rare event of a system transaction failure. In this case, entries are still
/// filled using a source not known at the time of block execution, which guarantees unpredicatability.
access(contract) fun backfill(randomSource: [UInt8]) {

let correctIndex = self.findGapAndReturnCorrectIndex()
var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length)
// optional optimization for the happy common path: if no gaps are detected,
access(contract) fun backfill(randomSource: [UInt8], currentArrayLength: UInt64, correctIndex: UInt64) {
// optional optimization for the happy common path: if no gaps are detected,
// backfilling isn't needed, return early
if correctIndex == self.gapStartIndex {
self.gapStartIndex = arrayLength + 1
self.gapStartIndex = currentArrayLength + 1
return
}

// If a new gap is detected in the current transaction, fill the gap with empty entries.
// This happens in the rare case where a new gap occurs because of a system transaction failure.
var arrayLength = currentArrayLength
while correctIndex > arrayLength {
RandomBeaconHistory.randomSourceHistory.append([])
arrayLength = arrayLength + 1
}

var newEntry = randomSource
var index = self.gapStartIndex
var count = 0 as UInt64
var count = UInt64(0)
while count < self.maxEntriesPerCall {
// move to the next empty entry
while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index] != [] {
while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index].length > 0 {
index = index + 1
}
// if we reach the end of the array then all existing gaps got filled
Expand All @@ -185,21 +167,37 @@ access(all) contract RandomBeaconHistory {
// emit an event about backfilled entries
if count > 0 {
let gapStartHeight = RandomBeaconHistory.lowestHeight! + self.gapStartIndex
emit RandomHistoryBackfilled(blockHeight: getCurrentBlock().height, gapStartHeight: gapStartHeight, count: count)
emit RandomHistoryBackfilled(
blockHeight: getCurrentBlock().height,
gapStartHeight: gapStartHeight,
count: count
)
}

// no more backfilling is possible but we need to update `gapStartIndex`
// to:
// - the next empty index if gaps still exist
// - the length of the array at the end of the transaction if there are no gaps
while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index] != [] {
// - the length of the array at the end of the transaction if there are no gaps
while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index].length > 0 {
index = index + 1
}
if index == arrayLength {
if index == arrayLength {
index = index + 1 // take into account the upcoming append of the SoR at the correct index
}
self.gapStartIndex = index
}

access(all) fun setMaxEntriesPerCall(max: UInt64) {
assert(
max > 0,
message: "the maximum entry per call must be strictly positive"
)
self.maxEntriesPerCall = max
}

access(all) fun getMaxEntriesPerCall() : UInt64 {
return self.maxEntriesPerCall
}
}

/* --- RandomSourceHistory --- */
Expand All @@ -209,7 +207,7 @@ access(all) contract RandomBeaconHistory {
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
Expand All @@ -225,7 +223,7 @@ access(all) contract RandomBeaconHistory {
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
Expand Down Expand Up @@ -256,7 +254,7 @@ access(all) contract RandomBeaconHistory {
message: "Problem finding random source history index"
)
assert(
index < UInt64(self.randomSourceHistory.length) && self.randomSourceHistory[index] != [],
index < UInt64(self.randomSourceHistory.length) && self.randomSourceHistory[index].length > 0,
message: "Source of randomness is currently not available but will be available soon"
)
return RandomSource(blockHeight: blockHeight, value: self.randomSourceHistory[index])
Expand Down Expand Up @@ -295,7 +293,7 @@ access(all) contract RandomBeaconHistory {
let lowestHeight = self.lowestHeight!
for i, value in self.randomSourceHistory.slice(from: Int(startIndex), upTo: Int(endIndex)) {
assert(
value != [],
value.length > 0,
message: "Source of randomness is currently not available but will be available soon"
)
values.append(
Expand Down Expand Up @@ -323,7 +321,7 @@ access(all) contract RandomBeaconHistory {
}

/// Getter for the contract's Backfiller resource
access(contract) fun borrowBackfiller(): &Backfiller? {
access(all) fun borrowBackfiller(): &Backfiller? {
return self.account.borrow<&Backfiller>(from: /storage/randomBeaconHistoryBackfiller)
}

Expand Down

0 comments on commit 9e5d257

Please sign in to comment.