feat: Implement the new specification of PoRA mine. (#307)
Some checks failed
abi-consistent-check / build-and-compare (push) Has been cancelled
code-coverage / unittest-cov (push) Has been cancelled
rust / check (push) Has been cancelled
rust / test (push) Has been cancelled
rust / lints (push) Has been cancelled
functional-test / test (push) Has been cancelled

* feat: Support faster PoRA specification

* Support subtasks in PoRA mine

* Fix test fails

* Check the contract version
This commit is contained in:
bruno-valante 2024-12-28 20:29:45 +08:00 committed by GitHub
parent bb74143ddc
commit 40d435597a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 419 additions and 48 deletions

View File

@ -5,7 +5,7 @@ pub const TB: usize = 1024 * GB;
pub const BYTES_PER_SECTOR: usize = 256;
pub const BYTES_PER_SEAL: usize = 4 * KB;
pub const BYTES_PER_SCRATCHPAD: usize = 64 * KB;
pub const BYTES_PER_SCRATCHPAD: usize = 16 * KB;
pub const BYTES_PER_LOAD: usize = 256 * KB;
pub const BYTES_PER_PRICING: usize = 8 * GB;
pub const BYTES_PER_MAX_MINING_RANGE: usize = 8 * TB;

View File

@ -1,4 +1,4 @@
use contract_interface::zgs_flow::MineContext;
use contract_interface::pora_mine::MineContext;
use ethereum_types::{H256, U256};
use rand::{self, Rng};
use std::time;
@ -35,16 +35,23 @@ pub struct PoraService {
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct PoraPuzzle {
context: MineContext,
target_quality: U256,
pora_target: U256,
max_shards: u64,
subtask_digest: H256,
}
impl PoraPuzzle {
pub fn new(context: MineContext, target_quality: U256, max_shards: u64) -> Self {
pub fn new(
context: MineContext,
pora_target: U256,
max_shards: u64,
subtask_digest: H256,
) -> Self {
Self {
context,
target_quality,
pora_target,
max_shards,
subtask_digest,
}
}
@ -255,7 +262,8 @@ impl PoraService {
miner_id: &self.miner_id,
mine_range_config: &self.mine_range,
context: &puzzle.context,
target_quality: &puzzle.target_quality,
subtask_digest: &puzzle.subtask_digest,
pora_target: &puzzle.pora_target,
loader: &*self.loader,
})
}

View File

@ -2,8 +2,9 @@ use super::metrics::*;
use crate::recall_range::RecallRange;
use crate::{MineRangeConfig, PoraLoader};
use blake2::{Blake2b512, Digest};
use contract_interface::zgs_flow::MineContext;
use contract_interface::pora_mine::MineContext;
use ethereum_types::{H256, U256};
use ethers::utils::keccak256;
use lighthouse_metrics::inc_counter;
use storage::log_store::MineLoadChunk;
use tiny_keccak::{Hasher, Keccak};
@ -24,7 +25,8 @@ pub(crate) struct Miner<'a> {
pub range: RecallRange,
pub miner_id: &'a H256,
pub context: &'a MineContext,
pub target_quality: &'a U256,
pub subtask_digest: &'a H256,
pub pora_target: &'a U256,
pub loader: &'a dyn PoraLoader,
pub mine_range_config: &'a MineRangeConfig,
}
@ -106,11 +108,11 @@ impl<'a> Miner<'a> {
.range
.difficulty_scale_x64(self.context.flow_length.as_u64());
if quality <= (self.target_quality / difficulty_scale_x64) << 64 {
if quality <= (self.pora_target / difficulty_scale_x64) << 64 {
debug!(
"Find a PoRA valid answer, quality: {}, target_quality {}, scale {:.3}",
"Find a PoRA valid answer, quality: {}, pora_target {}, scale {:.3}",
U256::MAX / quality,
U256::MAX / self.target_quality,
U256::MAX / self.pora_target,
difficulty_scale_x64.as_u128() as f64 / u64::MAX as f64
);
inc_counter(&HIT_COUNT);
@ -138,7 +140,7 @@ impl<'a> Miner<'a> {
let mut hasher = Blake2b512::new();
hasher.update(self.miner_id);
hasher.update(nonce);
hasher.update(self.context.digest);
hasher.update(self.subtask_digest);
hasher.update(self.range.digest());
hasher.finalize().into()
};
@ -148,7 +150,11 @@ impl<'a> Miner<'a> {
let mut scratch_pad =
[[0u8; BLAKE2B_OUTPUT_BYTES]; BYTES_PER_SCRATCHPAD / BLAKE2B_OUTPUT_BYTES];
for scratch_pad_cell in scratch_pad.iter_mut() {
digest = Blake2b512::new().chain_update(digest).finalize().into();
let output0 = keccak256(digest);
digest[..32].copy_from_slice(&output0);
let output1 = keccak256(digest);
digest[32..].copy_from_slice(&output1);
*scratch_pad_cell = digest;
}

View File

@ -87,7 +87,7 @@ impl Sealer {
async fn update_flow_length(&mut self) -> Result<()> {
let recent_context = self.flow_contract.make_context_with_result().call().await?;
debug!(target: "seal", "Recent context is {:?}", recent_context);
debug!("Recent context is {:?}", recent_context);
let recent_flow_length = recent_context.flow_length.as_u64();
if self.last_context_flow_length < recent_flow_length {

View File

@ -46,6 +46,7 @@ impl MineService {
msg_recv.resubscribe(),
provider.clone(),
&config,
miner_id,
);
let mine_answer_receiver = PoraService::spawn(

View File

@ -1,6 +1,6 @@
#![allow(unused)]
use contract_interface::{zgs_flow::MineContext, PoraMine, ZgsFlow};
use contract_interface::{zgs_flow::MineContext, PoraMine, WorkerContext, ZgsFlow};
use ethereum_types::{Address, H256, U256};
use ethers::{
contract::Contract,
@ -28,6 +28,8 @@ lazy_static! {
H256::from_str("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").unwrap();
}
const PORA_VERSION: u64 = 1;
pub struct MineContextWatcher {
provider: Arc<Provider<RetryClient<Http>>>,
flow_contract: ZgsFlow<Provider<RetryClient<Http>>>,
@ -36,6 +38,7 @@ pub struct MineContextWatcher {
mine_context_sender: broadcast::Sender<MineContextMessage>,
last_report: MineContextMessage,
query_interval: Duration,
miner_id: H256,
msg_recv: broadcast::Receiver<MinerMessage>,
}
@ -46,6 +49,7 @@ impl MineContextWatcher {
msg_recv: broadcast::Receiver<MinerMessage>,
provider: Arc<Provider<RetryClient<Http>>>,
config: &MinerConfig,
miner_id: H256,
) -> broadcast::Receiver<MineContextMessage> {
let mine_contract = PoraMine::new(config.mine_address, provider.clone());
let flow_contract = ZgsFlow::new(config.flow_address, provider.clone());
@ -60,6 +64,7 @@ impl MineContextWatcher {
msg_recv,
last_report: None,
query_interval: config.context_query_interval,
miner_id,
};
executor.spawn(
async move { Box::pin(watcher.start()).await },
@ -105,28 +110,14 @@ impl MineContextWatcher {
}
async fn query_recent_context(&mut self) -> Result<(), String> {
let context_call = self.flow_contract.make_context_with_result();
let valid_call = self.mine_contract.can_submit();
let quality_call = self.mine_contract.pora_target();
let shards_call = self.mine_contract.max_shards();
let (context, can_submit, quality, max_shards) = try_join!(
context_call.call(),
valid_call.call(),
quality_call.call(),
shards_call.call()
)
.map_err(|e| format!("Failed to query mining context: {:?}", e))?;
let report = if can_submit && context.digest != EMPTY_HASH.0 {
Some(PoraPuzzle::new(context, quality, max_shards))
} else {
None
};
let report = self.fetch_pora_puzzle().await?;
if report == self.last_report {
return Ok(());
}
debug!("Update pora puzzle: {:?}", report);
self.mine_context_sender
.send(report.clone())
.map_err(|e| format!("Failed to send out the most recent mine context: {:?}", e))?;
@ -134,4 +125,41 @@ impl MineContextWatcher {
Ok(())
}
async fn fetch_pora_puzzle(&self) -> Result<Option<PoraPuzzle>, String> {
let pora_version = self
.mine_contract
.pora_version()
.call()
.await
.map_err(|e| format!("Failed to query mining version: {:?}", e))?;
if pora_version != PORA_VERSION {
return Ok(None);
}
let miner_id = self.miner_id.0;
let WorkerContext {
context,
pora_target,
subtask_digest,
max_shards,
} = self
.mine_contract
.compute_worker_context(miner_id)
.call()
.await
.map_err(|e| format!("Failed to query mining context: {:?}", e))?;
if pora_target.is_zero() || context.digest == EMPTY_HASH.0 {
return Ok(None);
}
Ok(Some(PoraPuzzle::new(
context,
pora_target,
max_shards,
H256(subtask_digest),
)))
}
}

View File

@ -1 +1 @@
30f0f921a80078e43d578c82b746677bcf06d786
1e931c7b168f9bc2b55f7b8fd96946e35b373048

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -75,7 +75,7 @@ class MineTest(TestFramework):
wait_until(lambda: self.mine_contract.last_mined_epoch() == start_epoch + 1 and not self.mine_contract.can_submit(), timeout=120)
rewards = self.reward_contract.reward_distributes()
assert_equal(len(rewards), 2)
assert_equal(len(rewards), 4)
firstReward = rewards[0].args.amount
self.log.info("Received reward %d Gwei", firstReward / (10**9))
@ -96,8 +96,8 @@ class MineTest(TestFramework):
assert_equal(self.contract.epoch(), start_epoch + 2)
rewards = self.reward_contract.reward_distributes()
assert_equal(len(rewards), 4)
secondReward = rewards[2].args.amount
assert_equal(len(rewards), 8)
secondReward = rewards[4].args.amount
self.log.info("Received reward %d Gwei", secondReward / (10**9))
assert_greater_than(secondReward, 100 * firstReward / (start_epoch + 1))

View File

@ -105,6 +105,9 @@ class MineContractProxy(ContractProxy):
def can_submit(self, node_idx=0):
return self._call("canSubmit", node_idx)
def current_submissions(self, node_idx=0):
return self._call("currentSubmissions", node_idx)
def set_quality(self, quality, node_idx=0):
return self._send("setQuality", node_idx, _targetQuality=quality)