Skip to content

Commit 7191d79

Browse files
authored
Merge pull request #36 from l-adic/gas-metrics
Gas metrics
2 parents 873d9ce + 622e552 commit 7191d79

File tree

8 files changed

+187
-58
lines changed

8 files changed

+187
-58
lines changed

.github/workflows/node_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ jobs:
121121
make compile-ttc-contract
122122
make create-schema
123123
export TTC_ADDRESS=$(make deploy | tee /dev/tty | tail -n 1)
124-
TTC_ADDRESS="$TTC_ADDRESS" MAX_ACTORS=3 PROVER_TIMEOUT=1200 make run-node-tests
124+
TTC_ADDRESS="$TTC_ADDRESS" NUM_ACTORS=3 PROVER_TIMEOUT=1200 make run-node-tests
125125
126126
# Get short git SHA for tagging
127127
- name: Get short SHA

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ CHAIN_ID ?= 31337
9595
OWNER_KEY ?= 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
9696
MOCK_VERIFIER ?= false
9797
RISC0_DEV_MODE ?= true
98-
MAX_ACTORS ?= 20
98+
NUM_ACTORS ?= 20
9999
PROVER_TIMEOUT ?= 60
100100

101101
deploy-mock: ## Run node tests with mock verifier
@@ -121,7 +121,7 @@ run-node-tests: ## Run node tests with mock verifier
121121
NODE_PORT=$(NODE_PORT) \
122122
MONITOR_HOST=$(MONITOR_HOST) \
123123
MONITOR_PORT=$(MONITOR_PORT) \
124-
MAX_ACTORS=$(MAX_ACTORS) \
124+
NUM_ACTORS=$(NUM_ACTORS) \
125125
PROVER_TIMEOUT=$(PROVER_TIMEOUT) \
126126
cargo run -p host --bin demo $(CARGO_BUILD_OPTIONS) -- e2e \
127127
--chain-id $(CHAIN_ID) \

README.md

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,16 @@ Instead we create a pool, where the owners are asked to each rank the tokens in
1111
The [wikipedia article](https://en.wikipedia.org/wiki/Top_trading_cycle) does a good job explaining what the algorithm and setting is, and hints at various generalizations with links in the footnotes.
1212

1313
## Architecture
14-
1. A Solidity smart contract capabale of
15-
- holding NFTs in a custodial mannor (ideally with safe retrieval in case the participant wants to exit before completion)
16-
- accepting trading preferences
17-
- "locking down" for a period of time long enough to execute the trading algorithm off chain
18-
- accepting and validating proofs for the results of the trading algorithm (a "re-allocation")
19-
- allowing users to withdraw according to re-allocation
20-
2. A rust implementation of the Top Trading Cycle (TTC) algorithm, and a compatibility layer for inputs/outputs expected by the contract.
21-
3. Risc-Zero zkvm + [Steel](https://github.com/risc0/risc0-ethereum/tree/main/crates/steel) for generating Groth16 proofs of the TTC execution on smart contract data.
22-
4. TODO: A server to monitor the contracts for proof requests / callbacks, and a gpu accelerated environment to construct the proofs.
23-
5. TODO: A simple UI + testnet deployment for illustration purposes.
14+
For architecture diagrams, see [here](./docs/README.md#architecture-diagram). For more details on the flows and service interaction, see [here](./docs/README.md#flows)
2415

25-
## Test against local node
26-
The `host` crate contains an end-to-end test using a randomly generated allocation. See the [node_test](https://github.com/l-adic/ttc/blob/main/.github/workflows/node_test.yml) workflow for how you would set these up and run locally.
16+
## Development Environment Setup
2717

28-
## Development Environment
29-
30-
### Development Environment Setup
18+
Two tmuxinator configurations are provided for development. Both configurations set up:
19+
- Ethereum node and Postgres
20+
- Prover and Monitor servers
21+
- System monitoring (e.g htop, nvidia-smi)
22+
- Command shell
3123

32-
Two tmuxinator configurations are provided for development:
3324

3425
#### 1. Local Development (`.tmuxinator.yml`)
3526
Run services locally with cargo:
@@ -43,25 +34,31 @@ Run all services in Docker containers:
4334
tmuxinator start -p .tmuxinator.docker.yml
4435
```
4536

46-
The Docker configuration automatically rebuilds the prover-server and monitor-server images before starting to ensure they reflect your current code. This is important when you've made changes to:
47-
- Any Rust source files
48-
- Cargo.toml dependencies
49-
- Dockerfile configurations
37+
## Testing
38+
The `host` crate contains an end-to-end test using a randomly generated allocation. You can check the corresponding [github workflow](./.github/workflows/node_test.yml) for reference,
39+
and view the [Makefile](./Makefile) for a complete set of config options
40+
41+
1. Deploy the services (see [above](./README.md#development-environment-setup)) or use a hosted deployment.
42+
43+
2. You must fetch the `ImageID.sol` contract and store it in the correct location. This contract is what cryptographically binds the solidity verifier to the rust TTC program.
5044

51-
If you need to manually rebuild the images:
5245
```bash
53-
docker compose build prover-server monitor-server
46+
> make fetch-image-id-contract > contract/src/ImageID.sol
5447
```
5548

56-
Both configurations set up:
57-
- Ethereum node and Postgres logs
58-
- Prover and Monitor servers
59-
- System monitoring (htop)
60-
- Command shell
49+
3. Deploy the contracts:
50+
51+
```
52+
> make deploy
53+
```
54+
NOTE: use the `deploy-mock` variant if you are running a mock verifier (recommended if you aren't using a cuda accelerated prover).
55+
You should see the deploy address of the TTC contract printed to the console. There will be a json artifact written to `./deployments/<ttc-contract-address>/deployed.json`
56+
57+
4. Run the demo script with the desired config options, e.g.
58+
59+
```
60+
> TTC_ADDRESS=<ttc-contract-address> NUM_ACTORS=10 PROVER_TIMEOUT=600 make run-node-tests
61+
```
6162

62-
#### Tmux Key Bindings
63-
- Exit/detach from tmux: `Ctrl-b d`
64-
- Switch between windows: `Ctrl-b [0-9]` or mouse click
65-
- Switch between panes: `Ctrl-b arrow` or mouse click
66-
- Scroll in a pane: Mouse wheel or `Ctrl-b [` then arrow keys (press `q` to exit scroll mode)
67-
- Copy text: Select with mouse (may need to hold Shift depending on your terminal)
63+
You can control the log level via `RUST_LOG`. This script creates checkpoints, writing the relevant state to `./deployments/<ttc-contract-address>`. If the script errors or halts at any
64+
time, you can re-run from the last checkpoint using the same command as you used to start.

docs/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Architecture Diagram
1+
# Architecture Diagram
22
The TTC architecture consists of 2 hosted services (`Monitor` and `Prover`) and a shared Postgres database:
33

44
<p align="center">
@@ -12,6 +12,8 @@ The `Monitor` runs on an extremely basic VM, the `Prover` runs on a GPU enabled
1212
(currently using an [L4](https://www.nvidia.com/en-us/data-center/l4/) instance on GCP)
1313

1414

15+
# Flows
16+
1517
## Deployment Flow.
1618

1719
The `Operator` deploys the contracts and submits the address to the `Monitor` service. This spawns an event monitor loop looking for

host/bin/demo.rs

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use host::{
66
cli::{Command, DemoConfig},
77
contract::{nft::TestNFT, ttc::ITopTradingCycle},
88
env::{create_provider, init_console_subscriber},
9+
gas_metrics::{with_metrics, GasMetrics},
910
};
1011
use jsonrpsee::http_client::{HttpClient, HttpClientBuilder};
1112
use proptest::{
@@ -20,6 +21,7 @@ use risc0_steel::alloy::{
2021
signers::local::PrivateKeySigner,
2122
};
2223
use std::{collections::HashMap, path::Path, str::FromStr, thread::sleep, time::Duration};
24+
use tokio::sync::Mutex;
2325
use tracing::info;
2426
use ttc::strict::Preferences;
2527
use url::Url;
@@ -33,6 +35,7 @@ struct TestSetup {
3335
monitor: HttpClient,
3436
timeout: Duration,
3537
checkpointer: Checkpointer,
38+
gas_metrics: Mutex<GasMetrics>,
3639
}
3740

3841
fn make_token_preferences(
@@ -84,6 +87,7 @@ impl TestSetup {
8487
monitor,
8588
timeout: Duration::from_secs(config.prover_timeout),
8689
checkpointer,
90+
gas_metrics: Mutex::new(GasMetrics::new()),
8791
})
8892
}
8993

@@ -105,6 +109,7 @@ impl TestSetup {
105109
monitor,
106110
timeout: Duration::from_secs(config.prover_timeout),
107111
checkpointer,
112+
gas_metrics: Mutex::new(GasMetrics::new()),
108113
})
109114
}
110115

@@ -118,17 +123,29 @@ impl TestSetup {
118123
let nft = TestNFT::new(actor.token.collection, provider.clone());
119124
let ttc = ITopTradingCycle::new(self.ttc, provider);
120125
async move {
121-
nft.approve(self.ttc, actor.token.tokenId)
126+
let approval_tx = nft
127+
.approve(self.ttc, actor.token.tokenId)
122128
.send()
123129
.await?
124-
.watch()
130+
.get_receipt()
125131
.await?;
126-
ttc.depositNFT(actor.token.clone())
132+
with_metrics(&self.gas_metrics, |m| {
133+
m.inc_counter("approve");
134+
m.record_hist("approve", approval_tx.gas_used);
135+
})
136+
.await;
137+
let deposit_tx = ttc
138+
.depositNFT(actor.token.clone())
127139
.gas(self.config.base.max_gas)
128140
.send()
129141
.await?
130-
.watch()
142+
.get_receipt()
131143
.await?;
144+
with_metrics(&self.gas_metrics, |m| {
145+
m.inc_counter("approve");
146+
m.record_hist("approve", deposit_tx.gas_used);
147+
})
148+
.await;
132149
Ok(())
133150
}
134151
})
@@ -170,12 +187,18 @@ impl TestSetup {
170187
.map(|t| t.hash())
171188
.collect::<Vec<_>>();
172189
async move {
173-
ttc.setPreferences(actor.token.hash(), prefs.clone())
190+
let preferences_tx = ttc
191+
.setPreferences(actor.token.hash(), prefs.clone())
174192
.gas(self.config.base.max_gas)
175193
.send()
176194
.await?
177-
.watch()
195+
.get_receipt()
178196
.await?;
197+
with_metrics(&self.gas_metrics, |m| {
198+
m.inc_counter("setPreferences");
199+
m.record_hist("setPreferences", preferences_tx.gas_used);
200+
})
201+
.await;
179202
let ps = ttc.getPreferences(actor.token.hash()).call().await?._0;
180203
assert_eq!(ps, prefs, "Preferences not set correctly in contract!");
181204
info!(
@@ -205,12 +228,18 @@ impl TestSetup {
205228
let provider = create_provider(self.node_url.clone(), self.owner.clone());
206229
let ttc = ITopTradingCycle::new(self.ttc, provider);
207230
let journal_data = Bytes::from(proof.abi_encode());
208-
ttc.reallocateTokens(journal_data, Bytes::from(seal))
231+
let realloc_tx = ttc
232+
.reallocateTokens(journal_data, Bytes::from(seal))
209233
.gas(self.config.base.max_gas)
210234
.send()
211235
.await?
212-
.watch()
236+
.get_receipt()
213237
.await?;
238+
with_metrics(&self.gas_metrics, |m| {
239+
m.inc_counter("reallocateTokens");
240+
m.record_hist("reallocateTokens", realloc_tx.gas_used);
241+
})
242+
.await;
214243
let stable: Vec<Actor> = self
215244
.actors
216245
.iter()
@@ -248,17 +277,23 @@ impl TestSetup {
248277
let provider = create_provider(self.node_url.clone(), actor.wallet.clone());
249278
let ttc = ITopTradingCycle::new(self.ttc, provider.clone());
250279
async move {
251-
eprintln!(
280+
info!(
252281
"Withdrawing token {:#} for existing owner {:#}",
253282
actor.token.hash(),
254283
actor.address()
255284
);
256-
ttc.withdrawNFT(actor.token.hash())
285+
let withdraw_tx = ttc
286+
.withdrawNFT(actor.token.hash())
257287
.gas(self.config.base.max_gas)
258288
.send()
259289
.await?
260-
.watch()
290+
.get_receipt()
261291
.await?;
292+
with_metrics(&self.gas_metrics, |m| {
293+
m.inc_counter("withdrawNFT");
294+
m.record_hist("withdrawNFT", withdraw_tx.gas_used);
295+
})
296+
.await;
262297
Ok(())
263298
}
264299
})
@@ -276,17 +311,23 @@ impl TestSetup {
276311
let provider = create_provider(self.node_url.clone(), actor.wallet.clone());
277312
let ttc = ITopTradingCycle::new(self.ttc, provider.clone());
278313
async move {
279-
eprintln!(
314+
info!(
280315
"Withdrawing token {:#} for new owner {:#}",
281316
new_token_hash,
282317
actor.address()
283318
);
284-
ttc.withdrawNFT(*new_token_hash)
319+
let withdraw_tx = ttc
320+
.withdrawNFT(*new_token_hash)
285321
.gas(self.config.base.max_gas)
286322
.send()
287323
.await?
288-
.watch()
324+
.get_receipt()
289325
.await?;
326+
with_metrics(&self.gas_metrics, |m| {
327+
m.inc_counter("withdrawNFT");
328+
m.record_hist("withdrawNFT", withdraw_tx.gas_used);
329+
})
330+
.await;
290331
Ok(())
291332
}
292333
})
@@ -329,12 +370,17 @@ impl TestSetup {
329370
async fn advance_phase(&self) -> Result<()> {
330371
let provider = create_provider(self.node_url.clone(), self.owner.clone());
331372
let ttc = ITopTradingCycle::new(self.ttc, provider);
332-
ttc.advancePhase().send().await?.watch().await?;
373+
let advance_tx = ttc.advancePhase().send().await?.get_receipt().await?;
374+
with_metrics(&self.gas_metrics, |m| {
375+
m.inc_counter("advancePhase");
376+
m.record_hist("advancePhase", advance_tx.gas_used);
377+
})
378+
.await;
333379
Ok(())
334380
}
335381
}
336382

337-
async fn run_demo(setup: TestSetup) -> Result<()> {
383+
async fn run_demo(setup: &TestSetup) -> Result<()> {
338384
let ttc = {
339385
let provider = create_provider(setup.node_url.clone(), setup.owner.clone());
340386
ITopTradingCycle::new(setup.ttc, provider)
@@ -430,8 +476,10 @@ async fn main() -> Result<()> {
430476
};
431477
let test_case = {
432478
let mut runner = TestRunner::default();
433-
let strategy = (Preferences::<u64>::arbitrary_with(Some(2..=config.max_actors)))
434-
.prop_map(|prefs| prefs.map(U256::from));
479+
let strategy = (Preferences::<u64>::arbitrary_with(Some(
480+
config.num_actors..=config.num_actors,
481+
)))
482+
.prop_map(|prefs| prefs.map(U256::from));
435483
strategy.new_tree(&mut runner).unwrap().current()
436484
};
437485
let setup = {
@@ -447,7 +495,9 @@ async fn main() -> Result<()> {
447495
setup
448496
}
449497
};
450-
run_demo(setup).await
498+
let res = run_demo(&setup).await;
499+
info!("Metrics: {:}", &setup.gas_metrics.into_inner());
500+
res
451501
}
452502
Command::SubmitProof(config) => {
453503
info!("{}", serde_json::to_string_pretty(&config).unwrap());

host/src/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ pub struct DemoConfig {
6060
#[arg(long, env = "MONITOR_PORT", default_value = "3030")]
6161
pub monitor_port: String,
6262

63-
#[arg(long, env = "MAX_ACTORS", default_value_t = 10)]
64-
pub max_actors: usize,
63+
#[arg(long, env = "NUM_ACTORS", default_value_t = 10)]
64+
pub num_actors: usize,
6565

6666
/// Initial ETH balance for new accounts
6767
#[arg(long, env = "INITIAL_BALANCE", default_value = "5")]

0 commit comments

Comments
 (0)