Skip to content

Commit

Permalink
feat: introduce eval command (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
scarmuega authored Nov 19, 2023
1 parent 7566ce6 commit 1bf5a42
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 46 deletions.
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [Sync](./running/sync.md)
- [Serve](./running/serve.md)
- [Daemon](./running/daemon.md)
- [Eval](./running/eval.md)
- [API](./api/README.md)
- [gRPC](./api/grpc.md)
- [Ouroboros](./api/ouroboros.md)
Expand Down
18 changes: 18 additions & 0 deletions book/src/running/eval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# `eval` command

The `eval` is an utility for evaluating transactions. It takes tx data from an external source and uses the current ledger state to evaluate phase-1 validation rules.

## Usage

To execute the evaluation, run the following command from your terminal:

```bash
dolos eval --file <FILE> --era <ERA> --magic <MAGIC> --slot <SLOT>
```

The args should be interpreted as:

- `--file <FILE>`: the path to the file containing the tx data as hex-encoded cbor.
- `--era <ERA>`: the id of the era that should be used to interpret the transaction data.
- `--magic <MAGIC>`: the protocol magic of the network.
- `--slot <SLOT>`: the slot that should be used for retrieving protocol parameters.
30 changes: 30 additions & 0 deletions src/bin/dolos/data.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
use std::path::Path;

use miette::IntoDiagnostic;

use pallas::{ledger::traverse::MultiEraBlock, storage::rolldb::chain};

#[allow(dead_code)]
fn dump_txs(chain: &chain::Store) -> miette::Result<()> {
for header in chain.crawl() {
let (slot, hash) = header.into_diagnostic()?;
println!("dumping {slot}");

let block = chain.get_block(hash).into_diagnostic()?.unwrap();
let block = MultiEraBlock::decode(&block).into_diagnostic()?;

for tx in block.txs() {
let cbor = hex::encode(tx.encode());
let path = format!("{}.tx", tx.hash());
std::fs::write(Path::new(&path), cbor).into_diagnostic()?;
}
}

Ok(())
}

#[derive(Debug, clap::Args)]
pub struct Args {}

Expand Down Expand Up @@ -31,5 +56,10 @@ pub fn run(config: &super::Config, _args: &Args) -> miette::Result<()> {
println!("chain is empty");
}

// WIP utility to dump tx data for debugging purposes. Should be implemented as
// a subcommand.

// dump_txs(&chain)?;

Ok(())
}
95 changes: 95 additions & 0 deletions src/bin/dolos/eval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use miette::{Context, IntoDiagnostic};
use pallas::{
applying::{validate, Environment, UTxOs},
ledger::{
primitives::byron::TxIn,
traverse::{Era, MultiEraInput, MultiEraOutput, MultiEraTx},
},
};
use std::path::PathBuf;

use dolos::storage::applydb::ApplyDB;

#[derive(Debug, clap::Args)]
pub struct Args {
#[arg(long, short)]
file: PathBuf,

#[arg(long, short)]
era: u16,

#[arg(long, short)]
magic: u32,

#[arg(long, short)]
slot: u64,
}

type ResolveInputs = Vec<(TxIn, Vec<u8>)>;

pub fn resolve_inputs(tx: &MultiEraTx<'_>, ledger: &ApplyDB) -> miette::Result<ResolveInputs> {
let mut set = vec![];

for input in tx.inputs() {
let hash = input.hash();
let idx = input.index();

let bytes = ledger
.get_utxo(*hash, idx)
.into_diagnostic()
.context("fetching utxo from ledger")?
.ok_or(miette::miette!("utxo not found"))?;

//TODO: allow to pass extra utxos manually, to mimic what happens when
// consuming utxos from the same block;

let txin = pallas::ledger::primitives::byron::TxIn::Variant0(
pallas::codec::utils::CborWrap((*hash, idx as u32)),
);

set.push((txin, bytes));
}

Ok(set)
}

pub fn run(config: &super::Config, args: &Args) -> miette::Result<()> {
crate::common::setup_tracing(&config.logging)?;

let (_, _, ledger) = crate::common::open_data_stores(config)?;

let cbor = std::fs::read_to_string(&args.file)
.into_diagnostic()
.context("reading tx from file")?;

let cbor = hex::decode(&cbor)

Check failure on line 65 in src/bin/dolos/eval.rs

View workflow job for this annotation

GitHub Actions / Lint Rust

the borrowed expression implements the required traits
.into_diagnostic()
.context("decoding hex content from file")?;

let era = Era::try_from(args.era).unwrap();

let tx = pallas::ledger::traverse::MultiEraTx::decode_for_era(era, &cbor)
.into_diagnostic()
.context("decoding tx cbor")?;

let mut utxos: UTxOs = UTxOs::new();
let resolved = resolve_inputs(&tx, &ledger)?;

for (input, output) in resolved.iter() {
let key = MultiEraInput::from_byron(&input);

Check failure on line 79 in src/bin/dolos/eval.rs

View workflow job for this annotation

GitHub Actions / Lint Rust

this expression creates a reference which is immediately dereferenced by the compiler

let value = MultiEraOutput::decode(Era::Byron, &output)

Check failure on line 81 in src/bin/dolos/eval.rs

View workflow job for this annotation

GitHub Actions / Lint Rust

this expression creates a reference which is immediately dereferenced by the compiler
.into_diagnostic()
.context("decoding utxo cbor")?;

utxos.insert(key, value);
}

let env: Environment = ApplyDB::mk_environment(args.slot, args.magic)
.into_diagnostic()
.context("resolving pparams")?;

validate(&tx, &utxos, &env).unwrap();

Ok(())
}
3 changes: 3 additions & 0 deletions src/bin/dolos/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::path::PathBuf;
mod common;
mod daemon;
mod data;
mod eval;
mod serve;
mod sync;

Expand All @@ -16,6 +17,7 @@ enum Command {
Sync(sync::Args),
Data(data::Args),
Serve(serve::Args),
Eval(eval::Args),
}

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -99,6 +101,7 @@ fn main() -> Result<()> {
Command::Sync(x) => sync::run(&config, &x)?,
Command::Data(x) => data::run(&config, &x)?,
Command::Serve(x) => serve::run(config, &x)?,
Command::Eval(x) => eval::run(&config, &x)?,
};

Ok(())
Expand Down
93 changes: 48 additions & 45 deletions src/storage/applydb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,12 @@ impl<'a> ApplyBatch<'a> {
self.utxo_inserts.contains_key(&UtxoRef(tx, output))
}

// Meant to be used to get the UTxO associated with a transaction input, assuming the current
// block has already been traversed, appropriately filling utxo_inserts and utxo_deletes.
// Meant to be used to get the UTxO associated with a transaction input,
// assuming the current block has already been traversed, appropriately
// filling utxo_inserts and utxo_deletes.
pub fn get_same_block_utxo(&self, tx_hash: TxHash, ind: OutputIndex) -> Option<UtxoBody> {
// utxo_inserts contains the UTxOs produced in the current block which haven't been spent.
// utxo_inserts contains the UTxOs produced in the current block which haven't
// been spent.
self.utxo_inserts
.get(&UtxoRef(tx_hash, ind))
// utxo_deletes contains UTxOs previously stored in the DB, which we don't care
Expand Down Expand Up @@ -299,7 +301,7 @@ impl ApplyDB {
Ok(dbval.map(|x| x.0))
}

pub fn apply_block(&mut self, cbor: &[u8], prot_magic: &u32) -> Result<(), Error> {
pub fn apply_block(&mut self, cbor: &[u8], prot_magic: u32) -> Result<(), Error> {
let block = MultiEraBlock::decode(cbor).map_err(|_| Error::Cbor)?;
let slot = block.slot();
let hash = block.hash();
Expand Down Expand Up @@ -332,12 +334,13 @@ impl ApplyDB {
}
}

// TODO: move out of storage
for tx in txs.iter() {
if tx.era() == Era::Byron {
match self.get_inputs(tx, &batch) {
Ok(inputs) => {
let utxos: UTxOs = Self::mk_utxo(&inputs);
let env: Environment = Self::mk_environment(&block, prot_magic)?;
let env: Environment = Self::mk_environment(block.slot(), prot_magic)?;
match validate(tx, &utxos, &env) {
Ok(()) => (),
Err(err) => warn!("Transaction validation failed ({:?})", err),
Expand All @@ -357,7 +360,8 @@ impl ApplyDB {
Ok(())
}

fn get_inputs(
// TODO: move out of storage
pub fn get_inputs(
&self,
metx: &MultiEraTx,
batch: &ApplyBatch,
Expand Down Expand Up @@ -390,7 +394,8 @@ impl ApplyDB {
Ok(res)
}

fn mk_utxo<'a>(entries: &'a [(TxIn, TxOut)]) -> UTxOs<'a> {
// TODO: move out of storage
pub fn mk_utxo<'a>(entries: &'a [(TxIn, TxOut)]) -> UTxOs<'a> {
let mut utxos: UTxOs<'a> = UTxOs::new();
for (input, output) in entries.iter() {
let multi_era_input: MultiEraInput = MultiEraInput::from_byron(input);
Expand All @@ -400,44 +405,42 @@ impl ApplyDB {
utxos
}

fn mk_environment(block: &MultiEraBlock, prot_magic: &u32) -> Result<Environment, Error> {
if block.era() == Era::Byron {
let slot: u64 = block.header().slot();
if slot <= 322876 {
// These are the genesis values.
Ok(Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic: *prot_magic,
})
} else if slot > 322876 && slot <= 1784895 {
// Block hash were the update proposal was submitted:
// 850805044e0df6c13ced2190db7b11489672b0225d478a35a6db71fbfb33afc0
Ok(Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 65536,
}),
prot_magic: *prot_magic,
})
} else {
// Block hash were the update proposal was submitted:
// d798a8d617b25fc6456ffe2d90895a2c15a7271b671dab2d18d46f3d0e4ef495
Ok(Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 8192,
}),
prot_magic: *prot_magic,
})
}
// TODO: move out of storage
pub fn mk_environment(slot: u64, prot_magic: u32) -> Result<Environment, Error> {
if slot <= 322876 {
// These are the genesis values.
Ok(Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 4096,
}),
prot_magic,
})
} else if slot > 322876 && slot <= 1784895 {
// Block hash were the update proposal was submitted:
// 850805044e0df6c13ced2190db7b11489672b0225d478a35a6db71fbfb33afc0
Ok(Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 65536,
}),
prot_magic,
})
} else if slot < 4492800 {
// Block hash were the update proposal was submitted:
// d798a8d617b25fc6456ffe2d90895a2c15a7271b671dab2d18d46f3d0e4ef495
Ok(Environment {
prot_params: MultiEraProtParams::Byron(ByronProtParams {
min_fees_const: 155381,
min_fees_factor: 44,
max_tx_size: 8192,
}),
prot_magic,
})
} else {
Err(Error::UnimplementedEra)
unimplemented!("we don't have pparams after byron")
}
}

Expand Down Expand Up @@ -564,7 +567,7 @@ mod tests {
}
}

db.apply_block(&cbor, &764824073).unwrap(); // This is mainnet's protocol magic number.
db.apply_block(&cbor, 764824073).unwrap(); // This is mainnet's protocol magic number.

for tx in block.txs() {
for input in tx.consumes() {
Expand Down
2 changes: 1 addition & 1 deletion src/sync/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl gasket::framework::Worker<Stage> for Worker {
info!(slot, "applying block");
stage
.ledger
.apply_block(cbor, &stage.prot_magic)
.apply_block(cbor, stage.prot_magic)
.or_panic()?;
}
RollEvent::Undo(slot, _, cbor) => {
Expand Down

0 comments on commit 1bf5a42

Please sign in to comment.