10bf70b22b
* Remove check for bandwidth for incoming packets We should only accunt for packets that the client inputs to the mixnet * Introduce BandwidthController for both types of bandwidth creds * Add some non-coconut token bandwidth handling * Use thiserror for gateway-client lib * Add error handling * Unable to build for wasm for now * Fix wasm strange error * Disable non-coconut credentials for wasm client * Check for status and throw the error up * Send encrypted token cred from client * Gateway receive message and signature validation * Put the correct amount of tokens that were burned * [ci skip] Generate TS types * Eth endpoint and secret key as config parameters * Add eth_endpoint config argument for gateway * Update test as well * Separate panicable code from the safe one * Move some bandwidth controller panics up the call stack * Save contract corresponding to the eth endpoint * Fix template * Pass the web3 interface as well * Made event reads possible in gateway * Add checks for event data * Cosmos contract for double spending prevention * Add workflow for the new contract * Add validator rest URL to config * Rename eth_events to erc20_bridge * Pass cosmos mnemonic as well, and put the nymd client in ERC20Bridge * Call cosmos contract for final verification * Ask for config parameters in cli * Fix various stuff * Increase timeout to allow gateway to check the two chains * Put some logs for the new flow * Set consumed bandwidth invariantly of coconut feature * Fix clippy error * Add non-coconut checks * Use 2018 rust instead of 2021 * More verbose nymd error * Explicitly specify TOKENS_TO_BURN constant * Put eth burn function in a constant * Replace to_vec & append with iter & chain * Test for (de)serialization of TokenCredential * Minor rename * Separate credential creation from bandwidth claiming * Switch from panics to errors when claiming coconut bandwidth * Another append changed to chain * Update QA cosmos contract address * Simplify build/test/clippy separation on coconut feature * Fix bad features arg positioning * Use the start_after in cosmos contract query * Set a limit in line with a range on cosmos queries * Added unit tests for new cosmos contract * Fix bandwidth_remaining comparation * Get remaining bandwidth from gateway * Add contract build flag * Add a useful info log * Use a more robust eth depth for release builds * Include recipt logs in error message * Fix clippy for tests * Use Arc instead of clone * Rename as_bytes to to_bytes * Make signature verification in contract more verbose * Missed rename of paging constant * Fix gateway start with coconut enabled * Rename function to claim_token * Simplify nymd client setup * Check with block buffer on gateway as well * Update comment of double spending protection * Correct contract address * Backup the keypairs used for buying tokens, in case of error cases * Don't take any chances with the gateway timeout * [ci skip] Generate TS types * Updated cosmos contract to latest QA address * Add cli options for eth * Update network monitor timeout value as well Co-authored-by: neacsu <neacsu@users.noreply.github.com>
227 lines
7.2 KiB
Rust
227 lines
7.2 KiB
Rust
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use crate::error::GatewayClientError;
|
|
#[cfg(feature = "coconut")]
|
|
use credentials::coconut::{
|
|
bandwidth::{obtain_signature, prepare_for_spending},
|
|
utils::obtain_aggregate_verification_key,
|
|
};
|
|
#[cfg(not(feature = "coconut"))]
|
|
use credentials::token::bandwidth::TokenCredential;
|
|
#[cfg(not(feature = "coconut"))]
|
|
use crypto::asymmetric::identity;
|
|
use crypto::asymmetric::identity::PublicKey;
|
|
#[cfg(not(feature = "coconut"))]
|
|
use network_defaults::{
|
|
eth_contract::ETH_JSON_ABI, BANDWIDTH_VALUE, ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS,
|
|
ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN,
|
|
};
|
|
#[cfg(not(feature = "coconut"))]
|
|
use rand::rngs::OsRng;
|
|
#[cfg(not(feature = "coconut"))]
|
|
use secp256k1::SecretKey;
|
|
#[cfg(not(feature = "coconut"))]
|
|
use std::io::Write;
|
|
#[cfg(not(feature = "coconut"))]
|
|
use std::str::FromStr;
|
|
#[cfg(not(feature = "coconut"))]
|
|
use web3::{
|
|
contract::{Contract, Options},
|
|
transports::Http,
|
|
types::{Address, Bytes, U256, U64},
|
|
Web3,
|
|
};
|
|
|
|
#[cfg(not(feature = "coconut"))]
|
|
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
|
|
Contract::from_json(
|
|
web3.eth(),
|
|
Address::from(ETH_CONTRACT_ADDRESS),
|
|
json::parse(ETH_JSON_ABI)
|
|
.expect("Invalid json abi")
|
|
.dump()
|
|
.as_bytes(),
|
|
)
|
|
.expect("Invalid json abi")
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct BandwidthController {
|
|
#[cfg(feature = "coconut")]
|
|
validator_endpoints: Vec<url::Url>,
|
|
#[cfg(feature = "coconut")]
|
|
identity: PublicKey,
|
|
#[cfg(not(feature = "coconut"))]
|
|
contract: Contract<Http>,
|
|
#[cfg(not(feature = "coconut"))]
|
|
eth_private_key: SecretKey,
|
|
#[cfg(not(feature = "coconut"))]
|
|
backup_bandwidth_token_keys_dir: std::path::PathBuf,
|
|
}
|
|
|
|
impl BandwidthController {
|
|
#[cfg(feature = "coconut")]
|
|
pub fn new(validator_endpoints: Vec<url::Url>, identity: PublicKey) -> Self {
|
|
BandwidthController {
|
|
validator_endpoints,
|
|
identity,
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "coconut"))]
|
|
pub fn new(
|
|
eth_endpoint: String,
|
|
eth_private_key: String,
|
|
backup_bandwidth_token_keys_dir: std::path::PathBuf,
|
|
) -> Result<Self, GatewayClientError> {
|
|
// Fail early, on invalid url
|
|
let transport =
|
|
Http::new(ð_endpoint).map_err(|_| GatewayClientError::InvalidURL(eth_endpoint))?;
|
|
let web3 = web3::Web3::new(transport);
|
|
// Fail early, on invalid abi
|
|
let contract = eth_contract(web3);
|
|
let eth_private_key = secp256k1::SecretKey::from_str(ð_private_key)
|
|
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
|
|
|
|
Ok(BandwidthController {
|
|
contract,
|
|
eth_private_key,
|
|
backup_bandwidth_token_keys_dir,
|
|
})
|
|
}
|
|
|
|
#[cfg(not(feature = "coconut"))]
|
|
fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
|
|
std::fs::create_dir_all(&self.backup_bandwidth_token_keys_dir)?;
|
|
let file_path = self
|
|
.backup_bandwidth_token_keys_dir
|
|
.join(keypair.public_key().to_base58_string());
|
|
let mut file = std::fs::File::create(file_path)?;
|
|
file.write_all(&keypair.private_key().to_bytes())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "coconut")]
|
|
pub async fn prepare_coconut_credential(
|
|
&self,
|
|
) -> Result<coconut_interface::Credential, GatewayClientError> {
|
|
let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?;
|
|
|
|
let bandwidth_credential =
|
|
obtain_signature(&self.identity.to_bytes(), &self.validator_endpoints).await?;
|
|
// the above would presumably be loaded from a file
|
|
|
|
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
|
|
Ok(prepare_for_spending(
|
|
&self.identity.to_bytes(),
|
|
&bandwidth_credential,
|
|
&verification_key,
|
|
)?)
|
|
}
|
|
|
|
#[cfg(not(feature = "coconut"))]
|
|
pub async fn prepare_token_credential(
|
|
&self,
|
|
gateway_identity: PublicKey,
|
|
) -> Result<TokenCredential, GatewayClientError> {
|
|
let mut rng = OsRng;
|
|
|
|
let kp = identity::KeyPair::new(&mut rng);
|
|
self.backup_keypair(&kp)?;
|
|
|
|
let verification_key = *kp.public_key();
|
|
let signed_verification_key = kp.private_key().sign(&verification_key.to_bytes());
|
|
self.buy_token_credential(verification_key, signed_verification_key)
|
|
.await?;
|
|
|
|
let message: Vec<u8> = verification_key
|
|
.to_bytes()
|
|
.iter()
|
|
.chain(gateway_identity.to_bytes().iter())
|
|
.copied()
|
|
.collect();
|
|
let signature = kp.private_key().sign(&message);
|
|
|
|
Ok(TokenCredential::new(
|
|
verification_key,
|
|
gateway_identity,
|
|
BANDWIDTH_VALUE,
|
|
signature,
|
|
))
|
|
}
|
|
|
|
#[cfg(not(feature = "coconut"))]
|
|
pub async fn buy_token_credential(
|
|
&self,
|
|
verification_key: PublicKey,
|
|
signed_verification_key: identity::Signature,
|
|
) -> Result<(), GatewayClientError> {
|
|
// 0 means a transaction failure, 1 means success
|
|
let confirmations = if cfg!(debug_assertions) {
|
|
1
|
|
} else {
|
|
ETH_MIN_BLOCK_DEPTH
|
|
};
|
|
// 15 seconds per confirmation block + 10 seconds of network overhead
|
|
log::info!(
|
|
"Waiting for Ethereum transaction. This should take about {} seconds",
|
|
confirmations * 15 + 10
|
|
);
|
|
let recipt = self
|
|
.contract
|
|
.signed_call_with_confirmations(
|
|
ETH_BURN_FUNCTION_NAME,
|
|
(
|
|
U256::from(TOKENS_TO_BURN),
|
|
U256::from(&verification_key.to_bytes()),
|
|
Bytes(signed_verification_key.to_bytes().to_vec()),
|
|
),
|
|
Options::default(),
|
|
confirmations,
|
|
&self.eth_private_key,
|
|
)
|
|
.await?;
|
|
if Some(U64::from(0)) == recipt.status {
|
|
Err(GatewayClientError::BurnTokenError(
|
|
web3::Error::InvalidResponse(format!(
|
|
"Transaction status is 0 (failure): {:?}",
|
|
recipt.logs,
|
|
)),
|
|
))
|
|
} else {
|
|
log::info!(
|
|
"Bought bandwidth on Ethereum: {} MB",
|
|
BANDWIDTH_VALUE / 1024 / 1024
|
|
);
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "coconut"))]
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use network_defaults::ETH_EVENT_NAME;
|
|
|
|
#[test]
|
|
fn parse_contract() {
|
|
let transport =
|
|
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
|
|
let web3 = web3::Web3::new(transport);
|
|
// test no panic occurs
|
|
eth_contract(web3);
|
|
}
|
|
|
|
#[test]
|
|
fn check_event_name_constant_against_abi() {
|
|
let transport =
|
|
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
|
|
let web3 = web3::Web3::new(transport);
|
|
let contract = eth_contract(web3);
|
|
assert!(contract.abi().event(ETH_EVENT_NAME).is_ok());
|
|
}
|
|
}
|