Bladeren bron

feat(entropy): Entropy inspection (#1588)

* feat(fortuna): Utility command to check for unfulfilled requests with callback

* feat(fortuna): utility scripts for debugging reveal issues
Amin Moghaddam 1 jaar geleden
bovenliggende
commit
45a22d31b6

+ 78 - 13
apps/fortuna/Cargo.lock

@@ -552,15 +552,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chrono"
-version = "0.4.31"
+version = "0.4.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "num-traits",
  "serde",
- "windows-targets",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
@@ -1497,6 +1497,7 @@ dependencies = [
  "base64 0.21.4",
  "bincode",
  "byteorder",
+ "chrono",
  "clap",
  "ethabi",
  "ethers",
@@ -2533,7 +2534,7 @@ dependencies = [
  "libc",
  "redox_syscall 0.3.5",
  "smallvec",
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -4448,7 +4449,7 @@ version = "0.51.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -4457,7 +4458,7 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -4466,13 +4467,29 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
 ]
 
 [[package]]
@@ -4481,42 +4498,90 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+
 [[package]]
 name = "winnow"
 version = "0.5.16"

+ 1 - 0
apps/fortuna/Cargo.toml

@@ -34,6 +34,7 @@ utoipa-swagger-ui  = { version = "3.1.4", features = ["axum"] }
 once_cell = "1.18.0"
 lazy_static = "1.4.0"
 url = "2.5.0"
+chrono = { version = "0.4.38", features = ["clock", "std"] , default-features = false}
 
 
 [dev-dependencies]

+ 2 - 0
apps/fortuna/src/command.rs

@@ -1,5 +1,6 @@
 mod generate;
 mod get_request;
+mod inspect;
 mod register_provider;
 mod request_randomness;
 mod run;
@@ -8,6 +9,7 @@ mod setup_provider;
 pub use {
     generate::generate,
     get_request::get_request,
+    inspect::inspect,
     register_provider::register_provider,
     request_randomness::request_randomness,
     run::run,

+ 116 - 0
apps/fortuna/src/command/inspect.rs

@@ -0,0 +1,116 @@
+use {
+    crate::{
+        chain::ethereum::{
+            PythContract,
+            Request,
+        },
+        config::{
+            Config,
+            EthereumConfig,
+            InspectOptions,
+        },
+    },
+    anyhow::Result,
+    ethers::{
+        contract::Multicall,
+        middleware::Middleware,
+        prelude::{
+            Http,
+            Provider,
+        },
+    },
+};
+
+pub async fn inspect(opts: &InspectOptions) -> Result<()> {
+    match opts.chain_id.clone() {
+        Some(chain_id) => {
+            let chain_config = &Config::load(&opts.config.config)?.get_chain_config(&chain_id)?;
+            inspect_chain(chain_config, opts.num_requests, opts.multicall_batch_size).await?;
+        }
+        None => {
+            let config = Config::load(&opts.config.config)?;
+            for (chain_id, chain_config) in config.chains.iter() {
+                println!("Inspecting chain: {}", chain_id);
+                inspect_chain(chain_config, opts.num_requests, opts.multicall_batch_size).await?;
+            }
+        }
+    }
+    Ok(())
+}
+
+async fn inspect_chain(
+    chain_config: &EthereumConfig,
+    num_requests: u64,
+    multicall_batch_size: u64,
+) -> Result<()> {
+    let rpc_provider = Provider::<Http>::try_from(&chain_config.geth_rpc_addr)?;
+    let multicall_exists = rpc_provider
+        .get_code(ethers::contract::MULTICALL_ADDRESS, None)
+        .await
+        .expect("Failed to get code")
+        .len()
+        > 0;
+
+    let contract = PythContract::from_config(chain_config)?;
+    let entropy_provider = contract.get_default_provider().call().await?;
+    let provider_info = contract.get_provider_info(entropy_provider).call().await?;
+    let mut current_request_number = provider_info.sequence_number;
+    println!("Initial request number: {}", current_request_number);
+    let last_request_number = current_request_number.saturating_sub(num_requests);
+    if multicall_exists {
+        println!("Using multicall");
+        let mut multicall = Multicall::new(
+            rpc_provider.clone(),
+            Some(ethers::contract::MULTICALL_ADDRESS),
+        )
+        .await?;
+        while current_request_number > last_request_number {
+            multicall.clear_calls();
+            for _ in 0..multicall_batch_size {
+                if current_request_number == 0 {
+                    break;
+                }
+                multicall.add_call(
+                    contract.get_request(entropy_provider, current_request_number),
+                    false,
+                );
+                current_request_number -= 1;
+            }
+            let return_data: Vec<Request> = multicall.call_array().await?;
+            for request in return_data {
+                process_request(rpc_provider.clone(), request).await?;
+            }
+            println!("Current request number: {}", current_request_number);
+        }
+    } else {
+        println!("Multicall not deployed in this chain, fetching requests one by one");
+        while current_request_number > last_request_number {
+            let request = contract
+                .get_request(entropy_provider, current_request_number)
+                .call()
+                .await?;
+            process_request(rpc_provider.clone(), request).await?;
+            current_request_number -= 1;
+            if current_request_number % 100 == 0 {
+                println!("Current request number: {}", current_request_number);
+            }
+        }
+    }
+    Ok(())
+}
+
+async fn process_request(rpc_provider: Provider<Http>, request: Request) -> Result<()> {
+    if request.sequence_number != 0 && request.is_request_with_callback {
+        let block = rpc_provider
+            .get_block(request.block_number)
+            .await?
+            .expect("Block not found");
+        let datetime = chrono::DateTime::from_timestamp(block.timestamp.as_u64() as i64, 0)
+            .expect("Invalid timestamp");
+        println!(
+            "{} sequence_number:{} block_number:{} requester:{}",
+            datetime, request.sequence_number, request.block_number, request.requester
+        );
+    }
+    Ok(())
+}

+ 5 - 0
apps/fortuna/src/config.rs

@@ -30,6 +30,7 @@ use {
 pub use {
     generate::GenerateOptions,
     get_request::GetRequestOptions,
+    inspect::InspectOptions,
     register_provider::RegisterProviderOptions,
     request_randomness::RequestRandomnessOptions,
     run::RunOptions,
@@ -38,6 +39,7 @@ pub use {
 
 mod generate;
 mod get_request;
+mod inspect;
 mod register_provider;
 mod request_randomness;
 mod run;
@@ -66,6 +68,9 @@ pub enum Options {
     /// Request a random number from the contract.
     RequestRandomness(RequestRandomnessOptions),
 
+    /// Inspect recent requests and find unfulfilled requests with callback.
+    Inspect(InspectOptions),
+
     /// Generate a random number by running the entire protocol end-to-end
     Generate(GenerateOptions),
 

+ 28 - 0
apps/fortuna/src/config/inspect.rs

@@ -0,0 +1,28 @@
+use {
+    crate::{
+        api::ChainId,
+        config::ConfigOptions,
+    },
+    clap::Args,
+};
+
+
+#[derive(Args, Clone, Debug)]
+#[command(next_help_heading = "Inspect Options")]
+#[group(id = "Inspect")]
+pub struct InspectOptions {
+    #[command(flatten)]
+    pub config: ConfigOptions,
+
+    /// Check the requests on this chain, or all chains if not specified.
+    #[arg(long = "chain-id")]
+    pub chain_id: Option<ChainId>,
+
+    /// The number of requests to inspect starting from the most recent request.
+    #[arg(long = "num-requests", default_value = "1000")]
+    pub num_requests: u64,
+
+    /// The number of calls to make in each batch when using multicall.
+    #[arg(long = "multicall-batch-size", default_value = "100")]
+    pub multicall_batch_size: u64,
+}

+ 1 - 0
apps/fortuna/src/main.rs

@@ -42,5 +42,6 @@ async fn main() -> Result<()> {
         config::Options::RegisterProvider(opts) => command::register_provider(&opts).await,
         config::Options::SetupProvider(opts) => command::setup_provider(&opts).await,
         config::Options::RequestRandomness(opts) => command::request_randomness(&opts).await,
+        config::Options::Inspect(opts) => command::inspect(&opts).await,
     }
 }

+ 68 - 0
contract_manager/scripts/entropy_debug_reveal.ts

@@ -0,0 +1,68 @@
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { toPrivateKey } from "../src";
+import {
+  COMMON_DEPLOY_OPTIONS,
+  findEntropyContract,
+  findEvmChain,
+} from "./common";
+
+const parser = yargs(hideBin(process.argv))
+  .usage(
+    "Tries to reveal an entropy request with callback using the provided private key.\n" +
+      "This can be used to manually debug why a callback was not triggered.\n" +
+      "Usage: $0 --chain <chain-id> --private-key <private-key> --sequence-number <sequence-number>"
+  )
+  .options({
+    chain: {
+      type: "string",
+      demandOption: true,
+      desc: "test latency for the contract on this chain",
+    },
+    "private-key": COMMON_DEPLOY_OPTIONS["private-key"],
+    "sequence-number": {
+      type: "number",
+      demandOption: true,
+      desc: "sequence number of the request to reveal",
+    },
+  });
+
+async function main() {
+  const argv = await parser.argv;
+  const chain = findEvmChain(argv.chain);
+  const contract = findEntropyContract(chain);
+  const sequenceNumber = argv.sequenceNumber;
+
+  const provider = await contract.getDefaultProvider();
+  const providerInfo = await contract.getProviderInfo(provider);
+  const privateKey = toPrivateKey(argv.privateKey);
+  const request = await contract.getRequest(provider, sequenceNumber);
+  if (request.sequenceNumber === "0") {
+    console.log("Request not found");
+    return;
+  }
+  console.log("Request block number: ", request.blockNumber);
+  const userRandomNumber = await contract.getUserRandomNumber(
+    provider,
+    sequenceNumber,
+    parseInt(request.blockNumber)
+  );
+  console.log("User random number: ", userRandomNumber);
+  const revealUrl = providerInfo.uri + `/revelations/${sequenceNumber}`;
+  const fortunaResponse = await fetch(revealUrl);
+  if (fortunaResponse.status !== 200) {
+    console.log("Fortuna response status: ", fortunaResponse.status);
+    return;
+  }
+  const payload = await fortunaResponse.json();
+  const providerRevelation = "0x" + payload.value.data;
+  await contract.revealWithCallback(
+    userRandomNumber,
+    providerRevelation,
+    provider,
+    sequenceNumber,
+    privateKey
+  );
+}
+
+main();

+ 80 - 0
contract_manager/src/contracts/evm.ts

@@ -489,6 +489,17 @@ interface EntropyProviderInfo {
   currentCommitmentSequenceNumber: string;
 }
 
+interface EntropyRequest {
+  provider: string;
+  sequenceNumber: string;
+  numHashes: string;
+  commitment: string;
+  blockNumber: string;
+  requester: string;
+  useBlockhash: boolean;
+  isRequestWithCallback: boolean;
+}
+
 export class EvmEntropyContract extends Storable {
   static type = "EvmEntropyContract";
 
@@ -622,6 +633,75 @@ export class EvmEntropyContract extends Storable {
     };
   }
 
+  /**
+   * Returns the request for the given provider and sequence number
+   * This will return a EntropyRequest object with sequenceNumber "0" if the request does not exist
+   * @param provider The entropy provider address
+   * @param sequenceNumber The sequence number of the request for the provider
+   */
+  async getRequest(
+    provider: string,
+    sequenceNumber: number
+  ): Promise<EntropyRequest> {
+    const contract = this.getContract();
+    return contract.methods.getRequest(provider, sequenceNumber).call();
+  }
+
+  /**
+   * Returns the user random number for the request with the given provider and sequence number
+   * This method assumes the request was made with a callback option and fetches the user random number
+   * by finding the `RequestedWithCallback` log. The block number at which the request was made is required
+   * to find the log.
+   * @param provider The entropy provider address
+   * @param sequenceNumber The sequence number of the request for the provider
+   * @param block The block number at which the request was made, you can find this using the `getRequest` method
+   */
+  async getUserRandomNumber(
+    provider: string,
+    sequenceNumber: number,
+    block: number
+  ): Promise<string> {
+    const contract = this.getContract();
+    const result = await contract.getPastEvents("RequestedWithCallback", {
+      fromBlock: block,
+      toBlock: block,
+      filter: {
+        provider,
+        sequenceNumber: sequenceNumber,
+      },
+    });
+    return result[0].returnValues.userRandomNumber;
+  }
+
+  /**
+   * Submits a transaction to the entropy contract to reveal the random number and call the callback function
+   * @param userRandomNumber The random number generated by the user, you can find this using the `getUserRandomNumber` method
+   * @param providerRevelation The random number generated by the provider, you can find this via the provider server
+   * @param provider The entropy provider address
+   * @param sequenceNumber The sequence number of the request for the provider
+   * @param senderPrivateKey The private key to use for submitting the transaction on-chain
+   */
+  async revealWithCallback(
+    userRandomNumber: string,
+    providerRevelation: string,
+    provider: string,
+    sequenceNumber: number,
+    senderPrivateKey: PrivateKey
+  ) {
+    const web3 = new Web3(this.chain.getRpcUrl());
+    const contract = this.getContract();
+    const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
+    const transactionObject = contract.methods.revealWithCallback(
+      provider,
+      sequenceNumber,
+      userRandomNumber,
+      providerRevelation
+    );
+    return this.chain.estiamteAndSendTransaction(transactionObject, {
+      from: address,
+    });
+  }
+
   generateUserRandomNumber() {
     const web3 = new Web3(this.chain.getRpcUrl());
     return web3.utils.randomHex(32);