refactor: traits for on-chain Home and Replica

buddies-main-deployment
James Prestwich 4 years ago
parent f831e4a660
commit 8fb2db9269
No known key found for this signature in database
GPG Key ID: 75A7F5C06D747046
  1. 2273
      rust/optics-base/Cargo.lock
  2. 23
      rust/optics-base/Cargo.toml
  3. 10
      rust/optics-base/config/default.toml
  4. 347
      rust/optics-base/src/abis/Home.abi.json
  5. 369
      rust/optics-base/src/abis/ProcessingReplica.abi.json
  6. 180
      rust/optics-base/src/abis/mod.rs
  7. 43
      rust/optics-base/src/main.rs
  8. 86
      rust/optics-base/src/settings.rs
  9. 7
      rust/optics-base/update_abis.sh
  10. 1123
      rust/optics-core/Cargo.lock
  11. 3
      rust/optics-core/Cargo.toml
  12. 11
      rust/optics-core/src/accumulator/merkle.rs
  13. 42
      rust/optics-core/src/lib.rs
  14. 37
      rust/optics-core/src/traits/home.rs
  15. 107
      rust/optics-core/src/traits/mod.rs
  16. 31
      rust/optics-core/src/traits/replica.rs
  17. 12
      rust/optics-core/src/utils.rs
  18. 4
      solidity/contracts/Common.sol
  19. 8
      solidity/contracts/Home.sol
  20. 4
      solidity/contracts/Queue.sol
  21. 8
      solidity/contracts/Replica.sol

File diff suppressed because it is too large Load Diff

@ -0,0 +1,23 @@
[package]
name = "optics-base"
version = "0.1.0"
authors = ["James Prestwich <prestwich@clabs.co>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.0.1", features = ["rt"] }
optics-core = { path = "../optics-core" }
config = "0.10"
serde = "1.0.120"
serde_json = { version = "1.0.61", default-features = false }
ethers = { git = "https://github.com/gakonst/ethers-rs" }
ethers-core = { git = "https://github.com/gakonst/ethers-rs" }
ethers-providers = { git = "https://github.com/gakonst/ethers-rs" }
ethers-contract = { git = "https://github.com/gakonst/ethers-rs", features = ["abigen"] }
thiserror = { version = "1.0.22", default-features = false }
async-trait = { version = "0.1.42", default-features = false }
url = { version = "2.2.0", default-features = false }

@ -0,0 +1,10 @@
[home]
chain = "Ethereum"
http = "http://localhost:8545"
address = "0x0000000000000000000000000000000000000000"
[replicas]
[replicas.Celo]
chain = "Ethereum"
address = "0x0000000000000000000000000000000000000000"
ws = "wss://"

@ -0,0 +1,347 @@
[
{
"inputs": [
{
"internalType": "uint32",
"name": "_originSLIP44",
"type": "uint32"
},
{
"internalType": "address",
"name": "_updater",
"type": "address"
},
{
"internalType": "bytes32",
"name": "_current",
"type": "bytes32"
}
],
"stateMutability": "payable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint32",
"name": "destination",
"type": "uint32"
},
{
"indexed": true,
"internalType": "uint32",
"name": "sequence",
"type": "uint32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "current",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "message",
"type": "bytes"
}
],
"name": "Dispatch",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes32[2]",
"name": "_oldRoot",
"type": "bytes32[2]"
},
{
"indexed": false,
"internalType": "bytes32[2]",
"name": "_newRoot",
"type": "bytes32[2]"
},
{
"indexed": false,
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
},
{
"indexed": false,
"internalType": "bytes",
"name": "_signature2",
"type": "bytes"
}
],
"name": "DoubleUpdate",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "ImproperUpdate",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "_oldRoot",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "_newRoot",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "Update",
"type": "event"
},
{
"inputs": [],
"name": "DOMAIN_HASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "current",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32[2]",
"name": "_oldRoot",
"type": "bytes32[2]"
},
{
"internalType": "bytes32[2]",
"name": "_newRoot",
"type": "bytes32[2]"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_signature2",
"type": "bytes"
}
],
"name": "doubleUpdate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "destination",
"type": "uint32"
},
{
"internalType": "bytes32",
"name": "recipient",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "body",
"type": "bytes"
}
],
"name": "enqueue",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_oldRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_newRoot",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
}
],
"name": "improperUpdate",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "originSLIP44",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "root",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"name": "sequences",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "state",
"outputs": [
{
"internalType": "enum Common.States",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "suggestUpdate",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tree",
"outputs": [
{
"internalType": "uint256",
"name": "count",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_oldRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_newRoot",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
}
],
"name": "update",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "updater",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

@ -0,0 +1,369 @@
[
{
"inputs": [
{
"internalType": "uint32",
"name": "_originSLIP44",
"type": "uint32"
},
{
"internalType": "uint32",
"name": "_ownSLIP44",
"type": "uint32"
},
{
"internalType": "address",
"name": "_updater",
"type": "address"
},
{
"internalType": "uint256",
"name": "_optimisticSeconds",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "_start",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_lastProcessed",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes32[2]",
"name": "_oldRoot",
"type": "bytes32[2]"
},
{
"indexed": false,
"internalType": "bytes32[2]",
"name": "_newRoot",
"type": "bytes32[2]"
},
{
"indexed": false,
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
},
{
"indexed": false,
"internalType": "bytes",
"name": "_signature2",
"type": "bytes"
}
],
"name": "DoubleUpdate",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "_oldRoot",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "_newRoot",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "Update",
"type": "event"
},
{
"inputs": [],
"name": "DOMAIN_HASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PROCESS_GAS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "RESERVE_GAS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "confirm",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "current",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32[2]",
"name": "_oldRoot",
"type": "bytes32[2]"
},
{
"internalType": "bytes32[2]",
"name": "_newRoot",
"type": "bytes32[2]"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_signature2",
"type": "bytes"
}
],
"name": "doubleUpdate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "messages",
"outputs": [
{
"internalType": "enum ProcessingReplica.MessageStatus",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "optimisticSeconds",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "originSLIP44",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ownSLIP44",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "process",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "leaf",
"type": "bytes32"
},
{
"internalType": "bytes32[32]",
"name": "proof",
"type": "bytes32[32]"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "prove",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "leaf",
"type": "bytes32"
},
{
"internalType": "bytes32[32]",
"name": "proof",
"type": "bytes32[32]"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "message",
"type": "bytes"
}
],
"name": "proveAndProcess",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "state",
"outputs": [
{
"internalType": "enum Common.States",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_oldRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_newRoot",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
}
],
"name": "update",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "updater",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

@ -0,0 +1,180 @@
use async_trait::async_trait;
use ethers_contract::abigen;
use ethers_core::types::{Address, H256, U256};
use std::sync::Arc;
use optics_core::{
traits::{ChainCommunicationError, Common, Home, State, TxOutcome},
Message, SignedUpdate, Update,
};
abigen!(
ReplicaContractInternal,
"src/abis/ProcessingReplica.abi.json"
);
abigen!(HomeContractInternal, "src/abis/Home.abi.json");
#[derive(Debug)]
pub struct HomeContract<M>
where
M: ethers_providers::Middleware,
{
contract: HomeContractInternal<M>,
slip44: u32,
}
impl<M> HomeContract<M>
where
M: ethers_providers::Middleware,
{
pub fn at(slip44: u32, address: Address, provider: Arc<M>) -> Self {
Self {
contract: HomeContractInternal::new(address, provider),
slip44,
}
}
}
#[async_trait]
impl<M> Common for HomeContract<M>
where
M: ethers_providers::Middleware + 'static,
{
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {
let receipt_opt = self
.contract
.client()
.get_transaction_receipt(txid)
.await
.map_err(|e| ChainCommunicationError::CustomError(Box::new(e)))?;
Ok(receipt_opt.map(Into::into))
}
fn origin_slip44(&self) -> u32 {
self.slip44
}
async fn updater(&self) -> Result<H256, ChainCommunicationError> {
Ok(self.contract.updater().call().await?.into())
}
async fn state(&self) -> Result<State, ChainCommunicationError> {
let state = self.contract.state().call().await?;
match state {
0 => Ok(State::Waiting),
1 => Ok(State::Failed),
_ => unreachable!(),
}
}
async fn current_root(&self) -> Result<H256, ChainCommunicationError> {
Ok(self.contract.current().call().await?.into())
}
async fn update(&self, update: &SignedUpdate) -> Result<TxOutcome, ChainCommunicationError> {
Ok(self
.contract
.update(
update.update.previous_root.to_fixed_bytes(),
update.update.new_root.to_fixed_bytes(),
update.signature.to_vec(),
)
.send()
.await?
.await?
.into())
}
async fn double_update(
&self,
left: &SignedUpdate,
right: &SignedUpdate,
) -> Result<TxOutcome, ChainCommunicationError> {
Ok(self
.contract
.double_update(
[
left.update.previous_root.to_fixed_bytes(),
right.update.previous_root.to_fixed_bytes(),
],
[
left.update.new_root.to_fixed_bytes(),
right.update.new_root.to_fixed_bytes(),
],
left.signature.to_vec(),
right.signature.to_vec(),
)
.send()
.await?
.await?
.into())
}
}
#[async_trait]
impl<M> Home for HomeContract<M>
where
M: ethers_providers::Middleware + 'static,
{
async fn lookup_message(
&self,
destination: u32,
sequence: u32,
) -> Result<Option<Vec<u8>>, ChainCommunicationError> {
let filters = self
.contract
.dispatch_filter()
.topic1(U256::from(destination))
.topic2(U256::from(sequence))
.query()
.await?;
Ok(filters.into_iter().next().map(|f| f.message.clone()))
}
async fn sequences(&self, destination: u32) -> Result<u32, ChainCommunicationError> {
Ok(self.contract.sequences(destination).call().await?)
}
async fn enqueue(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError> {
Ok(self
.contract
.enqueue(
message.destination,
message.recipient.to_fixed_bytes(),
message.body.clone(),
)
.send()
.await?
.await?
.into())
}
async fn improper_update(
&self,
update: &SignedUpdate,
) -> Result<TxOutcome, ChainCommunicationError> {
Ok(self
.contract
.improper_update(
update.update.previous_root.to_fixed_bytes(),
update.update.new_root.to_fixed_bytes(),
update.signature.to_vec(),
)
.send()
.await?
.await?
.into())
}
async fn produce_update(&self) -> Result<Update, ChainCommunicationError> {
let (a, b) = self.contract.suggest_update().call().await?;
Ok(Update {
origin_chain: self.origin_slip44(),
previous_root: a.into(),
new_root: b.into(),
})
}
}

@ -0,0 +1,43 @@
mod abis;
mod settings;
use ethers_providers::{Http, Provider};
// use std::collections::HashMap;
use std::{convert::TryFrom, sync::Arc};
use optics_core::traits::{Home, Replica};
#[derive(Debug)]
struct App {
home: Box<dyn Home>,
replicas: Vec<Box<dyn Replica>>,
}
async fn _main(settings: settings::Settings) {
println!("{:?}", &settings);
let home = {
let provider = Arc::new(Provider::<Http>::try_from(settings.home().url()).expect("!url"));
Box::new(abis::HomeContract::at(
0,
settings.home().address().into(),
provider,
))
};
let app = App {
home,
replicas: vec![],
};
println!("{:?}", &app);
}
fn main() {
let settings = settings::Settings::new().expect("!config");
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(_main(settings))
}

@ -0,0 +1,86 @@
use config::{Config, ConfigError, Environment, File};
use std::{collections::HashMap, env};
use ethers_core::types::{Address, H256};
#[derive(Debug, serde::Deserialize)]
#[serde(untagged)]
pub(crate) enum Ethereum {
Http { address: Address, http: String },
Ws { address: Address, ws: String },
}
impl Ethereum {
pub fn url(&self) -> &str {
match self {
Self::Http { address: _, http } => &http,
Self::Ws { address: _, ws } => &ws,
}
}
pub fn address(&self) -> Address {
match self {
Self::Http { address, http: _ } => *address,
Self::Ws { address, ws: _ } => *address,
}
}
}
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "chain")]
pub(crate) enum Home {
Ethereum(Ethereum),
}
impl Home {
pub fn url(&self) -> &str {
match self {
Self::Ethereum(e) => e.url(),
}
}
pub fn address(&self) -> H256 {
match self {
Self::Ethereum(e) => e.address().into(),
}
}
}
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "chain")]
pub(crate) enum Replica {
Ethereum(Ethereum),
}
#[derive(Debug, serde::Deserialize)]
pub(crate) struct Settings {
home: Home,
replicas: HashMap<String, Replica>,
}
impl Settings {
pub fn new() -> Result<Self, ConfigError> {
let mut s = Config::new();
s.merge(File::with_name("config/default"))?;
let env = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
s.merge(File::with_name(&format!("config/{}", env)).required(false))?;
// Add in settings from the environment (with a prefix of OPTRELAY)
// Eg.. `OPTRELAY_DEBUG=1 would set the `debug` key
s.merge(Environment::with_prefix("OPTRELAY"))?;
s.try_into()
}
}
impl Settings {
pub fn home(&self) -> &Home {
&self.home
}
pub fn replicas(&self) -> &HashMap<String, Replica> {
&self.replicas
}
}

@ -0,0 +1,7 @@
#!/bin/zsh
cd ../../solidity && \
npm i && \
npm run compile && \
cat artifacts/contracts/Replica.sol/ProcessingReplica.json| jq .abi > ../rust/optics-base/src/abis/ProcessingReplica.abi.json && \
cat artifacts/contracts/Home.sol/Home.json| jq .abi > ../rust/optics-base/src/abis/Home.abi.json

File diff suppressed because it is too large Load Diff

@ -9,9 +9,12 @@ edition = "2018"
[dependencies]
ethers-core = { git = "https://github.com/gakonst/ethers-rs" }
ethers-signers = { git = "https://github.com/gakonst/ethers-rs" }
ethers-contract = { git = "https://github.com/gakonst/ethers-rs" }
ethers-providers = { git = "https://github.com/gakonst/ethers-rs" }
sha3 = "0.9.1"
lazy_static = "*"
thiserror = "*"
async-trait = { version = "0.1.42", default-features = false }
[dev-dependencies]
tokio = {version = "1.0.1", features = ["rt", "time"]}

@ -415,6 +415,17 @@ mod tests {
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it() {
ZERO_HASHES.iter().for_each(|e| {
dbg!(e);
});
}
}
/*
Apache License
Version 2.0, January 2004

@ -11,6 +11,11 @@ pub mod accumulator;
/// Model instantatiations of the on-chain structures
pub mod models;
/// Async Traits for Homes & Replicas for use in applications
pub mod traits;
mod utils;
use ethers_core::{
types::{Address, Signature, H256},
utils::hash_message,
@ -18,6 +23,8 @@ use ethers_core::{
use ethers_signers::Signer;
use sha3::{Digest, Keccak256};
use crate::utils::*;
/// Error types for Optics
#[derive(Debug, thiserror::Error)]
pub enum OpticsError {
@ -44,6 +51,13 @@ pub trait Encode {
fn write_to<W>(&self, writer: &mut W) -> std::io::Result<usize>
where
W: std::io::Write;
/// Serialize to a vec
fn to_vec(&self) -> Vec<u8> {
let mut buf = vec![];
self.write_to(&mut buf).expect("!alloc");
buf
}
}
impl Encode for Signature {
@ -56,25 +70,21 @@ impl Encode for Signature {
}
}
fn domain_hash(origin_slip44_id: u32) -> H256 {
H256::from_slice(
Keccak256::new()
.chain(origin_slip44_id.to_be_bytes())
.chain("OPTICS".as_bytes())
.finalize()
.as_slice(),
)
}
/// An Optics message between chains
#[derive(Debug, Clone)]
pub struct Message {
origin: u32, // 4 SLIP-44 ID
sender: H256, // 32 Address in origin convention
destination: u32, // 4 SLIP-44 ID
recipient: H256, // 32 Address in destination convention
sequence: u32, // 4 Count of all previous messages to destination
body: Vec<u8>, // 0+ Message contents
/// 4 SLIP-44 ID
pub origin: u32,
/// 32 Address in origin convention
pub sender: H256,
/// 4 SLIP-44 ID
pub destination: u32,
/// 32 Address in destination convention
pub recipient: H256,
/// 4 Count of all previous messages to destination
pub sequence: u32,
/// 0+ Message contents
pub body: Vec<u8>,
}
impl Encode for Message {

@ -0,0 +1,37 @@
use async_trait::async_trait;
use crate::{
traits::{ChainCommunicationError, Common, TxOutcome},
Message, SignedUpdate, Update,
};
/// Interface for the Home chain contract. Allows abstraction over different
/// chains
#[async_trait]
pub trait Home: Common {
/// Fetch the message to destination at the sequence number (or error).
/// This should fetch events from the chain API
async fn lookup_message(
&self,
destination: u32,
sequence: u32,
) -> Result<Option<Vec<u8>>, ChainCommunicationError>;
/// Fetch the sequence
async fn sequences(&self, destination: u32) -> Result<u32, ChainCommunicationError>;
/// Queue a message.
async fn enqueue(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError>;
/// Submit an improper update for slashing
async fn improper_update(
&self,
update: &SignedUpdate,
) -> Result<TxOutcome, ChainCommunicationError>;
/// Create a valid update based on the chain's current state.
/// This merely suggests an update. It does NOT ensure that no other valid
/// update has been produced. The updater MUST take measures to prevent
/// double-updating.
async fn produce_update(&self) -> Result<Update, ChainCommunicationError>;
}

@ -0,0 +1,107 @@
/// Interface for home chain contract
pub mod home;
/// Interface for replica chain contract
pub mod replica;
use async_trait::async_trait;
use ethers_core::types::{TransactionReceipt, H256};
use thiserror::Error;
use crate::{utils::domain_hash, SignedUpdate};
pub use home::*;
pub use replica::*;
/// Contract states
#[derive(Debug)]
pub enum State {
/// Contract is active
Waiting,
/// Contract has failed
Failed,
}
/// The result of a transaction
#[derive(Debug)]
pub struct TxOutcome {
/// The txid
pub txid: H256,
/// True if executed, false otherwise
pub executed: bool,
}
impl From<TransactionReceipt> for TxOutcome {
fn from(t: TransactionReceipt) -> Self {
Self {
txid: t.transaction_hash,
executed: t.status.unwrap().low_u32() == 1,
}
}
}
#[derive(Debug, Error)]
/// Error type for chain communication
pub enum ChainCommunicationError {
/// Provider Error
#[error(transparent)]
ProviderError(#[from] ethers_providers::ProviderError),
/// Contract Error
#[error(transparent)]
ContractError(Box<dyn std::error::Error>),
/// Custom error or contract error
#[error(transparent)]
CustomError(#[from] Box<dyn std::error::Error>),
}
impl<M> From<ethers_contract::ContractError<M>> for ChainCommunicationError
where
M: ethers_providers::Middleware + 'static,
{
fn from(e: ethers_contract::ContractError<M>) -> Self {
Self::ContractError(Box::new(e))
}
}
// impl<M> From<ethers_contract::ContractError<M>> for ChainCommunicationError
// where
// M: ethers_providers::Middleware + 'static,
// {
// fn from(e: ethers_contract::ContractError<M>) -> Self {
// Self::ContractError(Box::new(e))
// }
// }
/// Interface for attributes shared by Home and Replica
#[async_trait]
pub trait Common: Sync + Send + std::fmt::Debug {
/// Get the status of a transaction
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError>;
/// Return the slip44 ID
fn origin_slip44(&self) -> u32;
/// Return the domain hash
fn domain_hash(&self) -> H256 {
domain_hash(self.origin_slip44())
}
/// Fetch the current updater value
async fn updater(&self) -> Result<H256, ChainCommunicationError>;
/// Fetch the current state.
async fn state(&self) -> Result<State, ChainCommunicationError>;
/// Fetch the current root
async fn current_root(&self) -> Result<H256, ChainCommunicationError>;
/// Submit a signed update for inclusion
async fn update(&self, update: &SignedUpdate) -> Result<TxOutcome, ChainCommunicationError>;
/// Submit a double update for slashing
async fn double_update(
&self,
left: &SignedUpdate,
right: &SignedUpdate,
) -> Result<TxOutcome, ChainCommunicationError>;
}

@ -0,0 +1,31 @@
use async_trait::async_trait;
use ethers_core::types::{H256, U256};
use crate::{
traits::{ChainCommunicationError, Common, TxOutcome},
Message,
};
/// Interface for on-chain replicas
#[async_trait]
pub trait Replica: Common {
/// Return the pending root and time, if any
async fn pending(&self) -> Result<Option<(H256, U256)>, ChainCommunicationError>;
/// Confirm the next pending root (after its timer has elapsed);
async fn confirm(&self) -> Result<TxOutcome, ChainCommunicationError>;
/// Fetch the previous root.
async fn previous_root(&self) -> Result<H256, ChainCommunicationError>;
/// Prove inclusion of some leaf in the replica
async fn prove(
&self,
leaf: H256,
index: u32,
proof: [H256; 32],
) -> Result<TxOutcome, ChainCommunicationError>;
/// Trigger processing of a message
async fn process(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError>;
}

@ -0,0 +1,12 @@
use ethers_core::types::H256;
use sha3::{Digest, Keccak256};
pub(crate) fn domain_hash(origin_slip44_id: u32) -> H256 {
H256::from_slice(
Keccak256::new()
.chain(origin_slip44_id.to_be_bytes())
.chain("OPTICS".as_bytes())
.finalize()
.as_slice(),
)
}

@ -127,8 +127,8 @@ abstract contract Common {
}
function checkSig(
bytes32 _newRoot,
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) internal view returns (bool) {
bytes32 _digest =
@ -138,8 +138,8 @@ abstract contract Common {
}
function doubleUpdate(
bytes32[2] calldata _newRoot,
bytes32[2] calldata _oldRoot,
bytes32[2] calldata _newRoot,
bytes calldata _signature,
bytes calldata _signature2
) external notFailed {

@ -64,8 +64,8 @@ contract Home is MerkleTreeManager, QueueManager, Common {
}
function update(
bytes32 _newRoot,
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) external notFailed {
if (improperUpdate(_newRoot, _oldRoot, _signature)) return;
@ -77,8 +77,8 @@ contract Home is MerkleTreeManager, QueueManager, Common {
}
function improperUpdate(
bytes32 _newRoot,
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) public notFailed returns (bool) {
require(Common.checkSig(_newRoot, _oldRoot, _signature), "bad sig");
@ -90,4 +90,8 @@ contract Home is MerkleTreeManager, QueueManager, Common {
}
return false;
}
function suggestUpdate() external view returns (bytes32, bytes32) {
return (current, queue.lastItem());
}
}

@ -28,6 +28,10 @@ library QueueLib {
return false;
}
function lastItem(Queue storage _q) internal view returns (bytes32) {
return _q.queue[_q.last];
}
function enqueue(Queue storage _q, bytes32 _item) internal {
uint256 _last = _q.last + 1;
_q.last = _last;

@ -12,8 +12,6 @@ abstract contract Replica is Common {
bytes32 pending;
uint256 confirmAt;
event DoubleUpdate();
constructor(
uint32 _originSLIP44,
uint32 _ownSLIP44,
@ -38,8 +36,8 @@ abstract contract Replica is Common {
// TODO: refactor to queue
function update(
bytes32 _newRoot,
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) external notFailed {
require(current == _oldRoot, "Not current update");
@ -70,9 +68,9 @@ contract ProcessingReplica is Replica {
using Message for bytes29;
// minimum gas for message processing
uint256 PROCESS_GAS = 500000;
uint256 public constant PROCESS_GAS = 500000;
// reserved gas (to ensure tx completes in case message processing runs out)
uint256 RESERVE_GAS = 10000;
uint256 public constant RESERVE_GAS = 10000;
bytes32 previous; // to smooth over witness invalidation
uint256 lastProcessed;

Loading…
Cancel
Save