mirror of
https://github.com/mimblewimble/grin.git
synced 2025-05-08 02:01:14 +03:00
add CuckooParams::node_mask, obsolete EdgeType and sipnode shift arg (#3365)
This commit is contained in:
parent
39ca5d1c11
commit
238522a9fb
9 changed files with 155 additions and 295 deletions
|
@ -25,7 +25,7 @@ use crate::consensus::{
|
|||
use crate::core::block::HeaderVersion;
|
||||
use crate::pow::{
|
||||
self, new_cuckaroo_ctx, new_cuckarood_ctx, new_cuckaroom_ctx, new_cuckarooz_ctx,
|
||||
new_cuckatoo_ctx, EdgeType, PoWContext,
|
||||
new_cuckatoo_ctx, PoWContext,
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use util::OneTime;
|
||||
|
@ -219,10 +219,7 @@ pub fn create_pow_context<T>(
|
|||
edge_bits: u8,
|
||||
proof_size: usize,
|
||||
max_sols: u32,
|
||||
) -> Result<Box<dyn PoWContext<T>>, pow::Error>
|
||||
where
|
||||
T: EdgeType + 'static,
|
||||
{
|
||||
) -> Result<Box<dyn PoWContext>, pow::Error> {
|
||||
let chain_type = get_chain_type();
|
||||
match chain_type {
|
||||
// Mainnet has Cuckaroo{,d,m,z}29 for AR and Cuckatoo31+ for AF
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
//! Common types and traits for cuckoo family of solvers
|
||||
|
||||
use crate::pow::error::{Error, ErrorKind};
|
||||
use crate::pow::error::Error;
|
||||
use crate::pow::num::{PrimInt, ToPrimitive};
|
||||
use crate::pow::siphash::siphash24;
|
||||
use blake2::blake2b::blake2b;
|
||||
|
@ -31,49 +31,27 @@ impl EdgeType for u64 {}
|
|||
|
||||
/// An edge in the Cuckoo graph, simply references two u64 nodes.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
pub struct Edge<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
pub u: T,
|
||||
pub v: T,
|
||||
pub struct Edge {
|
||||
pub u: u64,
|
||||
pub v: u64,
|
||||
}
|
||||
|
||||
impl<T> fmt::Display for Edge<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl fmt::Display for Edge {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"(u: {}, v: {})",
|
||||
self.u.to_u64().unwrap_or(0),
|
||||
self.v.to_u64().unwrap_or(0)
|
||||
)
|
||||
write!(f, "(u: {}, v: {})", self.u, self.v)
|
||||
}
|
||||
}
|
||||
|
||||
/// An element of an adjencency list
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Link<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
pub next: T,
|
||||
pub to: T,
|
||||
pub struct Link {
|
||||
pub next: u64,
|
||||
pub to: u64,
|
||||
}
|
||||
|
||||
impl<T> fmt::Display for Link<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl fmt::Display for Link {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"(next: {}, to: {})",
|
||||
self.next.to_u64().unwrap_or(0),
|
||||
self.to.to_u64().unwrap_or(0)
|
||||
)
|
||||
write!(f, "(next: {}, to: {})", self.next, self.to)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,31 +113,29 @@ macro_rules! to_edge {
|
|||
|
||||
/// Utility struct to calculate commonly used Cuckoo parameters calculated
|
||||
/// from header, nonce, edge_bits, etc.
|
||||
pub struct CuckooParams<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
pub struct CuckooParams {
|
||||
pub edge_bits: u8,
|
||||
pub proof_size: usize,
|
||||
pub num_edges: u64,
|
||||
pub siphash_keys: [u64; 4],
|
||||
pub edge_mask: T,
|
||||
pub edge_mask: u64,
|
||||
pub node_mask: u64,
|
||||
}
|
||||
|
||||
impl<T> CuckooParams<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl CuckooParams {
|
||||
/// Instantiates new params and calculate edge mask, etc
|
||||
pub fn new(edge_bits: u8, proof_size: usize) -> Result<CuckooParams<T>, Error> {
|
||||
let num_edges = (1 as u64) << edge_bits;
|
||||
let edge_mask = to_edge!(T, num_edges - 1);
|
||||
pub fn new(edge_bits: u8, node_bits: u8, proof_size: usize) -> Result<CuckooParams, Error> {
|
||||
let num_edges = 1u64 << edge_bits;
|
||||
let edge_mask = num_edges - 1;
|
||||
let num_nodes = 1u64 << node_bits;
|
||||
let node_mask = num_nodes - 1;
|
||||
Ok(CuckooParams {
|
||||
edge_bits,
|
||||
proof_size,
|
||||
num_edges,
|
||||
siphash_keys: [0; 4],
|
||||
edge_mask,
|
||||
node_mask,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -170,16 +146,9 @@ where
|
|||
}
|
||||
|
||||
/// Return siphash masked for type
|
||||
pub fn sipnode(&self, edge: T, uorv: u64, shift: bool) -> Result<T, Error> {
|
||||
let hash_u64 = siphash24(
|
||||
&self.siphash_keys,
|
||||
2 * edge.to_u64().ok_or(ErrorKind::IntegerCast)? + uorv,
|
||||
);
|
||||
let mut masked = hash_u64 & self.edge_mask.to_u64().ok_or(ErrorKind::IntegerCast)?;
|
||||
if shift {
|
||||
masked <<= 1;
|
||||
masked |= uorv;
|
||||
}
|
||||
Ok(T::from(masked).ok_or(ErrorKind::IntegerCast)?)
|
||||
pub fn sipnode(&self, edge: u64, uorv: u64) -> Result<u64, Error> {
|
||||
let hash_u64 = siphash24(&self.siphash_keys, 2 * edge + uorv);
|
||||
let node = hash_u64 & self.node_mask;
|
||||
Ok(node)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
//! obtain blocks of values. Nodes are then extracted from those edges.
|
||||
|
||||
use crate::global;
|
||||
use crate::pow::common::{CuckooParams, EdgeType};
|
||||
use crate::pow::common::CuckooParams;
|
||||
use crate::pow::error::{Error, ErrorKind};
|
||||
use crate::pow::siphash::siphash_block;
|
||||
use crate::pow::{PoWContext, Proof};
|
||||
|
@ -32,29 +32,17 @@ use crate::pow::{PoWContext, Proof};
|
|||
/// Instantiate a new CuckarooContext as a PowContext. Note that this can't
|
||||
/// be moved in the PoWContext trait as this particular trait needs to be
|
||||
/// convertible to an object trait.
|
||||
pub fn new_cuckaroo_ctx<T>(
|
||||
edge_bits: u8,
|
||||
proof_size: usize,
|
||||
) -> Result<Box<dyn PoWContext<T>>, Error>
|
||||
where
|
||||
T: EdgeType + 'static,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size)?;
|
||||
pub fn new_cuckaroo_ctx(edge_bits: u8, proof_size: usize) -> Result<Box<dyn PoWContext>, Error> {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits, proof_size)?;
|
||||
Ok(Box::new(CuckarooContext { params }))
|
||||
}
|
||||
|
||||
/// Cuckaroo cycle context. Only includes the verifier for now.
|
||||
pub struct CuckarooContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
params: CuckooParams<T>,
|
||||
pub struct CuckarooContext {
|
||||
params: CuckooParams,
|
||||
}
|
||||
|
||||
impl<T> PoWContext<T> for CuckarooContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl PoWContext for CuckarooContext {
|
||||
fn set_header_nonce(
|
||||
&mut self,
|
||||
header: Vec<u8>,
|
||||
|
@ -76,10 +64,9 @@ where
|
|||
let mut uvs = vec![0u64; 2 * proof.proof_size()];
|
||||
let mut xor0: u64 = 0;
|
||||
let mut xor1: u64 = 0;
|
||||
let node_mask: u64 = to_u64!(self.params.edge_mask);
|
||||
|
||||
for n in 0..proof.proof_size() {
|
||||
if nonces[n] > to_u64!(self.params.edge_mask) {
|
||||
if nonces[n] > self.params.edge_mask {
|
||||
return Err(ErrorKind::Verification("edge too big".to_owned()).into());
|
||||
}
|
||||
if n > 0 && nonces[n] <= nonces[n - 1] {
|
||||
|
@ -87,9 +74,9 @@ where
|
|||
}
|
||||
// 21 is standard siphash rotation constant
|
||||
let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 21, false);
|
||||
uvs[2 * n] = edge & node_mask;
|
||||
uvs[2 * n] = edge & self.params.node_mask;
|
||||
xor0 ^= uvs[2 * n];
|
||||
uvs[2 * n + 1] = (edge >> 32) & node_mask;
|
||||
uvs[2 * n + 1] = (edge >> 32) & self.params.node_mask;
|
||||
xor1 ^= uvs[2 * n + 1];
|
||||
}
|
||||
if xor0 | xor1 != 0 {
|
||||
|
@ -169,7 +156,7 @@ mod test {
|
|||
#[test]
|
||||
fn cuckaroo19_vectors() {
|
||||
global::set_local_chain_type(global::ChainTypes::Mainnet);
|
||||
let mut ctx = new_impl::<u64>(19, 42);
|
||||
let mut ctx = new_impl(19, 42);
|
||||
ctx.params.siphash_keys = V1_19_HASH;
|
||||
assert!(ctx.verify(&Proof::new(V1_19_SOL.to_vec())).is_ok());
|
||||
ctx.params.siphash_keys = V2_19_HASH.clone();
|
||||
|
@ -177,11 +164,8 @@ mod test {
|
|||
assert!(ctx.verify(&Proof::zero(42)).is_err());
|
||||
}
|
||||
|
||||
fn new_impl<T>(edge_bits: u8, proof_size: usize) -> CuckarooContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size).unwrap();
|
||||
fn new_impl(edge_bits: u8, proof_size: usize) -> CuckarooContext {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits, proof_size).unwrap();
|
||||
CuckarooContext { params }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
//! and requires cycles to alternate between even- and odd-indexed edges.
|
||||
|
||||
use crate::global;
|
||||
use crate::pow::common::{CuckooParams, EdgeType};
|
||||
use crate::pow::common::CuckooParams;
|
||||
use crate::pow::error::{Error, ErrorKind};
|
||||
use crate::pow::siphash::siphash_block;
|
||||
use crate::pow::{PoWContext, Proof};
|
||||
|
@ -31,29 +31,17 @@ use crate::pow::{PoWContext, Proof};
|
|||
/// Instantiate a new CuckaroodContext as a PowContext. Note that this can't
|
||||
/// be moved in the PoWContext trait as this particular trait needs to be
|
||||
/// convertible to an object trait.
|
||||
pub fn new_cuckarood_ctx<T>(
|
||||
edge_bits: u8,
|
||||
proof_size: usize,
|
||||
) -> Result<Box<dyn PoWContext<T>>, Error>
|
||||
where
|
||||
T: EdgeType + 'static,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size)?;
|
||||
pub fn new_cuckarood_ctx(edge_bits: u8, proof_size: usize) -> Result<Box<dyn PoWContext>, Error> {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits - 1, proof_size)?;
|
||||
Ok(Box::new(CuckaroodContext { params }))
|
||||
}
|
||||
|
||||
/// Cuckarood cycle context. Only includes the verifier for now.
|
||||
pub struct CuckaroodContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
params: CuckooParams<T>,
|
||||
pub struct CuckaroodContext {
|
||||
params: CuckooParams,
|
||||
}
|
||||
|
||||
impl<T> PoWContext<T> for CuckaroodContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl PoWContext for CuckaroodContext {
|
||||
fn set_header_nonce(
|
||||
&mut self,
|
||||
header: Vec<u8>,
|
||||
|
@ -76,14 +64,13 @@ where
|
|||
let mut ndir = vec![0usize; 2];
|
||||
let mut xor0: u64 = 0;
|
||||
let mut xor1: u64 = 0;
|
||||
let node_mask: u64 = to_u64!(self.params.edge_mask) >> 1;
|
||||
|
||||
for n in 0..proof.proof_size() {
|
||||
let dir = (nonces[n] & 1) as usize;
|
||||
if ndir[dir] >= proof.proof_size() / 2 {
|
||||
return Err(ErrorKind::Verification("edges not balanced".to_owned()).into());
|
||||
}
|
||||
if nonces[n] > to_u64!(self.params.edge_mask) {
|
||||
if nonces[n] > self.params.edge_mask {
|
||||
return Err(ErrorKind::Verification("edge too big".to_owned()).into());
|
||||
}
|
||||
if n > 0 && nonces[n] <= nonces[n - 1] {
|
||||
|
@ -92,9 +79,9 @@ where
|
|||
// cuckarood uses a non-standard siphash rotation constant 25 as anti-ASIC tweak
|
||||
let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 25, false);
|
||||
let idx = 4 * ndir[dir] + 2 * dir;
|
||||
uvs[idx] = edge & node_mask;
|
||||
uvs[idx] = edge & self.params.node_mask;
|
||||
xor0 ^= uvs[idx];
|
||||
uvs[idx + 1] = (edge >> 32) & node_mask;
|
||||
uvs[idx + 1] = (edge >> 32) & self.params.node_mask;
|
||||
xor1 ^= uvs[idx + 1];
|
||||
ndir[dir] += 1;
|
||||
}
|
||||
|
@ -171,21 +158,18 @@ mod test {
|
|||
#[test]
|
||||
fn cuckarood19_29_vectors() {
|
||||
global::set_local_chain_type(global::ChainTypes::Mainnet);
|
||||
let mut ctx19 = new_impl::<u64>(19, 42);
|
||||
let mut ctx19 = new_impl(19, 42);
|
||||
ctx19.params.siphash_keys = V1_19_HASH;
|
||||
assert!(ctx19.verify(&Proof::new(V1_19_SOL.to_vec())).is_ok());
|
||||
assert!(ctx19.verify(&Proof::zero(42)).is_err());
|
||||
let mut ctx29 = new_impl::<u64>(29, 42);
|
||||
let mut ctx29 = new_impl(29, 42);
|
||||
ctx29.params.siphash_keys = V2_29_HASH;
|
||||
assert!(ctx29.verify(&Proof::new(V2_29_SOL.to_vec())).is_ok());
|
||||
assert!(ctx29.verify(&Proof::zero(42)).is_err());
|
||||
}
|
||||
|
||||
fn new_impl<T>(edge_bits: u8, proof_size: usize) -> CuckaroodContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size).unwrap();
|
||||
fn new_impl(edge_bits: u8, proof_size: usize) -> CuckaroodContext {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits - 1, proof_size).unwrap();
|
||||
CuckaroodContext { params }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
//! in a mono-partite graph, from which it derives the letter 'm'.
|
||||
|
||||
use crate::global;
|
||||
use crate::pow::common::{CuckooParams, EdgeType};
|
||||
use crate::pow::common::CuckooParams;
|
||||
use crate::pow::error::{Error, ErrorKind};
|
||||
use crate::pow::siphash::siphash_block;
|
||||
use crate::pow::{PoWContext, Proof};
|
||||
|
@ -30,29 +30,17 @@ use crate::pow::{PoWContext, Proof};
|
|||
/// Instantiate a new CuckaroomContext as a PowContext. Note that this can't
|
||||
/// be moved in the PoWContext trait as this particular trait needs to be
|
||||
/// convertible to an object trait.
|
||||
pub fn new_cuckaroom_ctx<T>(
|
||||
edge_bits: u8,
|
||||
proof_size: usize,
|
||||
) -> Result<Box<dyn PoWContext<T>>, Error>
|
||||
where
|
||||
T: EdgeType + 'static,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size)?;
|
||||
pub fn new_cuckaroom_ctx(edge_bits: u8, proof_size: usize) -> Result<Box<dyn PoWContext>, Error> {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits, proof_size)?;
|
||||
Ok(Box::new(CuckaroomContext { params }))
|
||||
}
|
||||
|
||||
/// Cuckaroom cycle context. Only includes the verifier for now.
|
||||
pub struct CuckaroomContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
params: CuckooParams<T>,
|
||||
pub struct CuckaroomContext {
|
||||
params: CuckooParams,
|
||||
}
|
||||
|
||||
impl<T> PoWContext<T> for CuckaroomContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl PoWContext for CuckaroomContext {
|
||||
fn set_header_nonce(
|
||||
&mut self,
|
||||
header: Vec<u8>,
|
||||
|
@ -76,10 +64,9 @@ where
|
|||
let mut to = vec![0u64; proofsize];
|
||||
let mut xor_from: u64 = 0;
|
||||
let mut xor_to: u64 = 0;
|
||||
let node_mask: u64 = to_u64!(self.params.edge_mask) >> 1;
|
||||
|
||||
for n in 0..proofsize {
|
||||
if nonces[n] > to_u64!(self.params.edge_mask) {
|
||||
if nonces[n] > self.params.edge_mask {
|
||||
return Err(ErrorKind::Verification("edge too big".to_owned()).into());
|
||||
}
|
||||
if n > 0 && nonces[n] <= nonces[n - 1] {
|
||||
|
@ -87,9 +74,9 @@ where
|
|||
}
|
||||
// 21 is standard siphash rotation constant
|
||||
let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 21, true);
|
||||
from[n] = edge & node_mask;
|
||||
from[n] = edge & self.params.node_mask;
|
||||
xor_from ^= from[n];
|
||||
to[n] = (edge >> 32) & node_mask;
|
||||
to[n] = (edge >> 32) & self.params.node_mask;
|
||||
xor_to ^= to[n];
|
||||
}
|
||||
if xor_from != xor_to {
|
||||
|
@ -164,21 +151,18 @@ mod test {
|
|||
#[test]
|
||||
fn cuckaroom19_29_vectors() {
|
||||
global::set_local_chain_type(global::ChainTypes::Mainnet);
|
||||
let mut ctx19 = new_impl::<u64>(19, 42);
|
||||
let mut ctx19 = new_impl(19, 42);
|
||||
ctx19.params.siphash_keys = V1_19_HASH;
|
||||
assert!(ctx19.verify(&Proof::new(V1_19_SOL.to_vec())).is_ok());
|
||||
assert!(ctx19.verify(&Proof::zero(42)).is_err());
|
||||
let mut ctx29 = new_impl::<u64>(29, 42);
|
||||
let mut ctx29 = new_impl(29, 42);
|
||||
ctx29.params.siphash_keys = V2_29_HASH;
|
||||
assert!(ctx29.verify(&Proof::new(V2_29_SOL.to_vec())).is_ok());
|
||||
assert!(ctx29.verify(&Proof::zero(42)).is_err());
|
||||
}
|
||||
|
||||
fn new_impl<T>(edge_bits: u8, proof_size: usize) -> CuckaroomContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size).unwrap();
|
||||
fn new_impl(edge_bits: u8, proof_size: usize) -> CuckaroomContext {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits, proof_size).unwrap();
|
||||
CuckaroomContext { params }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
//! accordingly.
|
||||
|
||||
use crate::global;
|
||||
use crate::pow::common::{CuckooParams, EdgeType};
|
||||
use crate::pow::common::CuckooParams;
|
||||
use crate::pow::error::{Error, ErrorKind};
|
||||
use crate::pow::siphash::siphash_block;
|
||||
use crate::pow::{PoWContext, Proof};
|
||||
|
@ -31,29 +31,17 @@ use crate::pow::{PoWContext, Proof};
|
|||
/// Instantiate a new CuckaroozContext as a PowContext. Note that this can't
|
||||
/// be moved in the PoWContext trait as this particular trait needs to be
|
||||
/// convertible to an object trait.
|
||||
pub fn new_cuckarooz_ctx<T>(
|
||||
edge_bits: u8,
|
||||
proof_size: usize,
|
||||
) -> Result<Box<dyn PoWContext<T>>, Error>
|
||||
where
|
||||
T: EdgeType + 'static,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size)?;
|
||||
pub fn new_cuckarooz_ctx(edge_bits: u8, proof_size: usize) -> Result<Box<dyn PoWContext>, Error> {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits + 1, proof_size)?;
|
||||
Ok(Box::new(CuckaroozContext { params }))
|
||||
}
|
||||
|
||||
/// Cuckarooz cycle context. Only includes the verifier for now.
|
||||
pub struct CuckaroozContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
params: CuckooParams<T>,
|
||||
pub struct CuckaroozContext {
|
||||
params: CuckooParams,
|
||||
}
|
||||
|
||||
impl<T> PoWContext<T> for CuckaroozContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl PoWContext for CuckaroozContext {
|
||||
fn set_header_nonce(
|
||||
&mut self,
|
||||
header: Vec<u8>,
|
||||
|
@ -74,10 +62,9 @@ where
|
|||
let nonces = &proof.nonces;
|
||||
let mut uvs = vec![0u64; 2 * proof.proof_size()];
|
||||
let mut xoruv: u64 = 0;
|
||||
let node_mask: u64 = to_u64!(self.params.edge_mask) << 1 | 1;
|
||||
|
||||
for n in 0..proof.proof_size() {
|
||||
if nonces[n] > to_u64!(self.params.edge_mask) {
|
||||
if nonces[n] > self.params.edge_mask {
|
||||
return Err(ErrorKind::Verification("edge too big".to_owned()).into());
|
||||
}
|
||||
if n > 0 && nonces[n] <= nonces[n - 1] {
|
||||
|
@ -85,8 +72,8 @@ where
|
|||
}
|
||||
// 21 is standard siphash rotation constant
|
||||
let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 21, true);
|
||||
uvs[2 * n] = edge & node_mask;
|
||||
uvs[2 * n + 1] = (edge >> 32) & node_mask;
|
||||
uvs[2 * n] = edge & self.params.node_mask;
|
||||
uvs[2 * n + 1] = (edge >> 32) & self.params.node_mask;
|
||||
xoruv ^= uvs[2 * n] ^ uvs[2 * n + 1];
|
||||
}
|
||||
if xoruv != 0 {
|
||||
|
@ -167,13 +154,13 @@ mod test {
|
|||
#[test]
|
||||
fn cuckarooz19_29_vectors() {
|
||||
global::set_local_chain_type(global::ChainTypes::Mainnet);
|
||||
let mut ctx19 = new_impl::<u64>(19, 42);
|
||||
let mut ctx19 = new_impl(19, 42);
|
||||
ctx19.params.siphash_keys = V1_19_HASH.clone();
|
||||
assert!(ctx19
|
||||
.verify(&Proof::new(V1_19_SOL.to_vec().clone()))
|
||||
.is_ok());
|
||||
assert!(ctx19.verify(&Proof::zero(42)).is_err());
|
||||
let mut ctx29 = new_impl::<u64>(29, 42);
|
||||
let mut ctx29 = new_impl(29, 42);
|
||||
ctx29.params.siphash_keys = V2_29_HASH.clone();
|
||||
assert!(ctx29
|
||||
.verify(&Proof::new(V2_29_SOL.to_vec().clone()))
|
||||
|
@ -181,11 +168,8 @@ mod test {
|
|||
assert!(ctx29.verify(&Proof::zero(42)).is_err());
|
||||
}
|
||||
|
||||
fn new_impl<T>(edge_bits: u8, proof_size: usize) -> CuckaroozContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
let params = CuckooParams::new(edge_bits, proof_size).unwrap();
|
||||
fn new_impl(edge_bits: u8, proof_size: usize) -> CuckaroozContext {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits + 1, proof_size).unwrap();
|
||||
CuckaroozContext { params }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
//! Implementation of Cuckatoo Cycle designed by John Tromp.
|
||||
use crate::global;
|
||||
use crate::pow::common::{CuckooParams, EdgeType, Link};
|
||||
use crate::pow::common::{CuckooParams, Link};
|
||||
use crate::pow::error::{Error, ErrorKind};
|
||||
use crate::pow::{PoWContext, Proof};
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
|
@ -21,18 +21,15 @@ use croaring::Bitmap;
|
|||
use std::mem;
|
||||
use util::ToHex;
|
||||
|
||||
struct Graph<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
struct Graph {
|
||||
/// Maximum number of edges
|
||||
max_edges: T,
|
||||
max_edges: u64,
|
||||
/// Maximum nodes
|
||||
max_nodes: u64,
|
||||
/// Adjacency links
|
||||
links: Vec<Link<T>>,
|
||||
links: Vec<Link>,
|
||||
/// Index into links array
|
||||
adj_list: Vec<T>,
|
||||
adj_list: Vec<u64>,
|
||||
///
|
||||
visited: Bitmap,
|
||||
/// Maximum solutions
|
||||
|
@ -42,19 +39,16 @@ where
|
|||
/// proof size
|
||||
proof_size: usize,
|
||||
/// define NIL type
|
||||
nil: T,
|
||||
nil: u64,
|
||||
}
|
||||
|
||||
impl<T> Graph<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl Graph {
|
||||
/// Create a new graph with given parameters
|
||||
pub fn new(max_edges: T, max_sols: u32, proof_size: usize) -> Result<Graph<T>, Error> {
|
||||
if to_u64!(max_edges) >= u64::max_value() / 2 {
|
||||
pub fn new(max_edges: u64, max_sols: u32, proof_size: usize) -> Result<Graph, Error> {
|
||||
if max_edges >= u64::max_value() / 2 {
|
||||
return Err(ErrorKind::Verification("graph is to big to build".to_string()).into());
|
||||
}
|
||||
let max_nodes = 2 * to_u64!(max_edges);
|
||||
let max_nodes = 2 * max_edges;
|
||||
Ok(Graph {
|
||||
max_edges,
|
||||
max_nodes,
|
||||
|
@ -64,55 +58,52 @@ where
|
|||
adj_list: vec![],
|
||||
visited: Bitmap::create(),
|
||||
solutions: vec![],
|
||||
nil: T::max_value(),
|
||||
nil: u64::max_value(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), Error> {
|
||||
//TODO: Can be optimised
|
||||
self.links = Vec::with_capacity(2 * self.max_nodes as usize);
|
||||
self.adj_list = vec![T::max_value(); 2 * self.max_nodes as usize];
|
||||
self.adj_list = vec![u64::max_value(); 2 * self.max_nodes as usize];
|
||||
self.solutions = vec![Proof::zero(self.proof_size); 1];
|
||||
self.visited = Bitmap::create();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn byte_count(&self) -> Result<u64, Error> {
|
||||
Ok(
|
||||
2 * to_u64!(self.max_edges) * mem::size_of::<Link<T>>() as u64
|
||||
+ mem::size_of::<T>() as u64 * 2 * self.max_nodes,
|
||||
)
|
||||
Ok(2 * self.max_edges * mem::size_of::<Link>() as u64
|
||||
+ mem::size_of::<u64>() as u64 * 2 * self.max_nodes)
|
||||
}
|
||||
|
||||
/// Add an edge to the graph
|
||||
pub fn add_edge(&mut self, u: T, mut v: T) -> Result<(), Error> {
|
||||
let max_nodes_t = to_edge!(T, self.max_nodes);
|
||||
if u >= max_nodes_t || v >= max_nodes_t {
|
||||
pub fn add_edge(&mut self, u: u64, mut v: u64) -> Result<(), Error> {
|
||||
if u >= self.max_nodes || v >= self.max_nodes {
|
||||
return Err(ErrorKind::EdgeAddition.into());
|
||||
}
|
||||
v = v + to_edge!(T, self.max_nodes);
|
||||
let adj_u = self.adj_list[to_usize!(u ^ T::one())];
|
||||
let adj_v = self.adj_list[to_usize!(v ^ T::one())];
|
||||
v = v + self.max_nodes;
|
||||
let adj_u = self.adj_list[(u ^ 1) as usize];
|
||||
let adj_v = self.adj_list[(v ^ 1) as usize];
|
||||
if adj_u != self.nil && adj_v != self.nil {
|
||||
let sol_index = self.solutions.len() - 1;
|
||||
self.solutions[sol_index].nonces[0] = self.links.len() as u64 / 2;
|
||||
self.cycles_with_link(1, u, v)?;
|
||||
}
|
||||
let ulink = self.links.len();
|
||||
let vlink = self.links.len() + 1;
|
||||
if to_edge!(T, vlink) == self.nil {
|
||||
let ulink = self.links.len() as u64;
|
||||
let vlink = (self.links.len() + 1) as u64;
|
||||
if vlink == self.nil {
|
||||
return Err(ErrorKind::EdgeAddition.into());
|
||||
}
|
||||
self.links.push(Link {
|
||||
next: self.adj_list[to_usize!(u)],
|
||||
next: self.adj_list[u as usize],
|
||||
to: u,
|
||||
});
|
||||
self.links.push(Link {
|
||||
next: self.adj_list[to_usize!(v)],
|
||||
next: self.adj_list[v as usize],
|
||||
to: v,
|
||||
});
|
||||
self.adj_list[to_usize!(u)] = T::from(ulink).ok_or(ErrorKind::IntegerCast)?;
|
||||
self.adj_list[to_usize!(v)] = T::from(vlink).ok_or(ErrorKind::IntegerCast)?;
|
||||
self.adj_list[u as usize] = ulink;
|
||||
self.adj_list[v as usize] = vlink;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -120,11 +111,11 @@ where
|
|||
self.visited.contains(u as u32)
|
||||
}
|
||||
|
||||
fn cycles_with_link(&mut self, len: u32, u: T, dest: T) -> Result<(), Error> {
|
||||
if self.test_bit(to_u64!(u >> 1)) {
|
||||
fn cycles_with_link(&mut self, len: u32, u: u64, dest: u64) -> Result<(), Error> {
|
||||
if self.test_bit(u >> 1) {
|
||||
return Ok(());
|
||||
}
|
||||
if (u ^ T::one()) == dest {
|
||||
if (u ^ 1) == dest {
|
||||
if len == self.proof_size as u32 {
|
||||
if self.solutions.len() < self.max_sols as usize {
|
||||
// create next solution
|
||||
|
@ -135,20 +126,20 @@ where
|
|||
} else if len == self.proof_size as u32 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut au1 = self.adj_list[to_usize!(u ^ T::one())];
|
||||
let mut au1 = self.adj_list[(u ^ 1) as usize];
|
||||
if au1 != self.nil {
|
||||
self.visited.add(to_u32!(u >> 1));
|
||||
self.visited.add((u >> 1) as u32);
|
||||
while au1 != self.nil {
|
||||
let i = self.solutions.len() - 1;
|
||||
self.solutions[i].nonces[len as usize] = to_u64!(au1) / 2;
|
||||
let link_index = to_usize!(au1 ^ T::one());
|
||||
self.solutions[i].nonces[len as usize] = au1 / 2;
|
||||
let link_index = (au1 ^ 1) as usize;
|
||||
let link = self.links[link_index].to;
|
||||
if link != self.nil {
|
||||
self.cycles_with_link(len + 1, link, dest)?;
|
||||
}
|
||||
au1 = self.links[to_usize!(au1)].next;
|
||||
au1 = self.links[au1 as usize].next;
|
||||
}
|
||||
self.visited.remove(to_u32!(u >> 1));
|
||||
self.visited.remove((u >> 1) as u32);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -157,32 +148,23 @@ where
|
|||
/// Instantiate a new CuckatooContext as a PowContext. Note that this can't
|
||||
/// be moved in the PoWContext trait as this particular trait needs to be
|
||||
/// convertible to an object trait.
|
||||
pub fn new_cuckatoo_ctx<T>(
|
||||
pub fn new_cuckatoo_ctx(
|
||||
edge_bits: u8,
|
||||
proof_size: usize,
|
||||
max_sols: u32,
|
||||
) -> Result<Box<dyn PoWContext<T>>, Error>
|
||||
where
|
||||
T: EdgeType + 'static,
|
||||
{
|
||||
Ok(Box::new(CuckatooContext::<T>::new_impl(
|
||||
) -> Result<Box<dyn PoWContext>, Error> {
|
||||
Ok(Box::new(CuckatooContext::new_impl(
|
||||
edge_bits, proof_size, max_sols,
|
||||
)?))
|
||||
}
|
||||
|
||||
/// Cuckatoo solver context
|
||||
pub struct CuckatooContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
params: CuckooParams<T>,
|
||||
graph: Graph<T>,
|
||||
pub struct CuckatooContext {
|
||||
params: CuckooParams,
|
||||
graph: Graph,
|
||||
}
|
||||
|
||||
impl<T> PoWContext<T> for CuckatooContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl PoWContext for CuckatooContext {
|
||||
fn set_header_nonce(
|
||||
&mut self,
|
||||
header: Vec<u8>,
|
||||
|
@ -202,18 +184,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> CuckatooContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
impl CuckatooContext {
|
||||
/// New Solver context
|
||||
pub fn new_impl(
|
||||
edge_bits: u8,
|
||||
proof_size: usize,
|
||||
max_sols: u32,
|
||||
) -> Result<CuckatooContext<T>, Error> {
|
||||
let params = CuckooParams::new(edge_bits, proof_size)?;
|
||||
let num_edges = to_edge!(T, params.num_edges);
|
||||
) -> Result<CuckatooContext, Error> {
|
||||
let params = CuckooParams::new(edge_bits, edge_bits, proof_size)?;
|
||||
let num_edges = params.num_edges;
|
||||
Ok(CuckatooContext {
|
||||
params,
|
||||
graph: Graph::new(num_edges, max_sols, proof_size)?,
|
||||
|
@ -246,11 +225,6 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Return siphash masked for type
|
||||
pub fn sipnode(&self, edge: T, uorv: u64) -> Result<T, Error> {
|
||||
self.params.sipnode(edge, uorv, false)
|
||||
}
|
||||
|
||||
/// Simple implementation of algorithm
|
||||
pub fn find_cycles_iter<I>(&mut self, iter: I) -> Result<Vec<Proof>, Error>
|
||||
where
|
||||
|
@ -259,9 +233,9 @@ where
|
|||
let mut val = vec![];
|
||||
for n in iter {
|
||||
val.push(n);
|
||||
let u = self.sipnode(to_edge!(T, n), 0)?;
|
||||
let v = self.sipnode(to_edge!(T, n), 1)?;
|
||||
self.graph.add_edge(to_edge!(T, u), to_edge!(T, v))?;
|
||||
let u = self.params.sipnode(n, 0)?;
|
||||
let v = self.params.sipnode(n, 1)?;
|
||||
self.graph.add_edge(u, v)?;
|
||||
}
|
||||
self.graph.solutions.pop();
|
||||
for s in &mut self.graph.solutions {
|
||||
|
@ -290,14 +264,14 @@ where
|
|||
let mut xor1: u64 = xor0;
|
||||
|
||||
for n in 0..proof.proof_size() {
|
||||
if nonces[n] > to_u64!(self.params.edge_mask) {
|
||||
if nonces[n] > self.params.edge_mask {
|
||||
return Err(ErrorKind::Verification("edge too big".to_owned()).into());
|
||||
}
|
||||
if n > 0 && nonces[n] <= nonces[n - 1] {
|
||||
return Err(ErrorKind::Verification("edges not ascending".to_owned()).into());
|
||||
}
|
||||
uvs[2 * n] = to_u64!(self.sipnode(to_edge!(T, nonces[n]), 0)?);
|
||||
uvs[2 * n + 1] = to_u64!(self.sipnode(to_edge!(T, nonces[n]), 1)?);
|
||||
uvs[2 * n] = self.params.sipnode(nonces[n], 0)?;
|
||||
uvs[2 * n + 1] = self.params.sipnode(nonces[n], 1)?;
|
||||
xor0 ^= uvs[2 * n];
|
||||
xor1 ^= uvs[2 * n + 1];
|
||||
}
|
||||
|
@ -368,65 +342,56 @@ mod test {
|
|||
#[test]
|
||||
fn cuckatoo() {
|
||||
global::set_local_chain_type(global::ChainTypes::Mainnet);
|
||||
let ret = basic_solve::<u32>();
|
||||
let ret = basic_solve();
|
||||
if let Err(r) = ret {
|
||||
panic!("basic_solve u32: Error: {}", r);
|
||||
}
|
||||
let ret = basic_solve::<u64>();
|
||||
let ret = basic_solve();
|
||||
if let Err(r) = ret {
|
||||
panic!("basic_solve u64: Error: {}", r);
|
||||
}
|
||||
let ret = validate29_vectors::<u32>();
|
||||
let ret = validate29_vectors();
|
||||
if let Err(r) = ret {
|
||||
panic!("validate_29_vectors u32: Error: {}", r);
|
||||
}
|
||||
let ret = validate29_vectors::<u64>();
|
||||
let ret = validate29_vectors();
|
||||
if let Err(r) = ret {
|
||||
panic!("validate_29_vectors u64: Error: {}", r);
|
||||
}
|
||||
let ret = validate31_vectors::<u32>();
|
||||
let ret = validate31_vectors();
|
||||
if let Err(r) = ret {
|
||||
panic!("validate_31_vectors u32: Error: {}", r);
|
||||
}
|
||||
let ret = validate31_vectors::<u64>();
|
||||
let ret = validate31_vectors();
|
||||
if let Err(r) = ret {
|
||||
panic!("validate_31_vectors u64: Error: {}", r);
|
||||
}
|
||||
let ret = validate_fail::<u32>();
|
||||
let ret = validate_fail();
|
||||
if let Err(r) = ret {
|
||||
panic!("validate_fail u32: Error: {}", r);
|
||||
}
|
||||
let ret = validate_fail::<u64>();
|
||||
let ret = validate_fail();
|
||||
if let Err(r) = ret {
|
||||
panic!("validate_fail u64: Error: {}", r);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate29_vectors<T>() -> Result<(), Error>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
let mut ctx = CuckatooContext::<u32>::new_impl(29, 42, 10).unwrap();
|
||||
fn validate29_vectors() -> Result<(), Error> {
|
||||
let mut ctx = CuckatooContext::new_impl(29, 42, 10).unwrap();
|
||||
ctx.set_header_nonce([0u8; 80].to_vec(), Some(20), false)?;
|
||||
assert!(ctx.verify(&Proof::new(V1_29.to_vec())).is_ok());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate31_vectors<T>() -> Result<(), Error>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
let mut ctx = CuckatooContext::<u32>::new_impl(31, 42, 10).unwrap();
|
||||
fn validate31_vectors() -> Result<(), Error> {
|
||||
let mut ctx = CuckatooContext::new_impl(31, 42, 10).unwrap();
|
||||
ctx.set_header_nonce([0u8; 80].to_vec(), Some(99), false)?;
|
||||
assert!(ctx.verify(&Proof::new(V1_31.to_vec())).is_ok());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_fail<T>() -> Result<(), Error>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
let mut ctx = CuckatooContext::<u32>::new_impl(29, 42, 10).unwrap();
|
||||
fn validate_fail() -> Result<(), Error> {
|
||||
let mut ctx = CuckatooContext::new_impl(29, 42, 10).unwrap();
|
||||
let mut header = [0u8; 80];
|
||||
header[0] = 1u8;
|
||||
ctx.set_header_nonce(header.to_vec(), Some(20), false)?;
|
||||
|
@ -440,10 +405,7 @@ mod test {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn basic_solve<T>() -> Result<(), Error>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
fn basic_solve() -> Result<(), Error> {
|
||||
let nonce = 1546569;
|
||||
let _range = 1;
|
||||
let header = [0u8; 80].to_vec();
|
||||
|
@ -458,7 +420,7 @@ mod test {
|
|||
String::from_utf8(header.clone()).unwrap(),
|
||||
nonce
|
||||
);
|
||||
let mut ctx_u32 = CuckatooContext::<u32>::new_impl(edge_bits, proof_size, max_sols)?;
|
||||
let mut ctx_u32 = CuckatooContext::new_impl(edge_bits, proof_size, max_sols)?;
|
||||
let mut bytes = ctx_u32.byte_count()?;
|
||||
let mut unit = 0;
|
||||
while bytes >= 10240 {
|
||||
|
|
|
@ -25,7 +25,7 @@ use crate::pow::Proof;
|
|||
/// Tromp's implementation, as it's not optimized for performance and reuses
|
||||
/// croaring which is likely sub-optimal for this task.
|
||||
pub struct Lean {
|
||||
params: CuckooParams<u32>,
|
||||
params: CuckooParams,
|
||||
edges: Bitmap,
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ impl Lean {
|
|||
/// Instantiates a new lean miner based on some Cuckatoo parameters
|
||||
pub fn new(edge_bits: u8) -> Lean {
|
||||
// note that proof size doesn't matter to a lean miner
|
||||
let params = CuckooParams::new(edge_bits, 42).unwrap();
|
||||
let params = CuckooParams::new(edge_bits, edge_bits, 42).unwrap();
|
||||
|
||||
// edge bitmap, before trimming all of them are on
|
||||
let mut edges = Bitmap::create_with_capacity(params.num_edges as u32);
|
||||
|
@ -58,7 +58,7 @@ impl Lean {
|
|||
|
||||
/// Finds the Cuckatoo Cycles on the remaining edges. Delegates the finding
|
||||
/// to a context, passing the trimmed edges iterator.
|
||||
pub fn find_cycles(&self, mut ctx: CuckatooContext<u32>) -> Result<Vec<Proof>, Error> {
|
||||
pub fn find_cycles(&self, mut ctx: CuckatooContext) -> Result<Vec<Proof>, Error> {
|
||||
ctx.find_cycles_iter(self.edges.iter().map(|e| e as u64))
|
||||
}
|
||||
|
||||
|
@ -68,15 +68,15 @@ impl Lean {
|
|||
let mut nodes = Bitmap::create();
|
||||
// increment count for each node
|
||||
for e in self.edges.iter() {
|
||||
let node = self.params.sipnode(e, uorv, false).unwrap();
|
||||
nodes.add(node);
|
||||
let node = self.params.sipnode(e.into(), uorv).unwrap();
|
||||
nodes.add(node as u32);
|
||||
}
|
||||
|
||||
// then kill edges with lone nodes (no neighbour at ^1)
|
||||
let mut to_kill = Bitmap::create();
|
||||
for e in self.edges.iter() {
|
||||
let node = self.params.sipnode(e, uorv, false).unwrap();
|
||||
if !nodes.contains(node ^ 1) {
|
||||
let node = self.params.sipnode(e.into(), uorv).unwrap();
|
||||
if !nodes.contains((node ^ 1) as u32) {
|
||||
to_kill.add(e);
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ mod test {
|
|||
lean.set_header_nonce(header.clone(), nonce);
|
||||
lean.trim();
|
||||
|
||||
let mut ctx_u32 = CuckatooContext::<u32>::new_impl(edge_bits, 42, 10).unwrap();
|
||||
let mut ctx_u32 = CuckatooContext::new_impl(edge_bits, 42, 10).unwrap();
|
||||
ctx_u32.set_header_nonce(header, Some(nonce), true).unwrap();
|
||||
lean.find_cycles(ctx_u32).unwrap();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
use crate::consensus::{graph_weight, MIN_DIFFICULTY, SECOND_POW_EDGE_BITS};
|
||||
use crate::core::hash::{DefaultHashable, Hashed};
|
||||
use crate::global;
|
||||
use crate::pow::common::EdgeType;
|
||||
use crate::pow::error::Error;
|
||||
use crate::ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
@ -28,10 +27,7 @@ use std::{fmt, iter};
|
|||
|
||||
/// Generic trait for a solver/verifier providing common interface into Cuckoo-family PoW
|
||||
/// Mostly used for verification, but also for test mining if necessary
|
||||
pub trait PoWContext<T>
|
||||
where
|
||||
T: EdgeType,
|
||||
{
|
||||
pub trait PoWContext {
|
||||
/// Sets the header along with an optional nonce at the end
|
||||
/// solve: whether to set up structures for a solve (true) or just validate (false)
|
||||
fn set_header_nonce(
|
||||
|
|
Loading…
Add table
Reference in a new issue