From 07e64d7d348b9b3e66e0f010fe4df7741c2b0170 Mon Sep 17 00:00:00 2001 From: Adam Wierzbicki Date: Mon, 11 Jan 2021 17:31:51 +0100 Subject: [PATCH] Payments: Mainnet/testnet preparation Introduced changes to the payment service and payment driver API necessary for supporting both mainnet and testnet in a single Yagna daemon process. This changes require drivers to be network-aware. Signed-off-by: Adam Wierzbicki --- Cargo.lock | 15 ++- Cargo.toml | 4 +- agent/provider/src/payments/mod.rs | 2 +- agent/provider/src/payments/pricing.rs | 19 ++- agent/provider/src/provider_agent.rs | 23 ++-- agent/provider/src/startup_config.rs | 4 +- core/model/src/driver.rs | 16 ++- core/model/src/payment.rs | 44 +++++-- core/payment-driver/base/src/bus.rs | 17 ++- core/payment-driver/base/src/driver.rs | 5 +- core/payment-driver/dummy/Cargo.toml | 1 + core/payment-driver/dummy/src/lib.rs | 4 +- core/payment-driver/dummy/src/service.rs | 22 +++- core/payment-driver/gnt/Cargo.toml | 1 + core/payment-driver/gnt/src/gnt.rs | 8 +- core/payment-driver/gnt/src/lib.rs | 7 +- core/payment-driver/gnt/src/service.rs | 18 ++- core/payment-driver/zksync/Cargo.toml | 1 + core/payment-driver/zksync/src/driver.rs | 38 ++++-- core/payment-driver/zksync/src/lib.rs | 5 +- core/payment/README.md | 10 +- core/payment/examples/payment_api.rs | 2 +- core/payment/src/accounts.rs | 13 +- core/payment/src/api/accounts.rs | 8 -- core/payment/src/cli.rs | 5 + core/payment/src/error.rs | 12 -- core/payment/src/processor.rs | 146 ++++++++++++++++------- core/payment/src/service.rs | 7 +- 28 files changed, 320 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55546cc46c..9d8639d88b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3302,6 +3302,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -5331,7 +5337,7 @@ version = "0.5.1" source = "git+https://github.com/tworec/serial_test.git?branch=actix_rt_test#6c848541e89262f90ca693d9e1a84c6f3df0953b" dependencies = [ "lazy_static", - "parking_lot 0.10.2", + "parking_lot 0.11.1", "serial_test_derive 0.5.1", ] @@ -6910,7 +6916,7 @@ dependencies = [ [[package]] name = "ya-client" version = "0.4.0" -source = "git+https://github.com/golemfactory/ya-client.git?branch=event-api/master#721259d017a3306af3724eb393aa2f9383d74b9c" +source = "git+https://github.com/golemfactory/ya-client.git?rev=94f4cc5844477374f97dacfa22a53a15f3a59ada#94f4cc5844477374f97dacfa22a53a15f3a59ada" dependencies = [ "awc 1.0.1", "bytes 0.5.6", @@ -6947,7 +6953,7 @@ dependencies = [ [[package]] name = "ya-client-model" version = "0.2.0" -source = "git+https://github.com/golemfactory/ya-client.git?branch=event-api/master#721259d017a3306af3724eb393aa2f9383d74b9c" +source = "git+https://github.com/golemfactory/ya-client.git?rev=94f4cc5844477374f97dacfa22a53a15f3a59ada#94f4cc5844477374f97dacfa22a53a15f3a59ada" dependencies = [ "bigdecimal 0.1.2", "chrono", @@ -7010,6 +7016,7 @@ dependencies = [ "chrono", "futures 0.3.8", "log", + "maplit", "serde_json", "uuid 0.8.1", "ya-core-model", @@ -7096,6 +7103,7 @@ dependencies = [ "lazy_static", "libsqlite3-sys", "log", + "maplit", "num-bigint 0.2.6", "r2d2", "rlp", @@ -7735,6 +7743,7 @@ dependencies = [ "hex", "lazy_static", "log", + "maplit", "num", "serde_json", "tiny-keccak 1.5.0", diff --git a/Cargo.toml b/Cargo.toml index 9c7d7cb12e..0552ed5e13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,8 +172,8 @@ ya-sb-router = { path = "service-bus/router" } ya-sb-util = { path = "service-bus/util" } ## CLIENT -ya-client = { git = "https://github.com/golemfactory/ya-client.git", branch = "event-api/master"} -ya-client-model = { git = "https://github.com/golemfactory/ya-client.git", branch = "event-api/master"} +ya-client = { git = "https://github.com/golemfactory/ya-client.git", rev = "94f4cc5844477374f97dacfa22a53a15f3a59ada"} +ya-client-model = { git = "https://github.com/golemfactory/ya-client.git", rev = "94f4cc5844477374f97dacfa22a53a15f3a59ada"} #ya-client = { path = "../ya-client" } #ya-client-model = { path = "../ya-client/model" } diff --git a/agent/provider/src/payments/mod.rs b/agent/provider/src/payments/mod.rs index d7a3709b84..f65469ecaf 100644 --- a/agent/provider/src/payments/mod.rs +++ b/agent/provider/src/payments/mod.rs @@ -6,4 +6,4 @@ mod pricing; pub use factory::PaymentModelFactory; pub use payments::Payments; -pub use pricing::{LinearPricing, LinearPricingOffer, PricingOffer}; +pub use pricing::{AccountView, LinearPricing, LinearPricingOffer, PricingOffer}; diff --git a/agent/provider/src/payments/pricing.rs b/agent/provider/src/payments/pricing.rs index 644bc41bef..f6454d05b6 100644 --- a/agent/provider/src/payments/pricing.rs +++ b/agent/provider/src/payments/pricing.rs @@ -8,11 +8,26 @@ use ya_client_model::payment::Account; use super::model::{PaymentDescription, PaymentModel}; use crate::market::presets::{Coefficient, Preset}; +#[derive(Clone, Debug)] +pub struct AccountView { + pub address: String, + pub platform: String, +} + +impl From for AccountView { + fn from(account: Account) -> Self { + Self { + address: account.address, + platform: account.platform, + } + } +} + pub trait PricingOffer { fn prices(&self, preset: &Preset) -> Vec<(Coefficient, f64)>; fn build( &self, - accounts: &Vec, + accounts: &Vec, initial_price: f64, prices: Vec<(String, f64)>, ) -> Result; @@ -89,7 +104,7 @@ impl PricingOffer for LinearPricingOffer { fn build( &self, - accounts: &Vec, + accounts: &Vec, initial_price: f64, prices: Vec<(String, f64)>, ) -> Result { diff --git a/agent/provider/src/provider_agent.rs b/agent/provider/src/provider_agent.rs index 5a8834095d..89517d3bf9 100644 --- a/agent/provider/src/provider_agent.rs +++ b/agent/provider/src/provider_agent.rs @@ -5,7 +5,7 @@ use crate::execution::{ use crate::hardware; use crate::market::provider_market::{OfferKind, Shutdown as MarketShutdown, Unsubscribe}; use crate::market::{CreateOffer, Preset, PresetManager, ProviderMarket}; -use crate::payments::{LinearPricingOffer, Payments, PricingOffer}; +use crate::payments::{AccountView, LinearPricingOffer, Payments, PricingOffer}; use crate::startup_config::{FileMonitor, NodeConfig, ProviderConfig, RecvAccount, RunConfig}; use crate::tasks::task_manager::{InitializeTaskManager, TaskManager}; @@ -23,7 +23,6 @@ use std::{fs, io}; use ya_agreement_utils::agreement::TypedArrayPointer; use ya_agreement_utils::*; use ya_client::cli::ProviderApi; -use ya_client_model::payment::Account; use ya_utils_actix::actix_handler::send_message; use ya_utils_path::SwapSave; @@ -34,7 +33,7 @@ pub struct ProviderAgent { task_manager: Addr, presets: PresetManager, hardware: hardware::Manager, - accounts: Vec, + accounts: Vec, } struct GlobalsManager { @@ -127,7 +126,13 @@ impl ProviderAgent { let api = ProviderApi::try_from(&args.api)?; log::info!("Loading payment accounts..."); - let accounts: Vec = api.payment.get_provider_accounts().await?; + let accounts: Vec = api + .payment + .get_provider_accounts() + .await? + .into_iter() + .map(Into::into) + .collect(); log::info!("Payment accounts: {:#?}", accounts); let registry = config.registry()?; registry.validate()?; @@ -171,7 +176,7 @@ impl ProviderAgent { inf_node_info: InfNodeInfo, runner: Addr, market: Addr, - accounts: Vec, + accounts: Vec, ) -> anyhow::Result<()> { if presets.is_empty() { return Err(anyhow!("No Presets were selected. Can't create offers.")); @@ -252,19 +257,19 @@ impl ProviderAgent { node_info } - fn accounts(&self) -> Vec { + fn accounts(&self) -> Vec { let globals = self.globals.get_state(); if let Some(account) = &globals.account { let mut accounts = Vec::new(); if account.platform.is_some() { - let zkaddr = Account { + let zkaddr = AccountView { platform: account.platform.clone().unwrap(), address: account.address.to_lowercase(), }; accounts.push(zkaddr); } else { - for &platform in &["NGNT", "ZK-NGNT"] { - accounts.push(Account { + for &platform in &["erc20-rinkeby-tglm", "zksync-rinkeby-tglm"] { + accounts.push(AccountView { platform: platform.to_string(), address: account.address.to_lowercase(), }) diff --git a/agent/provider/src/startup_config.rs b/agent/provider/src/startup_config.rs index 2697f64f33..b0c013bec6 100644 --- a/agent/provider/src/startup_config.rs +++ b/agent/provider/src/startup_config.rs @@ -112,8 +112,8 @@ impl FromStr for RecvAccount { (Some(driver), Some(addr), None) => { let platform = Some( match driver { - "zksync" | "zk" => "ZK-NGNT", - "eth" | "l1" => "NGTN", + "zksync" | "zk" => "zksync-rinkeby-tglm", + "eth" | "l1" => "erc20-rinkeby-tglm", _ => anyhow::bail!("unknown driver: {}", driver), } .to_string(), diff --git a/core/model/src/driver.rs b/core/model/src/driver.rs index 1ff3601efa..53c02ffd13 100644 --- a/core/model/src/driver.rs +++ b/core/model/src/driver.rs @@ -116,12 +116,24 @@ impl RpcMessage for VerifyPayment { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Init { address: String, + network: Option, + token: Option, mode: AccountMode, } impl Init { - pub fn new(address: String, mode: AccountMode) -> Init { - Init { address, mode } + pub fn new( + address: String, + network: Option, + token: Option, + mode: AccountMode, + ) -> Init { + Init { + address, + network, + token, + mode, + } } pub fn address(&self) -> String { self.address.clone() diff --git a/core/model/src/payment.rs b/core/model/src/payment.rs index 42c6aaa21b..7a981860bc 100644 --- a/core/model/src/payment.rs +++ b/core/model/src/payment.rs @@ -21,6 +21,7 @@ pub mod local { use crate::driver::{AccountMode, PaymentConfirmation}; use bigdecimal::{BigDecimal, Zero}; use chrono::{DateTime, Utc}; + use std::collections::HashMap; use std::fmt::Display; use ya_client_model::NodeId; @@ -129,17 +130,37 @@ pub mod local { #[error("")] pub struct NoError {} // This is needed because () doesn't implement Display + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct Network { + pub default_token: String, + pub tokens: HashMap, // token -> platform + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct DriverDetails { + pub default_network: String, + pub networks: HashMap, + pub recv_init_required: bool, // Is account initialization required for receiving payments + } + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RegisterDriver { pub driver_name: String, - pub platform: String, - pub recv_init_required: bool, // Is account initialization required for receiving payments + pub details: DriverDetails, + } + + #[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] + pub enum RegisterDriverError { + #[error("Invalid default token specified: token={0}, network={1}")] + InvalidDefaultToken(String, String), + #[error("Invalid default network specified: {0}")] + InvalidDefaultNetwork(String), } impl RpcMessage for RegisterDriver { const ID: &'static str = "RegisterDriver"; type Item = (); - type Error = NoError; + type Error = RegisterDriverError; } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -153,9 +174,10 @@ pub mod local { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RegisterAccount { - pub platform: String, pub address: String, pub driver: String, + pub network: String, + pub token: String, pub mode: AccountMode, } @@ -165,6 +187,10 @@ pub mod local { AlreadyRegistered(String, String), #[error("Driver not registered: {0}")] DriverNotRegistered(String), + #[error("Network not supported by driver: network={0}, driver={1}")] + UnsupportedNetwork(String, String), + #[error("Token not supported by driver: token={0}, network={1}, driver={2}")] + UnsupportedToken(String, String, String), #[error("Error while registering account: {0}")] Other(String), } @@ -190,6 +216,7 @@ pub mod local { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NotifyPayment { pub driver: String, + pub platform: String, pub amount: BigDecimal, pub sender: String, pub recipient: String, @@ -287,15 +314,6 @@ pub mod local { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GetAccounts {} - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct Account { - pub platform: String, - pub address: String, - pub driver: String, - pub send: bool, - pub receive: bool, - } - impl RpcMessage for GetAccounts { const ID: &'static str = "GetAccounts"; type Item = Vec; diff --git a/core/payment-driver/base/src/bus.rs b/core/payment-driver/base/src/bus.rs index 8e9bc81815..2008ac835f 100644 --- a/core/payment-driver/base/src/bus.rs +++ b/core/payment-driver/base/src/bus.rs @@ -70,8 +70,11 @@ pub async fn bind_service( log::debug!("Registering driver in payment service..."); let message = payment_srv::RegisterDriver { driver_name: driver.get_name(), - platform: driver.get_platform(), - recv_init_required: driver.recv_init_required(), + details: payment_srv::DriverDetails { + default_network: driver.get_default_network(), + networks: Default::default(), + recv_init_required: driver.recv_init_required(), + }, }; service(payment_srv::BUS_ID).send(message).await?.unwrap(); // Unwrap on purpose because it's NoError log::debug!("Successfully registered driver in payment service."); @@ -102,13 +105,15 @@ pub async fn list_unlocked_identities() -> Result, GenericError> { pub async fn register_account( driver: &(dyn PaymentDriver), address: &str, + network: &str, + token: &str, mode: AccountMode, ) -> Result<(), GenericError> { - let address = address.to_string(); let msg = payment_srv::RegisterAccount { - platform: driver.get_platform(), - address, + address: address.to_string(), driver: driver.get_name(), + network: network.to_string(), + token: token.to_string(), mode, }; service(payment_srv::BUS_ID) @@ -130,12 +135,14 @@ pub async fn sign(node_id: NodeId, payload: Vec) -> Result, GenericE pub async fn notify_payment( driver_name: &str, + platform: &str, order_ids: Vec, details: &PaymentDetails, confirmation: Vec, ) -> Result<(), GenericError> { let msg = payment_srv::NotifyPayment { driver: driver_name.to_string(), + platform: platform.to_string(), amount: details.amount.clone(), sender: details.sender.clone(), recipient: details.recipient.clone(), diff --git a/core/payment-driver/base/src/driver.rs b/core/payment-driver/base/src/driver.rs index 5ecb755216..a94cc68b4c 100644 --- a/core/payment-driver/base/src/driver.rs +++ b/core/payment-driver/base/src/driver.rs @@ -13,8 +13,10 @@ use crate::model::*; // Public revealed uses, required to implement this trait pub use async_trait::async_trait; pub use bigdecimal::BigDecimal; +use std::collections::HashMap; pub use ya_client_model::NodeId; pub use ya_core_model::identity::{event::Event as IdentityEvent, Error as IdentityError}; +pub use ya_core_model::payment::local::Network; #[async_trait(?Send)] pub trait PaymentDriver { @@ -34,7 +36,8 @@ pub trait PaymentDriver { // used by bus to bind service fn get_name(&self) -> String; - fn get_platform(&self) -> String; + fn get_default_network(&self) -> String; + fn get_networks(&self) -> HashMap; fn recv_init_required(&self) -> bool; async fn get_transaction_balance( diff --git a/core/payment-driver/dummy/Cargo.toml b/core/payment-driver/dummy/Cargo.toml index 8aea5c65c7..4841f13f13 100644 --- a/core/payment-driver/dummy/Cargo.toml +++ b/core/payment-driver/dummy/Cargo.toml @@ -19,5 +19,6 @@ bigdecimal = "0.1.0" chrono = { version = "0.4", features = ["serde"] } futures3 = { version = "0.3", features = ["compat"], package = "futures" } log = "0.4.8" +maplit = "1.0" serde_json = "1.0" uuid = { version = "0.8", features = ["v4"] } diff --git a/core/payment-driver/dummy/src/lib.rs b/core/payment-driver/dummy/src/lib.rs index 4415552df2..c753a82ba7 100644 --- a/core/payment-driver/dummy/src/lib.rs +++ b/core/payment-driver/dummy/src/lib.rs @@ -1,7 +1,9 @@ mod service; -pub const PLATFORM_NAME: &'static str = "DUMMY"; pub const DRIVER_NAME: &'static str = "dummy"; +pub const NETWORK_NAME: &'static str = "dummy"; +pub const TOKEN_NAME: &'static str = "GLM"; +pub const PLATFORM_NAME: &'static str = "dummy-glm"; pub struct PaymentDriverService; diff --git a/core/payment-driver/dummy/src/service.rs b/core/payment-driver/dummy/src/service.rs index 9278083e81..8fad374648 100644 --- a/core/payment-driver/dummy/src/service.rs +++ b/core/payment-driver/dummy/src/service.rs @@ -1,7 +1,8 @@ -use crate::{DRIVER_NAME, PLATFORM_NAME}; +use crate::{DRIVER_NAME, NETWORK_NAME, PLATFORM_NAME, TOKEN_NAME}; use actix::Arbiter; use bigdecimal::BigDecimal; use chrono::Utc; +use maplit::hashmap; use std::str::FromStr; use uuid::Uuid; use ya_core_model::driver::*; @@ -25,10 +26,21 @@ pub fn bind_service() { pub async fn register_in_payment_service() -> anyhow::Result<()> { log::debug!("Registering driver in payment service..."); + let details = payment_srv::DriverDetails { + default_network: NETWORK_NAME.to_string(), + networks: hashmap! { + NETWORK_NAME.to_string() => payment_srv::Network { + default_token: TOKEN_NAME.to_string(), + tokens: hashmap! { + TOKEN_NAME.to_string() => PLATFORM_NAME.to_string() + } + } + }, + recv_init_required: false, + }; let message = payment_srv::RegisterDriver { driver_name: DRIVER_NAME.to_string(), - platform: PLATFORM_NAME.to_string(), - recv_init_required: false, + details, }; service(payment_srv::BUS_ID).send(message).await?.unwrap(); // Unwrap on purpose because it's NoError log::debug!("Successfully registered driver in payment service."); @@ -43,9 +55,10 @@ async fn init(_db: (), _caller: String, msg: Init) -> Result let mode = msg.mode(); let msg = payment_srv::RegisterAccount { - platform: PLATFORM_NAME.to_string(), address, driver: DRIVER_NAME.to_string(), + network: "".to_string(), + token: "".to_string(), mode, }; bus::service(payment_srv::BUS_ID) @@ -95,6 +108,7 @@ async fn schedule_payment( let order_id = Uuid::new_v4().to_string(); let msg = payment_srv::NotifyPayment { driver: DRIVER_NAME.to_string(), + platform: PLATFORM_NAME.to_string(), amount: details.amount, sender: details.sender, recipient: details.recipient, diff --git a/core/payment-driver/gnt/Cargo.toml b/core/payment-driver/gnt/Cargo.toml index d07958dbfd..ed3424db2b 100644 --- a/core/payment-driver/gnt/Cargo.toml +++ b/core/payment-driver/gnt/Cargo.toml @@ -30,6 +30,7 @@ hex = "0.4" lazy_static = "1.4" log = "0.4.8" num-bigint = "0.2" +maplit = "1.0" r2d2 = "0.8" rlp = "0.4" secp256k1 = "0.15" diff --git a/core/payment-driver/gnt/src/gnt.rs b/core/payment-driver/gnt/src/gnt.rs index 18c54309e3..1372b9a642 100644 --- a/core/payment-driver/gnt/src/gnt.rs +++ b/core/payment-driver/gnt/src/gnt.rs @@ -4,7 +4,9 @@ pub mod ethereum; pub mod faucet; pub mod sender; -use crate::{GNTDriverError, GNTDriverResult, DRIVER_NAME, PLATFORM_NAME}; +use crate::{ + GNTDriverError, GNTDriverResult, DEFAULT_NETWORK, DEFAULT_PLATFORM, DEFAULT_TOKEN, DRIVER_NAME, +}; use bigdecimal::BigDecimal; use std::future::Future; use std::pin::Pin; @@ -23,6 +25,7 @@ pub(crate) async fn notify_payment( ) -> GNTDriverResult<()> { let msg = payment::NotifyPayment { driver: DRIVER_NAME.to_string(), + platform: DEFAULT_PLATFORM.to_string(), // TODO: Implement multi-network support amount, sender, recipient, @@ -41,9 +44,10 @@ pub(crate) async fn notify_payment( pub(crate) async fn register_account(address: String, mode: AccountMode) -> GNTDriverResult<()> { log::info!("Register account: {}, mode: {:?}", address, mode); let msg = payment::RegisterAccount { - platform: PLATFORM_NAME.to_string(), address, driver: DRIVER_NAME.to_string(), + network: DEFAULT_NETWORK.to_string(), // TODO: Implement multi-network support + token: DEFAULT_TOKEN.to_string(), // TODO: Implement multi-network support mode, }; bus::service(payment::BUS_ID) diff --git a/core/payment-driver/gnt/src/lib.rs b/core/payment-driver/gnt/src/lib.rs index 28f71733c3..992b5f475d 100644 --- a/core/payment-driver/gnt/src/lib.rs +++ b/core/payment-driver/gnt/src/lib.rs @@ -52,8 +52,11 @@ pub type GNTDriverResult = Result; const GNT_FAUCET_GAS: u32 = 90000; const CREATE_FAUCET_FUNCTION: &str = "create"; -pub const PLATFORM_NAME: &'static str = "NGNT"; -pub const DRIVER_NAME: &'static str = "ngnt"; +pub const DRIVER_NAME: &'static str = "erc20"; + +pub const DEFAULT_NETWORK: &'static str = "rinkeby"; +pub const DEFAULT_TOKEN: &'static str = "tGLM"; +pub const DEFAULT_PLATFORM: &'static str = "erc20-rinkeby-tglm"; const ETH_FAUCET_MAX_WAIT: time::Duration = time::Duration::from_secs(180); diff --git a/core/payment-driver/gnt/src/service.rs b/core/payment-driver/gnt/src/service.rs index f090f47b60..c812708ee6 100644 --- a/core/payment-driver/gnt/src/service.rs +++ b/core/payment-driver/gnt/src/service.rs @@ -1,6 +1,7 @@ use crate::processor::GNTDriverProcessor; -use crate::{DRIVER_NAME, PLATFORM_NAME}; +use crate::{DEFAULT_NETWORK, DEFAULT_PLATFORM, DEFAULT_TOKEN, DRIVER_NAME}; use bigdecimal::BigDecimal; +use maplit::hashmap; use ya_core_model::driver::*; use ya_core_model::payment::local as payment_srv; use ya_persistence::executor::DbExecutor; @@ -33,10 +34,21 @@ pub async fn subscribe_to_identity_events() -> anyhow::Result<()> { pub async fn register_in_payment_service() -> anyhow::Result<()> { log::debug!("Registering driver in payment service..."); + let networks = hashmap! { // TODO: Implement multi-network support + DEFAULT_NETWORK.to_string() => payment_srv::Network { + default_token: DEFAULT_TOKEN.to_string(), + tokens: hashmap! { + DEFAULT_TOKEN.to_string() => DEFAULT_PLATFORM.to_string() + } + } + }; let message = payment_srv::RegisterDriver { driver_name: DRIVER_NAME.to_string(), - platform: PLATFORM_NAME.to_string(), - recv_init_required: false, + details: payment_srv::DriverDetails { + default_network: DEFAULT_NETWORK.to_string(), + networks, + recv_init_required: false, + }, }; service(payment_srv::BUS_ID).send(message).await?.unwrap(); // Unwrap on purpose because it's NoError log::debug!("Successfully registered driver in payment service."); diff --git a/core/payment-driver/zksync/Cargo.toml b/core/payment-driver/zksync/Cargo.toml index ec683873be..3dbe7a9b27 100644 --- a/core/payment-driver/zksync/Cargo.toml +++ b/core/payment-driver/zksync/Cargo.toml @@ -18,6 +18,7 @@ hex = "0.4" lazy_static = "1.4" log = "0.4.8" num = { version = "0.2", features = ["serde"] } +maplit = "1.0" serde_json = "^1.0" tiny-keccak = "1.4.2" tokio = { version = "0.2", features = ["full"] } diff --git a/core/payment-driver/zksync/src/driver.rs b/core/payment-driver/zksync/src/driver.rs index 9cf8dd678f..b317a81694 100644 --- a/core/payment-driver/zksync/src/driver.rs +++ b/core/payment-driver/zksync/src/driver.rs @@ -5,7 +5,9 @@ */ // Extrnal crates use chrono::Utc; +use maplit::hashmap; use serde_json; +use std::collections::HashMap; use uuid::Uuid; // Workspace uses @@ -15,14 +17,16 @@ use ya_payment_driver::{ cron::PaymentDriverCron, dao::DbExecutor, db::models::PaymentEntity, - driver::{async_trait, BigDecimal, IdentityError, IdentityEvent, PaymentDriver}, + driver::{async_trait, BigDecimal, IdentityError, IdentityEvent, Network, PaymentDriver}, model::*, utils, }; use ya_utils_futures::timeout::IntoTimeoutFuture; // Local uses -use crate::{dao::ZksyncDao, zksync::wallet, DRIVER_NAME, PLATFORM_NAME}; +use crate::{ + dao::ZksyncDao, zksync::wallet, DEFAULT_NETWORK, DEFAULT_PLATFORM, DEFAULT_TOKEN, DRIVER_NAME, +}; pub struct ZksyncDriver { active_accounts: AccountsRc, @@ -126,8 +130,21 @@ impl PaymentDriver for ZksyncDriver { DRIVER_NAME.to_string() } - fn get_platform(&self) -> String { - PLATFORM_NAME.to_string() + fn get_default_network(&self) -> String { + DEFAULT_NETWORK.to_string() + } + + fn get_networks(&self) -> HashMap { + // TODO: Implement multi-network support + + hashmap! { + DEFAULT_NETWORK.to_string() => Network { + default_token: DEFAULT_TOKEN.to_string(), + tokens: hashmap! { + DEFAULT_TOKEN.to_string() => DEFAULT_PLATFORM.to_string() + } + } + } } fn recv_init_required(&self) -> bool { @@ -161,14 +178,17 @@ impl PaymentDriver for ZksyncDriver { .map_err(GenericError::new)??; let mode = msg.mode(); - bus::register_account(self, &address, mode).await?; + let network = DEFAULT_NETWORK; // TODO: Implement multi-network support + let token = DEFAULT_TOKEN; // TODO: Implement multi-network support + bus::register_account(self, &address, network, token, mode).await?; log::info!( - "Initialised payment account. mode={:?}, address={}, driver={}, platform={}", + "Initialised payment account. mode={:?}, address={}, driver={}, network={}, token={}", mode, &address, DRIVER_NAME, - PLATFORM_NAME + network, + token ); Ok(Ack {}) } @@ -261,8 +281,10 @@ impl PaymentDriverCron for ZksyncDriver { .map(|payment| utils::db_amount_to_big_dec(payment.amount)) .sum::(); let tx_hash = to_confirmation(&details).unwrap(); + let platform = DEFAULT_PLATFORM; // TODO: Implement multi-network support if let Err(e) = - bus::notify_payment(&self.get_name(), order_ids, &details, tx_hash).await + bus::notify_payment(&self.get_name(), platform, order_ids, &details, tx_hash) + .await { log::error!("{}", e) }; diff --git a/core/payment-driver/zksync/src/lib.rs b/core/payment-driver/zksync/src/lib.rs index 243356f8d2..77017fd4f4 100644 --- a/core/payment-driver/zksync/src/lib.rs +++ b/core/payment-driver/zksync/src/lib.rs @@ -5,10 +5,13 @@ */ // Public -pub const PLATFORM_NAME: &'static str = "ZK-NGNT"; pub const DRIVER_NAME: &'static str = "zksync"; pub const ZKSYNC_TOKEN_NAME: &'static str = "GNT"; +pub const DEFAULT_NETWORK: &'static str = "rinkeby"; +pub const DEFAULT_TOKEN: &'static str = "tGLM"; +pub const DEFAULT_PLATFORM: &'static str = "zksync-rinkeby-tglm"; + pub use service::ZksyncService as PaymentDriverService; // Private diff --git a/core/payment/README.md b/core/payment/README.md index 4365e43985..66072a5a50 100644 --- a/core/payment/README.md +++ b/core/payment/README.md @@ -17,11 +17,11 @@ By default the NGNT driver is selected, extra drivers need to be specifically lo You can enable multiple drivers at the same time, use this table for the required feature flags and platform parameters: -|Driver name|Platform name|Feature flag|Public explorer|Local|Testnet|Mainnet| -|-|-|-|-|-|-|-| -|zk-ngnt|ZK-NGNT|`zksync-driver`|[zkscan](https://rinkeby.zkscan.io/)|x|x|| -|ngnt|NGNT|`gnt-driver`|[etherscan](https://rinkeby.etherscan.io/token/0xd94e3dc39d4cad1dad634e7eb585a57a19dc7efe)|x|x|| -|dummy|DUMMY|`dummy-driver`|None|x||| +|Driver name|Feature flag|Public explorer|Local|Testnet|Mainnet| +|-|-|-|-|-|-| +|zksync|`zksync-driver`|[zkscan](https://rinkeby.zkscan.io/)|x|x|| +|erc20|`gnt-driver`|[etherscan](https://rinkeby.etherscan.io/token/0xd94e3dc39d4cad1dad634e7eb585a57a19dc7efe)|x|x|| +|dummy|`dummy-driver`|None|x||| ### Examples: diff --git a/core/payment/examples/payment_api.rs b/core/payment/examples/payment_api.rs index 3353f3cdb0..858db676ca 100644 --- a/core/payment/examples/payment_api.rs +++ b/core/payment/examples/payment_api.rs @@ -213,7 +213,7 @@ async fn main() -> anyhow::Result<()> { } Driver::Zksync => { start_zksync_driver(&db, requestor_account).await?; - (zksync::DRIVER_NAME, zksync::PLATFORM_NAME) + (zksync::DRIVER_NAME, zksync::DEFAULT_PLATFORM) } }; diff --git a/core/payment/src/accounts.rs b/core/payment/src/accounts.rs index ec5283b324..57371994fd 100644 --- a/core/payment/src/accounts.rs +++ b/core/payment/src/accounts.rs @@ -17,6 +17,10 @@ fn accounts_path(data_dir: &Path) -> PathBuf { pub(crate) struct Account { pub driver: String, pub address: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub network: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub token: Option, pub send: bool, pub receive: bool, } @@ -27,7 +31,12 @@ pub(crate) async fn init_account(account: Account) -> anyhow::Result<()> { mode.set(AccountMode::SEND, account.send); mode.set(AccountMode::RECV, account.receive); bus::service(driver_bus_id(account.driver)) - .call(Init::new(account.address, mode)) + .call(Init::new( + account.address, + account.network, + account.token, + mode, + )) .await??; log::debug!("Account initialized."); Ok(()) @@ -73,6 +82,8 @@ pub async fn save_default_account(data_dir: &Path, drivers: Vec) -> anyh .map(|driver| Account { driver, address: default_node_id.to_string(), + network: None, // Use default + token: None, // Use default send: false, receive: true, }) diff --git a/core/payment/src/api/accounts.rs b/core/payment/src/api/accounts.rs index 8ffbaae3a1..d0cfe2c2ed 100644 --- a/core/payment/src/api/accounts.rs +++ b/core/payment/src/api/accounts.rs @@ -28,10 +28,6 @@ async fn get_provider_accounts(id: Identity) -> HttpResponse { .into_iter() .filter(|account| account.receive) .filter(|account| account.address == node_id) // TODO: Implement proper account permission system - .map(|account| Account { - platform: account.platform, - address: account.address, - }) .collect(); response::ok(recv_accounts) } @@ -47,10 +43,6 @@ async fn get_requestor_accounts(id: Identity) -> HttpResponse { .into_iter() .filter(|account| account.send) .filter(|account| account.address == node_id) // TODO: Implement proper account permission system - .map(|account| Account { - platform: account.platform, - address: account.address, - }) .collect(); response::ok(recv_accounts) } diff --git a/core/payment/src/cli.rs b/core/payment/src/cli.rs index f3db1daee1..b8b6c7cc28 100644 --- a/core/payment/src/cli.rs +++ b/core/payment/src/cli.rs @@ -17,6 +17,8 @@ pub enum PaymentCli { provider: bool, #[structopt(long, default_value = DEFAULT_PAYMENT_DRIVER)] driver: String, + #[structopt(long)] + network: Option, }, Status { address: Option, @@ -45,6 +47,7 @@ impl PaymentCli { PaymentCli::Init { address, driver, + network, requestor, provider, } => { @@ -53,6 +56,8 @@ impl PaymentCli { let account = Account { driver, address, + network, + token: None, // Use default -- we don't yet support other tokens than GLM send: requestor, receive: provider, }; diff --git a/core/payment/src/error.rs b/core/payment/src/error.rs index 3e729dc66b..6cda968f10 100644 --- a/core/payment/src/error.rs +++ b/core/payment/src/error.rs @@ -140,16 +140,6 @@ pub mod processor { } } - #[derive(thiserror::Error, Debug)] - #[error("Driver '{0}' not registered.")] - pub struct DriverNotRegistered(String); - - impl DriverNotRegistered { - pub fn new(driver: &str) -> Self { - Self(driver.to_owned()) - } - } - #[derive(thiserror::Error, Debug)] pub enum SchedulePaymentError { #[error("{0}")] @@ -208,8 +198,6 @@ pub mod processor { #[derive(thiserror::Error, Debug)] pub enum NotifyPaymentError { - #[error("{0}")] - DriverNotRegistered(#[from] DriverNotRegistered), #[error("{0}")] Validation(#[from] OrderValidationError), #[error("Service bus error: {0}")] diff --git a/core/payment/src/processor.rs b/core/payment/src/processor.rs index 16c6af9680..59f4291660 100644 --- a/core/payment/src/processor.rs +++ b/core/payment/src/processor.rs @@ -1,7 +1,7 @@ use crate::dao::{ActivityDao, AgreementDao, AllocationDao, OrderDao, PaymentDao}; use crate::error::processor::{ - AccountNotRegistered, DriverNotRegistered, GetStatusError, NotifyPaymentError, - OrderValidationError, SchedulePaymentError, ValidateAllocationError, VerifyPaymentError, + AccountNotRegistered, GetStatusError, NotifyPaymentError, OrderValidationError, + SchedulePaymentError, ValidateAllocationError, VerifyPaymentError, }; use crate::models::order::ReadObj as DbOrder; use bigdecimal::{BigDecimal, Zero}; @@ -10,13 +10,13 @@ use metrics::counter; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::sync::Arc; -use ya_client_model::payment::{ActivityPayment, AgreementPayment, Payment}; +use ya_client_model::payment::{Account, ActivityPayment, AgreementPayment, Payment}; use ya_core_model::driver::{ self, driver_bus_id, AccountMode, PaymentConfirmation, PaymentDetails, ValidateAllocation, }; use ya_core_model::payment::local::{ - Account, NotifyPayment, RegisterAccount, RegisterAccountError, RegisterDriver, SchedulePayment, - UnregisterAccount, UnregisterDriver, + DriverDetails, NotifyPayment, RegisterAccount, RegisterAccountError, RegisterDriver, + RegisterDriverError, SchedulePayment, UnregisterAccount, UnregisterDriver, }; use ya_core_model::payment::public::{SendPayment, BUS_ID}; use ya_net::RemoteEndpoint; @@ -57,44 +57,110 @@ async fn validate_orders( Ok(()) } +#[derive(Clone, Debug)] +struct AccountDetails { + pub driver: String, + pub network: String, + pub token: String, + pub mode: AccountMode, +} + #[derive(Clone, Default)] struct DriverRegistry { - accounts: HashMap<(String, String), (String, AccountMode)>, // (platform, address) -> (driver, mode) - drivers: HashMap, // driver -> (platform, recv_init_required) + accounts: HashMap<(String, String), AccountDetails>, // (platform, address) -> details + drivers: HashMap, // driver_name -> details + platforms: HashMap>, // platform -> (driver_name -> recv_init_required) } impl DriverRegistry { - pub fn register_driver(&mut self, msg: RegisterDriver) { - self.drivers - .insert(msg.driver_name, (msg.platform, msg.recv_init_required)); + pub fn register_driver(&mut self, msg: RegisterDriver) -> Result<(), RegisterDriverError> { + let RegisterDriver { + driver_name, + details, + } = msg; + if !details.networks.contains_key(&details.default_network) { + return Err(RegisterDriverError::InvalidDefaultNetwork( + details.default_network, + )); + } + for (network_name, network) in details.networks.iter() { + if !network.tokens.contains_key(&network.default_token) { + return Err(RegisterDriverError::InvalidDefaultToken( + network.default_token.clone(), + network_name.to_string(), + )); + } + for (token, platform) in network.tokens.iter() { + self.platforms + .entry(platform.clone()) + .or_default() + .insert(driver_name.clone(), details.recv_init_required); + } + } + self.drivers.insert(driver_name, details); + Ok(()) } pub fn unregister_driver(&mut self, msg: UnregisterDriver) { let driver_name = msg.0; - self.drivers.remove(&driver_name); + let details = self.drivers.remove(&driver_name); + if let Some(details) = details { + for (network_name, network) in details.networks.iter() { + for (token, platform) in network.tokens.iter() { + self.platforms + .entry(platform.clone()) + .or_default() + .remove(&driver_name); + } + } + } self.accounts - .retain(|_, (driver, _)| driver != &driver_name); + .retain(|_, details| details.driver != driver_name); } pub fn register_account(&mut self, msg: RegisterAccount) -> Result<(), RegisterAccountError> { - let platform = match self.drivers.get(&msg.driver) { + let driver_details = match self.drivers.get(&msg.driver) { None => return Err(RegisterAccountError::DriverNotRegistered(msg.driver)), - Some((platform, _)) => platform.clone(), + Some(details) => details, + }; + let network = match driver_details.networks.get(&msg.network) { + None => { + return Err(RegisterAccountError::UnsupportedNetwork( + msg.network, + msg.driver, + )) + } + Some(network) => network, + }; + let platform = match network.tokens.get(&msg.token) { + None => { + return Err(RegisterAccountError::UnsupportedToken( + msg.token, + msg.network, + msg.driver, + )) + } + Some(platform) => platform.clone(), }; match self.accounts.entry((platform, msg.address.clone())) { Entry::Occupied(mut entry) => { - let (driver, mode) = entry.get_mut(); - if driver != &msg.driver { + let details = entry.get_mut(); + if details.driver != msg.driver { return Err(RegisterAccountError::AlreadyRegistered( msg.address, - driver.to_string(), + details.driver.to_string(), )); } - *mode |= msg.mode; + details.mode |= msg.mode; } Entry::Vacant(entry) => { - entry.insert((msg.driver, msg.mode)); + entry.insert(AccountDetails { + driver: msg.driver, + network: msg.network, + token: msg.token, + mode: msg.mode, + }); } }; Ok(()) @@ -107,12 +173,14 @@ impl DriverRegistry { pub fn get_accounts(&self) -> Vec { self.accounts .iter() - .map(|((platform, address), (driver, mode))| Account { + .map(|((platform, address), details)| Account { platform: platform.clone(), address: address.clone(), - driver: driver.clone(), - send: mode.contains(AccountMode::SEND), - receive: mode.contains(AccountMode::RECV), + driver: details.driver.clone(), + network: details.network.clone(), + token: details.token.clone(), + send: details.mode.contains(AccountMode::SEND), + receive: details.mode.contains(AccountMode::RECV), }) .collect() } @@ -123,41 +191,29 @@ impl DriverRegistry { address: &str, mode: AccountMode, ) -> Result { - if let Some((driver, reg_mode)) = self + if let Some(details) = self .accounts .get(&(platform.to_owned(), address.to_owned())) { - if reg_mode.contains(mode) { - return Ok(driver.to_owned()); + if details.mode.contains(mode) { + return Ok(details.driver.clone()); } } // If it's recv-only mode or no-mode (i.e. checking status) we can use any driver that // supports the given platform and doesn't require init for receiving. if !mode.contains(AccountMode::SEND) { - for (driver, (driver_platform, recv_init_required)) in self.drivers.iter() { - if driver_platform == platform && !*recv_init_required { - return Ok(driver.to_owned()); + if let Some(drivers) = self.platforms.get(platform) { + for (driver, recv_init_required) in drivers.iter() { + if !*recv_init_required { + return Ok(driver.clone()); + } } } } Err(AccountNotRegistered::new(platform, address, mode)) } - - pub fn platform(&self, driver: &str) -> Result { - match self.drivers.get(driver) { - Some((platform, _)) => Ok(platform.to_owned()), - None => Err(DriverNotRegistered::new(driver)), - } - } - - pub fn recv_init_required(&self, driver: &str) -> Result { - match self.drivers.get(driver) { - Some((_, required)) => Ok(*required), - None => Err(DriverNotRegistered::new(driver)), - } - } } #[derive(Clone)] @@ -174,7 +230,7 @@ impl PaymentProcessor { } } - pub async fn register_driver(&self, msg: RegisterDriver) { + pub async fn register_driver(&self, msg: RegisterDriver) -> Result<(), RegisterDriverError> { self.registry.lock().await.register_driver(msg) } @@ -195,7 +251,7 @@ impl PaymentProcessor { } pub async fn notify_payment(&self, msg: NotifyPayment) -> Result<(), NotifyPaymentError> { - let payment_platform = self.registry.lock().await.platform(&msg.driver)?; + let payment_platform = msg.platform; let payer_addr = msg.sender; let payee_addr = msg.recipient; diff --git a/core/payment/src/service.rs b/core/payment/src/service.rs index 5fc77cfc8d..7a405c411b 100644 --- a/core/payment/src/service.rs +++ b/core/payment/src/service.rs @@ -18,7 +18,7 @@ mod local { use super::*; use crate::dao::*; use std::collections::BTreeMap; - use ya_client_model::payment::DocumentStatus; + use ya_client_model::payment::{Account, DocumentStatus}; use ya_core_model::payment::local::*; use ya_persistence::types::Role; @@ -75,9 +75,8 @@ mod local { processor: PaymentProcessor, sender: String, msg: RegisterDriver, - ) -> Result<(), NoError> { - processor.register_driver(msg).await; - Ok(()) + ) -> Result<(), RegisterDriverError> { + processor.register_driver(msg).await } async fn unregister_driver(