Skip to content

Commit

Permalink
[fortuna] Fix config and CLI args (#1605)
Browse files Browse the repository at this point in the history
* cleanup config

* merge configs

* more simplification

* move everything to config

* version bump

* nicer loading of secrets

* fix readme

* precommit

* fix

* precommit

* pr comments
  • Loading branch information
jayantk authored May 24, 2024
1 parent 81c2493 commit 8887a09
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 232 deletions.
2 changes: 1 addition & 1 deletion apps/fortuna/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/fortuna/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fortuna"
version = "5.4.5"
version = "6.0.0"
edition = "2021"

[dependencies]
Expand Down
21 changes: 9 additions & 12 deletions apps/fortuna/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
Fortuna is a webservice that serves random numbers according to the Entropy protocol.
The webservice generates a hash chain of random numbers and reveals them to callers when permitted by the protocol.
The hash chain is generated from a secret key that is provided to the server on startup.
The service also operates a keeper task that performs callback transactions for user requests.

A single instance of this webservice can simultaneously serve random numbers for several different blockchains.
A single instance of this service can simultaneously serve random numbers for several different blockchains.
Each blockchain is configured in `config.yaml`.

## Build & Test
Expand All @@ -21,20 +22,16 @@ registering a new randomness provider, or drawing a random value. To see the ava

To start an instance of the webserver for local testing, you first need to perform a few setup steps:

1. Create `config.yaml` file to point to the desired blockchains and Entropy contracts. Copy the content in `config.sample.yaml` to start with.
1. Generate a secret key. The secret key is a 32-byte random value used to construct the hash chains.
You can generate this value using the `openssl` command:
`openssl rand -hex 32`
1. Generate an ethereum wallet for the provider. You can do this in foundry using `cast wallet new`.
Note both the private key and the address; you will need both for subsequent steps.
1. Register a randomness provider for this service: `cargo run -- register-provider --chain-id <chain id> --secret <secret> --private-key <private-key>`.
The chain id is the key of the blockchain in `config.yaml`, the secret is from step (2), and the private key is from step (3).
Note that you need to run this command once per blockchain configured in `config.yaml`.
1. Create a `config.yaml` file to point to the desired blockchains and Entropy contracts. Copy the content in `config.sample.yaml` and follow the directions inside to generate the necessary private keys and secrets.
1. Make sure the wallets you have generated in step (1) contain some gas tokens for the configured networks.
1. Run `cargo run -- setup-provider` to register a randomness provider for this service. This command
will update the on-chain contracts such that the configured provider key is a randomness provider,
and its on-chain configuration matches `config.yaml`.

Once you've completed the setup, simply run the following command, using the secret from step (2) and the wallet address from step (3) as the provider:
Once you've completed the setup, simply run the following command to start the service:

```bash
cargo run -- run --secret <secret> --provider <provider>
RUST_LOG=INFO cargo run -- run
```

This command will start the webservice on `localhost:34000`.
39 changes: 39 additions & 0 deletions apps/fortuna/config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,44 @@ chains:
lightlink_pegasus:
geth_rpc_addr: https://replicator.pegasus.lightlink.io/rpc/v1
contract_addr: 0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a

# Keeper configuration for the chain
reveal_delay_blocks: 0
gas_limit: 500000

# Provider configuration
fee: 1500000000000000
# Historical commitments -- delete this block for local development purposes
commitments:
# prettier-ignore
- seed: [219,125,217,197,234,88,208,120,21,181,172,143,239,102,41,233,167,212,237,106,37,255,184,165,238,121,230,155,116,158,173,48]
chain_length: 10000
original_commitment_sequence_number: 104
provider:
uri: http://localhost:8080/
chain_length: 100000

# An ethereum wallet address and private key. Generate with `cast wallet new`
address: 0xADDRESS
private_key:
# For local development, you can hardcode the private key here
value: 0xabcd
# For production, you can store the private key in a file.
# file: provider-key.txt
# A 32 byte random value in hexadecimal
# Generate with `openssl rand -hex 32`
secret:
# For local development, you can hardcode the value here
value: abcd
# For production, you can store the private key in a file.
# file: secret.txt
keeper:
# An ethereum wallet address and private key for running the keeper service.
# This does not have to be the same key as the provider's key above.
# Generate with `cast wallet new`.
# The keeper private key can be omitted to run the webservice without the keeper.
private_key:
# For local development, you can hardcode the private key here
value: 0xabcd
# For production, you can store the private key in a file.
# file: keeper-key.txt
8 changes: 0 additions & 8 deletions apps/fortuna/provider-config.sample.yaml

This file was deleted.

46 changes: 37 additions & 9 deletions apps/fortuna/src/command/register_provider.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
use {
crate::{
api::{
get_register_uri,
ChainId,
},
chain::ethereum::SignablePythContract,
config::{
Config,
EthereumConfig,
ProviderConfig,
RegisterProviderOptions,
},
state::PebbleHashChain,
},
anyhow::Result,
anyhow::{
anyhow,
Result,
},
ethers::{
abi::Bytes,
signers::{
Expand All @@ -28,42 +37,61 @@ pub struct CommitmentMetadata {
/// 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<()> {
let chain_config = Config::load(&opts.config.config)?.get_chain_config(&opts.chain_id)?;
let config = Config::load(&opts.config.config)?;
let chain_config = config.get_chain_config(&opts.chain_id)?;

register_provider_from_config(&config.provider, &opts.chain_id, &chain_config).await?;

Ok(())
}

pub async fn register_provider_from_config(
provider_config: &ProviderConfig,
chain_id: &ChainId,
chain_config: &EthereumConfig,
) -> Result<()> {
let private_key_string = provider_config.private_key.load()?.ok_or(anyhow!(
"Please specify a provider private key in the config"
))?;

// Initialize a Provider to interface with the EVM contract.
let contract =
Arc::new(SignablePythContract::from_config(&chain_config, &opts.private_key).await?);
Arc::new(SignablePythContract::from_config(&chain_config, &private_key_string).await?);
// Create a new random hash chain.
let random = rand::random::<[u8; 32]>();
let secret = opts.randomness.load_secret()?;
let secret = provider_config
.secret
.load()?
.ok_or(anyhow!("Please specify a provider secret in the config"))?;

let commitment_length = opts.randomness.chain_length;
let commitment_length = provider_config.chain_length;
let mut chain = PebbleHashChain::from_config(
&secret,
&opts.chain_id,
&opts.private_key.clone().parse::<LocalWallet>()?.address(),
&chain_id,
&private_key_string.parse::<LocalWallet>()?.address(),
&chain_config.contract_addr,
&random,
commitment_length,
)?;

// Arguments to the contract to register our new provider.
let fee_in_wei = opts.fee;
let fee_in_wei = chain_config.fee;
let commitment = chain.reveal()?;
// 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,
};
let uri = get_register_uri(&provider_config.uri, &chain_id)?;
let call = contract.register(
fee_in_wei,
commitment,
bincode::serialize(&commitment_metadata)?.into(),
commitment_length,
// Use Bytes to serialize the uri. Most users will be using JS/TS to deserialize this uri.
// Bincode is a different encoding mechanisms, and I didn't find any JS/TS library to parse bincode.
Bytes::from(opts.uri.as_str()).into(),
Bytes::from(uri.as_str()).into(),
);
let mut gas_estimate = call.estimate_gas().await?;
let gas_multiplier = U256::from(2); //TODO: smarter gas estimation
Expand Down
44 changes: 32 additions & 12 deletions apps/fortuna/src/command/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use {
Commitment,
Config,
EthereumConfig,
ProviderConfig,
RunOptions,
},
keeper,
Expand All @@ -32,7 +31,10 @@ use {
Http,
Provider,
},
types::BlockNumber,
types::{
Address,
BlockNumber,
},
},
prometheus_client::{
encoding::EncodeLabelSet,
Expand Down Expand Up @@ -150,12 +152,15 @@ pub async fn run_keeper(

pub async fn run(opts: &RunOptions) -> Result<()> {
let config = Config::load(&opts.config.config)?;
let secret = opts.randomness.load_secret()?;
let secret = config.provider.secret.load()?.ok_or(anyhow!(
"Please specify a provider secret in the config file."
))?;
let (tx_exit, rx_exit) = watch::channel(false);

let mut chains: HashMap<ChainId, BlockchainState> = HashMap::new();
for (chain_id, chain_config) in &config.chains {
let state = setup_chain_state(&opts, &secret, chain_id, chain_config).await;
let state =
setup_chain_state(&config.provider.address, &secret, chain_id, chain_config).await;
match state {
Ok(state) => {
chains.insert(chain_id.clone(), state);
Expand Down Expand Up @@ -184,13 +189,15 @@ pub async fn run(opts: &RunOptions) -> Result<()> {

let metrics_registry = Arc::new(RwLock::new(Registry::default()));

if let Some(keeper_private_key) = opts.load_keeper_private_key()? {
if let Some(keeper_private_key) = config.keeper.private_key.load()? {
spawn(run_keeper(
chains.clone(),
config.clone(),
keeper_private_key,
metrics_registry.clone(),
));
} else {
tracing::info!("Not starting keeper service: no keeper private key specified. Please add one to the config if you would like to run the keeper service.")
}

// Spawn a thread to track latest block lag. This helps us know if the rpc is up and updated with the latest block.
Expand All @@ -202,16 +209,19 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
}

async fn setup_chain_state(
opts: &&RunOptions,
provider: &Address,
secret: &String,
chain_id: &ChainId,
chain_config: &EthereumConfig,
) -> Result<BlockchainState> {
let provider_config = ProviderConfig::load(&opts.provider_config.provider_config)?;
let contract = Arc::new(PythContract::from_config(&chain_config)?);
let provider_chain_config = provider_config.get_chain_config(chain_id)?;
let mut provider_commitments = provider_chain_config.get_sorted_commitments();
let provider_info = contract.get_provider_info(opts.provider).call().await?;
let mut provider_commitments = chain_config.commitments.clone().unwrap_or(Vec::new());
provider_commitments.sort_by(|c1, c2| {
c1.original_commitment_sequence_number
.cmp(&c2.original_commitment_sequence_number)
});

let provider_info = contract.get_provider_info(*provider).call().await?;
let latest_metadata = bincode::deserialize::<CommitmentMetadata>(
&provider_info.commitment_metadata,
)
Expand All @@ -223,6 +233,16 @@ async fn setup_chain_state(
)
})?;

let last_prior_commitment = provider_commitments.last();
if last_prior_commitment.is_some()
&& last_prior_commitment
.unwrap()
.original_commitment_sequence_number
>= provider_info.original_commitment_sequence_number
{
return Err(anyhow!("The current hash chain for chain id {} has configured commitments for sequence numbers greater than the current on-chain sequence number. Are the commitments configured correctly?", &chain_id));
}

provider_commitments.push(Commitment {
seed: latest_metadata.seed,
chain_length: latest_metadata.chain_length,
Expand All @@ -243,7 +263,7 @@ async fn setup_chain_state(
let pebble_hash_chain = PebbleHashChain::from_config(
&secret,
&chain_id,
&opts.provider,
&provider,
&chain_config.contract_addr,
&commitment.seed,
commitment.chain_length,
Expand All @@ -268,7 +288,7 @@ async fn setup_chain_state(
id: chain_id.clone(),
state: Arc::new(chain_state),
contract,
provider_address: opts.provider,
provider_address: provider.clone(),
reveal_delay_blocks: chain_config.reveal_delay_blocks,
confirmed_block_status: chain_config.confirmed_block_status,
};
Expand Down
Loading

0 comments on commit 8887a09

Please sign in to comment.