Skip to content

Commit 8887a09

Browse files
authored
[fortuna] Fix config and CLI args (#1605)
* cleanup config * merge configs * more simplification * move everything to config * version bump * nicer loading of secrets * fix readme * precommit * fix * precommit * pr comments
1 parent 81c2493 commit 8887a09

File tree

13 files changed

+213
-232
lines changed

13 files changed

+213
-232
lines changed

apps/fortuna/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/fortuna/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fortuna"
3-
version = "5.4.5"
3+
version = "6.0.0"
44
edition = "2021"
55

66
[dependencies]

apps/fortuna/README.md

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
Fortuna is a webservice that serves random numbers according to the Entropy protocol.
44
The webservice generates a hash chain of random numbers and reveals them to callers when permitted by the protocol.
55
The hash chain is generated from a secret key that is provided to the server on startup.
6+
The service also operates a keeper task that performs callback transactions for user requests.
67

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

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

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

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

34-
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:
31+
Once you've completed the setup, simply run the following command to start the service:
3532

3633
```bash
37-
cargo run -- run --secret <secret> --provider <provider>
34+
RUST_LOG=INFO cargo run -- run
3835
```
3936

4037
This command will start the webservice on `localhost:34000`.

apps/fortuna/config.sample.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,44 @@ chains:
22
lightlink_pegasus:
33
geth_rpc_addr: https://replicator.pegasus.lightlink.io/rpc/v1
44
contract_addr: 0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a
5+
6+
# Keeper configuration for the chain
57
reveal_delay_blocks: 0
68
gas_limit: 500000
9+
10+
# Provider configuration
11+
fee: 1500000000000000
12+
# Historical commitments -- delete this block for local development purposes
13+
commitments:
14+
# prettier-ignore
15+
- 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]
16+
chain_length: 10000
17+
original_commitment_sequence_number: 104
18+
provider:
19+
uri: http://localhost:8080/
20+
chain_length: 100000
21+
22+
# An ethereum wallet address and private key. Generate with `cast wallet new`
23+
address: 0xADDRESS
24+
private_key:
25+
# For local development, you can hardcode the private key here
26+
value: 0xabcd
27+
# For production, you can store the private key in a file.
28+
# file: provider-key.txt
29+
# A 32 byte random value in hexadecimal
30+
# Generate with `openssl rand -hex 32`
31+
secret:
32+
# For local development, you can hardcode the value here
33+
value: abcd
34+
# For production, you can store the private key in a file.
35+
# file: secret.txt
36+
keeper:
37+
# An ethereum wallet address and private key for running the keeper service.
38+
# This does not have to be the same key as the provider's key above.
39+
# Generate with `cast wallet new`.
40+
# The keeper private key can be omitted to run the webservice without the keeper.
41+
private_key:
42+
# For local development, you can hardcode the private key here
43+
value: 0xabcd
44+
# For production, you can store the private key in a file.
45+
# file: keeper-key.txt

apps/fortuna/provider-config.sample.yaml

Lines changed: 0 additions & 8 deletions
This file was deleted.

apps/fortuna/src/command/register_provider.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
use {
22
crate::{
3+
api::{
4+
get_register_uri,
5+
ChainId,
6+
},
37
chain::ethereum::SignablePythContract,
48
config::{
59
Config,
10+
EthereumConfig,
11+
ProviderConfig,
612
RegisterProviderOptions,
713
},
814
state::PebbleHashChain,
915
},
10-
anyhow::Result,
16+
anyhow::{
17+
anyhow,
18+
Result,
19+
},
1120
ethers::{
1221
abi::Bytes,
1322
signers::{
@@ -28,42 +37,61 @@ pub struct CommitmentMetadata {
2837
/// Register as a randomness provider. This method will generate and commit to a new random
2938
/// hash chain from the configured secret & a newly generated random value.
3039
pub async fn register_provider(opts: &RegisterProviderOptions) -> Result<()> {
31-
let chain_config = Config::load(&opts.config.config)?.get_chain_config(&opts.chain_id)?;
40+
let config = Config::load(&opts.config.config)?;
41+
let chain_config = config.get_chain_config(&opts.chain_id)?;
42+
43+
register_provider_from_config(&config.provider, &opts.chain_id, &chain_config).await?;
44+
45+
Ok(())
46+
}
47+
48+
pub async fn register_provider_from_config(
49+
provider_config: &ProviderConfig,
50+
chain_id: &ChainId,
51+
chain_config: &EthereumConfig,
52+
) -> Result<()> {
53+
let private_key_string = provider_config.private_key.load()?.ok_or(anyhow!(
54+
"Please specify a provider private key in the config"
55+
))?;
3256

3357
// Initialize a Provider to interface with the EVM contract.
3458
let contract =
35-
Arc::new(SignablePythContract::from_config(&chain_config, &opts.private_key).await?);
59+
Arc::new(SignablePythContract::from_config(&chain_config, &private_key_string).await?);
3660
// Create a new random hash chain.
3761
let random = rand::random::<[u8; 32]>();
38-
let secret = opts.randomness.load_secret()?;
62+
let secret = provider_config
63+
.secret
64+
.load()?
65+
.ok_or(anyhow!("Please specify a provider secret in the config"))?;
3966

40-
let commitment_length = opts.randomness.chain_length;
67+
let commitment_length = provider_config.chain_length;
4168
let mut chain = PebbleHashChain::from_config(
4269
&secret,
43-
&opts.chain_id,
44-
&opts.private_key.clone().parse::<LocalWallet>()?.address(),
70+
&chain_id,
71+
&private_key_string.parse::<LocalWallet>()?.address(),
4572
&chain_config.contract_addr,
4673
&random,
4774
commitment_length,
4875
)?;
4976

5077
// Arguments to the contract to register our new provider.
51-
let fee_in_wei = opts.fee;
78+
let fee_in_wei = chain_config.fee;
5279
let commitment = chain.reveal()?;
5380
// Store the random seed and chain length in the metadata field so that we can regenerate the hash
5481
// chain at-will. (This is secure because you can't generate the chain unless you also have the secret)
5582
let commitment_metadata = CommitmentMetadata {
5683
seed: random,
5784
chain_length: commitment_length,
5885
};
86+
let uri = get_register_uri(&provider_config.uri, &chain_id)?;
5987
let call = contract.register(
6088
fee_in_wei,
6189
commitment,
6290
bincode::serialize(&commitment_metadata)?.into(),
6391
commitment_length,
6492
// Use Bytes to serialize the uri. Most users will be using JS/TS to deserialize this uri.
6593
// Bincode is a different encoding mechanisms, and I didn't find any JS/TS library to parse bincode.
66-
Bytes::from(opts.uri.as_str()).into(),
94+
Bytes::from(uri.as_str()).into(),
6795
);
6896
let mut gas_estimate = call.estimate_gas().await?;
6997
let gas_multiplier = U256::from(2); //TODO: smarter gas estimation

apps/fortuna/src/command/run.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use {
1111
Commitment,
1212
Config,
1313
EthereumConfig,
14-
ProviderConfig,
1514
RunOptions,
1615
},
1716
keeper,
@@ -32,7 +31,10 @@ use {
3231
Http,
3332
Provider,
3433
},
35-
types::BlockNumber,
34+
types::{
35+
Address,
36+
BlockNumber,
37+
},
3638
},
3739
prometheus_client::{
3840
encoding::EncodeLabelSet,
@@ -150,12 +152,15 @@ pub async fn run_keeper(
150152

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

156160
let mut chains: HashMap<ChainId, BlockchainState> = HashMap::new();
157161
for (chain_id, chain_config) in &config.chains {
158-
let state = setup_chain_state(&opts, &secret, chain_id, chain_config).await;
162+
let state =
163+
setup_chain_state(&config.provider.address, &secret, chain_id, chain_config).await;
159164
match state {
160165
Ok(state) => {
161166
chains.insert(chain_id.clone(), state);
@@ -184,13 +189,15 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
184189

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

187-
if let Some(keeper_private_key) = opts.load_keeper_private_key()? {
192+
if let Some(keeper_private_key) = config.keeper.private_key.load()? {
188193
spawn(run_keeper(
189194
chains.clone(),
190195
config.clone(),
191196
keeper_private_key,
192197
metrics_registry.clone(),
193198
));
199+
} else {
200+
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.")
194201
}
195202

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

204211
async fn setup_chain_state(
205-
opts: &&RunOptions,
212+
provider: &Address,
206213
secret: &String,
207214
chain_id: &ChainId,
208215
chain_config: &EthereumConfig,
209216
) -> Result<BlockchainState> {
210-
let provider_config = ProviderConfig::load(&opts.provider_config.provider_config)?;
211217
let contract = Arc::new(PythContract::from_config(&chain_config)?);
212-
let provider_chain_config = provider_config.get_chain_config(chain_id)?;
213-
let mut provider_commitments = provider_chain_config.get_sorted_commitments();
214-
let provider_info = contract.get_provider_info(opts.provider).call().await?;
218+
let mut provider_commitments = chain_config.commitments.clone().unwrap_or(Vec::new());
219+
provider_commitments.sort_by(|c1, c2| {
220+
c1.original_commitment_sequence_number
221+
.cmp(&c2.original_commitment_sequence_number)
222+
});
223+
224+
let provider_info = contract.get_provider_info(*provider).call().await?;
215225
let latest_metadata = bincode::deserialize::<CommitmentMetadata>(
216226
&provider_info.commitment_metadata,
217227
)
@@ -223,6 +233,16 @@ async fn setup_chain_state(
223233
)
224234
})?;
225235

236+
let last_prior_commitment = provider_commitments.last();
237+
if last_prior_commitment.is_some()
238+
&& last_prior_commitment
239+
.unwrap()
240+
.original_commitment_sequence_number
241+
>= provider_info.original_commitment_sequence_number
242+
{
243+
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));
244+
}
245+
226246
provider_commitments.push(Commitment {
227247
seed: latest_metadata.seed,
228248
chain_length: latest_metadata.chain_length,
@@ -243,7 +263,7 @@ async fn setup_chain_state(
243263
let pebble_hash_chain = PebbleHashChain::from_config(
244264
&secret,
245265
&chain_id,
246-
&opts.provider,
266+
&provider,
247267
&chain_config.contract_addr,
248268
&commitment.seed,
249269
commitment.chain_length,
@@ -268,7 +288,7 @@ async fn setup_chain_state(
268288
id: chain_id.clone(),
269289
state: Arc::new(chain_state),
270290
contract,
271-
provider_address: opts.provider,
291+
provider_address: provider.clone(),
272292
reveal_delay_blocks: chain_config.reveal_delay_blocks,
273293
confirmed_block_status: chain_config.confirmed_block_status,
274294
};

0 commit comments

Comments
 (0)