Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chaincash store updates/impl #43

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
514 changes: 292 additions & 222 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
tracing = "0.1.37"
ergo_client = { git = "https://github.com/ross-weir/ergo_client.git" }
ergo_lib = { git = "https://github.com/ergoplatform/sigma-rust.git", branch = "master", package = "ergo-lib", features = ["compiler"] }
2 changes: 1 addition & 1 deletion config/predicates/example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
type = "or"
conditions = [
# the owner of the note is either PK1 or PK2
{type = "whitelist", agents = ["PK1", "PK2"]},
{type = "whitelist", kind = "owner", agents = ["PK1", "PK2"]},
# the note has at least 100% collateral
{type = "collateral", percent = 100}
]
10 changes: 5 additions & 5 deletions crates/chaincash_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ use tracing::info;

#[derive(Error, Debug)]
pub enum Error {
#[error("store error: {0}")]
#[error(transparent)]
Store(#[from] chaincash_store::Error),

#[error("server error: {0}")]
#[error(transparent)]
Server(#[from] chaincash_server::Error),

#[error("offchain error: {0}")]
OffChain(#[from] chaincash_offchain::Error),
#[error(transparent)]
Node(#[from] chaincash_offchain::node::NodeError),

#[error("Failed to load chaincash predicates specified in config file")]
LoadPredicate(#[from] chaincash_predicate::Error),

#[error("config error: {0}")]
#[error(transparent)]
Config(#[from] config::ConfigError),
}

Expand Down
3 changes: 1 addition & 2 deletions crates/chaincash_offchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ license.workspace = true

[dependencies]
once_cell = "1.18.0"
ergo_lib = { git = "https://github.com/ergoplatform/sigma-rust.git", branch = "master", package = "ergo-lib", features = ["compiler"] }
bs58 = "0.5.0"
ergo_lib = { workspace = true }
ergo_client = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
73 changes: 73 additions & 0 deletions crates/chaincash_offchain/src/boxes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use ergo_lib::{
ergo_chain_types::EcPoint,
ergotree_ir::{
chain::ergo_box::{ErgoBox, NonMandatoryRegisterId, RegisterValueError},
mir::constant::TryExtractInto,
types::stype::SType,
},
};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error("Failed to access register")]
BadRegister(#[from] RegisterValueError),

#[error("Box field was unexpectedly empty: {0}")]
FieldNotSet(&'static str),

#[error("Box field '{field}' was set to incorrect type: {tpe}")]
InvalidType { field: String, tpe: SType },
}

pub struct ReserveBoxSpec {
pub owner: EcPoint,
pub refund_height: i64,
pub identifier: String,
}

impl TryFrom<&ErgoBox> for ReserveBoxSpec {
type Error = Error;

fn try_from(value: &ErgoBox) -> Result<Self, Self::Error> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should also check the ergo tree to ensure its a reserve box

let owner = value
.get_register(NonMandatoryRegisterId::R4.into())?
.ok_or_else(|| Error::FieldNotSet("owner"))
.and_then(|reg| {
if reg.tpe == SType::SGroupElement {
Ok(reg.v.try_extract_into::<EcPoint>().unwrap())
} else {
Err(Error::InvalidType {
field: "owner".to_owned(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this field could be &'static str typed

tpe: reg.tpe,
})
}
})?;
let refund_height = value
.get_register(NonMandatoryRegisterId::R5.into())?
.ok_or_else(|| Error::FieldNotSet("refund_height"))
.and_then(|reg| {
if reg.tpe == SType::SLong {
Ok(reg.v.try_extract_into::<i64>().unwrap())
} else {
Err(Error::InvalidType {
field: "refund_height".to_owned(),
tpe: reg.tpe,
})
}
})?;
let identifier = value
.tokens
.as_ref()
.ok_or_else(|| Error::FieldNotSet("reserve box missing NFT"))?
.get(0)
.ok_or_else(|| Error::FieldNotSet("token at index 0 missing, no identifier nft"))?
.token_id;

Ok(Self {
owner,
refund_height,
identifier: String::from(identifier),
})
}
}
10 changes: 5 additions & 5 deletions crates/chaincash_offchain/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ use ergo_lib::ergoscript_compiler::compiler::compile;
use ergo_lib::ergotree_ir::ergo_tree::ErgoTree;
use once_cell::sync::Lazy;

pub(crate) static RESERVE_CONTRACT: &str =
pub static RESERVE_CONTRACT: &str =
include_str!("../../../contracts/chaincash/contracts/onchain/reserve.es");
// Not currently able to compile with sigma-rust, using node api instead
pub(crate) static RESERVE_ERGO_TREE: Lazy<ErgoTree> =
pub static RESERVE_ERGO_TREE: Lazy<ErgoTree> =
Lazy::new(|| compile(RESERVE_CONTRACT, Default::default()).unwrap());

pub(crate) static RECEIPT_CONTRACT: &str =
pub static RECEIPT_CONTRACT: &str =
include_str!("../../../contracts/chaincash/contracts/onchain/receipt.es");

pub(crate) static NOTE_CONTRACT: &str =
pub static NOTE_CONTRACT: &str =
include_str!("../../../contracts/chaincash/contracts/onchain/note.es");
// Not currently able to compile with sigma-rust, using node api instead
pub(crate) static NOTE_ERGO_TREE: Lazy<ErgoTree> =
pub static NOTE_ERGO_TREE: Lazy<ErgoTree> =
Lazy::new(|| compile(NOTE_CONTRACT, Default::default()).unwrap());
16 changes: 1 addition & 15 deletions crates/chaincash_offchain/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
pub mod boxes;
pub mod contracts;
pub mod node;
pub mod transactions;

use thiserror::Error;

pub use ergo_client::Error as ClientError;
pub use transactions::TransactionService;

#[derive(Debug, Error)]
pub enum Error {
#[error("Ergo client error: {0}")]
Client(#[from] ergo_client::Error),

#[error("Ergo Node client error")]
Node(#[from] ergo_client::node::NodeError),

#[error("transaction error: {0}")]
Transaction(#[from] transactions::TransactionError),
}
2 changes: 1 addition & 1 deletion crates/chaincash_offchain/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct Config {
api_key: String,
}

pub fn node_from_config(cfg: &Config) -> Result<NodeClient, crate::Error> {
pub fn node_from_config(cfg: &Config) -> Result<NodeClient, NodeError> {
Ok(NodeClient::from_url_str(
&cfg.url,
cfg.api_key.clone(),
Expand Down
123 changes: 9 additions & 114 deletions crates/chaincash_offchain/src/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
pub mod notes;
pub mod reserves;

use crate::contracts::{NOTE_CONTRACT, RECEIPT_CONTRACT, RESERVE_CONTRACT};

use self::notes::{mint_note_transaction, MintNoteRequest};
use self::reserves::{mint_reserve_transaction, MintReserveRequest};
use ergo_client::node::NodeClient;
use ergo_lib::chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError;
use ergo_lib::ergo_chain_types::blake2b256_hash;
use ergo_lib::ergotree_ir::chain::address::AddressEncoderError;
use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValue;
use ergo_lib::ergotree_ir::chain::ergo_box::{box_value::BoxValueError, ErgoBox};
use ergo_lib::ergotree_ir::chain::token::TokenAmountError;
use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
use ergo_lib::wallet::box_selector::{
BoxSelection, BoxSelector, BoxSelectorError, SimpleBoxSelector,
use ergo_lib::{
chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError,
ergotree_ir::chain::{
address::AddressEncoderError, ergo_box::box_value::BoxValueError, token::TokenAmountError,
},
wallet::{box_selector::BoxSelectorError, tx_builder::TxBuilderError},
};
use ergo_lib::wallet::tx_builder::{TxBuilderError, SUGGESTED_TX_FEE};
use thiserror::Error;

#[derive(Debug, Error)]
Expand Down Expand Up @@ -50,103 +41,7 @@ pub enum TransactionError {
}

pub struct TxContext {
current_height: u32,
change_address: String,
fee: u64,
}

#[derive(Clone)]
pub struct TransactionService<'a> {
node: &'a NodeClient,
}

impl<'a> TransactionService<'a> {
pub fn new(node: &'a NodeClient) -> Self {
Self { node }
}

async fn box_selection_with_amount(
&self,
amount: u64,
) -> Result<BoxSelection<ErgoBox>, crate::Error> {
let inputs = self
.node
.extensions()
.get_utxos_summing_amount(amount)
.await?;
// kinda irrelevant since we already have suitable boxes but box selectors required by ergo-lib txbuilder
Ok(SimpleBoxSelector::new()
.select(
inputs,
amount.try_into().map_err(TransactionError::from)?,
&[],
)
.map_err(TransactionError::from)?)
}

async fn get_tx_ctx(&self) -> Result<TxContext, crate::Error> {
let wallet_status = self.node.endpoints().wallet()?.status().await?;
let info = self.node.endpoints().root()?.info().await?;

if wallet_status.change_address.is_empty() {
Err(TransactionError::ChangeAddress(
"address not set".to_owned(),
))?
} else {
Ok(TxContext {
current_height: info.full_height as u32,
change_address: wallet_status.change_address,
fee: *SUGGESTED_TX_FEE().as_u64(),
})
}
}

pub async fn mint_reserve(&self, request: MintReserveRequest) -> Result<String, crate::Error> {
let ctx = self.get_tx_ctx().await?;
let selected_inputs = self
.box_selection_with_amount(request.amount + ctx.fee)
.await?;
let reserve_tree = self
.node
.extensions()
.compile_contract(RESERVE_CONTRACT)
.await?;
let unsigned_tx = mint_reserve_transaction(request, reserve_tree, selected_inputs, ctx)?;

Ok(self.node.extensions().sign_and_submit(unsigned_tx).await?)
}

pub async fn mint_note(&self, request: MintNoteRequest) -> Result<String, crate::Error> {
let reserve_tree_bytes = self
.node
.extensions()
.compile_contract(RESERVE_CONTRACT)
.await?
.sigma_serialize_bytes()
.unwrap();
let reserve_hash = bs58::encode(blake2b256_hash(&reserve_tree_bytes[1..])).into_string();
let receipt_contract = RECEIPT_CONTRACT.replace("$reserveContractHash", &reserve_hash);
let receipt_tree_bytes = self
.node
.extensions()
.compile_contract(&receipt_contract)
.await?
.sigma_serialize_bytes()
.unwrap();
let receipt_hash = bs58::encode(blake2b256_hash(&receipt_tree_bytes[1..])).into_string();
let ctx = self.get_tx_ctx().await?;
let selected_inputs = self
.box_selection_with_amount(BoxValue::SAFE_USER_MIN.as_u64() + ctx.fee)
.await?;
let note_contract = NOTE_CONTRACT
.replace("$reserveContractHash", &reserve_hash)
.replace("$receiptContractHash", &receipt_hash);
let contract_tree = self
.node
.extensions()
.compile_contract(&note_contract)
.await?;
let unsigned_tx = mint_note_transaction(request, contract_tree, selected_inputs, ctx)?;
Ok(self.node.extensions().sign_and_submit(unsigned_tx).await?)
}
pub current_height: u32,
pub change_address: String,
pub fee: u64,
}
1 change: 1 addition & 0 deletions crates/chaincash_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ license.workspace = true
chaincash_offchain = { path = "../chaincash_offchain" }
chaincash_store = { path = "../chaincash_store" }
chaincash_predicate = { path = "../chaincash_predicate" }
chaincash_services = { path = "../chaincash_services" }
axum = "0.7.1"
tower = "0.4.13"
hyper = { version = "0.14.27", features = ["full"] }
Expand Down
24 changes: 3 additions & 21 deletions crates/chaincash_server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ trait AsStatusCode {
fn as_status_code(&self) -> StatusCode;
}

// Despite the name this doesn't represent a clientside error in the context of server/client
// it means the node client library threw an error
impl AsStatusCode for chaincash_offchain::ClientError {
fn as_status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
}

impl AsStatusCode for chaincash_offchain::node::NodeError {
fn as_status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
Expand All @@ -28,26 +20,16 @@ impl AsStatusCode for chaincash_offchain::transactions::TransactionError {
}
}

impl AsStatusCode for chaincash_offchain::Error {
fn as_status_code(&self) -> StatusCode {
match self {
chaincash_offchain::Error::Client(e) => e.as_status_code(),
chaincash_offchain::Error::Node(e) => e.as_status_code(),
chaincash_offchain::Error::Transaction(e) => e.as_status_code(),
}
}
}

#[derive(Debug, Error)]
pub enum ApiError {
#[error("offchain error: {0}")]
OffChain(#[from] chaincash_offchain::Error),
#[error("Transaction service error")]
TransactionService(#[from] chaincash_services::transaction::TransactionServiceError),
}

impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status_code, msg) = match self {
ApiError::OffChain(e) => (e.as_status_code(), e.to_string()),
ApiError::TransactionService(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
};
let body = Json(json!({
"error": {
Expand Down
2 changes: 1 addition & 1 deletion crates/chaincash_server/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! ChainCash payment server creation and serving.
use axum::{routing::get, Router};
use chaincash_offchain::TransactionService;
use chaincash_predicate::predicates::Predicate;
use chaincash_services::transaction::TransactionService;
use chaincash_store::ChainCashStore;
use ergo_client::node::NodeClient;
use tracing::info;
Expand Down
13 changes: 13 additions & 0 deletions crates/chaincash_services/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "chaincash_services"
version.workspace = true
edition.workspace = true
license.workspace = true

[dependencies]
bs58 = "0.5.0"
ergo_lib = { workspace = true }
ergo_client = { workspace = true }
chaincash_store = { path = "../chaincash_store" }
chaincash_offchain = { path = "../chaincash_offchain" }
thiserror = { workspace = true }
1 change: 1 addition & 0 deletions crates/chaincash_services/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod transaction;
Loading
Loading