From 272df6c01e8fa09ab772466ef508dc8ac3045d7e Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef <50252200+tarakby@users.noreply.github.com> Date: Mon, 15 Apr 2024 12:04:12 -0600 Subject: [PATCH] SoR History update (#416) * first update to handle possible gaps in history * extend tests to cover single gap case * update getRandomSourceHistoryPage and add tests for backfilled pages * add test for non-contiguous gap edge case * enforce minimum source length * add RandomBeaconHistory.Backfiller and integrate with SoR commitment * bump Flow CLI version in CI workflow * simplify the backfilling logic * update gapStartIndex init value * add event for missing and backfilled SoRs * update assets * set and get for the backfiller limit * add a note about the hash output size * reduce calls to the large array length * go generate * move all backfilling logic inside the backfiller resource * move correct index discover to Backfiller resource * add coverage.lcov to .gitignore * re-add early return to optimize common case * rewrap contract comments * update .borrowBackfiller() access & add event test coverage * tests: add non-continuous gaps events check and use backfilling constant * PR review: use array length to compare with empty array and minor changes * make generate * optimize unnecessary function calls * minor optimization --------- Co-authored-by: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +- .gitignore | 1 + contracts/RandomBeaconHistory.cdc | 208 +++++++- lib/go/contracts/internal/assets/assets.go | 6 +- lib/go/templates/internal/assets/assets.go | 48 ++ tests/test_random_beacon_history.cdc | 446 +++++++++++++++++- .../scripts/get_backfiller_max_entries.cdc | 6 + .../set_backfiller_max_entries.cdc | 11 + 8 files changed, 692 insertions(+), 40 deletions(-) create mode 100644 transactions/randomBeaconHistory/scripts/get_backfiller_max_entries.cdc create mode 100644 transactions/randomBeaconHistory/transactions/set_backfiller_max_entries.cdc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c40b3d7b6..3a1270cc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,14 +11,14 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v1 with: - go-version: '1.21.5' + go-version: "1.21.5" - uses: actions/setup-node@v3 with: node-version: 16 - cache: 'npm' + cache: "npm" cache-dependency-path: lib/js/test/package-lock.json - name: Install Flow CLI - run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" -- v1.5.0 + run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" -- v1.15.0 - name: Flow cli Version run: flow version - name: Update PATH diff --git a/.gitignore b/.gitignore index 50da31e1d..d432ffe8c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ node_modules git coverage.json +coverage.lcov flow.json diff --git a/contracts/RandomBeaconHistory.cdc b/contracts/RandomBeaconHistory.cdc index 865d06e68..dc73e9ae6 100644 --- a/contracts/RandomBeaconHistory.cdc +++ b/contracts/RandomBeaconHistory.cdc @@ -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 /// @@ -26,6 +26,19 @@ access(all) contract RandomBeaconHistory { /// The path of the Heartbeat resource in the deployment account access(all) let HeartbeatStoragePath: StoragePath + // Event emitted when missing SoRs from past heartbeats are detected and will be backfilled: + // - `blockHeight` is the height where the gap is detected + // - `gapStartHeight` is the height of the first missing entry detected + access(all) event RandomHistoryMissing(blockHeight: UInt64, gapStartHeight: UInt64) + + // Event emitted when missing SoRs are backfilled on the current heartbeat: + // - `blockHeight` is the height where the backfill happened, it also defines the SoR used to backfill + // - `gapStartHeight` is the height of the first backfilled entry + // - `count` is the number of backfilled entries + // Note that in very rare cases, the backfilled gap may not be contiguous. This event does not + // fully define the backfilled entries in this case. + access(all) event RandomHistoryBackfilled(blockHeight: UInt64, gapStartHeight: UInt64, count: UInt64) + /* --- Hearbeat --- */ // /// The Heartbeat resource containing each block's source of randomness in sequence @@ -36,17 +49,166 @@ 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 + /// block. + /// access(all) fun heartbeat(randomSourceHistory: [UInt8]) { + assert( + // random source must be at least 128 bits + randomSourceHistory.length >= 128 / 8, + message: "Random source must be at least 128 bits" + ) + let currentBlockHeight = getCurrentBlock().height + // init lowestBlockHeight if it is not set yet if RandomBeaconHistory.lowestHeight == nil { RandomBeaconHistory.lowestHeight = currentBlockHeight } + var maybeBackfiller = RandomBeaconHistory.borrowBackfiller() + // Create & save Backfiller if it is not saved yet + if maybeBackfiller == nil { + RandomBeaconHistory.account.save(<-create Backfiller(), to: /storage/randomBeaconHistoryBackfiller) + maybeBackfiller = RandomBeaconHistory.borrowBackfiller() + } + let backfiller = maybeBackfiller ?? panic("Problem borrowing backfiller") + + // check for any existing gap and backfill using the input random source if needed. + backfiller.backfill(randomSource: randomSourceHistory) + // we are now at the correct index to record the source of randomness + // created by the protocol for the current block RandomBeaconHistory.randomSourceHistory.append(randomSourceHistory) } } + /* --- Backfiller --- */ + // + /// A recovery mechanism designed to backfill missed sources of randomness in the event of a missed commitment due + /// 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, + /// because of a heartbeat failure. + /// There may be non contiguous gaps in the history, `gapStartIndex` is the start index of the lowest-height + /// gap. + /// If no gaps exist, `gapStartIndex` is equal to the `randomSourceHistory` array length. + access(contract) var gapStartIndex: UInt64 + /// BackFilling is limited to a maximum number of entries per call to limit the computation cost. + /// This means a large gap may need a few calls to get fully backfilled. + access(contract) var maxEntriesPerCall: UInt64 + + init() { + self.gapStartIndex = UInt64(RandomBeaconHistory.randomSourceHistory.length) + 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 lowestHeight = RandomBeaconHistory.lowestHeight! + let correctIndex = currentBlockHeight - lowestHeight + + // if a new gap is detected, emit an event + var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + if correctIndex > arrayLength { + let gapStartHeight = 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. + // + /// When backfilling, all entries use the same entropy. Each entry is extracted from `randomSource` using + /// successive hashing. This makes sure the entries are all distinct although they provide + /// the same entropy. + // + /// 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, + // backfilling isn't needed, return early + if correctIndex == self.gapStartIndex { + self.gapStartIndex = arrayLength + 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. + while correctIndex > UInt64(RandomBeaconHistory.randomSourceHistory.length) { + RandomBeaconHistory.randomSourceHistory.append([]) + } + + arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + var newEntry = randomSource + var index = self.gapStartIndex + var count = 0 as UInt64 + while count < self.maxEntriesPerCall { + // move to the next empty entry + 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 + if index == arrayLength { + break + } + // back fill the empty entry + // It is guaranteed that sha3 output (256 bits) is larger than the minimum + // required size of an SoR (128 bits) + newEntry = HashAlgorithm.SHA3_256.hash(newEntry) + RandomBeaconHistory.randomSourceHistory[index] = newEntry + index = index + 1 + count = count + 1 + } + + // emit an event about backfilled entries + if count > 0 { + let gapStartHeight = RandomBeaconHistory.lowestHeight! + self.gapStartIndex + 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].length > 0 { + index = index + 1 + } + if index == arrayLength { + index = index + 1 // take into account the upcoming append of the SoR at the correct index + } + self.gapStartIndex = index + } + } + /* --- RandomSourceHistory --- */ // /// Represents a random source value for a given block height @@ -54,7 +216,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 @@ -70,7 +232,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 @@ -97,13 +259,17 @@ access(all) contract RandomBeaconHistory { } let index = blockHeight - self.lowestHeight! assert( - index >= 0 && index < UInt64(self.randomSourceHistory.length), + index >= 0, message: "Problem finding random source history index" ) + assert( + 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]) } - /// Retrieves a page from the history of random sources, ordered chronologically + /// Retrieves a page from the history of random sources recorded so far, ordered chronologically /// /// @param page: The page number to retrieve, 0-indexed /// @param perPage: The number of random sources to include per page @@ -126,6 +292,7 @@ access(all) contract RandomBeaconHistory { 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) @@ -133,11 +300,15 @@ access(all) contract RandomBeaconHistory { // 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)) { + for i, value in self.randomSourceHistory.slice(from: Int(startIndex), upTo: Int(endIndex)) { + assert( + value.length > 0, + message: "Source of randomness is currently not available but will be available soon" + ) values.append( RandomSource( blockHeight: lowestHeight + startIndex + UInt64(i), - value: self.randomSourceHistory[startIndex + UInt64(i)] + value: value ) ) } @@ -158,6 +329,11 @@ access(all) contract RandomBeaconHistory { return self.lowestHeight ?? panic("History has not yet been initialized") } + /// Getter for the contract's Backfiller resource + access(contract) fun borrowBackfiller(): &Backfiller? { + return self.account.borrow<&Backfiller>(from: /storage/randomBeaconHistoryBackfiller) + } + init() { self.lowestHeight = nil self.randomSourceHistory = [] diff --git a/lib/go/contracts/internal/assets/assets.go b/lib/go/contracts/internal/assets/assets.go index 72944a9ba..7c21275de 100644 --- a/lib/go/contracts/internal/assets/assets.go +++ b/lib/go/contracts/internal/assets/assets.go @@ -9,7 +9,7 @@ // FlowToken.cdc (11.433kB) // LockedTokens.cdc (29.28kB) // NodeVersionBeacon.cdc (22.585kB) -// RandomBeaconHistory.cdc (6.953kB) +// RandomBeaconHistory.cdc (15.836kB) // StakingProxy.cdc (5.483kB) // epochs/FlowClusterQC.cdc (17.938kB) // epochs/FlowDKG.cdc (18.385kB) @@ -264,7 +264,7 @@ func nodeversionbeaconCdc() (*asset, error) { return a, nil } -var _randombeaconhistoryCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x58\x6d\x8f\xdb\xb8\xf1\x7f\xbf\x9f\x62\x6e\x5f\xdc\xdf\x4e\xfc\x70\xb9\xfb\xa3\x28\x8c\x38\xe9\x5d\x80\x5c\x16\x48\xaf\x8b\xdd\x14\x7d\xb1\x58\xf4\x28\x69\x64\xb1\x47\x93\x2a\x49\xd9\x71\x0f\xf9\xee\xc5\x90\x94\x4c\xca\xd4\x26\x29\xda\x05\x9c\x58\x16\x39\x8f\xbf\xf9\xcd\x90\xeb\xf5\x1a\xee\x98\xac\xd4\xfe\x27\x64\xa5\x92\xef\xb8\xb1\x4a\x9f\x60\xf6\xf6\xfd\xcd\x2d\xbc\xf8\xfe\x87\xf9\xd5\x7a\xbd\xa6\x0f\x7c\x68\xb8\x81\x52\x49\xab\x59\x69\x81\x96\xa1\x01\xdb\x20\x34\x61\x8f\xaa\x41\x3b\x51\x60\x54\xa7\x4b\x34\xb0\x43\x89\x9a\x59\xac\xa0\x38\xb9\xa5\x6f\x85\x3a\x82\x44\x7b\x54\xfa\xb7\x15\x7c\x68\x10\x2a\xac\xb9\xc4\x0a\xde\x21\xd3\xb6\x40\x66\x41\xa3\xdf\x0e\xdc\x38\xbd\x5d\x5b\x5d\x88\xb8\x47\x7d\xe0\x25\xc2\x8f\x65\xa9\x3a\x69\x81\x59\xf7\x0e\x65\x45\x56\xe0\x01\xf5\x09\x0a\xa1\xca\xdf\xe0\xc8\x6d\x03\xb6\x61\xd6\x3f\xff\x9f\x09\xc6\x9d\xad\x95\x68\xcc\x6a\xf0\xf2\x6f\x0d\x17\xe8\x84\x85\x75\x07\x26\x3a\x34\xc0\x34\x82\x61\x35\x8a\xd3\xa5\x57\x3e\x80\xe0\x23\x08\x33\xa9\xe4\xb2\xd5\x58\xf1\xd2\xb2\x42\xe0\x02\x3a\x59\x70\x66\xfc\xf7\x03\x6a\x5e\x73\xfa\x3e\x07\x26\x2b\xb0\x9a\x49\xb3\xe7\x96\xc4\x71\x69\x95\xf7\xe3\x23\x96\x9d\xe5\x4a\x82\xb3\x09\xe5\x81\x6b\x25\xf7\x28\x2d\x1c\x38\x73\x4b\x4a\xb5\xa7\x5d\x5c\xee\xbc\x08\x56\xd2\xfa\x05\x74\xc6\xfd\xd4\x20\x68\x76\xec\x8d\xaf\xb5\xda\x83\x4d\xf2\x57\x29\x34\x20\x95\x85\x5d\xc7\x34\x93\x16\x11\xc8\x6e\x4d\xb1\xb3\xbc\x10\xe8\x34\x47\x11\x72\xd9\xa2\x2c\xb9\x24\x71\x43\xd6\xa2\xac\xb0\x02\xab\xa0\x40\xe8\x8c\xf3\x80\x34\xfc\xa3\x93\xce\x1a\x1f\x7c\xe6\x24\x79\x7b\x9d\x7c\x26\x60\x8f\x65\xc3\x24\x37\x7b\xda\xdc\x6a\x75\xe0\x15\x02\x93\xa0\x64\xd9\x30\x2e\xa3\x1c\xa5\x46\x8d\x52\x06\x37\xce\x12\x26\x8c\x02\x8d\xa4\x62\xb0\xa8\x33\xb9\x2c\x7a\x83\xa0\x35\xd8\x55\x6a\x19\xd0\x2a\xbb\x7d\x81\x9a\x42\xdd\xa7\x56\x69\x98\xdd\xde\xfd\xf2\xf3\x9c\x24\xf5\xe9\x26\xfb\x98\x2e\xb8\xd5\x4c\x9f\x96\x42\xc9\x1d\x18\xfc\x67\x87\x32\x06\x53\x50\xb4\x72\xd2\xe8\xf3\x56\x69\xe8\x0c\xdb\x8d\x00\x07\xc7\x06\x35\x12\xd6\x3b\x61\x81\x15\x4a\xbb\x80\x71\x9f\x13\x46\xdf\x4c\x87\x0b\xe0\xce\xc1\xc8\x37\x92\x19\xb9\xf7\x86\x55\xce\x80\xa2\xe3\xc2\x2e\xb9\x84\xba\x8f\xfd\xaf\xe7\xa8\x79\x7c\xfe\xba\x80\x63\xc3\xcb\x66\x88\x58\xc1\x28\x65\x1e\x64\x3e\x56\xac\x1e\x81\xd9\xf9\xe1\x0b\xe3\x0e\x59\xe5\x56\xd5\x9d\x10\xe0\xe8\x81\x5c\xd8\x40\x63\x6d\x6b\x36\xeb\xf5\x8e\xdb\xa6\x2b\x56\xa5\xda\xaf\x95\xac\x85\x3a\xae\x6b\xc1\x5b\xb3\x6e\x3b\x21\xd6\x2f\xbe\xff\xc1\x49\x62\x65\x89\xc6\xcc\x98\x10\xf3\x33\x16\x73\x04\xf4\xfb\xd5\x15\x00\x80\x27\x1e\x84\x06\xf9\xae\x71\x75\xee\x7d\x70\x86\x70\x6d\x6c\xb6\x9c\xe1\xc8\x7c\xd0\x34\x45\x8c\xe4\x04\xbd\xbd\xca\x39\x1c\x98\x06\xa1\x8e\x68\xec\x3b\x27\x7a\x03\x7f\xbd\x91\xf6\x0f\xff\xff\x7a\x50\x7b\x7f\x99\xdc\x9e\xd7\x7a\xd1\x3d\x01\x0c\xe4\xb5\xf0\xbc\x58\x01\x33\x1e\x2e\x9a\x9d\x40\x1d\x50\x03\x83\x3d\x6b\x5b\x57\x9b\x84\xd5\xaa\x2b\xd1\xad\x65\x3b\xcc\x1b\x28\xd0\x06\xb5\xf7\x4e\x6b\x08\xcc\x06\x1e\x1e\xc8\xd2\x3f\x3e\x3e\xa6\x11\x6a\x99\x6d\xc8\xd4\xc4\x9e\x88\x4c\xa5\x7b\x53\x61\x2b\xd4\xc9\x31\x09\xf3\xdc\x19\x6b\x77\x69\x21\xc5\x83\x80\x7b\x6f\xe2\x2d\xb3\xcd\x06\xa2\x87\xa0\xfb\x19\x2c\x97\xcb\x33\x2b\xd0\xc3\xb3\x75\x30\x2b\xb1\x2e\x63\x11\xb9\xca\xb8\xa4\x90\x20\x2b\x9b\x27\xf9\x99\xcc\xef\x8b\xad\x97\x7b\x61\xf8\x20\xf9\xac\x2c\xa0\xa8\xb7\xe4\x0d\x13\x82\xa8\x97\xf2\xa6\x8e\x12\xf5\x74\xc0\x16\xd9\x46\xb3\x08\xa9\xf7\x8d\x2f\x10\x57\x95\xc2\x23\xd6\x98\x68\xff\x53\xcb\x34\xdb\xe7\x92\xea\x42\x94\x08\xf1\x28\x21\x55\x59\x71\xb1\xdb\x75\x27\xa1\xe9\xed\x9f\xe5\x21\xe3\x11\x33\x8f\xe3\x41\x7f\x94\xea\xb2\xd3\x1a\xa5\xfd\x89\xa2\xef\x4b\x01\xb6\xb0\x43\xfb\x26\xfa\x7d\x36\x5f\xf9\x02\x4c\x76\xf3\x3a\x57\xb9\xab\xb8\xaa\x60\xbb\x05\xc9\x05\xfc\x9e\x6c\xa4\xbf\xcf\xef\xcc\x58\x96\x48\xf9\x94\xfa\x92\x13\x98\x09\xc6\x8a\xb5\x2d\xca\x2a\x17\xa7\xf9\xd5\x59\x74\xa4\x20\x80\xfc\x2e\x93\xb6\x3c\xde\xef\xb0\xd5\x68\x50\x5a\x03\x6c\x94\x55\xd7\x1d\xa0\x56\xc4\x07\x3b\x7e\x40\x19\xa6\x94\x28\xba\x39\x64\x1b\xab\xbb\x81\x27\xbd\x09\x51\x48\xc7\xc5\x5b\x9c\x03\xd6\xb3\xda\xe4\x5a\x67\xd0\x00\x10\xb7\x6c\x58\xcb\x25\xb7\xb3\x8c\xb0\xc5\x68\xd7\x7c\x94\x5e\x83\xa2\x5e\x15\x09\x9e\x8a\x89\x1c\xba\xa5\x3e\x28\x5b\x2f\xf5\x6b\x73\x70\x4b\xad\x35\x9f\x87\x37\x9e\x60\x4c\x1a\xb7\x30\x09\x10\x7f\x13\x51\x97\x8d\x56\x52\x09\xb5\xe3\x25\x13\xe2\xe4\x58\x51\x57\x81\xa7\x99\x31\xaa\xe4\x7e\xd4\xfb\x0f\xf3\x14\x9b\x39\x9d\xb2\x96\xed\xf0\xb3\xb9\x6a\x51\xdf\x7e\xc9\x3a\xab\x2c\x13\xef\x51\xee\x88\xb3\xbf\x24\xff\x66\x03\x0f\xb1\xcd\x39\x1c\xc4\x16\x2e\xc6\xa6\x2c\x72\x3a\x17\x13\xc2\xb3\x70\x21\xf1\xb0\x75\x71\xc8\xbc\xf4\xda\xe8\xbd\xff\x76\xb9\x24\xd2\x0f\xdb\xd8\x9a\x09\xb8\x99\x1e\x6f\xe6\x49\xc0\xbd\xe9\x47\x94\x3f\xa3\x6d\x54\x65\x26\x90\xf6\x33\x5a\x8b\xda\xd5\x75\x34\x73\xa6\x1d\x8c\xd9\x6c\xcd\xaf\xe0\x96\x49\x5e\x1a\x62\x53\x37\xb5\x53\x97\x33\x63\xc4\x01\x72\xdb\xa0\x1e\x14\xb6\x1a\x4b\xac\x1c\x8c\x01\x3f\x96\x88\xa1\x23\x0d\x83\x49\x38\x93\xad\xe0\x17\x65\xd1\x9f\x7f\x58\xde\x2e\x32\xda\xab\xa2\x79\x5d\x08\x37\x80\x16\x18\x90\xe2\x4e\x02\xbd\xd6\x4e\x5a\x2e\xfa\xc5\xcf\x5f\xac\x92\x3a\x88\x3a\x1c\x4b\xba\x09\xf5\xb6\xc4\x97\xf3\x10\x47\x3d\xce\x6a\x8e\x07\x9c\x8c\xdb\xa5\x0e\x8d\xb6\xd3\xd2\x89\x9d\x0a\x34\x09\xbb\x0c\x35\x0d\x65\x09\x15\xf8\x72\x9d\xac\x66\x6a\xac\x5e\xc3\x5f\xea\xbb\x41\xfe\x2c\xf5\x2e\xc3\x8f\xf3\xcd\x14\x53\xb7\x1a\x73\xe0\x4f\xfa\xde\x37\xae\x63\x6e\xe0\xba\x6f\x32\x0d\xf3\x87\x82\x13\x51\x3b\xa2\x74\xf5\xc8\x99\xe0\xff\xc2\xea\x3a\x11\x16\x73\xee\xab\xed\xa5\xec\x6f\x36\x70\x7d\x97\xc7\xd7\x00\xa8\x31\x82\xa6\x35\xbc\x9c\x9c\x12\x36\x70\x7d\x9f\xcb\x4c\xef\x45\xaf\xe3\x7a\x54\x7b\x10\x46\x12\x2e\x2b\xfc\x98\x76\x0d\x58\x66\xdc\x39\xb3\x9a\x31\xa8\xed\x2c\x9d\x4e\x9c\x90\x57\x5b\xf8\x0e\xbe\xfd\x36\x3c\xbd\x0c\x19\x9a\x39\x59\xb9\xe9\x40\x38\xd2\x98\x2f\x12\x51\x7b\x34\xc6\xb1\xdd\xf5\xad\x56\x85\xc0\x3d\xd4\x5c\xba\x1e\x91\x76\xf7\xfe\x22\xc4\x29\x3b\x3b\x77\x9e\x2b\x02\x74\x63\x74\xa4\x0d\x36\x7a\x18\xba\xec\x94\xad\x0f\x4e\xcd\xe3\x3c\xa1\x2d\x37\x7e\xf8\x92\xa2\xe9\xc3\xd1\x6a\x38\xfd\x3f\x71\x51\xb3\x98\x6a\x87\x53\x15\xee\xbb\x81\x3f\x76\xec\xb0\x3f\x41\x47\xf5\xbc\x80\xef\x96\xce\xbe\x70\xfe\x8a\xf7\xf6\xbd\x83\xb6\x87\x9d\x97\x47\x2c\xab\x80\xcb\x52\x74\x15\xd2\x86\x73\x63\xc8\x51\xc1\x8f\x93\x1d\x37\x3a\x63\xe4\x86\x00\x2e\xa1\x6c\x54\xe4\xb1\x8f\x43\x32\x04\x0c\xda\xbe\x76\x18\x38\x70\x3c\x3a\x0e\xd9\xa1\x9d\xb0\x6f\xf6\x77\x78\xb2\xaf\x8e\x98\x24\x3f\x4b\xfc\x0f\x49\x25\x2d\xcb\x7c\x3b\x87\x2d\x3c\x3c\x26\xeb\xd2\x5e\xfc\x85\x15\x77\x1e\xe6\xe9\x64\x6e\x2c\xd3\xf6\x26\xd0\x80\x83\xd8\xb3\x8b\xde\xcf\xeb\x78\xd9\xab\x44\xed\x28\x1e\xb1\xb4\xdc\x6c\xf0\x29\x51\x8e\xb2\xea\x17\x47\x3b\x9f\xe7\x0c\x18\x96\x3e\xa5\x3e\x92\xf7\xb4\x72\x5f\xbc\x84\x68\xdc\xb7\xf6\xe4\x1d\xe7\x75\x3f\x13\x0c\x9d\x5e\x30\x63\xd3\x51\x29\x8d\xc5\x76\x7b\xd6\x99\x9a\x92\x21\xa0\x18\x8e\x1e\x8c\xf4\x6f\x04\xc5\xf0\x65\x34\xe3\x45\x0f\xe7\x41\xcf\xff\x1f\x9f\xa3\x62\xdf\x6e\xac\xbf\x47\x73\xf7\x21\x3d\x17\x31\x59\x51\x95\x86\xc9\xd9\x79\x9c\xa9\xd4\x04\x5f\xa3\x63\xe2\x13\x7d\x81\x66\x1b\xbe\x08\xe5\xea\xae\x10\x26\x40\x68\x04\x2f\x71\x46\x44\xb9\x81\x1b\x69\x67\xe7\x60\xce\x17\xd0\xb5\x1f\x94\xff\xb9\x8f\xea\x7c\x3c\xc3\x86\x5b\xbf\x70\xb6\x9c\x38\xec\x06\xc2\xbf\x78\x0b\x69\x63\xdd\xa4\x0e\x3e\x4f\x41\x18\xca\x89\x8f\x5a\x54\x62\xc8\x13\x3d\x23\x2f\xeb\xf1\x42\xd6\xfc\x2a\xff\x14\x65\xf4\x33\x58\x4a\x04\x44\xc0\x4a\x7f\x1f\x83\x2c\x79\x3b\x09\xb8\x4c\xec\x37\x63\xa4\x5c\xf4\xc5\xd1\x90\x3e\x31\x92\x7e\xe5\xbd\xe2\xd4\x64\xfa\xdf\x95\x3e\xd5\x56\xde\x47\x40\x99\xcd\xfb\xae\x11\x61\x33\x58\x74\xd9\x10\x5e\xbf\x86\x96\xce\x1d\xb3\x2f\xeb\x08\x49\x30\xdd\x79\x30\xae\x80\x4b\xf1\xae\xdd\xa4\xef\x73\xd7\x5e\x49\xeb\x70\xab\x72\x17\x8f\xb0\x85\x75\xb8\x2a\x5d\xbf\x15\xea\x98\xb9\xe9\x19\xb6\x5d\xa5\xe2\xc2\x1d\xe7\xca\xb0\x03\xce\x5e\x2e\x4b\x8d\x44\x40\xc3\xea\xd9\x9c\x58\x6d\x33\xad\xba\xf7\xfb\xd3\xd5\xbf\x03\x00\x00\xff\xff\x71\x04\x28\x86\x29\x1b\x00\x00" +var _randombeaconhistoryCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x5b\x5f\xaf\xdb\xb6\x92\x7f\xcf\xa7\x98\xe6\x21\xb5\x5b\x1f\x3b\x49\xf7\x16\x85\x11\xa7\x9b\x06\x4d\x13\xa0\xbd\x1b\x9c\x64\x71\x1f\x82\xe0\x86\x96\xc6\x16\x6f\x64\x52\x25\x29\xfb\xb8\x45\xbe\xfb\x82\x43\x52\x22\x25\xca\xe7\xa4\x48\xf7\x00\x6d\x64\x99\x9c\x7f\x1c\xce\xfc\x66\x48\xaf\x56\x2b\xb8\x66\xa2\x94\x87\x9f\x90\x15\x52\xbc\xe4\xda\x48\x75\x86\xd9\x8b\x5f\x5f\xbd\x86\x47\x8f\xbf\x9b\xdf\x5b\xad\x56\xf6\x3f\x78\x5b\x71\x0d\x85\x14\x46\xb1\xc2\x80\x1d\x86\x1a\x4c\x85\x50\xf9\x39\x72\x07\x8a\x48\x81\x96\xad\x2a\x50\xc3\x1e\x05\x2a\x66\xb0\x84\xed\x99\x86\xbe\xa8\xe5\x09\x04\x9a\x93\x54\x1f\x97\xf0\xb6\x42\x28\x71\xc7\x05\x96\xf0\x12\x99\x32\x5b\x64\x06\x14\xba\xe9\xc0\x35\xf1\x6d\x9b\x72\x44\xe2\x0d\xaa\x23\x2f\x10\x9e\x15\x85\x6c\x85\x01\x66\xe8\x3b\x14\xa5\x95\x02\x8f\xa8\xce\xb0\xad\x65\xf1\x11\x4e\xdc\x54\x60\x2a\x66\xdc\xe7\xaf\xb5\x17\xae\x97\x56\xa0\xd6\xcb\x4e\xcb\x7f\x55\xbc\x46\x22\xe6\xc7\x1d\x59\xdd\xa2\x06\xa6\x10\x34\xdb\x61\x7d\x1e\x6b\xe5\x0c\x08\xce\x82\x30\x13\x52\x5c\x35\x0a\x4b\x5e\x18\xb6\xad\x71\x01\xad\xd8\x72\xa6\xdd\xf3\x11\x15\xdf\x71\xfb\x3c\x07\x26\x4a\xe2\x69\x14\x13\xfa\xc0\x8d\x25\xc9\x85\x91\x4e\x97\x1b\x2c\x5a\xc3\xa5\x00\x14\x47\xae\xa4\x38\xa0\x30\x70\xe4\x8c\xbe\x2d\xe4\xc1\x4e\xe0\x62\xef\x66\xb3\xc2\x0e\x5d\x40\xab\xe9\x55\x85\xa0\xd8\x29\xc8\xbe\x53\xf2\x00\x26\x5e\x3e\x62\x5b\x4a\xd4\x20\xa4\x81\x7d\xcb\x14\x13\x06\x11\xac\xe8\xca\x9a\xcf\xf0\x6d\x8d\xb1\x81\x68\xb1\xec\x22\xd1\x1a\x71\x6d\x05\x45\x51\x62\x09\x46\xc2\x16\xa1\xd5\x24\xbc\xe5\xf0\x9f\x56\x90\x34\xce\xf6\xcc\xcb\x4a\x74\x59\x4d\x9c\x0f\x58\x54\x4c\x70\x7d\xb0\x93\x1b\x25\x8f\xbc\x44\x60\x02\xa4\x28\x2a\xc6\x45\xb4\x44\xd3\x02\x11\xa1\x57\x24\x0a\xab\xb5\x04\x85\x96\x4f\x27\x52\xab\x73\xab\xe8\x25\x6a\x34\xb6\xa5\xbc\xf2\xde\x2a\xda\xc3\x16\x15\xd1\xf3\x4b\x2b\x15\xcc\x5e\x5f\xff\xf3\x97\xb9\xa5\x14\x96\xdb\x0a\xc8\xd4\x96\x1b\xc5\xd4\xf9\xaa\x96\x62\x0f\x1a\x7f\x6f\x51\xc4\xce\xe4\x19\xf5\xfe\xf4\x42\x2a\x68\x35\xdb\x0f\x1c\x0e\x4e\x15\x2a\xb4\xbe\xde\xd6\x06\xd8\x56\x2a\xb2\x18\x77\x0b\xc2\xec\x93\x6e\x71\x01\x9c\x14\x8c\x74\x73\x0e\xd3\xeb\xf7\x9c\x95\x24\xc1\xb6\xe5\xb5\xb9\xe2\x02\x76\xc1\xfa\x1f\x7a\xbb\x39\x07\xfd\xb0\x80\x53\xc5\x8b\xaa\x33\xd9\x96\xd9\x45\x93\xc2\xd1\xac\x9c\x83\xa7\xde\xdc\x2b\x72\x8d\xac\xa4\x41\xbb\xb6\xae\x81\xc2\x83\x55\x61\x0d\x95\x31\x8d\x5e\xaf\x56\x7b\x6e\xaa\x76\xbb\x2c\xe4\x61\x25\xc5\xae\x96\xa7\xd5\xae\xe6\x8d\x5e\x35\x6d\x5d\xaf\x1e\x3d\xfe\x8e\x28\xb1\xa2\x40\xad\x67\xac\xae\xe7\x7d\x2c\xc9\x05\xa0\x3f\xef\xdd\x03\x00\x70\x81\x07\xa1\x42\xbe\xaf\x68\x9f\x3b\x15\x48\x10\xae\xb4\xc9\x6e\x67\x38\x31\x67\x34\x65\x2d\x66\xe9\x78\xbe\x81\xe5\x1c\x8e\x4c\x41\x2d\x4f\xa8\xcd\x4b\x22\xbd\x86\xff\x7d\x25\xcc\xf7\xff\xf5\x63\xc7\xf6\xcd\x78\x71\x43\x5c\x0b\xa4\x43\x00\xe8\x82\xd7\xc2\xc5\xc5\x12\x98\x76\xee\xa2\xd8\x19\xe4\x11\x15\x30\x38\xb0\xa6\xa1\xcd\x69\x7d\xb5\x6c\x0b\xa4\xb1\x6c\x8f\x79\x01\x6b\x34\x9e\xed\x1b\xe2\xea\x0d\xb3\x86\x77\xef\xac\xa4\x3f\xbc\x7f\x9f\x5a\xa8\x61\xa6\xb2\xa2\x26\xf2\x44\xc1\x54\xd0\x37\x25\x36\xb5\x3c\x53\x28\x61\x2e\x76\xc6\xdc\x69\x59\x2c\xe3\x8e\xc0\x1b\x27\xe2\x6b\x66\xaa\x35\x44\x1f\x02\x6f\xf8\xf9\x68\x69\xa1\x8f\x5e\xa7\x0a\x05\x1c\xb8\xa6\x28\xf4\x46\x5e\xfb\xd8\xd3\x30\x6d\xa0\x0a\x34\x5d\x30\x2d\xd1\x60\x61\xe7\x30\x51\xc2\x89\xd7\xb5\x8d\x22\x5b\x56\x7c\xdc\xf1\xba\xc6\x72\x1d\x18\xc0\x15\x7c\xa0\xd0\xed\xd6\xe9\x83\xf5\x5e\xd3\x7b\x84\xdb\x47\xf6\xc5\x9e\x35\xf6\xbb\x40\x37\x9e\xbf\x67\xcd\x1b\xc3\x94\xc9\x93\xf0\x46\x73\xee\x14\x84\x47\x61\xd4\x39\x25\x16\x1b\x09\x49\x6d\xe7\xb8\x7e\x65\x7e\x73\x33\x67\x91\xb0\xc1\xa9\x16\x90\x4a\x10\xde\xcf\xef\x6c\x46\x6b\xb1\xde\x38\x20\xdd\x6a\x16\xad\x52\x76\x5e\x67\xda\xcf\xb7\x5a\x20\x0a\x15\x6b\x1a\x14\x58\x52\xcc\xa1\xf0\xe0\x52\xb3\x9b\xf7\x46\x5e\xbb\x10\x6f\xa3\xbd\x9f\xf2\x17\x2d\x1c\xa9\x41\x46\x8e\xc9\x90\x47\x76\xb3\x5d\x60\xb6\xb3\x07\x73\x38\xea\x30\xeb\x9f\xd2\xa0\xcb\xef\x5c\x00\xe5\x7d\x65\x6d\x55\x30\x8d\x7a\x91\x68\x88\x25\xb9\xc8\x81\x9d\x29\xc6\x6e\x91\x02\x10\xdf\xb7\xb2\xa5\xf4\xc6\xb5\x5f\xd5\x90\x17\x03\x0b\x1b\xf0\xce\xde\x1a\x43\x8a\x5e\x1a\xb7\xbd\x6c\x82\x65\x1a\x97\x77\xf1\x96\x9f\x3a\x1a\x9f\xe3\x30\x0b\x20\x0b\x0d\xfd\xe7\x1b\xb8\xba\xba\xea\xb3\xb3\xfd\xf0\xcd\xca\x8b\x9f\x04\x89\x4c\x60\xb0\x46\x60\x5c\x90\xcf\xb3\xa2\xba\x08\x93\xac\x9a\x21\xe7\x05\xba\x23\x65\x3b\xca\x3d\x33\x1f\xcc\x83\x24\xcf\x59\x5d\x5b\x04\x64\xc3\xa7\x3c\x09\xb7\xc4\xf9\xb8\xb5\xc8\xe2\xbd\x85\x8f\xc0\xce\x4d\x3c\x80\x28\xd3\x28\x1d\x73\x4c\xb8\xff\x77\xc3\x14\x3b\xe4\x62\x2b\x99\x28\x21\xe2\x82\xb5\x65\x35\x49\xee\x6d\xc0\xa4\x8d\x92\x46\x16\xb2\x86\x03\xfb\x88\x1a\x74\xab\x68\x7a\xc1\xea\xda\xf9\x46\x97\x99\xa5\xcd\x2a\x0d\x2a\x0f\x51\x6d\xae\x00\x7d\xd6\x06\x0f\x34\xda\x61\xad\x08\xd6\x25\xfc\x0a\x79\xb0\x70\x34\x05\xbc\xdd\xc2\x81\x96\x6e\x37\xc4\xe1\x21\x2c\xa9\x8b\x69\x5b\xf4\x24\x8e\x8c\xbb\x65\x90\xa2\x3e\x87\x04\x51\x54\xbc\x4e\x94\x75\xb3\x97\x59\xfd\xe3\x65\xdf\xb5\xa2\x8f\x43\xb3\x7c\xe6\x72\x89\x6b\x1e\xfb\x03\x91\xd1\x1a\x95\x99\x25\xef\xfc\xde\x4b\x57\xe3\xd0\x6a\xda\xb8\xcc\x40\x8d\x36\xa7\x3c\x7a\xfc\x03\x6c\xb9\xd1\xa3\xa9\x19\xfe\xcb\x1a\xc5\xde\x54\xf0\x74\x43\xd3\x56\xf0\xc3\x62\x34\xed\x80\xda\x62\xb5\x35\xdc\xbf\xbe\x1b\xe3\xfb\x09\x89\x79\xaa\x97\xcd\xa4\x7e\x0d\x7e\xea\xf7\x38\x6c\x60\x8f\xe6\x79\xf4\x7e\x36\x5f\xba\x40\x79\x6f\xa0\x3d\x17\xdc\x78\x94\x12\x13\xe0\x3b\x8f\x0b\x6d\x1c\xd3\x68\xe0\x8c\xe9\x54\xbe\xcb\x61\xaa\x65\x8c\x77\x60\xb3\x01\xc1\x6b\xf8\x73\x64\x83\xdb\x67\x66\x94\x4a\xa8\x7c\x4a\x3e\x59\xa4\x75\x60\xe7\x2d\x76\x21\x4f\xc1\x26\xcb\x65\x2b\x95\x92\xa7\x7e\xd8\x6c\x3e\x34\xc8\x73\x85\x16\x89\x3f\x00\xcd\x8e\x08\x11\xc1\xd4\x24\xec\x88\x65\xce\x28\x23\x31\x3e\xc7\x06\x1e\x2f\x2d\x2d\xf5\xd9\x93\xab\xc2\x89\x12\x4b\xbb\x00\x23\xd7\xb0\xf2\xb0\x6e\xa5\xc6\x44\xfa\xd1\xf3\xb1\xef\x7d\x09\x1b\x7d\x1a\x39\xe0\x36\xa6\x38\xe4\xf1\xe3\x8f\xd0\x30\xc1\x8b\xd9\xfd\xd7\x4a\x6e\x6b\x3c\x80\xa3\x6f\xb3\x41\x3f\xf1\xfe\xc0\xaf\x6d\x14\xaa\xb0\xf8\x08\x3b\xa9\x80\x89\x33\xe0\x0d\xd7\x54\x8b\xda\x04\x6b\xf1\x5c\x87\x2a\xfa\x7a\x94\x8b\xa6\x35\x83\xfd\xcc\x77\x20\x10\x4b\x2c\x97\x09\xfd\x9e\xf3\x32\x3c\x26\x01\x65\x9d\xdb\xde\x63\x19\x4f\x48\xb0\x49\xc8\x53\x08\x97\x85\x54\x0a\x0b\x0b\x15\x4a\xbc\xe9\x63\x7b\x5c\x28\x26\xf9\x6e\xa4\x35\xad\x79\x07\xfb\xbb\x88\x6f\xed\x30\x0a\xb8\xf7\x6e\x73\xa8\x5c\x8c\x22\x10\x56\xe6\xc2\x67\xbf\xce\x6e\x8d\x3f\x25\xb9\x3f\x5a\xd3\x7c\xf6\x7f\x46\xba\x12\x40\xea\xab\xef\x12\x35\xdf\x8b\x14\xd5\x11\xee\xc4\xb2\xab\x75\x46\x00\x80\xf2\x0e\x01\x1a\xb9\xb3\x65\x8d\x1b\xee\x8a\x7c\x2a\x2c\xca\xb6\x03\x07\x96\x70\x97\xdb\xa2\x8c\x06\x3b\xc6\xeb\x56\x79\xa8\x74\x11\x45\x44\x9a\xfd\x99\xe4\x25\x82\x47\x7e\x29\x13\x80\x49\x85\x80\x93\xf3\x43\xc6\x90\x1f\x7c\x59\xe6\x2b\xf0\xb4\xb2\x3b\x21\x79\x8c\xe9\x4a\xbc\x45\x9a\x0b\xb1\x60\xb6\xf2\x26\xcd\xbb\x6c\x97\x6a\x03\x3d\x30\x50\x48\x70\x73\x4b\x8d\x95\x08\x6e\x5a\x11\x3b\x5b\xfa\xf6\xd9\xa2\x07\xd1\xaf\xac\x4e\x1d\x0a\xd6\x63\x3d\x5d\x38\xbe\x1a\x24\x0d\x6a\x60\xb0\x26\x95\xe2\xd5\x0e\x84\x74\xfc\x68\x97\x66\xd9\xe0\xef\x2d\xab\xc1\x37\x9e\x2e\xd9\xcc\xa5\xd0\xe5\x10\x00\xa4\x65\x75\x42\x3f\x40\xd5\x44\x28\xbb\xa6\x2f\x78\x5d\xdb\xd8\xc0\x35\xd4\xfc\xc0\x8d\xf3\x42\x5b\x26\xdf\xf0\x43\x7b\x88\xc0\x7f\xc0\xd8\x16\x31\x39\x34\x25\xdd\x94\xd0\x09\x6b\x5a\xc3\xc8\xab\x0a\xa9\xcd\x70\x11\xb8\x86\x03\x32\x61\x31\x56\xcd\xd4\x1e\xfb\x22\x00\x6d\xf1\x09\x3b\x3c\x11\x55\xed\x7a\x3d\xc6\xc3\xfd\x1e\xe3\xdf\xa2\xed\x81\xdd\xfc\xec\x04\x7c\x8d\xca\x22\xdb\x4e\xe3\x6e\x9e\x4d\xe3\xb3\xf9\x20\xcd\x68\xac\x77\xcb\xc4\x54\xb0\xf1\x33\x67\x77\x8d\x16\x6e\x39\xe6\x63\xba\x23\xa1\x60\x03\x8f\x1e\x3e\x8c\x42\x48\x16\xc3\x1d\x39\x9e\x08\xc8\xed\xd1\xfc\x36\x24\x31\x9b\x43\x50\x6d\xa0\x8a\x42\xd3\x2a\x31\xc1\xf9\x36\x9e\x96\x9d\xce\xb1\x3b\xb0\xce\x79\x86\xb6\x9b\x02\x8c\x07\x76\x03\x4f\xe1\xe1\x25\x5c\x67\x5d\x26\xb8\x98\xc3\xc3\x9d\x5b\x05\x90\xa7\x8d\xe2\x85\xa9\xcf\xd0\x48\xcd\x0d\x3f\xe2\x10\xe4\xdd\xc9\xdc\x07\x76\x93\x53\x9d\x7a\x82\x5c\xf8\xd2\x65\x94\x90\x28\x02\xfb\x9e\x35\x82\xc0\x53\x1a\xa0\x96\x76\x3f\xb3\x61\xab\x63\x41\x8d\x03\xa2\x98\xf0\xc9\xb5\x27\x5c\xe8\xbe\x08\xe7\x23\xff\x0e\xfe\xb0\xe3\xa2\xfc\x85\x35\xcf\x44\x79\x4d\x6b\xfd\xdc\xc9\x4d\x6e\x3b\x9b\x47\x6e\xf1\x65\xf1\xef\xe7\x9a\x67\x38\x3f\x14\x43\xa4\x73\xcb\xec\xbe\xce\x6c\xa3\x77\x45\xa4\xcd\x7b\xd8\x38\xa8\xe2\x8c\x37\xd2\x67\x80\x84\x6f\x03\xcb\x5f\x8d\x0d\x12\x31\xcb\x42\x69\xb8\x4a\x98\x8c\x60\x0d\xb7\x1e\x60\x55\xcf\x7a\x01\x30\xe1\xd4\x1d\x61\x70\x8a\xe0\xbf\xba\x1a\xe8\x8b\x44\x1a\xbe\x4b\x95\x79\x9a\xb0\x18\x83\x6a\xab\x7d\xda\xd6\x80\x4d\x6a\xcf\x6f\x63\x0a\xa3\xf9\xa4\xde\xed\x2d\xb7\xb1\x45\xc7\xdd\x94\xf4\xf3\x25\x08\xed\x63\x5b\xac\xe8\xd4\xb6\x0e\x48\x45\xdb\xc0\xa1\xe9\xbc\x02\x0f\x8d\x39\x77\x09\x6c\x66\xd3\xf0\x7c\x90\xf7\x7d\x66\xa5\x2c\x6f\xf7\xa7\x3f\xac\xc1\xd0\x49\x1e\xa4\xeb\x14\x8f\x38\x78\x9d\x64\xec\x0f\xbe\x9f\x60\xb3\x1b\xe1\xf3\xba\x0e\x02\x8c\x90\x81\x21\x88\xe2\x40\x32\x61\x84\x2c\x3a\xf8\x8f\x8d\x8b\xe1\x14\xce\x48\xf8\x90\x71\x9a\x0f\x5f\xeb\x11\x38\x18\x34\x4a\xfe\x55\xa1\xe8\xd2\x2a\x17\xfb\x45\x2c\x5a\x7f\x5e\xc3\x0e\x48\x2f\x65\x73\x5e\xc2\xcf\xac\xa8\x7c\x98\xb6\x30\xe5\x86\xe2\x92\x55\xcc\x1a\x69\xa0\x36\xd9\x22\xe1\xa8\x5b\x0a\x68\xfc\x88\x50\x31\x5d\x71\xb1\xf7\x7d\xbe\xb8\x43\x53\x61\x27\x83\x35\x84\x95\xa9\xa4\x6a\xa6\x30\xc0\x6a\x53\xc9\x76\x4f\xd1\xe6\x1c\xfa\x4c\x09\x8b\x91\xc8\x53\xda\x13\x02\xa3\x46\x8b\x2c\x8a\x56\x05\x27\xa0\x66\x65\x84\xa6\x2f\x40\x65\x78\x15\xf5\x18\x17\x89\xd0\xda\xf0\x28\xd1\x5a\x76\xbe\x3d\xe9\xfc\x83\x85\xe2\xc6\x02\xdb\x8f\x42\x9e\x44\x28\x88\x0c\x3f\x10\x98\x75\xbd\xa3\xee\xc8\x31\x1c\x14\x75\x07\x83\x1a\x5a\xe1\x8e\x34\x99\x61\x5b\x5e\x73\x73\xbe\x00\x8b\x6c\xc6\x98\x28\xdc\xa6\x5a\x40\x99\xc8\x48\x99\xf5\x72\xe2\xf9\xfb\x63\xdc\x6a\x05\xb2\xb1\x26\x61\x35\x3d\x1c\xf8\x1f\x0e\x6a\x86\x92\xaf\x62\x4d\x73\xa6\xea\x47\x0a\x3a\x83\x59\x53\x55\xeb\x21\x77\x7c\xd0\xb1\x18\x12\x8e\xf6\x02\x70\x2d\xbe\x36\xbe\x18\x5e\x84\xa0\x83\x4c\xd5\xe7\x8b\x21\x77\xb3\xc9\xe1\xc8\x4c\xec\xcd\xa2\xcd\xd8\x5a\xdf\xc2\xa3\x71\x13\x8d\xc4\x18\x04\xc7\xa1\x16\xaf\x26\xd2\x51\xd7\x50\xf4\x35\x71\x72\x4c\x4d\x79\x3c\x1c\xda\x50\x3e\x4f\x22\xe5\x72\xc8\x84\x76\xad\x3b\xa1\xd0\xc9\xd6\xb1\x7b\xc1\x57\x72\xbd\x14\xb4\xc3\x74\x5a\xac\xdd\x56\x83\x86\xbf\x13\x9d\xff\x0f\xf2\xda\x5f\x73\xa4\x3b\xf6\x95\x2e\xb4\x01\xde\xbd\x1f\xa6\xa6\x14\x06\x7f\x69\x6f\xb7\x5b\x48\xe0\xe9\x67\x0a\xb8\x9b\x04\x2b\x8d\xc6\xf1\x78\x9b\x26\x9e\x35\x1a\xea\xee\x68\x6c\xe0\xa1\x4d\x4d\x83\x72\x30\xb6\xb9\x1d\xf5\x64\x0a\x51\x8f\x8d\xb9\x5a\xc1\x41\x1e\x31\xd4\xad\x02\x6f\x4c\xe4\x46\xe7\xd1\x78\xc7\xc6\xc9\xfd\x24\x31\xde\x83\x07\x77\x5d\x99\x77\x34\xfd\x7d\xd7\x4b\x86\x87\x19\xc1\x80\x6a\x3e\x67\x1e\xf7\x6f\x6e\x77\x7d\xca\x29\xc4\x77\x70\x42\x50\xd4\xd0\x8f\x1a\xfc\xf6\xd1\xe1\x04\x63\xb3\x28\x25\xce\xa8\xe7\xa6\x61\x2f\x8d\x8f\xf9\x23\xaa\x7c\x17\x84\xd9\xdc\x02\xd0\xec\xdf\x56\x21\xfb\x78\x37\x59\x6d\xfc\xea\x77\xf2\x25\xcb\x77\x37\x35\xba\x84\x52\x3a\x60\xae\x2b\xf6\x1d\xc8\xd6\x34\xad\x81\xd9\xe3\x7f\x7c\x4f\x3d\xf5\x39\xf5\x04\x6c\xad\x6e\x43\x2c\x73\xbb\xfd\xc0\x85\xad\xd9\xb2\x67\x04\xf8\x7b\xcb\x2d\x54\xd2\xfc\x0f\xb7\xdb\x05\x9d\x57\xce\x42\x93\x7e\xdc\x6b\x8d\x9c\xfc\x25\xd3\xd5\xb3\x7a\x2f\x15\x37\xd5\x61\xf9\xe6\xe5\xb3\xef\xfe\xfd\xf8\x1f\xdf\x2f\x2d\x60\x98\x85\x61\x63\x02\x9f\xe7\x2e\xb0\xe9\x38\x8e\x97\xe7\x56\x47\x09\xdb\xc7\xfd\x3b\x1c\x31\x8e\xc8\x49\x19\x00\x6c\x2b\xdb\xd1\xa1\x6b\x38\x40\xed\x84\xd8\x79\xea\x79\x87\xce\x22\xf7\x5b\x4b\x1f\xf8\xf6\xb6\xe0\x00\x59\x54\x1f\x1d\x8d\xe6\x5d\x34\x46\xfb\x53\xa5\xe4\xb8\x07\x60\xff\x2e\xd7\x01\xf9\x39\xfe\xc4\xb5\xbf\x2c\x11\xff\x5d\x8c\xcf\xab\x95\x05\x02\x07\x19\x9d\xdd\xfb\x8e\x57\x57\x20\x6c\x5b\x63\x37\x3c\xf5\xa2\x8c\xf4\x28\x7b\x08\xc2\x87\x44\x8d\x5c\x0f\x5f\xc1\xd5\x30\x06\x3a\x7f\xe2\x3b\x17\x1f\x08\x21\xba\x98\x31\x31\xd5\x47\xb4\x24\xd8\xa4\xa7\x8c\x26\x3d\x95\xb4\xb4\x47\x05\x44\x26\xae\xff\x3f\x04\xdc\xcb\x7b\xe8\xd3\xd0\xd5\xef\x18\x0e\x47\x54\xbd\xf5\xd9\x47\x74\xd7\xf4\xfc\x89\x10\x19\xa6\x6d\x0a\x79\x20\xb0\x4d\xa9\x3b\xd8\xcb\xc6\xa1\xdc\xe1\xc3\x05\x01\xb3\x50\x8d\x0f\x0a\x4f\x18\x9d\x00\x5c\x67\xce\xb3\xf3\x47\x01\xd7\xd8\x28\xd4\x28\x8c\xad\x13\xd3\x03\x19\xba\xbd\xe6\xca\x46\xd8\xf3\xa3\x2d\xd8\xa8\x32\x88\xda\x33\xb9\x66\xbd\x36\xaa\xed\xee\x71\x39\x11\x22\x7b\x0e\x2f\x17\x65\xee\x3b\x4c\x8e\x25\x81\xba\xb2\x61\xd0\x52\xcd\x5e\x9c\x48\x67\x64\xbb\xae\xdb\xa4\x19\xb5\x9d\x38\xc5\xa4\xa1\xce\x20\x1b\x47\xf5\x73\xed\xff\x9a\xed\x71\x62\x0d\x9e\xbb\x5b\x17\x3a\xb5\x99\xbf\xa5\x28\x55\x89\x36\x9f\x15\x95\x92\x42\xd6\x72\xcf\x0b\xea\x5d\x59\x7f\x53\xa5\xbf\x43\xc6\xb4\x96\x05\x77\xc7\x51\x7f\x71\x8d\x62\x31\xa7\x97\xab\xa1\xc6\xe9\x2d\xeb\xd4\xa0\x7a\x7d\x97\x71\x46\x1a\x56\xbb\x1d\x77\xb7\xb5\xd7\x6b\x78\x17\xcb\x3c\xf4\x81\x58\xba\xc5\x50\x8c\x45\x8e\xdf\x62\x82\x70\xd6\x55\x2c\x79\xd8\x90\x0d\x32\x5f\x3a\x6e\xf6\x7b\xf7\x34\x1e\x12\xf1\x87\x4d\x2c\xcd\x84\xab\xe9\xe0\x6b\xfa\xa2\xb3\x3d\x0f\x57\x27\x7f\x43\x53\xc9\x52\x4f\x78\xd9\x2f\x68\x0c\xaa\xae\x56\xcd\x5e\xe9\x61\x26\xbb\xd7\x97\xf0\x9a\x09\x5e\x68\x1f\xe5\x09\x64\xa1\x1e\x7a\x1b\x20\xb7\x29\xa0\x63\xd8\x28\x2c\xb0\x24\x17\x06\xbc\x29\x10\x7d\x9f\xbb\xbb\x30\xe9\x9b\x5e\xcb\xe8\xde\x16\xcb\xcb\x65\x85\x76\xac\x84\xbb\x1e\xe8\x2f\x6d\x39\x2f\xb1\xc9\xb3\xe3\xda\x0a\xc3\xeb\x30\xf8\xdb\x47\xe9\xa1\x62\x74\xe5\x87\x25\x6d\xd6\xb7\x15\xa6\xba\xf4\x97\x4b\xa5\x2d\x7d\x15\xc7\x23\x5e\x3e\x1a\x4e\x78\xf8\xa2\xfd\xed\x05\x43\x53\xc5\x3b\x32\xb5\xad\x8a\x92\x30\xe0\xb6\xea\xe4\x4e\xa6\x13\x13\x1a\xf9\x3f\xbb\xeb\x8e\xfe\x2c\xd5\x2e\x13\x1b\xe7\xeb\xa9\x08\xdd\x28\xcc\x39\x7f\xd2\x9b\xfd\x8a\xee\x4a\xac\xe1\x7e\x48\x2e\x15\x73\xb7\x2d\xce\x36\xa4\x23\x0a\xda\x8f\x9c\xd5\xfc\x0f\x2c\xd3\x13\x93\x38\xde\x3e\xdd\x8c\x69\x7f\xb5\x86\xfb\xd7\x79\xff\xea\x1c\x6a\xe8\x41\xd3\x1c\x9e\x4c\x62\xc2\x35\xdc\x7f\x93\x5b\x99\xa0\x45\xe0\x71\x7f\xb0\xf7\xc0\x43\xe0\x00\x08\xb6\x49\xb3\x7e\xac\x4e\x1f\xd1\x32\xe7\x54\x8e\xc8\xd3\xcd\xf0\x98\xaa\x3f\xa2\x0a\x57\x31\x76\x5c\x50\xb0\x4f\x53\x74\x68\x1b\x13\x9d\x5e\xd2\xf9\x1d\x98\x3e\x09\x3d\x02\x12\xf9\x52\xe7\xe2\xc1\x03\x98\x1a\x33\x06\x64\x53\x7a\x64\x6d\xcd\x75\xe8\x08\xd5\xee\x1e\x66\x7f\x0d\x8d\xb0\xb0\xbf\x08\xdc\xbf\xd5\x52\x8a\x9c\x9a\x7e\xbb\xc5\x1e\x9d\x02\x82\x6d\x7c\x00\xe0\x51\xc1\x2d\x4a\xcd\x93\x50\x4b\x50\xc9\x85\x01\x8b\x94\x28\x15\x74\xdd\xf9\xe9\x1f\xbd\x74\x9e\xaa\x25\xec\x98\x5a\x4c\xa5\xf3\xa9\x28\xe5\x32\x9a\xbb\xd2\xbd\xef\x2e\xc1\x46\x31\x69\x01\x0f\xaf\x48\xde\xee\x92\x73\x3f\x37\xe4\xbf\xb7\xc9\xf5\xd9\x81\x84\x46\x02\x17\x45\xdd\x96\xee\x16\x62\x97\xdc\x72\xe1\xec\xd9\x24\x62\x88\x2e\x8e\xe6\x40\x0c\x17\x50\x54\x32\xd2\xd8\xd9\x21\x01\x31\x1d\xb7\xcf\x05\x33\xf1\x41\xf5\x84\x7c\xb3\x7f\xc3\x45\x6c\x30\x88\x86\x79\x2c\xf4\x37\x06\xc6\x34\xb4\xe4\x21\x09\x6c\xe0\xdd\xfb\x64\x5c\x8a\x27\xee\xb8\x9d\x7b\xc8\x74\x64\xca\x9d\x34\x85\xba\x82\x5c\xec\x9b\x11\x7e\xe1\xbb\x78\xd8\xd3\x84\xed\xc0\x1e\x31\xb5\x1c\xbe\xf9\x94\x30\x47\x51\x76\xfd\xfd\x7e\xe6\xb7\x39\x01\xba\xa1\x97\xd8\x47\xf4\xf2\xcc\xbb\x47\xb7\x9b\xa9\xad\x4e\xf5\x31\x69\xce\x77\x01\xd8\x74\x70\xa5\x66\xda\xa4\x78\x2f\x35\xc6\x66\xd3\x33\xcd\x5e\x83\x98\xf2\x47\xe7\x8d\xf6\xff\x91\x2f\xfa\x87\x01\x50\x8d\x3e\xf4\x68\xd5\xfd\x3b\x9f\xd0\xed\x95\x71\x3f\x52\xa2\x1f\x9b\x74\x47\x8b\xa2\xb4\xdb\xd4\x43\x7f\xd2\x38\xb3\x55\x13\x07\x1b\x9c\x6f\x5f\x48\x6e\x16\xa0\x71\x2f\x9e\xbb\x18\x3e\xe1\x85\xba\xe6\x05\xce\x6c\xe4\x5c\xc3\x2b\x61\x66\xbd\x31\xe7\x0b\x68\x9b\xb7\xd2\xbd\x0e\x56\x9d\xdf\xf5\xb6\x07\x71\x9e\xcc\x43\xf0\xb7\xe7\x22\x18\xf5\x7f\xfc\x8f\xbf\x7c\xf7\x7e\xa2\x69\xe8\x73\xd5\xed\xad\xad\xc1\xd1\x78\xb2\x5f\xfc\xce\xe7\xf3\x7c\xcf\xca\xa7\xbb\xb4\x6a\xcd\xcb\x9c\x75\xa8\x5b\x5c\x39\x21\x10\xf9\x75\xfa\x7e\xe8\xe3\xc9\xb7\x93\xfe\x9e\x31\xe8\x7a\xe8\xa8\xa3\x3c\x3d\x28\x74\x26\x60\xfd\x67\xfe\x66\x6c\x0a\xdd\x7f\x59\xea\x53\x69\xed\xd7\x68\xf5\x93\x2b\x36\x83\x35\x1a\x27\xa4\xfe\x52\xef\x9d\x32\xd2\x6d\xc6\x0c\xe7\xba\x5f\xeb\xf8\x2e\x66\xb8\x9f\x19\xeb\x30\x38\x01\x1e\xdd\x54\x5e\xc3\x83\xfe\xe3\x8f\x13\xaa\x84\x6b\xd6\x6e\xfa\x93\x68\xc6\x53\x1f\x43\x3e\xe7\x82\xb5\x57\x6b\x74\x03\x6f\x6c\x35\xca\xe2\xe9\xf7\xb9\x9f\x88\x24\x19\x99\x46\xe5\x7e\x2b\x07\x9b\x5e\xca\x17\xb5\x3c\x65\x9a\x9d\xdd\xb4\x7b\x29\xb9\xfc\x35\xf3\x6e\x74\xb8\x65\x3e\xc9\x3a\xe8\xfd\xe9\xde\xff\x05\x00\x00\xff\xff\xb5\x69\x82\xc7\xdc\x3d\x00\x00" func randombeaconhistoryCdcBytes() ([]byte, error) { return bindataRead( @@ -280,7 +280,7 @@ func randombeaconhistoryCdc() (*asset, error) { } info := bindataFileInfo{name: "RandomBeaconHistory.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6b, 0x4, 0x9a, 0xe, 0x8a, 0xc0, 0x13, 0xd1, 0xa1, 0x68, 0x78, 0x86, 0x47, 0x78, 0x49, 0xf5, 0xb5, 0x14, 0x2b, 0x9d, 0x22, 0x59, 0xf, 0xb5, 0x84, 0x91, 0x5b, 0x73, 0xee, 0x7, 0x8c, 0xf1}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9b, 0x6c, 0xab, 0x3a, 0xef, 0xd9, 0x33, 0x37, 0x18, 0x7, 0xf7, 0x65, 0xa9, 0x76, 0x87, 0x4b, 0xb0, 0x25, 0x47, 0x55, 0x2, 0x58, 0xd, 0x1f, 0x95, 0x91, 0x3e, 0xda, 0x9, 0xd9, 0xe7, 0xba}} return a, nil } diff --git a/lib/go/templates/internal/assets/assets.go b/lib/go/templates/internal/assets/assets.go index fa6092e8e..4bb33f61d 100644 --- a/lib/go/templates/internal/assets/assets.go +++ b/lib/go/templates/internal/assets/assets.go @@ -240,9 +240,11 @@ // quorumCertificate/scripts/get_voter_is_registered.cdc (198B) // quorumCertificate/scripts/get_voting_completed.cdc (187B) // quorumCertificate/submit_vote.cdc (584B) +// randomBeaconHistory/scripts/get_backfiller_max_entries.cdc (285B) // randomBeaconHistory/scripts/get_latest_source_of_randomness.cdc (200B) // randomBeaconHistory/scripts/get_source_of_randomness.cdc (305B) // randomBeaconHistory/scripts/get_source_of_randomness_page.cdc (326B) +// randomBeaconHistory/transactions/set_backfiller_max_entries.cdc (356B) // stakingCollection/close_stake.cdc (758B) // stakingCollection/create_machine_account.cdc (1.152kB) // stakingCollection/create_new_tokenholder_acct.cdc (2.95kB) @@ -5163,6 +5165,26 @@ func quorumcertificateSubmit_voteCdc() (*asset, error) { return a, nil } +var _randombeaconhistoryScriptsGet_backfiller_max_entriesCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\xce\xc1\x4a\xc4\x40\x0c\xc6\xf1\x7b\x9f\xe2\x63\x0f\xd2\xb9\x74\x2f\xe2\xa1\xa8\xa5\x2b\x82\x1e\x04\x11\x7c\x80\xec\x34\xad\x83\xd3\x44\x32\x29\x2a\xe2\xbb\x8b\x22\xae\xd0\xbd\x05\xf2\xe3\xe3\x9f\xe6\x17\x35\xc7\xe6\x81\x64\xd0\x79\xc7\x14\x55\x6e\x52\x71\xb5\xf7\x4d\x55\x51\x8c\x5c\x4a\x4d\x39\x07\x8c\x8b\x60\xa6\x24\xf5\x9e\xe2\xf3\x98\x72\x66\xeb\x87\xc1\xb8\x94\x16\xbf\x47\x68\xf1\x78\x2b\x7e\x76\xda\xe1\xa3\x02\x80\xcc\x8e\x03\xc7\x05\x26\xf6\x7e\xf1\xa7\x3e\x46\x5d\xc4\xd7\x53\xa1\xd9\xab\x99\xbe\x9e\x9f\x1c\x09\x6a\x76\x7f\xfc\xb2\x1e\x4d\xe7\x16\xdb\xef\x07\x4d\xbc\xb5\x35\x3f\xe8\xf0\x13\x63\xec\x8b\xc9\xbf\x9e\xae\x99\xd8\xef\xe8\xed\x5a\xdc\x12\x97\x7b\xb6\x2b\xca\xb9\x0e\xe8\x3a\x48\xca\xd5\xe7\x57\x00\x00\x00\xff\xff\x66\x04\xfc\x7c\x1d\x01\x00\x00" + +func randombeaconhistoryScriptsGet_backfiller_max_entriesCdcBytes() ([]byte, error) { + return bindataRead( + _randombeaconhistoryScriptsGet_backfiller_max_entriesCdc, + "randomBeaconHistory/scripts/get_backfiller_max_entries.cdc", + ) +} + +func randombeaconhistoryScriptsGet_backfiller_max_entriesCdc() (*asset, error) { + bytes, err := randombeaconhistoryScriptsGet_backfiller_max_entriesCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "randomBeaconHistory/scripts/get_backfiller_max_entries.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x27, 0x94, 0x9b, 0x45, 0x96, 0xec, 0xc4, 0xcb, 0xe2, 0x22, 0x8, 0x15, 0xba, 0xe5, 0xfd, 0xe9, 0x7d, 0x20, 0xa4, 0x5a, 0x6a, 0xe4, 0xa3, 0x90, 0x85, 0xcf, 0x95, 0x67, 0x36, 0x88, 0x1a, 0x86}} + return a, nil +} + var _randombeaconhistoryScriptsGet_latest_source_of_randomnessCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8d\xb1\xae\x83\x30\x0c\x45\xf7\x7c\xc5\x15\x13\x19\x1e\xd2\x5b\x19\xe9\xc2\x56\xa9\xfd\x82\x28\x35\x10\x15\xec\xca\x71\x86\xaa\xea\xbf\x57\x84\x15\x8f\xf7\x9c\x23\xa7\xed\x25\x6a\x68\x6e\x81\x1f\xb2\x0d\x14\xa2\xf0\x98\xb2\x89\xbe\x1b\xe7\x42\x8c\x94\x73\x1b\xd6\xd5\x63\x2a\x8c\x2d\x24\x6e\x7d\x8f\x13\xbb\x3b\xb6\xbb\x14\x8d\x84\x8f\x03\x00\x25\x2b\xca\xa7\x76\xae\xde\x75\x3a\x18\xef\x5f\x6a\xb2\x5f\xb0\x61\x95\xf8\x1c\x29\xcd\x8b\xf5\x98\xc9\x2e\x45\x95\xf8\x98\x5b\xdf\x2d\x95\xe0\x0f\xff\xb5\xf1\xee\xeb\x7e\x01\x00\x00\xff\xff\xf0\x98\xc8\x29\xc8\x00\x00\x00" func randombeaconhistoryScriptsGet_latest_source_of_randomnessCdcBytes() ([]byte, error) { @@ -5223,6 +5245,26 @@ func randombeaconhistoryScriptsGet_source_of_randomness_pageCdc() (*asset, error return a, nil } +var _randombeaconhistoryTransactionsSet_backfiller_max_entriesCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x90\xc1\x4a\x03\x41\x10\x44\xef\xf3\x15\xc5\x1e\x64\xf7\x92\x5c\xc4\xc3\xa2\x86\x24\x08\x7a\x10\x44\xf0\x03\x3a\x93\x8e\x0e\xce\x4e\x2f\x3d\xbd\xa8\x48\xfe\x5d\xc6\xe0\xac\xa0\x75\x7e\xaf\xab\xa9\x30\x8c\xa2\x86\xe6\x91\xd2\x5e\x86\x0d\x93\x97\x74\x1b\xb2\x89\x7e\x34\xce\x99\x52\xca\xe4\x2d\x48\x6a\x07\x7a\xbf\x49\xa6\x81\x73\x8f\xa7\xbb\x64\x17\xe7\x1d\x3e\x1d\x00\x8c\xca\x23\x29\xb7\xe4\xbd\xf5\x58\x4f\xf6\xb2\xf6\x5e\xa6\x64\x3f\x40\x49\x64\xc3\x8e\xfc\xeb\x21\xc4\xc8\x8a\x2b\x14\x7a\xb1\x13\x55\x79\xbb\x3c\xfb\xa7\x7e\xb1\xa9\xf4\x75\x5b\xcf\x94\x1c\x54\x86\x1e\xcb\x42\xd1\x33\x2f\xf5\xaf\x3b\xab\x55\xec\xb0\x5a\x61\xa4\x14\x7c\xdb\x6c\x65\x8a\x7b\x24\x31\x9c\xfa\x7f\x3f\xa6\x9c\x65\x52\xcf\x4d\xf7\xad\x56\x7f\x46\x16\x99\xed\xbe\x8e\xf1\xc0\xba\xa5\x18\xcb\x3c\x3d\xe6\x8d\xba\x93\x79\x84\x3b\xba\xaf\x00\x00\x00\xff\xff\xc6\xa9\x1e\xdb\x64\x01\x00\x00" + +func randombeaconhistoryTransactionsSet_backfiller_max_entriesCdcBytes() ([]byte, error) { + return bindataRead( + _randombeaconhistoryTransactionsSet_backfiller_max_entriesCdc, + "randomBeaconHistory/transactions/set_backfiller_max_entries.cdc", + ) +} + +func randombeaconhistoryTransactionsSet_backfiller_max_entriesCdc() (*asset, error) { + bytes, err := randombeaconhistoryTransactionsSet_backfiller_max_entriesCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "randomBeaconHistory/transactions/set_backfiller_max_entries.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x25, 0xe9, 0x54, 0x5f, 0x52, 0xc7, 0xdd, 0xf5, 0x86, 0x1, 0xa7, 0x3a, 0xc7, 0x64, 0x11, 0x66, 0xea, 0x37, 0xb, 0xc1, 0x14, 0x60, 0xf3, 0x46, 0x7b, 0x3a, 0x44, 0x81, 0x31, 0xb2, 0x58, 0xa7}} + return a, nil +} + var _stakingcollectionClose_stakeCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x52\xc1\x6e\xd4\x40\x0c\xbd\xe7\x2b\xac\x1e\xd0\x56\xaa\xb6\x08\x6e\x11\xb0\x8a\xb2\x05\x45\x54\x2d\x6a\x96\x0f\x70\x26\x4e\x32\x30\x19\x47\x33\x4e\x5b\x84\xfa\xef\x68\x66\x48\x40\xdd\x3d\xac\x0f\x89\xfc\x64\xbf\xf7\x6c\x8f\x1e\x27\x76\x02\x9f\x0d\x3f\xd5\x82\x3f\xb5\xed\x4b\x36\x86\x94\x68\xb6\xd0\x39\x1e\xe1\xed\x73\x7d\x28\xbe\x56\x77\x5f\xca\xfb\xdb\xdb\x9b\xf2\x50\xdd\xdf\x15\xfb\xfd\xc3\x4d\x5d\x67\xd9\xf5\x35\x94\x86\x3d\x79\xe0\x59\x00\xc1\x27\x0a\xe0\xe6\x07\x29\x01\x6d\x41\x06\x5a\x51\xb5\x32\x87\xc6\xc3\xa0\x3d\xb4\x4c\x1e\x2c\x0b\x38\x1a\xf9\x91\x62\xb9\x23\xc5\xae\x4d\xe2\x21\xd7\x2d\x59\xd1\xf2\x0b\x04\x1b\x43\x57\xa1\xb7\x99\x05\xb4\xa4\xee\x91\x30\xc8\xa0\xc4\x62\x54\x8a\x67\x2b\x09\x50\xc9\x9b\x16\x50\x68\x83\x0a\x3d\x92\x0b\x25\xe4\x23\x8a\x3d\x6a\x9b\x65\xe2\xd0\x7a\x8c\xc6\x36\x96\x5b\xaa\xf6\x39\xd4\xe2\xb4\xed\xaf\xa0\x25\x43\x3d\x0a\xbb\x00\x7e\xaf\xac\xbc\x7f\xb7\xbb\x84\xdf\x19\x00\x40\xfc\x18\x92\x65\xc0\x7f\x9b\x7b\xa0\x2e\x87\x37\x27\x97\xba\x3d\x42\xb2\xc8\x33\x39\x9a\xd0\xd1\xe6\xef\x00\x39\x14\xb3\x0c\x45\x4a\x16\xc1\x10\x9e\x4c\xb7\x3d\x25\x08\x1f\x97\xe1\xb7\x0d\x3b\xc7\x4f\x1f\xce\x35\xf0\x69\x13\x76\x9d\x9f\x7e\x04\xc7\xe5\xb5\xb0\xc3\x9e\xbe\xa1\x0c\x97\xab\xad\x10\xbb\x1d\x4c\x68\xb5\xda\x5c\x94\x3c\x9b\x36\xde\x35\x59\x01\x47\x1d\x08\xc3\x11\xd7\x45\x62\x78\x49\x3b\xa0\x67\x52\xb3\xd0\x39\xd3\x6e\xe3\x6d\x03\x1f\xad\x37\x4b\xff\x57\x37\xfb\x2f\x59\xb4\x5e\xb2\x3f\x01\x00\x00\xff\xff\x6e\x12\x74\xdf\xf6\x02\x00\x00" func stakingcollectionClose_stakeCdcBytes() ([]byte, error) { @@ -6574,9 +6616,11 @@ var _bindata = map[string]func() (*asset, error){ "quorumCertificate/scripts/get_voter_is_registered.cdc": quorumcertificateScriptsGet_voter_is_registeredCdc, "quorumCertificate/scripts/get_voting_completed.cdc": quorumcertificateScriptsGet_voting_completedCdc, "quorumCertificate/submit_vote.cdc": quorumcertificateSubmit_voteCdc, + "randomBeaconHistory/scripts/get_backfiller_max_entries.cdc": randombeaconhistoryScriptsGet_backfiller_max_entriesCdc, "randomBeaconHistory/scripts/get_latest_source_of_randomness.cdc": randombeaconhistoryScriptsGet_latest_source_of_randomnessCdc, "randomBeaconHistory/scripts/get_source_of_randomness.cdc": randombeaconhistoryScriptsGet_source_of_randomnessCdc, "randomBeaconHistory/scripts/get_source_of_randomness_page.cdc": randombeaconhistoryScriptsGet_source_of_randomness_pageCdc, + "randomBeaconHistory/transactions/set_backfiller_max_entries.cdc": randombeaconhistoryTransactionsSet_backfiller_max_entriesCdc, "stakingCollection/close_stake.cdc": stakingcollectionClose_stakeCdc, "stakingCollection/create_machine_account.cdc": stakingcollectionCreate_machine_accountCdc, "stakingCollection/create_new_tokenholder_acct.cdc": stakingcollectionCreate_new_tokenholder_acctCdc, @@ -6970,10 +7014,14 @@ var _bintree = &bintree{nil, map[string]*bintree{ }}, "randomBeaconHistory": {nil, map[string]*bintree{ "scripts": {nil, map[string]*bintree{ + "get_backfiller_max_entries.cdc": {randombeaconhistoryScriptsGet_backfiller_max_entriesCdc, map[string]*bintree{}}, "get_latest_source_of_randomness.cdc": {randombeaconhistoryScriptsGet_latest_source_of_randomnessCdc, map[string]*bintree{}}, "get_source_of_randomness.cdc": {randombeaconhistoryScriptsGet_source_of_randomnessCdc, map[string]*bintree{}}, "get_source_of_randomness_page.cdc": {randombeaconhistoryScriptsGet_source_of_randomness_pageCdc, map[string]*bintree{}}, }}, + "transactions": {nil, map[string]*bintree{ + "set_backfiller_max_entries.cdc": {randombeaconhistoryTransactionsSet_backfiller_max_entriesCdc, map[string]*bintree{}}, + }}, }}, "stakingCollection": {nil, map[string]*bintree{ "close_stake.cdc": {stakingcollectionClose_stakeCdc, map[string]*bintree{}}, diff --git a/tests/test_random_beacon_history.cdc b/tests/test_random_beacon_history.cdc index ee42e7bfe..705102a79 100644 --- a/tests/test_random_beacon_history.cdc +++ b/tests/test_random_beacon_history.cdc @@ -3,6 +3,7 @@ import BlockchainHelpers import "RandomBeaconHistory" access(all) let admin = Test.getAccount(0x0000000000000007) +access(all) let defaultBackfillerMax = UInt64(100) access(all) fun setup() { @@ -27,6 +28,7 @@ fun testGetLowestHeightWhenNotInitialized() { ) } + access(all) fun testGetRandomSourceHistoryPageWithoutLowestHeightSet() { let page: UInt64 = 1 @@ -42,9 +44,10 @@ fun testGetRandomSourceHistoryPageWithoutLowestHeightSet() { ) } + access(all) fun testGetSourceOfRandomnessWithoutLowestHeightSet() { - let atBlockHeight: UInt64 = 101 + let atBlockHeight: UInt64 = getCurrentBlockHeight() + 10 let scriptResult = executeScript( "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", [atBlockHeight] @@ -56,31 +59,33 @@ fun testGetSourceOfRandomnessWithoutLowestHeightSet() { ) } +// At this point the history array is empty access(all) fun testRecordRandomSource() { - let randomSource: [UInt8] = [0, 1, 1, 2, 3, 5, 8] - let txResult = executeTransaction( + let randomSource: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] + // executeTransaction advances the block height then executes the transaction! + let txResult = executeTransaction( "transactions/record_random_source.cdc", [randomSource], admin ) Test.expect(txResult, Test.beSucceeded()) - - let page: UInt64 = 0 - let perPage: UInt64 = 10 + // get backfiller max entries let scriptResult = executeScript( - "../transactions/randomBeaconHistory/scripts/get_source_of_randomness_page.cdc", - [page, perPage] + "../transactions/randomBeaconHistory/scripts/get_backfiller_max_entries.cdc", + [admin.address] ) Test.expect(scriptResult, Test.beSucceeded()) - - let history = (scriptResult.returnValue as! RandomBeaconHistory.RandomSourceHistoryPage?)! - Test.assertEqual(randomSource, history.values[0]!.value) + let resultBackfillerMax = scriptResult.returnValue! as! UInt64 + Test.assertEqual(defaultBackfillerMax, resultBackfillerMax) } +// At this point the history array has 1 entry access(all) -fun testGetSourceOfRandomnessForMissingBlockHeight() { - let atBlockHeight: UInt64 = 101 +fun testGetSourceOfRandomnessForFutureBlockHeight() { + // random source entry of the current block height must be only available + // starting from the next block height + let atBlockHeight: UInt64 = getCurrentBlockHeight() let scriptResult = executeScript( "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", [atBlockHeight] @@ -92,6 +97,7 @@ fun testGetSourceOfRandomnessForMissingBlockHeight() { ) } +// At this point the history array has 1 entry access(all) fun testGetSourceOfRandomnessPrecedingRecordedHistory() { let lowestHeight = (executeScript( @@ -111,10 +117,38 @@ fun testGetSourceOfRandomnessPrecedingRecordedHistory() { ) } +// At this point the history array has 1 entry access(all) -fun testGetSourceOfRandomnessFromPreviousBlockHeight() { - // Commit current block and advance to the next one - Test.commitBlock() +fun testGetRecordedSourceOfRandomnessPage() { + let page: UInt64 = 0 + let perPage: UInt64 = 10 + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness_page.cdc", + [page, perPage] + ) + Test.expect(scriptResult, Test.beSucceeded()) + + let history = (scriptResult.returnValue as! RandomBeaconHistory.RandomSourceHistoryPage?)! + Test.assertEqual(page, history.page) + Test.assertEqual(perPage, history.perPage) + Test.assertEqual(UInt64(1), history.totalLength) + Test.assertEqual(1, history.values.length) + let value: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] + Test.assertEqual(value, history.values[0]!.value) +} + +// At this point the history array has 1 entry +access(all) +fun testGetRecordedSourceOfRandomness() { + // record a new random source and advance to the next block, + // this way the previous block's entry becomes available + let value: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] + let txResult = executeTransaction( + "transactions/record_random_source.cdc", + [value], + admin + ) + Test.expect(txResult, Test.beSucceeded()) let atBlockHeight: UInt64 = getCurrentBlockHeight() - 1 let scriptResult = executeScript( @@ -124,11 +158,11 @@ fun testGetSourceOfRandomnessFromPreviousBlockHeight() { Test.expect(scriptResult, Test.beSucceeded()) let randomSource = scriptResult.returnValue! as! RandomBeaconHistory.RandomSource - let value: [UInt8] = [0, 1, 1, 2, 3, 5, 8] Test.assertEqual(atBlockHeight, randomSource.blockHeight) Test.assertEqual(value, randomSource.value) } +// At this point the history array has 2 entries access(all) fun testGetLatestSourceOfRandomness() { let scriptResult = executeScript( @@ -139,7 +173,7 @@ fun testGetLatestSourceOfRandomness() { let randomSource = scriptResult.returnValue! as! RandomBeaconHistory.RandomSource let atBlockHeight: UInt64 = getCurrentBlockHeight() - 1 - let value: [UInt8] = [0, 1, 1, 2, 3, 5, 8] + let value: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] Test.assertEqual(atBlockHeight, randomSource.blockHeight) Test.assertEqual(value, randomSource.value) } @@ -158,3 +192,379 @@ fun testGetRandomSourceHistoryPageExceedingLastPage() { let history = (scriptResult.returnValue as! RandomBeaconHistory.RandomSourceHistoryPage?)! Test.expect(history.values, Test.beEmpty()) } + + +// At this point the history array has 2 entries +access(all) +fun testGetMissingSourceFromGap() { + // advance blocks without recording a random source, hence creating a gap + // in the history array. + // So far only two entry were recorded + let gapLength = UInt64(90) + var i = UInt64(0) + while i < gapLength { + Test.commitBlock() + i = i + 1 + } + + // sources in the gap are non recorded and must be backfilled later + let gapStartHeight = RandomBeaconHistory.getLowestHeight() + 2 // skip the 2 recorded entries + + i = 0 + while i < gapLength-1 { + let atBlockHeight = gapStartHeight + i + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", + [atBlockHeight] + ) + Test.expect(scriptResult, Test.beFailed()) + Test.assertError( + scriptResult, + errorMessage: "Source of randomness is currently not available but will be available soon" + ) + i = i + 1 + } +} + +// At this point the history array has 2 entries and then an empty gap +access(all) +fun testGetPageFromGap() { + // So far only two entry were recorded and then there is a gap + // the page function only returns the SoRs recorded so far which are limited to the 2 entries + let recordedSources = 2 + + let page: UInt64 = 0 + let perPage: UInt64 = 4 + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness_page.cdc", + [page, perPage] + ) + Test.expect(scriptResult, Test.beSucceeded()) + + let history = (scriptResult.returnValue as! RandomBeaconHistory.RandomSourceHistoryPage?)! + Test.assertEqual(page, history.page) + Test.assertEqual(perPage, history.perPage) + Test.assertEqual(UInt64(recordedSources), history.totalLength) + Test.assertEqual(recordedSources, history.values.length) + let value: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] + Test.assertEqual(value, history.values[0]!.value) +} + + +// at this point the history has 2 entries and then a gap +access(all) +fun testGetBackfilledSource() { + let gapLength = UInt64(90) // matching the length from the previous test + + // record a new random source, which would trigger fully backfilling the gap + // (when the gap size is less than 100, since the contracts backfills up to 100 entries at a time) + assert(gapLength <= defaultBackfillerMax) + var value: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] + let txResult = executeTransaction( + "transactions/record_random_source.cdc", + [value], + admin + ) + Test.expect(txResult, Test.beSucceeded()) + + // make sure latest source got recorded as expected + Test.commitBlock() + let atBlockHeight: UInt64 = getCurrentBlockHeight() - 1 + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", + [atBlockHeight] + ) + Test.expect(scriptResult, Test.beSucceeded()) + + + let randomSource = scriptResult.returnValue! as! RandomBeaconHistory.RandomSource + Test.assertEqual(atBlockHeight, randomSource.blockHeight) + Test.assertEqual(value, randomSource.value) + + + // check the gap and make sure it got backfilled + let gapStartHeight = RandomBeaconHistory.getLowestHeight() + 2 // skip the 2 recorded entries + var i = UInt64(0) + while i < gapLength { + let atBlockHeight = gapStartHeight + i + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", + [atBlockHeight] + ) + Test.expect(scriptResult, Test.beSucceeded()) + + let randomSource = scriptResult.returnValue! as! RandomBeaconHistory.RandomSource + Test.assertEqual(atBlockHeight, randomSource.blockHeight) + // compare against the expected backfilled value + value = HashAlgorithm.SHA3_256.hash(value) + Test.assertEqual(value, randomSource.value) + i = i + 1 + } + + // Confirm event values + let missingEvents = Test.eventsOfType(Type()) + Test.assertEqual(1, missingEvents.length) + let missingEvent = missingEvents[0] as! RandomBeaconHistory.RandomHistoryMissing + Test.assertEqual(atBlockHeight, missingEvent.blockHeight) + Test.assertEqual(gapStartHeight, missingEvent.gapStartHeight) + + let backfilledEvents = Test.eventsOfType(Type()) + Test.assertEqual(1, backfilledEvents.length) + let backfilledEvent = backfilledEvents[0] as! RandomBeaconHistory.RandomHistoryBackfilled + Test.assertEqual(atBlockHeight, backfilledEvent.blockHeight) + Test.assertEqual(gapStartHeight, backfilledEvent.gapStartHeight) + Test.assertEqual(gapLength, backfilledEvent.count) +} + +// At this point the history array has 2+90+1 = 93 entries and no gaps +access(all) +fun testGetPageAfterBackfilling() { + // So far only two entry were recorded and then there is a gap + // the page function only returns the SoRs recorded so far which are limited to the 2 entries + let recordedSources = UInt64(93) + + let page: UInt64 = 5 + let perPage: UInt64 = 10 + assert((page+1) * perPage < recordedSources) + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness_page.cdc", + [page, perPage] + ) + Test.expect(scriptResult, Test.beSucceeded()) + + let history = (scriptResult.returnValue as! RandomBeaconHistory.RandomSourceHistoryPage?)! + Test.assertEqual(page, history.page) + Test.assertEqual(perPage, history.perPage) + Test.assertEqual(recordedSources, history.totalLength) + Test.assertEqual(perPage, UInt64(history.values.length)) +} + +// reset the blockchain state back to the lowest height (1 SoR entry) +// +// The next section tests an edge case where a gap is not contiguous. This happens +// when an initial large gap doesn't get fully backfilled before another gap occurs. +access(all) +fun testNonContiguousGaps() { + Test.reset(to: RandomBeaconHistory.getLowestHeight()) + + // advance blocks without recording a random source, hence creating a gap + // in the history array. The gap is long enough so that it can't be backfilled + // in one transaction only (gap is larger than 100) + var gapLength = UInt64(120) + assert (gapLength > defaultBackfillerMax) + var i = UInt64(0) + while i < gapLength { + Test.commitBlock() + i = i + 1 + } + + // record a new random source, which would trigger partially backfilling the gap + // (when the gap size is more than 100, since the contracts backfills up to 100 entries at a time) + var value: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] + var txResult = executeTransaction( + "transactions/record_random_source.cdc", + [value], + admin + ) + Test.expect(txResult, Test.beSucceeded()) + var backfillingHeight = getCurrentBlockHeight() + + // the gap is partially backfilled + // for `gapStartHeight` skip the 1 recorded entry and backfilled entries + var gapStartHeight = RandomBeaconHistory.getLowestHeight() + defaultBackfillerMax + 1 + // the remaining gap after backfilling + gapLength = gapLength - defaultBackfillerMax + + // check that the gap isn't fully backfilled + // (check that the gap got backfilled was covered by an earlier test) + i = 0 + while i < gapLength { + let atBlockHeight = gapStartHeight + i + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", + [atBlockHeight] + ) + Test.expect(scriptResult, Test.beFailed()) + Test.assertError( + scriptResult, + errorMessage: "Source of randomness is currently not available but will be available soon" + ) + i = i + 1 + } + + // check that getting pages also fails in this case + // because some entries are missing from the page + var page: UInt64 = 0 + var perPage: UInt64 = defaultBackfillerMax + 5 // 5 entries are empty on the SoR array + var scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness_page.cdc", + [page, perPage] + ) + Test.expect(scriptResult, Test.beFailed()) + Test.assertError( + scriptResult, + errorMessage: "Source of randomness is currently not available but will be available soon" + ) + + // Confirm event values + // There should be one event of each type + var missingEvents = Test.eventsOfType(Type()) + Test.assertEqual(1, missingEvents.length) + var missingEvent = missingEvents[0] as! RandomBeaconHistory.RandomHistoryMissing + Test.assertEqual(backfillingHeight, missingEvent.blockHeight) + Test.assertEqual(gapStartHeight - defaultBackfillerMax, missingEvent.gapStartHeight) + + var backfilledEvents = Test.eventsOfType(Type()) + Test.assertEqual(1, backfilledEvents.length) + var backfilledEvent = backfilledEvents[0] as! RandomBeaconHistory.RandomHistoryBackfilled + Test.assertEqual(backfillingHeight, backfilledEvent.blockHeight) + Test.assertEqual(gapStartHeight- defaultBackfillerMax, backfilledEvent.gapStartHeight) + Test.assertEqual(defaultBackfillerMax, backfilledEvent.count) + + // insert a new gap and make sure it can be all backfilled in the next transaction + var newGapLength = UInt64(20) + assert (gapLength + newGapLength < defaultBackfillerMax) + + i = UInt64(0) + while i < newGapLength { + Test.commitBlock() + i = i + 1 + } + + // at this point there is a gap of size `gapLength` followed by one entry, and then + // a new gap of size `newGapLength` + // one call to the heartbeat function should backfill both gaps + txResult = executeTransaction( + "transactions/record_random_source.cdc", + [value], + admin + ) + Test.expect(txResult, Test.beSucceeded()) + backfillingHeight = getCurrentBlockHeight() + + // check that both first and second gap are backfilled + i = 0 + while i < gapLength { + let atBlockHeight = gapStartHeight + i + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", + [atBlockHeight] + ) + Test.expect(scriptResult, Test.beSucceeded()) + i = i + 1 + } + gapStartHeight = gapStartHeight + gapLength + 1 + i = 0 + while i < newGapLength { + let atBlockHeight = gapStartHeight + i + let scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness.cdc", + [atBlockHeight] + ) + Test.expect(scriptResult, Test.beSucceeded()) + i = i + 1 + } + + // check getting a page with the entire history succeeds, + // which means no entry got left empty. + let totalSources = gapStartHeight + newGapLength + 1 - RandomBeaconHistory.getLowestHeight() + + page = 0 + perPage = totalSources + scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_source_of_randomness_page.cdc", + [page, perPage] + ) + Test.expect(scriptResult, Test.beSucceeded()) + + let history = (scriptResult.returnValue as! RandomBeaconHistory.RandomSourceHistoryPage?)! + Test.assertEqual(page, history.page) + Test.assertEqual(perPage, history.perPage) + Test.assertEqual(totalSources, history.totalLength) + Test.assertEqual(perPage, UInt64(history.values.length)) + + // Confirm event values + + // There should be two events of each type + // the first events were already checked above - focus only on the second event of each type + missingEvents = Test.eventsOfType(Type()) + Test.assertEqual(2, missingEvents.length) + missingEvent = missingEvents[1] as! RandomBeaconHistory.RandomHistoryMissing + Test.assertEqual(backfillingHeight, missingEvent.blockHeight) + Test.assertEqual(gapStartHeight, missingEvent.gapStartHeight) + + backfilledEvents = Test.eventsOfType(Type()) + Test.assertEqual(2, backfilledEvents.length) + backfilledEvent = backfilledEvents[1] as! RandomBeaconHistory.RandomHistoryBackfilled + Test.assertEqual(backfillingHeight, backfilledEvent.blockHeight) + Test.assertEqual(gapStartHeight - gapLength - 1, backfilledEvent.gapStartHeight) + Test.assertEqual(gapLength + newGapLength, backfilledEvent.count) +} + +// independent test from the rest (it resets the blockchain state) +access(all) +fun testRecordInvalidRandomSource() { + // reset the blockchain state back to the lowest height (1 SoR entry) + Test.reset(to: RandomBeaconHistory.getLowestHeight()) + + let invalidRandomSource: [UInt8] = [0, 1, 1, 2, 3, 5, 8] + assert (invalidRandomSource.length < (128/8)) + // short sources should be rejected + let txResult = executeTransaction( + "transactions/record_random_source.cdc", + [invalidRandomSource], + admin + ) + Test.expect(txResult, Test.beFailed()) + Test.assertError( + txResult, + errorMessage: "Random source must be at least 128 bits" + ) +} + +// independent test from the rest (it resets the blockchain state) +access(all) +fun testBackfillerMaxEntryPerCall() { + // reset the blockchain state back to the lowest height (1 SoR entry) + Test.reset(to: RandomBeaconHistory.getLowestHeight()) + // get backfiller max entries + var scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_backfiller_max_entries.cdc", + [admin.address] + ) + Test.expect(scriptResult, Test.beSucceeded()) + var resultBackfillerMax = scriptResult.returnValue! as! UInt64 + Test.assertEqual(defaultBackfillerMax, resultBackfillerMax) + + let randomSource: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 8] + // this creates a backfiller + var txResult = executeTransaction( + "transactions/record_random_source.cdc", + [randomSource], + admin + ) + Test.expect(txResult, Test.beSucceeded()) + // set backfiller max entries + let newBackfillerMax = UInt64(55) + txResult = executeTransaction( + "../transactions/randomBeaconHistory/transactions/set_backfiller_max_entries.cdc", + [newBackfillerMax], + admin + ) + Test.expect(txResult, Test.beSucceeded()) + // get backfiller max entries + scriptResult = executeScript( + "../transactions/randomBeaconHistory/scripts/get_backfiller_max_entries.cdc", + [admin.address] + ) + Test.expect(scriptResult, Test.beSucceeded()) + resultBackfillerMax = scriptResult.returnValue! as! UInt64 + Test.assertEqual(resultBackfillerMax!, newBackfillerMax) + // invalid backfiller max entries + txResult = executeTransaction( + "../transactions/randomBeaconHistory/transactions/set_backfiller_max_entries.cdc", + [0], + admin + ) + Test.expect(txResult, Test.beFailed()) +} diff --git a/transactions/randomBeaconHistory/scripts/get_backfiller_max_entries.cdc b/transactions/randomBeaconHistory/scripts/get_backfiller_max_entries.cdc new file mode 100644 index 000000000..b47555a60 --- /dev/null +++ b/transactions/randomBeaconHistory/scripts/get_backfiller_max_entries.cdc @@ -0,0 +1,6 @@ +import "RandomBeaconHistory" + +access(all) fun main(backfillerAddress: Address): UInt64? { + let backfiller = getAuthAccount(backfillerAddress).borrow<&RandomBeaconHistory.Backfiller>(from: /storage/randomBeaconHistoryBackfiller) + return backfiller?.getMaxEntriesPerCall() ?? nil +} \ No newline at end of file diff --git a/transactions/randomBeaconHistory/transactions/set_backfiller_max_entries.cdc b/transactions/randomBeaconHistory/transactions/set_backfiller_max_entries.cdc new file mode 100644 index 000000000..0065d3753 --- /dev/null +++ b/transactions/randomBeaconHistory/transactions/set_backfiller_max_entries.cdc @@ -0,0 +1,11 @@ +import "RandomBeaconHistory" + +transaction(maxEntries: UInt64) { + prepare(acct: AuthAccount) { + let backfiller = acct.borrow<&RandomBeaconHistory.Backfiller>( + from: /storage/randomBeaconHistoryBackfiller + ) ?? panic("Could not borrow backfiller resource") + + backfiller.setMaxEntriesPerCall(max: maxEntries) + } +}