1161 lines
40 KiB
Rust
1161 lines
40 KiB
Rust
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use super::InternalSignRequest;
|
|
use crate::coconut::error::{CoconutError, Result};
|
|
use coconut_bandwidth_contract_common::events::{
|
|
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO,
|
|
DEPOSIT_VALUE,
|
|
};
|
|
use coconut_bandwidth_contract_common::spend_credential::{
|
|
SpendCredential, SpendCredentialResponse,
|
|
};
|
|
use coconut_interface::{hash_to_scalar, Credential, VerificationKey};
|
|
use config::defaults::VOUCHER_INFO;
|
|
use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg};
|
|
use credentials::coconut::bandwidth::BandwidthVoucher;
|
|
use credentials::coconut::params::{
|
|
ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm,
|
|
};
|
|
use crypto::shared_key::recompute_shared_key;
|
|
use crypto::symmetric::stream_cipher;
|
|
use nymcoconut::tests::helpers::theta_from_keys_and_attributes;
|
|
use nymcoconut::{
|
|
prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, Parameters,
|
|
};
|
|
use validator_api_requests::coconut::{
|
|
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
|
|
};
|
|
use validator_client::nymd::Coin;
|
|
use validator_client::nymd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, TxResponse};
|
|
use validator_client::validator_api::routes::{
|
|
API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL,
|
|
COCONUT_ROUTES, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL,
|
|
};
|
|
|
|
use crate::coconut::State;
|
|
use crate::ValidatorApiStorage;
|
|
use async_trait::async_trait;
|
|
use coconut_dkg_common::dealer::{
|
|
ContractDealing, DealerDetails, DealerDetailsResponse, DealerType,
|
|
};
|
|
use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX};
|
|
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, TOTAL_DEALINGS};
|
|
use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
|
|
use contracts_common::dealings::ContractSafeBytes;
|
|
use crypto::asymmetric::{encryption, identity};
|
|
use cw3::ProposalResponse;
|
|
use dkg::Threshold;
|
|
use rand_07::rngs::OsRng;
|
|
use rand_07::Rng;
|
|
use rocket::http::Status;
|
|
use rocket::local::asynchronous::Client;
|
|
use std::collections::HashMap;
|
|
use std::str::FromStr;
|
|
use std::sync::{Arc, RwLock};
|
|
use validator_client::nymd::cosmwasm_client::logs::Log;
|
|
use validator_client::nymd::cosmwasm_client::types::ExecuteResult;
|
|
|
|
const TEST_COIN_DENOM: &str = "unym";
|
|
const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct DummyClient {
|
|
validator_address: AccountId,
|
|
tx_db: Arc<RwLock<HashMap<String, TxResponse>>>,
|
|
proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>,
|
|
spent_credential_db: Arc<RwLock<HashMap<String, SpendCredentialResponse>>>,
|
|
|
|
epoch_state: Arc<RwLock<EpochState>>,
|
|
dealer_details: Arc<RwLock<HashMap<String, DealerDetails>>>,
|
|
threshold: Arc<RwLock<Option<Threshold>>>,
|
|
dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
|
|
verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>,
|
|
}
|
|
|
|
impl DummyClient {
|
|
pub fn new(validator_address: AccountId) -> Self {
|
|
Self {
|
|
validator_address,
|
|
tx_db: Arc::new(RwLock::new(HashMap::new())),
|
|
proposal_db: Arc::new(RwLock::new(HashMap::new())),
|
|
spent_credential_db: Arc::new(RwLock::new(HashMap::new())),
|
|
epoch_state: Arc::new(RwLock::new(EpochState::default())),
|
|
dealer_details: Arc::new(RwLock::new(HashMap::new())),
|
|
threshold: Arc::new(RwLock::new(None)),
|
|
dealings: Arc::new(RwLock::new(HashMap::new())),
|
|
verification_share: Arc::new(RwLock::new(HashMap::new())),
|
|
}
|
|
}
|
|
|
|
pub fn with_tx_db(mut self, tx_db: &Arc<RwLock<HashMap<String, TxResponse>>>) -> Self {
|
|
self.tx_db = Arc::clone(tx_db);
|
|
self
|
|
}
|
|
|
|
pub fn with_proposal_db(
|
|
mut self,
|
|
proposal_db: &Arc<RwLock<HashMap<u64, ProposalResponse>>>,
|
|
) -> Self {
|
|
self.proposal_db = Arc::clone(proposal_db);
|
|
self
|
|
}
|
|
|
|
pub fn with_spent_credential_db(
|
|
mut self,
|
|
spent_credential_db: &Arc<RwLock<HashMap<String, SpendCredentialResponse>>>,
|
|
) -> Self {
|
|
self.spent_credential_db = Arc::clone(spent_credential_db);
|
|
self
|
|
}
|
|
|
|
pub fn _with_epoch_state(mut self, epoch_state: &Arc<RwLock<EpochState>>) -> Self {
|
|
self.epoch_state = Arc::clone(epoch_state);
|
|
self
|
|
}
|
|
|
|
pub fn with_dealer_details(
|
|
mut self,
|
|
dealer_details: &Arc<RwLock<HashMap<String, DealerDetails>>>,
|
|
) -> Self {
|
|
self.dealer_details = Arc::clone(dealer_details);
|
|
self
|
|
}
|
|
|
|
pub fn with_threshold(mut self, threshold: &Arc<RwLock<Option<Threshold>>>) -> Self {
|
|
self.threshold = Arc::clone(threshold);
|
|
self
|
|
}
|
|
|
|
pub fn with_dealings(
|
|
mut self,
|
|
dealings: &Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
|
|
) -> Self {
|
|
self.dealings = Arc::clone(dealings);
|
|
self
|
|
}
|
|
|
|
pub fn with_verification_share(
|
|
mut self,
|
|
verification_share: &Arc<RwLock<HashMap<String, ContractVKShare>>>,
|
|
) -> Self {
|
|
self.verification_share = Arc::clone(verification_share);
|
|
self
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl super::client::Client for DummyClient {
|
|
async fn address(&self) -> AccountId {
|
|
self.validator_address.clone()
|
|
}
|
|
|
|
async fn get_tx(&self, tx_hash: &str) -> Result<TxResponse> {
|
|
self.tx_db
|
|
.read()
|
|
.unwrap()
|
|
.get(tx_hash)
|
|
.cloned()
|
|
.ok_or(CoconutError::TxHashParseError)
|
|
}
|
|
|
|
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse> {
|
|
self.proposal_db
|
|
.read()
|
|
.unwrap()
|
|
.get(&proposal_id)
|
|
.cloned()
|
|
.ok_or(CoconutError::IncorrectProposal {
|
|
reason: String::from("proposal not found"),
|
|
})
|
|
}
|
|
|
|
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>> {
|
|
Ok(self.proposal_db.read().unwrap().values().cloned().collect())
|
|
}
|
|
|
|
async fn get_spent_credential(
|
|
&self,
|
|
blinded_serial_number: String,
|
|
) -> Result<SpendCredentialResponse> {
|
|
self.spent_credential_db
|
|
.read()
|
|
.unwrap()
|
|
.get(&blinded_serial_number)
|
|
.cloned()
|
|
.ok_or(CoconutError::InvalidCredentialStatus {
|
|
status: String::from("spent credential not found"),
|
|
})
|
|
}
|
|
|
|
async fn get_current_epoch_state(&self) -> Result<EpochState> {
|
|
Ok(*self.epoch_state.read().unwrap())
|
|
}
|
|
|
|
async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>> {
|
|
Ok(*self.threshold.read().unwrap())
|
|
}
|
|
|
|
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
|
|
Ok(DealerDetailsResponse {
|
|
details: self
|
|
.dealer_details
|
|
.read()
|
|
.unwrap()
|
|
.get(self.validator_address.as_ref())
|
|
.cloned(),
|
|
dealer_type: DealerType::Current,
|
|
})
|
|
}
|
|
|
|
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>> {
|
|
Ok(self
|
|
.dealer_details
|
|
.read()
|
|
.unwrap()
|
|
.values()
|
|
.cloned()
|
|
.collect())
|
|
}
|
|
|
|
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>> {
|
|
Ok(self
|
|
.dealings
|
|
.read()
|
|
.unwrap()
|
|
.iter()
|
|
.map(|(dealer, dealings)| ContractDealing {
|
|
dealing: dealings.get(idx).unwrap().clone(),
|
|
dealer: Addr::unchecked(dealer),
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
async fn get_verification_key_shares(&self) -> Result<Vec<ContractVKShare>> {
|
|
Ok(self
|
|
.verification_share
|
|
.read()
|
|
.unwrap()
|
|
.values()
|
|
.cloned()
|
|
.collect())
|
|
}
|
|
|
|
async fn vote_proposal(
|
|
&self,
|
|
proposal_id: u64,
|
|
vote_yes: bool,
|
|
_fee: Option<Fee>,
|
|
) -> Result<()> {
|
|
if let Some(proposal) = self.proposal_db.write().unwrap().get_mut(&proposal_id) {
|
|
// for now, just suppose that first vote is honest
|
|
if proposal.status == cw3::Status::Open {
|
|
if vote_yes {
|
|
proposal.status = cw3::Status::Passed;
|
|
} else {
|
|
proposal.status = cw3::Status::Rejected;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn execute_proposal(&self, proposal_id: u64) -> Result<()> {
|
|
self.proposal_db
|
|
.write()
|
|
.unwrap()
|
|
.entry(proposal_id)
|
|
.and_modify(|prop| {
|
|
if prop.status == cw3::Status::Passed {
|
|
prop.status = cw3::Status::Executed
|
|
}
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
async fn register_dealer(
|
|
&self,
|
|
bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
|
|
announce_address: String,
|
|
) -> Result<ExecuteResult> {
|
|
let assigned_index = OsRng.gen();
|
|
self.dealer_details.write().unwrap().insert(
|
|
self.validator_address.to_string(),
|
|
DealerDetails {
|
|
address: Addr::unchecked(self.validator_address.to_string()),
|
|
bte_public_key_with_proof,
|
|
announce_address,
|
|
assigned_index,
|
|
},
|
|
);
|
|
Ok(ExecuteResult {
|
|
logs: vec![Log {
|
|
msg_index: 0,
|
|
events: vec![cosmwasm_std::Event::new("wasm")
|
|
.add_attribute(NODE_INDEX, assigned_index.to_string())],
|
|
}],
|
|
data: Default::default(),
|
|
transaction_hash: Hash::new([0; 32]),
|
|
gas_info: Default::default(),
|
|
})
|
|
}
|
|
|
|
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult> {
|
|
self.dealings
|
|
.write()
|
|
.unwrap()
|
|
.entry(self.validator_address.to_string())
|
|
.and_modify(|v| {
|
|
if v.len() < TOTAL_DEALINGS {
|
|
v.push(dealing_bytes.clone())
|
|
}
|
|
})
|
|
.or_insert_with(|| vec![dealing_bytes]);
|
|
|
|
Ok(ExecuteResult {
|
|
logs: vec![],
|
|
data: Default::default(),
|
|
transaction_hash: Hash::new([0; 32]),
|
|
gas_info: Default::default(),
|
|
})
|
|
}
|
|
|
|
async fn submit_verification_key_share(
|
|
&self,
|
|
share: VerificationKeyShare,
|
|
) -> Result<ExecuteResult> {
|
|
let dealer_details = self
|
|
.dealer_details
|
|
.read()
|
|
.unwrap()
|
|
.get(self.validator_address.as_ref())
|
|
.unwrap()
|
|
.clone();
|
|
self.verification_share.write().unwrap().insert(
|
|
self.validator_address.to_string(),
|
|
ContractVKShare {
|
|
share,
|
|
announce_address: dealer_details.announce_address.clone(),
|
|
node_index: dealer_details.assigned_index,
|
|
owner: Addr::unchecked(self.validator_address.to_string()),
|
|
verified: false,
|
|
},
|
|
);
|
|
let proposal_id = OsRng.gen();
|
|
let verify_vk_share_req = coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare {
|
|
owner: Addr::unchecked(self.validator_address.as_ref()),
|
|
};
|
|
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
|
contract_addr: String::new(),
|
|
msg: to_binary(&verify_vk_share_req).unwrap(),
|
|
funds: vec![],
|
|
});
|
|
let proposal = ProposalResponse {
|
|
id: proposal_id,
|
|
title: String::new(),
|
|
description: String::new(),
|
|
msgs: vec![verify_vk_share_msg],
|
|
status: cw3::Status::Open,
|
|
expires: cw_utils::Expiration::Never {},
|
|
threshold: cw_utils::ThresholdResponse::AbsolutePercentage {
|
|
percentage: Decimal::from_ratio(2u32, 3u32),
|
|
total_weight: 100,
|
|
},
|
|
};
|
|
self.proposal_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(proposal_id, proposal);
|
|
Ok(ExecuteResult {
|
|
logs: vec![Log {
|
|
msg_index: 0,
|
|
events: vec![cosmwasm_std::Event::new("wasm")
|
|
.add_attribute(DKG_PROPOSAL_ID, proposal_id.to_string())],
|
|
}],
|
|
data: Default::default(),
|
|
transaction_hash: Hash::new([0; 32]),
|
|
gas_info: Default::default(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct DummyCommunicationChannel {
|
|
aggregated_verification_key: VerificationKey,
|
|
}
|
|
|
|
impl DummyCommunicationChannel {
|
|
pub fn new(aggregated_verification_key: VerificationKey) -> Self {
|
|
DummyCommunicationChannel {
|
|
aggregated_verification_key,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl super::comm::APICommunicationChannel for DummyCommunicationChannel {
|
|
async fn aggregated_verification_key(&self) -> Result<VerificationKey> {
|
|
Ok(self.aggregated_verification_key.clone())
|
|
}
|
|
}
|
|
|
|
pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse {
|
|
TxResponse {
|
|
hash: Hash::from_str(tx_hash).unwrap(),
|
|
height: Default::default(),
|
|
index: 0,
|
|
tx_result: DeliverTx {
|
|
code: Default::default(),
|
|
data: Default::default(),
|
|
log: Default::default(),
|
|
info: Default::default(),
|
|
gas_wanted: Default::default(),
|
|
gas_used: Default::default(),
|
|
events: vec![],
|
|
codespace: Default::default(),
|
|
},
|
|
tx: vec![].into(),
|
|
proof: None,
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn signed_before() {
|
|
let tx_hash =
|
|
Hash::from_str("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E").unwrap();
|
|
let tx_entry = tx_entry_fixture(&tx_hash.to_string());
|
|
let signature = String::from(
|
|
"2DHbEZ6pzToGpsAXJrqJi7Wj1pAXeT18283q2YEEyNH5gTymwRozWBdja6SMAVt1dyYmUnM4ZNhsJ4wxZyGh4Z6J",
|
|
);
|
|
|
|
let params = Parameters::new(4).unwrap();
|
|
let mut rng = OsRng;
|
|
let voucher = BandwidthVoucher::new(
|
|
¶ms,
|
|
"1234".to_string(),
|
|
VOUCHER_INFO.to_string(),
|
|
tx_hash,
|
|
identity::PrivateKey::from_base58_string(
|
|
identity::KeyPair::new(&mut rng)
|
|
.private_key()
|
|
.to_base58_string(),
|
|
)
|
|
.unwrap(),
|
|
encryption::PrivateKey::from_bytes(
|
|
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
|
|
)
|
|
.unwrap(),
|
|
);
|
|
let (_, blind_sign_req) = prepare_blind_sign(
|
|
¶ms,
|
|
&voucher.get_private_attributes(),
|
|
&voucher.get_public_attributes(),
|
|
)
|
|
.unwrap();
|
|
|
|
let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0);
|
|
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 tx_db = Arc::new(RwLock::new(HashMap::new()));
|
|
tx_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(tx_hash.to_string(), tx_entry.clone());
|
|
let nymd_client =
|
|
DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap())
|
|
.with_tx_db(&tx_db);
|
|
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
|
|
let staged_key_pair = crate::coconut::KeyPair::new();
|
|
staged_key_pair.set(key_pair).await;
|
|
|
|
let rocket = rocket::build().attach(InternalSignRequest::stage(
|
|
nymd_client,
|
|
TEST_COIN_DENOM.to_string(),
|
|
staged_key_pair,
|
|
comm_channel,
|
|
storage.clone(),
|
|
));
|
|
let client = Client::tracked(rocket)
|
|
.await
|
|
.expect("valid rocket instance");
|
|
|
|
let request_body = BlindSignRequestBody::new(
|
|
&blind_sign_req,
|
|
tx_hash.to_string(),
|
|
signature.clone(),
|
|
&voucher.get_public_attributes(),
|
|
voucher.get_public_attributes_plain(),
|
|
4,
|
|
);
|
|
|
|
let encrypted_signature = vec![1, 2, 3, 4];
|
|
let remote_key = [42; 32];
|
|
let expected_response = BlindedSignatureResponse::new(encrypted_signature, remote_key);
|
|
storage
|
|
.insert_blinded_signature_response(
|
|
&tx_hash.to_string(),
|
|
&expected_response.to_base58_string(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_BLIND_SIGN
|
|
))
|
|
.json(&request_body)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
// This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893
|
|
// let blinded_signature_response = response
|
|
// .into_json::<BlindedSignatureResponse>()
|
|
// .await
|
|
// .unwrap();
|
|
let blinded_signature_response =
|
|
serde_json::from_str::<BlindedSignatureResponse>(&response.into_string().await.unwrap())
|
|
.unwrap();
|
|
assert_eq!(
|
|
blinded_signature_response.to_bytes(),
|
|
expected_response.to_bytes()
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn state_functions() {
|
|
let nymd_client =
|
|
DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap());
|
|
let params = Parameters::new(4).unwrap();
|
|
let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0);
|
|
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 comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
|
|
let staged_key_pair = crate::coconut::KeyPair::new();
|
|
staged_key_pair.set(key_pair).await;
|
|
let state = State::new(
|
|
nymd_client,
|
|
TEST_COIN_DENOM.to_string(),
|
|
staged_key_pair,
|
|
comm_channel,
|
|
storage.clone(),
|
|
);
|
|
|
|
let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E");
|
|
assert!(state.signed_before(&tx_hash).await.unwrap().is_none());
|
|
|
|
let encrypted_signature = vec![1, 2, 3, 4];
|
|
let remote_key = [42; 32];
|
|
let expected_response = BlindedSignatureResponse::new(encrypted_signature, remote_key);
|
|
storage
|
|
.insert_blinded_signature_response(&tx_hash, &expected_response.to_base58_string())
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(
|
|
state
|
|
.signed_before(&tx_hash)
|
|
.await
|
|
.unwrap()
|
|
.unwrap()
|
|
.to_bytes(),
|
|
expected_response.to_bytes()
|
|
);
|
|
|
|
let encryption_keypair = crypto::asymmetric::encryption::KeyPair::new(&mut OsRng);
|
|
let blinded_signature = BlindedSignature::from_bytes(&[
|
|
183, 217, 166, 113, 40, 123, 74, 25, 72, 31, 136, 19, 125, 95, 217, 228, 96, 113, 25, 240,
|
|
12, 102, 125, 11, 174, 20, 216, 82, 192, 71, 27, 194, 48, 20, 17, 95, 243, 179, 82, 21, 57,
|
|
143, 101, 19, 22, 186, 147, 13, 147, 238, 39, 119, 15, 36, 251, 131, 250, 38, 185, 113,
|
|
187, 40, 227, 107, 134, 190, 123, 183, 126, 176, 226, 173, 147, 137, 17, 175, 13, 115, 78,
|
|
222, 119, 93, 146, 116, 229, 0, 152, 51, 232, 2, 102, 204, 147, 202, 254, 243,
|
|
])
|
|
.unwrap();
|
|
// Check that the new payload is not stored if there was already something signed for tx_hash
|
|
assert_eq!(
|
|
state
|
|
.encrypt_and_store(
|
|
&tx_hash,
|
|
encryption_keypair.public_key(),
|
|
&blinded_signature,
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.to_bytes(),
|
|
expected_response.to_bytes()
|
|
);
|
|
|
|
// And use a new hash to store a new signature
|
|
let tx_hash = String::from("97D64C38D6601B1F0FD3A82E20D252685CB7A210AFB0261018590659AB82B0BF");
|
|
let response = state
|
|
.encrypt_and_store(
|
|
&tx_hash,
|
|
encryption_keypair.public_key(),
|
|
&blinded_signature,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let remote_key =
|
|
crypto::asymmetric::encryption::PublicKey::from_bytes(&response.remote_key).unwrap();
|
|
|
|
let encryption_key = recompute_shared_key::<
|
|
ValidatorApiCredentialEncryptionAlgorithm,
|
|
ValidatorApiCredentialHkdfAlgorithm,
|
|
>(&remote_key, encryption_keypair.private_key());
|
|
let zero_iv = stream_cipher::zero_iv::<ValidatorApiCredentialEncryptionAlgorithm>();
|
|
let blinded_signature_bytes = stream_cipher::decrypt::<ValidatorApiCredentialEncryptionAlgorithm>(
|
|
&encryption_key,
|
|
&zero_iv,
|
|
&response.encrypted_signature,
|
|
);
|
|
|
|
let received_blinded_signature =
|
|
BlindedSignature::from_bytes(&blinded_signature_bytes).unwrap();
|
|
assert_eq!(
|
|
blinded_signature.to_bytes(),
|
|
received_blinded_signature.to_bytes()
|
|
);
|
|
|
|
// Check that the same value for tx_hash is returned
|
|
|
|
let other_signature = BlindedSignature::from_bytes(&[
|
|
183, 217, 166, 113, 40, 123, 74, 25, 72, 31, 136, 19, 125, 95, 217, 228, 96, 113, 25, 240,
|
|
12, 102, 125, 11, 174, 20, 216, 82, 192, 71, 27, 194, 48, 20, 17, 95, 243, 179, 82, 21, 57,
|
|
143, 101, 19, 22, 186, 147, 13, 131, 236, 38, 138, 192, 235, 169, 142, 176, 118, 153, 238,
|
|
141, 91, 94, 139, 168, 214, 17, 250, 96, 206, 139, 89, 139, 87, 31, 8, 106, 171, 8, 140,
|
|
201, 158, 18, 152, 24, 98, 153, 170, 141, 35, 190, 200, 19, 148, 71, 197,
|
|
])
|
|
.unwrap();
|
|
assert_eq!(
|
|
state
|
|
.encrypt_and_store(&tx_hash, encryption_keypair.public_key(), &other_signature,)
|
|
.await
|
|
.unwrap()
|
|
.to_bytes(),
|
|
response.to_bytes()
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn blind_sign_correct() {
|
|
let tx_hash =
|
|
Hash::from_str("7C41AF8266D91DE55E1C8F4712E6A952A165ED3D8C27C7B00428CBD0DE00A52B").unwrap();
|
|
|
|
let params = Parameters::new(4).unwrap();
|
|
let mut rng = OsRng;
|
|
let voucher = BandwidthVoucher::new(
|
|
¶ms,
|
|
"1234".to_string(),
|
|
VOUCHER_INFO.to_string(),
|
|
tx_hash,
|
|
identity::PrivateKey::from_base58_string(
|
|
identity::KeyPair::new(&mut rng)
|
|
.private_key()
|
|
.to_base58_string(),
|
|
)
|
|
.unwrap(),
|
|
encryption::PrivateKey::from_bytes(
|
|
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
|
|
)
|
|
.unwrap(),
|
|
);
|
|
|
|
let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0);
|
|
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 tx_db = Arc::new(RwLock::new(HashMap::new()));
|
|
|
|
let mut tx_entry = tx_entry_fixture(&tx_hash.to_string());
|
|
tx_entry.tx_result.events.push(Event {
|
|
type_str: format!("wasm-{}", DEPOSITED_FUNDS_EVENT_TYPE),
|
|
attributes: vec![],
|
|
});
|
|
tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![
|
|
Tag {
|
|
key: DEPOSIT_VALUE.parse().unwrap(),
|
|
value: "1234".parse().unwrap(),
|
|
},
|
|
Tag {
|
|
key: DEPOSIT_INFO.parse().unwrap(),
|
|
value: VOUCHER_INFO.parse().unwrap(),
|
|
},
|
|
Tag {
|
|
key: DEPOSIT_IDENTITY_KEY.parse().unwrap(),
|
|
value: "64auwDkWan7R8yH1Mwe9dS4qXgrDBCUNDg3Q4KFnd2P5"
|
|
.parse()
|
|
.unwrap(),
|
|
},
|
|
Tag {
|
|
key: DEPOSIT_ENCRYPTION_KEY.parse().unwrap(),
|
|
value: "HxnTpWTkgigSTAysVKLE8pEiUULHdTT1BxFfzfJvQRi6"
|
|
.parse()
|
|
.unwrap(),
|
|
},
|
|
];
|
|
tx_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(tx_hash.to_string(), tx_entry.clone());
|
|
let nymd_client =
|
|
DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap())
|
|
.with_tx_db(&tx_db);
|
|
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
|
|
let staged_key_pair = crate::coconut::KeyPair::new();
|
|
staged_key_pair.set(key_pair).await;
|
|
|
|
let rocket = rocket::build().attach(InternalSignRequest::stage(
|
|
nymd_client,
|
|
TEST_COIN_DENOM.to_string(),
|
|
staged_key_pair,
|
|
comm_channel,
|
|
storage.clone(),
|
|
));
|
|
let client = Client::tracked(rocket)
|
|
.await
|
|
.expect("valid rocket instance");
|
|
|
|
// hard-coded values, that generate a correct signature
|
|
let blind_sign_req = BlindSignRequest::from_bytes(&[
|
|
176, 113, 19, 237, 218, 252, 113, 20, 225, 238, 59, 88, 217, 45, 233, 178, 65, 28, 242, 0,
|
|
222, 48, 110, 216, 26, 111, 51, 235, 61, 74, 200, 15, 130, 245, 45, 170, 155, 190, 156, 77,
|
|
180, 142, 29, 63, 15, 224, 150, 31, 139, 24, 65, 175, 143, 153, 11, 203, 33, 16, 152, 22,
|
|
221, 203, 99, 233, 208, 142, 161, 194, 46, 227, 177, 96, 119, 30, 175, 69, 104, 14, 2, 191,
|
|
26, 94, 30, 165, 15, 28, 40, 176, 1, 78, 253, 79, 20, 137, 102, 74, 2, 0, 0, 0, 0, 0, 0, 0,
|
|
131, 133, 112, 115, 53, 98, 58, 166, 240, 70, 185, 210, 203, 12, 114, 66, 180, 38, 139, 12,
|
|
187, 45, 250, 201, 68, 102, 159, 172, 218, 124, 151, 23, 172, 18, 216, 122, 246, 7, 185,
|
|
76, 20, 167, 123, 122, 152, 241, 175, 226, 176, 8, 170, 70, 140, 252, 36, 130, 67, 204,
|
|
111, 116, 107, 92, 200, 77, 252, 31, 138, 18, 10, 215, 165, 243, 95, 199, 193, 61, 200,
|
|
187, 22, 198, 109, 213, 145, 71, 171, 132, 174, 68, 105, 248, 0, 115, 50, 55, 199, 84, 67,
|
|
16, 125, 216, 250, 154, 115, 174, 9, 206, 44, 88, 63, 163, 124, 10, 239, 64, 158, 191, 27,
|
|
169, 177, 194, 223, 142, 202, 206, 189, 122, 123, 91, 171, 15, 40, 192, 148, 75, 174, 24,
|
|
116, 229, 127, 170, 110, 183, 151, 2, 118, 168, 22, 113, 87, 237, 91, 228, 249, 120, 114,
|
|
255, 53, 175, 245, 39, 2, 0, 0, 0, 0, 0, 0, 0, 225, 45, 230, 25, 62, 202, 96, 166, 171,
|
|
241, 206, 137, 254, 51, 154, 255, 122, 130, 107, 54, 5, 206, 207, 120, 193, 214, 64, 10,
|
|
111, 195, 86, 55, 201, 36, 10, 18, 154, 158, 183, 87, 185, 59, 228, 89, 134, 193, 217, 188,
|
|
64, 164, 249, 21, 248, 20, 207, 58, 31, 10, 19, 176, 246, 150, 45, 48, 2, 0, 0, 0, 0, 0, 0,
|
|
0, 173, 60, 65, 209, 100, 114, 138, 186, 158, 150, 109, 230, 111, 86, 101, 72, 194, 237,
|
|
173, 195, 139, 175, 238, 25, 169, 18, 188, 75, 77, 54, 111, 20, 115, 235, 195, 2, 123, 133,
|
|
164, 81, 15, 45, 11, 84, 139, 38, 8, 224, 197, 181, 95, 147, 49, 77, 193, 207, 52, 141,
|
|
195, 195, 66, 137, 17, 32,
|
|
])
|
|
.unwrap();
|
|
let request_body = BlindSignRequestBody::new(
|
|
&blind_sign_req,
|
|
tx_hash.to_string(),
|
|
"gSFgpma5GAVMcsmZwKieqGNHNd3dPzcfa8eT2Qn2LoBccSeyiJdphREbNrkuh5XWxMe2hUsranaYzLro48L9Qhd"
|
|
.to_string(),
|
|
&voucher.get_public_attributes(),
|
|
voucher.get_public_attributes_plain(),
|
|
4,
|
|
);
|
|
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_BLIND_SIGN
|
|
))
|
|
.json(&request_body)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::Ok);
|
|
// This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893
|
|
// assert!(response.into_json::<BlindedSignatureResponse>().is_some());
|
|
let blinded_signature_response =
|
|
serde_json::from_str::<BlindedSignatureResponse>(&response.into_string().await.unwrap());
|
|
assert!(blinded_signature_response.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn signature_test() {
|
|
let tx_hash = String::from("7C41AF8266D91DE55E1C8F4712E6A952A165ED3D8C27C7B00428CBD0DE00A52B");
|
|
let params = Parameters::new(4).unwrap();
|
|
|
|
let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0);
|
|
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 nymd_client =
|
|
DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap());
|
|
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
|
|
let staged_key_pair = crate::coconut::KeyPair::new();
|
|
staged_key_pair.set(key_pair).await;
|
|
|
|
let rocket = rocket::build().attach(InternalSignRequest::stage(
|
|
nymd_client,
|
|
TEST_COIN_DENOM.to_string(),
|
|
staged_key_pair,
|
|
comm_channel,
|
|
storage.clone(),
|
|
));
|
|
let client = Client::tracked(rocket)
|
|
.await
|
|
.expect("valid rocket instance");
|
|
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&tx_hash)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
assert_eq!(
|
|
response.into_string().await.unwrap(),
|
|
CoconutError::NoSignature.to_string()
|
|
);
|
|
|
|
let encrypted_signature = vec![1, 2, 3, 4];
|
|
let remote_key = [42; 32];
|
|
let expected_response = BlindedSignatureResponse::new(encrypted_signature, remote_key);
|
|
storage
|
|
.insert_blinded_signature_response(&tx_hash, &expected_response.to_base58_string())
|
|
.await
|
|
.unwrap();
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&tx_hash)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::Ok);
|
|
// This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893
|
|
// let blinded_signature_response = response
|
|
// .into_json::<BlindedSignatureResponse>()
|
|
// .await
|
|
// .unwrap();
|
|
let blinded_signature_response =
|
|
serde_json::from_str::<BlindedSignatureResponse>(&response.into_string().await.unwrap())
|
|
.unwrap();
|
|
assert_eq!(
|
|
blinded_signature_response.to_bytes(),
|
|
expected_response.to_bytes()
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn verification_of_bandwidth_credential() {
|
|
// Setup variables
|
|
let validator_address = AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap();
|
|
let proposal_db = Arc::new(RwLock::new(HashMap::new()));
|
|
let spent_credential_db = Arc::new(RwLock::new(HashMap::new()));
|
|
let nymd_client = DummyClient::new(validator_address.clone())
|
|
.with_proposal_db(&proposal_db)
|
|
.with_spent_credential_db(&spent_credential_db);
|
|
let mut db_dir = std::env::temp_dir();
|
|
let params = Parameters::new(4).unwrap();
|
|
let mut key_pairs = ttp_keygen(¶ms, 1, 1).unwrap();
|
|
let voucher_value = 1234u64;
|
|
let voucher_info = "voucher info";
|
|
let public_attributes = vec![
|
|
hash_to_scalar(voucher_value.to_string()),
|
|
hash_to_scalar(voucher_info),
|
|
];
|
|
let indices: Vec<u64> = key_pairs
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(idx, _)| (idx + 1) as u64)
|
|
.collect();
|
|
let theta =
|
|
theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes).unwrap();
|
|
let key_pair = key_pairs.remove(0);
|
|
db_dir.push(&key_pair.verification_key().to_bs58()[..8]);
|
|
let storage1 = ValidatorApiStorage::init(db_dir).await.unwrap();
|
|
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
|
|
let staged_key_pair = crate::coconut::KeyPair::new();
|
|
staged_key_pair.set(key_pair).await;
|
|
let rocket = rocket::build().attach(InternalSignRequest::stage(
|
|
nymd_client.clone(),
|
|
TEST_COIN_DENOM.to_string(),
|
|
staged_key_pair,
|
|
comm_channel.clone(),
|
|
storage1.clone(),
|
|
));
|
|
|
|
let client = Client::tracked(rocket)
|
|
.await
|
|
.expect("valid rocket instance");
|
|
|
|
let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string());
|
|
let proposal_id = 42;
|
|
// The address is not used, so we can use a duplicate
|
|
let gateway_cosmos_addr = validator_address.clone();
|
|
let req =
|
|
VerifyCredentialBody::new(credential.clone(), proposal_id, gateway_cosmos_addr.clone());
|
|
|
|
// Test endpoint with not proposal for the proposal id
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
assert_eq!(
|
|
response.into_string().await.unwrap(),
|
|
CoconutError::IncorrectProposal {
|
|
reason: "proposal not found".to_string()
|
|
}
|
|
.to_string()
|
|
);
|
|
|
|
let mut proposal = ProposalResponse {
|
|
id: proposal_id,
|
|
title: String::new(),
|
|
description: String::from("25mnnoCcUfeizfC85avvroFg2prpEZBgJbJM2SLtkgyyUkoAU3cqJiqWmg8cMHEPjfFf5sQF92SMAM2vbEoLZvUjenvXhadTLdA4TqMYArJpihyqirW2AhGoNehtcdcK5gnH"),
|
|
msgs: vec![],
|
|
status: cw3::Status::Open,
|
|
expires: cw_utils::Expiration::Never {},
|
|
threshold: cw_utils::ThresholdResponse::AbsolutePercentage {
|
|
percentage: Decimal::from_ratio(2u32, 3u32),
|
|
total_weight: 100,
|
|
},
|
|
};
|
|
|
|
// Test the endpoint with a different blinded serial number in the description
|
|
proposal_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(proposal_id, proposal.clone());
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
assert_eq!(
|
|
response.into_string().await.unwrap(),
|
|
CoconutError::IncorrectProposal {
|
|
reason: "incorrect blinded serial number in description".to_string()
|
|
}
|
|
.to_string()
|
|
);
|
|
|
|
// Test the endpoint with no msg in the proposal action
|
|
proposal.description = credential.blinded_serial_number();
|
|
proposal_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(proposal_id, proposal.clone());
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
assert_eq!(
|
|
response.into_string().await.unwrap(),
|
|
CoconutError::IncorrectProposal {
|
|
reason: "action is not to release funds".to_string()
|
|
}
|
|
.to_string()
|
|
);
|
|
|
|
// Test the endpoint without any credential recorded in the Coconut Bandwidth Contract
|
|
let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM);
|
|
let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds {
|
|
funds: funds.clone().into(),
|
|
};
|
|
let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
|
contract_addr: String::new(),
|
|
msg: to_binary(&msg).unwrap(),
|
|
funds: vec![],
|
|
});
|
|
proposal.msgs = vec![cosmos_msg];
|
|
proposal_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(proposal_id, proposal.clone());
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
assert_eq!(
|
|
response.into_string().await.unwrap(),
|
|
CoconutError::InvalidCredentialStatus {
|
|
status: "spent credential not found".to_string()
|
|
}
|
|
.to_string()
|
|
);
|
|
|
|
spent_credential_db.write().unwrap().insert(
|
|
credential.blinded_serial_number(),
|
|
SpendCredentialResponse::new(None),
|
|
);
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
assert_eq!(
|
|
response.into_string().await.unwrap(),
|
|
CoconutError::InvalidCredentialStatus {
|
|
status: "Inexistent".to_string()
|
|
}
|
|
.to_string()
|
|
);
|
|
|
|
// Test the endpoint with a credential that doesn't verify correctly
|
|
let mut spent_credential = SpendCredential::new(
|
|
funds.clone().into(),
|
|
credential.blinded_serial_number(),
|
|
Addr::unchecked("unimportant"),
|
|
);
|
|
spent_credential_db.write().unwrap().insert(
|
|
credential.blinded_serial_number(),
|
|
SpendCredentialResponse::new(Some(spent_credential.clone())),
|
|
);
|
|
let bad_credential = Credential::new(
|
|
4,
|
|
theta.clone(),
|
|
voucher_value,
|
|
String::from("bad voucher info"),
|
|
);
|
|
let bad_req =
|
|
VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone());
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&bad_req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::Ok);
|
|
let verify_credential_response =
|
|
serde_json::from_str::<VerifyCredentialResponse>(&response.into_string().await.unwrap())
|
|
.unwrap();
|
|
assert!(!verify_credential_response.verification_result);
|
|
assert_eq!(
|
|
cw3::Status::Rejected,
|
|
proposal_db
|
|
.read()
|
|
.unwrap()
|
|
.get(&proposal_id)
|
|
.unwrap()
|
|
.status
|
|
);
|
|
|
|
// Test the endpoint with a proposal that has a different value for the funds to be released
|
|
// then what's in the credential
|
|
let funds = Coin::new((voucher_value + 10) as u128, TEST_COIN_DENOM);
|
|
let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds {
|
|
funds: funds.clone().into(),
|
|
};
|
|
let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
|
contract_addr: String::new(),
|
|
msg: to_binary(&msg).unwrap(),
|
|
funds: vec![],
|
|
});
|
|
proposal.msgs = vec![cosmos_msg];
|
|
proposal_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(proposal_id, proposal.clone());
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::Ok);
|
|
let verify_credential_response =
|
|
serde_json::from_str::<VerifyCredentialResponse>(&response.into_string().await.unwrap())
|
|
.unwrap();
|
|
assert!(!verify_credential_response.verification_result);
|
|
assert_eq!(
|
|
cw3::Status::Rejected,
|
|
proposal_db
|
|
.read()
|
|
.unwrap()
|
|
.get(&proposal_id)
|
|
.unwrap()
|
|
.status
|
|
);
|
|
|
|
// Test the endpoint with every dependency met
|
|
let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM);
|
|
let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds {
|
|
funds: funds.clone().into(),
|
|
};
|
|
let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
|
contract_addr: String::new(),
|
|
msg: to_binary(&msg).unwrap(),
|
|
funds: vec![],
|
|
});
|
|
proposal.msgs = vec![cosmos_msg];
|
|
proposal_db
|
|
.write()
|
|
.unwrap()
|
|
.insert(proposal_id, proposal.clone());
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::Ok);
|
|
let verify_credential_response =
|
|
serde_json::from_str::<VerifyCredentialResponse>(&response.into_string().await.unwrap())
|
|
.unwrap();
|
|
assert!(verify_credential_response.verification_result);
|
|
assert_eq!(
|
|
cw3::Status::Passed,
|
|
proposal_db
|
|
.read()
|
|
.unwrap()
|
|
.get(&proposal_id)
|
|
.unwrap()
|
|
.status
|
|
);
|
|
|
|
// Test the endpoint with the credential marked as Spent in the Coconut Bandwidth Contract
|
|
spent_credential.mark_as_spent();
|
|
spent_credential_db.write().unwrap().insert(
|
|
credential.blinded_serial_number(),
|
|
SpendCredentialResponse::new(Some(spent_credential)),
|
|
);
|
|
let response = client
|
|
.post(format!(
|
|
"/{}/{}/{}/{}",
|
|
API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL
|
|
))
|
|
.json(&req)
|
|
.dispatch()
|
|
.await;
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
assert_eq!(
|
|
response.into_string().await.unwrap(),
|
|
CoconutError::InvalidCredentialStatus {
|
|
status: "Spent".to_string()
|
|
}
|
|
.to_string()
|
|
);
|
|
}
|