فهرست منبع

[fortuna] Add confirmation delay before responding (#1161)

* Update abi

* fix tests

* update contract

* fix fortuna

* test confirmation delay

* cleanup

* fix

* split the fields

* saturating sub

* rename
Jayant Krishnamurthy 1 سال پیش
والد
کامیت
c592fd36ca

+ 4 - 0
fortuna/config.yaml

@@ -2,13 +2,17 @@ chains:
   optimism-goerli:
     geth_rpc_addr: https://goerli.optimism.io
     contract_addr: 0x28F16Af4D87523910b843a801454AEde5F9B0459
+    reveal_delay_blocks: 0
   avalanche-fuji:
     geth_rpc_addr: https://api.avax-test.network/ext/bc/C/rpc
     contract_addr: 0xD42c7a708E74AD19401D907a14146F006c851Ee3
+    reveal_delay_blocks: 0
   eos-evm-testnet:
     geth_rpc_addr: https://api.testnet.evm.eosnetwork.com/
     contract_addr: 0xD42c7a708E74AD19401D907a14146F006c851Ee3
+    reveal_delay_blocks: 0
     legacy_tx: true
   arbitrum-goerli:
     geth_rpc_addr: https://arbitrum-goerli.publicnode.com
     contract_addr: 0xd9eAcfFB8e80b7193042499485EF8369b08E85B6
+    reveal_delay_blocks: 0

+ 97 - 54
fortuna/src/abi.json

@@ -1,19 +1,29 @@
 [
   {
-    "type": "constructor",
-    "inputs": [
+    "type": "function",
+    "name": "NUM_REQUESTS",
+    "inputs": [],
+    "outputs": [
       {
-        "name": "pythFeeInWei",
-        "type": "uint256",
-        "internalType": "uint256"
-      },
+        "name": "",
+        "type": "uint8",
+        "internalType": "uint8"
+      }
+    ],
+    "stateMutability": "view"
+  },
+  {
+    "type": "function",
+    "name": "NUM_REQUESTS_MASK",
+    "inputs": [],
+    "outputs": [
       {
-        "name": "defaultProvider",
-        "type": "address",
-        "internalType": "address"
+        "name": "",
+        "type": "bytes1",
+        "internalType": "bytes1"
       }
     ],
-    "stateMutability": "nonpayable"
+    "stateMutability": "view"
   },
   {
     "type": "function",
@@ -70,8 +80,8 @@
     "outputs": [
       {
         "name": "accruedPythFeesInWei",
-        "type": "uint256",
-        "internalType": "uint256"
+        "type": "uint128",
+        "internalType": "uint128"
       }
     ],
     "stateMutability": "view"
@@ -102,8 +112,8 @@
     "outputs": [
       {
         "name": "feeAmount",
-        "type": "uint256",
-        "internalType": "uint256"
+        "type": "uint128",
+        "internalType": "uint128"
       }
     ],
     "stateMutability": "view"
@@ -126,13 +136,13 @@
         "components": [
           {
             "name": "feeInWei",
-            "type": "uint256",
-            "internalType": "uint256"
+            "type": "uint128",
+            "internalType": "uint128"
           },
           {
             "name": "accruedFeesInWei",
-            "type": "uint256",
-            "internalType": "uint256"
+            "type": "uint128",
+            "internalType": "uint128"
           },
           {
             "name": "originalCommitment",
@@ -179,6 +189,19 @@
     ],
     "stateMutability": "view"
   },
+  {
+    "type": "function",
+    "name": "getPythFee",
+    "inputs": [],
+    "outputs": [
+      {
+        "name": "feeAmount",
+        "type": "uint128",
+        "internalType": "uint128"
+      }
+    ],
+    "stateMutability": "view"
+  },
   {
     "type": "function",
     "name": "getRequest",
@@ -211,24 +234,29 @@
             "internalType": "uint64"
           },
           {
-            "name": "userCommitment",
-            "type": "bytes32",
-            "internalType": "bytes32"
+            "name": "numHashes",
+            "type": "uint32",
+            "internalType": "uint32"
           },
           {
-            "name": "providerCommitment",
+            "name": "commitment",
             "type": "bytes32",
             "internalType": "bytes32"
           },
           {
-            "name": "providerCommitmentSequenceNumber",
+            "name": "blockNumber",
             "type": "uint64",
             "internalType": "uint64"
           },
           {
-            "name": "blockNumber",
-            "type": "uint256",
-            "internalType": "uint256"
+            "name": "requester",
+            "type": "address",
+            "internalType": "address"
+          },
+          {
+            "name": "useBlockhash",
+            "type": "bool",
+            "internalType": "bool"
           }
         ]
       }
@@ -241,8 +269,8 @@
     "inputs": [
       {
         "name": "feeInWei",
-        "type": "uint256",
-        "internalType": "uint256"
+        "type": "uint128",
+        "internalType": "uint128"
       },
       {
         "name": "commitment",
@@ -337,8 +365,8 @@
     "inputs": [
       {
         "name": "amount",
-        "type": "uint256",
-        "internalType": "uint256"
+        "type": "uint128",
+        "internalType": "uint128"
       }
     ],
     "outputs": [],
@@ -356,13 +384,13 @@
         "components": [
           {
             "name": "feeInWei",
-            "type": "uint256",
-            "internalType": "uint256"
+            "type": "uint128",
+            "internalType": "uint128"
           },
           {
             "name": "accruedFeesInWei",
-            "type": "uint256",
-            "internalType": "uint256"
+            "type": "uint128",
+            "internalType": "uint128"
           },
           {
             "name": "originalCommitment",
@@ -430,24 +458,29 @@
             "internalType": "uint64"
           },
           {
-            "name": "userCommitment",
-            "type": "bytes32",
-            "internalType": "bytes32"
+            "name": "numHashes",
+            "type": "uint32",
+            "internalType": "uint32"
           },
           {
-            "name": "providerCommitment",
+            "name": "commitment",
             "type": "bytes32",
             "internalType": "bytes32"
           },
           {
-            "name": "providerCommitmentSequenceNumber",
+            "name": "blockNumber",
             "type": "uint64",
             "internalType": "uint64"
           },
           {
-            "name": "blockNumber",
-            "type": "uint256",
-            "internalType": "uint256"
+            "name": "requester",
+            "type": "address",
+            "internalType": "address"
+          },
+          {
+            "name": "useBlockhash",
+            "type": "bool",
+            "internalType": "bool"
           }
         ]
       }
@@ -475,24 +508,29 @@
             "internalType": "uint64"
           },
           {
-            "name": "userCommitment",
-            "type": "bytes32",
-            "internalType": "bytes32"
+            "name": "numHashes",
+            "type": "uint32",
+            "internalType": "uint32"
           },
           {
-            "name": "providerCommitment",
+            "name": "commitment",
             "type": "bytes32",
             "internalType": "bytes32"
           },
           {
-            "name": "providerCommitmentSequenceNumber",
+            "name": "blockNumber",
             "type": "uint64",
             "internalType": "uint64"
           },
           {
-            "name": "blockNumber",
-            "type": "uint256",
-            "internalType": "uint256"
+            "name": "requester",
+            "type": "address",
+            "internalType": "address"
+          },
+          {
+            "name": "useBlockhash",
+            "type": "bool",
+            "internalType": "bool"
           }
         ]
       },
@@ -530,27 +568,32 @@
   },
   {
     "type": "error",
-    "name": "IncorrectProviderRevelation",
+    "name": "IncorrectRevelation",
     "inputs": []
   },
   {
     "type": "error",
-    "name": "IncorrectUserRevelation",
+    "name": "InsufficientFee",
     "inputs": []
   },
   {
     "type": "error",
-    "name": "InsufficientFee",
+    "name": "NoSuchProvider",
     "inputs": []
   },
   {
     "type": "error",
-    "name": "NoSuchProvider",
+    "name": "NoSuchRequest",
     "inputs": []
   },
   {
     "type": "error",
     "name": "OutOfRandomness",
     "inputs": []
+  },
+  {
+    "type": "error",
+    "name": "Unauthorized",
+    "inputs": []
   }
 ]

+ 109 - 20
fortuna/src/api.rs

@@ -1,6 +1,9 @@
 use {
     crate::{
-        chain::reader::EntropyReader,
+        chain::reader::{
+            BlockNumber,
+            EntropyReader,
+        },
         state::HashChainState,
     },
     axum::{
@@ -68,11 +71,14 @@ impl ApiState {
 #[derive(Clone)]
 pub struct BlockchainState {
     /// The hash chain(s) required to serve random numbers for this blockchain
-    pub state:            Arc<HashChainState>,
+    pub state:               Arc<HashChainState>,
     /// The contract that the server is fulfilling requests for.
-    pub contract:         Arc<dyn EntropyReader>,
+    pub contract:            Arc<dyn EntropyReader>,
     /// The address of the provider that this server is operating for.
-    pub provider_address: Address,
+    pub provider_address:    Address,
+    /// The server will wait for this many block confirmations of a request before revealing
+    /// the random number.
+    pub reveal_delay_blocks: BlockNumber,
 }
 
 pub struct Metrics {
@@ -115,6 +121,9 @@ pub enum RestError {
     /// The caller requested a random value that can't currently be revealed (because it
     /// hasn't been committed to on-chain)
     NoPendingRequest,
+    /// The request exists, but the server is waiting for more confirmations (more blocks
+    /// to be mined) before revealing the random number.
+    PendingConfirmation,
     /// The server cannot currently communicate with the blockchain, so is not able to verify
     /// which random values have been requested.
     TemporarilyUnavailable,
@@ -136,6 +145,10 @@ impl IntoResponse for RestError {
             RestError::NoPendingRequest => (
                 StatusCode::FORBIDDEN,
                 "The random value cannot currently be retrieved",
+            ).into_response(),
+            RestError::PendingConfirmation => (
+                StatusCode::FORBIDDEN,
+                "The request needs additional confirmations before the random value can be retrieved. Try your request again later.",
             )
                 .into_response(),
             RestError::TemporarilyUnavailable => (
@@ -210,20 +223,22 @@ mod test {
     }
 
     fn test_server() -> (TestServer, Arc<MockEntropyReader>, Arc<MockEntropyReader>) {
-        let eth_read = Arc::new(MockEntropyReader::with_requests(&[]));
+        let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
 
         let eth_state = BlockchainState {
-            state:            ETH_CHAIN.clone(),
-            contract:         eth_read.clone(),
-            provider_address: PROVIDER,
+            state:               ETH_CHAIN.clone(),
+            contract:            eth_read.clone(),
+            provider_address:    PROVIDER,
+            reveal_delay_blocks: 1,
         };
 
-        let avax_read = Arc::new(MockEntropyReader::with_requests(&[]));
+        let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
 
         let avax_state = BlockchainState {
-            state:            AVAX_CHAIN.clone(),
-            contract:         avax_read.clone(),
-            provider_address: PROVIDER,
+            state:               AVAX_CHAIN.clone(),
+            contract:            avax_read.clone(),
+            provider_address:    PROVIDER,
+            reveal_delay_blocks: 2,
         };
 
         let api_state = ApiState::new(&[
@@ -258,7 +273,7 @@ mod test {
         .await;
 
         // Once someone requests the number, then it is accessible
-        eth_contract.insert(PROVIDER, 0);
+        eth_contract.insert(PROVIDER, 0, 1, false);
         let response =
             get_and_assert_status(&server, "/v1/chains/ethereum/revelations/0", StatusCode::OK)
                 .await;
@@ -267,12 +282,12 @@ mod test {
         });
 
         // Each chain and provider has its own set of requests
-        eth_contract.insert(PROVIDER, 100);
-        eth_contract.insert(*OTHER_PROVIDER, 101);
-        eth_contract.insert(PROVIDER, 102);
-        avax_contract.insert(PROVIDER, 102);
-        avax_contract.insert(PROVIDER, 103);
-        avax_contract.insert(*OTHER_PROVIDER, 104);
+        eth_contract.insert(PROVIDER, 100, 1, false);
+        eth_contract.insert(*OTHER_PROVIDER, 101, 1, false);
+        eth_contract.insert(PROVIDER, 102, 1, false);
+        avax_contract.insert(PROVIDER, 102, 1, false);
+        avax_contract.insert(PROVIDER, 103, 1, false);
+        avax_contract.insert(*OTHER_PROVIDER, 104, 1, false);
 
         let response = get_and_assert_status(
             &server,
@@ -365,7 +380,7 @@ mod test {
             StatusCode::FORBIDDEN,
         )
         .await;
-        avax_contract.insert(PROVIDER, 99);
+        avax_contract.insert(PROVIDER, 99, 1, false);
         get_and_assert_status(
             &server,
             "/v1/chains/avalanche/revelations/99",
@@ -373,4 +388,78 @@ mod test {
         )
         .await;
     }
+
+    #[tokio::test]
+    async fn test_revelation_confirmation_delay() {
+        let (server, eth_contract, avax_contract) = test_server();
+
+        eth_contract.insert(PROVIDER, 0, 10, false);
+        eth_contract.insert(PROVIDER, 1, 11, false);
+        eth_contract.insert(PROVIDER, 2, 12, false);
+
+        avax_contract.insert(PROVIDER, 100, 10, false);
+        avax_contract.insert(PROVIDER, 101, 11, false);
+
+        eth_contract.set_block_number(10);
+        avax_contract.set_block_number(10);
+
+        get_and_assert_status(
+            &server,
+            "/v1/chains/ethereum/revelations/0",
+            StatusCode::FORBIDDEN,
+        )
+        .await;
+
+        get_and_assert_status(
+            &server,
+            "/v1/chains/avalanche/revelations/100",
+            StatusCode::FORBIDDEN,
+        )
+        .await;
+
+        eth_contract.set_block_number(11);
+        avax_contract.set_block_number(11);
+
+        get_and_assert_status(&server, "/v1/chains/ethereum/revelations/0", StatusCode::OK).await;
+
+        get_and_assert_status(
+            &server,
+            "/v1/chains/ethereum/revelations/1",
+            StatusCode::FORBIDDEN,
+        )
+        .await;
+
+        get_and_assert_status(
+            &server,
+            "/v1/chains/avalanche/revelations/100",
+            StatusCode::FORBIDDEN,
+        )
+        .await;
+
+        eth_contract.set_block_number(12);
+        avax_contract.set_block_number(12);
+
+        get_and_assert_status(&server, "/v1/chains/ethereum/revelations/1", StatusCode::OK).await;
+
+        get_and_assert_status(
+            &server,
+            "/v1/chains/ethereum/revelations/2",
+            StatusCode::FORBIDDEN,
+        )
+        .await;
+
+        get_and_assert_status(
+            &server,
+            "/v1/chains/avalanche/revelations/100",
+            StatusCode::OK,
+        )
+        .await;
+
+        get_and_assert_status(
+            &server,
+            "/v1/chains/avalanche/revelations/101",
+            StatusCode::FORBIDDEN,
+        )
+        .await;
+    }
 }

+ 14 - 6
fortuna/src/api/revelation.rs

@@ -15,6 +15,7 @@ use {
     },
     pythnet_sdk::wire::array,
     serde_with::serde_as,
+    tokio::try_join,
     utoipa::{
         IntoParams,
         ToSchema,
@@ -59,14 +60,20 @@ pub async fn revelation(
         .get(&chain_id)
         .ok_or_else(|| RestError::InvalidChainId)?;
 
-    let maybe_request = state
-        .contract
-        .get_request(state.provider_address, sequence)
-        .await
-        .map_err(|_| RestError::TemporarilyUnavailable)?;
+    let maybe_request_fut = state.contract.get_request(state.provider_address, sequence);
+
+    let current_block_number_fut = state.contract.get_block_number();
+
+    let (maybe_request, current_block_number) =
+        try_join!(maybe_request_fut, current_block_number_fut).map_err(|e| {
+            tracing::error!("RPC request failed {}", e);
+            RestError::TemporarilyUnavailable
+        })?;
 
     match maybe_request {
-        Some(_) => {
+        Some(r)
+            if current_block_number.saturating_sub(state.reveal_delay_blocks) >= r.block_number =>
+        {
             let value = &state
                 .state
                 .reveal(sequence)
@@ -77,6 +84,7 @@ pub async fn revelation(
                 value: encoded_value,
             }))
         }
+        Some(_) => Err(RestError::PendingConfirmation),
         None => Err(RestError::NoPendingRequest),
     }
 }

+ 10 - 1
fortuna/src/chain/ethereum.rs

@@ -2,7 +2,10 @@ use {
     crate::{
         chain::{
             reader,
-            reader::EntropyReader,
+            reader::{
+                BlockNumber,
+                EntropyReader,
+            },
         },
         config::EthereumConfig,
     },
@@ -194,9 +197,15 @@ impl EntropyReader for PythContract {
             Ok(Some(reader::Request {
                 provider:        r.provider,
                 sequence_number: r.sequence_number,
+                block_number:    r.block_number.try_into()?,
+                use_blockhash:   r.use_blockhash,
             }))
         } else {
             Ok(None)
         }
     }
+
+    async fn get_block_number(&self) -> Result<BlockNumber> {
+        Ok(self.client().get_block_number().await?.as_u64())
+    }
 }

+ 37 - 5
fortuna/src/chain/reader.rs

@@ -4,6 +4,8 @@ use {
     ethers::types::Address,
 };
 
+pub type BlockNumber = u64;
+
 /// EntropyReader is the read-only interface of the Entropy contract.
 #[async_trait]
 pub trait EntropyReader: Send + Sync {
@@ -12,6 +14,8 @@ pub trait EntropyReader: Send + Sync {
     /// need to become more generic.
     async fn get_request(&self, provider: Address, sequence_number: u64)
         -> Result<Option<Request>>;
+
+    async fn get_block_number(&self) -> Result<BlockNumber>;
 }
 
 /// An in-flight request stored in the contract.
@@ -21,6 +25,9 @@ pub trait EntropyReader: Send + Sync {
 pub struct Request {
     pub provider:        Address,
     pub sequence_number: u64,
+    // The block number where this request was created
+    pub block_number:    BlockNumber,
+    pub use_blockhash:   bool,
 }
 
 
@@ -28,6 +35,7 @@ pub struct Request {
 pub mod mock {
     use {
         crate::chain::reader::{
+            BlockNumber,
             EntropyReader,
             Request,
         },
@@ -41,19 +49,26 @@ pub mod mock {
     /// This class is internally locked to allow tests to modify the in-flight requests while
     /// the API is also holding a pointer to the same data structure.
     pub struct MockEntropyReader {
+        block_number: RwLock<BlockNumber>,
         /// The set of requests that are currently in-flight.
-        requests: RwLock<Vec<Request>>,
+        requests:     RwLock<Vec<Request>>,
     }
 
     impl MockEntropyReader {
-        pub fn with_requests(requests: &[(Address, u64)]) -> MockEntropyReader {
+        pub fn with_requests(
+            block_number: BlockNumber,
+            requests: &[(Address, u64, BlockNumber, bool)],
+        ) -> MockEntropyReader {
             MockEntropyReader {
-                requests: RwLock::new(
+                block_number: RwLock::new(block_number),
+                requests:     RwLock::new(
                     requests
                         .iter()
-                        .map(|&(a, s)| Request {
+                        .map(|&(a, s, b, u)| Request {
                             provider:        a,
                             sequence_number: s,
+                            block_number:    b,
+                            use_blockhash:   u,
                         })
                         .collect(),
                 ),
@@ -61,13 +76,26 @@ pub mod mock {
         }
 
         /// Insert a new request into the set of in-flight requests.
-        pub fn insert(&self, provider: Address, sequence: u64) -> &Self {
+        pub fn insert(
+            &self,
+            provider: Address,
+            sequence: u64,
+            block_number: BlockNumber,
+            use_blockhash: bool,
+        ) -> &Self {
             self.requests.write().unwrap().push(Request {
                 provider,
                 sequence_number: sequence,
+                block_number,
+                use_blockhash,
             });
             self
         }
+
+        pub fn set_block_number(&self, block_number: BlockNumber) -> &Self {
+            *(self.block_number.write().unwrap()) = block_number;
+            self
+        }
     }
 
     #[async_trait]
@@ -85,5 +113,9 @@ pub mod mock {
                 .find(|&r| r.sequence_number == sequence_number && r.provider == provider)
                 .map(|r| (*r).clone()))
         }
+
+        async fn get_block_number(&self) -> Result<BlockNumber> {
+            Ok(*self.block_number.read().unwrap())
+        }
     }
 }

+ 1 - 0
fortuna/src/command/run.rs

@@ -88,6 +88,7 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
             state: Arc::new(chain_state),
             contract,
             provider_address: opts.provider,
+            reveal_delay_blocks: chain_config.reveal_delay_blocks,
         };
 
         chains.insert(chain_id.clone(), state);

+ 7 - 1
fortuna/src/config.rs

@@ -1,5 +1,8 @@
 use {
-    crate::api::ChainId,
+    crate::{
+        api::ChainId,
+        chain::reader::BlockNumber,
+    },
     anyhow::{
         anyhow,
         Result,
@@ -115,6 +118,9 @@ pub struct EthereumConfig {
     /// Address of a Pyth Randomness contract to interact with.
     pub contract_addr: Address,
 
+    /// How many blocks to wait before revealing the random number.
+    pub reveal_delay_blocks: BlockNumber,
+
     /// Use the legacy transaction format (for networks without EIP 1559)
     #[serde(default)]
     pub legacy_tx: bool,

+ 1 - 2
fortuna/src/config/register_provider.rs

@@ -7,7 +7,6 @@ use {
         },
     },
     clap::Args,
-    ethers::types::U256,
 };
 
 #[derive(Args, Clone, Debug)]
@@ -34,7 +33,7 @@ pub struct RegisterProviderOptions {
     /// The fee to charge (in wei) for each requested random number
     #[arg(long = "pyth-contract-fee")]
     #[arg(default_value = "100")]
-    pub fee: U256,
+    pub fee: u128,
 
     /// The URI where clients can retrieve random values from this provider,
     /// i.e., wherever fortuna for this provider will be hosted.

+ 3 - 6
target_chains/ethereum/contracts/contracts/entropy/Entropy.sol

@@ -209,11 +209,8 @@ abstract contract Entropy is IEntropy, EntropyState {
         );
         req.requester = msg.sender;
 
-        if (useBlockHash) {
-            req.blockNumber = SafeCast.toUint96(block.number);
-        } else {
-            req.blockNumber = 0;
-        }
+        req.blockNumber = SafeCast.toUint64(block.number);
+        req.useBlockhash = useBlockHash;
 
         emit Requested(req);
     }
@@ -255,7 +252,7 @@ abstract contract Entropy is IEntropy, EntropyState {
         ) revert EntropyErrors.IncorrectRevelation();
 
         bytes32 blockHash = bytes32(uint256(0));
-        if (req.blockNumber != 0) {
+        if (req.useBlockhash) {
             blockHash = blockhash(req.blockNumber);
         }
 

+ 16 - 2
target_chains/ethereum/contracts/forge-test/Entropy.t.sol

@@ -205,8 +205,13 @@ contract EntropyTest is Test, EntropyTestUtils {
     }
 
     function testBasicFlow() public {
+        vm.roll(17);
         uint64 sequenceNumber = request(user2, provider1, 42, false);
-        assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 0);
+        assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 17);
+        assertEq(
+            random.getRequest(provider1, sequenceNumber).useBlockhash,
+            false
+        );
 
         assertRevealSucceeds(
             user2,
@@ -229,13 +234,18 @@ contract EntropyTest is Test, EntropyTestUtils {
     }
 
     function testDefaultProvider() public {
+        vm.roll(20);
         uint64 sequenceNumber = request(
             user2,
             random.getDefaultProvider(),
             42,
             false
         );
-        assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 0);
+        assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 20);
+        assertEq(
+            random.getRequest(provider1, sequenceNumber).useBlockhash,
+            false
+        );
 
         assertRevealReverts(
             user2,
@@ -390,6 +400,10 @@ contract EntropyTest is Test, EntropyTestUtils {
             random.getRequest(provider1, sequenceNumber).blockNumber,
             1234
         );
+        assertEq(
+            random.getRequest(provider1, sequenceNumber).useBlockhash,
+            true
+        );
 
         assertRevealSucceeds(
             user2,

+ 8 - 4
target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol

@@ -45,11 +45,15 @@ contract EntropyStructs {
         // eliminating 1 store.
         bytes32 commitment;
         // Storage slot 3 //
-        // If nonzero, the randomness requester wants the blockhash of this block to be incorporated into the random number.
-        // Note that we're using a uint96 such that we have an additional 20 bytes of storage afterward for an address.
-        // Although block.number returns a uint256, 96 bits should be plenty to index all of the blocks ever generated.
-        uint96 blockNumber;
+        // The number of the block where this request was created.
+        // Note that we're using a uint64 such that we have an additional space for an address and other fields in
+        // this storage slot. Although block.number returns a uint256, 64 bits should be plenty to index all of the
+        // blocks ever generated.
+        uint64 blockNumber;
         // The address that requested this random number.
         address requester;
+        // If true, incorporate the blockhash of blockNumber into the generated random value.
+        bool useBlockhash;
+        // There are 3 remaining bytes of free space in this slot.
     }
 }