EspressoSystems / cape

Configurable Asset Privacy for Ethereum
https://cape.docs.espressosys.com/
GNU General Public License v3.0
94 stars 16 forks source link

Emit an event with the content of the accepted transactions when a block is submitted. #380

Closed philippecamacho closed 2 years ago

philippecamacho commented 2 years ago

The information needed will be specified in #362.

philippecamacho commented 2 years ago

Note: consider parsing the ethereum transaction receipt.

philippecamacho commented 2 years ago

Data needed for the wallet for updating the sparse data structures for creating new transactions

When a block is committed we need the list of wraps that were inserted in the merkle tree.

philippecamacho commented 2 years ago

@sveitser Should we not simply extract the information from CALL_DATA?

Check if ethers-rs can help for this.

@joe

sveitser commented 2 years ago

@philippecamacho @tri-joe @jbearer

Looks doable to get the calldata and deserialize it back into the function input argument of type CapeBlock. The test below passes.

We can do

There is also https://docs.rs/ethers/0.6.2/ethers/contract/builders/struct.Event.html#method.query_with_meta to get the events with tx hash (and more). I haven't tested this yet though.

See the code is between the // XXX blocks below.

#[tokio::test]
async fn test_submit_block_to_cape_contract() -> Result<()> {
    let contract = deploy_cape_test().await;

    // Create three transactions
    let rng = &mut ark_std::test_rng();
    let num_transfer_txn = 1;
    let num_mint_txn = 1;
    let num_freeze_txn = 1;
    let params = TxnsParams::generate_txns(
        rng,
        num_transfer_txn,
        num_mint_txn,
        num_freeze_txn,
        CapeLedger::merkle_height(),
    );
    let miner = UserPubKey::default();

    let nf = params.txns[0].nullifiers()[0];
    let root = params.txns[0].merkle_root();

    // temporarily no burn txn yet.
    let cape_block = CapeBlock::generate(params.txns, vec![], miner.address())?;

    // Check that some nullifier is not yet inserted
    assert!(
        !contract
            .nullifiers(nf.generic_into::<NullifierSol>().0)
            .call()
            .await?
    );

    // TODO should not require to manually submit the root here
    contract
        .add_root(root.generic_into::<MerkleRootSol>().0)
        .send()
        .await?
        .await?;

    // Submit to the contract
    let receipt = contract
        .submit_cape_block(cape_block.clone().into())
        .send()
        .await?
        .await?;

    // XXX
    // via block number
    let block_num = receipt.clone().unwrap().block_number.unwrap();
    use ethers::prelude::{Http, Provider};
    use std::convert::TryFrom;
    let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
    let block = provider.get_block_with_txs(block_num).await;
    let tx = block.unwrap().unwrap().transactions[0].clone(); // TODO find the right transactions
    let decoded: sol::CapeBlock = contract.decode("submitCapeBlock", tx.input).unwrap();
    println!("decoded {:?}", decoded);
    assert_eq!(decoded, cape_block.clone().into());

    // via tx hash
    let tx_hash = receipt.unwrap().transaction_hash;
    let tx = provider.get_transaction(tx_hash).await.unwrap().unwrap();
    let decoded: sol::CapeBlock = contract.decode("submitCapeBlock", tx.input).unwrap();
    println!("decoded {:?}", decoded);
    assert_eq!(decoded, cape_block.into());

    // XXX

    // Check that now the nullifier has been inserted
    assert!(
        contract
            .nullifiers(nf.generic_into::<NullifierSol>().0)
            .call()
            .await?
    );
    Ok(())
}
philippecamacho commented 2 years ago

@philippecamacho @tri-joe @jbearer

Looks doable to get the calldata and deserialize it back into the function input argument of type CapeBlock. The test below passes.

We can do

  • block number -> block -> transactions -> transaction -> calldata -> CapeBlock
  • transaction hash -> transaction -> calldata -> decode -> CapeBlock

There is also https://docs.rs/ethers/0.6.2/ethers/contract/builders/struct.Event.html#method.query_with_meta to get the events with tx hash (and more). I haven't tested this yet though.

See the code is between the // XXX blocks below.

#[tokio::test]
async fn test_submit_block_to_cape_contract() -> Result<()> {
    let contract = deploy_cape_test().await;

    // Create three transactions
    let rng = &mut ark_std::test_rng();
    let num_transfer_txn = 1;
    let num_mint_txn = 1;
    let num_freeze_txn = 1;
    let params = TxnsParams::generate_txns(
        rng,
        num_transfer_txn,
        num_mint_txn,
        num_freeze_txn,
        CapeLedger::merkle_height(),
    );
    let miner = UserPubKey::default();

    let nf = params.txns[0].nullifiers()[0];
    let root = params.txns[0].merkle_root();

    // temporarily no burn txn yet.
    let cape_block = CapeBlock::generate(params.txns, vec![], miner.address())?;

    // Check that some nullifier is not yet inserted
    assert!(
        !contract
            .nullifiers(nf.generic_into::<NullifierSol>().0)
            .call()
            .await?
    );

    // TODO should not require to manually submit the root here
    contract
        .add_root(root.generic_into::<MerkleRootSol>().0)
        .send()
        .await?
        .await?;

    // Submit to the contract
    let receipt = contract
        .submit_cape_block(cape_block.clone().into())
        .send()
        .await?
        .await?;

    // XXX
    // via block number
    let block_num = receipt.clone().unwrap().block_number.unwrap();
    use ethers::prelude::{Http, Provider};
    use std::convert::TryFrom;
    let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
    let block = provider.get_block_with_txs(block_num).await;
    let tx = block.unwrap().unwrap().transactions[0].clone(); // TODO find the right transactions
    let decoded: sol::CapeBlock = contract.decode("submitCapeBlock", tx.input).unwrap();
    println!("decoded {:?}", decoded);
    assert_eq!(decoded, cape_block.clone().into());

    // via tx hash
    let tx_hash = receipt.unwrap().transaction_hash;
    let tx = provider.get_transaction(tx_hash).await.unwrap().unwrap();
    let decoded: sol::CapeBlock = contract.decode("submitCapeBlock", tx.input).unwrap();
    println!("decoded {:?}", decoded);
    assert_eq!(decoded, cape_block.into());

    // XXX

    // Check that now the nullifier has been inserted
    assert!(
        contract
            .nullifiers(nf.generic_into::<NullifierSol>().0)
            .call()
            .await?
    );
    Ok(())
}

Awesome!

sveitser commented 2 years ago

There is also a type safe(er) way to do it but I'm running into an issue with ambiguous imports inside the abigen macro when trying it in the cape repo. ...Call is provided by the abigen.

Works in a sample repo

let decoded = MyFunctionCall::decode(tx.input).unwrap();
let arg = decoded.my_arg;

In cape it would be

let decoded = SubmitCapeBlockCall::decode(tx.input).unwrap();
let block = decoded.new_block;

The error is

note: `SubmitCapeBlockCall` could refer to the struct imported here
  --> contracts/rust/src/types.rs:26:1
   |
26 | / abigen!(
27 | |     AssetRegistry,
28 | |     "../abi/contracts/AssetRegistry.sol/AssetRegistry/abi.json",
29 | |     event_derives(serde::Deserialize, serde::Serialize);
...  |
94 | |
95 | | );
   | |__^
   = help: consider adding an explicit import of `SubmitCapeBlockCall` to disambiguate
note: `SubmitCapeBlockCall` could also refer to the struct imported here
  --> contracts/rust/src/types.rs:26:1
   |
26 | / abigen!(
27 | |     AssetRegistry,
28 | |     "../abi/contracts/AssetRegistry.sol/AssetRegistry/abi.json",
29 | |     event_derives(serde::Deserialize, serde::Serialize);
...  |
94 | |
95 | | );
   | |__^
   = help: consider adding an explicit import of `SubmitCapeBlockCall` to disambiguate
   = note: this error originates in the macro `abigen` (in Nightly builds, run with -Z macro-backtrace for more info)

Consider opening an issue on ethers-rs. To reproduce the error I think using a simple contract defining a struct and another contract inheriting from the first contract and putting them both in the abigen may trigger the error. abigen_expanded.rs.txt