Skip to content

Commit

Permalink
Feat: core hashchain logic (#816)
Browse files Browse the repository at this point in the history
## Description

This PR continues the effort of merging #705 in multiple pieces.

This PR introduces the core hashchain logic as a library crate. The
reasons to factor the code in this way are:

1. Allows this PR to be safely merged because it has no impact on
existing engine code
2. The same core logic will be reusable between components that will
perform the hashchain computation in the future, namely: Aurora Engine
smart contract, standalone engine, Borealis Refiner.

In the next PR I'll pull in the changes from #705 which actually
introduce the hashchain calculation into the Aurora Engine smart
contract and standalone engine.

## Performance / NEAR gas cost considerations

N/A

## Testing

New hashchain tests

---------

Co-authored-by: Oleksandr Anyshchenko <oleksandr.anyshchenko@aurora.dev>
  • Loading branch information
birchmd and aleksuss authored Aug 14, 2023
1 parent af38aff commit 880da84
Show file tree
Hide file tree
Showing 9 changed files with 998 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.0-
evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.0-aurora", default-features = false, features = ["std"] }
evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.0-aurora", default-features = false, features = ["std", "tracing"] }
evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.0-aurora", default-features = false, features = ["std", "tracing"] }
fixed-hash = { version = "0.8.0", default-features = false}
git2 = "0.17"
hex = { version = "0.4", default-features = false, features = ["alloc"] }
ibig = { version = "0.3", default-features = false, features = ["num-traits"] }
impl-serde = { version = "0.4.0", default-features = false}
lazy_static = "1"
libsecp256k1 = { version = "0.7", default-features = false }
near-crypto = "0.17"
Expand Down Expand Up @@ -71,6 +73,7 @@ workspaces = "0.7"
resolver = "2"
members = [
"engine",
"engine-hashchain",
"engine-test-doubles",
"engine-modexp",
"engine-precompiles",
Expand Down
22 changes: 22 additions & 0 deletions engine-hashchain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "aurora-engine-hashchain"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
readme.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
autobenches = false

[dependencies]
aurora-engine-types.workspace = true
aurora-engine-sdk.workspace = true
fixed-hash.workspace = true
impl-serde.workspace = true

[features]
default = ["std"]
std = ["aurora-engine-types/std", "aurora-engine-sdk/std", "fixed-hash/std", "impl-serde/std"]
borsh-compat = ["aurora-engine-types/borsh-compat", "aurora-engine-sdk/borsh-compat"]
85 changes: 85 additions & 0 deletions engine-hashchain/src/bloom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Based on Parity Common Eth Bloom implementation
//! Link: <https://github.com/paritytech/parity-common/blob/master/ethbloom/src/lib.rs>
//!
//! Reimplemented here since there is a large mismatch in types and dependencies.
#![allow(clippy::expl_impl_clone_on_copy)]

use aurora_engine_sdk::keccak;
use aurora_engine_types::borsh::{self, BorshDeserialize, BorshSerialize};
use aurora_engine_types::parameters::engine::ResultLog;
use fixed_hash::construct_fixed_hash;
use impl_serde::impl_fixed_hash_serde;

const BLOOM_SIZE: usize = 256;
const BLOOM_BITS: u32 = 3;

construct_fixed_hash! {
/// Bloom hash type with 256 bytes (2048 bits) size.
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Bloom(BLOOM_SIZE);
}

impl_fixed_hash_serde!(Bloom, BLOOM_SIZE);

/// Returns log2.
const fn log2(x: usize) -> u32 {
if x <= 1 {
return 0;
}

let n = x.leading_zeros();
usize::BITS - n
}

impl Bloom {
/// Add a new element to the bloom filter
#[allow(clippy::as_conversions)]
pub fn accrue(&mut self, input: &[u8]) {
let m = self.0.len();
let bloom_bits = m * 8;
let mask = bloom_bits - 1;
let bloom_bytes = (log2(bloom_bits) + 7) / 8;
let hash = keccak(input);
let mut ptr = 0;

for _ in 0..BLOOM_BITS {
let mut index = 0;
for _ in 0..bloom_bytes {
index = (index << 8) | hash[ptr] as usize;
ptr += 1;
}
index &= mask;
self.0[m - 1 - index / 8] |= 1 << (index % 8);
}
}

/// Merge two bloom filters
pub fn accrue_bloom(&mut self, bloom: &Self) {
for i in 0..BLOOM_SIZE {
self.0[i] |= bloom.0[i];
}
}
}

#[must_use]
pub fn get_logs_bloom(logs: &[ResultLog]) -> Bloom {
let mut logs_bloom = Bloom::default();

for log in logs {
logs_bloom.accrue_bloom(&get_log_bloom(log));
}

logs_bloom
}

#[must_use]
pub fn get_log_bloom(log: &ResultLog) -> Bloom {
let mut log_bloom = Bloom::default();

log_bloom.accrue(log.address.as_bytes());
for topic in &log.topics {
log_bloom.accrue(&topic[..]);
}

log_bloom
}
33 changes: 33 additions & 0 deletions engine-hashchain/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
pub const ERR_STATE_NOT_FOUND: &[u8; 19] = b"ERR_STATE_NOT_FOUND";
pub const ERR_STATE_SERIALIZATION_FAILED: &[u8; 26] = b"ERR_STATE_SERIALIZE_FAILED";
pub const ERR_STATE_CORRUPTED: &[u8; 19] = b"ERR_STATE_CORRUPTED";
pub const ERR_BLOCK_HEIGHT_INCORRECT: &[u8; 26] = b"ERR_BLOCK_HEIGHT_INCORRECT";
pub const ERR_REQUIRES_FEATURE_INTEGRATION_TEST: &[u8; 37] =
b"ERR_REQUIRES_FEATURE_INTEGRATION_TEST";

#[derive(Debug)]
/// Blockchain Hashchain Error
pub enum BlockchainHashchainError {
/// The state is missing from storage, need to initialize with contract `new` method.
NotFound,
/// The state serialized had failed.
SerializationFailed,
/// The state is corrupted, possibly due to failed state migration.
DeserializationFailed,
/// The block height is incorrect regarding the current block height.
BlockHeightIncorrect,
/// Some functionality requires integration-test feature.
RequiresFeatureIntegrationTest,
}

impl AsRef<[u8]> for BlockchainHashchainError {
fn as_ref(&self) -> &[u8] {
match self {
Self::NotFound => ERR_STATE_NOT_FOUND,
Self::SerializationFailed => ERR_STATE_SERIALIZATION_FAILED,
Self::DeserializationFailed => ERR_STATE_CORRUPTED,
Self::BlockHeightIncorrect => ERR_BLOCK_HEIGHT_INCORRECT,
Self::RequiresFeatureIntegrationTest => ERR_REQUIRES_FEATURE_INTEGRATION_TEST,
}
}
}
Loading

0 comments on commit 880da84

Please sign in to comment.