add finalize_order api

This commit is contained in:
creating2morrow 2023-06-05 11:24:20 -04:00
parent 60786f23d4
commit 89e7a0cbb5
7 changed files with 219 additions and 34 deletions

View file

@ -23,6 +23,7 @@ pub const EXPORT_MSIG: &str = "export";
pub const MAKE_MSIG: &str = "make";
pub const PREPARE_MSIG: &str = "prepare";
pub const SIGN_MSIG: &str = "sign";
pub const TXSET_MSIG: &str = "txset";
pub const VALID_MSIG_MSG_LENGTH: usize = 4;
#[derive(PartialEq)]
@ -129,14 +130,22 @@ fn parse_multisig_message(mid: String) -> MultisigMessageData {
let decoded = String::from_utf8(bytes).unwrap_or(utils::empty_string());
let values = decoded.split(":");
let mut v: Vec<String> = values.map(|s| String::from(s)).collect();
if v.len() != VALID_MSIG_MSG_LENGTH {
let sub_type: String = v.remove(0);
let valid_length = if sub_type == TXSET_MSIG {
VALID_MSIG_MSG_LENGTH - 2
} else {
VALID_MSIG_MSG_LENGTH - 1
};
if v.len() != valid_length {
return Default::default();
}
let sub_type: String = v.remove(0);
let orid: String = v.remove(0);
let customer_info: String = v.remove(0);
let mediator_info: String = v.remove(0);
let info = format!("{}:{}", customer_info, mediator_info);
let mut info = String::from(&customer_info);
if sub_type != TXSET_MSIG {
let mediator_info: String = v.remove(0);
info = format!("{}:{}", customer_info, mediator_info);
}
bytes = Vec::new();
debug!("zero decryption bytes: {:?}", bytes);
MultisigMessageData {

View file

@ -236,8 +236,8 @@ pub struct Product {
pub image: Vec<u8>,
pub in_stock: bool,
pub name: String,
pub price: u64,
pub qty: u64,
pub price: u128,
pub qty: u128,
}
impl Default for Product {
@ -272,11 +272,11 @@ impl Product {
Err(_) => false,
};
let name = v.remove(0);
let price = match v.remove(0).parse::<u64>() {
let price = match v.remove(0).parse::<u128>() {
Ok(p) => p,
Err(_) => 0,
};
let qty = match v.remove(0).parse::<u64>() {
let qty = match v.remove(0).parse::<u128>() {
Ok(q) => q,
Err(_) => 0,
};
@ -331,7 +331,7 @@ pub struct Order {
/// This is the final destination for the payment
pub subaddress: String,
pub status: String,
pub quantity: u64,
pub quantity: u128,
pub vend_kex_1: String,
pub vend_kex_2: String,
pub vend_kex_3: String,
@ -445,7 +445,7 @@ impl Order {
};
let subaddress = v.remove(0);
let status = v.remove(0);
let quantity = match v.remove(0).parse::<u64>() {
let quantity = match v.remove(0).parse::<u128>() {
Ok(d) => d,
Err(_) => 0,
};

View file

@ -29,6 +29,7 @@ enum RpcFields {
Close,
CreateAddress,
CreateWallet,
DescribeTransfer,
ExchangeMultisigKeys,
Export,
GetTxProof,
@ -57,6 +58,7 @@ impl RpcFields {
RpcFields::Close => String::from("close_wallet"),
RpcFields::CreateAddress => String::from("create_address"),
RpcFields::CreateWallet => String::from("create_wallet"),
RpcFields::DescribeTransfer => String::from("describe_transfer"),
RpcFields::ExchangeMultisigKeys => String::from("exchange_multisig_keys"),
RpcFields::Export => String::from("export_multisig_info"),
RpcFields::GetTxProof => String::from("get_tx_proof"),
@ -940,6 +942,45 @@ pub async fn transfer(d: reqres::Destination) -> reqres::XmrRpcTransferResponse
}
}
/// Performs the xmr rpc 'describe_transfer' method
pub async fn describe_transfer(multisig_txset: &String) -> reqres::XmrRpcDescribeTransferResponse {
info!("executing {}", RpcFields::DescribeTransfer.value());
let client = reqwest::Client::new();
let host = get_rpc_host();
let params: reqres::XmrRpcDescribeTransferParams = reqres::XmrRpcDescribeTransferParams {
multisig_txset: String::from(multisig_txset),
};
let req = reqres::XmrRpcDescribeTransfrerRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(),
method: RpcFields::DescribeTransfer.value(),
params,
};
let login: RpcLogin = get_rpc_creds();
match client
.post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => {
let res = response
.json::<reqres::XmrRpcDescribeTransferResponse>()
.await;
debug!(
"{} response: {:?}",
RpcFields::DescribeTransfer.value(),
res
);
match res {
Ok(res) => res,
_ => Default::default(),
}
}
Err(_) => Default::default(),
}
}
/// Performs the xmr rpc 'sweep_all' method
pub async fn sweep_all(address: String) -> reqres::XmrRpcSweepAllResponse {
info!("executing {}", RpcFields::SweepAll.value());

View file

@ -1,10 +1,14 @@
use crate::{
contact,
db,
gpg,
i2p,
message,
models::*,
monero,
product,
reqres,
utils, product, gpg, i2p,
utils,
};
use log::{
debug,
@ -13,13 +17,8 @@ use log::{
};
use rocket::serde::json::Json;
/*
TODOs(c2m):
- release tracking (locker code?) when txset is released, update to delivered
*/
enum StatusType {
_Delivered,
Delivered,
MultisigMissing,
MulitsigComplete,
Shipped,
@ -28,7 +27,7 @@ enum StatusType {
impl StatusType {
pub fn value(&self) -> String {
match *self {
StatusType::_Delivered => String::from("Delivered"),
StatusType::Delivered => String::from("Delivered"),
StatusType::MultisigMissing => String::from("MultisigMissing"),
StatusType::MulitsigComplete => String::from("MulitsigComplete"),
StatusType::Shipped => String::from("Shipped"),
@ -195,7 +194,7 @@ pub async fn secure_retrieval(orid: &String, signature: &String) -> Order {
}
/// Check for import multisig info, validate block time and that the
///
///
/// order wallet has been funded properly. Update the order to multisig complete
pub async fn validate_order_for_ship(orid: &String) -> bool {
info!("validating order for shipment");
@ -226,14 +225,15 @@ pub async fn validate_order_for_ship(orid: &String) -> bool {
ready_to_ship
}
/// Write encrypted delivery info to lmdb. Once the customer releases the signed txset
///
/// they will have access to this information (tracking number, locker code, etc.)
/// Write encrypted delivery info to lmdb. Once the customer releases the signed
/// txset
///
/// they will have access to this information (tracking number, locker code,
/// etc.)
pub async fn upload_delivery_info(orid: &String, delivery_info: &Vec<u8>) {
info!("uploading delivery info");
let name = i2p::get_destination(None);
let e_delivery_info: Vec<u8> = gpg::encrypt(name, &delivery_info)
.unwrap_or(Vec::new());
let e_delivery_info: Vec<u8> = gpg::encrypt(name, &delivery_info).unwrap_or(Vec::new());
if e_delivery_info.is_empty() {
error!("unable to encrypt delivery info");
}
@ -248,3 +248,42 @@ pub async fn upload_delivery_info(orid: &String, delivery_info: &Vec<u8>) {
db::Interface::async_delete(&s.env, &s.handle, &m_order.orid).await;
db::Interface::async_write(&s.env, &s.handle, &m_order.orid, &Order::to_db(&m_order)).await;
}
/// The vendor will first search for a encrypted multisig message in the form
///
/// txset-{order id}-{.b32.i2p}
pub async fn finalize_order(orid: &String) -> reqres::FinalizeOrderResponse {
info!("finalizing order");
let mut m_order: Order = find(orid);
let s = db::Interface::async_open().await;
let key = format!("{}-{}-{}", message::TXSET_MSIG, orid, &m_order.cid);
let txset = db::Interface::async_read(&s.env, &s.handle, &key).await;
// describe transer to check amount, address and unlock_time
let r_describe: reqres::XmrRpcDescribeTransferResponse =
monero::describe_transfer(&txset).await;
let m_product: Product = product::find(&m_order.pid);
let total: u128 = m_product.price * m_order.quantity;
let description: &reqres::TransferDescription = &r_describe.result.desc[0];
let is_valid_payment: bool = description.amount_out + description.fee >= total
&& description.unlock_time < monero::LockTimeLimit::Blocks.value();
if !is_valid_payment {
return Default::default();
}
let r_submit: reqres::XmrRpcSubmitMultisigResponse =
sign_and_submit_multisig(orid, &txset).await;
if r_submit.result.tx_hash_list.is_empty() {
return Default::default();
}
// lookup delivery info
let delivery_key = format!("delivery-{}", orid);
let r_delivery_info: String = db::Interface::async_read(&s.env, &s.handle, &delivery_key).await;
let delivery_info: Vec<u8> = hex::decode(r_delivery_info).unwrap_or(Vec::new());
// update the order
m_order.status = StatusType::Delivered.value();
db::Interface::async_delete(&s.env, &s.handle, &m_order.orid).await;
db::Interface::async_write(&s.env, &s.handle, &m_order.orid, &Order::to_db(&m_order)).await;
reqres::FinalizeOrderResponse {
orid: String::from(orid),
delivery_info,
}
}

View file

@ -122,6 +122,11 @@ pub struct XmrRpcTransferParams {
pub get_tx_key: bool,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcDescribeTransferParams {
pub multisig_txset: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcSweepAllParams {
pub address: String,
@ -279,6 +284,14 @@ pub struct XmrRpcTransfrerRequest {
pub params: XmrRpcTransferParams,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcDescribeTransfrerRequest {
pub jsonrpc: String,
pub id: String,
pub method: String,
pub params: XmrRpcDescribeTransferParams,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcSweepAllRequest {
pub jsonrpc: String,
@ -459,6 +472,25 @@ pub struct XmrRpcTranferResult {
pub unsigned_txset: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct TransferDescription {
pub amount_in: u128,
pub amount_out: u128,
pub recepients: Vec<Destination>,
pub change_address: String,
pub change_amount: u128,
pub fee: u128,
pub ring_size: u64,
pub unlock_time: u64,
pub dummy_outputs: u64,
pub extra: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcDescribeTranferResult {
pub desc: Vec<TransferDescription>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct KeyImageList {
key_images: Vec<String>,
@ -995,6 +1027,19 @@ impl Default for XmrRpcTransferResponse {
}
}
#[derive(Deserialize, Debug)]
pub struct XmrRpcDescribeTransferResponse {
pub result: XmrRpcDescribeTranferResult,
}
impl Default for XmrRpcDescribeTransferResponse {
fn default() -> Self {
XmrRpcDescribeTransferResponse {
result: XmrRpcDescribeTranferResult { desc: Vec::new() },
}
}
}
#[derive(Deserialize, Debug)]
pub struct XmrRpcSweepAllResponse {
pub result: XmrRpcSweepAllResult,
@ -1110,7 +1155,7 @@ pub struct OrderRequest {
pub cid: String,
pub pid: String,
pub ship_address: Vec<u8>,
pub quantity: u64,
pub quantity: u128,
}
impl Default for OrderRequest {
@ -1162,3 +1207,21 @@ impl Default for SignAndSubmitRequest {
}
}
}
/// Response for the order finalization
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "rocket::serde")]
pub struct FinalizeOrderResponse {
pub orid: String,
/// This is encrypted by the vendors NEVEKO gpg key
pub delivery_info: Vec<u8>,
}
impl Default for FinalizeOrderResponse {
fn default() -> Self {
FinalizeOrderResponse {
orid: utils::empty_string(),
delivery_info: Vec::new(),
}
}
}

View file

@ -1,9 +1,10 @@
use rocket::{
catch,
get,
http::Status,
post,
response::status::Custom,
serde::json::Json, catch,
serde::json::Json,
};
use neveko_core::*;
@ -157,7 +158,31 @@ pub async fn rx_multisig_message(
///
/// Protected: true
#[post("/<orid>")]
pub async fn request_shipment(orid: String, _jwp: proof::PaymentProof) -> Custom<Json<models::Message>> {
pub async fn request_shipment(
orid: String,
_jwp: proof::PaymentProof,
) -> Custom<Json<models::Message>> {
let is_ready: bool = order::validate_order_for_ship(&orid).await;
if !is_ready {
return Custom(Status::BadRequest, Json(Default::default()));
}
Custom(Status::Ok, Json(Default::default()))
}
/// Send tx_data_hex, which is the output from signing
///
/// a multisig transaction from the order wallet to the
///
/// vendor's subaddress. After that the vendor will submit the
///
/// transaction and return the encrypted delivery information.
///
/// Protected: true
#[post("/<orid>")]
pub async fn finalize_order(
orid: String,
_jwp: proof::PaymentProof,
) -> Custom<Json<reqres::FinalizeOrderResponse>> {
let is_ready: bool = order::validate_order_for_ship(&orid).await;
if !is_ready {
return Custom(Status::BadRequest, Json(Default::default()));
@ -175,7 +200,6 @@ pub async fn create_dispute(
Custom(Status::Ok, Json(m_dispute))
}
// Catchers
//----------------------------------------------------------------

View file

@ -16,11 +16,14 @@ async fn rocket() -> _ {
env_logger::init();
utils::start_up().await;
rocket::custom(&config)
.register("/", catchers![
controller::internal_error,
controller::not_found,
controller::payment_required
])
.register(
"/",
catchers![
controller::internal_error,
controller::not_found,
controller::payment_required
],
)
.mount("/multisig/info", routes![controller::get_multisig_info])
.mount("/invoice", routes![controller::gen_invoice])
.mount("/message/rx", routes![controller::rx_message])
@ -34,6 +37,12 @@ async fn rocket() -> _ {
.mount("/xmr/rpc", routes![controller::get_version])
.mount(
"/market",
routes![controller::create_order, controller::get_products, controller::create_dispute],
routes![
controller::create_order,
controller::create_dispute,
controller::get_products,
controller::finalize_order,
controller::request_shipment,
],
)
}