use anyhow::{Result, anyhow};
use ethexe_common::{
Address, Digest, ToDigest,
consensus::BatchCommitmentValidationReply,
db::OnChainStorageRO,
ecdsa::{ContractSignature, PublicKey},
gear::BatchCommitment,
};
use gprimitives::H256;
use gsigner::secp256k1::{Secp256k1SignerExt, Signer};
use parity_scale_codec::{Decode, Encode};
use std::collections::{BTreeMap, HashSet};
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub struct MultisignedBatchCommitment {
batch: BatchCommitment,
batch_digest: Digest,
router_address: Address,
signatures: BTreeMap<Address, ContractSignature>,
}
impl MultisignedBatchCommitment {
pub fn new(
batch: BatchCommitment,
signer: &Signer,
router_address: Address,
pub_key: PublicKey,
) -> Result<Self> {
let batch_digest = batch.to_digest();
let signature =
signer.sign_for_contract_digest(router_address, pub_key, batch_digest, None)?;
let signatures: BTreeMap<_, _> = [(pub_key.to_address(), signature)].into_iter().collect();
Ok(Self {
batch,
batch_digest,
router_address,
signatures,
})
}
pub fn accept_batch_commitment_validation_reply(
&mut self,
reply: BatchCommitmentValidationReply,
check_origin: impl FnOnce(Address) -> Result<()>,
) -> Result<()> {
let BatchCommitmentValidationReply { digest, signature } = reply;
anyhow::ensure!(digest == self.batch_digest, "Invalid reply digest");
let origin = signature
.validate(self.router_address, digest)?
.to_address();
check_origin(origin)?;
self.signatures.insert(origin, signature);
Ok(())
}
pub fn signatures(&self) -> &BTreeMap<Address, ContractSignature> {
&self.signatures
}
pub fn batch(&self) -> &BatchCommitment {
&self.batch
}
pub fn into_parts(self) -> (BatchCommitment, Vec<ContractSignature>) {
(self.batch, self.signatures.into_values().collect())
}
}
pub fn has_duplicates<T: std::hash::Hash + Eq>(data: &[T]) -> bool {
let mut seen = HashSet::new();
data.iter().any(|item| !seen.insert(item))
}
pub fn is_eth_block_canonical_to<DB: OnChainStorageRO>(
db: &DB,
target: H256,
head: H256,
) -> Result<bool> {
if target.is_zero() {
return Ok(true);
}
let target_height = db
.block_header(target)
.ok_or_else(|| anyhow!("eth chain walk: missing header for target {target}"))?
.height;
let mut current = head;
loop {
if current == target {
return Ok(true);
}
if current.is_zero() {
return Ok(false);
}
let header = db
.block_header(current)
.ok_or_else(|| anyhow!("eth chain walk: missing header for {current}"))?;
if header.height <= target_height {
return Ok(false);
}
current = header.parent_hash;
}
}