Feature/spend coconut (#1261)

* Add coconut verifier structure for coconut protocol in gateway

* Add endpoint for validator-api cred verification

* Remove unused signature field

* Register new endpoint

* Improve validator-api config handling

* Aggregate verif result from all apis

* Simplify aggregate functions

* Verify cred on apis correctly

* Introduced coconut bandwidth contract to validator client

* Fix rebase double import

* Fix clippy on non-coconut

* Add multisig contract address to validator client

* Refactor Credential struct

* Do bincode magic in the coconut interface

* Implement serialization for credential and remove bindcode

* Fix clippy and don't remove dkg

* Client release funds proposal

* Add wrapper for blinded serial number

Also compare theta with a blinded serial number (in base 58 form)
for future double spend protection.

* Only post blinded serial number to blockchain

* Validator api propose credential spending

* Fix wallet

* Gateway calls proposal creation

* Query for proposal in verify coconut

* Remove db from git

* Verify against proposal description

* Validator apis vote based on verification of cred

* Fix wallet fmt

* Execute the release of funds

* Fix translation between token and bytes

* Update CHANGELOG
This commit is contained in:
Bogdan-Ștefan Neacşu
2022-06-02 16:54:59 +03:00
committed by GitHub
parent a5759ab227
commit 5f7b3db9a4
75 changed files with 1580 additions and 565 deletions
+13 -1
View File
@@ -2,9 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::error::Result;
use validator_client::nymd::TxResponse;
use multisig_contract_common::msg::ProposalResponse;
use validator_client::nymd::{Fee, TxResponse};
#[async_trait]
pub trait Client {
async fn get_tx(&self, tx_hash: &str) -> Result<TxResponse>;
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse>;
async fn propose_release_funds(
&self,
title: String,
blinded_serial_number: String,
voucher_value: u128,
fee: Option<Fee>,
) -> Result<u64>;
async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>)
-> Result<()>;
async fn execute_proposal(&self, proposal_id: u64, fee: Option<Fee>) -> Result<()>;
}
+14
View File
@@ -63,8 +63,22 @@ pub enum CoconutError {
#[error("Error in coconut interface - {0}")]
CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError),
#[error("Could not create proposal for spending credential")]
CreateProposalError,
#[error("Storage error - {0}")]
StorageError(#[from] ValidatorApiStorageError),
#[error("Credentials error - {0}")]
CredentialsError(#[from] credentials::error::Error),
#[error(
"Incorrect credential proposal description. Expected blinded serial number in base 58"
)]
IncorrectProposal,
#[error("Internal error: {0}")]
InternalError(String),
}
impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError {
+98 -11
View File
@@ -14,15 +14,19 @@ use crate::ValidatorApiStorage;
use coconut_interface::{
Attribute, BlindSignRequest, BlindSignRequestBody, BlindedSignature, BlindedSignatureResponse,
KeyPair, Parameters, VerificationKeyResponse,
ExecuteReleaseFundsRequestBody, KeyPair, Parameters, ProposeReleaseFundsRequestBody,
ProposeReleaseFundsResponse, VerificationKey, VerificationKeyResponse, VerifyCredentialBody,
VerifyCredentialResponse,
};
use config::defaults::VALIDATOR_API_VERSION;
use credentials::coconut::params::{
ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm,
};
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::encryption;
use crypto::shared_key::new_ephemeral_shared_key;
use crypto::symmetric::stream_cipher;
use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES};
use getset::{CopyGetters, Getters};
use rand_07::rngs::OsRng;
@@ -30,26 +34,33 @@ use rocket::fairing::AdHoc;
use rocket::serde::json::Json;
use rocket::State as RocketState;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES};
use tokio::sync::Mutex;
use url::Url;
pub struct State {
client: Arc<RwLock<dyn LocalClient + Send + Sync>>,
client: Arc<dyn LocalClient + Send + Sync>,
key_pair: KeyPair,
validator_apis: Vec<Url>,
storage: ValidatorApiStorage,
rng: Arc<Mutex<OsRng>>,
}
impl State {
pub(crate) fn new<C>(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> Self
pub(crate) fn new<C>(
client: C,
key_pair: KeyPair,
validator_apis: Vec<Url>,
storage: ValidatorApiStorage,
) -> Self
where
C: LocalClient + Send + Sync + 'static,
{
let client = Arc::new(RwLock::new(client));
let client = Arc::new(client);
let rng = Arc::new(Mutex::new(OsRng));
Self {
client,
key_pair,
validator_apis,
storage,
rng,
}
@@ -109,6 +120,10 @@ impl State {
Ok(response)
}
}
pub async fn verification_key(&self) -> Result<VerificationKey> {
Ok(obtain_aggregate_verification_key(&self.validator_apis).await?)
}
}
#[derive(Getters, CopyGetters, Debug)]
@@ -135,11 +150,16 @@ impl InternalSignRequest {
}
}
pub fn stage<C>(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> AdHoc
pub fn stage<C>(
client: C,
key_pair: KeyPair,
validator_apis: Vec<Url>,
storage: ValidatorApiStorage,
) -> AdHoc
where
C: LocalClient + Send + Sync + 'static,
{
let state = State::new(client, key_pair, storage);
let state = State::new(client, key_pair, validator_apis, storage);
AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async {
rocket.manage(state).mount(
// this format! is so ugly...
@@ -150,7 +170,10 @@ impl InternalSignRequest {
routes![
post_blind_sign,
get_verification_key,
post_partial_bandwidth_credential
post_partial_bandwidth_credential,
verify_bandwidth_credential,
post_propose_release_funds,
post_execute_release_funds
],
)
})
@@ -183,8 +206,6 @@ pub async fn post_blind_sign(
}
let tx = state
.client
.read()
.await
.get_tx(blind_sign_request_body.tx_hash())
.await?;
let encryption_key = extract_encryption_key(&blind_sign_request_body, tx).await?;
@@ -226,3 +247,69 @@ pub async fn get_verification_key(
state.key_pair.verification_key(),
)))
}
#[post("/verify-bandwidth-credential", data = "<verify_credential_body>")]
pub async fn verify_bandwidth_credential(
verify_credential_body: Json<VerifyCredentialBody>,
state: &RocketState<State>,
) -> Result<Json<VerifyCredentialResponse>> {
let proposal_id = *verify_credential_body.0.proposal_id();
let proposal = state.client.get_proposal(proposal_id).await?;
// Proposal description is the blinded serial number
if !verify_credential_body
.0
.credential()
.has_blinded_serial_number(&proposal.description)?
{
return Err(CoconutError::IncorrectProposal);
}
let verification_key = state.verification_key().await?;
let verification_result = verify_credential_body
.0
.credential()
.verify(&verification_key);
// Vote yes or no on the proposal based on the verification result
state
.client
.vote_proposal(proposal_id, verification_result, None)
.await?;
Ok(Json(VerifyCredentialResponse::new(verification_result)))
}
#[post("/propose-release-funds", data = "<propose_release_funds>")]
pub async fn post_propose_release_funds(
propose_release_funds: Json<ProposeReleaseFundsRequestBody>,
state: &RocketState<State>,
) -> Result<Json<ProposeReleaseFundsResponse>> {
let verification_key = state.verification_key().await?;
if !propose_release_funds
.0
.credential()
.verify(&verification_key)
{
return Err(CoconutError::CreateProposalError);
}
let title = String::from("Create proposal to spend a coconut credential");
let blinded_serial_number = propose_release_funds.0.credential().blinded_serial_number();
let voucher_value = propose_release_funds.0.credential().voucher_value() as u128;
let proposal_id = state
.client
.propose_release_funds(title, blinded_serial_number, voucher_value, None)
.await?;
Ok(Json(ProposeReleaseFundsResponse::new(proposal_id)))
}
#[post("/execute-release-funds", data = "<execute_release_funds>")]
pub async fn post_execute_release_funds(
execute_release_funds: Json<ExecuteReleaseFundsRequestBody>,
state: &RocketState<State>,
) -> Result<Json<()>> {
let proposal_id = *execute_release_funds.0.proposal_id();
state.client.execute_proposal(proposal_id, None).await?;
Ok(Json(()))
}
+43 -3
View File
@@ -15,10 +15,11 @@ use credentials::coconut::params::{
};
use crypto::shared_key::recompute_shared_key;
use crypto::symmetric::stream_cipher;
use multisig_contract_common::msg::ProposalResponse;
use nymcoconut::{
prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters,
};
use validator_client::nymd::{tx::Hash, DeliverTx, Event, Tag, TxResponse};
use validator_client::nymd::{tx::Hash, DeliverTx, Event, Fee, Tag, TxResponse};
use validator_client::validator_api::routes::{
API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL,
COCONUT_ROUTES, COCONUT_VERIFICATION_KEY,
@@ -56,6 +57,37 @@ impl super::client::Client for DummyClient {
.cloned()
.ok_or(CoconutError::TxHashParseError)
}
async fn get_proposal(&self, _proposal_id: u64) -> Result<ProposalResponse> {
todo!()
}
async fn propose_release_funds(
&self,
_title: String,
_blinded_serial_number: String,
_voucher_value: u128,
_fee: Option<Fee>,
) -> Result<u64> {
todo!()
}
async fn vote_proposal(
&self,
_proposal_id: u64,
_vote_yes: bool,
_fee: Option<Fee>,
) -> Result<()> {
todo!()
}
async fn execute_proposal(
&self,
proposal_id: u64,
fee: Option<Fee>,
) -> crate::coconut::error::Result<()> {
todo!()
}
}
pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse {
@@ -87,7 +119,12 @@ async fn check_signer_verif_key(key_pair: KeyPair) {
let nymd_db = Arc::new(RwLock::new(HashMap::new()));
let nymd_client = DummyClient::new(&nymd_db);
let rocket = rocket::build().attach(InternalSignRequest::stage(nymd_client, key_pair, storage));
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage,
));
let client = Client::tracked(rocket)
.await
@@ -172,6 +209,7 @@ async fn signed_before() {
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage.clone(),
));
let client = Client::tracked(rocket)
@@ -231,7 +269,7 @@ async fn state_functions() {
let mut db_dir = std::env::temp_dir();
db_dir.push(&key_pair.verification_key().to_bs58()[..8]);
let storage = ValidatorApiStorage::init(db_dir).await.unwrap();
let state = State::new(nymd_client, key_pair, storage.clone());
let state = State::new(nymd_client, key_pair, vec![], storage.clone());
let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E");
assert!(state.signed_before(&tx_hash).await.unwrap().is_none());
@@ -393,6 +431,7 @@ async fn blind_sign_correct() {
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage.clone(),
));
let client = Client::tracked(rocket)
@@ -465,6 +504,7 @@ async fn signature_test() {
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage.clone(),
));
let client = Client::tracked(rocket)
+52 -23
View File
@@ -2,16 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::config::template::config_template;
use config::defaults::{default_api_endpoints, DEFAULT_NETWORK};
use config::defaults::DEFAULT_NETWORK;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
use url::Url;
#[cfg(feature = "coconut")]
use coconut_interface::{Base58, KeyPair};
mod template;
pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
@@ -82,17 +79,20 @@ impl NymConfig for Config {
}
fn config_directory(&self) -> PathBuf {
self.root_directory().join("config")
self.root_directory().join(self.get_id()).join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory().join("data")
self.root_directory().join(self.get_id()).join("data")
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct Base {
/// ID specifies the human readable ID of this particular validator-api.
id: String,
local_validator: Url,
/// Address of the validator contract managing the network
@@ -105,6 +105,7 @@ pub struct Base {
impl Default for Base {
fn default() -> Self {
Base {
id: String::default(),
local_validator: DEFAULT_LOCAL_VALIDATOR
.parse()
.expect("default local validator is malformed!"),
@@ -128,11 +129,6 @@ pub struct NetworkMonitor {
#[serde(default)]
disabled_credentials_mode: bool,
/// Specifies list of all validators on the network issuing coconut credentials.
/// A special care must be taken to ensure they are in correct order.
/// The list must also contain THIS validator that is running the test
all_validator_apis: Vec<Url>,
/// Specifies the interval at which the network monitor sends the test packets.
#[serde(with = "humantime_serde")]
run_interval: Duration,
@@ -189,8 +185,10 @@ pub struct NetworkMonitor {
}
impl NetworkMonitor {
pub const DB_FILE: &'static str = "credentials_database.db";
fn default_credentials_database_path() -> PathBuf {
Config::default_data_directory(None).join("credentials_database.db")
Config::default_data_directory(None).join(Self::DB_FILE)
}
}
@@ -201,7 +199,6 @@ impl Default for NetworkMonitor {
min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY,
enabled: false,
disabled_credentials_mode: true,
all_validator_apis: default_api_endpoints(),
run_interval: DEFAULT_MONITOR_RUN_INTERVAL,
gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL,
gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE,
@@ -230,8 +227,10 @@ pub struct NodeStatusAPI {
}
impl NodeStatusAPI {
pub const DB_FILE: &'static str = "db.sqlite";
fn default_database_path() -> PathBuf {
Config::default_data_directory(None).join("db.sqlite")
Config::default_data_directory(None).join(Self::DB_FILE)
}
}
@@ -279,15 +278,31 @@ impl Default for Rewarding {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
#[cfg(feature = "coconut")]
pub struct CoconutSigner {
/// Specifies whether rewarding service is enabled in this process.
enabled: bool,
/// Base58 encoded signing keypair
keypair_bs58: String,
/// Path to the signing keypair
keypair_path: PathBuf,
/// Specifies list of all validators on the network issuing coconut credentials.
/// A special care must be taken to ensure they are in correct order.
/// The list must also contain THIS validator that is running the test
all_validator_apis: Vec<Url>,
}
#[cfg(feature = "coconut")]
impl Default for CoconutSigner {
fn default() -> Self {
CoconutSigner {
enabled: false,
keypair_path: PathBuf::default(),
all_validator_apis: config::defaults::default_api_endpoints(),
}
}
}
impl Config {
@@ -295,9 +310,18 @@ impl Config {
Config::default()
}
pub fn with_id(mut self, id: &str) -> Self {
self.base.id = id.to_string();
self.node_status_api.database_path =
Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE);
self.network_monitor.credentials_database_path =
Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE);
self
}
#[cfg(feature = "coconut")]
pub fn keypair(&self) -> KeyPair {
KeyPair::try_from_bs58(self.coconut_signer.keypair_bs58.clone()).unwrap()
pub fn keypair_path(&self) -> PathBuf {
self.coconut_signer.keypair_path.clone()
}
pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self {
@@ -337,13 +361,14 @@ impl Config {
}
#[cfg(feature = "coconut")]
pub fn with_keypair<S: Into<String>>(mut self, keypair_bs58: S) -> Self {
self.coconut_signer.keypair_bs58 = keypair_bs58.into();
pub fn with_keypair_path(mut self, keypair_path: PathBuf) -> Self {
self.coconut_signer.keypair_path = keypair_path;
self
}
#[cfg(feature = "coconut")]
pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec<Url>) -> Self {
self.network_monitor.all_validator_apis = validator_api_urls;
self.coconut_signer.all_validator_apis = validator_api_urls;
self
}
@@ -374,6 +399,10 @@ impl Config {
self
}
pub fn get_id(&self) -> String {
self.base.id.clone()
}
pub fn get_network_monitor_enabled(&self) -> bool {
self.network_monitor.enabled
}
@@ -474,7 +503,7 @@ impl Config {
// fix dead code warnings as this method is only ever used with coconut feature
#[cfg(feature = "coconut")]
pub fn get_all_validator_api_endpoints(&self) -> Vec<Url> {
self.network_monitor.all_validator_apis.clone()
self.coconut_signer.all_validator_apis.clone()
}
// TODO: Remove if still unused
+23 -12
View File
@@ -10,12 +10,18 @@ pub(crate) fn config_template() -> &'static str {
[base]
# ID specifies the human readable ID of this particular validator-api.
id = '{{ base.id }}'
# Validator server to which the API will be getting information about the network.
local_validator = '{{ base.local_validator }}'
# Address of the validator contract managing the network.
mixnet_contract_address = '{{ base.mixnet_contract_address }}'
# Mnemonic used for rewarding and validator interaction
mnemonic = '{{ base.mnemonic }}'
##### network monitor config options #####
[network_monitor]
@@ -26,15 +32,6 @@ enabled = {{ network_monitor.enabled }}
# to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ network_monitor.disabled_credentials_mode }}
# Specifies list of all validators on the network issuing coconut credentials.
# A special care must be taken to ensure they are in correct order.
# The list must also contain THIS validator that is running the test
all_validator_apis = [
{{#each network_monitor.all_validator_apis }}
'{{this}}',
{{/each}}
]
# Specifies the interval at which the network monitor sends the test packets.
run_interval = '{{ network_monitor.run_interval }}'
@@ -92,13 +89,27 @@ database_path = '{{ node_status_api.database_path }}'
# Specifies whether rewarding service is enabled in this process.
enabled = {{ rewarding.enabled }}
# Mnemonic (currently of the network monitor) used for rewarding
mnemonic = '{{ rewarding.mnemonic }}'
# Specifies the minimum percentage of monitor test run data present in order to
# distribute rewards for given interval.
# Note, only values in range 0-100 are valid
minimum_interval_monitor_threshold = {{ rewarding.minimum_interval_monitor_threshold }}
[coconut_signer]
# Specifies whether rewarding service is enabled in this process.
enabled = {{ coconut_signer.enabled }}
# Path to the signing keypair
keypair_path = '{{ coconut_signer.keypair_path }}'
# Specifies list of all validators on the network issuing coconut credentials.
# A special care must be taken to ensure they are in correct order.
# The list must also contain THIS validator that is running the test
all_validator_apis = [
{{#each coconut_signer.all_validator_apis }}
'{{this}}',
{{/each}}
]
"#
}
+45 -32
View File
@@ -23,17 +23,18 @@ use rocket::{Ignite, Rocket};
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors};
use rocket_okapi::mount_endpoints_and_merged_docs;
use rocket_okapi::swagger_ui::make_swagger_ui;
use std::process;
use std::sync::Arc;
use std::time::Duration;
use std::{fs, process};
use tokio::sync::Notify;
use url::Url;
// use validator_client::nymd::SigningNymdClient;
// use validator_client::ValidatorClientError;
use crate::rewarded_set_updater::RewardedSetUpdater;
#[cfg(feature = "coconut")]
use coconut::InternalSignRequest;
#[cfg(feature = "coconut")]
use coconut_interface::{Base58, KeyPair};
use validator_client::nymd::SigningNymdClient;
pub(crate) mod config;
@@ -48,18 +49,19 @@ mod swagger;
#[cfg(feature = "coconut")]
mod coconut;
const ID: &str = "id";
const MONITORING_ENABLED: &str = "enable-monitor";
const REWARDING_ENABLED: &str = "enable-rewarding";
const MIXNET_CONTRACT_ARG: &str = "mixnet-contract";
const MNEMONIC_ARG: &str = "mnemonic";
const WRITE_CONFIG_ARG: &str = "save-config";
const NYMD_VALIDATOR_ARG: &str = "nymd-validator";
const API_VALIDATORS_ARG: &str = "api-validators";
const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
#[cfg(feature = "coconut")]
const API_VALIDATORS_ARG: &str = "api-validators";
#[cfg(feature = "coconut")]
const KEYPAIR_ARG: &str = "keypair";
#[cfg(feature = "coconut")]
const COCONUT_ENABLED: &str = "enable-coconut";
@@ -73,7 +75,8 @@ const REWARDING_MONITOR_THRESHOLD_ARG: &str = "monitor-threshold";
const MIN_MIXNODE_RELIABILITY_ARG: &str = "min_mixnode_reliability";
const MIN_GATEWAY_RELIABILITY_ARG: &str = "min_gateway_reliability";
fn parse_validators(raw: &str) -> Vec<Url> {
#[cfg(feature = "coconut")]
fn parse_validators(raw: &str) -> Vec<url::Url> {
raw.split(',')
.map(|raw_validator| {
raw_validator
@@ -124,6 +127,12 @@ fn parse_args<'a>() -> ArgMatches<'a> {
.version(crate_version!())
.long_version(&*build_details)
.author("Nymtech")
.arg(
Arg::with_name(ID)
.help("Id of the validator-api we want to run")
.long(ID)
.takes_value(true)
)
.arg(
Arg::with_name(MONITORING_ENABLED)
.help("specifies whether a network monitoring is enabled on this API")
@@ -159,12 +168,6 @@ fn parse_args<'a>() -> ArgMatches<'a> {
.long(WRITE_CONFIG_ARG)
.short("w")
)
.arg(
Arg::with_name(API_VALIDATORS_ARG)
.help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered")
.long(API_VALIDATORS_ARG)
.takes_value(true)
)
.arg(
Arg::with_name(REWARDING_MONITOR_THRESHOLD_ARG)
.help("Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval.")
@@ -185,10 +188,16 @@ fn parse_args<'a>() -> ArgMatches<'a> {
.takes_value(true)
.long(KEYPAIR_ARG),
)
.arg(
Arg::with_name(API_VALIDATORS_ARG)
.help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered")
.long(API_VALIDATORS_ARG)
.takes_value(true)
)
.arg(
Arg::with_name(COCONUT_ENABLED)
.help("Flag to indicate whether coconut signer authority is enabled on this API")
.requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG])
.requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG, API_VALIDATORS_ARG])
.long(COCONUT_ENABLED),
);
@@ -236,12 +245,18 @@ fn setup_logging() {
.filter_module("sled", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("_", log::LevelFilter::Warn)
.filter_module("rocket::server", log::LevelFilter::Warn)
.init();
}
fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
if let Some(id) = matches.value_of(ID) {
fs::create_dir_all(Config::default_config_directory(Some(id)))
.expect("Could not create config directory");
fs::create_dir_all(Config::default_data_directory(Some(id)))
.expect("Could not create data directory");
config = config.with_id(id);
}
if matches.is_present(MONITORING_ENABLED) {
config = config.with_network_monitor_enabled(true)
}
@@ -255,6 +270,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
config = config.with_coconut_signer_enabled(true)
}
#[cfg(feature = "coconut")]
if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) {
config = config.with_custom_validator_apis(parse_validators(raw_validators));
}
@@ -311,11 +327,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
#[cfg(feature = "coconut")]
if let Some(keypair_path) = matches.value_of(KEYPAIR_ARG) {
let keypair_bs58 = std::fs::read_to_string(keypair_path)
.unwrap()
.trim()
.to_string();
config = config.with_keypair(keypair_bs58)
config = config.with_keypair_path(keypair_path.into())
}
#[cfg(not(feature = "coconut"))]
@@ -402,7 +414,7 @@ fn expected_monitor_test_runs(config: &Config, interval_length: Duration) -> usi
async fn setup_rocket(
config: &Config,
liftoff_notify: Arc<Notify>,
_nymd_client: Option<Client<SigningNymdClient>>,
_nymd_client: Client<SigningNymdClient>,
) -> Result<Rocket<Ignite>> {
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
let mut rocket = rocket::build();
@@ -434,9 +446,14 @@ async fn setup_rocket(
#[cfg(feature = "coconut")]
let rocket = if config.get_coconut_signer_enabled() {
let keypair_bs58 = fs::read_to_string(config.keypair_path())?
.trim()
.to_string();
let keypair = KeyPair::try_from_bs58(keypair_bs58)?;
rocket.attach(InternalSignRequest::stage(
_nymd_client.expect("Should have a signing client here"),
config.keypair(),
_nymd_client,
keypair,
config.get_all_validator_api_endpoints(),
storage.clone().unwrap(),
))
} else {
@@ -486,10 +503,11 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
let system_version = env!("CARGO_PKG_VERSION");
// try to load config from the file, if it doesn't exist, use default values
let config = match Config::load_from_file(None) {
let id = matches.value_of(ID);
let config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(_) => {
let config_path = Config::default_config_file_path(None)
let config_path = Config::default_config_file_path(id)
.into_os_string()
.into_string()
.unwrap();
@@ -507,11 +525,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
return Ok(());
}
let signing_nymd_client = if matches.is_present(MNEMONIC_ARG) {
Some(Client::new_signing(&config))
} else {
None
};
let signing_nymd_client = Client::new_signing(&config);
let liftoff_notify = Arc::new(Notify::new());
@@ -529,9 +543,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
// if network monitor is disabled, we're not going to be sending any rewarding hence
// we're not starting signing client
if config.get_network_monitor_enabled() {
let nymd_client = signing_nymd_client.expect("We should have a signing client here");
let validator_cache_refresher = ValidatorCacheRefresher::new(
nymd_client.clone(),
signing_nymd_client.clone(),
config.get_caching_interval(),
validator_cache.clone(),
);
@@ -546,7 +559,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
tokio::spawn(async move { uptime_updater.run().await });
let mut rewarded_set_updater =
RewardedSetUpdater::new(nymd_client, validator_cache.clone(), storage).await?;
RewardedSetUpdater::new(signing_nymd_client, validator_cache.clone(), storage).await?;
// spawn rewarded set updater
tokio::spawn(async move { rewarded_set_updater.run().await.unwrap() });
+91 -32
View File
@@ -1,21 +1,28 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use crate::rewarded_set_updater::error::RewardingError;
#[cfg(feature = "coconut")]
use async_trait::async_trait;
use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT};
use mixnet_contract_common::Interval;
use mixnet_contract_common::{
reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond,
IdentityKey, MixNodeBond, MixnodeRewardingStatusResponse, RewardedSetNodeStatus,
};
use serde::Serialize;
#[cfg(feature = "coconut")]
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time::sleep;
use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT};
use mixnet_contract_common::{
reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond,
IdentityKey, Interval, MixNodeBond, MixnodeRewardingStatusResponse, RewardedSetNodeStatus,
};
#[cfg(feature = "coconut")]
use multisig_contract_common::msg::ProposalResponse;
#[cfg(feature = "coconut")]
use validator_client::nymd::{
cosmwasm_client::logs::find_attribute,
traits::{MultisigSigningClient, QueryClient},
};
use validator_client::nymd::{
hash::{Hash, SHA256_HASH_SIZE},
Coin, CosmWasmClient, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient,
@@ -23,6 +30,11 @@ use validator_client::nymd::{
};
use validator_client::ValidatorClientError;
#[cfg(feature = "coconut")]
use crate::coconut::error::CoconutError;
use crate::config::Config;
use crate::rewarded_set_updater::error::RewardingError;
pub(crate) struct Client<C>(pub(crate) Arc<RwLock<validator_client::Client<C>>>);
impl<C> Clone for Client<C> {
@@ -46,14 +58,8 @@ impl Client<QueryNymdClient> {
.parse()
.expect("the mixnet contract address is invalid!");
let client_config = validator_client::Config::new(
network,
nymd_url,
api_url,
Some(mixnet_contract),
None,
None,
);
let client_config = validator_client::Config::new(network, nymd_url, api_url)
.with_mixnode_contract_address(mixnet_contract);
let inner =
validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!");
@@ -80,14 +86,8 @@ impl Client<SigningNymdClient> {
.parse()
.expect("the mnemonic is invalid!");
let client_config = validator_client::Config::new(
network,
nymd_url,
api_url,
Some(mixnet_contract),
None,
None,
);
let client_config = validator_client::Config::new(network, nymd_url, api_url)
.with_mixnode_contract_address(mixnet_contract);
let inner = validator_client::Client::new_signing(client_config, mnemonic)
.expect("Failed to connect to nymd!");
@@ -366,12 +366,7 @@ impl<C> Client<C> {
C: SigningCosmWasmClient + Sync,
M: Serialize + Clone + Send,
{
let contract = self
.0
.read()
.await
.get_mixnet_contract_address()
.ok_or(RewardingError::UnspecifiedContractAddress)?;
let contract = self.0.read().await.get_mixnet_contract_address();
// grab the write lock here so we're sure nothing else is executing anything on the contract
// in the meantime
@@ -420,7 +415,7 @@ impl<C> Client<C> {
#[cfg(feature = "coconut")]
impl<C> crate::coconut::client::Client for Client<C>
where
C: CosmWasmClient + Sync + Send,
C: SigningCosmWasmClient + Sync + Send,
{
async fn get_tx(
&self,
@@ -428,7 +423,71 @@ where
) -> crate::coconut::error::Result<validator_client::nymd::TxResponse> {
let tx_hash = tx_hash
.parse::<validator_client::nymd::tx::Hash>()
.map_err(|_| crate::coconut::error::CoconutError::TxHashParseError)?;
.map_err(|_| CoconutError::TxHashParseError)?;
Ok(self.0.read().await.nymd.get_tx(tx_hash).await?)
}
async fn get_proposal(
&self,
proposal_id: u64,
) -> crate::coconut::error::Result<ProposalResponse> {
Ok(self.0.read().await.nymd.get_proposal(proposal_id).await?)
}
async fn propose_release_funds(
&self,
title: String,
blinded_serial_number: String,
voucher_value: u128,
fee: Option<Fee>,
) -> Result<u64, CoconutError> {
let res = self
.0
.read()
.await
.nymd
.propose_release_funds(title, blinded_serial_number, voucher_value, fee)
.await?;
let proposal_id = u64::from_str(
&find_attribute(&res.logs, "wasm", "proposal_id")
.ok_or_else(|| {
CoconutError::InternalError("No attribute with proposal_id as key".to_string())
})?
.value,
)
.map_err(|_| {
CoconutError::InternalError("proposal_id could not be parsed to u64".to_string())
})?;
Ok(proposal_id)
}
async fn vote_proposal(
&self,
proposal_id: u64,
vote_yes: bool,
fee: Option<Fee>,
) -> Result<(), CoconutError> {
self.0
.read()
.await
.nymd
.vote_proposal(proposal_id, vote_yes, fee)
.await?;
Ok(())
}
async fn execute_proposal(
&self,
proposal_id: u64,
fee: Option<Fee>,
) -> Result<(), CoconutError> {
self.0
.read()
.await
.nymd
.execute_proposal(proposal_id, fee)
.await?;
Ok(())
}
}
@@ -8,9 +8,6 @@ use validator_client::ValidatorClientError;
#[derive(Debug, Error)]
pub enum RewardingError {
#[error("Could not distribute rewards as the contract address was unspecified")]
UnspecifiedContractAddress,
// #[error("There were no mixnodes to reward (network is dead)")]
// NoMixnodesToReward,
#[error("Failed to execute the smart contract - {0}")]