瀏覽代碼

[entropy] Add a default provider (#1150)

* making a mess here

* add default provider

* cleanup

* test new abi

* add uri to fortuna

* pr comments
Jayant Krishnamurthy 2 年之前
父節點
當前提交
a5646bf840

+ 1 - 1
fortuna/Cargo.lock

@@ -1486,7 +1486,7 @@ dependencies = [
 
 [[package]]
 name = "fortuna"
-version = "1.1.0"
+version = "2.0.0"
 dependencies = [
  "anyhow",
  "axum",

+ 360 - 286
fortuna/src/abi.json

@@ -1,482 +1,556 @@
 [
   {
-    "anonymous": false,
+    "type": "constructor",
     "inputs": [
       {
-        "components": [
-          {
-            "internalType": "uint256",
-            "name": "feeInWei",
-            "type": "uint256"
-          },
-          {
-            "internalType": "uint256",
-            "name": "accruedFeesInWei",
-            "type": "uint256"
-          },
-          {
-            "internalType": "bytes32",
-            "name": "originalCommitment",
-            "type": "bytes32"
-          },
-          {
-            "internalType": "uint64",
-            "name": "originalCommitmentSequenceNumber",
-            "type": "uint64"
-          },
-          {
-            "internalType": "bytes",
-            "name": "commitmentMetadata",
-            "type": "bytes"
-          },
-          {
-            "internalType": "uint64",
-            "name": "endSequenceNumber",
-            "type": "uint64"
-          },
-          {
-            "internalType": "uint64",
-            "name": "sequenceNumber",
-            "type": "uint64"
-          },
-          {
-            "internalType": "bytes32",
-            "name": "currentCommitment",
-            "type": "bytes32"
-          },
-          {
-            "internalType": "uint64",
-            "name": "currentCommitmentSequenceNumber",
-            "type": "uint64"
-          }
-        ],
-        "indexed": false,
-        "internalType": "struct EntropyStructs.ProviderInfo",
-        "name": "provider",
-        "type": "tuple"
-      }
-    ],
-    "name": "Registered",
-    "type": "event"
-  },
-  {
-    "anonymous": false,
-    "inputs": [
-      {
-        "components": [
-          {
-            "internalType": "address",
-            "name": "provider",
-            "type": "address"
-          },
-          {
-            "internalType": "uint64",
-            "name": "sequenceNumber",
-            "type": "uint64"
-          },
-          {
-            "internalType": "bytes32",
-            "name": "userCommitment",
-            "type": "bytes32"
-          },
-          {
-            "internalType": "bytes32",
-            "name": "providerCommitment",
-            "type": "bytes32"
-          },
-          {
-            "internalType": "uint64",
-            "name": "providerCommitmentSequenceNumber",
-            "type": "uint64"
-          },
-          {
-            "internalType": "uint256",
-            "name": "blockNumber",
-            "type": "uint256"
-          }
-        ],
-        "indexed": false,
-        "internalType": "struct EntropyStructs.Request",
-        "name": "request",
-        "type": "tuple"
-      }
-    ],
-    "name": "Requested",
-    "type": "event"
-  },
-  {
-    "anonymous": false,
-    "inputs": [
-      {
-        "components": [
-          {
-            "internalType": "address",
-            "name": "provider",
-            "type": "address"
-          },
-          {
-            "internalType": "uint64",
-            "name": "sequenceNumber",
-            "type": "uint64"
-          },
-          {
-            "internalType": "bytes32",
-            "name": "userCommitment",
-            "type": "bytes32"
-          },
-          {
-            "internalType": "bytes32",
-            "name": "providerCommitment",
-            "type": "bytes32"
-          },
-          {
-            "internalType": "uint64",
-            "name": "providerCommitmentSequenceNumber",
-            "type": "uint64"
-          },
-          {
-            "internalType": "uint256",
-            "name": "blockNumber",
-            "type": "uint256"
-          }
-        ],
-        "indexed": false,
-        "internalType": "struct EntropyStructs.Request",
-        "name": "request",
-        "type": "tuple"
+        "name": "pythFeeInWei",
+        "type": "uint256",
+        "internalType": "uint256"
       },
       {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "userRevelation",
-        "type": "bytes32"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "providerRevelation",
-        "type": "bytes32"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "blockHash",
-        "type": "bytes32"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "randomNumber",
-        "type": "bytes32"
+        "name": "defaultProvider",
+        "type": "address",
+        "internalType": "address"
       }
     ],
-    "name": "Revealed",
-    "type": "event"
+    "stateMutability": "nonpayable"
   },
   {
+    "type": "function",
+    "name": "combineRandomValues",
     "inputs": [
       {
-        "internalType": "bytes32",
         "name": "userRandomness",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       },
       {
-        "internalType": "bytes32",
         "name": "providerRandomness",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       },
       {
-        "internalType": "bytes32",
         "name": "blockHash",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       }
     ],
-    "name": "combineRandomValues",
     "outputs": [
       {
-        "internalType": "bytes32",
         "name": "combinedRandomness",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       }
     ],
-    "stateMutability": "pure",
-    "type": "function"
+    "stateMutability": "pure"
   },
   {
+    "type": "function",
+    "name": "constructUserCommitment",
     "inputs": [
       {
-        "internalType": "bytes32",
         "name": "userRandomness",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       }
     ],
-    "name": "constructUserCommitment",
     "outputs": [
       {
-        "internalType": "bytes32",
         "name": "userCommitment",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       }
     ],
-    "stateMutability": "pure",
-    "type": "function"
+    "stateMutability": "pure"
   },
   {
-    "inputs": [],
+    "type": "function",
     "name": "getAccruedPythFees",
+    "inputs": [],
     "outputs": [
       {
-        "internalType": "uint256",
         "name": "accruedPythFeesInWei",
-        "type": "uint256"
+        "type": "uint256",
+        "internalType": "uint256"
       }
     ],
-    "stateMutability": "view",
-    "type": "function"
+    "stateMutability": "view"
   },
   {
-    "inputs": [
+    "type": "function",
+    "name": "getDefaultProvider",
+    "inputs": [],
+    "outputs": [
       {
-        "internalType": "address",
         "name": "provider",
-        "type": "address"
+        "type": "address",
+        "internalType": "address"
       }
     ],
+    "stateMutability": "view"
+  },
+  {
+    "type": "function",
     "name": "getFee",
+    "inputs": [
+      {
+        "name": "provider",
+        "type": "address",
+        "internalType": "address"
+      }
+    ],
     "outputs": [
       {
-        "internalType": "uint256",
         "name": "feeAmount",
-        "type": "uint256"
+        "type": "uint256",
+        "internalType": "uint256"
       }
     ],
-    "stateMutability": "view",
-    "type": "function"
+    "stateMutability": "view"
   },
   {
+    "type": "function",
+    "name": "getProviderInfo",
     "inputs": [
       {
-        "internalType": "address",
         "name": "provider",
-        "type": "address"
+        "type": "address",
+        "internalType": "address"
       }
     ],
-    "name": "getProviderInfo",
     "outputs": [
       {
+        "name": "info",
+        "type": "tuple",
+        "internalType": "struct EntropyStructs.ProviderInfo",
         "components": [
           {
-            "internalType": "uint256",
             "name": "feeInWei",
-            "type": "uint256"
+            "type": "uint256",
+            "internalType": "uint256"
           },
           {
-            "internalType": "uint256",
             "name": "accruedFeesInWei",
-            "type": "uint256"
+            "type": "uint256",
+            "internalType": "uint256"
           },
           {
-            "internalType": "bytes32",
             "name": "originalCommitment",
-            "type": "bytes32"
+            "type": "bytes32",
+            "internalType": "bytes32"
           },
           {
-            "internalType": "uint64",
             "name": "originalCommitmentSequenceNumber",
-            "type": "uint64"
+            "type": "uint64",
+            "internalType": "uint64"
           },
           {
-            "internalType": "bytes",
             "name": "commitmentMetadata",
-            "type": "bytes"
+            "type": "bytes",
+            "internalType": "bytes"
+          },
+          {
+            "name": "uri",
+            "type": "bytes",
+            "internalType": "bytes"
           },
           {
-            "internalType": "uint64",
             "name": "endSequenceNumber",
-            "type": "uint64"
+            "type": "uint64",
+            "internalType": "uint64"
           },
           {
-            "internalType": "uint64",
             "name": "sequenceNumber",
-            "type": "uint64"
+            "type": "uint64",
+            "internalType": "uint64"
           },
           {
-            "internalType": "bytes32",
             "name": "currentCommitment",
-            "type": "bytes32"
+            "type": "bytes32",
+            "internalType": "bytes32"
           },
           {
-            "internalType": "uint64",
             "name": "currentCommitmentSequenceNumber",
-            "type": "uint64"
+            "type": "uint64",
+            "internalType": "uint64"
           }
-        ],
-        "internalType": "struct EntropyStructs.ProviderInfo",
-        "name": "info",
-        "type": "tuple"
+        ]
       }
     ],
-    "stateMutability": "view",
-    "type": "function"
+    "stateMutability": "view"
   },
   {
+    "type": "function",
+    "name": "getRequest",
     "inputs": [
       {
-        "internalType": "address",
         "name": "provider",
-        "type": "address"
+        "type": "address",
+        "internalType": "address"
       },
       {
-        "internalType": "uint64",
         "name": "sequenceNumber",
-        "type": "uint64"
+        "type": "uint64",
+        "internalType": "uint64"
       }
     ],
-    "name": "getRequest",
     "outputs": [
       {
+        "name": "req",
+        "type": "tuple",
+        "internalType": "struct EntropyStructs.Request",
         "components": [
           {
-            "internalType": "address",
             "name": "provider",
-            "type": "address"
+            "type": "address",
+            "internalType": "address"
           },
           {
-            "internalType": "uint64",
             "name": "sequenceNumber",
-            "type": "uint64"
+            "type": "uint64",
+            "internalType": "uint64"
           },
           {
-            "internalType": "bytes32",
             "name": "userCommitment",
-            "type": "bytes32"
+            "type": "bytes32",
+            "internalType": "bytes32"
           },
           {
-            "internalType": "bytes32",
             "name": "providerCommitment",
-            "type": "bytes32"
+            "type": "bytes32",
+            "internalType": "bytes32"
           },
           {
-            "internalType": "uint64",
             "name": "providerCommitmentSequenceNumber",
-            "type": "uint64"
+            "type": "uint64",
+            "internalType": "uint64"
           },
           {
-            "internalType": "uint256",
             "name": "blockNumber",
-            "type": "uint256"
+            "type": "uint256",
+            "internalType": "uint256"
           }
-        ],
-        "internalType": "struct EntropyStructs.Request",
-        "name": "req",
-        "type": "tuple"
+        ]
       }
     ],
-    "stateMutability": "view",
-    "type": "function"
+    "stateMutability": "view"
   },
   {
+    "type": "function",
+    "name": "register",
     "inputs": [
       {
-        "internalType": "uint256",
         "name": "feeInWei",
-        "type": "uint256"
+        "type": "uint256",
+        "internalType": "uint256"
       },
       {
-        "internalType": "bytes32",
         "name": "commitment",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       },
       {
-        "internalType": "bytes",
         "name": "commitmentMetadata",
-        "type": "bytes"
+        "type": "bytes",
+        "internalType": "bytes"
       },
       {
-        "internalType": "uint64",
         "name": "chainLength",
-        "type": "uint64"
+        "type": "uint64",
+        "internalType": "uint64"
+      },
+      {
+        "name": "uri",
+        "type": "bytes",
+        "internalType": "bytes"
       }
     ],
-    "name": "register",
     "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
+    "stateMutability": "nonpayable"
   },
   {
+    "type": "function",
+    "name": "request",
     "inputs": [
       {
-        "internalType": "address",
         "name": "provider",
-        "type": "address"
+        "type": "address",
+        "internalType": "address"
       },
       {
-        "internalType": "bytes32",
         "name": "userCommitment",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       },
       {
-        "internalType": "bool",
         "name": "useBlockHash",
-        "type": "bool"
+        "type": "bool",
+        "internalType": "bool"
       }
     ],
-    "name": "request",
     "outputs": [
       {
-        "internalType": "uint64",
         "name": "assignedSequenceNumber",
-        "type": "uint64"
+        "type": "uint64",
+        "internalType": "uint64"
       }
     ],
-    "stateMutability": "payable",
-    "type": "function"
+    "stateMutability": "payable"
   },
   {
+    "type": "function",
+    "name": "reveal",
     "inputs": [
       {
-        "internalType": "address",
         "name": "provider",
-        "type": "address"
+        "type": "address",
+        "internalType": "address"
       },
       {
-        "internalType": "uint64",
         "name": "sequenceNumber",
-        "type": "uint64"
+        "type": "uint64",
+        "internalType": "uint64"
       },
       {
-        "internalType": "bytes32",
         "name": "userRandomness",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       },
       {
-        "internalType": "bytes32",
         "name": "providerRevelation",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       }
     ],
-    "name": "reveal",
     "outputs": [
       {
-        "internalType": "bytes32",
         "name": "randomNumber",
-        "type": "bytes32"
+        "type": "bytes32",
+        "internalType": "bytes32"
       }
     ],
-    "stateMutability": "nonpayable",
-    "type": "function"
+    "stateMutability": "nonpayable"
   },
   {
+    "type": "function",
+    "name": "withdraw",
     "inputs": [
       {
-        "internalType": "uint256",
         "name": "amount",
-        "type": "uint256"
+        "type": "uint256",
+        "internalType": "uint256"
       }
     ],
-    "name": "withdraw",
     "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
+    "stateMutability": "nonpayable"
+  },
+  {
+    "type": "event",
+    "name": "Registered",
+    "inputs": [
+      {
+        "name": "provider",
+        "type": "tuple",
+        "indexed": false,
+        "internalType": "struct EntropyStructs.ProviderInfo",
+        "components": [
+          {
+            "name": "feeInWei",
+            "type": "uint256",
+            "internalType": "uint256"
+          },
+          {
+            "name": "accruedFeesInWei",
+            "type": "uint256",
+            "internalType": "uint256"
+          },
+          {
+            "name": "originalCommitment",
+            "type": "bytes32",
+            "internalType": "bytes32"
+          },
+          {
+            "name": "originalCommitmentSequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          },
+          {
+            "name": "commitmentMetadata",
+            "type": "bytes",
+            "internalType": "bytes"
+          },
+          {
+            "name": "uri",
+            "type": "bytes",
+            "internalType": "bytes"
+          },
+          {
+            "name": "endSequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          },
+          {
+            "name": "sequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          },
+          {
+            "name": "currentCommitment",
+            "type": "bytes32",
+            "internalType": "bytes32"
+          },
+          {
+            "name": "currentCommitmentSequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          }
+        ]
+      }
+    ],
+    "anonymous": false
+  },
+  {
+    "type": "event",
+    "name": "Requested",
+    "inputs": [
+      {
+        "name": "request",
+        "type": "tuple",
+        "indexed": false,
+        "internalType": "struct EntropyStructs.Request",
+        "components": [
+          {
+            "name": "provider",
+            "type": "address",
+            "internalType": "address"
+          },
+          {
+            "name": "sequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          },
+          {
+            "name": "userCommitment",
+            "type": "bytes32",
+            "internalType": "bytes32"
+          },
+          {
+            "name": "providerCommitment",
+            "type": "bytes32",
+            "internalType": "bytes32"
+          },
+          {
+            "name": "providerCommitmentSequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          },
+          {
+            "name": "blockNumber",
+            "type": "uint256",
+            "internalType": "uint256"
+          }
+        ]
+      }
+    ],
+    "anonymous": false
+  },
+  {
+    "type": "event",
+    "name": "Revealed",
+    "inputs": [
+      {
+        "name": "request",
+        "type": "tuple",
+        "indexed": false,
+        "internalType": "struct EntropyStructs.Request",
+        "components": [
+          {
+            "name": "provider",
+            "type": "address",
+            "internalType": "address"
+          },
+          {
+            "name": "sequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          },
+          {
+            "name": "userCommitment",
+            "type": "bytes32",
+            "internalType": "bytes32"
+          },
+          {
+            "name": "providerCommitment",
+            "type": "bytes32",
+            "internalType": "bytes32"
+          },
+          {
+            "name": "providerCommitmentSequenceNumber",
+            "type": "uint64",
+            "internalType": "uint64"
+          },
+          {
+            "name": "blockNumber",
+            "type": "uint256",
+            "internalType": "uint256"
+          }
+        ]
+      },
+      {
+        "name": "userRevelation",
+        "type": "bytes32",
+        "indexed": false,
+        "internalType": "bytes32"
+      },
+      {
+        "name": "providerRevelation",
+        "type": "bytes32",
+        "indexed": false,
+        "internalType": "bytes32"
+      },
+      {
+        "name": "blockHash",
+        "type": "bytes32",
+        "indexed": false,
+        "internalType": "bytes32"
+      },
+      {
+        "name": "randomNumber",
+        "type": "bytes32",
+        "indexed": false,
+        "internalType": "bytes32"
+      }
+    ],
+    "anonymous": false
+  },
+  {
+    "type": "error",
+    "name": "AssertionFailure",
+    "inputs": []
+  },
+  {
+    "type": "error",
+    "name": "IncorrectProviderRevelation",
+    "inputs": []
+  },
+  {
+    "type": "error",
+    "name": "IncorrectUserRevelation",
+    "inputs": []
+  },
+  {
+    "type": "error",
+    "name": "InsufficientFee",
+    "inputs": []
+  },
+  {
+    "type": "error",
+    "name": "NoSuchProvider",
+    "inputs": []
+  },
+  {
+    "type": "error",
+    "name": "OutOfRandomness",
+    "inputs": []
   }
 ]

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

@@ -55,6 +55,7 @@ pub async fn register_provider(opts: &RegisterProviderOptions) -> Result<()> {
             commitment,
             bincode::serialize(&commitment_metadata)?.into(),
             commitment_length,
+            bincode::serialize(&opts.uri)?.into(),
         )
         .send()
         .await?

+ 6 - 0
fortuna/src/config/register_provider.rs

@@ -36,4 +36,10 @@ pub struct RegisterProviderOptions {
     #[arg(long = "pyth-contract-fee")]
     #[arg(default_value = "100")]
     pub fee: U256,
+
+    /// The URI where clients can retrieve random values from this provider,
+    /// i.e., wherever fortuna for this provider will be hosted.
+    #[arg(long = "uri")]
+    #[arg(default_value = "")]
+    pub uri: String,
 }

+ 14 - 10
target_chains/ethereum/contracts/contracts/entropy/Entropy.sol

@@ -72,19 +72,12 @@ import "./EntropyState.sol";
 // protocol prior to the random number being revealed (i.e., prior to step (6) above). Integrators should ensure that
 // the user is always incentivized to reveal their random number, and that the protocol has an escape hatch for
 // cases where the user chooses not to reveal.
-//
-// TODOs:
-// - governance??
-// - correct method access modifiers (public vs external)
-// - gas optimizations
-// - function to check invariants??
-// - need to increment pyth fees if someone transfers funds to the contract via another method
-// - off-chain data ERC support?
 contract Entropy is IEntropy, EntropyState {
     // TODO: Use an upgradeable proxy
-    constructor(uint pythFeeInWei) {
+    constructor(uint pythFeeInWei, address defaultProvider) {
         _state.accruedPythFeesInWei = 0;
         _state.pythFeeInWei = pythFeeInWei;
+        _state.defaultProvider = defaultProvider;
     }
 
     // Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters
@@ -96,7 +89,8 @@ contract Entropy is IEntropy, EntropyState {
         uint feeInWei,
         bytes32 commitment,
         bytes calldata commitmentMetadata,
-        uint64 chainLength
+        uint64 chainLength,
+        bytes calldata uri
     ) public override {
         if (chainLength == 0) revert EntropyErrors.AssertionFailure();
 
@@ -117,6 +111,7 @@ contract Entropy is IEntropy, EntropyState {
         provider.currentCommitmentSequenceNumber = provider.sequenceNumber;
         provider.commitmentMetadata = commitmentMetadata;
         provider.endSequenceNumber = provider.sequenceNumber + chainLength;
+        provider.uri = uri;
 
         provider.sequenceNumber += 1;
 
@@ -262,6 +257,15 @@ contract Entropy is IEntropy, EntropyState {
         info = _state.providers[provider];
     }
 
+    function getDefaultProvider()
+        public
+        view
+        override
+        returns (address provider)
+    {
+        provider = _state.defaultProvider;
+    }
+
     function getRequest(
         address provider,
         uint64 sequenceNumber

+ 1 - 0
target_chains/ethereum/contracts/contracts/entropy/EntropyState.sol

@@ -8,6 +8,7 @@ contract EntropyInternalStructs {
     struct State {
         uint pythFeeInWei;
         uint accruedPythFeesInWei;
+        address defaultProvider;
         mapping(address => EntropyStructs.ProviderInfo) providers;
         mapping(bytes32 => EntropyStructs.Request) requests;
     }

+ 72 - 8
target_chains/ethereum/contracts/forge-test/Entropy.t.sol

@@ -19,10 +19,13 @@ contract EntropyTest is Test {
     bytes32[] provider1Proofs;
     uint provider1FeeInWei = 8;
     uint64 provider1ChainLength = 100;
+    bytes provider1Uri = bytes("https://foo.com");
+    bytes provider1CommitmentMetadata = hex"0100";
 
     address public provider2 = address(2);
     bytes32[] provider2Proofs;
     uint provider2FeeInWei = 20;
+    bytes provider2Uri = bytes("https://bar.com");
 
     address public user1 = address(3);
     address public user2 = address(4);
@@ -32,7 +35,7 @@ contract EntropyTest is Test {
     bytes32 ALL_ZEROS = bytes32(uint256(0));
 
     function setUp() public {
-        random = new Entropy(pythFeeInWei);
+        random = new Entropy(pythFeeInWei, provider1);
 
         bytes32[] memory hashChain1 = generateHashChain(
             provider1,
@@ -44,14 +47,21 @@ contract EntropyTest is Test {
         random.register(
             provider1FeeInWei,
             provider1Proofs[0],
-            hex"0100",
-            provider1ChainLength
+            provider1CommitmentMetadata,
+            provider1ChainLength,
+            provider1Uri
         );
 
         bytes32[] memory hashChain2 = generateHashChain(provider2, 0, 100);
         provider2Proofs = hashChain2;
         vm.prank(provider2);
-        random.register(provider2FeeInWei, provider2Proofs[0], hex"0200", 100);
+        random.register(
+            provider2FeeInWei,
+            provider2Proofs[0],
+            hex"0200",
+            100,
+            provider2Uri
+        );
     }
 
     function generateHashChain(
@@ -59,7 +69,9 @@ contract EntropyTest is Test {
         uint64 startSequenceNumber,
         uint64 size
     ) public pure returns (bytes32[] memory hashChain) {
-        bytes32 initialValue = keccak256(abi.encodePacked(startSequenceNumber));
+        bytes32 initialValue = keccak256(
+            abi.encodePacked(provider, startSequenceNumber)
+        );
         hashChain = new bytes32[](size);
         for (uint64 i = 0; i < size; i++) {
             hashChain[size - (i + 1)] = initialValue;
@@ -212,6 +224,31 @@ contract EntropyTest is Test {
         );
     }
 
+    function testDefaultProvider() public {
+        uint64 sequenceNumber = request(
+            user2,
+            random.getDefaultProvider(),
+            42,
+            false
+        );
+        assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 0);
+
+        assertRevealReverts(
+            random.getDefaultProvider(),
+            sequenceNumber,
+            42,
+            provider2Proofs[sequenceNumber]
+        );
+
+        assertRevealSucceeds(
+            random.getDefaultProvider(),
+            sequenceNumber,
+            42,
+            provider1Proofs[sequenceNumber],
+            ALL_ZEROS
+        );
+    }
+
     function testNoSuchProvider() public {
         assertRequestReverts(10000000, unregisteredProvider, 42, false);
     }
@@ -318,7 +355,13 @@ contract EntropyTest is Test {
             10
         );
         vm.prank(provider1);
-        random.register(provider1FeeInWei, newHashChain[0], hex"0100", 10);
+        random.register(
+            provider1FeeInWei,
+            newHashChain[0],
+            hex"0100",
+            10,
+            provider1Uri
+        );
         assertInvariants();
         EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
             provider1
@@ -387,7 +430,13 @@ contract EntropyTest is Test {
 
         // Check that overflowing the fee arithmetic causes the transaction to revert.
         vm.prank(provider1);
-        random.register(MAX_UINT256, provider1Proofs[0], hex"0100", 100);
+        random.register(
+            MAX_UINT256,
+            provider1Proofs[0],
+            hex"0100",
+            100,
+            provider1Uri
+        );
         vm.expectRevert();
         random.getFee(provider1);
     }
@@ -437,7 +486,13 @@ contract EntropyTest is Test {
 
         // Reregistering updates the required fees
         vm.prank(provider1);
-        random.register(12345, provider1Proofs[0], hex"0100", 100);
+        random.register(
+            12345,
+            provider1Proofs[0],
+            hex"0100",
+            100,
+            provider1Uri
+        );
 
         assertRequestReverts(pythFeeInWei + 12345 - 1, provider1, 42, false);
         requestWithFee(user2, pythFeeInWei + 12345, provider1, 42, false);
@@ -466,4 +521,13 @@ contract EntropyTest is Test {
         vm.expectRevert();
         random.withdraw(providerOneBalance);
     }
+
+    function testGetProviderInfo() public {
+        EntropyStructs.ProviderInfo memory providerInfo1 = random
+            .getProviderInfo(provider1);
+        // These two fields aren't used by the Entropy contract itself -- they're just convenient info to store
+        // on-chain -- so they aren't tested in the other tests.
+        assertEq(providerInfo1.uri, provider1Uri);
+        assertEq(providerInfo1.commitmentMetadata, provider1CommitmentMetadata);
+    }
 }

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

@@ -21,6 +21,10 @@ contract EntropyStructs {
         // Metadata for the current commitment. Providers may optionally use this field to to help
         // manage rotations (i.e., to pick the sequence number from the correct hash chain).
         bytes commitmentMetadata;
+        // Optional URI where clients can retrieve revelations for the provider.
+        // Client SDKs can use this field to automatically determine how to retrieve random values for each provider.
+        // TODO: specify the API that must be implemented at this URI
+        bytes uri;
         // The first sequence number that is *not* included in the current commitment (i.e., an exclusive end index).
         // The contract maintains the invariant that sequenceNumber <= endSequenceNumber.
         // If sequenceNumber == endSequenceNumber, the provider must rotate their commitment to add additional random values.

+ 4 - 1
target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol

@@ -13,7 +13,8 @@ interface IEntropy is EntropyEvents {
         uint feeInWei,
         bytes32 commitment,
         bytes calldata commitmentMetadata,
-        uint64 chainLength
+        uint64 chainLength,
+        bytes calldata uri
     ) external;
 
     // Withdraw a portion of the accumulated fees for the provider msg.sender.
@@ -55,6 +56,8 @@ interface IEntropy is EntropyEvents {
         address provider
     ) external view returns (EntropyStructs.ProviderInfo memory info);
 
+    function getDefaultProvider() external view returns (address provider);
+
     function getRequest(
         address provider,
         uint64 sequenceNumber