Skip to content

Commit

Permalink
Add Header.checkPow method
Browse files Browse the repository at this point in the history
  • Loading branch information
SethDusek committed Jan 6, 2025
1 parent 0b3cdd9 commit db90b62
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 6 deletions.
13 changes: 10 additions & 3 deletions ergo-chain-types/src/autolykos_pow_scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use num_bigint::{BigInt, Sign};
use num_traits::Num;
use sigma_ser::ScorexSerializationError;
use sigma_util::hash::blake2b256_hash;
use thiserror::Error;

use crate::Header;

Expand Down Expand Up @@ -242,22 +243,28 @@ fn as_unsigned_byte_array(
let start = usize::from(bytes[0] == 0);
let count = bytes.len() - start;
if count > length {
return Err(AutolykosPowSchemeError::BigIntToFixedByteArrayError);
return Err(AutolykosPowSchemeError::BigIntToByteArrayError);
}
let mut res: Vec<_> = vec![0; length];
res[(length - count)..].copy_from_slice(&bytes[start..]);
Ok(res)
}

/// Autolykos POW scheme error
#[derive(PartialEq, Eq, Debug, Clone, From)]
#[derive(PartialEq, Eq, Debug, Clone, From, Error)]
pub enum AutolykosPowSchemeError {
/// Scorex-serialization error
#[error("Scorex serialization error: {0}")]
ScorexSerializationError(ScorexSerializationError),
/// Error occurring when trying to convert a `BigInt` into a fixed-length byte-array.
BigIntToFixedByteArrayError,
#[error("Error converting BigInt to byte array")]
BigIntToByteArrayError,
/// Occurs when `Header.version == 1` and the `pow_distance` parameter is None.
#[error("PoW distance not found for Autolykos1 Header")]
MissingPowDistanceParameter,
/// Checking proof-of-work for AutolykosV1 is not supported
#[error("Header.check_pow is not supported for Autolykos1")]
Unsupported,
}

/// The following tests are taken from <https://github.com/ergoplatform/ergo/blob/f7b91c0be00531c6d042c10a8855149ca6924373/src/test/scala/org/ergoplatform/mining/AutolykosPowSchemeSpec.scala#L43-L130>
Expand Down
15 changes: 15 additions & 0 deletions ergo-chain-types/src/header.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
//! Block header
use crate::autolykos_pow_scheme::{
decode_compact_bits, order_bigint, AutolykosPowScheme, AutolykosPowSchemeError,
};
use crate::{ADDigest, BlockId, Digest32, EcPoint};
use alloc::boxed::Box;
use alloc::vec::Vec;
Expand Down Expand Up @@ -83,6 +86,16 @@ impl Header {
}
Ok(data)
}
/// Check that proof of work was valid for header. Only Autolykos2 is supported
pub fn check_pow(&self) -> Result<bool, AutolykosPowSchemeError> {
if self.version != 1 {
let hit = AutolykosPowScheme::default().pow_hit(self)?;
let target = order_bigint() / decode_compact_bits(self.n_bits);
Ok(hit < target)
} else {
Err(AutolykosPowSchemeError::Unsupported)
}
}
}

impl ScorexSerializable for Header {
Expand Down Expand Up @@ -419,6 +432,7 @@ mod tests {
}"#;
let header: Header = serde_json::from_str(json).unwrap();
assert_eq!(header.height, 471746);
assert!(header.check_pow().unwrap());
assert_eq!(
header.autolykos_solution.pow_distance,
Some(BigInt::from_str(
Expand Down Expand Up @@ -457,6 +471,7 @@ mod tests {
}"#;
let header: Header = serde_json::from_str(json).unwrap();
assert_eq!(header.height, 471746);
assert!(header.check_pow().unwrap());
assert_eq!(
header.autolykos_solution.pow_distance,
Some(BigInt::from_str(
Expand Down
1 change: 1 addition & 0 deletions ergotree-interpreter/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
sheader::POW_DISTANCE_METHOD_ID => self::sheader::POW_DISTANCE_EVAL_FN,
sheader::POW_NONCE_METHOD_ID => self::sheader::POW_NONCE_EVAL_FN,
sheader::VOTES_METHOD_ID => self::sheader::VOTES_EVAL_FN,
sheader::CHECK_POW_METHOD_ID => self::sheader::CHECK_POW_EVAL_FN,
method_id => {
return Err(EvalError::NotFound(format!(
"Eval fn: method {:?} with method id {:?} not found in SHeader",
Expand Down
4 changes: 4 additions & 0 deletions ergotree-interpreter/src/eval/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use alloc::boxed::Box;
use alloc::string::String;
use core::fmt::Debug;
use core::fmt::Display;
use ergo_chain_types::autolykos_pow_scheme::AutolykosPowSchemeError;
use ergotree_ir::ergo_tree::ErgoTreeVersion;
use ergotree_ir::mir::expr::SubstDeserializeError;

Expand Down Expand Up @@ -87,6 +88,9 @@ pub enum EvalError {
/// Deserialize substitution error, see [`ergotree_ir::mir::expr::Expr::substitute_deserialize`]
#[error("DeserializeRegister/DeserializeContext error: {0}")]
SubstDeserializeError(#[from] SubstDeserializeError),
/// Autolykos PoW error
#[error("Autolykos PoW error: {0}")]
AutolykosPowSchemeError(#[from] AutolykosPowSchemeError),
}

/// Wrapped error with source span
Expand Down
67 changes: 64 additions & 3 deletions ergotree-interpreter/src/eval/sheader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use core::convert::TryInto;

use alloc::vec::Vec;
use ergo_chain_types::Header;
use ergotree_ir::{bigint256::BigInt256, mir::constant::TryExtractInto};
use ergotree_ir::{
bigint256::BigInt256,
mir::{constant::TryExtractInto, value::Value},
};

use super::{EvalError, EvalFn};

Expand Down Expand Up @@ -90,6 +93,14 @@ pub(crate) static VOTES_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Ok(Into::<Vec<u8>>::into(header.votes).into())
};

pub(crate) static CHECK_POW_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| match obj {
Value::Header(header) => Ok(header.check_pow()?.into()),
_ => Err(EvalError::UnexpectedValue(format!(
"SHeader.checkpow expected obj to be Value::Global, got {:?}",
obj
))),
};

#[cfg(test)]
#[cfg(feature = "arbitrary")]
#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
Expand All @@ -101,8 +112,15 @@ mod tests {
use ergotree_ir::{
bigint256::BigInt256,
chain::context::Context,
mir::{coll_by_index::ByIndex, expr::Expr, property_call::PropertyCall},
types::{scontext, sheader, smethod::SMethod},
mir::{
coll_by_index::ByIndex, expr::Expr, method_call::MethodCall,
property_call::PropertyCall,
},
types::{
scontext::{self, HEADERS_PROPERTY},
sheader,
smethod::SMethod,
},
};
use sigma_test_util::force_any_val;
use sigma_util::AsVecU8;
Expand Down Expand Up @@ -343,4 +361,47 @@ mod tests {
let expr = create_get_header_property_expr(unknown_property);
assert!(try_eval_out_wo_ctx::<i8>(&expr).is_err());
}
#[test]
fn test_eval_check_pow() {
let mut ctx = force_any_val::<Context>();
ctx.headers[0] = serde_json::from_str(
r#"{
"extensionId": "d51a477cc12b187d9bc7f464b22d00e3aa7c92463874e863bf3acf2f427bb48b",
"difficulty": "1595361307131904",
"votes": "000000",
"timestamp": 1736177881102,
"size": 220,
"unparsedBytes": "",
"stateRoot": "4dfafb43842680fd5870d8204a218f873479e1f5da1b34b059ca8da526abcc8719",
"height": 1433531,
"nBits": 117811961,
"version": 3,
"id": "3473e7b5aaf623e4260d5798253d26f3cdc912c12594b7e3a979e3db8ed883f6",
"adProofsRoot": "73160faa9f0e47bf7da598d4e9d3de58e8a24b8564458ad8a4d926514f435dc1",
"transactionsRoot": "c88d5f50ece85c2b918b5bd41d2bc06159e6db1b3aad95091d994c836a172950",
"extensionHash": "d5a43bf63c1d8c7f10b15b6d2446abe565b93a4fd3f5ca785b00e6bda831644f",
"powSolutions": {
"pk": "0274e729bb6615cbda94d9d176a2f1525068f12b330e38bbbf387232797dfd891f",
"w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"n": "a6905b8c65f5864a",
"d": 0
},
"adProofsId": "80a5ff0c6cd98440163bd27f2d7c775ea516af09024a98d9d83f16029bfbd034",
"transactionsId": "c7315c49df258522d3e92ce2653d9f4d8a35309a7a7dd470ebf8db53dd3fb792",
"parentId": "93172f3152a6a25dc89dc45ede1130c5eb86636a50bfb93a999556d16016ceb7"
}"#,
)
.unwrap();
// Add a mainnet block header with valid PoW to context. TODO: this can be simplified once Header serialization is added to sigma-rust (v6.0), right now we need to access CONTEXT.headers(0)
let headers = PropertyCall::new(Expr::Context, HEADERS_PROPERTY.clone()).unwrap();
let header = ByIndex::new(headers.into(), 0i32.into(), None).unwrap();
let check_pow: Expr =
MethodCall::new(header.into(), sheader::CHECK_POW_METHOD.clone(), vec![])
.unwrap()
.into();
assert!(eval_out::<bool>(&check_pow, &ctx));
// Mutate header to invalidate proof-of-work
ctx.headers[0].timestamp -= 1;
assert!(!eval_out::<bool>(&check_pow, &ctx));
}
}
20 changes: 20 additions & 0 deletions ergotree-ir/src/types/sheader.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#![allow(missing_docs)]

use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use lazy_static::lazy_static;

use crate::ergo_tree::ErgoTreeVersion;
use crate::serialization::types::TypeCode;
use crate::types::sfunc::SFunc;
use crate::types::stype_companion::STypeCompanion;

use super::smethod::{MethodId, SMethod, SMethodDesc};
use super::stype::SType::{self, SByte, SColl};
Expand Down Expand Up @@ -44,6 +48,8 @@ pub const POW_NONCE_METHOD_ID: MethodId = MethodId(13);
pub const POW_DISTANCE_METHOD_ID: MethodId = MethodId(14);
/// `Header.votes`
pub const VOTES_METHOD_ID: MethodId = MethodId(15);
/// `Header.checkPow`
pub const CHECK_POW_METHOD_ID: MethodId = MethodId(16);

lazy_static! {
/// Header method descriptors
Expand All @@ -64,6 +70,7 @@ lazy_static! {
&POW_NONCE_PROPERTY_METHOD_DESC,
&POW_DISTANCE_PROPERTY_METHOD_DESC,
&VOTES_PROPERTY_METHOD_DESC,
&CHECK_POW_METHOD_DESC
]
;
}
Expand Down Expand Up @@ -154,6 +161,19 @@ lazy_static! {
);
static ref VOTES_PROPERTY_METHOD_DESC: SMethodDesc =
property("votes", SColl(SByte.into()), VOTES_METHOD_ID);
static ref CHECK_POW_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: CHECK_POW_METHOD_ID,
name: "checkPow",
tpe: SFunc {
t_dom: vec![SType::SHeader],
t_range: Box::new(SType::SBoolean),
tpe_params: vec![]
},
explicit_type_args: vec![],
min_version: ErgoTreeVersion::V3
};
/// Header.checkPow
pub static ref CHECK_POW_METHOD: SMethod = SMethod::new(STypeCompanion::Header, CHECK_POW_METHOD_DESC.clone());
}

fn property(name: &'static str, res_tpe: SType, id: MethodId) -> SMethodDesc {
Expand Down

0 comments on commit db90b62

Please sign in to comment.