Compare commits

...

8 Commits

Author SHA1 Message Date
durch f838ddffe2 Remove mixnode modifications 2023-03-27 14:21:44 +02:00
durch 7f7c33d10b Bundle libcpucycles 2023-03-27 14:11:03 +02:00
durch e7fdd3d076 mixnode feature 2023-03-27 09:57:28 +02:00
durch 4dc89bd65f Rename 2023-03-27 09:13:20 +02:00
durch 9aa3b9507d cpu cycle ffi 2023-03-15 11:21:48 +00:00
durch 61e88f304b Checkpoint 2023-03-14 14:38:34 +00:00
Pierre Dommerc 7ff043d8df Feature/bonding signature UI (#3157)
* wip

* updated gateways 'sign' command

* in-wallet verification of mix bonding signature

* changed signature of vesting contract trait method

* updated wallet bonding endpoints

* tauri commands for generating signing payloads

* renamed signer to sender

* verifying new signatures in the contract

* fixed existing mixnet unit tests

* unit tests for invalid signatures

* fixed other usages of MessageSignature + FromStr

* using base58-encoded serialization

* removed owner-signature from details response

* added ability to construct bonding payloads via nym-cli

* removed signature from bonding payload args

* moved 'message_type' from 'ContractMessageContent' to 'SignableMessage'

* refactor(wallet-rust): rename owner_signature args

* feat(wallet-bonding): handle user signature

* feat(wallet-bonding): fix bonding

* feat(wallet-bonding): fix lint issue

* feat(wallet-bonding): ui adjustment

* make the location field mandatory for payload signing

* feat(wallet-bonding): remove ownersignature field, remove dead code

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: Tommy Verrall <tommyvez@protonmail.com>
2023-03-10 15:53:21 +01:00
Jon Häggblad c904d245d2 Remove old erc20 bandwidth-claim-contract (#3162) 2023-03-08 13:57:11 +01:00
156 changed files with 7190 additions and 1550 deletions
+1
View File
@@ -43,3 +43,4 @@ Cargo.lock
nym-connect/Cargo.lock
.parcel-cache
**/.DS_Store
cpu-cycles/libcpucycles/build
Generated
+4 -10
View File
@@ -2706,9 +2706,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.139"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "libgit2-sys"
@@ -3140,14 +3140,6 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "nym-bandwidth-claim-contract"
version = "0.1.0"
dependencies = [
"schemars",
"serde",
]
[[package]]
name = "nym-bin-common"
version = "0.2.0"
@@ -3220,6 +3212,7 @@ dependencies = [
"log",
"nym-coconut-bandwidth-contract-common",
"nym-coconut-dkg-common",
"nym-contracts-common",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
@@ -3515,6 +3508,7 @@ dependencies = [
"mixnode-common",
"nym-bin-common",
"nym-config",
"nym-contracts-common",
"nym-crypto",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
-1
View File
@@ -22,7 +22,6 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
"common/bandwidth-claim-contract",
"common/bin-common",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
@@ -1,10 +0,0 @@
[package]
name = "nym-bandwidth-claim-contract"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -1,45 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
// Serializable structures for what we find in common/crypto
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct PublicKey([u8; 32]);
impl PublicKey {
pub fn new(bytes: [u8; 32]) -> Self {
PublicKey(bytes)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
}
impl AsRef<[u8]> for PublicKey {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct Signature([u8; 32], [u8; 32]);
impl Signature {
pub fn new(bytes: [u8; 64]) -> Self {
let mut sig1 = [0u8; 32];
let mut sig2 = [0u8; 32];
sig1.copy_from_slice(&bytes[..32]);
sig2.copy_from_slice(&bytes[32..]);
Signature(sig1, sig2)
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut res = [0u8; 64];
res[..32].copy_from_slice(&self.0);
res[32..].copy_from_slice(&self.1);
res
}
}
@@ -1,3 +0,0 @@
pub mod keys;
pub mod msg;
pub mod payment;
@@ -1,30 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::keys::PublicKey;
use crate::payment::LinkPaymentData;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct InstantiateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
LinkPayment { data: LinkPaymentData },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetPayments {
limit: Option<u32>,
start_after: Option<PublicKey>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
@@ -1,73 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::keys::{PublicKey, Signature};
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct Payment {
verification_key: PublicKey,
gateway_identity: PublicKey,
bandwidth: u64,
}
impl Payment {
pub fn new(verification_key: PublicKey, gateway_identity: PublicKey, bandwidth: u64) -> Self {
Payment {
verification_key,
gateway_identity,
bandwidth,
}
}
pub fn verification_key(&self) -> PublicKey {
self.verification_key
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct LinkPaymentData {
pub verification_key: PublicKey,
pub gateway_identity: PublicKey,
pub bandwidth: u64,
pub signature: Signature,
}
impl LinkPaymentData {
pub fn new(
verification_key: [u8; 32],
gateway_identity: [u8; 32],
bandwidth: u64,
signature: [u8; 64],
) -> Self {
LinkPaymentData {
verification_key: PublicKey::new(verification_key),
gateway_identity: PublicKey::new(gateway_identity),
bandwidth,
signature: Signature::new(signature),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedPaymentResponse {
pub payments: Vec<Payment>,
pub per_page: usize,
pub start_next_after: Option<PublicKey>,
}
impl PagedPaymentResponse {
pub fn new(
payments: Vec<Payment>,
per_page: usize,
start_next_after: Option<PublicKey>,
) -> Self {
PagedPaymentResponse {
payments,
per_page,
start_next_after,
}
}
}
@@ -43,6 +43,7 @@ pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{self, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
use cosmwasm_std::Addr;
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use signing_client::Client as SigningNyxdClient;
@@ -369,6 +370,15 @@ where
&self.client_address.as_ref().unwrap()[0]
}
pub fn cw_address(&self) -> Addr
where
C: SigningCosmWasmClient,
{
// the call to unchecked is fine here as we're converting directly from `AccountId`
// which must have been a valid bech32 address
Addr::unchecked(self.address().as_ref())
}
pub fn signer(&self) -> &DirectSecp256k1HdWallet
where
C: SigningCosmWasmClient,
@@ -6,6 +6,7 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::NyxdClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::signing::Nonce;
use nym_mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey};
use nym_mixnet_contract_common::families::Family;
use nym_mixnet_contract_common::mixnode::{
@@ -390,6 +391,13 @@ pub trait MixnetQueryClient {
.await
}
async fn get_signing_nonce(&self, address: &AccountId) -> Result<Nonce, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetSigningNonce {
address: address.to_string(),
})
.await
}
async fn get_node_family_by_label(&self, label: &str) -> Result<Option<Family>, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByLabel {
label: label.to_string(),
@@ -8,6 +8,7 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
use nym_mixnet_contract_common::{
@@ -290,7 +291,7 @@ pub trait MixnetSigningClient {
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -311,7 +312,7 @@ pub trait MixnetSigningClient {
owner: AccountId,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -442,7 +443,7 @@ pub trait MixnetSigningClient {
async fn bond_gateway(
&self,
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -461,7 +462,7 @@ pub trait MixnetSigningClient {
&self,
owner: AccountId,
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -6,6 +6,7 @@ use crate::nyxd::cosmwasm_client::types::ExecuteResult;
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Coin, Fee, NyxdClient};
use async_trait::async_trait;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::{Gateway, MixId, MixNode};
use nym_vesting_contract_common::messages::{
@@ -43,7 +44,7 @@ pub trait VestingSigningClient {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
@@ -61,7 +62,7 @@ pub trait VestingSigningClient {
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
@@ -208,14 +209,14 @@ impl<C: SigningCosmWasmClient + Sync + Send + Clone> VestingSigningClient for Ny
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::BondGateway {
gateway,
owner_signature: owner_signature.to_string(),
owner_signature,
amount: pledge.into(),
};
self.client
@@ -272,7 +273,7 @@ impl<C: SigningCosmWasmClient + Sync + Send + Clone> VestingSigningClient for Ny
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -281,7 +282,7 @@ impl<C: SigningCosmWasmClient + Sync + Send + Clone> VestingSigningClient for Ny
VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature: owner_signature.to_string(),
owner_signature,
amount: pledge.into(),
},
vec![],
+1
View File
@@ -30,6 +30,7 @@ cosmwasm-std = { version = "1.0.0" }
validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
nym-network-defaults = { path = "../network-defaults" }
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" }
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
+10 -3
View File
@@ -1,13 +1,20 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmrs::AccountId;
use cosmwasm_std::{Addr, Coin as CosmWasmCoin, Decimal};
use log::error;
use std::error::Error;
use std::fmt::Display;
use cosmwasm_std::{Coin as CosmWasmCoin, Decimal};
use log::error;
use validator_client::nyxd::Coin;
// TODO: perhaps it should be moved to some global common crate?
pub fn account_id_to_cw_addr(account_id: &AccountId) -> Addr {
// the call to unchecked is fine here as we're converting directly from `AccountId`
// which must have been a valid bech32 address
Addr::unchecked(account_id.as_ref())
}
pub fn pretty_coin(coin: &Coin) -> String {
let amount = Decimal::from_ratio(coin.amount, 1_000_000u128);
let denom = if coin.denom.starts_with('u') {
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::{info, warn};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::Coin;
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use validator_client::nyxd::traits::MixnetSigningClient;
@@ -14,7 +15,7 @@ pub struct Args {
pub host: String,
#[clap(long)]
pub signature: String,
pub signature: MessageSignature,
#[clap(long)]
pub mix_port: Option<u16>,
@@ -23,7 +24,7 @@ pub struct Args {
pub clients_port: Option<u16>,
#[clap(long)]
pub location: Option<String>,
pub location: String,
#[clap(long)]
pub sphinx_key: String,
@@ -59,9 +60,7 @@ pub async fn bond_gateway(args: Args, client: SigningClient) {
host: args.host,
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
clients_port: args.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT),
location: args
.location
.unwrap_or_else(|| "secret gateway location".to_owned()),
location: args.location,
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
@@ -0,0 +1,81 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use crate::utils::account_id_to_cw_addr;
use clap::Parser;
use cosmwasm_std::Coin;
use nym_mixnet_contract_common::construct_gateway_bonding_sign_payload;
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use validator_client::nyxd::traits::MixnetQueryClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: String,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub clients_port: Option<u16>,
#[clap(long)]
pub location: String,
#[clap(long)]
pub sphinx_key: String,
#[clap(long)]
pub identity_key: String,
#[clap(long)]
pub version: String,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub amount: u128,
/// Indicates whether the gateway is going to get bonded via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn create_payload(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
let gateway = nym_mixnet_contract_common::Gateway {
host: args.host,
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
clients_port: args.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT),
location: args.location,
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
};
let coin = Coin::new(args.amount, denom);
let nonce = match client.get_signing_nonce(client.address()).await {
Ok(nonce) => nonce,
Err(err) => {
eprint!(
"failed to query for the signing nonce of {}: {err}",
client.address()
);
return;
}
};
let address = account_id_to_cw_addr(client.address());
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(client.vesting_contract_address()))
} else {
None
};
let payload = construct_gateway_bonding_sign_payload(nonce, address, proxy, coin, gateway);
println!("{}", payload.to_base58_string().unwrap())
}
@@ -4,6 +4,7 @@
use clap::{Args, Subcommand};
pub mod bond_gateway;
pub mod gateway_bonding_sign_payload;
pub mod unbond_gateway;
pub mod vesting_bond_gateway;
pub mod vesting_unbond_gateway;
@@ -25,4 +26,6 @@ pub enum MixnetOperatorsGatewayCommands {
VestingBond(vesting_bond_gateway::Args),
/// Unbound from a gateway (when originally using locked tokens)
VestingUnbound(vesting_unbond_gateway::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateGatewayBondingSignPayload(gateway_bonding_sign_payload::Args),
}
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::{info, warn};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::{Coin, Gateway};
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use validator_client::nyxd::VestingSigningClient;
@@ -14,7 +15,7 @@ pub struct Args {
pub host: String,
#[clap(long)]
pub signature: String,
pub signature: MessageSignature,
#[clap(long)]
pub mix_port: Option<u16>,
@@ -23,7 +24,7 @@ pub struct Args {
pub clients_port: Option<u16>,
#[clap(long)]
pub location: Option<String>,
pub location: String,
#[clap(long)]
pub sphinx_key: String,
@@ -57,9 +58,7 @@ pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str
host: args.host,
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
clients_port: args.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT),
location: args
.location
.unwrap_or_else(|| "secret gateway location".to_owned()),
location: args.location,
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
@@ -68,7 +67,7 @@ pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_bond_gateway(gateway, &args.signature, coin.into(), None)
.vesting_bond_gateway(gateway, args.signature, coin.into(), None)
.await
.expect("failed to bond gateway!");
@@ -5,6 +5,7 @@ use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::{Coin, MixNodeCostParams, Percent};
use nym_network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
@@ -20,7 +21,7 @@ pub struct Args {
pub host: String,
#[clap(long)]
pub signature: String,
pub signature: MessageSignature,
#[clap(long)]
pub mix_port: Option<u16>,
@@ -0,0 +1,108 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use crate::utils::account_id_to_cw_addr;
use clap::Parser;
use cosmwasm_std::{Coin, Uint128};
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNodeCostParams};
use nym_network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nyxd::traits::MixnetQueryClient;
use validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: String,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub verloc_port: Option<u16>,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub sphinx_key: String,
#[clap(long)]
pub identity_key: String,
#[clap(long)]
pub version: String,
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub amount: u128,
/// Indicates whether the mixnode is going to get bonded via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn create_payload(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
let mixnode = nym_mixnet_contract_common::MixNode {
host: args.host,
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
verloc_port: args.verloc_port.unwrap_or(DEFAULT_VERLOC_LISTENING_PORT),
http_api_port: args
.http_api_port
.unwrap_or(DEFAULT_HTTP_API_LISTENING_PORT),
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let nonce = match client.get_signing_nonce(client.address()).await {
Ok(nonce) => nonce,
Err(err) => {
eprint!(
"failed to query for the signing nonce of {}: {err}",
client.address()
);
return;
}
};
let address = account_id_to_cw_addr(client.address());
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(client.vesting_contract_address()))
} else {
None
};
let payload =
construct_mixnode_bonding_sign_payload(nonce, address, proxy, coin, mixnode, cost_params);
println!("{}", payload.to_base58_string().unwrap())
}
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod bond_mixnode;
pub mod keys;
pub mod mixnode_bonding_sign_payload;
pub mod rewards;
pub mod settings;
pub mod unbond_mixnode;
@@ -34,4 +35,6 @@ pub enum MixnetOperatorsMixnodeCommands {
BondVesting(vesting_bond_mixnode::Args),
/// Unbound from a mixnode (when originally using locked tokens)
UnboundVesting(vesting_unbond_mixnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args),
}
@@ -1,10 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::{Coin, MixNodeCostParams};
use nym_mixnet_contract_common::{MixNode, Percent};
use nym_network_defaults::{
@@ -18,7 +19,7 @@ pub struct Args {
pub host: String,
#[clap(long)]
pub signature: String,
pub signature: MessageSignature,
#[clap(long)]
pub mix_port: Option<u16>,
@@ -95,7 +96,7 @@ pub async fn vesting_bond_mixnode(client: SigningClient, args: Args, denom: &str
};
let res = client
.vesting_bond_mixnode(mixnode, cost_params, &args.signature, coin.into(), None)
.vesting_bond_mixnode(mixnode, cost_params, args.signature, coin.into(), None)
.await
.expect("failed to bond vesting mixnode!");
@@ -6,6 +6,7 @@
pub mod dealings;
pub mod events;
pub mod signing;
pub mod types;
pub use types::*;
@@ -0,0 +1,259 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{from_slice, to_vec, Addr, Coin, MessageInfo, StdResult};
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
pub use verifier::Verifier;
pub mod verifier;
pub type Nonce = u32;
// define this type explicitly for [hopefully] better usability
// (so you wouldn't need to worry about whether you should use bytes, bs58, etc.)
#[derive(Clone, Debug, PartialEq, JsonSchema)]
pub struct MessageSignature(Vec<u8>);
impl MessageSignature {
pub fn as_bs58_string(&self) -> String {
bs58::encode(&self.0).into_string()
}
}
impl<'a> From<&'a [u8]> for MessageSignature {
fn from(value: &'a [u8]) -> Self {
MessageSignature(value.to_vec())
}
}
impl From<Vec<u8>> for MessageSignature {
fn from(value: Vec<u8>) -> Self {
MessageSignature(value)
}
}
impl<'a> TryFrom<&'a str> for MessageSignature {
type Error = bs58::decode::Error;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Ok(MessageSignature(bs58::decode(value).into_vec()?))
}
}
impl TryFrom<String> for MessageSignature {
type Error = bs58::decode::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl AsRef<[u8]> for MessageSignature {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<'de> Deserialize<'de> for MessageSignature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let inner = String::deserialize(deserializer)?;
let bytes = bs58::decode(inner).into_vec().map_err(de::Error::custom)?;
Ok(MessageSignature(bytes))
}
}
impl Serialize for MessageSignature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bs58_encoded = self.as_bs58_string();
bs58_encoded.serialize(serializer)
}
}
impl Display for MessageSignature {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_bs58_string())
}
}
impl FromStr for MessageSignature {
type Err = bs58::decode::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
pub trait SigningPurpose {
fn message_type() -> MessageType;
}
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct MessageType(String);
impl MessageType {
pub fn new<S: Into<String>>(typ: S) -> Self {
MessageType(typ.into())
}
}
impl<T> From<T> for MessageType
where
T: ToString,
{
fn from(value: T) -> Self {
MessageType(value.to_string())
}
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[serde(rename_all = "lowercase")]
pub enum SigningAlgorithm {
#[default]
Ed25519,
Secp256k1,
}
impl SigningAlgorithm {
pub fn is_ed25519(&self) -> bool {
matches!(self, SigningAlgorithm::Ed25519)
}
}
// TODO: maybe move this one to repo-wide common?
// TODO: should it perhaps also include the public key itself?
#[derive(Serialize, Deserialize)]
pub struct SignableMessage<T> {
pub nonce: u32,
pub algorithm: SigningAlgorithm,
pub message_type: MessageType,
pub content: T,
}
impl<T> SignableMessage<T>
where
T: SigningPurpose,
{
pub fn new(nonce: u32, content: T) -> Self {
SignableMessage {
nonce,
algorithm: SigningAlgorithm::Ed25519,
message_type: T::message_type(),
content,
}
}
pub fn with_signing_algorithm(mut self, algorithm: SigningAlgorithm) -> Self {
self.algorithm = algorithm;
self
}
pub fn to_plaintext(&self) -> StdResult<Vec<u8>>
where
T: Serialize,
{
to_vec(self)
}
pub fn to_sha256_plaintext_digest(&self) -> StdResult<Vec<u8>>
where
T: Serialize,
{
unimplemented!()
}
pub fn to_json_string(&self) -> StdResult<String>
where
T: Serialize,
{
// if you look into implementation of `serde_json_wasm::to_string` this [i.e. the String conversion]
// CAN'T fail, but let's avoid this unnecessary unwrap either way
self.to_plaintext()
.map(|s| String::from_utf8(s).unwrap_or(String::from("SERIALIZATION FAILURE")))
}
pub fn to_base58_string(&self) -> StdResult<String>
where
T: Serialize,
{
self.to_plaintext().map(|s| bs58::encode(s).into_string())
}
pub fn try_from_bytes(bytes: &[u8]) -> StdResult<SignableMessage<T>>
where
T: DeserializeOwned,
{
from_slice(bytes)
}
pub fn try_from_string(raw: &str) -> StdResult<SignableMessage<T>>
where
T: DeserializeOwned,
{
Self::try_from_bytes(raw.as_bytes())
}
pub fn try_from_base58_string(raw: &str) -> bs58::decode::Result<StdResult<SignableMessage<T>>>
where
T: DeserializeOwned,
{
bs58::decode(raw)
.into_vec()
.map(|d| Self::try_from_bytes(&d))
}
}
#[derive(Serialize)]
pub struct ContractMessageContent<T> {
pub sender: Addr,
pub proxy: Option<Addr>,
pub funds: Vec<Coin>,
pub data: T,
}
impl<T> SigningPurpose for ContractMessageContent<T>
where
T: SigningPurpose,
{
fn message_type() -> MessageType {
T::message_type()
}
}
impl<T> ContractMessageContent<T> {
pub fn new(sender: Addr, proxy: Option<Addr>, funds: Vec<Coin>, data: T) -> Self {
ContractMessageContent {
sender,
proxy,
funds,
data,
}
}
pub fn new_with_info(info: MessageInfo, signer: Addr, data: T) -> Self {
let proxy = if info.sender == signer {
None
} else {
Some(info.sender)
};
ContractMessageContent {
sender: signer,
proxy,
funds: info.funds,
data,
}
}
}
@@ -0,0 +1,81 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::signing::{MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose};
use cosmwasm_std::{Api, StdError, VerificationError};
use serde::Serialize;
use thiserror::Error;
pub trait Verifier {
type Error: From<StdError>;
fn verify_message<T: Serialize + SigningPurpose>(
&self,
message: SignableMessage<T>,
signature: MessageSignature,
public_key: &[u8],
) -> Result<bool, Self::Error> {
match message.algorithm {
SigningAlgorithm::Ed25519 => {
let plaintext = message.to_plaintext()?;
self.verify_ed25519(&plaintext, signature.as_ref(), public_key)
}
SigningAlgorithm::Secp256k1 => {
let plaintext = message.to_sha256_plaintext_digest()?;
self.verify_secp256k1(&plaintext, signature.as_ref(), public_key)
}
}
}
fn verify_ed25519(
&self,
_message: &[u8],
_signature: &[u8],
_public_key: &[u8],
) -> Result<bool, Self::Error> {
unimplemented!()
}
fn verify_secp256k1(
&self,
_message_hash: &[u8],
_signature: &[u8],
_public_key: &[u8],
) -> Result<bool, Self::Error> {
unimplemented!()
}
}
#[derive(Debug, Error, PartialEq)]
pub enum ApiVerifierError {
#[error(transparent)]
Verification(#[from] VerificationError),
#[error(transparent)]
Std(#[from] StdError),
}
impl<T> Verifier for T
where
T: Api + ?Sized,
{
type Error = ApiVerifierError;
fn verify_ed25519(
&self,
message: &[u8],
signature: &[u8],
public_key: &[u8],
) -> Result<bool, Self::Error> {
Ok(self.ed25519_verify(message, signature, public_key)?)
}
fn verify_secp256k1(
&self,
message_hash: &[u8],
signature: &[u8],
public_key: &[u8],
) -> Result<bool, Self::Error> {
Ok(self.secp256k1_verify(message_hash, signature, public_key)?)
}
}
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochState, IdentityKey, MixId};
use contracts_common::signing::verifier::ApiVerifierError;
use cosmwasm_std::{Addr, Coin, Decimal};
use thiserror::Error;
@@ -218,4 +219,10 @@ pub enum MixnetContractError {
value: String,
error_message: String,
},
#[error("failed to verify message signature: {source}")]
SignatureVerificationFailure {
#[from]
source: ApiVerifierError,
},
}
@@ -17,6 +17,7 @@ mod msg;
pub mod pending_events;
pub mod reward_params;
pub mod rewarding;
pub mod signing_types;
mod types;
pub use contracts_common::types::*;
@@ -43,4 +44,5 @@ pub use pending_events::{
PendingIntervalEventData, PendingIntervalEventKind,
};
pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams};
pub use signing_types::*;
pub use types::*;
@@ -10,6 +10,7 @@ use crate::reward_params::{
};
use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent};
use crate::{Gateway, IdentityKey, MixNode};
use contracts_common::signing::MessageSignature;
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -153,12 +154,12 @@ pub enum ExecuteMsg {
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
},
BondMixnodeOnBehalf {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
owner: String,
},
PledgeMore {},
@@ -187,12 +188,12 @@ pub enum ExecuteMsg {
// gateway-related:
BondGateway {
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
},
BondGatewayOnBehalf {
gateway: Gateway,
owner: String,
owner_signature: String,
owner_signature: MessageSignature,
},
UnbondGateway {},
UnbondGatewayOnBehalf {
@@ -500,6 +501,11 @@ pub enum QueryMsg {
start_after: Option<u32>,
},
GetNumberOfPendingEvents {},
// signing-related
GetSigningNonce {
address: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
@@ -0,0 +1,151 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{Gateway, MixNode, MixNodeCostParams};
use contracts_common::signing::{
ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose,
};
use cosmwasm_std::{Addr, Coin};
use serde::Serialize;
pub type SignableMixNodeBondingMsg = SignableMessage<ContractMessageContent<MixnodeBondingPayload>>;
pub type SignableGatewayBondingMsg = SignableMessage<ContractMessageContent<GatewayBondingPayload>>;
#[derive(Serialize)]
pub struct MixnodeBondingPayload {
mix_node: MixNode,
cost_params: MixNodeCostParams,
}
impl MixnodeBondingPayload {
pub fn new(mix_node: MixNode, cost_params: MixNodeCostParams) -> Self {
Self {
mix_node,
cost_params,
}
}
}
impl SigningPurpose for MixnodeBondingPayload {
fn message_type() -> MessageType {
MessageType::new("mixnode-bonding")
}
}
pub fn construct_mixnode_bonding_sign_payload(
nonce: Nonce,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
mix_node: MixNode,
cost_params: MixNodeCostParams,
) -> SignableMixNodeBondingMsg {
let payload = MixnodeBondingPayload::new(mix_node, cost_params);
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
SignableMessage::new(nonce, content)
}
#[derive(Serialize)]
pub struct GatewayBondingPayload {
gateway: Gateway,
}
impl GatewayBondingPayload {
pub fn new(gateway: Gateway) -> Self {
Self { gateway }
}
}
impl SigningPurpose for GatewayBondingPayload {
fn message_type() -> MessageType {
MessageType::new("gateway-bonding")
}
}
pub fn construct_gateway_bonding_sign_payload(
nonce: Nonce,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
gateway: Gateway,
) -> SignableGatewayBondingMsg {
let payload = GatewayBondingPayload::new(gateway);
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
SignableMessage::new(nonce, content)
}
#[derive(Serialize)]
pub struct FamilyCreationSignature {
label: String,
// TODO: add any extra fields?
}
impl FamilyCreationSignature {
pub fn new(label: String) -> Self {
Self { label }
}
}
impl SigningPurpose for FamilyCreationSignature {
fn message_type() -> MessageType {
MessageType::new("family-creation")
}
}
#[derive(Serialize)]
pub struct FamilyJoinSignature {
family_head: String,
// TODO: add any extra fields?
}
impl FamilyJoinSignature {
pub fn new(family_head: String) -> Self {
Self { family_head }
}
}
impl SigningPurpose for FamilyJoinSignature {
fn message_type() -> MessageType {
MessageType::new("family-join")
}
}
#[derive(Serialize)]
pub struct FamilyLeaveSignature {
family_head: String,
// TODO: add any extra fields?
}
impl FamilyLeaveSignature {
pub fn new(family_head: String) -> Self {
Self { family_head }
}
}
impl SigningPurpose for FamilyLeaveSignature {
fn message_type() -> MessageType {
MessageType::new("family-leave")
}
}
#[derive(Serialize)]
pub struct FamilyKickSignature {
member: String,
// TODO: add any extra fields?
}
impl FamilyKickSignature {
pub fn new(member: String) -> Self {
Self { member }
}
}
impl SigningPurpose for FamilyKickSignature {
fn message_type() -> MessageType {
MessageType::new("family-member-removal")
}
}
// TODO: depending on our threat model, we should perhaps extend it to include all _on_behalf methods
@@ -1,3 +1,4 @@
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract_common::{
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
@@ -118,7 +119,7 @@ pub enum ExecuteMsg {
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
amount: Coin,
},
PledgeMore {
@@ -131,7 +132,7 @@ pub enum ExecuteMsg {
},
BondGateway {
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
amount: Coin,
},
UnbondGateway {},
-2
View File
@@ -81,7 +81,6 @@ impl GatewayBond {
pub struct GatewayNodeDetailsResponse {
pub identity_key: String,
pub sphinx_key: String,
pub owner_signature: String,
pub announce_address: String,
pub bind_address: String,
pub version: String,
@@ -94,7 +93,6 @@ impl fmt::Display for GatewayNodeDetailsResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Identity Key: {}", self.identity_key)?;
writeln!(f, "Sphinx Key: {}", self.sphinx_key)?;
writeln!(f, "Owner Signature: {}", self.owner_signature)?;
writeln!(
f,
"Host: {} (bind address: {})",
-2
View File
@@ -167,7 +167,6 @@ impl MixNodeCostParams {
pub struct MixnodeNodeDetailsResponse {
pub identity_key: String,
pub sphinx_key: String,
pub owner_signature: String,
pub announce_address: String,
pub bind_address: String,
pub version: String,
@@ -182,7 +181,6 @@ impl fmt::Display for MixnodeNodeDetailsResponse {
let wallet_address = self.wallet_address.clone().unwrap_or_default();
writeln!(f, "Identity Key: {}", self.identity_key)?;
writeln!(f, "Sphinx Key: {}", self.sphinx_key)?;
writeln!(f, "Owner Signature: {}", self.owner_signature)?;
writeln!(
f,
"Host: {} (bind address: {})",
-1
View File
@@ -9,7 +9,6 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
nym-bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
-1
View File
@@ -7,7 +7,6 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nym-bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
+1 -2
View File
@@ -24,7 +24,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.2.0" }
#nym-config = { path = "../../common/config"}
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.2.0" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
@@ -41,7 +41,6 @@ semver = { version = "1.0.16", default-features = false }
[dev-dependencies]
cosmwasm-schema = "1.0.0"
rand_chacha = "0.2"
#rand = "0.7"
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
[build-dependencies]
+32 -30
View File
@@ -39,41 +39,43 @@ pub const FAMILIES_DEFAULT_RETRIEVAL_LIMIT: u32 = 10;
pub const FAMILIES_MAX_RETRIEVAL_LIMIT: u32 = 20;
// storage keys
pub(crate) const DELEGATION_PK_NAMESPACE: &str = "dl";
pub(crate) const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
pub(crate) const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
pub const DELEGATION_PK_NAMESPACE: &str = "dl";
pub const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
pub const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
pub(crate) const GATEWAYS_PK_NAMESPACE: &str = "gt";
pub(crate) const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto";
pub const GATEWAYS_PK_NAMESPACE: &str = "gt";
pub const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto";
pub(crate) const REWARDED_SET_KEY: &str = "rs";
pub(crate) const CURRENT_EPOCH_STATUS_KEY: &str = "ces";
pub(crate) const CURRENT_INTERVAL_KEY: &str = "ci";
pub(crate) const EPOCH_EVENT_ID_COUNTER_KEY: &str = "eic";
pub(crate) const INTERVAL_EVENT_ID_COUNTER_KEY: &str = "iic";
pub(crate) const PENDING_EPOCH_EVENTS_NAMESPACE: &str = "pee";
pub(crate) const PENDING_INTERVAL_EVENTS_NAMESPACE: &str = "pie";
pub const REWARDED_SET_KEY: &str = "rs";
pub const CURRENT_EPOCH_STATUS_KEY: &str = "ces";
pub const CURRENT_INTERVAL_KEY: &str = "ci";
pub const EPOCH_EVENT_ID_COUNTER_KEY: &str = "eic";
pub const INTERVAL_EVENT_ID_COUNTER_KEY: &str = "iic";
pub const PENDING_EPOCH_EVENTS_NAMESPACE: &str = "pee";
pub const PENDING_INTERVAL_EVENTS_NAMESPACE: &str = "pie";
pub(crate) const LAST_EPOCH_EVENT_ID_KEY: &str = "lee";
pub(crate) const LAST_INTERVAL_EVENT_ID_KEY: &str = "lie";
pub const LAST_EPOCH_EVENT_ID_KEY: &str = "lee";
pub const LAST_INTERVAL_EVENT_ID_KEY: &str = "lie";
pub(crate) const CONTRACT_STATE_KEY: &str = "state";
pub const CONTRACT_STATE_KEY: &str = "state";
pub(crate) const LAYER_DISTRIBUTION_KEY: &str = "layers";
pub(crate) const NODE_ID_COUNTER_KEY: &str = "nic";
pub(crate) const MIXNODES_PK_NAMESPACE: &str = "mnn";
pub(crate) const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
pub(crate) const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni";
pub(crate) const MIXNODES_SPHINX_IDX_NAMESPACE: &str = "mns";
pub const LAYER_DISTRIBUTION_KEY: &str = "layers";
pub const NODE_ID_COUNTER_KEY: &str = "nic";
pub const MIXNODES_PK_NAMESPACE: &str = "mnn";
pub const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
pub const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni";
pub const MIXNODES_SPHINX_IDX_NAMESPACE: &str = "mns";
pub(crate) const UNBONDED_MIXNODES_PK_NAMESPACE: &str = "ubm";
pub(crate) const UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE: &str = "umo";
pub(crate) const UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "umi";
pub const UNBONDED_MIXNODES_PK_NAMESPACE: &str = "ubm";
pub const UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE: &str = "umo";
pub const UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "umi";
pub(crate) const REWARDING_PARAMS_KEY: &str = "rparams";
pub(crate) const PENDING_REWARD_POOL_KEY: &str = "prp";
pub(crate) const MIXNODES_REWARDING_PK_NAMESPACE: &str = "mnr";
pub const REWARDING_PARAMS_KEY: &str = "rparams";
pub const PENDING_REWARD_POOL_KEY: &str = "prp";
pub const MIXNODES_REWARDING_PK_NAMESPACE: &str = "mnr";
pub(crate) const FAMILIES_INDEX_NAMESPACE: &str = "faml2";
pub(crate) const FAMILIES_MAP_NAMESPACE: &str = "fam2";
pub(crate) const MEMBERS_MAP_NAMESPACE: &str = "memb2";
pub const FAMILIES_INDEX_NAMESPACE: &str = "faml2";
pub const FAMILIES_MAP_NAMESPACE: &str = "fam2";
pub const MEMBERS_MAP_NAMESPACE: &str = "memb2";
pub const SIGNING_NONCES_NAMESPACE: &str = "sn";
+3
View File
@@ -581,6 +581,9 @@ pub fn query(
QueryMsg::GetNumberOfPendingEvents {} => to_binary(
&crate::interval::queries::query_number_of_pending_events(deps)?,
),
QueryMsg::GetSigningNonce { address } => to_binary(
&crate::signing::queries::query_current_signing_nonce(deps, address)?,
),
};
Ok(query_res?)
+62 -50
View File
@@ -305,71 +305,77 @@ mod test {
use crate::families::queries::{get_family_by_head, get_family_by_label};
use crate::families::storage::is_family_member;
use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge;
use crate::support::tests::{fixtures, test_helpers};
use cosmwasm_std::testing::{mock_env, mock_info};
use crate::support::tests::fixtures;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::mock_info;
#[test]
fn test_family_crud() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
let env = test.env();
let head = "alice";
let malicious_head = "timmy";
let member = "bob";
let minimum_pledge = minimum_mixnode_pledge(deps.as_ref().storage).unwrap();
let (head_mixnode, head_sig, head_keypair) =
test_helpers::mixnode_with_signature(&mut rng, head);
let (malicious_mixnode, malicious_sig, _malicious_keypair) =
test_helpers::mixnode_with_signature(&mut rng, malicious_head);
let minimum_pledge = minimum_mixnode_pledge(test.deps().storage).unwrap();
let cost_params = fixtures::mix_node_cost_params_fixture();
let member = "bob";
let (member_mixnode, member_sig, _) =
test_helpers::mixnode_with_signature(&mut rng, member);
let (head_mixnode, head_bond_sig, head_keypair) = test.mixnode_with_signature(head, None);
let (malicious_mixnode, malicious_bond_sig, malicious_keypair) =
test.mixnode_with_signature(malicious_head, None);
let (member_mixnode, member_bond_sig, member_keypair) =
test.mixnode_with_signature(member, None);
// we are informed that we didn't send enough funds
crate::mixnodes::transactions::try_add_mixnode(
deps.as_mut(),
test.deps_mut(),
env.clone(),
mock_info(head, &[minimum_pledge.clone()]),
head_mixnode.clone(),
cost_params.clone(),
head_sig.clone(),
head_bond_sig,
)
.unwrap();
crate::mixnodes::transactions::try_add_mixnode(
deps.as_mut(),
test.deps_mut(),
env.clone(),
mock_info(malicious_head, &[minimum_pledge.clone()]),
malicious_mixnode,
cost_params.clone(),
malicious_sig.clone(),
malicious_bond_sig,
)
.unwrap();
crate::mixnodes::transactions::try_add_mixnode(
deps.as_mut(),
test.deps_mut(),
env,
mock_info(member, &[minimum_pledge]),
member_mixnode.clone(),
cost_params,
member_sig.clone(),
member_bond_sig,
)
.unwrap();
try_create_family(deps.as_mut(), mock_info(head, &[]), head_sig, "test").unwrap();
let old_style_head_sig = head_keypair.private_key().sign_text(head);
let old_style_malicious_head_sig =
malicious_keypair.private_key().sign_text(malicious_head);
let old_style_member_sig = member_keypair.private_key().sign_text(member);
try_create_family(
test.deps_mut(),
mock_info(head, &[]),
old_style_head_sig,
"test",
)
.unwrap();
let family_head = FamilyHead::new(&head_mixnode.identity_key);
assert!(get_family(&family_head, &deps.storage).is_ok());
assert!(get_family(&family_head, test.deps().storage).is_ok());
let nope = try_create_family(
deps.as_mut(),
test.deps_mut(),
mock_info(malicious_head, &[]),
malicious_sig,
old_style_malicious_head_sig,
"test",
);
@@ -381,11 +387,11 @@ mod test {
},
}
let family = get_family_by_label("test", &deps.storage).unwrap();
let family = get_family_by_label("test", test.deps().storage).unwrap();
assert!(family.is_some());
assert_eq!(family.unwrap().head_identity(), family_head.identity());
let family = get_family_by_head(family_head.identity(), &deps.storage).unwrap();
let family = get_family_by_head(family_head.identity(), test.deps().storage).unwrap();
assert_eq!(family.head_identity(), family_head.identity());
let join_signature = head_keypair
@@ -394,41 +400,47 @@ mod test {
.to_base58_string();
try_join_family(
deps.as_mut(),
test.deps_mut(),
mock_info(member, &[]),
Some(member_sig.clone()),
Some(old_style_member_sig.clone()),
join_signature.clone(),
head_mixnode.identity_key.clone(),
)
.unwrap();
let family = get_family(&family_head, &deps.storage).unwrap();
let family = get_family(&family_head, test.deps().storage).unwrap();
assert!(is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap());
assert!(
is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
try_leave_family(
deps.as_mut(),
test.deps_mut(),
mock_info(member, &[]),
member_sig.clone(),
old_style_member_sig.clone(),
head_mixnode.identity_key.clone(),
)
.unwrap();
let family = get_family(&family_head, &deps.storage).unwrap();
assert!(!is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap());
let family = get_family(&family_head, test.deps().storage).unwrap();
assert!(
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
try_join_family(
deps.as_mut(),
test.deps_mut(),
mock_info(member, &[]),
Some(member_sig.clone()),
join_signature.clone(),
head_mixnode.identity_key.clone(),
Some(old_style_member_sig),
join_signature,
head_mixnode.identity_key,
)
.unwrap();
let family = get_family(&family_head, &deps.storage).unwrap();
let family = get_family(&family_head, test.deps().storage).unwrap();
assert!(is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap());
assert!(
is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
// try_head_kick_member(
// deps.as_mut(),
@@ -438,8 +450,8 @@ mod test {
// )
// .unwrap();
// let family = get_family(&family_head, &deps.storage).unwrap();
// assert!(!is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap());
// let family = get_family(&family_head, test.deps().storage).unwrap();
// assert!(!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap());
}
#[cfg(test)]
@@ -456,7 +468,7 @@ mod test {
let head = "alice";
let (_, keypair) = test.add_dummy_mixnode_with_keypair(head, None);
let (_, keypair) = test.add_dummy_mixnode_with_proxy_and_keypair(head, None);
let sig = keypair.private_key().sign_text(head);
let res = try_create_family_on_behalf(
@@ -495,7 +507,7 @@ mod test {
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
// TODO: those signatures are WRONG and have to be c hanged
let join_signature = head_keys
@@ -542,7 +554,7 @@ mod test {
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
// TODO: those signatures are WRONG and have to be changed
let join_signature = head_keys
@@ -599,7 +611,7 @@ mod test {
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
// TODO: those signatures are WRONG and have to be c hanged
let join_signature = head_keys
@@ -613,7 +625,7 @@ mod test {
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
new_member.to_string(),
Some(member_sig.clone()),
Some(member_sig),
join_signature,
head_identity,
)
+1
View File
@@ -2,5 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod signature_helpers;
pub mod storage;
pub mod transactions;
+27 -46
View File
@@ -62,6 +62,7 @@ pub(crate) mod tests {
use crate::contract::execute;
use crate::support::tests;
use crate::support::tests::test_helpers;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
@@ -73,27 +74,22 @@ pub(crate) mod tests {
#[test]
fn gateways_paged_retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
test.add_dummy_gateways(1000);
let limit = 2;
test_helpers::add_dummy_gateways(&mut rng, deps.as_mut(), env, 1000);
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
let page1 = query_gateways_paged(test.deps(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
}
#[test]
fn gateways_paged_retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_gateways(&mut rng, deps.as_mut(), env, 1000);
let mut test = TestSetup::new();
test.add_dummy_gateways(1000);
// query without explicitly setting a limit
let page1 = query_gateways_paged(deps.as_ref(), None, None).unwrap();
let page1 = query_gateways_paged(test.deps(), None, None).unwrap();
assert_eq!(
GATEWAY_BOND_DEFAULT_RETRIEVAL_LIMIT,
@@ -103,15 +99,12 @@ pub(crate) mod tests {
#[test]
fn gateways_paged_retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_gateways(&mut rng, deps.as_mut(), env, 1000);
let mut test = TestSetup::new();
test.add_dummy_gateways(1000);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * GATEWAY_BOND_DEFAULT_RETRIEVAL_LIMIT;
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
let page1 = query_gateways_paged(test.deps(), None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = GATEWAY_BOND_MAX_RETRIEVAL_LIMIT;
@@ -121,25 +114,27 @@ pub(crate) mod tests {
#[test]
fn gateway_pagination_works() {
let mut deps = test_helpers::init_contract();
let _env = mock_env();
let mut rng = test_helpers::test_rng();
let stake = tests::fixtures::good_gateway_pledge();
// prepare 4 messages and identities that are sorted by the generated identities
// (because we query them in an ascended manner)
let mut exec_data = (0..4)
.map(|i| {
let sender = format!("nym-addr{}", i);
let (msg, identity) = tests::messages::valid_bond_gateway_msg(&mut rng, &sender);
let (msg, identity) = tests::messages::valid_bond_gateway_msg(
&mut rng,
deps.as_ref(),
stake.clone(),
&sender,
);
(msg, (sender, identity))
})
.collect::<Vec<_>>();
exec_data.sort_by(|(_, (_, id1)), (_, (_, id2))| id1.cmp(id2));
let (messages, sender_identities): (Vec<_>, Vec<_>) = exec_data.into_iter().unzip();
let info = mock_info(
&sender_identities[0].0.clone(),
&tests::fixtures::good_gateway_pledge(),
);
let info = mock_info(&sender_identities[0].0.clone(), &stake);
execute(deps.as_mut(), mock_env(), info, messages[0].clone()).unwrap();
let per_page = 2;
@@ -200,43 +195,29 @@ pub(crate) mod tests {
#[test]
fn query_for_gateway_owner_works() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
// "fred" does not own a mixnode if there are no mixnodes
let res = query_owned_gateway(deps.as_ref(), "fred".to_string()).unwrap();
let res = query_owned_gateway(test.deps(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
// mixnode was added to "bob", "fred" still does not own one
test_helpers::add_gateway(
&mut rng,
deps.as_mut(),
env.clone(),
"bob",
tests::fixtures::good_gateway_pledge(),
);
// gateway was added to "bob", "fred" still does not own one
test.add_dummy_gateway("bob", None);
let res = query_owned_gateway(deps.as_ref(), "fred".to_string()).unwrap();
let res = query_owned_gateway(test.deps(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
// "fred" now owns a gateway!
test_helpers::add_gateway(
&mut rng,
deps.as_mut(),
env,
"fred",
tests::fixtures::good_gateway_pledge(),
);
test.add_dummy_gateway("fred", None);
let res = query_owned_gateway(deps.as_ref(), "fred".to_string()).unwrap();
let res = query_owned_gateway(test.deps(), "fred".to_string()).unwrap();
assert!(res.gateway.is_some());
// but after unbonding it, he doesn't own one anymore
crate::gateways::transactions::try_remove_gateway(deps.as_mut(), mock_info("fred", &[]))
crate::gateways::transactions::try_remove_gateway(test.deps_mut(), mock_info("fred", &[]))
.unwrap();
let res = query_owned_gateway(deps.as_ref(), "fred".to_string()).unwrap();
let res = query_owned_gateway(test.deps(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
}
}
@@ -0,0 +1,34 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::signing::storage as signing_storage;
use cosmwasm_std::{Addr, Coin, Deps};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{construct_gateway_bonding_sign_payload, Gateway};
use nym_contracts_common::signing::MessageSignature;
use nym_contracts_common::signing::Verifier;
pub(crate) fn verify_gateway_bonding_signature(
deps: Deps<'_>,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
gateway: Gateway,
signature: MessageSignature,
) -> Result<(), MixnetContractError> {
// recover the public key
let mut public_key = [0u8; 32];
bs58::decode(&gateway.identity_key)
.into(&mut public_key)
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
let msg = construct_gateway_bonding_sign_payload(nonce, sender, proxy, pledge, gateway);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
} else {
Err(MixnetContractError::InvalidEd25519Signature)
}
}
+129 -76
View File
@@ -2,15 +2,17 @@
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::gateways::signature_helpers::verify_gateway_bonding_signature;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_no_existing_bond, ensure_sent_by_vesting_contract, validate_node_identity_signature,
validate_pledge,
ensure_no_existing_bond, ensure_sent_by_vesting_contract, validate_pledge,
};
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{new_gateway_bonding_event, new_gateway_unbonding_event};
use mixnet_contract_common::{Gateway, GatewayBond};
use nym_contracts_common::signing::MessageSignature;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
pub fn try_add_gateway(
@@ -18,7 +20,7 @@ pub fn try_add_gateway(
env: Env,
info: MessageInfo,
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
_try_add_gateway(
deps,
@@ -37,7 +39,7 @@ pub fn try_add_gateway_on_behalf(
info: MessageInfo,
gateway: Gateway,
owner: String,
owner_signature: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
@@ -54,13 +56,15 @@ pub fn try_add_gateway_on_behalf(
)
}
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
pub(crate) fn _try_add_gateway(
deps: DepsMut<'_>,
env: Env,
gateway: Gateway,
pledge: Vec<Coin>,
owner: Addr,
owner_signature: String,
owner_signature: MessageSignature,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// check if the pledge contains any funds of the appropriate denomination
@@ -82,13 +86,18 @@ pub(crate) fn _try_add_gateway(
}
// check if this sender actually owns the gateway by checking the signature
validate_node_identity_signature(
verify_gateway_bonding_signature(
deps.as_ref(),
&owner,
&owner_signature,
&gateway.identity_key,
owner.clone(),
proxy.clone(),
pledge.clone(),
gateway.clone(),
owner_signature,
)?;
// update the signing nonce associated with this sender so that the future signature would be made on the new value
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
let gateway_identity = gateway.identity_key.clone();
let bond = GatewayBond::new(
pledge.clone(),
@@ -182,39 +191,41 @@ pub(crate) fn _try_remove_gateway(
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::execute;
use crate::gateways::queries;
use crate::gateways::transactions::{
try_add_gateway, try_add_gateway_on_behalf, try_remove_gateway_on_behalf,
};
use crate::interval::pending_events;
use crate::mixnet_contract_settings::storage::minimum_gateway_pledge;
use crate::support::tests;
use crate::support::tests::fixtures::{good_gateway_pledge, TEST_COIN_DENOM};
use crate::support::tests::fixtures;
use crate::support::tests::fixtures::{good_gateway_pledge, good_mixnode_pledge};
use crate::support::tests::test_helpers::TestSetup;
use crate::support::tests::{fixtures, test_helpers};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coin, Addr, BankMsg, Response, Uint128};
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Addr, BankMsg, Response, Uint128};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::new_gateway_unbonding_event;
use mixnet_contract_common::ExecuteMsg;
#[test]
fn gateway_add() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
// if we fail validation (by say not sending enough funds
let sender = "alice";
let minimum_pledge = minimum_gateway_pledge(deps.as_ref().storage).unwrap();
let minimum_pledge = minimum_gateway_pledge(test.deps().storage).unwrap();
let mut insufficient_pledge = minimum_pledge.clone();
insufficient_pledge.amount -= Uint128::new(1000);
let info = mock_info(sender, &[insufficient_pledge.clone()]);
let (gateway, sig) = test_helpers::gateway_with_signature(&mut rng, sender);
let (gateway, sig) =
test.gateway_with_signature(sender, Some(vec![insufficient_pledge.clone()]));
let env = test.env();
let result = try_add_gateway(
deps.as_mut(),
test.deps_mut(),
env.clone(),
info,
gateway.clone(),
@@ -233,47 +244,23 @@ pub mod tests {
// if the signature provided is invalid, the bonding also fails
let info = mock_info(sender, &[minimum_pledge]);
let result = try_add_gateway(
deps.as_mut(),
env.clone(),
info.clone(),
gateway.clone(),
"bad-signature".into(),
);
assert!(matches!(
result,
Err(MixnetContractError::MalformedEd25519Signature(..))
));
// if there was already a gateway bonded by particular user
test_helpers::add_gateway(
&mut rng,
deps.as_mut(),
env.clone(),
sender,
fixtures::good_gateway_pledge(),
);
test.add_dummy_gateway(sender, None);
// it fails
let result = try_add_gateway(deps.as_mut(), env.clone(), info, gateway, sig);
let result = try_add_gateway(test.deps_mut(), env.clone(), info, gateway, sig);
assert_eq!(Err(MixnetContractError::AlreadyOwnsGateway), result);
// the same holds if the user already owns a mixnode
let sender2 = "mixnode-owner";
let mix_id = test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
sender2,
vec![coin(100_000_000, TEST_COIN_DENOM)],
);
let mix_id = test.add_dummy_mixnode(sender2, None);
let info = mock_info(sender2, &fixtures::good_gateway_pledge());
let (gateway, sig) = test_helpers::gateway_with_signature(&mut rng, sender2);
let (gateway, sig) = test.gateway_with_signature(sender2, None);
let result = try_add_gateway(
deps.as_mut(),
test.deps_mut(),
env.clone(),
info.clone(),
gateway.clone(),
@@ -282,12 +269,84 @@ pub mod tests {
assert_eq!(Err(MixnetContractError::AlreadyOwnsMixnode), result);
// but after he unbonds it, it's all fine again
pending_events::unbond_mixnode(deps.as_mut(), &env, 123, mix_id).unwrap();
pending_events::unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
let result = try_add_gateway(deps.as_mut(), env, info, gateway, sig);
let result = try_add_gateway(test.deps_mut(), env, info, gateway, sig);
assert!(result.is_ok());
}
#[test]
fn adding_gateway_with_invalid_signatures() {
let mut test = TestSetup::new();
let env = test.env();
let sender = "alice";
let pledge = good_mixnode_pledge();
let info = mock_info(sender, pledge.as_ref());
let (gateway, signature) = test.gateway_with_signature(sender, Some(pledge.clone()));
// using different parameters than what the signature was made on
let mut modified_gateway = gateway.clone();
modified_gateway.mix_port += 1;
let res = try_add_gateway(
test.deps_mut(),
env.clone(),
info,
modified_gateway,
signature.clone(),
);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
// even stake amount is protected
let mut different_pledge = pledge.clone();
different_pledge[0].amount += Uint128::new(12345);
let info = mock_info(sender, different_pledge.as_ref());
let res = try_add_gateway(
test.deps_mut(),
env.clone(),
info.clone(),
gateway.clone(),
signature.clone(),
);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
let other_sender = mock_info("another-sender", pledge.as_ref());
let res = try_add_gateway(
test.deps_mut(),
env.clone(),
other_sender,
gateway.clone(),
signature.clone(),
);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
// trying to reuse the same signature for another bonding fails (because nonce doesn't match!)
let info = mock_info(sender, pledge.as_ref());
let current_nonce =
signing_storage::get_signing_nonce(test.deps().storage, Addr::unchecked(sender))
.unwrap();
assert_eq!(0, current_nonce);
let res = try_add_gateway(
test.deps_mut(),
env.clone(),
info.clone(),
gateway.clone(),
signature.clone(),
);
assert!(res.is_ok());
let updated_nonce =
signing_storage::get_signing_nonce(test.deps().storage, Addr::unchecked(sender))
.unwrap();
assert_eq!(1, updated_nonce);
_try_remove_gateway(test.deps_mut(), Addr::unchecked(sender), None).unwrap();
let res = try_add_gateway(test.deps_mut(), env, info, gateway, signature);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
}
#[test]
fn gateway_add_with_illegal_proxy() {
let mut test = TestSetup::new();
@@ -297,7 +356,7 @@ pub mod tests {
let vesting_contract = test.vesting_contract();
let owner = "alice";
let (gateway, sig) = test_helpers::gateway_with_signature(&mut test.rng, owner);
let (gateway, sig) = test.gateway_with_signature(owner, None);
let res = try_add_gateway_on_behalf(
test.deps_mut(),
@@ -320,14 +379,13 @@ pub mod tests {
#[test]
fn gateway_remove() {
let mut deps = test_helpers::init_contract();
let mut rng = test_helpers::test_rng();
let env = mock_env();
let mut test = TestSetup::new();
let env = test.env();
// try unbond when no nodes exist yet
let info = mock_info("anyone", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
let result = execute(test.deps_mut(), env.clone(), info, msg);
// we're told that there is no node for our address
assert_eq!(
@@ -338,18 +396,12 @@ pub mod tests {
);
// let's add a node owned by bob
test_helpers::add_gateway(
&mut rng,
deps.as_mut(),
env.clone(),
"bob",
fixtures::good_gateway_pledge(),
);
test.add_dummy_gateway("bob", None);
// attempt to unbond fred's node, which doesn't exist
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
let result = execute(test.deps_mut(), env.clone(), info, msg);
assert_eq!(
result,
Err(MixnetContractError::NoAssociatedGatewayBond {
@@ -358,28 +410,27 @@ pub mod tests {
);
// bob's node is still there
let nodes = tests::queries::get_gateways(&mut deps);
let nodes = queries::query_gateways_paged(test.deps(), None, None)
.unwrap()
.nodes;
assert_eq!(1, nodes.len());
let first_node = &nodes[0];
assert_eq!(&Addr::unchecked("bob"), first_node.owner());
// add a node owned by fred
let fred_identity = test_helpers::add_gateway(
&mut rng,
deps.as_mut(),
env,
"fred",
tests::fixtures::good_gateway_pledge(),
);
let fred_identity = test.add_dummy_gateway("fred", None);
// let's make sure we now have 2 nodes:
assert_eq!(2, tests::queries::get_gateways(&mut deps).len());
let nodes = queries::query_gateways_paged(test.deps(), None, None)
.unwrap()
.nodes;
assert_eq!(2, nodes.len());
// unbond fred's node
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let remove_fred = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
let remove_fred = execute(test.deps_mut(), env, info.clone(), msg).unwrap();
// we should see a funds transfer from the contract back to fred
let expected_message = BankMsg::Send {
@@ -401,9 +452,11 @@ pub mod tests {
assert_eq!(expected_response, remove_fred);
// only 1 node now exists, owned by bob:
let gateway_bonds = tests::queries::get_gateways(&mut deps);
assert_eq!(1, gateway_bonds.len());
assert_eq!(&Addr::unchecked("bob"), gateway_bonds[0].owner());
let nodes = queries::query_gateways_paged(test.deps(), None, None)
.unwrap()
.nodes;
assert_eq!(1, nodes.len());
assert_eq!(&Addr::unchecked("bob"), nodes[0].owner());
}
#[test]
+1
View File
@@ -14,6 +14,7 @@ mod mixnet_contract_settings;
mod mixnodes;
mod queued_migrations;
mod rewards;
mod signing;
mod support;
#[cfg(feature = "contract-testing")]
+1
View File
@@ -3,5 +3,6 @@
pub mod helpers;
pub mod queries;
pub mod signature_helpers;
pub mod storage;
pub mod transactions;
+71 -147
View File
@@ -273,30 +273,24 @@ pub(crate) mod tests {
#[cfg(test)]
mod mixnode_bonds {
use super::*;
use crate::support::tests::fixtures::good_mixnode_pledge;
#[test]
fn obeys_limits() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
test.add_dummy_mixnodes(1000);
let limit = 2;
test_helpers::add_dummy_mixnodes(&mut rng, deps.as_mut(), env, 1000);
let page1 = query_mixnode_bonds_paged(deps.as_ref(), None, Some(limit)).unwrap();
let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
}
#[test]
fn has_default_limit() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_mixnodes(&mut rng, deps.as_mut(), env, 1000);
let mut test = TestSetup::new();
test.add_dummy_mixnodes(1000);
// query without explicitly setting a limit
let page1 = query_mixnode_bonds_paged(deps.as_ref(), None, None).unwrap();
let page1 = query_mixnode_bonds_paged(test.deps(), None, None).unwrap();
assert_eq!(
MIXNODE_BOND_DEFAULT_RETRIEVAL_LIMIT,
@@ -306,14 +300,12 @@ pub(crate) mod tests {
#[test]
fn has_max_limit() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_mixnodes(&mut rng, deps.as_mut(), env, 1000);
let mut test = TestSetup::new();
test.add_dummy_mixnodes(1000);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000;
let page1 = query_mixnode_bonds_paged(deps.as_ref(), None, Some(crazy_limit)).unwrap();
let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
assert_eq!(MIXNODE_BOND_MAX_RETRIEVAL_LIMIT, page1.nodes.len() as u32);
@@ -322,63 +314,43 @@ pub(crate) mod tests {
#[test]
fn pagination_works() {
// as we add mixnodes, we're always inserting them in ascending manner due to monotonically increasing id
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
"addr1",
good_mixnode_pledge(),
);
test.add_dummy_mixnode("addr1", None);
let per_page = 2;
let page1 = query_mixnode_bonds_paged(deps.as_ref(), None, Some(per_page)).unwrap();
let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.nodes.len());
// save another
test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
"addr2",
good_mixnode_pledge(),
);
test.add_dummy_mixnode("addr2", None);
// page1 should have 2 results on it
let page1 = query_mixnode_bonds_paged(deps.as_ref(), None, Some(per_page)).unwrap();
let page1 = query_mixnode_bonds_paged(test.deps(), None, Some(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
"addr3",
good_mixnode_pledge(),
);
test.add_dummy_mixnode("addr3", None);
// page1 still has the same 2 results
let another_page1 =
query_mixnode_bonds_paged(deps.as_ref(), None, Some(per_page)).unwrap();
query_mixnode_bonds_paged(test.deps(), None, Some(per_page)).unwrap();
assert_eq!(2, another_page1.nodes.len());
assert_eq!(page1, another_page1);
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_mixnode_bonds_paged(deps.as_ref(), Some(start_after), Some(per_page))
.unwrap();
let page2 =
query_mixnode_bonds_paged(test.deps(), Some(start_after), Some(per_page)).unwrap();
assert_eq!(1, page2.nodes.len());
// save another one
test_helpers::add_mixnode(&mut rng, deps.as_mut(), env, "addr4", good_mixnode_pledge());
test.add_dummy_mixnode("addr4", None);
let page2 = query_mixnode_bonds_paged(deps.as_ref(), Some(start_after), Some(per_page))
.unwrap();
let page2 =
query_mixnode_bonds_paged(test.deps(), Some(start_after), Some(per_page)).unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.nodes.len());
@@ -388,29 +360,24 @@ pub(crate) mod tests {
#[cfg(test)]
mod mixnode_details {
use super::*;
use crate::support::tests::fixtures::good_mixnode_pledge;
#[test]
fn obeys_limits() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
test.add_dummy_mixnodes(1000);
let limit = 2;
test_helpers::add_dummy_mixnodes(&mut rng, deps.as_mut(), env, 1000);
let page1 = query_mixnodes_details_paged(deps.as_ref(), None, Some(limit)).unwrap();
let page1 = query_mixnodes_details_paged(test.deps(), None, Some(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
}
#[test]
fn has_default_limit() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_mixnodes(&mut rng, deps.as_mut(), env, 1000);
let mut test = TestSetup::new();
test.add_dummy_mixnodes(1000);
// query without explicitly setting a limit
let page1 = query_mixnodes_details_paged(deps.as_ref(), None, None).unwrap();
let page1 = query_mixnodes_details_paged(test.deps(), None, None).unwrap();
assert_eq!(
MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT,
@@ -420,15 +387,12 @@ pub(crate) mod tests {
#[test]
fn has_max_limit() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
test_helpers::add_dummy_mixnodes(&mut rng, deps.as_mut(), env, 1000);
let mut test = TestSetup::new();
test.add_dummy_mixnodes(1000);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000;
let page1 =
query_mixnodes_details_paged(deps.as_ref(), None, Some(crazy_limit)).unwrap();
let page1 = query_mixnodes_details_paged(test.deps(), None, Some(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
assert_eq!(
@@ -440,64 +404,44 @@ pub(crate) mod tests {
#[test]
fn pagination_works() {
// as we add mixnodes, we're always inserting them in ascending manner due to monotonically increasing id
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
"addr1",
good_mixnode_pledge(),
);
test.add_dummy_mixnode("addr1", None);
let per_page = 2;
let page1 = query_mixnodes_details_paged(deps.as_ref(), None, Some(per_page)).unwrap();
let page1 = query_mixnodes_details_paged(test.deps(), None, Some(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.nodes.len());
// save another
test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
"addr2",
good_mixnode_pledge(),
);
test.add_dummy_mixnode("addr2", None);
// page1 should have 2 results on it
let page1 = query_mixnodes_details_paged(deps.as_ref(), None, Some(per_page)).unwrap();
let page1 = query_mixnodes_details_paged(test.deps(), None, Some(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
"addr3",
good_mixnode_pledge(),
);
test.add_dummy_mixnode("addr3", None);
// page1 still has the same 2 results
let another_page1 =
query_mixnodes_details_paged(deps.as_ref(), None, Some(per_page)).unwrap();
query_mixnodes_details_paged(test.deps(), None, Some(per_page)).unwrap();
assert_eq!(2, another_page1.nodes.len());
assert_eq!(page1, another_page1);
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 =
query_mixnodes_details_paged(deps.as_ref(), Some(start_after), Some(per_page))
query_mixnodes_details_paged(test.deps(), Some(start_after), Some(per_page))
.unwrap();
assert_eq!(1, page2.nodes.len());
// save another one
test_helpers::add_mixnode(&mut rng, deps.as_mut(), env, "addr4", good_mixnode_pledge());
test.add_dummy_mixnode("addr4", None);
let page2 =
query_mixnodes_details_paged(deps.as_ref(), Some(start_after), Some(per_page))
query_mixnodes_details_paged(test.deps(), Some(start_after), Some(per_page))
.unwrap();
// now we have 2 pages, with 2 results on the second page
@@ -1135,26 +1079,18 @@ pub(crate) mod tests {
#[test]
fn query_for_owned_mixnode() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
let address = "mix-owner".to_string();
// when it doesnt exist
let res = query_owned_mixnode(deps.as_ref(), address.clone()).unwrap();
let res = query_owned_mixnode(test.deps(), address.clone()).unwrap();
assert!(res.mixnode_details.is_none());
assert_eq!(address, res.address);
// when it [fully] exists
let id = test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env,
&address,
good_mixnode_pledge(),
);
let res = query_owned_mixnode(deps.as_ref(), address.clone()).unwrap();
let id = test.add_dummy_mixnode(&address, None);
let res = query_owned_mixnode(test.deps(), address.clone()).unwrap();
let details = res.mixnode_details.unwrap();
assert_eq!(address, details.bond_information.owner);
assert_eq!(
@@ -1171,30 +1107,27 @@ pub(crate) mod tests {
rewarding_details.delegates = Decimal::raw(12345);
rewarding_details.unique_delegations = 1;
rewards_storage::MIXNODE_REWARDING
.save(deps.as_mut().storage, id, &rewarding_details)
.save(test.deps_mut().storage, id, &rewarding_details)
.unwrap();
pending_events::unbond_mixnode(deps.as_mut(), &mock_env(), 123, id).unwrap();
let res = query_owned_mixnode(deps.as_ref(), address.clone()).unwrap();
pending_events::unbond_mixnode(test.deps_mut(), &mock_env(), 123, id).unwrap();
let res = query_owned_mixnode(test.deps(), address.clone()).unwrap();
assert!(res.mixnode_details.is_none());
assert_eq!(address, res.address);
}
#[test]
fn query_for_mixnode_details() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
// no node under this id
let res = query_mixnode_details(deps.as_ref(), 42).unwrap();
let res = query_mixnode_details(test.deps(), 42).unwrap();
assert!(res.mixnode_details.is_none());
assert_eq!(42, res.mix_id);
// it exists
let mix_id =
test_helpers::add_mixnode(&mut rng, deps.as_mut(), env, "foomp", good_mixnode_pledge());
let res = query_mixnode_details(deps.as_ref(), mix_id).unwrap();
let mix_id = test.add_dummy_mixnode("foomp", None);
let res = query_mixnode_details(test.deps(), mix_id).unwrap();
let details = res.mixnode_details.unwrap();
assert_eq!(mix_id, details.bond_information.mix_id);
assert_eq!(
@@ -1227,18 +1160,15 @@ pub(crate) mod tests {
#[test]
fn query_for_mixnode_rewarding_details() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
// no node under this id
let res = query_mixnode_rewarding_details(deps.as_ref(), 42).unwrap();
let res = query_mixnode_rewarding_details(test.deps(), 42).unwrap();
assert!(res.rewarding_details.is_none());
assert_eq!(42, res.mix_id);
let mix_id =
test_helpers::add_mixnode(&mut rng, deps.as_mut(), env, "foomp", good_mixnode_pledge());
let res = query_mixnode_rewarding_details(deps.as_ref(), mix_id).unwrap();
let mix_id = test.add_dummy_mixnode("foomp", None);
let res = query_mixnode_rewarding_details(test.deps(), mix_id).unwrap();
let details = res.rewarding_details.unwrap();
assert_eq!(
fixtures::mix_node_cost_params_fixture(),
@@ -1249,80 +1179,74 @@ pub(crate) mod tests {
#[test]
fn query_for_unbonded_mixnode() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
let sender = "mix-owner";
// no node under this id
let res = query_unbonded_mixnode(deps.as_ref(), 42).unwrap();
let res = query_unbonded_mixnode(test.deps(), 42).unwrap();
assert!(res.unbonded_info.is_none());
assert_eq!(42, res.mix_id);
// add and unbond the mixnode
let mix_id =
test_helpers::add_mixnode(&mut rng, deps.as_mut(), env, sender, good_mixnode_pledge());
pending_events::unbond_mixnode(deps.as_mut(), &mock_env(), 123, mix_id).unwrap();
let mix_id = test.add_dummy_mixnode(sender, None);
pending_events::unbond_mixnode(test.deps_mut(), &mock_env(), 123, mix_id).unwrap();
let res = query_unbonded_mixnode(deps.as_ref(), mix_id).unwrap();
let res = query_unbonded_mixnode(test.deps(), mix_id).unwrap();
assert_eq!(res.unbonded_info.unwrap().owner, sender);
assert_eq!(mix_id, res.mix_id);
}
#[test]
fn query_for_stake_saturation() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
// no node under this id
let res = query_stake_saturation(deps.as_ref(), 42).unwrap();
let res = query_stake_saturation(test.deps(), 42).unwrap();
assert!(res.current_saturation.is_none());
assert!(res.uncapped_saturation.is_none());
assert_eq!(42, res.mix_id);
let rewarding_params = rewards_storage::REWARDING_PARAMS
.load(deps.as_ref().storage)
.load(test.deps().storage)
.unwrap();
let saturation_point = rewarding_params.interval.stake_saturation_point;
let mix_id =
test_helpers::add_mixnode(&mut rng, deps.as_mut(), env, "foomp", good_mixnode_pledge());
let mix_id = test.add_dummy_mixnode("foomp", None);
// below saturation point
// there's only the base pledge without any delegation
let expected =
Decimal::from_atomics(good_mixnode_pledge()[0].amount, 0).unwrap() / saturation_point;
let res = query_stake_saturation(deps.as_ref(), mix_id).unwrap();
let res = query_stake_saturation(test.deps(), mix_id).unwrap();
assert_eq!(expected, res.current_saturation.unwrap());
assert_eq!(expected, res.uncapped_saturation.unwrap());
assert_eq!(mix_id, res.mix_id);
// exactly at saturation point
let mut mix_rewarding = rewards_storage::MIXNODE_REWARDING
.load(deps.as_ref().storage, mix_id)
.load(test.deps().storage, mix_id)
.unwrap();
mix_rewarding.operator = saturation_point;
rewards_storage::MIXNODE_REWARDING
.save(deps.as_mut().storage, mix_id, &mix_rewarding)
.save(test.deps_mut().storage, mix_id, &mix_rewarding)
.unwrap();
let res = query_stake_saturation(deps.as_ref(), mix_id).unwrap();
let res = query_stake_saturation(test.deps(), mix_id).unwrap();
assert_eq!(Decimal::one(), res.current_saturation.unwrap());
assert_eq!(Decimal::one(), res.uncapped_saturation.unwrap());
assert_eq!(mix_id, res.mix_id);
// above the saturation point
let mut mix_rewarding = rewards_storage::MIXNODE_REWARDING
.load(deps.as_ref().storage, mix_id)
.load(test.deps().storage, mix_id)
.unwrap();
mix_rewarding.delegates = mix_rewarding.operator * Decimal::percent(150);
rewards_storage::MIXNODE_REWARDING
.save(deps.as_mut().storage, mix_id, &mix_rewarding)
.save(test.deps_mut().storage, mix_id, &mix_rewarding)
.unwrap();
let res = query_stake_saturation(deps.as_ref(), mix_id).unwrap();
let res = query_stake_saturation(test.deps(), mix_id).unwrap();
assert_eq!(Decimal::one(), res.current_saturation.unwrap());
assert_eq!(Decimal::percent(250), res.uncapped_saturation.unwrap());
assert_eq!(mix_id, res.mix_id);
@@ -0,0 +1,36 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::signing::storage as signing_storage;
use cosmwasm_std::{Addr, Coin, Deps};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode, MixNodeCostParams};
use nym_contracts_common::signing::MessageSignature;
use nym_contracts_common::signing::Verifier;
pub(crate) fn verify_mixnode_bonding_signature(
deps: Deps<'_>,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
mixnode: MixNode,
cost_params: MixNodeCostParams,
signature: MessageSignature,
) -> Result<(), MixnetContractError> {
// recover the public key
let mut public_key = [0u8; 32];
bs58::decode(&mixnode.identity_key)
.into(&mut public_key)
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
let msg =
construct_mixnode_bonding_sign_payload(nonce, sender, proxy, pledge, mixnode, cost_params);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
} else {
Err(MixnetContractError::InvalidEd25519Signature)
}
}
+142 -82
View File
@@ -9,10 +9,11 @@ use crate::mixnet_contract_settings::storage::rewarding_denom;
use crate::mixnodes::helpers::{
get_mixnode_details_by_owner, must_get_mixnode_bond_by_owner, save_new_mixnode,
};
use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
ensure_proxy_match, ensure_sent_by_vesting_contract, validate_node_identity_signature,
validate_pledge,
ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
};
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
@@ -24,6 +25,7 @@ use mixnet_contract_common::events::{
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind};
use mixnet_contract_common::{Layer, MixId, MixNode};
use nym_contracts_common::signing::MessageSignature;
pub(crate) fn update_mixnode_layer(
mix_id: MixId,
@@ -61,7 +63,7 @@ pub fn try_add_mixnode(
info: MessageInfo,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
_try_add_mixnode(
deps,
@@ -82,7 +84,7 @@ pub fn try_add_mixnode_on_behalf(
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner: String,
owner_signature: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
@@ -101,6 +103,9 @@ pub fn try_add_mixnode_on_behalf(
}
// I'm not entirely sure how to deal with this warning at the current moment
//
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
#[allow(clippy::too_many_arguments)]
fn _try_add_mixnode(
deps: DepsMut<'_>,
@@ -109,7 +114,7 @@ fn _try_add_mixnode(
cost_params: MixNodeCostParams,
pledge: Vec<Coin>,
owner: Addr,
owner_signature: String,
owner_signature: MessageSignature,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// check if the pledge contains any funds of the appropriate denomination
@@ -126,13 +131,19 @@ fn _try_add_mixnode(
// the bond information due to `UniqueIndex` constraint defined on those fields.
// check if this sender actually owns the mixnode by checking the signature
validate_node_identity_signature(
verify_mixnode_bonding_signature(
deps.as_ref(),
&owner,
&owner_signature,
&mixnode.identity_key,
owner.clone(),
proxy.clone(),
pledge.clone(),
mixnode.clone(),
cost_params.clone(),
owner_signature,
)?;
// update the signing nonce associated with this sender so that the future signature would be made on the new value
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
let node_identity = mixnode.identity_key.clone();
let (node_id, layer) = save_new_mixnode(
deps.storage,
@@ -393,7 +404,7 @@ pub mod tests {
use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM};
use crate::support::tests::test_helpers::TestSetup;
use crate::support::tests::{fixtures, test_helpers};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Order, StdResult, Uint128};
use mixnet_contract_common::{
EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent,
@@ -401,23 +412,23 @@ pub mod tests {
#[test]
fn mixnode_add() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
let env = test.env();
let sender = "alice";
let minimum_pledge = minimum_mixnode_pledge(deps.as_ref().storage).unwrap();
let minimum_pledge = minimum_mixnode_pledge(test.deps().storage).unwrap();
let mut insufficient_pledge = minimum_pledge.clone();
insufficient_pledge.amount -= Uint128::new(1000);
// if we don't send enough funds
let info = mock_info(sender, &[insufficient_pledge.clone()]);
let (mixnode, sig, _) = test_helpers::mixnode_with_signature(&mut rng, sender);
let (mixnode, sig, _) =
test.mixnode_with_signature(sender, Some(vec![insufficient_pledge.clone()]));
let cost_params = fixtures::mix_node_cost_params_fixture();
// we are informed that we didn't send enough funds
let result = try_add_mixnode(
deps.as_mut(),
test.deps_mut(),
env.clone(),
info,
mixnode.clone(),
@@ -435,31 +446,12 @@ pub mod tests {
// if the signature provided is invalid, the bonding also fails
let info = mock_info(sender, &[minimum_pledge]);
let result = try_add_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
mixnode.clone(),
cost_params.clone(),
"bad-signature".into(),
);
assert!(matches!(
result,
Err(MixnetContractError::MalformedEd25519Signature(..))
));
// if there was already a mixnode bonded by particular user
test_helpers::add_mixnode(
&mut rng,
deps.as_mut(),
env.clone(),
sender,
fixtures::good_mixnode_pledge(),
);
test.add_dummy_mixnode(sender, None);
// it fails
let result = try_add_mixnode(
deps.as_mut(),
test.deps_mut(),
env.clone(),
info,
mixnode,
@@ -471,19 +463,13 @@ pub mod tests {
// the same holds if the user already owns a gateway
let sender2 = "gateway-owner";
test_helpers::add_gateway(
&mut rng,
deps.as_mut(),
env.clone(),
sender2,
tests::fixtures::good_gateway_pledge(),
);
test.add_dummy_gateway(sender2, None);
let info = mock_info(sender2, &tests::fixtures::good_mixnode_pledge());
let (mixnode, sig, _) = test_helpers::mixnode_with_signature(&mut rng, sender2);
let (mixnode, sig, _) = test.mixnode_with_signature(sender2, None);
let result = try_add_mixnode(
deps.as_mut(),
test.deps_mut(),
env.clone(),
info.clone(),
mixnode.clone(),
@@ -494,14 +480,14 @@ pub mod tests {
// but after he unbonds it, it's all fine again
let msg = ExecuteMsg::UnbondGateway {};
execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
execute(test.deps_mut(), env.clone(), info.clone(), msg).unwrap();
let result = try_add_mixnode(deps.as_mut(), env, info, mixnode, cost_params, sig);
let result = try_add_mixnode(test.deps_mut(), env, info, mixnode, cost_params, sig);
assert!(result.is_ok());
// make sure we got assigned the next id (note: we have already bonded a mixnode before in this test)
let bond = must_get_mixnode_bond_by_owner(deps.as_ref().storage, &Addr::unchecked(sender2))
.unwrap();
let bond =
must_get_mixnode_bond_by_owner(test.deps().storage, &Addr::unchecked(sender2)).unwrap();
assert_eq!(2, bond.mix_id);
// and make sure we're on layer 2 (because it was the next empty one)
@@ -513,10 +499,84 @@ pub mod tests {
layer2: 1,
layer3: 0,
};
assert_eq!(
expected,
storage::LAYERS.load(deps.as_ref().storage).unwrap()
)
assert_eq!(expected, storage::LAYERS.load(test.deps().storage).unwrap())
}
#[test]
fn adding_mixnode_with_invalid_signatures() {
let mut test = TestSetup::new();
let env = test.env();
let sender = "alice";
let pledge = good_mixnode_pledge();
let info = mock_info(sender, pledge.as_ref());
let (mixnode, signature, _) = test.mixnode_with_signature(sender, Some(pledge.clone()));
// the above using cost params fixture
let cost_params = fixtures::mix_node_cost_params_fixture();
// using different parameters than what the signature was made on
let mut modified_mixnode = mixnode.clone();
modified_mixnode.mix_port += 1;
let res = try_add_mixnode(
test.deps_mut(),
env.clone(),
info,
modified_mixnode,
cost_params.clone(),
signature.clone(),
);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
// even stake amount is protected
let mut different_pledge = pledge.clone();
different_pledge[0].amount += Uint128::new(12345);
let info = mock_info(sender, different_pledge.as_ref());
let res = try_add_mixnode(
test.deps_mut(),
env.clone(),
info.clone(),
mixnode.clone(),
cost_params.clone(),
signature.clone(),
);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
let other_sender = mock_info("another-sender", pledge.as_ref());
let res = try_add_mixnode(
test.deps_mut(),
env.clone(),
other_sender,
mixnode.clone(),
cost_params.clone(),
signature.clone(),
);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
// trying to reuse the same signature for another bonding fails (because nonce doesn't match!)
let info = mock_info(sender, pledge.as_ref());
let current_nonce =
signing_storage::get_signing_nonce(test.deps().storage, Addr::unchecked(sender))
.unwrap();
assert_eq!(0, current_nonce);
let res = try_add_mixnode(
test.deps_mut(),
env.clone(),
info.clone(),
mixnode.clone(),
cost_params.clone(),
signature.clone(),
);
assert!(res.is_ok());
let updated_nonce =
signing_storage::get_signing_nonce(test.deps().storage, Addr::unchecked(sender))
.unwrap();
assert_eq!(1, updated_nonce);
test.immediately_unbond_mixnode(1);
let res = try_add_mixnode(test.deps_mut(), env, info, mixnode, cost_params, signature);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
}
#[test]
@@ -528,10 +588,9 @@ pub mod tests {
let vesting_contract = test.vesting_contract();
let owner = "alice";
let (mixnode, sig, _) = test_helpers::mixnode_with_signature(&mut test.rng, owner);
let (mixnode, sig, _) = test.mixnode_with_signature(owner, None);
let cost_params = fixtures::mix_node_cost_params_fixture();
// we are informed that we didn't send enough funds
let res = try_add_mixnode_on_behalf(
test.deps_mut(),
env,
@@ -920,52 +979,53 @@ pub mod tests {
#[test]
fn adding_mixnode_with_duplicate_sphinx_key_errors_out() {
let mut deps = test_helpers::init_contract();
let mut rng = test_helpers::test_rng();
let mut test = TestSetup::new();
let env = test.env();
let keypair1 = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
let keypair2 = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
let sig1 = keypair1.private_key().sign_text("alice");
let sig2 = keypair1.private_key().sign_text("bob");
let keypair1 = nym_crypto::asymmetric::identity::KeyPair::new(&mut test.rng);
let keypair2 = nym_crypto::asymmetric::identity::KeyPair::new(&mut test.rng);
let info_alice = mock_info("alice", &tests::fixtures::good_mixnode_pledge());
let info_bob = mock_info("bob", &tests::fixtures::good_mixnode_pledge());
let mut mixnode = MixNode {
let cost_params = fixtures::mix_node_cost_params_fixture();
let mixnode1 = MixNode {
host: "1.2.3.4".to_string(),
mix_port: 1234,
verloc_port: 1234,
http_api_port: 1234,
sphinx_key: nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng)
sphinx_key: nym_crypto::asymmetric::encryption::KeyPair::new(&mut test.rng)
.public_key()
.to_base58_string(),
identity_key: keypair1.public_key().to_base58_string(),
version: "v0.1.2.3".to_string(),
};
let cost_params = fixtures::mix_node_cost_params_fixture();
// change identity but reuse sphinx key
let mut mixnode2 = mixnode1.clone();
mixnode2.sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut test.rng)
.public_key()
.to_base58_string();
let sig1 =
test.mixnode_bonding_signature(keypair1.private_key(), "alice", mixnode1.clone(), None);
let sig2 =
test.mixnode_bonding_signature(keypair2.private_key(), "bob", mixnode2.clone(), None);
let info_alice = mock_info("alice", &tests::fixtures::good_mixnode_pledge());
let info_bob = mock_info("bob", &tests::fixtures::good_mixnode_pledge());
assert!(try_add_mixnode(
deps.as_mut(),
mock_env(),
test.deps_mut(),
env.clone(),
info_alice,
mixnode.clone(),
mixnode1,
cost_params.clone(),
sig1
)
.is_ok());
mixnode.identity_key = keypair2.public_key().to_base58_string();
// change identity but reuse sphinx key
assert!(try_add_mixnode(
deps.as_mut(),
mock_env(),
info_bob,
mixnode,
cost_params,
sig2
)
.is_err());
assert!(
try_add_mixnode(test.deps_mut(), env, info_bob, mixnode2, cost_params, sig2).is_err()
);
}
#[cfg(test)]
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::signing::storage::get_signing_nonce;
use cosmwasm_std::{Deps, StdResult};
use nym_contracts_common::signing::Nonce;
pub fn query_current_signing_nonce(deps: Deps<'_>, address: String) -> StdResult<Nonce> {
let address = deps.api.addr_validate(&address)?;
get_signing_nonce(deps.storage, address)
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::SIGNING_NONCES_NAMESPACE;
use cosmwasm_std::{Addr, StdResult, Storage};
use cw_storage_plus::Map;
use nym_contracts_common::signing::Nonce;
pub const NONCES: Map<'_, Addr, Nonce> = Map::new(SIGNING_NONCES_NAMESPACE);
pub fn get_signing_nonce(storage: &dyn Storage, address: Addr) -> StdResult<Nonce> {
let nonce = NONCES.may_load(storage, address)?.unwrap_or(0);
Ok(nonce)
}
pub fn update_signing_nonce(
storage: &mut dyn Storage,
address: Addr,
value: Nonce,
) -> StdResult<()> {
NONCES.save(storage, address, &value)
}
pub fn increment_signing_nonce(storage: &mut dyn Storage, address: Addr) -> StdResult<()> {
// get the current nonce
let nonce = get_signing_nonce(storage, address.clone())?;
// increment it for the next use
update_signing_nonce(storage, address, nonce + 1)
}
+3 -3
View File
@@ -377,7 +377,7 @@ pub fn validate_node_identity_signature(
signature: &str,
identity: IdentityKeyRef<'_>,
) -> Result<(), MixnetContractError> {
validate_signature(deps, owner.as_bytes(), signature, identity)
validate_ed25519_signature(deps, owner.as_bytes(), signature, identity)
}
pub fn validate_family_signature(
@@ -386,10 +386,10 @@ pub fn validate_family_signature(
signature: &str,
family_head: IdentityKeyRef<'_>,
) -> Result<(), MixnetContractError> {
validate_signature(deps, family_member.as_bytes(), signature, family_head)
validate_ed25519_signature(deps, family_member.as_bytes(), signature, family_head)
}
pub(crate) fn validate_signature(
pub(crate) fn validate_ed25519_signature(
deps: Deps<'_>,
signed_bytes: &[u8],
signature: &str,
+18 -9
View File
@@ -1,25 +1,34 @@
use cosmwasm_std::{Coin, Deps};
use mixnet_contract_common::{ExecuteMsg, Gateway, IdentityKey};
use nym_crypto::asymmetric::identity;
use rand_chacha::rand_core::{CryptoRng, RngCore};
use crate::support::tests;
use crate::support::tests::test_helpers::{ed25519_sign_message, gateway_bonding_sign_payload};
pub(crate) fn valid_bond_gateway_msg(
mut rng: impl RngCore + CryptoRng,
deps: Deps<'_>,
stake: Vec<Coin>,
sender: &str,
) -> (ExecuteMsg, IdentityKey) {
let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let keypair = identity::KeyPair::new(&mut rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng);
let gateway = Gateway {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::gateway_fixture()
};
let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let identity_key = keypair.public_key().to_base58_string();
(
ExecuteMsg::BondGateway {
gateway: Gateway {
identity_key: identity_key.clone(),
..tests::fixtures::gateway_fixture()
},
gateway,
owner_signature,
},
identity_key,
+290 -189
View File
@@ -5,8 +5,6 @@
pub mod fixtures;
#[cfg(test)]
pub mod messages;
#[cfg(test)]
pub mod queries;
#[cfg(test)]
pub mod test_helpers {
@@ -24,7 +22,8 @@ pub mod test_helpers {
use crate::interval::{pending_events, storage as interval_storage};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnet_contract_settings::storage::{
minimum_mixnode_pledge, rewarding_denom, rewarding_validator_address,
minimum_gateway_pledge, minimum_mixnode_pledge, rewarding_denom,
rewarding_validator_address,
};
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::storage::mixnode_bonds;
@@ -36,8 +35,11 @@ pub mod test_helpers {
};
use crate::rewards::storage as rewards_storage;
use crate::rewards::transactions::try_reward_mixnode;
use crate::signing::storage as signing_storage;
use crate::support::tests;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::fixtures::{
good_gateway_pledge, good_mixnode_pledge, TEST_COIN_DENOM,
};
use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::testing::mock_info;
@@ -59,13 +61,19 @@ pub mod test_helpers {
use mixnet_contract_common::rewarding::simulator::Simulator;
use mixnet_contract_common::rewarding::RewardDistribution;
use mixnet_contract_common::{
Delegation, EpochState, EpochStatus, Gateway, IdentityKey, InitialRewardingParams,
InstantiateMsg, Interval, MixId, MixNode, MixNodeBond, Percent, RewardedSetNodeStatus,
Delegation, EpochState, EpochStatus, Gateway, GatewayBondingPayload, IdentityKey,
InitialRewardingParams, InstantiateMsg, Interval, MixId, MixNode, MixNodeBond,
MixnodeBondingPayload, Percent, RewardedSetNodeStatus, SignableGatewayBondingMsg,
SignableMixNodeBondingMsg,
};
use nym_contracts_common::signing::{
ContractMessageContent, MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose,
};
use nym_crypto::asymmetric::identity;
use nym_crypto::asymmetric::identity::KeyPair;
use rand_chacha::rand_core::{CryptoRng, RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use serde::Serialize;
use std::time::Duration;
pub fn assert_eq_with_leeway(a: Uint128, b: Uint128, leeway: Uint128) {
@@ -190,7 +198,7 @@ pub mod test_helpers {
head: &str,
label: &str,
) -> (MixId, identity::KeyPair) {
let (mix_id, keys) = self.add_dummy_mixnode_with_keypair(head, None);
let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None);
let sig = keys.private_key().sign_text(head);
try_create_family(self.deps_mut(), mock_info(head, &[]), sig, label).unwrap();
@@ -198,6 +206,57 @@ pub mod test_helpers {
}
pub fn add_dummy_mixnode(&mut self, owner: &str, stake: Option<Uint128>) -> MixId {
let stake = self.make_mix_pledge(stake);
let (mixnode, owner_signature, _) =
self.mixnode_with_signature(owner, Some(stake.clone()));
let info = mock_info(owner, stake.as_ref());
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
.may_load(self.deps().storage)
.unwrap()
.unwrap_or_default();
let env = self.env();
try_add_mixnode(
self.deps_mut(),
env,
info,
mixnode,
tests::fixtures::mix_node_cost_params_fixture(),
owner_signature,
)
.unwrap();
// newly added mixnode gets assigned the current counter + 1
current_id_counter + 1
}
pub fn add_dummy_gateway(&mut self, sender: &str, stake: Option<Uint128>) -> IdentityKey {
let stake = self.make_gateway_pledge(stake);
let (gateway, owner_signature) =
self.gateway_with_signature(sender, Some(stake.clone()));
let info = mock_info(sender, &stake);
let key = gateway.identity_key.clone();
let env = self.env();
try_add_gateway(self.deps_mut(), env, info, gateway, owner_signature).unwrap();
key
}
pub fn add_dummy_mixnodes(&mut self, n: usize) {
for i in 0..n {
self.add_dummy_mixnode(&format!("owner{i}"), None);
}
}
pub fn add_dummy_gateways(&mut self, n: usize) {
for i in 0..n {
self.add_dummy_gateway(&format!("owner{i}"), None);
}
}
pub fn make_mix_pledge(&self, stake: Option<Uint128>) -> Vec<Coin> {
let stake = match stake {
Some(amount) => {
let denom = rewarding_denom(self.deps().storage).unwrap();
@@ -205,36 +264,61 @@ pub mod test_helpers {
}
None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(),
};
let env = self.env();
add_mixnode(&mut self.rng, self.deps.as_mut(), env, owner, vec![stake])
vec![stake]
}
pub fn add_dummy_mixnode_with_keypair(
pub fn make_gateway_pledge(&self, stake: Option<Uint128>) -> Vec<Coin> {
let stake = match stake {
Some(amount) => {
let denom = rewarding_denom(self.deps().storage).unwrap();
Coin { denom, amount }
}
None => minimum_gateway_pledge(self.deps.as_ref().storage).unwrap(),
};
vec![stake]
}
pub fn mixnode_bonding_signature(
&mut self,
key: &identity::PrivateKey,
owner: &str,
mixnode: MixNode,
stake: Option<Uint128>,
) -> MessageSignature {
let stake = self.make_mix_pledge(stake);
let msg = mixnode_bonding_sign_payload(self.deps(), owner, None, mixnode, stake);
ed25519_sign_message(msg, key)
}
pub fn add_dummy_mixnode_with_proxy_and_keypair(
&mut self,
owner: &str,
stake: Option<Uint128>,
) -> (MixId, identity::KeyPair) {
let stake = match stake {
Some(amount) => {
let denom = rewarding_denom(self.deps().storage).unwrap();
Coin { denom, amount }
}
None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(),
};
let stake = self.make_mix_pledge(stake);
let proxy = self.vesting_contract();
let keypair = identity::KeyPair::new(&mut self.rng);
let owner_signature = keypair
.private_key()
.sign(owner.as_bytes())
.to_base58_string();
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let mixnode = MixNode {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
};
let info = mock_info(proxy.as_str(), &[stake]);
let key = keypair.public_key().to_base58_string();
let msg = mixnode_bonding_sign_payload(
self.deps(),
owner,
Some(proxy.clone()),
mixnode.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let info = mock_info(proxy.as_str(), &stake);
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
.may_load(self.deps().storage)
.unwrap()
@@ -245,11 +329,7 @@ pub mod test_helpers {
self.deps_mut(),
env,
info,
MixNode {
identity_key: key,
sphinx_key: legit_sphinx_key.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
},
mixnode,
tests::fixtures::mix_node_cost_params_fixture(),
owner.to_string(),
owner_signature,
@@ -265,7 +345,8 @@ pub mod test_helpers {
owner: &str,
stake: Option<Uint128>,
) -> MixId {
self.add_dummy_mixnode_with_keypair(owner, stake).0
self.add_dummy_mixnode_with_proxy_and_keypair(owner, stake)
.0
}
pub fn set_illegal_mixnode_proxy(&mut self, mix_id: MixId, proxy: Addr) {
@@ -312,14 +393,26 @@ pub mod test_helpers {
None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(),
};
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let proxy = self.vesting_contract();
let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut self.rng);
let owner_signature = keypair
.private_key()
.sign(owner.as_bytes())
.to_base58_string();
let gateway = Gateway {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::gateway_fixture()
};
let msg = gateway_bonding_sign_payload(
self.deps(),
owner,
Some(proxy.clone()),
gateway.clone(),
vec![stake.clone()],
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let env = self.env();
let info = mock_info(proxy.as_ref(), &[stake]);
@@ -328,11 +421,7 @@ pub mod test_helpers {
self.deps_mut(),
env,
info,
Gateway {
identity_key: keypair.public_key().to_base58_string(),
sphinx_key: legit_sphinx_key.public_key().to_base58_string(),
..tests::fixtures::gateway_fixture()
},
gateway,
owner.to_string(),
owner_signature,
)
@@ -351,6 +440,63 @@ pub mod test_helpers {
mix_id
}
pub fn mixnode_with_signature(
&mut self,
sender: &str,
stake: Option<Vec<Coin>>,
) -> (MixNode, MessageSignature, KeyPair) {
let stake = stake.unwrap_or(good_mixnode_pledge());
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let mixnode = MixNode {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
};
let msg = mixnode_bonding_sign_payload(
self.deps(),
sender,
None,
mixnode.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
(mixnode, owner_signature, keypair)
}
pub fn gateway_with_signature(
&mut self,
sender: &str,
stake: Option<Vec<Coin>>,
) -> (Gateway, MessageSignature) {
let stake = stake.unwrap_or(good_gateway_pledge());
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let gateway = Gateway {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::gateway_fixture()
};
let msg = gateway_bonding_sign_payload(
self.deps(),
sender,
None,
gateway.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
(gateway, owner_signature)
}
pub fn start_unbonding_mixnode(&mut self, mix_id: MixId) {
let bond_details = mixnodes_storage::mixnode_bonds()
.load(self.deps().storage, mix_id)
@@ -714,6 +860,22 @@ pub mod test_helpers {
}
}
pub fn ed25519_sign_message<T: Serialize + SigningPurpose>(
message: SignableMessage<T>,
private_key: &identity::PrivateKey,
) -> MessageSignature {
match message.algorithm {
SigningAlgorithm::Ed25519 => {
let plaintext = message.to_plaintext().unwrap();
let signature = private_key.sign(&plaintext);
MessageSignature::from(signature.to_bytes().as_ref())
}
SigningAlgorithm::Secp256k1 => {
unimplemented!()
}
}
}
pub fn simulator_from_single_node_state(deps: Deps<'_>, node: MixId) -> Simulator {
let mix_rewarding = rewards_storage::MIXNODE_REWARDING
.load(deps.storage, node)
@@ -800,54 +962,54 @@ pub mod test_helpers {
perform_pending_interval_actions(deps.branch(), &env, None).unwrap();
}
pub fn mixnode_with_signature(
mut rng: impl RngCore + CryptoRng,
sender: &str,
) -> (MixNode, String, KeyPair) {
let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng);
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
// pub fn mixnode_with_signature(
// mut rng: impl RngCore + CryptoRng,
// deps: Deps<'_>,
// sender: &str,
// stake: Option<Vec<Coin>>,
// ) -> (MixNode, MessageSignature, KeyPair) {
// // hehe stupid workaround for bypassing the immutable borrow and removing duplicate code
//
// let stake = stake.unwrap_or(good_mixnode_pledge());
//
// let keypair = identity::KeyPair::new(&mut rng);
// let identity_key = keypair.public_key().to_base58_string();
// let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng);
//
// let mixnode = MixNode {
// identity_key,
// sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
// ..tests::fixtures::mix_node_fixture()
// };
// let msg = mixnode_bonding_sign_payload(deps, sender, None, mixnode.clone(), stake.clone());
// let owner_signature = ed25519_sign_message(msg, keypair.private_key());
//
// (mixnode, owner_signature, keypair)
// }
let identity_key = keypair.public_key().to_base58_string();
let sphinx_key = legit_sphinx_key.public_key().to_base58_string();
(
MixNode {
identity_key,
sphinx_key,
..tests::fixtures::mix_node_fixture()
},
owner_signature,
keypair,
)
}
pub fn gateway_with_signature(
mut rng: impl RngCore + CryptoRng,
sender: &str,
) -> (Gateway, String) {
let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng);
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let identity_key = keypair.public_key().to_base58_string();
let sphinx_key = legit_sphinx_key.public_key().to_base58_string();
(
Gateway {
identity_key,
sphinx_key,
..tests::fixtures::gateway_fixture()
},
owner_signature,
)
}
// pub fn gateway_with_signature(
// mut rng: impl RngCore + CryptoRng,
// deps: Deps<'_>,
// sender: &str,
// stake: Option<Vec<Coin>>,
// ) -> (Gateway, MessageSignature) {
// let stake = stake.unwrap_or(good_gateway_pledge());
//
// let keypair = identity::KeyPair::new(&mut rng);
// let identity_key = keypair.public_key().to_base58_string();
// let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng);
//
// let gateway = Gateway {
// identity_key,
// sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
// ..tests::fixtures::gateway_fixture()
// };
//
// let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake.clone());
// let owner_signature = ed25519_sign_message(msg, keypair.private_key());
//
// (gateway, owner_signature)
// }
pub fn add_dummy_delegations(mut deps: DepsMut<'_>, env: Env, mix_id: MixId, n: usize) {
for i in 0..n {
@@ -864,39 +1026,22 @@ pub mod test_helpers {
}
}
pub fn add_dummy_mixnodes(
mut rng: impl RngCore + CryptoRng,
mut deps: DepsMut<'_>,
env: Env,
n: usize,
) {
for i in 0..n {
add_mixnode(
&mut rng,
deps.branch(),
env.clone(),
&format!("owner{}", i),
tests::fixtures::good_mixnode_pledge(),
);
}
}
pub fn add_dummy_gateways(
mut rng: impl RngCore + CryptoRng,
mut deps: DepsMut<'_>,
env: Env,
n: usize,
) {
for i in 0..n {
add_gateway(
&mut rng,
deps.branch(),
env.clone(),
&format!("owner{}", i),
tests::fixtures::good_mixnode_pledge(),
);
}
}
// pub fn add_dummy_mixnodes(
// mut rng: impl RngCore + CryptoRng,
// mut deps: DepsMut<'_>,
// env: Env,
// n: usize,
// ) {
// for i in 0..n {
// add_mixnode(
// &mut rng,
// deps.branch(),
// env.clone(),
// &format!("owner{}", i),
// tests::fixtures::good_mixnode_pledge(),
// );
// }
// }
pub fn add_dummy_unbonded_mixnodes(
mut rng: impl RngCore + CryptoRng,
@@ -968,79 +1113,35 @@ pub mod test_helpers {
id
}
// note to whoever wants to refactor this function, you dont want to grab rng here directly
// via `let rng = test_rng()`
// because it's extremely likely you might end up calling `add_mixnode()` multiple times
// in the same test and thus you're going to get mixnodes with the same keys and that's
// not what you want (presumably)
pub fn add_mixnode(
mut rng: impl RngCore + CryptoRng,
deps: DepsMut<'_>,
env: Env,
sender: &str,
pub fn mixnode_bonding_sign_payload(
deps: Deps<'_>,
owner: &str,
proxy: Option<Addr>,
mixnode: MixNode,
stake: Vec<Coin>,
) -> MixId {
let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
) -> SignableMixNodeBondingMsg {
let cost_params = tests::fixtures::mix_node_cost_params_fixture();
let nonce =
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng);
let info = mock_info(sender, &stake);
let key = keypair.public_key().to_base58_string();
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
.may_load(deps.storage)
.unwrap()
.unwrap_or_default();
try_add_mixnode(
deps,
env,
info,
MixNode {
identity_key: key,
sphinx_key: legit_sphinx_key.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
},
tests::fixtures::mix_node_cost_params_fixture(),
owner_signature,
)
.unwrap();
// newly added mixnode gets assigned the current counter + 1
current_id_counter + 1
let payload = MixnodeBondingPayload::new(mixnode, cost_params);
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
SignableMixNodeBondingMsg::new(nonce, content)
}
// same note as with `add_mixnode`
pub fn add_gateway(
mut rng: impl RngCore + CryptoRng,
deps: DepsMut<'_>,
env: Env,
sender: &str,
pub fn gateway_bonding_sign_payload(
deps: Deps<'_>,
owner: &str,
proxy: Option<Addr>,
gateway: Gateway,
stake: Vec<Coin>,
) -> String {
let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
) -> SignableGatewayBondingMsg {
let nonce =
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
let info = mock_info(sender, &stake);
let key = keypair.public_key().to_base58_string();
try_add_gateway(
deps,
env,
info,
Gateway {
identity_key: key.clone(),
..tests::fixtures::gateway_fixture()
},
owner_signature,
)
.unwrap();
key
let payload = GatewayBondingPayload::new(gateway);
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
SignableGatewayBondingMsg::new(nonce, content)
}
fn initial_rewarding_params() -> InitialRewardingParams {
@@ -1,39 +0,0 @@
use crate::contract::query;
use cosmwasm_std::{
from_binary,
testing::{mock_env, MockApi, MockQuerier, MockStorage},
OwnedDeps,
};
use mixnet_contract_common::{GatewayBond, PagedGatewayResponse, QueryMsg};
// I honestly don't know why we're using this way of querying in tests, but I couldn't be bothered to change it
// since I haven't done anything to gateways
pub fn get_gateways(deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>) -> Vec<GatewayBond> {
let result = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetGateways {
start_after: None,
limit: None,
},
)
.unwrap();
let page: PagedGatewayResponse = from_binary(&result).unwrap();
if page.start_next_after.is_some() {
let next_page = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetGateways {
start_after: page.start_next_after,
limit: None,
},
)
.unwrap();
let next_page: PagedGatewayResponse = from_binary(&next_page).unwrap();
if !next_page.nodes.is_empty() {
panic!("we didn't manage to get all gateways in a single query")
}
}
page.nodes
}
+3 -2
View File
@@ -7,6 +7,7 @@ use crate::traits::{
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, NodeFamilies, VestingAccount,
};
use crate::vesting::{populate_vesting_periods, Account};
use contracts_common::signing::MessageSignature;
use contracts_common::ContractBuildInformation;
use cosmwasm_std::{
coin, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order,
@@ -411,7 +412,7 @@ fn try_update_staking_address(
/// Bond a gateway, sends [mixnet_contract_common::ExecuteMsg::BondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
pub fn try_bond_gateway(
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
amount: Coin,
info: MessageInfo,
env: Env,
@@ -448,7 +449,7 @@ pub fn try_track_unbond_gateway(
pub fn try_bond_mixnode(
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
amount: Coin,
info: MessageInfo,
env: Env,
@@ -1,4 +1,5 @@
use crate::errors::ContractError;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Env, Response, Storage};
use mixnet_contract_common::{
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
@@ -12,7 +13,7 @@ pub trait MixnodeBondingAccount {
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
env: &Env,
storage: &mut dyn Storage,
@@ -50,7 +51,7 @@ pub trait GatewayBondingAccount {
fn try_bond_gateway(
&self,
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
env: &Env,
storage: &mut dyn Storage,
@@ -2,6 +2,7 @@ use super::PledgeData;
use crate::errors::ContractError;
use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::traits::GatewayBondingAccount;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128};
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, Gateway};
use vesting_contract_common::events::{
@@ -14,7 +15,7 @@ impl GatewayBondingAccount for Account {
fn try_bond_gateway(
&self,
gateway: Gateway,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
env: &Env,
storage: &mut dyn Storage,
@@ -5,6 +5,7 @@ use super::Account;
use crate::errors::ContractError;
use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::traits::MixnodeBondingAccount;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128};
use mixnet_contract_common::mixnode::MixNodeConfigUpdate;
use mixnet_contract_common::mixnode::MixNodeCostParams;
@@ -32,7 +33,7 @@ impl MixnodeBondingAccount for Account {
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner_signature: MessageSignature,
pledge: Coin,
env: &Env,
storage: &mut dyn Storage,
+7 -6
View File
@@ -47,6 +47,7 @@ mod tests {
use crate::traits::VestingAccount;
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
use crate::vesting::{populate_vesting_periods, Account};
use contracts_common::signing::MessageSignature;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coin, coins, Addr, Coin, Timestamp, Uint128};
use mixnet_contract_common::mixnode::MixNodeCostParams;
@@ -674,7 +675,7 @@ mod tests {
let err = account.try_bond_mixnode(
mix_node.clone(),
cost_params.clone(),
"alice".to_string(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(1_000_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
@@ -687,7 +688,7 @@ mod tests {
let ok = account.try_bond_mixnode(
mix_node.clone(),
cost_params.clone(),
"alice".to_string(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(90_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
@@ -704,7 +705,7 @@ mod tests {
let err = account.try_bond_mixnode(
mix_node,
cost_params,
"alice".to_string(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(10_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
@@ -738,7 +739,7 @@ mod tests {
// Try delegating too much
let err = account.try_bond_gateway(
gateway.clone(),
"alice".to_string(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(1_000_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
@@ -750,7 +751,7 @@ mod tests {
let ok = account.try_bond_gateway(
gateway.clone(),
"alice".to_string(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(90_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
@@ -766,7 +767,7 @@ mod tests {
// Try delegating too much again
let err = account.try_bond_gateway(
gateway,
"alice".to_string(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(500_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "cpu-cycles"
version = "0.1.0"
edition = "2021"
build = "build.rs"
links = "cpucycles"
[dependencies]
libc = "0.2.140"
[build-dependencies]
cfg-if = "1"
+65
View File
@@ -0,0 +1,65 @@
use std::{env, path::PathBuf, process::Command};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = PathBuf::from(&out_dir);
let source_path = PathBuf::from("libcpucycles")
.canonicalize()
.expect("cannot canonicalize path");
cfg_if::cfg_if! {
if #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "mips", target_arch = "powerpc", target_arch = "powerpc64", target_arch = "arm")))] {
panic!("Unsupported architecture - {}!", env::var("CARGO_CFG_TARGET_ARCH").unwrap(), )
}
};
let mut compile_o_command = Command::new("./configure");
let compile_o_command = compile_o_command
.current_dir(&source_path)
.arg(format!("--prefix={out_dir}"));
match compile_o_command.output() {
Ok(output) => {
if !output.status.success() {
panic!("{:?}", unsafe {
std::str::from_utf8_unchecked(&output.stderr)
})
}
}
Err(e) => panic!("{e}"),
}
let mut compile_o_command = Command::new("make");
let compile_o_command = compile_o_command.current_dir(&source_path).arg("install");
match compile_o_command.output() {
Ok(output) => {
if !output.status.success() {
panic!("{:?}", unsafe {
std::str::from_utf8_unchecked(&output.stderr)
})
}
}
Err(e) => panic!("{e}"),
}
println!(
"cargo:rustc-link-search=native={}",
out_path.join("lib").to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=cpucycles");
let mut compile_o_command = Command::new("make");
let compile_o_command = compile_o_command.current_dir(source_path).arg("clean");
match compile_o_command.output() {
Ok(output) => {
if !output.status.success() {
panic!("{:?}", unsafe {
std::str::from_utf8_unchecked(&output.stderr)
})
}
}
Err(e) => panic!("{e}"),
}
}
+8
View File
@@ -0,0 +1,8 @@
default:
cd build && $(MAKE)
install:
cd build && $(MAKE) install
clean:
cd build && $(MAKE) clean
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import os
import datetime
import markdown
def load(fn):
with open(fn) as f:
return f.read()
style = load('autogen/html-style')
sitetitle = load('autogen/html-title')
files = []
with open('autogen/html-files') as f:
for line in f:
line = line.strip()
line = line.split(':')
if len(line) != 3: continue
files += [line]
for md,html,pagetitle in files:
fnmd = 'doc/%s.md' % md
fnhtml = 'doc/html/%s.html' % html
output = ''
x = load(fnmd)
x = markdown.markdown(x,extensions=['markdown.extensions.extra','markdown.extensions.tables'])
mtime = datetime.datetime.utcfromtimestamp(os.path.getmtime(fnmd)).strftime('%Y.%m.%d')
output += '<html>\n<head>\n'
output += style
output += '<title>\n'
output += pagetitle
output += '</title>\n'
output += '</head>\n'
output += '<body>\n'
output += '<div class=headline>\n'
output += sitetitle
output += '</div>\n'
output += '<div class=nav>\n'
for submd,subhtml,subpagetitle in files:
if subhtml == html:
output += '<div class="navt here">'
output += pagetitle+'\n'
else:
output += '<div class="navt away">'
output += '<a href=%s.html>%s</a>\n' % (subhtml,subpagetitle)
output += '</div>'
output += '</div>\n'
output += '<div class=main>\n'
output += x
output += '<hr><font size=1><b>Version:</b>\n'
output += 'This is version %s of the "%s" web page.\n' % (mtime,pagetitle)
output += '</font>\n'
output += '</div>\n'
output += '</body>\n'
output += '</html>\n'
if not os.path.exists(fnhtml) or output != load(fnhtml):
with open(fnhtml+'.new','w') as f:
f.write(output)
os.chmod(fnhtml+'.new',0o444)
os.rename(fnhtml+'.new',fnhtml)
@@ -0,0 +1,7 @@
readme:index:Intro
download:download:Download
install:install:Install
api:api:API
counters:counters:Counters
selection:selection:Selection
security:security:Security
@@ -0,0 +1,32 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
html{overflow-y:scroll}
body{font-family:sans-serif}
p,ul,ol,blockquote,pre{font-size:0.9em;line-height:1.6em}
li p{font-size:1.0em}
blockquote p{font-size:1.0em}
tt{font-size:1.2em}
code{font-size:1.2em}
h1{font-size:1.5em}
h2{font-size:1.3em}
h3{font-size:1.0em}
h1 a{text-decoration:none}
table{border-collapse:collapse}
th,td{border:1px solid black}
table a{text-decoration:none}
table tr{font-size:0.9em;line-height:1.6em}
.links a:hover{text-decoration:underline}
.links a:active{text-decoration:underline}
.links img{width:200px;padding-left:1em}
.links td{border:0px;padding-top:0.5em;padding-bottom:0.5em}
.headline{padding:0;font-weight:bold;font-size:1.5em;vertical-align:top;padding-bottom:0.5em;color:#125d0d}
.navt{display:inline-block;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;
min-width:14%;margin:0;padding:0;padding-left:0.5em;padding-right:0.5em;vertical-align:center;
font-weight:bold;font-size:1.1em;text-align:center;border:1px solid black}
.here{border-bottom:0px;background-color:#ffffff}
.away{background-color:#125d0d;}
.away a{text-decoration:none;display:block;color:#ffffff}
.away a:hover,.away a:active{text-decoration:underline}
.main{margin:0;padding-top:0em;padding-bottom:1%;clear:both}
</style>
@@ -0,0 +1 @@
libcpucycles
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
pandoc --standalone --to man --metadata title=cpucycles --metadata section=3 < doc/api.md > doc/man/cpucycles.3
@@ -0,0 +1,93 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <cpucycles.h>
#define TIMINGS 63
static long long t[TIMINGS+1];
static void t_print(void)
{
long long median = 0;
long long i,j;
for (i = 0;i < TIMINGS;++i)
t[i] = t[i+1]-t[i];
for (j = 0;j < TIMINGS;++j) {
long long belowj = 0;
long long abovej = 0;
for (i = 0;i < TIMINGS;++i) if (t[i] < t[j]) ++belowj;
for (i = 0;i < TIMINGS;++i) if (t[i] > t[j]) ++abovej;
if (belowj*2 < TIMINGS && abovej*2 < TIMINGS) {
median = t[j];
break;
}
}
printf(" %lld ",median);
for (i = 0;i < TIMINGS;++i)
printf("%+lld",t[i]-median);
printf("\n");
fflush(stdout);
}
static long long microseconds(void)
{
struct timeval t;
long long result;
gettimeofday(&t,(struct timezone *) 0);
result = t.tv_sec;
result *= 1000000;
result += t.tv_usec;
return result;
}
static volatile int v;
static void measure_cpucycles(void)
{
long long loops,i,j;
printf("cpucycles persecond %lld\n",cpucycles_persecond());
printf("cpucycles implementation %s\n",cpucycles_implementation());
for (i = 0;i <= TIMINGS;++i)
t[i] = cpucycles();
printf("cpucycles median"); t_print();
for (loops = 1024;loops <= 1048576;loops *= 2) {
long long t00,t01,t10,t11;
long long m0,m1;
double ratiobelow,ratioabove;
t00 = cpucycles();
m0 = microseconds();
t01 = cpucycles();
for (j = 0;j < loops;++j) v = 0;
t10 = cpucycles();
m1 = microseconds();
t11 = cpucycles();
if (t01 < t00) continue;
if (t10 < t01) continue;
if (t11 < t10) continue;
if (m1 <= m0+2) continue;
ratiobelow = floor((1000000.0*(t10-t01))/(m1+1-m0));
ratioabove = ceil((1000000.0*(t11-t00))/(m1-m0-1));
printf("cpucycles observed persecond %.0lf...%.0lf with %lld loops %lld microseconds\n",ratiobelow,ratioabove,loops,m1-m0);
}
}
int main(int argc,char **argv)
{
cpucycles_tracesetup();
printf("cpucycles version %s\n",cpucycles_version());
measure_cpucycles();
return 0;
}
@@ -0,0 +1,2 @@
gcc -Wall -fPIC -fwrapv -O -fvisibility=hidden
clang -Wall -fPIC -fwrapv -Qunused-arguments -O -fvisibility=hidden
+309
View File
@@ -0,0 +1,309 @@
#!/usr/bin/env python3
import os
import shutil
import sys
import platform
import subprocess
import tempfile
prefix = '/usr/local'
clean = True
linktype = 'so'
host = platform.machine()
host = ''.join(c for c in host if c in '_0123456789abcdefghijklmnopqrstuvwxyz')
if host == 'x86_64': host = 'amd64'
if host == 'i386': host = 'x86'
if host == 'i686': host = 'x86'
if host.startswith('armv8') or host.startswith('aarch64'): host = 'arm64'
if host.startswith('arm'): host = 'arm32'
if host.startswith('riscv64'): host = 'riscv64'
if host.startswith('riscv'): host = 'riscv32'
if host.startswith('mips64'): host = 'mips64'
if host.startswith('mips'): host = 'mips32'
if host.startswith('powerpc64') or host.startswith('ppc64'): host = 'ppc64'
if host.startswith('powerpc') or host.startswith('ppc'): host = 'ppc32'
if host.startswith('sparcv9') or host.startswith('sun4u'): host = 'sparc64'
if host.startswith('sparc') or host.startswith('sun'): host = 'sparc32'
makefile = ''
for arg in sys.argv[1:]:
if arg.startswith('--prefix='):
prefix = arg[9:]
continue
if arg.startswith('--host='):
host = arg[7:]
host = host.split('-')[0]
continue
if arg == '--clean':
clean = True
continue
if arg == '--noclean':
clean = False
continue
raise ValueError('unrecognized argument %s' % arg)
echoargs = './configure'
echoargs += ' --prefix=%s' % prefix
echoargs += ' --host=%s' % host
if clean: echoargs += ' --clean'
if not clean: echoargs += ' --noclean'
print(echoargs)
if prefix[0] != '/':
raise ValueError('prefix %s is not an absolute path' % prefix)
rpath = None
# XXX: rpath = '%s/lib' % prefix
if clean:
shutil.rmtree('build/%s' % host,ignore_errors=True)
def dirlinksym(dir,source,target):
with tempfile.TemporaryDirectory(dir=dir) as t:
os.symlink(target,'%s/symlink' % t)
os.rename('%s/symlink' % t,'%s/%s' % (dir,source))
os.makedirs('build/%s' % host,exist_ok=True)
os.makedirs('build/%s/package/bin' % host,exist_ok=True)
os.makedirs('build/%s/package/lib' % host,exist_ok=True)
os.makedirs('build/%s/package/include' % host,exist_ok=True)
if clean:
os.symlink('../..','build/%s/src' % host)
# ----- build scripts
os.makedirs('build/%s/scripts'%host,exist_ok=True)
dirlinksym('build/%s/scripts'%host,'install','../src/scripts-build/install')
# ----- compilers
def compilerversion(c):
try:
p = subprocess.Popen(c.split()+['--version'],stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal_newlines=True)
out,err = p.communicate()
assert not err
assert not p.returncode
return out
except:
pass
firstcompiler = None
with open('compilers/default') as f:
for c in f.readlines():
c = c.strip()
cv = compilerversion(c)
if cv == None:
print('skipping default compiler %s' % c)
continue
print('using default compiler %s' % c)
firstcompiler = c
break
if firstcompiler is None:
raise ValueError('did not find a working compiler')
with open('build/%s/scripts/compiledefault' % host,'w') as f:
f.write('#!/bin/sh\n')
f.write('\n')
f.write('dir="$1"; shift\n')
f.write('base="$1"; shift\n')
f.write('ext="$1"; shift\n')
f.write('\n')
f.write('cd "$dir" && \\\n')
f.write('%s \\\n' % firstcompiler)
f.write(' "$@" \\\n')
f.write(' -c "$base.$ext"\n')
os.chmod('build/%s/scripts/compiledefault' % host,0o755)
# ----- libcpucycles
os.makedirs('build/%s/cpucycles' % host,exist_ok=True)
os.makedirs('build/%s/package/man/man3' % host,exist_ok=True)
dirlinksym('build/%s/cpucycles'%host,'cpucycles.h','../src/cpucycles/cpucycles.h')
dirlinksym('build/%s/cpucycles'%host,'cpucycles_internal.h','../src/cpucycles/cpucycles_internal.h')
shutil.copy2('cpucycles/cpucycles.h','build/%s/package/include/cpucycles.h'%host)
shutil.copy2('doc/man/cpucycles.3','build/%s/package/man/man3/cpucycles.3'%host)
with open('build/%s/cpucycles/compile-ticks' % host,'w') as f:
f.write('#!/bin/sh\n')
f.write('arch="$1"; shift\n')
f.write('x="$1"; shift\n')
f.write('for source in try-"$arch"-"$x".c try-default-zero.c\n')
f.write('do\n')
f.write(' cp "$source" "$arch"-"$x".c\n')
f.write(' %s \\\n' % firstcompiler)
f.write(' -Dticks=cpucycles_ticks_"$arch"_"$x" \\\n')
f.write(' -Dticks_setup=cpucycles_ticks_"$arch"_"$x"_setup \\\n')
f.write(' -c "$arch"-"$x".c\n')
f.write(' case $? in\n')
f.write(' 0) break ;;\n')
f.write(' 111) exit 111 ;;\n')
f.write(' *) echo "skipping option that did not compile" ;;\n')
f.write(' esac\n')
f.write('done\n')
os.chmod('build/%s/cpucycles/compile-ticks' % host,0o755)
cpucyclesoptions = []
cpucyclesofiles = []
with open('cpucycles/options') as f:
for line in f:
line = line.strip()
if line == '': continue
if line[0] == '#': continue
base = line.split()[0]
if not os.path.exists('cpucycles/%s.c' % base): continue
cpucycles = base.split('-')
if len(cpucycles) != 2: continue
if cpucycles[0] not in (host,'default'): continue
cpucyclesoptions += [cpucycles]
cpucyclesoptions += [['default','zero']] # must be last
for cpucycles in cpucyclesoptions:
base = '-'.join(cpucycles)
cpucyclesofiles += ['cpucycles/%s.o' % base]
dirlinksym('build/%s/cpucycles'%host,'try-%s.c'%base,'../src/cpucycles/%s.c'%base)
M = 'cpucycles/%s.o: cpucycles/try-%s.c cpucycles/try-default-zero.c\n' % (base,base)
M += '\tcd cpucycles && ./compile-ticks %s %s\n' % tuple(cpucycles)
M += '\n'
makefile = M + makefile
for fn in sorted(os.listdir('cpucycles')):
if not fn.endswith('.c'): continue
if '-' in fn: continue
base = fn[:-2]
cpucyclesofiles += ['cpucycles/%s.o' % base]
dirlinksym('build/%s/cpucycles'%host,fn,'../src/cpucycles/%s'%fn)
M = 'cpucycles/%s.o: cpucycles/%s.c\n' % (base,base)
M += '\tscripts/compiledefault cpucycles %s c\n' % base
M += '\n'
makefile = M + makefile
with open('build/%s/cpucycles/options.inc' % host,'w') as f:
f.write('#define NUMOPTIONS %d\n' % len(cpucyclesoptions))
f.write('#define DEFAULTOPTION (NUMOPTIONS-1)\n')
f.write('\n')
for cpucycles in cpucyclesoptions:
f.write('extern long long cpucycles_ticks_%s_%s_setup(void);\n' % (cpucycles[0],cpucycles[1]))
f.write('extern long long cpucycles_ticks_%s_%s(void);\n' % (cpucycles[0],cpucycles[1]))
f.write('\n')
f.write('static struct {\n')
f.write(' const char *implementation;\n')
f.write(' long long (*ticks_setup)(void);\n')
f.write(' long long (*ticks)(void);\n')
f.write('} options[NUMOPTIONS] = {\n')
for cpucycles in cpucyclesoptions:
f.write('{ "%s-%s", cpucycles_ticks_%s_%s_setup, cpucycles_ticks_%s_%s },\n' % (cpucycles[0],cpucycles[1],cpucycles[0],cpucycles[1],cpucycles[0],cpucycles[1]))
f.write('} ;\n')
dirlinksym('build/%s/scripts'%host,'staticlib','../src/scripts-build/staticlib')
M = 'package/lib/libcpucycles.a: scripts/staticlib %s\n' % ' '.join(cpucyclesofiles)
M += '\tscripts/staticlib %s\n' % ' '.join(cpucyclesofiles)
M += '\n'
makefile = M + makefile
with open('build/%s/scripts/sharedlib' % host,'w') as f:
f.write('#!/bin/sh\n')
f.write('\n')
f.write('%s -shared \\\n' % firstcompiler)
if rpath:
f.write(' -Wl,-rpath=%s \\\n' % rpath)
f.write(' -Wl,-soname,libcpucycles.so.1 \\\n')
f.write(' -o package/lib/libcpucycles.so.1 \\\n')
f.write(' "$@"\n')
f.write('chmod 644 package/lib/libcpucycles.so.1\n')
os.chmod('build/%s/scripts/sharedlib' % host,0o755)
M = 'package/lib/libcpucycles.so.1: scripts/sharedlib %s\n' % ' '.join(cpucyclesofiles)
M += '\tscripts/sharedlib %s\n' % ' '.join(cpucyclesofiles)
M += '\n'
makefile = M + makefile
M = 'package/lib/libcpucycles.so: package/lib/libcpucycles.so.1\n'
M += '\trm -f package/lib/libcpucycles.so\n'
M += '\tln -s libcpucycles.so.1 package/lib/libcpucycles.so\n'
M += '\n'
makefile = M + makefile
# ----- command
os.makedirs('build/%s/command'%host)
for c in sorted(os.listdir('command')):
dirlinksym('build/%s/command'%host,c,'../src/command/%s'%c)
dirlinksym('build/%s/command'%host,'bin','../package/bin')
dirlinksym('build/%s/command'%host,'lib','../package/lib')
dirlinksym('build/%s/command'%host,'include','../package/include')
with open('build/%s/command/link' % host,'w') as f:
f.write('#!/bin/sh\n')
f.write('target="$1"; shift\n')
f.write('%s \\\n' % firstcompiler)
f.write(' -o "$target" "$@"\n')
os.chmod('build/%s/command/link' % host,0o755)
commands = []
for fn in sorted(os.listdir('command')):
if not fn.endswith('.c'): continue
libs = ['libcpucycles']
base = fn[:-2]
M = 'command/%s.o: command/%s.c\n' % (base,base)
M += '\tscripts/compiledefault command %s c -I include\n' % base
M += '\n'
makefile = M + makefile
M = 'package/bin/%s: command/%s.o%s\n' % (base,base,''.join(' package/lib/%s.%s' % (x,linktype) for x in libs))
M += '\tcd command && ./link bin/%s %s.o%s -lm -lrt\n' % (base,base,''.join(' lib/%s.%s' % (x,linktype) for x in libs))
M += '\n'
makefile = M + makefile
commands += ['package/bin/%s' % base]
M = 'commands: %s\n' % ' '.join(commands)
M += '\n'
makefile = M + makefile
# ----- make install
M = 'install: scripts/install default\n'
M += '\tscripts/install %s\n' % prefix
M += '\n'
makefile = M + makefile
# ----- make default
M = 'default: package/lib/libcpucycles.a package/lib/libcpucycles.so package/lib/libcpucycles.so.1 \\\n'
M += 'commands\n'
M += '\n'
makefile = M + makefile
with open('build/%s/Makefile' % host,'w') as f:
f.write(makefile)
# ----- build/0, build/Makefile
dirlinksym('build','0',host)
with open('build/Makefile','w') as f:
f.write('default:\n')
f.write('\tcd %s && $(MAKE)\n' % host)
f.write('\n')
f.write('install:\n')
f.write('\tcd %s && $(MAKE) install\n' % host)
f.write('\n')
f.write('clean:\n')
f.write('\trm -r %s\n' % host)
@@ -0,0 +1,53 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/amd64rdpmc.c
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include "cpucycles_internal.h"
static struct perf_event_attr attr;
static int fdperf = -1;
static struct perf_event_mmap_page *buf = 0;
long long ticks(void)
{
long long result;
unsigned int seq;
long long index;
long long offset;
do {
seq = buf->lock;
asm volatile("" ::: "memory");
index = buf->index;
offset = buf->offset;
asm volatile("rdpmc;shlq $32,%%rdx;orq %%rdx,%%rax"
: "=a"(result) : "c"(index-1) : "%rdx");
asm volatile("" ::: "memory");
} while (buf->lock != seq);
result += offset;
result &= 0xffffffffffff;
return result;
}
long long ticks_setup(void)
{
if (fdperf == -1) {
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_CPU_CYCLES;
attr.exclude_kernel = 1;
fdperf = syscall(__NR_perf_event_open,&attr,0,-1,-1,0);
if (fdperf == -1) return cpucycles_SKIP;
buf = mmap(NULL,sysconf(_SC_PAGESIZE),PROT_READ,MAP_SHARED,fdperf,0);
}
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_CYCLECOUNTER;
}
@@ -0,0 +1,22 @@
// version 20230105
// public domain
// djb
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
#include "cpucycles_internal.h"
long long ticks(void)
{
return __rdtsc();
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_MAYBECYCLECOUNTER;
}
@@ -0,0 +1,20 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/amd64tscfreq.c
#include "cpucycles_internal.h"
long long ticks(void)
{
unsigned long long result;
asm volatile(".byte 15;.byte 49;shlq $32,%%rdx;orq %%rdx,%%rax"
: "=a"(result) :: "%rdx");
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_MAYBECYCLECOUNTER;
}
@@ -0,0 +1,27 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/cortex.c
#include "cpucycles_internal.h"
long long ticks(void)
{
unsigned int result;
asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(result));
return (unsigned long long) result;
}
static long enable(void)
{
asm volatile("mcr p15, 0, %0, c9, c12, 0" :: "r"(17));
asm volatile("mcr p15, 0, %0, c9, c12, 1" :: "r"(0x8000000f));
asm volatile("mcr p15, 0, %0, c9, c12, 3" :: "r"(0x8000000f));
}
long long ticks_setup(void)
{
if (!cpucycles_works(enable)) return cpucycles_SKIP;
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_EXTEND32;
}
@@ -0,0 +1,19 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/armv8.c
#include "cpucycles_internal.h"
long long ticks(void)
{
long long result;
asm volatile("mrs %0, PMCCNTR_EL0" : "=r" (result));
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_CYCLECOUNTER;
}
@@ -0,0 +1,19 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/vct.c
#include "cpucycles_internal.h"
long long ticks(void)
{
long long result;
asm volatile("mrs %0, CNTVCT_EL0" : "=r" (result));
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_FINDMULTIPLIER;
}
@@ -0,0 +1,25 @@
// version 20230115
// public domain
// djb
// 20230115 djb: cpucycles_version()
// 20230114 djb: improve punctuation
#ifndef cpucycles_h
#define cpucycles_h
#ifdef __cplusplus
extern "C" {
#endif
extern long long (*cpucycles)(void) __attribute__((visibility("default")));
extern const char *cpucycles_implementation(void) __attribute__((visibility("default")));
extern const char *cpucycles_version(void) __attribute__((visibility("default")));
extern long long cpucycles_persecond(void) __attribute__((visibility("default")));
extern void cpucycles_tracesetup(void) __attribute__((visibility("default")));
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,20 @@
// version 20230105
// public domain
// djb
#ifndef cpucycles_internal_h
#define cpucycles_internal_h
extern long long cpucycles_init(void);
extern long long cpucycles_microseconds(void);
extern int cpucycles_works(long long (*)(void));
// return values from ticks_setup():
#define cpucycles_SKIP (0)
#define cpucycles_CYCLECOUNTER (-1)
#define cpucycles_MAYBECYCLECOUNTER (-2)
#define cpucycles_FINDMULTIPLIER (-3)
#define cpucycles_EXTEND32 (-32)
// and positive values mean known ticks/second
#endif
@@ -0,0 +1,15 @@
// version 20230105
// public domain
// djb
#include "cpucycles_internal.h"
long long ticks_setup(void)
{
return 1000000;
}
long long ticks(void)
{
return cpucycles_microseconds();
}
@@ -0,0 +1,17 @@
// version 20230105
// public domain
// djb
#include <mach/mach_time.h>
#include "cpucycles_internal.h"
long long ticks(void)
{
return mach_absolute_time();
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_FINDMULTIPLIER;
}
@@ -0,0 +1,23 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/monotonic.c
#include <time.h>
#include <sys/time.h>
long long ticks_setup(void)
{
return 1000000000;
}
long long ticks(void)
{
struct timespec t;
long long result;
clock_gettime(CLOCK_MONOTONIC,&t);
result = t.tv_sec;
result *= 1000000000;
result += t.tv_nsec;
return result;
}
@@ -0,0 +1,101 @@
// version 20230106
// public domain
// djb
// adapted from supercop/cpucycles/perfevent.c
// 20230106 djb: read() into int64_t instead of long long
// 20230106 djb: add comment on RUNNING/ENABLED
/*
This code intentionally avoids dividing by the
PERF_FORMAT_TOTAL_TIME_RUNNING/ENABLED ratio.
The motivation for that ratio is as follows:
* A typical CPU has a limited number of performance-monitoring
counters active at once. For example, there are 8 "programmable"
counters on Intel Skylake.
* "perf stat" allows the user to enable more counters. The OS kernel
periodically (e.g., every millisecond) changes the limited number of
active hardware counters to a new subset of the enabled counters, and
"perf stat" reports PERF_FORMAT_TOTAL_TIME_RUNNING/ENABLED for each
counter, the fraction of time spent with that counter running.
For long-running programs, dividing the hardware counter by
RUNNING/ENABLED usually produces a reasonable estimate of what the count
would have been without competition from other counters.
A fixable problem with this multiplexing of counters is that the kernel
appears to simply cycle through counters, so unlucky programs can
trigger moiré effects. The fix is to select random subsets of counters.
A more fundamental problem is that cpucycles() has to be usable for
timing short subroutines, including subroutines so short that the OS has
no opportunity to change from one selection of counters to another. Say
RUNNING is 0; should cpucycles() then divide by 0?
If a caller runs cpucycles(), X(), cpucycles(), X(), etc., and the cycle
counter happens to be enabled for only 80% of the runs of X(), then
simply computing the median difference of adjacent cycle counts, with no
scaling, will filter out the zeros and correctly compute the cost of X.
Averages won't (without scaling), but averages have other problems, such
as being heavily influenced by interrupts. (Omitting kernel time from
perf results does not remove the influence of interrupts on caches.)
Given the importance of cycle counting, it is better to have cycle
counters always running. For example, on Skylake, Intel provides the 8
"programmable" counters on top of a separate cycle counter ("fixed
counter 1"), so there is no good reason for the kernel to waste a
"programmable" counter on a cycle counter, there is no good reason to
turn the cycle counter off, and there is no good reason for RUNNING to
be below ENABLED for the cycle counter.
Of course, applications that use just one performance counter at a time
don't have to worry about kernels getting this wrong, and don't have to
worry about the possibility of getting noisy or invalid results on CPUs
that have heavier constraints on the number of simultaneous counters.
*/
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include "cpucycles_internal.h"
static int fddev = -1;
long long ticks(void)
{
int64_t result;
if (read(fddev,&result,sizeof result) < sizeof result) return 0;
return result;
}
long long ticks_setup(void)
{
if (fddev == -1) {
static struct perf_event_attr attr;
memset(&attr,0,sizeof attr);
attr.type = PERF_TYPE_HARDWARE;
attr.size = sizeof(struct perf_event_attr);
attr.config = PERF_COUNT_HW_CPU_CYCLES;
attr.disabled = 1;
attr.exclude_kernel = 1;
attr.exclude_hv = 1;
fddev = syscall(__NR_perf_event_open,&attr,0,-1,-1,0);
if (fddev == -1) return cpucycles_SKIP;
ioctl(fddev,PERF_EVENT_IOC_RESET,0);
ioctl(fddev,PERF_EVENT_IOC_ENABLE,0);
}
return cpucycles_MAYBECYCLECOUNTER;
}
@@ -0,0 +1,15 @@
// version 20230105
// public domain
// djb
#include "cpucycles_internal.h"
long long ticks_setup(void)
{
return cpucycles_SKIP;
}
long long ticks(void)
{
return 0;
}
@@ -0,0 +1,33 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/mips.c
// mips32 release 2 instruction rdhwr
// 7c02103b: read hwr#2 (cycle count) into $2
// 7c02183b: read hwr#3 (cycle-count multiplier) into $2
#include "cpucycles_internal.h"
static unsigned int multiplier = 0;
static long long multiplier_set(void)
{
asm volatile(".long 0x7c02183b; move %0,$2" : "=r"(multiplier) : : "$2");
return multiplier;
}
long long ticks(void)
{
unsigned int result;
asm volatile(".long 0x7c02103b; move %0,$2" : "=r"(result) :: "$2");
result *= multiplier;
return (unsigned long long) result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(multiplier_set)) return cpucycles_SKIP;
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_EXTEND32;
}
+19
View File
@@ -0,0 +1,19 @@
amd64-pmc
amd64-tsc
amd64-tscasm
arm32-cortex
arm64-pmc
arm64-vct
mips64-cc
ppc32-mftb
ppc64-mftb
riscv32-rdcycle
riscv64-rdcycle
s390x-stckf
sparc64-rdtick
x86-tsc
x86-tscasm
default-perfevent
default-mach
default-monotonic
default-gettimeofday
@@ -0,0 +1,30 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/powerpccpuinfo.c
#include "cpucycles_internal.h"
long long ticks(void)
{
unsigned int high, low, newhigh;
unsigned long long result;
do {
asm volatile(
"mftbu %0; mftb %1; mftbu %2"
: "=r" (high), "=r" (low), "=r" (newhigh)
);
} while (newhigh != high);
result = high;
result <<= 32;
result |= low;
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_FINDMULTIPLIER;
}
@@ -0,0 +1,30 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/powerpccpuinfo.c
#include "cpucycles_internal.h"
long long ticks(void)
{
unsigned int high, low, newhigh;
unsigned long long result;
do {
asm volatile(
"mftbu %0; mftb %1; mftbu %2"
: "=r" (high), "=r" (low), "=r" (newhigh)
);
} while (newhigh != high);
result = high;
result <<= 32;
result |= low;
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_FINDMULTIPLIER;
}
@@ -0,0 +1,39 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/riscv.c
// which has code from djb and Romain Dolbeau
#include "cpucycles_internal.h"
#ifndef __riscv_xlen
#error this code is only for riscv platforms
#endif
#if __riscv_xlen != 32
#error this code is only for riscv32 platforms
#endif
long long ticks(void)
{
unsigned int low, high, newhigh;
unsigned long long result;
asm volatile( "start%=:\n"
"rdcycleh %0\n"
"rdcycle %1\n"
"rdcycleh %2\n"
"bne %0, %2, start%=\n"
: "=r"(high), "=r"(low), "=r"(newhigh));
result = high;
result <<= 32;
result |= low;
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_CYCLECOUNTER;
}
@@ -0,0 +1,29 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/riscv.c
// which has code from djb and Romain Dolbeau
#include "cpucycles_internal.h"
#ifndef __riscv_xlen
#error this code is only for riscv platforms
#endif
#if __riscv_xlen != 64
#error this code is only for riscv64 platforms
#endif
long long ticks(void)
{
long long result;
asm volatile("rdcycle %0" : "=r" (result));
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_CYCLECOUNTER;
}
@@ -0,0 +1,20 @@
// version 20230106
// public domain
// djb
// adapted from sparc64-rdtick.c
#include "cpucycles_internal.h"
long long ticks(void)
{
long long result;
asm volatile("stckf 0(%0)" :: "a"(&result) : "memory","cc");
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return 4096000000; // manual says 2^12 per microsecond
}
@@ -0,0 +1,24 @@
// version 20230105
// public domain
// djb
// adapted from supercop/cpucycles/sparccpuinfo.c
#include "cpucycles_internal.h"
#if defined(__sparcv8) || defined(__sparcv8plus)
#error this code is only for sparc64 platforms
#endif
long long ticks(void)
{
long long result;
asm volatile("rd %%tick,%0" : "=r" (result));
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_CYCLECOUNTER;
}
+420
View File
@@ -0,0 +1,420 @@
// version 20230115
// public domain
// djb
// includes some pieces adapted from supercop
// 20230115 djb: cpucycles_version()
// 20230106 djb: support "cpu MHz static" (ibm z15)
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <signal.h>
#include <setjmp.h>
#include "cpucycles.h"
#include "cpucycles_internal.h"
static int tracesetup = 0;
void cpucycles_tracesetup(void)
{
tracesetup = 1;
}
static jmp_buf crash_jmp;
static void crash(int s)
{
siglongjmp(crash_jmp,0);
}
int cpucycles_works(long long (*ticks)(void))
{
volatile int result = 0;
struct sigaction old_sigill;
struct sigaction old_sigfpe;
struct sigaction old_sigbus;
struct sigaction old_sigsegv;
struct sigaction crash_action;
memset(&crash_action,0,sizeof crash_action);
crash_action.sa_handler = crash;
sigaction(SIGILL,0,&old_sigill);
sigaction(SIGFPE,0,&old_sigfpe);
sigaction(SIGBUS,0,&old_sigbus);
sigaction(SIGSEGV,0,&old_sigsegv);
if (!sigsetjmp(crash_jmp,1)) {
sigaction(SIGILL,&crash_action,0);
sigaction(SIGFPE,&crash_action,0);
sigaction(SIGBUS,&crash_action,0);
sigaction(SIGSEGV,&crash_action,0);
ticks();
result = 1;
}
sigaction(SIGILL,&old_sigill,0);
sigaction(SIGFPE,&old_sigfpe,0);
sigaction(SIGBUS,&old_sigbus,0);
sigaction(SIGSEGV,&old_sigsegv,0);
return result;
}
static double osfreq(void)
{
FILE *f;
char *x;
double result;
int s;
f = fopen("/etc/cpucyclespersecond", "r");
if (f) {
s = fscanf(f,"%lf",&result);
fclose(f);
if (s > 0) return result;
}
f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed", "r");
if (f) {
s = fscanf(f,"%lf",&result);
fclose(f);
if (s > 0) return 1000.0 * result;
}
f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r");
if (f) {
s = fscanf(f,"%lf",&result);
fclose(f);
if (s > 0) return 1000.0 * result;
}
f = fopen("/sys/devices/system/cpu/cpu0/clock_tick", "r");
if (f) {
s = fscanf(f,"%lf",&result);
fclose(f);
if (s > 0) return result;
}
f = fopen("/proc/cpuinfo","r");
if (f) {
for (;;) {
s = fscanf(f,"cpu MHz : %lf",&result);
if (s > 0) break;
if (s == 0) s = fscanf(f,"%*[^\n]\n");
if (s < 0) { result = 0; break; }
}
fclose(f);
if (result) return 1000000.0 * result;
}
f = fopen("/proc/cpuinfo","r");
if (f) {
for (;;) {
s = fscanf(f,"clock : %lf",&result);
if (s > 0) break;
if (s == 0) s = fscanf(f,"%*[^\n]\n");
if (s < 0) { result = 0; break; }
}
fclose(f);
if (result) return 1000000.0 * result;
}
f = fopen("/proc/cpuinfo","r");
if (f) {
for (;;) {
s = fscanf(f,"cpu MHz static : %lf",&result);
if (s > 0) break;
if (s == 0) s = fscanf(f,"%*[^\n]\n");
if (s < 0) { result = 0; break; }
}
fclose(f);
if (result) return 1000000.0 * result;
}
f = popen("sysctl hw.cpufrequency 2>/dev/null","r");
if (f) {
s = fscanf(f,"hw.cpufrequency: %lf",&result);
pclose(f);
if (s > 0) if (result > 0) return result;
}
f = popen("/usr/sbin/lsattr -E -l proc0 -a frequency 2>/dev/null","r");
if (f) {
s = fscanf(f,"frequency %lf",&result);
pclose(f);
if (s > 0) return result;
}
f = popen("/usr/sbin/psrinfo -v 2>/dev/null","r");
if (f) {
for (;;) {
s = fscanf(f," The %*s processor operates at %lf MHz",&result);
if (s > 0) break;
if (s == 0) s = fscanf(f,"%*[^\n]\n");
if (s < 0) { result = 0; break; }
}
pclose(f);
if (result) return 1000000.0 * result;
}
x = getenv("cpucyclespersecond");
if (x) {
s = sscanf(x,"%lf",&result);
if (s > 0) return result;
}
return 2399987654.0;
}
static long long persecond = 0;
static const char *implementation = "none";
long long (*cpucycles)(void) = cpucycles_init;
const char *cpucycles_implementation(void)
{
cpucycles();
return implementation;
}
long long cpucycles_persecond(void)
{
cpucycles();
return persecond;
}
const char *cpucycles_version(void)
{
return "20230115";
}
// ----- cycle counter scaled from ticks
static double cpucycles_scaled_scaling = 0;
static long long cpucycles_scaled_offset = 0;
static long long (*cpucycles_scaled_from)(void) = 0;
static long long cpucycles_scaled(void)
{
return (cpucycles_scaled_from()-cpucycles_scaled_offset)*cpucycles_scaled_scaling;
}
// ----- cycle counter extended from 32-bit ticks
static long long (*cpucycles_extend32_from)(void) = 0;
static uint32_t cpucycles_extend32_prev_ticks;
static long long cpucycles_extend32_prev_us;
static long long cpucycles_extend32_prev_cycles;
static void cpucycles_extend32_setup(void)
{
long long (*ticks)(void) = cpucycles_extend32_from;
cpucycles_extend32_prev_ticks = ticks();
cpucycles_extend32_prev_us = cpucycles_microseconds();
cpucycles_extend32_prev_cycles = 0;
}
static long long cpucycles_extend32(void)
{
long long (*ticks)(void) = cpucycles_extend32_from;
uint32_t new_ticks = ticks();
unsigned long long delta_ticks = new_ticks-cpucycles_extend32_prev_ticks;
long long new_us = cpucycles_microseconds();
long long delta_us = new_us-cpucycles_extend32_prev_us;
// assume that number of cycles cannot increase by 2^32 in 2ms
if (delta_us < 1000)
return cpucycles_extend32_prev_cycles+delta_ticks;
cpucycles_extend32_prev_ticks = new_ticks;
cpucycles_extend32_prev_us = new_us;
if (delta_us >= 2000) {
long long target = (delta_us*0.000001)*persecond;
while (delta_ticks+2147483648ULL < target)
delta_ticks += 4294967296ULL;
}
return cpucycles_extend32_prev_cycles += delta_ticks;
}
// ----- estimating cycles per tick
long long cpucycles_microseconds(void)
{
struct timeval t;
long long result;
gettimeofday(&t,(struct timezone *) 0);
result = t.tv_sec;
result *= 1000000;
result += t.tv_usec;
return result;
}
static double estimate_cyclespertick(long long (*ticks)(void))
{
long long t0,t1,us0,us1;
t0 = ticks();
us0 = cpucycles_microseconds();
do {
t1 = ticks();
us1 = cpucycles_microseconds();
} while (us1-us0 < 10000 || t1-t0 < 1000);
if (t1 <= t0) return 0;
t1 -= t0;
us1 -= us0;
return (persecond * 0.000001 * (double) us1) / (double) t1;
}
// ----- selecting an option
#include "options.inc"
#define CALLS 1000
#define ESTIMATES 3
long long cpucycles_init(void)
{
long long precision[NUMOPTIONS];
double scaling[NUMOPTIONS];
int only32[NUMOPTIONS];
long long bestprecision;
long long bestopt;
long long opt;
persecond = osfreq();
for (opt = 0;opt < NUMOPTIONS;++opt) {
long long freq = options[opt].ticks_setup();
long long tries;
precision[opt] = 0;
scaling[opt] = 0;
only32[opt] = 0;
if (freq > 0) {
scaling[opt] = persecond*1.0/freq;
} else if (freq == cpucycles_CYCLECOUNTER) {
scaling[opt] = 1.0;
} else if (freq == cpucycles_EXTEND32) {
only32[opt] = 1;
scaling[opt] = 1.0;
} else if (freq == cpucycles_MAYBECYCLECOUNTER) {
scaling[opt] = 1.0;
} else if (freq == cpucycles_FINDMULTIPLIER) {
int ok = 0;
double denom;
long long loop;
for (denom = 1;denom <= 1024;denom += denom) {
double est[ESTIMATES];
for (loop = 0;loop < ESTIMATES;++loop)
est[loop] = denom*estimate_cyclespertick(options[opt].ticks);
scaling[opt] = (double) (long long) est[0];
if (scaling[opt] < est[0]-0.5) scaling[opt] += 1;
if (scaling[opt] > est[0]+0.5) scaling[opt] -= 1;
ok = 1;
for (loop = 0;loop < ESTIMATES;++loop) {
if (est[loop]-scaling[opt] > 0.1) ok = 0;
if (scaling[opt]-est[loop] > 0.1) ok = 0;
}
if (ok) {
scaling[opt] /= denom;
break;
}
scaling[opt] = 0;
}
if (!ok) continue;
} else {
continue;
}
for (tries = 0;tries < 10;++tries) {
long long t[CALLS+1];
long long ok = 1;
long long i;
if (scaling[opt] == 1.0) {
for (i = 0;i <= CALLS;++i)
t[i] = options[opt].ticks();
} else {
double scalingopt = scaling[opt];
long long offset = options[opt].ticks();
for (i = 0;i <= CALLS;++i)
t[i] = (options[opt].ticks()-offset)*scalingopt;
}
for (i = 0;i < CALLS;++i)
if (t[i] > t[i+1])
ok = 0;
if (t[0] == t[CALLS])
ok = 0;
if (ok) {
long long smallestdiff = 0;
for (i = 0;i < CALLS;++i) {
long long diff = t[i+1]-t[i];
if (diff <= 0) continue;
if (smallestdiff == 0 || diff < smallestdiff)
smallestdiff = diff;
}
precision[opt] = smallestdiff;
// tilt selection towards more robust counters
if (freq != cpucycles_CYCLECOUNTER && freq != cpucycles_EXTEND32)
precision[opt] += 100;
if (freq > 0)
precision[opt] += 100;
break;
}
// otherwise keep trying
// since !ok can be caused by overflow
// or by core swap
}
}
if (tracesetup) {
for (opt = 0;opt < NUMOPTIONS;++opt)
printf("cpucycles tracesetup %lld %s precision %lld scaling %lf only32 %d\n"
,opt,options[opt].implementation,precision[opt],scaling[opt],only32[opt]);
}
bestopt = DEFAULTOPTION;
bestprecision = 0;
for (opt = 0;opt < NUMOPTIONS;++opt)
if (precision[opt] > 0)
if (!bestprecision || precision[opt] < bestprecision) {
bestopt = opt;
bestprecision = precision[opt];
}
implementation = options[bestopt].implementation;
if (scaling[bestopt] == 1.0) {
if (only32[bestopt]) {
cpucycles_extend32_from = options[bestopt].ticks;
cpucycles_extend32_setup();
cpucycles = cpucycles_extend32;
} else {
cpucycles = options[bestopt].ticks;
}
} else {
cpucycles_scaled_scaling = scaling[bestopt];
cpucycles_scaled_from = options[bestopt].ticks;
cpucycles_scaled_offset = cpucycles_scaled_from();
cpucycles = cpucycles_scaled;
}
return cpucycles();
}
@@ -0,0 +1,22 @@
// version 20230105
// public domain
// djb
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
#include "cpucycles_internal.h"
long long ticks(void)
{
return __rdtsc();
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_MAYBECYCLECOUNTER;
}
@@ -0,0 +1,22 @@
// version 20230105
// public domain
// djb
#include "cpucycles_internal.h"
#ifndef __i386__
#error this code is only for 32-bit x86 platforms
#endif
long long ticks(void)
{
long long result;
asm volatile(".byte 15;.byte 49" : "=A" (result));
return result;
}
long long ticks_setup(void)
{
if (!cpucycles_works(ticks)) return cpucycles_SKIP;
return cpucycles_MAYBECYCLECOUNTER;
}
+47
View File
@@ -0,0 +1,47 @@
### NAME
cpucycles - count CPU cycles
### SYNOPSIS
#include <cpucycles.h>
long long count = cpucycles();
long long persecond = cpucycles_persecond();
const char *implementation = cpucycles_implementation();
const char *version = cpucycles_version();
Link with `-lcpucycles`. Old systems may also need `-lrt`.
### DESCRIPTION
`cpucycles()` returns an estimate for the number of CPU cycles that have
occurred since an unspecified time in the past (perhaps system boot,
perhaps program startup).
Accessing true cycle counters can be difficult on some CPUs and
operating systems. `cpucycles()` does its best to produce accurate
results, but selects a low-precision counter if the only other option is
failure.
`cpucycles_persecond()` returns an estimate for the number of CPU cycles
per second. This estimate comes from `/etc/cpucyclespersecond` if that
file exists, otherwise from various OS mechanisms, otherwise from the
`cpucyclespersecond` environment variable if that is set, otherwise
2399987654.
`cpucycles_implementation()` returns the name of the counter in use:
e.g., `"amd64-pmc"`.
`cpucycles_version()` returns the `libcpucycles` version number as a
string: e.g., `"20230115"`. Results of `cpucycles_implementation()`
should be interpreted relative to `cpucycles_version()`.
`cpucycles` is actually a function pointer. The first call to
`cpucycles()` or `cpucycles_persecond()` or `cpucycles_implementation()`
selects one of the available counters and updates the `cpucycles`
pointer accordingly. Subsequent calls to `cpucycles()` are thread-safe.
### SEE ALSO
**gettimeofday**(2), **clock_gettime**(2)
+447
View File
@@ -0,0 +1,447 @@
Currently libcpucycles supports the following cycle counters. Some
cycle counters are actually other forms of counters that libcpucycles
scales to imitate a cycle counter. There is
[separate documentation](selection.html)
for how libcpucycles makes a choice of cycle counter. See also
[security considerations](security.html) regarding enabling or disabling
counters and regarding Turbo Boost.
`amd64-pmc`: Requires a 64-bit Intel/AMD platform. Requires the Linux
perf_event interface. Accesses a cycle counter through RDPMC. Requires
`/proc/sys/kernel/perf_event_paranoid` to be at most 2 for user-level
RDPMC access. This counter runs at the clock frequency of the CPU core.
`amd64-tsc`, `amd64-tscasm`: Requires a 64-bit Intel/AMD platform.
Requires RDTSC to be enabled, which it is by default. Uses RDTSC to
access the CPU's time-stamp counter. On current CPUs, this is an
off-core clock rather than a cycle counter, but it is typically a very
fast off-core clock, making it adequate for seeing cycle counts if
overclocking and underclocking are disabled. The difference between
`tsc` and `tscasm` is that `tsc` uses the compiler's `__rdtsc()` while
`tscasm` uses inline assembly.
`arm32-cortex`: Requires a 32-bit ARMv7-A platform. Uses
`mrc p15, 0, %0, c9, c13, 0` to read the cycle counter. Requires user
access to the cycle counter, which is not enabled by default but can be
enabled under Linux via
[a kernel module](https://github.com/thoughtpolice/enable_arm_pmu).
This counter is natively 32 bits, but libcpucycles watches how the
counter and `gettimeofday` increase to compute a 64-bit extension of the
counter.
`arm64-pmc`: Requires a 64-bit ARMv8-A platform. Uses
`mrs %0, PMCCNTR_EL0` to read the cycle counter. Requires user access
to the cycle counter, which is not enabled by default but can be enabled
under Linux via
[a kernel module](https://github.com/rdolbeau/enable_arm_pmu).
`arm64-vct`: Requires a 64-bit ARMv8-A platform. Uses
`mrs %0, CNTVCT_EL0` to read a "virtual count" timer. This is an
off-core clock, typically running at 24MHz. Results are scaled by
libcpucycles.
`mips64-cc`: Requires a 64-bit MIPS platform. (Maybe the same code would
also work as `mips32-cc`, but this has not been tested yet.) Uses RDHWR
to read the hardware cycle counter (hardware register 2 times a constant
scale factor in hardware register 3). This counter is natively 32 bits,
but libcpucycles watches how the counter and `gettimeofday` increase to
compute a 64-bit extension of the counter.
`ppc32-mftb`: Requires a 32-bit PowerPC platform. Uses `mftb` and
`mftbu` to read the "time base". This is an off-core clock, typically
running at 24MHz.
`ppc64-mftb`: Requires a 64-bit PowerPC platform. Uses `mftb` and
`mftbu` to read the "time base". This is an off-core clock, typically
running at 24MHz.
`riscv32-rdcycle`: Requires a 32-bit RISC-V platform. Uses `rdcycle`
and `rdcycleh` to read a cycle counter.
`riscv64-rdcycle`: Requires a 64-bit RISC-V platform. Uses `rdcycle`
to read a cycle counter.
`s390x-stckf`: Requires a 64-bit z/Architecture platform. Uses `stckf`
to read the TOD clock, which is documented to run at 4096MHz. On the
z15, this looks like a doubling of an off-core 2048MHz clock. Results
are scaled by libcpucycles.
`sparc64-rdtick`: Requires a 64-bit SPARC platform. Uses `rd %tick`
to read a cycle counter.
`x86-tsc`, `x86-tscasm`: Same as `amd64-tsc` and `amd64-tscasm`, but
for 32-bit Intel/AMD platforms instead of 64-bit Intel/AMD platforms.
`default-gettimeofday`: Reasonably portable. Resolution is limited to 1
microsecond. Results are scaled by libcpucycles.
`default-mach`: Requires an OS with `mach_absolute_time()`. Typically
runs at 24MHz. Results are scaled by libcpucycles.
`default-monotonic`: Requires `CLOCK_MONOTONIC`. Reasonably portable,
although might fail on older systems where `default-gettimeofday` works.
Resolution is limited to 1 nanosecond. Can be almost as good as a cycle
counter, or orders of magnitude worse, depending on the OS and CPU.
Results are scaled by libcpucycles.
`default-perfevent`: Requires the Linux `perf_event` interface, and a
CPU where `perf_event` supports `PERF_COUNT_HW_CPU_CYCLES`. Similar
variations in quality to `default-monotonic`, without the 1-nanosecond
limitation.
`default-zero`: The horrifying last resort if nothing else works.
## Examples
These are examples of `cpucycles-info` output on various machines. The
machines named `gcc*` are from the
[GCC Compile Farm](https://gcc.gnu.org/wiki/CompileFarm).
A `median` line saying, e.g., `47 +47+28+0+2-5+0+2-5...` means that the
differences between adjacent cycle counts were 47+47, 47+28, 47+0, 47+2,
475, 47+0, 47+2, 475, etc., with median difference 47. The first few
differences are typically larger because of cache effects.
`pi3aplus`,
Broadcom BCM2837B0:
```
cpucycles version 20230105
cpucycles tracesetup 0 arm64-pmc precision 9 scaling 1.000000 only32 0
cpucycles tracesetup 1 arm64-vct precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-perfevent precision 189 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-monotonic precision 272 scaling 1.400000 only32 0
cpucycles tracesetup 5 default-gettimeofday precision 1600 scaling 1400.000000 only32 0
cpucycles tracesetup 6 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 1400000000
cpucycles implementation arm64-pmc
cpucycles median 10 +10+8+3+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 1032000000...4224666667 with 1024 loops 4 microseconds
cpucycles observed persecond 1286000000...1756000000 with 2048 loops 7 microseconds
cpucycles observed persecond 1368266666...1598000000 with 4096 loops 14 microseconds
cpucycles observed persecond 1366700000...1473428572 with 8192 loops 29 microseconds
cpucycles observed persecond 1366100000...1417534483 with 16384 loops 59 microseconds
cpucycles observed persecond 1332739837...1357132232 with 32768 loops 122 microseconds
cpucycles observed persecond 1354483471...1366945834 with 65536 loops 241 microseconds
cpucycles observed persecond 1385684989...1392195330 with 131072 loops 472 microseconds
cpucycles observed persecond 1347223021...1350328528 with 262144 loops 972 microseconds
cpucycles observed persecond 1375460125...1377069853 with 524288 loops 1905 microseconds
cpucycles observed persecond 1376527697...1377335961 with 1048576 loops 3808 microseconds
```
`bblack`,
TI Sitara XAM3359AZCZ100:
```
cpucycles version 20230105
cpucycles tracesetup 0 arm32-cortex precision 8 scaling 1.000000 only32 1
cpucycles tracesetup 1 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 1283 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 1200 scaling 1000.000000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 1000000000
cpucycles implementation arm32-cortex
cpucycles median 1260 +1506+62+31+7+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+13+7+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 622181818...2101888889 with 1024 loops 10 microseconds
cpucycles observed persecond 806133333...1492615385 with 2048 loops 14 microseconds
cpucycles observed persecond 879880000...1232565218 with 4096 loops 24 microseconds
cpucycles observed persecond 939577777...1130581396 with 8192 loops 44 microseconds
cpucycles observed persecond 956954022...1050047059 with 16384 loops 86 microseconds
cpucycles observed persecond 982878542...1020685715 with 32768 loops 246 microseconds
cpucycles observed persecond 988105105...1012217523 with 65536 loops 332 microseconds
cpucycles observed persecond 993752077...1007159723 with 131072 loops 721 microseconds
cpucycles observed persecond 995364296...1004009448 with 262144 loops 1377 microseconds
cpucycles observed persecond 998216306...1001821536 with 524288 loops 2685 microseconds
cpucycles observed persecond 998991848...1000914196 with 1048576 loops 5397 microseconds
```
`hiphop`,
Intel Xeon E3-1220 v3:
```
cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 40 scaling 1.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 124 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 124 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 160 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 272 scaling 3.100000 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 3300 scaling 3100.000000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3100000000
cpucycles implementation amd64-pmc
cpucycles median 44 +38+23+23+23-4+0-4+0-4+0-4+0+10-4-2+1-4+1-4+1+17+1-4+1-4+1-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4
cpucycles observed persecond 2066500000...4235000000 with 8192 loops 3 microseconds
cpucycles observed persecond 2760833333...4200250000 with 16384 loops 5 microseconds
cpucycles observed persecond 2743416666...3313100000 with 32768 loops 11 microseconds
cpucycles observed persecond 2986227272...3295000000 with 65536 loops 21 microseconds
cpucycles observed persecond 3052069767...3206073171 with 131072 loops 42 microseconds
cpucycles observed persecond 3050395348...3125523810 with 262144 loops 85 microseconds
cpucycles observed persecond 3085123529...3123059524 with 524288 loops 169 microseconds
cpucycles observed persecond 3084561764...3103434912 with 1048576 loops 339 microseconds
```
`nucnuc`,
Intel Pentium N3700:
```
cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 26 scaling 1.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 120 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 120 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 427 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 320 scaling 1.600000 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 1800 scaling 1600.000000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 1600000000
cpucycles implementation amd64-pmc
cpucycles median 66 +12+12+14+14-1-1+0-1+0-1+0-1+0+1-1+0-1+0-1+0-2+0-1+0-1+0-1+0-2+0-1+0-1+0-1+0-2+0-1+0-1+1-1+0-2-1-1+0-1+0-1+0-2+0-1+2+0-1+0-1+0+0-1
cpucycles observed persecond 1060500000...2325000000 with 2048 loops 3 microseconds
cpucycles observed persecond 1387166666...2208250000 with 4096 loops 5 microseconds
cpucycles observed persecond 1376083333...1705500000 with 8192 loops 11 microseconds
cpucycles observed persecond 1495727272...1671800000 with 16384 loops 21 microseconds
cpucycles observed persecond 1563428571...1655100000 with 32768 loops 41 microseconds
cpucycles observed persecond 1580807228...1626234568 with 65536 loops 82 microseconds
cpucycles observed persecond 1589539393...1612619632 with 131072 loops 164 microseconds
cpucycles observed persecond 1598841463...1610230062 with 262144 loops 327 microseconds
cpucycles observed persecond 1564336810...1569988042 with 524288 loops 670 microseconds
cpucycles observed persecond 1599759725...1602608098 with 1048576 loops 1310 microseconds
```
`saber214`,
AMD FX-8350:
```
cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 167 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 168 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 376 scaling 4.013452 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 4213 scaling 4013.452000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 4013452000
cpucycles implementation amd64-tsc
cpucycles median 77 +87-2+21+7+4+1+0+2-2-7-4+0+1+4-2+3+1-2-2+5-6+2+2+2+2+1-1-1+0-4+0-1-1-1-2+3-1-1+2-2+0+0+2+0+0+2-2-2+1-1-2+2-5+2+0+2+0+1+0+3-2-1-1
cpucycles observed persecond 2767500000...5759000000 with 4096 loops 3 microseconds
cpucycles observed persecond 3426000000...4893800000 with 8192 loops 6 microseconds
cpucycles observed persecond 3724076923...4446363637 with 16384 loops 12 microseconds
cpucycles observed persecond 3977833333...4363318182 with 32768 loops 23 microseconds
cpucycles observed persecond 3984854166...4168739131 with 65536 loops 47 microseconds
cpucycles observed persecond 3981709923...4048193799 with 131072 loops 130 microseconds
cpucycles observed persecond 3982716417...4026914573 with 262144 loops 200 microseconds
cpucycles observed persecond 4001637602...4025136987 with 524288 loops 366 microseconds
cpucycles observed persecond 4007411111...4018600248 with 1048576 loops 809 microseconds
```
`gcc14`,
Intel Xeon E5-2620 v3,
Debian testing (bookworm),
Linux kernel 6.0.0-6-amd64:
```
cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 41 scaling 1.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 148 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 148 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 159 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 289 scaling 3.200000 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 3400 scaling 3200.000000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3200000000
cpucycles implementation amd64-pmc
cpucycles median 47 +47+28+0+2-5+0+2-5+16+2-5+0+2-5+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0
cpucycles observed persecond 1653800000...2819333334 with 8192 loops 4 microseconds
cpucycles observed persecond 1832111111...2389285715 with 16384 loops 8 microseconds
cpucycles observed persecond 1936058823...2207200000 with 32768 loops 16 microseconds
cpucycles observed persecond 2052843750...2196200000 with 65536 loops 31 microseconds
cpucycles observed persecond 2050750000...2120048388 with 131072 loops 63 microseconds
cpucycles observed persecond 2081896825...2117048388 with 262144 loops 125 microseconds
cpucycles observed persecond 2089478087...2107044177 with 524288 loops 250 microseconds
cpucycles observed persecond 2093343313...2102124249 with 1048576 loops 500 microseconds
```
`gcc23`,
Cavium Octeon II V0.1,
Debian 8.11,
Linux kernel 4.1.4:
```
cpucycles version 20230105
cpucycles tracesetup 0 mips64-cc precision 24 scaling 1.000000 only32 1
cpucycles tracesetup 1 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 46702 scaling 2.399988 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 45799 scaling 2399.987654 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 2399987654
cpucycles implementation mips64-cc
cpucycles median 2177 +828+17+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 641900000...1845125000 with 1024 loops 9 microseconds
cpucycles observed persecond 745357142...1352083334 with 2048 loops 13 microseconds
cpucycles observed persecond 809826086...1162333334 with 4096 loops 22 microseconds
cpucycles observed persecond 897717948...1104405406 with 8192 loops 38 microseconds
cpucycles observed persecond 957467532...1059986667 with 16384 loops 76 microseconds
cpucycles observed persecond 973102189...1029777778 with 32768 loops 136 microseconds
cpucycles observed persecond 986518656...1015830828 with 65536 loops 267 microseconds
cpucycles observed persecond 993452830...1008166667 with 131072 loops 529 microseconds
cpucycles observed persecond 996036966...1003403609 with 262144 loops 1054 microseconds
cpucycles observed persecond 984706378...1001682630 with 524288 loops 2131 microseconds
cpucycles observed persecond 992585292...1001178580 with 1048576 loops 4296 microseconds
```
`gcc45`,
AMD Athlon II X4 640,
Debian 8.11,
Linux kernel 3.16.0-11-686-pae:
```
cpucycles version 20230105
cpucycles tracesetup 0 x86-tsc precision 199 scaling 1.000000 only32 0
cpucycles tracesetup 1 x86-tscasm precision 199 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-perfevent precision 170 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-monotonic precision 941 scaling 3.000000 only32 0
cpucycles tracesetup 5 default-gettimeofday precision 3200 scaling 3000.000000 only32 0
cpucycles tracesetup 6 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3000000000
cpucycles implementation default-perfevent
cpucycles median 72 +12+0+0+0+0+0+0+0+5+0+0+0+0+0+0+0+2+0+0+0+0+0+0+0+1+0+0+0+0+0+0+0+2+0+0+0+0+0+0+0+1+0+0+0+0+0+0+0+2+0+0+0+0+0+0+0+1+0+0+0+0+0+0
cpucycles observed persecond 541500000...1812000000 with 1024 loops 3 microseconds
cpucycles observed persecond 712333333...1212250000 with 2048 loops 5 microseconds
cpucycles observed persecond 1193285714...1733600000 with 4096 loops 6 microseconds
cpucycles observed persecond 1689176470...1804562500 with 8192 loops 33 microseconds
cpucycles observed persecond 1713074626...1770600000 with 16384 loops 66 microseconds
cpucycles observed persecond 1765107692...1795140625 with 32768 loops 129 microseconds
cpucycles observed persecond 1785369649...1800603922 with 65536 loops 256 microseconds
cpucycles observed persecond 1781377862...1796288462 with 131072 loops 261 microseconds
cpucycles observed persecond 1772647398...1778247827 with 262144 loops 691 microseconds
cpucycles observed persecond 1789670493...1794149598 with 524288 loops 870 microseconds
cpucycles observed persecond 1860276211...1861561332 with 1048576 loops 3156 microseconds
```
`gcc92`,
SiFive Freedom U740,
Ubuntu 22.04,
Linux kernel 5.15.0-1014-generic:
```
cpucycles version 20230105
cpucycles tracesetup 0 riscv64-rdcycle precision 8 scaling 1.000000 only32 0
cpucycles tracesetup 1 default-perfevent precision 3024 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 2599 scaling 2.399988 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 2599 scaling 2399.987654 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 2399987654
cpucycles implementation riscv64-rdcycle
cpucycles median 8 +33+27+1+1+1+1+0+0+0+22+0+0+0+0+0+0+0+628+0+0+0+7+0+0+0+145+0+0+0+0+0+0+0+22+0+0+0+0+0+0+0+158+0+0+0+0+0+0+0+22+0+0+0+0+0+0+0+22+0+0+0+0+0
cpucycles observed persecond 530250000...1978000000 with 1024 loops 3 microseconds
cpucycles observed persecond 831000000...1915666667 with 2048 loops 4 microseconds
cpucycles observed persecond 1055750000...1689500000 with 4096 loops 7 microseconds
cpucycles observed persecond 1045562500...1305428572 with 8192 loops 15 microseconds
cpucycles observed persecond 1102700000...1236357143 with 16384 loops 29 microseconds
cpucycles observed persecond 1176053571...1247444445 with 32768 loops 55 microseconds
cpucycles observed persecond 1173321428...1209127273 with 65536 loops 111 microseconds
cpucycles observed persecond 1187805429...1205210046 with 131072 loops 220 microseconds
cpucycles observed persecond 1192415909...1201157535 with 262144 loops 439 microseconds
cpucycles observed persecond 1194694760...1199247717 with 524288 loops 877 microseconds
cpucycles observed persecond 1194656004...1197023034 with 1048576 loops 1781 microseconds
```
`gcc103`,
Apple M1 (Icestorm-M1 + Firestorm-M1),
Debian unstable (bookworm),
Linux kernel 6.0.0-rc5-asahi-00001-gc62bd3fe430f:
```
cpucycles version 20230105
cpucycles tracesetup 0 arm64-pmc precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 1 arm64-vct precision 186 scaling 86.000000 only32 0
cpucycles tracesetup 2 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-monotonic precision 285 scaling 2.064000 only32 0
cpucycles tracesetup 5 default-gettimeofday precision 2264 scaling 2064.000000 only32 0
cpucycles tracesetup 6 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 2064000000
cpucycles implementation arm64-vct
cpucycles median 0 +0+86+0+0+0+0+0+0+0+0+0+0+0+0+86+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+86+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+86+0+0+0+0+0+0+0+0
cpucycles observed persecond 1784500000...3655000000 with 8192 loops 3 microseconds
cpucycles observed persecond 1773750000...2393666667 with 16384 loops 7 microseconds
cpucycles observed persecond 1897733333...2222769231 with 32768 loops 14 microseconds
cpucycles observed persecond 1951310344...2114962963 with 65536 loops 28 microseconds
cpucycles observed persecond 2024071428...2107000000 with 131072 loops 55 microseconds
cpucycles observed persecond 2041531531...2082935780 with 262144 loops 110 microseconds
cpucycles observed persecond 2051158371...2071461188 with 524288 loops 220 microseconds
cpucycles observed persecond 2058539682...2068309795 with 1048576 loops 440 microseconds
```
`gcc112` (`gcc2-power8`),
IBM POWER8E,
CentOS 7.9 AltArch,
Linux kernel 3.10.0-1127.13.1.el7.ppc64le:
```
cpucycles version 20230105
cpucycles tracesetup 0 ppc64-mftb precision 251 scaling 7.207031 only32 0
cpucycles tracesetup 1 default-perfevent precision 295 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 536 scaling 3.690000 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 3890 scaling 3690.000000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3690000000
cpucycles implementation ppc64-mftb
cpucycles median 195 +2969-8+14+0-8+7-8-7+7+6-7-1+0-1+0+7+7-15+7-1-7+6+0+0-8+0+6+0-8+7+0+7-8-8-7-1+7-8+7+0-8+0+14-8-7+6+0-8+7+7-15+0-1+0-1+14+0-15+14+0-1+7+0
cpucycles observed persecond 2603750000...5510000000 with 2048 loops 3 microseconds
cpucycles observed persecond 3430500000...6052250000 with 4096 loops 5 microseconds
cpucycles observed persecond 3411333333...4457500000 with 8192 loops 11 microseconds
cpucycles observed persecond 3548695652...4060333334 with 16384 loops 22 microseconds
cpucycles observed persecond 3624977777...3876534884 with 32768 loops 44 microseconds
cpucycles observed persecond 3621855555...3745363637 with 65536 loops 89 microseconds
cpucycles observed persecond 3660157303...3722227273 with 131072 loops 177 microseconds
cpucycles observed persecond 3680471751...3711622160 with 262144 loops 353 microseconds
cpucycles observed persecond 3685321074...3700886525 with 524288 loops 706 microseconds
cpucycles observed persecond 3687745930...3695537208 with 1048576 loops 1412 microseconds
```
`gcc202`,
UltraSparc T5,
Debian unstable (bookworm),
Linux kernel 5.19.0-2-sparc64-smp:
```
cpucycles version 20230105
cpucycles tracesetup 0 sparc64-rdtick precision 65 scaling 1.000000 only32 0
cpucycles tracesetup 1 default-perfevent precision 386 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 442 scaling 3.599910 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 3799 scaling 3599.910000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3599910000
cpucycles implementation sparc64-rdtick
cpucycles median 73 +24+0+24+24+24+24+24+24+0+1+24+0+1+24+0+1+24+0+0+1+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+1+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 2751500000...4258250000 with 4096 loops 5 microseconds
cpucycles observed persecond 3289200000...4206875000 with 8192 loops 9 microseconds
cpucycles observed persecond 3454789473...3900823530 with 16384 loops 18 microseconds
cpucycles observed persecond 3452026315...3659888889 with 32768 loops 37 microseconds
cpucycles observed persecond 3543770270...3650916667 with 65536 loops 73 microseconds
cpucycles observed persecond 3567299319...3620662069 with 131072 loops 146 microseconds
cpucycles observed persecond 3591373287...3618220690 with 262144 loops 291 microseconds
cpucycles observed persecond 3597353344...3610774527 with 524288 loops 582 microseconds
cpucycles observed persecond 3595899403...3603058071 with 1048576 loops 1172 microseconds
```
IBM z15:
```
cpucycles version 20230106
cpucycles tracesetup 0 s390x-stckf precision 250 scaling 1.269531 only32 0
cpucycles tracesetup 1 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 272 scaling 5.200000 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 5400 scaling 5200.000000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 5200000000
cpucycles implementation s390x-stckf
cpucycles median 48 +87+8+0-2+0+0+38-2+0+1-3+1+28+0+3-3+1+0+28+0-2+3+0-2+36+0+0+0+1+0+28+0-2+0+3-2+35+1+0-2+0+3+28+0-2+0+0-2+3+25+3+0-2+0+1+35+1+0+0-2+0+28+0
cpucycles observed persecond 4948941176...5627733334 with 8192 loops 16 microseconds
cpucycles observed persecond 4104125000...5515666667 with 16384 loops 7 microseconds
cpucycles observed persecond 5047076923...5987818182 with 32768 loops 12 microseconds
cpucycles observed persecond 5044846153...5475708334 with 65536 loops 25 microseconds
cpucycles observed persecond 5141313725...5357428572 with 131072 loops 50 microseconds
cpucycles observed persecond 5150892156...5257250000 with 262144 loops 101 microseconds
cpucycles observed persecond 5183421568...5236549505 with 524288 loops 203 microseconds
cpucycles observed persecond 5190282555...5216582717 with 1048576 loops 406 microseconds
```
+30
View File
@@ -0,0 +1,30 @@
To download and unpack the latest version of libcpucycles:
wget -m https://cpucycles.cr.yp.to/libcpucycles-latest-version.txt
version=$(cat cpucycles.cr.yp.to/libcpucycles-latest-version.txt)
wget -m https://cpucycles.cr.yp.to/libcpucycles-$version.tar.gz
tar -xzf cpucycles.cr.yp.to/libcpucycles-$version.tar.gz
cd libcpucycles-$version
Then [install](install.html).
### Archives and changelog (reverse chronological)
[`libcpucycles-20230115.tar.gz`](libcpucycles-20230115.tar.gz) [browse](libcpucycles-20230115.html)
Update actual `cpucycles_version` behavior to match documentation.
[`libcpucycles-20230110.tar.gz`](libcpucycles-20230110.tar.gz) [browse](libcpucycles-20230110.html)
`doc/api.md`: Document `cpucycles_version()`.
Add `s390x-stckf` counter.
`cpucycles/default-perfevent.c`: Read into `int64_t` instead of `long long`.
Add comment explaining issues with `PERF_FORMAT_TOTAL_TIME_RUNNING`.
`configure`: Improve `uname` handling.
`doc/api.md`: Update description of default frequency.
[`libcpucycles-20230105.tar.gz`](libcpucycles-20230105.tar.gz) [browse](libcpucycles-20230105.html)
+91
View File
@@ -0,0 +1,91 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
html{overflow-y:scroll}
body{font-family:sans-serif}
p,ul,ol,blockquote,pre{font-size:0.9em;line-height:1.6em}
li p{font-size:1.0em}
blockquote p{font-size:1.0em}
tt{font-size:1.2em}
code{font-size:1.2em}
h1{font-size:1.5em}
h2{font-size:1.3em}
h3{font-size:1.0em}
h1 a{text-decoration:none}
table{border-collapse:collapse}
th,td{border:1px solid black}
table a{text-decoration:none}
table tr{font-size:0.9em;line-height:1.6em}
.links a:hover{text-decoration:underline}
.links a:active{text-decoration:underline}
.links img{width:200px;padding-left:1em}
.links td{border:0px;padding-top:0.5em;padding-bottom:0.5em}
.headline{padding:0;font-weight:bold;font-size:1.5em;vertical-align:top;padding-bottom:0.5em;color:#125d0d}
.navt{display:inline-block;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;
min-width:14%;margin:0;padding:0;padding-left:0.5em;padding-right:0.5em;vertical-align:center;
font-weight:bold;font-size:1.1em;text-align:center;border:1px solid black}
.here{border-bottom:0px;background-color:#ffffff}
.away{background-color:#125d0d;}
.away a{text-decoration:none;display:block;color:#ffffff}
.away a:hover,.away a:active{text-decoration:underline}
.main{margin:0;padding-top:0em;padding-bottom:1%;clear:both}
</style>
<title>
API</title>
</head>
<body>
<div class=headline>
libcpucycles
</div>
<div class=nav>
<div class="navt away"><a href=index.html>Intro</a>
</div><div class="navt away"><a href=download.html>Download</a>
</div><div class="navt away"><a href=install.html>Install</a>
</div><div class="navt here">API
</div><div class="navt away"><a href=counters.html>Counters</a>
</div><div class="navt away"><a href=selection.html>Selection</a>
</div><div class="navt away"><a href=security.html>Security</a>
</div></div>
<div class=main>
<h3>NAME</h3>
<p>cpucycles - count CPU cycles</p>
<h3>SYNOPSIS</h3>
<pre><code>#include &lt;cpucycles.h&gt;
long long count = cpucycles();
long long persecond = cpucycles_persecond();
const char *implementation = cpucycles_implementation();
const char *version = cpucycles_version();
</code></pre>
<p>Link with <code>-lcpucycles</code>. Old systems may also need <code>-lrt</code>.</p>
<h3>DESCRIPTION</h3>
<p><code>cpucycles()</code> returns an estimate for the number of CPU cycles that have
occurred since an unspecified time in the past (perhaps system boot,
perhaps program startup).</p>
<p>Accessing true cycle counters can be difficult on some CPUs and
operating systems. <code>cpucycles()</code> does its best to produce accurate
results, but selects a low-precision counter if the only other option is
failure.</p>
<p><code>cpucycles_persecond()</code> returns an estimate for the number of CPU cycles
per second. This estimate comes from <code>/etc/cpucyclespersecond</code> if that
file exists, otherwise from various OS mechanisms, otherwise from the
<code>cpucyclespersecond</code> environment variable if that is set, otherwise
2399987654.</p>
<p><code>cpucycles_implementation()</code> returns the name of the counter in use:
e.g., <code>"amd64-pmc"</code>.</p>
<p><code>cpucycles_version()</code> returns the <code>libcpucycles</code> version number as a
string: e.g., <code>"20230115"</code>. Results of <code>cpucycles_implementation()</code>
should be interpreted relative to <code>cpucycles_version()</code>.</p>
<p><code>cpucycles</code> is actually a function pointer. The first call to
<code>cpucycles()</code> or <code>cpucycles_persecond()</code> or <code>cpucycles_implementation()</code>
selects one of the available counters and updates the <code>cpucycles</code>
pointer accordingly. Subsequent calls to <code>cpucycles()</code> are thread-safe.</p>
<h3>SEE ALSO</h3>
<p><strong>gettimeofday</strong>(2), <strong>clock_gettime</strong>(2)</p><hr><font size=1><b>Version:</b>
This is version 2023.01.15 of the "API" web page.
</font>
</div>
</body>
</html>
@@ -0,0 +1,456 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
html{overflow-y:scroll}
body{font-family:sans-serif}
p,ul,ol,blockquote,pre{font-size:0.9em;line-height:1.6em}
li p{font-size:1.0em}
blockquote p{font-size:1.0em}
tt{font-size:1.2em}
code{font-size:1.2em}
h1{font-size:1.5em}
h2{font-size:1.3em}
h3{font-size:1.0em}
h1 a{text-decoration:none}
table{border-collapse:collapse}
th,td{border:1px solid black}
table a{text-decoration:none}
table tr{font-size:0.9em;line-height:1.6em}
.links a:hover{text-decoration:underline}
.links a:active{text-decoration:underline}
.links img{width:200px;padding-left:1em}
.links td{border:0px;padding-top:0.5em;padding-bottom:0.5em}
.headline{padding:0;font-weight:bold;font-size:1.5em;vertical-align:top;padding-bottom:0.5em;color:#125d0d}
.navt{display:inline-block;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;
min-width:14%;margin:0;padding:0;padding-left:0.5em;padding-right:0.5em;vertical-align:center;
font-weight:bold;font-size:1.1em;text-align:center;border:1px solid black}
.here{border-bottom:0px;background-color:#ffffff}
.away{background-color:#125d0d;}
.away a{text-decoration:none;display:block;color:#ffffff}
.away a:hover,.away a:active{text-decoration:underline}
.main{margin:0;padding-top:0em;padding-bottom:1%;clear:both}
</style>
<title>
Counters</title>
</head>
<body>
<div class=headline>
libcpucycles
</div>
<div class=nav>
<div class="navt away"><a href=index.html>Intro</a>
</div><div class="navt away"><a href=download.html>Download</a>
</div><div class="navt away"><a href=install.html>Install</a>
</div><div class="navt away"><a href=api.html>API</a>
</div><div class="navt here">Counters
</div><div class="navt away"><a href=selection.html>Selection</a>
</div><div class="navt away"><a href=security.html>Security</a>
</div></div>
<div class=main>
<p>Currently libcpucycles supports the following cycle counters. Some
cycle counters are actually other forms of counters that libcpucycles
scales to imitate a cycle counter. There is
<a href="selection.html">separate documentation</a>
for how libcpucycles makes a choice of cycle counter. See also
<a href="security.html">security considerations</a> regarding enabling or disabling
counters and regarding Turbo Boost.</p>
<p><code>amd64-pmc</code>: Requires a 64-bit Intel/AMD platform. Requires the Linux
perf_event interface. Accesses a cycle counter through RDPMC. Requires
<code>/proc/sys/kernel/perf_event_paranoid</code> to be at most 2 for user-level
RDPMC access. This counter runs at the clock frequency of the CPU core.</p>
<p><code>amd64-tsc</code>, <code>amd64-tscasm</code>: Requires a 64-bit Intel/AMD platform.
Requires RDTSC to be enabled, which it is by default. Uses RDTSC to
access the CPU's time-stamp counter. On current CPUs, this is an
off-core clock rather than a cycle counter, but it is typically a very
fast off-core clock, making it adequate for seeing cycle counts if
overclocking and underclocking are disabled. The difference between
<code>tsc</code> and <code>tscasm</code> is that <code>tsc</code> uses the compiler's <code>__rdtsc()</code> while
<code>tscasm</code> uses inline assembly.</p>
<p><code>arm32-cortex</code>: Requires a 32-bit ARMv7-A platform. Uses
<code>mrc p15, 0, %0, c9, c13, 0</code> to read the cycle counter. Requires user
access to the cycle counter, which is not enabled by default but can be
enabled under Linux via
<a href="https://github.com/thoughtpolice/enable_arm_pmu">a kernel module</a>.
This counter is natively 32 bits, but libcpucycles watches how the
counter and <code>gettimeofday</code> increase to compute a 64-bit extension of the
counter.</p>
<p><code>arm64-pmc</code>: Requires a 64-bit ARMv8-A platform. Uses
<code>mrs %0, PMCCNTR_EL0</code> to read the cycle counter. Requires user access
to the cycle counter, which is not enabled by default but can be enabled
under Linux via
<a href="https://github.com/rdolbeau/enable_arm_pmu">a kernel module</a>.</p>
<p><code>arm64-vct</code>: Requires a 64-bit ARMv8-A platform. Uses
<code>mrs %0, CNTVCT_EL0</code> to read a "virtual count" timer. This is an
off-core clock, typically running at 24MHz. Results are scaled by
libcpucycles.</p>
<p><code>mips64-cc</code>: Requires a 64-bit MIPS platform. (Maybe the same code would
also work as <code>mips32-cc</code>, but this has not been tested yet.) Uses RDHWR
to read the hardware cycle counter (hardware register 2 times a constant
scale factor in hardware register 3). This counter is natively 32 bits,
but libcpucycles watches how the counter and <code>gettimeofday</code> increase to
compute a 64-bit extension of the counter.</p>
<p><code>ppc32-mftb</code>: Requires a 32-bit PowerPC platform. Uses <code>mftb</code> and
<code>mftbu</code> to read the "time base". This is an off-core clock, typically
running at 24MHz.</p>
<p><code>ppc64-mftb</code>: Requires a 64-bit PowerPC platform. Uses <code>mftb</code> and
<code>mftbu</code> to read the "time base". This is an off-core clock, typically
running at 24MHz.</p>
<p><code>riscv32-rdcycle</code>: Requires a 32-bit RISC-V platform. Uses <code>rdcycle</code>
and <code>rdcycleh</code> to read a cycle counter.</p>
<p><code>riscv64-rdcycle</code>: Requires a 64-bit RISC-V platform. Uses <code>rdcycle</code>
to read a cycle counter.</p>
<p><code>s390x-stckf</code>: Requires a 64-bit z/Architecture platform. Uses <code>stckf</code>
to read the TOD clock, which is documented to run at 4096MHz. On the
z15, this looks like a doubling of an off-core 2048MHz clock. Results
are scaled by libcpucycles.</p>
<p><code>sparc64-rdtick</code>: Requires a 64-bit SPARC platform. Uses <code>rd %tick</code>
to read a cycle counter.</p>
<p><code>x86-tsc</code>, <code>x86-tscasm</code>: Same as <code>amd64-tsc</code> and <code>amd64-tscasm</code>, but
for 32-bit Intel/AMD platforms instead of 64-bit Intel/AMD platforms.</p>
<p><code>default-gettimeofday</code>: Reasonably portable. Resolution is limited to 1
microsecond. Results are scaled by libcpucycles.</p>
<p><code>default-mach</code>: Requires an OS with <code>mach_absolute_time()</code>. Typically
runs at 24MHz. Results are scaled by libcpucycles.</p>
<p><code>default-monotonic</code>: Requires <code>CLOCK_MONOTONIC</code>. Reasonably portable,
although might fail on older systems where <code>default-gettimeofday</code> works.
Resolution is limited to 1 nanosecond. Can be almost as good as a cycle
counter, or orders of magnitude worse, depending on the OS and CPU.
Results are scaled by libcpucycles.</p>
<p><code>default-perfevent</code>: Requires the Linux <code>perf_event</code> interface, and a
CPU where <code>perf_event</code> supports <code>PERF_COUNT_HW_CPU_CYCLES</code>. Similar
variations in quality to <code>default-monotonic</code>, without the 1-nanosecond
limitation.</p>
<p><code>default-zero</code>: The horrifying last resort if nothing else works.</p>
<h2>Examples</h2>
<p>These are examples of <code>cpucycles-info</code> output on various machines. The
machines named <code>gcc*</code> are from the
<a href="https://gcc.gnu.org/wiki/CompileFarm">GCC Compile Farm</a>.</p>
<p>A <code>median</code> line saying, e.g., <code>47 +47+28+0+2-5+0+2-5...</code> means that the
differences between adjacent cycle counts were 47+47, 47+28, 47+0, 47+2,
475, 47+0, 47+2, 475, etc., with median difference 47. The first few
differences are typically larger because of cache effects.</p>
<p><code>pi3aplus</code>,
Broadcom BCM2837B0:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 arm64-pmc precision 9 scaling 1.000000 only32 0
cpucycles tracesetup 1 arm64-vct precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-perfevent precision 189 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-monotonic precision 272 scaling 1.400000 only32 0
cpucycles tracesetup 5 default-gettimeofday precision 1600 scaling 1400.000000 only32 0
cpucycles tracesetup 6 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 1400000000
cpucycles implementation arm64-pmc
cpucycles median 10 +10+8+3+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 1032000000...4224666667 with 1024 loops 4 microseconds
cpucycles observed persecond 1286000000...1756000000 with 2048 loops 7 microseconds
cpucycles observed persecond 1368266666...1598000000 with 4096 loops 14 microseconds
cpucycles observed persecond 1366700000...1473428572 with 8192 loops 29 microseconds
cpucycles observed persecond 1366100000...1417534483 with 16384 loops 59 microseconds
cpucycles observed persecond 1332739837...1357132232 with 32768 loops 122 microseconds
cpucycles observed persecond 1354483471...1366945834 with 65536 loops 241 microseconds
cpucycles observed persecond 1385684989...1392195330 with 131072 loops 472 microseconds
cpucycles observed persecond 1347223021...1350328528 with 262144 loops 972 microseconds
cpucycles observed persecond 1375460125...1377069853 with 524288 loops 1905 microseconds
cpucycles observed persecond 1376527697...1377335961 with 1048576 loops 3808 microseconds
</code></pre>
<p><code>bblack</code>,
TI Sitara XAM3359AZCZ100:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 arm32-cortex precision 8 scaling 1.000000 only32 1
cpucycles tracesetup 1 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 1283 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 1200 scaling 1000.000000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 1000000000
cpucycles implementation arm32-cortex
cpucycles median 1260 +1506+62+31+7+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+13+7+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 622181818...2101888889 with 1024 loops 10 microseconds
cpucycles observed persecond 806133333...1492615385 with 2048 loops 14 microseconds
cpucycles observed persecond 879880000...1232565218 with 4096 loops 24 microseconds
cpucycles observed persecond 939577777...1130581396 with 8192 loops 44 microseconds
cpucycles observed persecond 956954022...1050047059 with 16384 loops 86 microseconds
cpucycles observed persecond 982878542...1020685715 with 32768 loops 246 microseconds
cpucycles observed persecond 988105105...1012217523 with 65536 loops 332 microseconds
cpucycles observed persecond 993752077...1007159723 with 131072 loops 721 microseconds
cpucycles observed persecond 995364296...1004009448 with 262144 loops 1377 microseconds
cpucycles observed persecond 998216306...1001821536 with 524288 loops 2685 microseconds
cpucycles observed persecond 998991848...1000914196 with 1048576 loops 5397 microseconds
</code></pre>
<p><code>hiphop</code>,
Intel Xeon E3-1220 v3:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 40 scaling 1.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 124 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 124 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 160 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 272 scaling 3.100000 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 3300 scaling 3100.000000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3100000000
cpucycles implementation amd64-pmc
cpucycles median 44 +38+23+23+23-4+0-4+0-4+0-4+0+10-4-2+1-4+1-4+1+17+1-4+1-4+1-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4+0-4
cpucycles observed persecond 2066500000...4235000000 with 8192 loops 3 microseconds
cpucycles observed persecond 2760833333...4200250000 with 16384 loops 5 microseconds
cpucycles observed persecond 2743416666...3313100000 with 32768 loops 11 microseconds
cpucycles observed persecond 2986227272...3295000000 with 65536 loops 21 microseconds
cpucycles observed persecond 3052069767...3206073171 with 131072 loops 42 microseconds
cpucycles observed persecond 3050395348...3125523810 with 262144 loops 85 microseconds
cpucycles observed persecond 3085123529...3123059524 with 524288 loops 169 microseconds
cpucycles observed persecond 3084561764...3103434912 with 1048576 loops 339 microseconds
</code></pre>
<p><code>nucnuc</code>,
Intel Pentium N3700:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 26 scaling 1.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 120 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 120 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 427 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 320 scaling 1.600000 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 1800 scaling 1600.000000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 1600000000
cpucycles implementation amd64-pmc
cpucycles median 66 +12+12+14+14-1-1+0-1+0-1+0-1+0+1-1+0-1+0-1+0-2+0-1+0-1+0-1+0-2+0-1+0-1+0-1+0-2+0-1+0-1+1-1+0-2-1-1+0-1+0-1+0-2+0-1+2+0-1+0-1+0+0-1
cpucycles observed persecond 1060500000...2325000000 with 2048 loops 3 microseconds
cpucycles observed persecond 1387166666...2208250000 with 4096 loops 5 microseconds
cpucycles observed persecond 1376083333...1705500000 with 8192 loops 11 microseconds
cpucycles observed persecond 1495727272...1671800000 with 16384 loops 21 microseconds
cpucycles observed persecond 1563428571...1655100000 with 32768 loops 41 microseconds
cpucycles observed persecond 1580807228...1626234568 with 65536 loops 82 microseconds
cpucycles observed persecond 1589539393...1612619632 with 131072 loops 164 microseconds
cpucycles observed persecond 1598841463...1610230062 with 262144 loops 327 microseconds
cpucycles observed persecond 1564336810...1569988042 with 524288 loops 670 microseconds
cpucycles observed persecond 1599759725...1602608098 with 1048576 loops 1310 microseconds
</code></pre>
<p><code>saber214</code>,
AMD FX-8350:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 167 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 168 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 376 scaling 4.013452 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 4213 scaling 4013.452000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 4013452000
cpucycles implementation amd64-tsc
cpucycles median 77 +87-2+21+7+4+1+0+2-2-7-4+0+1+4-2+3+1-2-2+5-6+2+2+2+2+1-1-1+0-4+0-1-1-1-2+3-1-1+2-2+0+0+2+0+0+2-2-2+1-1-2+2-5+2+0+2+0+1+0+3-2-1-1
cpucycles observed persecond 2767500000...5759000000 with 4096 loops 3 microseconds
cpucycles observed persecond 3426000000...4893800000 with 8192 loops 6 microseconds
cpucycles observed persecond 3724076923...4446363637 with 16384 loops 12 microseconds
cpucycles observed persecond 3977833333...4363318182 with 32768 loops 23 microseconds
cpucycles observed persecond 3984854166...4168739131 with 65536 loops 47 microseconds
cpucycles observed persecond 3981709923...4048193799 with 131072 loops 130 microseconds
cpucycles observed persecond 3982716417...4026914573 with 262144 loops 200 microseconds
cpucycles observed persecond 4001637602...4025136987 with 524288 loops 366 microseconds
cpucycles observed persecond 4007411111...4018600248 with 1048576 loops 809 microseconds
</code></pre>
<p><code>gcc14</code>,
Intel Xeon E5-2620 v3,
Debian testing (bookworm),
Linux kernel 6.0.0-6-amd64:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 amd64-pmc precision 41 scaling 1.000000 only32 0
cpucycles tracesetup 1 amd64-tsc precision 148 scaling 1.000000 only32 0
cpucycles tracesetup 2 amd64-tscasm precision 148 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-perfevent precision 159 scaling 1.000000 only32 0
cpucycles tracesetup 4 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 5 default-monotonic precision 289 scaling 3.200000 only32 0
cpucycles tracesetup 6 default-gettimeofday precision 3400 scaling 3200.000000 only32 0
cpucycles tracesetup 7 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3200000000
cpucycles implementation amd64-pmc
cpucycles median 47 +47+28+0+2-5+0+2-5+16+2-5+0+2-5+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0+1-4+0
cpucycles observed persecond 1653800000...2819333334 with 8192 loops 4 microseconds
cpucycles observed persecond 1832111111...2389285715 with 16384 loops 8 microseconds
cpucycles observed persecond 1936058823...2207200000 with 32768 loops 16 microseconds
cpucycles observed persecond 2052843750...2196200000 with 65536 loops 31 microseconds
cpucycles observed persecond 2050750000...2120048388 with 131072 loops 63 microseconds
cpucycles observed persecond 2081896825...2117048388 with 262144 loops 125 microseconds
cpucycles observed persecond 2089478087...2107044177 with 524288 loops 250 microseconds
cpucycles observed persecond 2093343313...2102124249 with 1048576 loops 500 microseconds
</code></pre>
<p><code>gcc23</code>,
Cavium Octeon II V0.1,
Debian 8.11,
Linux kernel 4.1.4:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 mips64-cc precision 24 scaling 1.000000 only32 1
cpucycles tracesetup 1 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 46702 scaling 2.399988 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 45799 scaling 2399.987654 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 2399987654
cpucycles implementation mips64-cc
cpucycles median 2177 +828+17+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 641900000...1845125000 with 1024 loops 9 microseconds
cpucycles observed persecond 745357142...1352083334 with 2048 loops 13 microseconds
cpucycles observed persecond 809826086...1162333334 with 4096 loops 22 microseconds
cpucycles observed persecond 897717948...1104405406 with 8192 loops 38 microseconds
cpucycles observed persecond 957467532...1059986667 with 16384 loops 76 microseconds
cpucycles observed persecond 973102189...1029777778 with 32768 loops 136 microseconds
cpucycles observed persecond 986518656...1015830828 with 65536 loops 267 microseconds
cpucycles observed persecond 993452830...1008166667 with 131072 loops 529 microseconds
cpucycles observed persecond 996036966...1003403609 with 262144 loops 1054 microseconds
cpucycles observed persecond 984706378...1001682630 with 524288 loops 2131 microseconds
cpucycles observed persecond 992585292...1001178580 with 1048576 loops 4296 microseconds
</code></pre>
<p><code>gcc45</code>,
AMD Athlon II X4 640,
Debian 8.11,
Linux kernel 3.16.0-11-686-pae:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 x86-tsc precision 199 scaling 1.000000 only32 0
cpucycles tracesetup 1 x86-tscasm precision 199 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-perfevent precision 170 scaling 1.000000 only32 0
cpucycles tracesetup 3 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-monotonic precision 941 scaling 3.000000 only32 0
cpucycles tracesetup 5 default-gettimeofday precision 3200 scaling 3000.000000 only32 0
cpucycles tracesetup 6 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3000000000
cpucycles implementation default-perfevent
cpucycles median 72 +12+0+0+0+0+0+0+0+5+0+0+0+0+0+0+0+2+0+0+0+0+0+0+0+1+0+0+0+0+0+0+0+2+0+0+0+0+0+0+0+1+0+0+0+0+0+0+0+2+0+0+0+0+0+0+0+1+0+0+0+0+0+0
cpucycles observed persecond 541500000...1812000000 with 1024 loops 3 microseconds
cpucycles observed persecond 712333333...1212250000 with 2048 loops 5 microseconds
cpucycles observed persecond 1193285714...1733600000 with 4096 loops 6 microseconds
cpucycles observed persecond 1689176470...1804562500 with 8192 loops 33 microseconds
cpucycles observed persecond 1713074626...1770600000 with 16384 loops 66 microseconds
cpucycles observed persecond 1765107692...1795140625 with 32768 loops 129 microseconds
cpucycles observed persecond 1785369649...1800603922 with 65536 loops 256 microseconds
cpucycles observed persecond 1781377862...1796288462 with 131072 loops 261 microseconds
cpucycles observed persecond 1772647398...1778247827 with 262144 loops 691 microseconds
cpucycles observed persecond 1789670493...1794149598 with 524288 loops 870 microseconds
cpucycles observed persecond 1860276211...1861561332 with 1048576 loops 3156 microseconds
</code></pre>
<p><code>gcc92</code>,
SiFive Freedom U740,
Ubuntu 22.04,
Linux kernel 5.15.0-1014-generic:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 riscv64-rdcycle precision 8 scaling 1.000000 only32 0
cpucycles tracesetup 1 default-perfevent precision 3024 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 2599 scaling 2.399988 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 2599 scaling 2399.987654 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 2399987654
cpucycles implementation riscv64-rdcycle
cpucycles median 8 +33+27+1+1+1+1+0+0+0+22+0+0+0+0+0+0+0+628+0+0+0+7+0+0+0+145+0+0+0+0+0+0+0+22+0+0+0+0+0+0+0+158+0+0+0+0+0+0+0+22+0+0+0+0+0+0+0+22+0+0+0+0+0
cpucycles observed persecond 530250000...1978000000 with 1024 loops 3 microseconds
cpucycles observed persecond 831000000...1915666667 with 2048 loops 4 microseconds
cpucycles observed persecond 1055750000...1689500000 with 4096 loops 7 microseconds
cpucycles observed persecond 1045562500...1305428572 with 8192 loops 15 microseconds
cpucycles observed persecond 1102700000...1236357143 with 16384 loops 29 microseconds
cpucycles observed persecond 1176053571...1247444445 with 32768 loops 55 microseconds
cpucycles observed persecond 1173321428...1209127273 with 65536 loops 111 microseconds
cpucycles observed persecond 1187805429...1205210046 with 131072 loops 220 microseconds
cpucycles observed persecond 1192415909...1201157535 with 262144 loops 439 microseconds
cpucycles observed persecond 1194694760...1199247717 with 524288 loops 877 microseconds
cpucycles observed persecond 1194656004...1197023034 with 1048576 loops 1781 microseconds
</code></pre>
<p><code>gcc103</code>,
Apple M1 (Icestorm-M1 + Firestorm-M1),
Debian unstable (bookworm),
Linux kernel 6.0.0-rc5-asahi-00001-gc62bd3fe430f:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 arm64-pmc precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 1 arm64-vct precision 186 scaling 86.000000 only32 0
cpucycles tracesetup 2 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 4 default-monotonic precision 285 scaling 2.064000 only32 0
cpucycles tracesetup 5 default-gettimeofday precision 2264 scaling 2064.000000 only32 0
cpucycles tracesetup 6 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 2064000000
cpucycles implementation arm64-vct
cpucycles median 0 +0+86+0+0+0+0+0+0+0+0+0+0+0+0+86+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+86+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+86+0+0+0+0+0+0+0+0
cpucycles observed persecond 1784500000...3655000000 with 8192 loops 3 microseconds
cpucycles observed persecond 1773750000...2393666667 with 16384 loops 7 microseconds
cpucycles observed persecond 1897733333...2222769231 with 32768 loops 14 microseconds
cpucycles observed persecond 1951310344...2114962963 with 65536 loops 28 microseconds
cpucycles observed persecond 2024071428...2107000000 with 131072 loops 55 microseconds
cpucycles observed persecond 2041531531...2082935780 with 262144 loops 110 microseconds
cpucycles observed persecond 2051158371...2071461188 with 524288 loops 220 microseconds
cpucycles observed persecond 2058539682...2068309795 with 1048576 loops 440 microseconds
</code></pre>
<p><code>gcc112</code> (<code>gcc2-power8</code>),
IBM POWER8E,
CentOS 7.9 AltArch,
Linux kernel 3.10.0-1127.13.1.el7.ppc64le:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 ppc64-mftb precision 251 scaling 7.207031 only32 0
cpucycles tracesetup 1 default-perfevent precision 295 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 536 scaling 3.690000 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 3890 scaling 3690.000000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3690000000
cpucycles implementation ppc64-mftb
cpucycles median 195 +2969-8+14+0-8+7-8-7+7+6-7-1+0-1+0+7+7-15+7-1-7+6+0+0-8+0+6+0-8+7+0+7-8-8-7-1+7-8+7+0-8+0+14-8-7+6+0-8+7+7-15+0-1+0-1+14+0-15+14+0-1+7+0
cpucycles observed persecond 2603750000...5510000000 with 2048 loops 3 microseconds
cpucycles observed persecond 3430500000...6052250000 with 4096 loops 5 microseconds
cpucycles observed persecond 3411333333...4457500000 with 8192 loops 11 microseconds
cpucycles observed persecond 3548695652...4060333334 with 16384 loops 22 microseconds
cpucycles observed persecond 3624977777...3876534884 with 32768 loops 44 microseconds
cpucycles observed persecond 3621855555...3745363637 with 65536 loops 89 microseconds
cpucycles observed persecond 3660157303...3722227273 with 131072 loops 177 microseconds
cpucycles observed persecond 3680471751...3711622160 with 262144 loops 353 microseconds
cpucycles observed persecond 3685321074...3700886525 with 524288 loops 706 microseconds
cpucycles observed persecond 3687745930...3695537208 with 1048576 loops 1412 microseconds
</code></pre>
<p><code>gcc202</code>,
UltraSparc T5,
Debian unstable (bookworm),
Linux kernel 5.19.0-2-sparc64-smp:</p>
<pre><code>cpucycles version 20230105
cpucycles tracesetup 0 sparc64-rdtick precision 65 scaling 1.000000 only32 0
cpucycles tracesetup 1 default-perfevent precision 386 scaling 1.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 442 scaling 3.599910 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 3799 scaling 3599.910000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 3599910000
cpucycles implementation sparc64-rdtick
cpucycles median 73 +24+0+24+24+24+24+24+24+0+1+24+0+1+24+0+1+24+0+0+1+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+1+0+0+0+0+0+0+0+0+0+0+0+0+0
cpucycles observed persecond 2751500000...4258250000 with 4096 loops 5 microseconds
cpucycles observed persecond 3289200000...4206875000 with 8192 loops 9 microseconds
cpucycles observed persecond 3454789473...3900823530 with 16384 loops 18 microseconds
cpucycles observed persecond 3452026315...3659888889 with 32768 loops 37 microseconds
cpucycles observed persecond 3543770270...3650916667 with 65536 loops 73 microseconds
cpucycles observed persecond 3567299319...3620662069 with 131072 loops 146 microseconds
cpucycles observed persecond 3591373287...3618220690 with 262144 loops 291 microseconds
cpucycles observed persecond 3597353344...3610774527 with 524288 loops 582 microseconds
cpucycles observed persecond 3595899403...3603058071 with 1048576 loops 1172 microseconds
</code></pre>
<p>IBM z15:</p>
<pre><code>cpucycles version 20230106
cpucycles tracesetup 0 s390x-stckf precision 250 scaling 1.269531 only32 0
cpucycles tracesetup 1 default-perfevent precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 2 default-mach precision 0 scaling 0.000000 only32 0
cpucycles tracesetup 3 default-monotonic precision 272 scaling 5.200000 only32 0
cpucycles tracesetup 4 default-gettimeofday precision 5400 scaling 5200.000000 only32 0
cpucycles tracesetup 5 default-zero precision 0 scaling 0.000000 only32 0
cpucycles persecond 5200000000
cpucycles implementation s390x-stckf
cpucycles median 48 +87+8+0-2+0+0+38-2+0+1-3+1+28+0+3-3+1+0+28+0-2+3+0-2+36+0+0+0+1+0+28+0-2+0+3-2+35+1+0-2+0+3+28+0-2+0+0-2+3+25+3+0-2+0+1+35+1+0+0-2+0+28+0
cpucycles observed persecond 4948941176...5627733334 with 8192 loops 16 microseconds
cpucycles observed persecond 4104125000...5515666667 with 16384 loops 7 microseconds
cpucycles observed persecond 5047076923...5987818182 with 32768 loops 12 microseconds
cpucycles observed persecond 5044846153...5475708334 with 65536 loops 25 microseconds
cpucycles observed persecond 5141313725...5357428572 with 131072 loops 50 microseconds
cpucycles observed persecond 5150892156...5257250000 with 262144 loops 101 microseconds
cpucycles observed persecond 5183421568...5236549505 with 524288 loops 203 microseconds
cpucycles observed persecond 5190282555...5216582717 with 1048576 loops 406 microseconds
</code></pre><hr><font size=1><b>Version:</b>
This is version 2023.01.06 of the "Counters" web page.
</font>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More