[entropy] Change provider interface to simplify supporting many chains (#1149)

* grr

* cleanup

* fix semver
This commit is contained in:
Jayant Krishnamurthy 2023-11-27 07:39:54 -08:00 committed by GitHub
parent da72b6e250
commit b7a089b51e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 61 additions and 117 deletions

3
fortuna/Cargo.lock generated
View File

@ -1486,13 +1486,14 @@ dependencies = [
[[package]]
name = "fortuna"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"anyhow",
"axum",
"axum-macros",
"axum-test",
"base64 0.21.4",
"bincode",
"byteorder",
"clap",
"ethabi",

View File

@ -1,6 +1,6 @@
[package]
name = "fortuna"
version = "1.0.0"
version = "2.0.0"
edition = "2021"
[dependencies]
@ -8,6 +8,7 @@ anyhow = "1.0.75"
axum = { version = "0.6.20", features = ["json", "ws", "macros"] }
axum-macros = { version = "0.3.8" }
base64 = { version = "0.21.0" }
bincode = "1.3.3"
byteorder = "1.5.0"
clap = { version = "4.4.6", features = ["derive", "cargo", "env"] }
ethabi = "18.0.0"

View File

@ -1,45 +1,4 @@
[
{
"inputs": [
{
"internalType": "uint256",
"name": "pythFeeInWei",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AssertionFailure",
"type": "error"
},
{
"inputs": [],
"name": "IncorrectProviderRevelation",
"type": "error"
},
{
"inputs": [],
"name": "IncorrectUserRevelation",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientFee",
"type": "error"
},
{
"inputs": [],
"name": "NoSuchProvider",
"type": "error"
},
{
"inputs": [],
"name": "OutOfRandomness",
"type": "error"
},
{
"anonymous": false,
"inputs": [
@ -66,9 +25,9 @@
"type": "uint64"
},
{
"internalType": "bytes32",
"internalType": "bytes",
"name": "commitmentMetadata",
"type": "bytes32"
"type": "bytes"
},
{
"internalType": "uint64",
@ -92,7 +51,7 @@
}
],
"indexed": false,
"internalType": "struct PythRandomStructs.ProviderInfo",
"internalType": "struct EntropyStructs.ProviderInfo",
"name": "provider",
"type": "tuple"
}
@ -130,11 +89,6 @@
"name": "providerCommitmentSequenceNumber",
"type": "uint64"
},
{
"internalType": "bytes32",
"name": "providerCommitmentMetadata",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "blockNumber",
@ -142,7 +96,7 @@
}
],
"indexed": false,
"internalType": "struct PythRandomStructs.Request",
"internalType": "struct EntropyStructs.Request",
"name": "request",
"type": "tuple"
}
@ -180,11 +134,6 @@
"name": "providerCommitmentSequenceNumber",
"type": "uint64"
},
{
"internalType": "bytes32",
"name": "providerCommitmentMetadata",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "blockNumber",
@ -192,7 +141,7 @@
}
],
"indexed": false,
"internalType": "struct PythRandomStructs.Request",
"internalType": "struct EntropyStructs.Request",
"name": "request",
"type": "tuple"
},
@ -337,9 +286,9 @@
"type": "uint64"
},
{
"internalType": "bytes32",
"internalType": "bytes",
"name": "commitmentMetadata",
"type": "bytes32"
"type": "bytes"
},
{
"internalType": "uint64",
@ -362,7 +311,7 @@
"type": "uint64"
}
],
"internalType": "struct PythRandomStructs.ProviderInfo",
"internalType": "struct EntropyStructs.ProviderInfo",
"name": "info",
"type": "tuple"
}
@ -412,18 +361,13 @@
"name": "providerCommitmentSequenceNumber",
"type": "uint64"
},
{
"internalType": "bytes32",
"name": "providerCommitmentMetadata",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
}
],
"internalType": "struct PythRandomStructs.Request",
"internalType": "struct EntropyStructs.Request",
"name": "req",
"type": "tuple"
}
@ -444,9 +388,9 @@
"type": "bytes32"
},
{
"internalType": "bytes32",
"internalType": "bytes",
"name": "commitmentMetadata",
"type": "bytes32"
"type": "bytes"
},
{
"internalType": "uint64",

View File

@ -11,6 +11,12 @@ use {
std::sync::Arc,
};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CommitmentMetadata {
pub seed: [u8; 32],
pub chain_length: u64,
}
/// Register as a randomness provider. This method will generate and commit to a new random
/// hash chain from the configured secret & a newly generated random value.
pub async fn register_provider(opts: &RegisterProviderOptions) -> Result<()> {
@ -25,21 +31,29 @@ pub async fn register_provider(opts: &RegisterProviderOptions) -> Result<()> {
// Create a new random hash chain.
let random = rand::random::<[u8; 32]>();
let mut chain = PebbleHashChain::from_config(&opts.randomness, &opts.chain_id, random)?;
let commitment_length = opts.randomness.chain_length;
let mut chain = PebbleHashChain::from_config(
&opts.randomness.secret,
&opts.chain_id,
&random,
commitment_length,
)?;
// Arguments to the contract to register our new provider.
let fee_in_wei = opts.fee;
let commitment = chain.reveal()?;
// Store the random seed in the metadata field so that we can regenerate the hash chain
// at-will. (This is secure because you can't generate the chain unless you also have the secret)
let commitment_metadata = random;
let commitment_length = opts.randomness.chain_length;
// Store the random seed and chain length in the metadata field so that we can regenerate the hash
// chain at-will. (This is secure because you can't generate the chain unless you also have the secret)
let commitment_metadata = CommitmentMetadata {
seed: random,
chain_length: commitment_length,
};
if let Some(r) = contract
.register(
fee_in_wei,
commitment,
commitment_metadata,
bincode::serialize(&commitment_metadata)?.into(),
commitment_length,
)
.send()

View File

@ -2,6 +2,7 @@ use {
crate::{
api,
chain::ethereum::PythContract,
command::register_provider::CommitmentMetadata,
config::{
Config,
RunOptions,
@ -59,8 +60,15 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
// TODO: we may want to load the hash chain in a lazy/fault-tolerant way. If there are many blockchains,
// then it's more likely that some RPC fails. We should tolerate these faults and generate the hash chain
// later when a user request comes in for that chain.
let random: [u8; 32] = provider_info.commitment_metadata;
let hash_chain = PebbleHashChain::from_config(&opts.randomness, &chain_id, random)?;
let metadata =
bincode::deserialize::<CommitmentMetadata>(&provider_info.commitment_metadata)?;
let hash_chain = PebbleHashChain::from_config(
&opts.randomness.secret,
&chain_id,
&metadata.seed,
metadata.chain_length,
)?;
let chain_state = HashChainState {
offsets: vec![provider_info
.original_commitment_sequence_number

View File

@ -1,8 +1,5 @@
use {
crate::{
api::ChainId,
config::RandomnessOptions,
},
crate::api::ChainId,
anyhow::{
ensure,
Result,
@ -36,17 +33,18 @@ impl PebbleHashChain {
}
pub fn from_config(
opts: &RandomnessOptions,
secret: &str,
chain_id: &ChainId,
random: [u8; 32],
random: &[u8; 32],
chain_length: u64,
) -> Result<Self> {
let mut input: Vec<u8> = vec![];
input.extend_from_slice(&hex::decode(opts.secret.clone())?);
input.extend_from_slice(&hex::decode(secret)?);
input.extend_from_slice(&chain_id.as_bytes());
input.extend_from_slice(&random);
input.extend_from_slice(random);
let secret: [u8; 32] = Keccak256::digest(input).into();
Ok(Self::new(secret, opts.chain_length.try_into()?))
Ok(Self::new(secret, chain_length.try_into()?))
}
/// Reveal the next hash in the chain using the previous proof.

View File

@ -95,7 +95,7 @@ contract Entropy is IEntropy, EntropyState {
function register(
uint feeInWei,
bytes32 commitment,
bytes32 commitmentMetadata,
bytes calldata commitmentMetadata,
uint64 chainLength
) public override {
if (chainLength == 0) revert EntropyErrors.AssertionFailure();
@ -186,7 +186,6 @@ contract Entropy is IEntropy, EntropyState {
req.providerCommitment = providerInfo.currentCommitment;
req.providerCommitmentSequenceNumber = providerInfo
.currentCommitmentSequenceNumber;
req.providerCommitmentMetadata = providerInfo.commitmentMetadata;
if (useBlockHash) {
req.blockNumber = block.number;

View File

@ -44,19 +44,14 @@ contract EntropyTest is Test {
random.register(
provider1FeeInWei,
provider1Proofs[0],
bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
hex"0100",
provider1ChainLength
);
bytes32[] memory hashChain2 = generateHashChain(provider2, 0, 100);
provider2Proofs = hashChain2;
vm.prank(provider2);
random.register(
provider2FeeInWei,
provider2Proofs[0],
bytes32(keccak256(abi.encodePacked(uint256(0x0200)))),
100
);
random.register(provider2FeeInWei, provider2Proofs[0], hex"0200", 100);
}
function generateHashChain(
@ -323,12 +318,7 @@ contract EntropyTest is Test {
10
);
vm.prank(provider1);
random.register(
provider1FeeInWei,
newHashChain[0],
bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
10
);
random.register(provider1FeeInWei, newHashChain[0], hex"0100", 10);
assertInvariants();
EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
provider1
@ -397,12 +387,7 @@ contract EntropyTest is Test {
// Check that overflowing the fee arithmetic causes the transaction to revert.
vm.prank(provider1);
random.register(
MAX_UINT256,
provider1Proofs[0],
bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
100
);
random.register(MAX_UINT256, provider1Proofs[0], hex"0100", 100);
vm.expectRevert();
random.getFee(provider1);
}
@ -452,12 +437,7 @@ contract EntropyTest is Test {
// Reregistering updates the required fees
vm.prank(provider1);
random.register(
12345,
provider1Proofs[0],
bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
100
);
random.register(12345, provider1Proofs[0], hex"0100", 100);
assertRequestReverts(pythFeeInWei + 12345 - 1, provider1, 42, false);
requestWithFee(user2, pythFeeInWei + 12345, provider1, 42, false);

View File

@ -20,7 +20,7 @@ contract EntropyStructs {
uint64 originalCommitmentSequenceNumber;
// 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).
bytes32 commitmentMetadata;
bytes commitmentMetadata;
// 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.
@ -43,7 +43,6 @@ contract EntropyStructs {
bytes32 userCommitment;
bytes32 providerCommitment;
uint64 providerCommitmentSequenceNumber;
bytes32 providerCommitmentMetadata;
// If nonzero, the randomness requester wants the blockhash of this block to be incorporated into the random number.
uint256 blockNumber;
}

View File

@ -12,7 +12,7 @@ interface IEntropy is EntropyEvents {
function register(
uint feeInWei,
bytes32 commitment,
bytes32 commitmentMetadata,
bytes calldata commitmentMetadata,
uint64 chainLength
) external;

View File

@ -1,6 +1,6 @@
{
"name": "@pythnetwork/entropy-sdk-solidity",
"version": "0.1.1",
"version": "1.0.0",
"description": "Generate secure random numbers with Pyth Entropy",
"repository": {
"type": "git",