finish most logic, leaving some rust-style compiler issue to be solved

This commit is contained in:
Mengran Lan
2024-05-15 14:28:01 +08:00
parent 107aa5792b
commit 94bd5917ba
13 changed files with 455 additions and 72 deletions

View File

@@ -2991,17 +2991,23 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64 0.13.1",
"eth-keystore",
"eth-types",
"ethers-core 2.0.7 (git+https://github.com/scroll-tech/ethers-rs.git?branch=v2.0.7)",
"ethers-providers 2.0.7 (git+https://github.com/scroll-tech/ethers-rs.git?branch=v2.0.7)",
"futures",
"halo2_proofs",
"hex",
"log",
"once_cell",
"prover",
"rand",
"reqwest 0.12.4",
"rlp",
"serde",
"serde_json",
"snark-verifier-sdk",
"tiny-keccak",
]
[[package]]

View File

@@ -34,3 +34,9 @@ prover = { git = "https://github.com/scroll-tech/zkevm-circuits.git", tag = "v0.
eth-types = { git = "https://github.com/scroll-tech/zkevm-circuits.git", tag = "v0.10.3" }
base64 = "0.13.1"
reqwest = "0.12.4"
once_cell = "1.19.0"
hex = "0.4.3"
tiny-keccak = { version = "2.0.0", features = ["sha3", "keccak"] }
rand = "0.8.5"
eth-keystore = "0.5.0"
rlp = "0.5.2"

View File

@@ -10,25 +10,28 @@ use api::API;
use futures::executor::block_on;
use log;
use crate::key_signer::KeySigner;
pub struct Config {
pub endpoint: String,
pub prover_name: String,
pub prover_version: String,
pub hard_fork_name: String,
}
pub struct CoordinatorClient {
pub struct CoordinatorClient<'a> {
api: API,
token: Option<String>,
config: Config,
key_signer: &'a KeySigner,
}
impl CoordinatorClient {
pub fn new(config: Config) -> Result<Self> {
impl<'a> CoordinatorClient<'a> {
pub fn new(config: Config, key_signer: &'a KeySigner) -> Result<Self> {
let mut client = Self {
api: API::new(config.endpoint)?,
token: None,
config: config,
config,
key_signer,
};
client.login()?;
Ok(client)
@@ -54,11 +57,13 @@ impl CoordinatorClient {
hard_fork_name: self.config.hard_fork_name,
};
let buffer = login_message.rlp();
let signature = self.key_signer.sign_buffer(&buffer)?;
let login_request = LoginRequest {
message: login_message,
signature: login_message.sign_with_key(),
signature: signature,
};
let login_response = block_on(api.login(&login_request, &token));
let login_response = block_on(api.login(&login_request, &token))?;
if login_response.errcode != Success {
bail!("login failed: {}", login_response.errmsg)
}
@@ -71,8 +76,8 @@ impl CoordinatorClient {
Ok(())
}
pub fn get_task(&mut self, req: GetTaskRequest) -> Result<Response<GetTaskResponseData>> {
let response = block_on(self.api.get_task(&req, &self.token))?;
pub fn get_task(&mut self, req: &GetTaskRequest) -> Result<Response<GetTaskResponseData>> {
let response = block_on(self.api.get_task(req, &self.token.unwrap()))?;
if response.errcode == ErrJWTTokenExpired {
log::info!("JWT expired, attempting to re-login");
@@ -84,15 +89,15 @@ impl CoordinatorClient {
Ok(response)
}
pub fn submit_proof(&mut self, req: SubmitProofRequest) -> Result<Response<SubmitProofResponseData>> {
let response = block_on(self.api.get_task(&req, &self.token))?;
pub fn submit_proof(&mut self, req: &SubmitProofRequest) -> Result<Response<SubmitProofResponseData>> {
let response = block_on(self.api.submit_proof(req, &self.token.unwrap()))?;
if response.errcode == ErrJWTTokenExpired {
log::info!("JWT expired, attempting to re-login");
self.login().context("JWT expired, re-login failed")?;
log::info!("re-login success");
} else if response.errcode != Success {
bail!("get task failed: {}", response.errmsg)
bail!("submit proof failed: {}", response.errmsg)
}
Ok(response)
}

View File

@@ -52,7 +52,7 @@ impl API {
pub async fn get_task(&self, req: &GetTaskRequest, token: &String) -> Result<Response<GetTaskResponseData>> {
let method = "/coordinator/v1/get_task";
let url = self.build_url(method)?;
let request_body = serde_json::to_string(&req)?;
let request_body = serde_json::to_string(req)?;
let response = self.client
.post(url)
@@ -67,11 +67,11 @@ impl API {
serde_json::from_str(&response_body).map_err(|e| anyhow::anyhow!(e))
}
pub async fn submit_proof(&self, req: SubmitProofRequest, token: &String) -> Result<Response<SubmitProofResponseData>> {
pub async fn submit_proof(&self, req: &SubmitProofRequest, token: &String) -> Result<Response<SubmitProofResponseData>> {
let method = "/coordinator/v1/submit_proof";
let url = self.build_url(method)?;
let request_body = serde_json::to_string(&req)?;
let request_body = serde_json::to_string(req)?;
let response = self.client
.post(url)

View File

@@ -1,4 +1,7 @@
use eth_types::Bytes;
use serde::{Deserialize, Serialize};
use crate::types::{ProofStatus, ProofFailureType};
use rlp::RlpStream;
#[derive(Serialize, Deserialize)]
pub struct Response<T> {
@@ -16,12 +19,15 @@ pub struct LoginMessage {
}
impl LoginMessage {
pub fn hash() -> Result<Vec<u8>> {
}
pub fn sign_with_key() -> Result<String> {
pub fn rlp(&self) -> Vec<u8> {
let mut rlp = RlpStream::new();
let num_fields = 4;
rlp.begin_list(num_fields);
rlp.append(&self.prover_name);
rlp.append(&self.prover_version);
rlp.append(&self.challenge);
rlp.append(&self.hard_fork_name);
rlp.out().freeze().into()
}
}
@@ -52,16 +58,17 @@ pub struct GetTaskResponseData {
pub task_id: String,
pub task_type: crate::types::ProofType,
pub task_data: String,
pub hard_fork_name: String,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Default)]
pub struct SubmitProofRequest {
pub uuid: String,
pub task_id: String,
pub task_type: i32,
pub status: i32,
pub task_type: crate::types::ProofType,
pub status: ProofStatus,
pub proof: String,
pub failure_type: Option<i32>,
pub failure_type: Option<ProofFailureType>,
pub failure_msg: Option<String>,
}

View File

@@ -1,4 +1,4 @@
mod types;
pub mod types;
use anyhow::Result;
use crate::types::CommonHash;

View File

@@ -19,6 +19,10 @@ pub struct BlockTrace {
pub mpt_witness: Vec<u8>,
}
pub fn get_block_number(block_trace: &ProverBlockTrace) -> Option<u64> {
block_trace.header.number.map(|n| n.as_u64())
}
pub type TxHash = H256;
/// this struct is tracked to https://github.com/scroll-tech/go-ethereum/blob/0f0cd99f7a2e/core/types/block.go#Header

View File

@@ -0,0 +1,108 @@
use std::path::Path;
use anyhow::Result;
use ethers_core::k256::{
ecdsa::{signature::hazmat::PrehashSigner, RecoveryId, Signature, SigningKey},
elliptic_curve::{sec1::ToEncodedPoint, FieldBytes},
Secp256k1,
PublicKey,
SecretKey
};
use hex::ToHex;
use tiny_keccak::{Hasher, Keccak};
use eth_types::{H256, U256};
use ethers_core::types::Signature as EthSignature;
pub struct KeySigner {
public_key: PublicKey,
signer: SigningKey,
}
impl KeySigner {
pub fn new(key_path: &str, passwd: &str) -> Result<Self> {
let p = Path::new(key_path);
let secret = if !p.exists() {
let dir = p.parent().unwrap();
let name = p.file_name().and_then(|s| s.to_str());
let mut rng = rand::thread_rng();
let (secret, _) = eth_keystore::new(dir, &mut rng, passwd, name)?;
secret
} else {
eth_keystore::decrypt_key(key_path, passwd).map_err(|e| anyhow::anyhow!(e))?
};
let secret_key = SecretKey::from_bytes(secret.as_slice().into())?;
let signer = SigningKey::from(secret_key.clone());
Ok(Self{
public_key: secret_key.public_key(),
signer: signer,
})
}
pub fn get_public_key(&self) -> String {
let v: Vec<u8> = Vec::from(self.public_key.to_encoded_point(true).as_bytes());
buffer_to_hex(&v, false)
}
/// Signs the provided hash.
pub fn sign_hash(&self, hash: H256) -> Result<EthSignature> {
let signer = &self.signer as &dyn PrehashSigner<(Signature, RecoveryId)>;
let (recoverable_sig, recovery_id) = signer.sign_prehash(hash.as_ref())?;
let v = u8::from(recovery_id) as u64;
let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
let r = U256::from_big_endian(r_bytes.as_slice());
let s = U256::from_big_endian(s_bytes.as_slice());
Ok(EthSignature { r, s, v })
}
pub fn sign_buffer<T>(&self, buffer: &T) -> Result<String>
where
T: AsRef<[u8]>,
{
let pre_hash = keccak256(buffer);
let hash_str = buffer_to_hex(&pre_hash, true);
println!("hash is {hash_str}");
let hash = H256::from(pre_hash);
let sig = self.sign_hash(hash)?;
Ok(buffer_to_hex(&sig.to_vec(), true))
}
}
fn buffer_to_hex<T>(buffer: &T, has_prefix: bool) -> String
where
T: AsRef<[u8]>,
{
if has_prefix {
format!("0x{}", buffer.encode_hex::<String>())
} else {
buffer.encode_hex::<String>()
}
}
/// Compute the Keccak-256 hash of input bytes.
///
/// Note that strings are interpreted as UTF-8 bytes,
// TODO: Add Solidity Keccak256 packing support
pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> [u8; 32] {
let mut output = [0u8; 32];
let mut hasher = Keccak::v256();
hasher.update(bytes.as_ref());
hasher.finalize(&mut output);
output
}

View File

@@ -1,6 +1,7 @@
mod version;
mod types;
mod config;
mod key_signer;
mod prover;
mod zk_circuits_handler;
mod coordinator_client;
@@ -36,9 +37,14 @@ impl<'a> TaskProcesser<'a> {
fn prove_and_submit(&self) -> Result<()> {
let task = self.prover.fetch_task()?;
let proof_detail = self.prover.prove_task(&task)?;
self.prover.submit_proof(&proof_detail, task.uuid)
match self.prover.prove_task(&task) {
Ok(proof_detail) => {
self.prover.submit_proof(&proof_detail, task.uuid)
},
Err(error) => {
self.prover.submit_error(&task, types::ProofFailureType::NoPanic, error)
},
}
}
}

View File

@@ -1,19 +1,30 @@
use anyhow::{Ok, Result};
use anyhow::{bail, Error, Ok, Result};
use ethers_core::types::BlockNumber;
use eth_types::U64;
use once_cell::sync::Lazy;
use std::cmp::Ordering;
use std::env;
use crate::types::{CommonHash, ProofFailureType, ProofStatus};
use crate::{config::Config, types::ProofType};
use crate::zk_circuits_handler::CircuitsHandlerProvider;
use crate::coordinator_client::CoordinatorClient;
use crate::zk_circuits_handler::{CircuitsHandler, CircuitsHandlerProvider};
use crate::coordinator_client::{CoordinatorClient, Config as CoordinatorConfig};
use crate::coordinator_client::types::*;
use crate::geth_client::GethClient;
use crate::geth_client::{types::get_block_number, GethClient};
use crate::key_signer::KeySigner;
use super::types::{Task, ProofDetail};
use prover::{ChunkProof, ChunkHash, BlockTrace};
// Only used for debugging.
pub(crate) static OUTPUT_DIR: Lazy<Option<String>> =
Lazy::new(|| env::var("PROVER_OUTPUT_DIR").ok());
pub struct Prover<'a> {
config: &'a Config,
circuits_client_provider: CircuitsHandlerProvider,
coordinator_client: CoordinatorClient,
key_signer: KeySigner,
circuits_handler_provider: CircuitsHandlerProvider,
coordinator_client: CoordinatorClient<'a>,
geth_client: GethClient,
}
@@ -27,11 +38,23 @@ impl<'a> Prover<'a> {
let proof_type = config.core.proof_type;
let params_path = config.core.params_path;
let assets_path = config.core.assets_path;
let keystore_path = config.keystore_path;
let keystore_password = config.keystore_password;
let coordinator_config = CoordinatorConfig {
endpoint: config.coordinator.base_url,
prover_name: config.prover_name,
prover_version: crate::version::get_version(),
hard_fork_name: config.hard_fork_name,
};
let key_signer = KeySigner::new(&keystore_path, &keystore_password)?;
let prover = Prover {
config: config,
circuits_client_provider: CircuitsHandlerProvider::new(proof_type, &params_path, &assets_path)?,
coordinator_client: CoordinatorClient::new(),
config,
key_signer,
circuits_handler_provider: CircuitsHandlerProvider::new(proof_type, &params_path, &assets_path)?,
coordinator_client: CoordinatorClient::new(coordinator_config, &key_signer)?,
geth_client: GethClient::new("test", &config.l2geth.endpoint)?,
};
@@ -43,14 +66,14 @@ impl<'a> Prover<'a> {
}
pub fn get_public_key(&self) -> String {
"".to_string()
self.key_signer.get_public_key()
}
pub fn fetch_task(&self) -> Result<Task> {
let mut req = GetTaskRequest {
task_type: self.get_proof_type(),
prover_height: None,
vks: self.circuits_client_provider.get_vks(),
vks: self.circuits_handler_provider.get_vks(),
};
if self.get_proof_type() == ProofType::ProofTypeChunk {
@@ -64,17 +87,89 @@ impl<'a> Prover<'a> {
unreachable!()
}
let resp = self.coordinator_client.get_task(req)?;
let resp = self.coordinator_client.get_task(&req)?;
Task::try_from(&resp.data.unwrap()).map_err(|e| anyhow::anyhow!(e))
}
pub fn prove_task(&self, task: &Task) -> Result<ProofDetail> {
let version = task.get_version();
if let Some(handler) = self.circuits_handler_provider.get_circuits_client(version) {
self.do_prove(task, handler)
} else {
bail!("failed to get a circuit handler")
}
}
fn do_prove(&self, task: &Task, handler: &Box<dyn CircuitsHandler>) -> Result<ProofDetail> {
let mut proof_detail = ProofDetail {
id: task.id,
proof_type: task.task_type,
status: ProofStatus::Error,
..Default::default()
};
match task.task_type {
ProofType::ProofTypeBatch => {
let chunk_hashes_proofs = self.gen_chunk_hashes_proofs(task)?;
let batch_proof = handler.aggregator_gen_agg_evm_proof(chunk_hashes_proofs,
None,
self.get_output_dir())?;
proof_detail.batch_proof = Some(batch_proof);
Ok(proof_detail)
},
ProofType::ProofTypeChunk => {
let chunk_trace = self.gen_chunk_traces(task)?;
let chunk_proof = handler.prover_gen_chunk_proof(chunk_trace,
None,
None,
self.get_output_dir())?;
proof_detail.chunk_proof = Some(chunk_proof);
Ok(proof_detail)
},
_ => bail!("task type invalid")
}
}
pub fn submit_proof(&self, proof_detail: &ProofDetail, uuid: String) -> Result<()> {
let proof_data = match proof_detail.proof_type {
ProofType::ProofTypeBatch => {
serde_json::to_string(&proof_detail.batch_proof.unwrap())?
},
ProofType::ProofTypeChunk => {
serde_json::to_string(&proof_detail.chunk_proof.unwrap())?
},
_ => unreachable!()
};
let request = SubmitProofRequest {
uuid,
task_id: proof_detail.id,
task_type: proof_detail.proof_type,
status: proof_detail.status,
proof: proof_data,
..Default::default()
};
Ok(())
}
pub fn submit_error(&self, task: &Task, failure_type: ProofFailureType, error: Error) -> Result<()> {
let request = SubmitProofRequest {
uuid: task.uuid,
task_id: task.id,
task_type: task.task_type,
status: ProofStatus::Error,
proof: todo!(),
failure_type: todo!(),
failure_msg: todo!(),
};
}
fn do_submit(&self, request: &SubmitProofRequest) -> Result<()> {
self.coordinator_client.submit_proof(request)?;
Ok(())
}
@@ -102,4 +197,62 @@ impl<'a> Prover<'a> {
_ => unreachable!(),
}
}
fn get_output_dir(&self) -> Option<&str> {
OUTPUT_DIR.as_deref()
}
fn gen_chunk_traces(&self, task: &Task) -> Result<Vec<BlockTrace>> {
if let Some(chunk_detail) = task.chunk_task_detail {
self.get_sorted_traces_by_hashes(chunk_detail.block_hashes)
} else {
bail!("invalid task")
}
}
fn gen_chunk_hashes_proofs(&self, task: &Task) -> Result<Vec<(ChunkHash, ChunkProof)>> {
if let Some(batch_detail) = task.batch_task_detail {
Ok(batch_detail.chunk_infos.into_iter().zip(batch_detail.chunk_proofs).collect())
} else {
bail!("invalid task")
}
}
fn get_sorted_traces_by_hashes(&self, block_hashes: Vec<CommonHash>) -> Result<Vec<BlockTrace>> {
if block_hashes.len() == 0 {
bail!("blockHashes is empty")
}
let mut block_traces = Vec::new();
for (_, hash) in block_hashes.into_iter().enumerate() {
let trace = self.geth_client.get_block_trace_by_hash(hash)?;
block_traces.push(trace.block_trace);
}
block_traces.sort_by(|a, b| {
if get_block_number(a) == None {
Ordering::Less
} else if get_block_number(b) == None {
Ordering::Greater
} else {
get_block_number(a).unwrap().cmp(&get_block_number(b).unwrap())
}
});
let block_numbers: Vec<u64> = block_traces.iter().map(|trace| {
match get_block_number(trace) {
Some(v) => v,
None => 0
}
}).collect();
let mut i = 0;
while i < block_numbers.len() - 1 {
if block_numbers[i] + 1 != block_numbers[i+1] {
bail!("block numbers are not continuous, got {} and {}", block_numbers[i], block_numbers[i+1])
}
i += 1;
}
Ok(block_traces)
}
}

View File

@@ -1,4 +1,4 @@
use prover::BatchProof;
use prover::{BatchProof, ChunkProof, ChunkHash};
use serde::{Deserialize, Serialize, Serializer, Deserializer};
use eth_types::H256;
@@ -54,37 +54,15 @@ impl Default for ProofType {
}
}
#[derive(Serialize, Deserialize)]
pub struct ChunkInfo {
pub chain_id: u64,
pub prev_state_root: CommonHash,
pub post_state_root: CommonHash,
pub withdraw_root: CommonHash,
pub data_hash: CommonHash,
pub is_padding: bool,
pub tx_bytes: Bytes,
}
#[derive(Serialize, Deserialize)]
pub struct ChunkProof {
pub storage_trace: Bytes,
pub protocol: Bytes,
pub proof: Bytes,
pub instances: Bytes,
pub vk: Bytes,
pub chunk_info: ChunkInfo,
pub git_version: String,
}
#[derive(Serialize, Deserialize)]
pub struct BatchTaskDetail {
chunk_infos: Vec<ChunkInfo>,
chunk_proofs: Vec<ChunkProof>,
pub chunk_infos: Vec<ChunkHash>,
pub chunk_proofs: Vec<ChunkProof>,
}
#[derive(Serialize, Deserialize)]
pub struct ChunkTaskDetail {
block_hashes: Vec<CommonHash>,
pub block_hashes: Vec<CommonHash>,
}
#[derive(Serialize, Deserialize, Default)]
@@ -97,6 +75,17 @@ pub struct Task {
pub batch_task_detail: Option<BatchTaskDetail>,
#[serde(default)]
pub chunk_task_detail: Option<ChunkTaskDetail>,
#[serde(default)]
pub hard_fork_name: Option<String>,
}
impl Task {
pub fn get_version(&self) -> String {
match self.hard_fork_name {
Some(v) => v,
None => "".to_string(),
}
}
}
impl TryFrom<&GetTaskResponseData> for Task {
@@ -109,6 +98,7 @@ impl TryFrom<&GetTaskResponseData> for Task {
task_type: value.task_type,
chunk_task_detail: None,
batch_task_detail: None,
hard_fork_name: None,
};
match task.task_type {
ProofType::ProofTypeBatch => {
@@ -123,13 +113,102 @@ impl TryFrom<&GetTaskResponseData> for Task {
}
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Default)]
pub struct ProofDetail {
pub id: String,
#[serde(rename = "type", default)]
pub proof_type: ProofType,
pub status: u32,
pub status: ProofStatus,
pub chunk_proof: Option<ChunkProof>,
pub batch_proof: Option<BatchProof>,
pub error: String,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProofFailureType {
Undefined,
Panic,
NoPanic
}
impl ProofFailureType {
fn from_u8(v: u8) -> Self {
match v {
1 => ProofFailureType::Panic,
2 => ProofFailureType::NoPanic,
_ => ProofFailureType::Undefined,
}
}
}
impl Serialize for ProofFailureType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
ProofFailureType::Undefined => serializer.serialize_u8(0),
ProofFailureType::Panic => serializer.serialize_u8(1),
ProofFailureType::NoPanic => serializer.serialize_u8(2),
}
}
}
impl<'de> Deserialize<'de> for ProofFailureType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let v: u8 = u8::deserialize(deserializer)?;
Ok(ProofFailureType::from_u8(v))
}
}
impl Default for ProofFailureType {
fn default() -> Self {
Self::Undefined
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProofStatus {
Ok,
Error,
}
impl ProofStatus {
fn from_u8(v: u8) -> Self {
match v {
0 => ProofStatus::Ok,
_ => ProofStatus::Error,
}
}
}
impl Serialize for ProofStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
ProofStatus::Ok => serializer.serialize_u8(0),
ProofStatus::Error => serializer.serialize_u8(1),
}
}
}
impl<'de> Deserialize<'de> for ProofStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let v: u8 = u8::deserialize(deserializer)?;
Ok(ProofStatus::from_u8(v))
}
}
impl Default for ProofStatus {
fn default() -> Self {
Self::Ok
}
}

View File

@@ -0,0 +1,10 @@
pub const TAG: &str = "v4.4.3";
pub const COMMIT: &str = "test";
pub const ZK_VERSION: &str = "000000-000000";
pub const VERSION: String = format!("{TAG}-{COMMIT}-{ZK_VERSION}");
pub fn get_version() -> String {
VERSION
}

View File

@@ -1,7 +1,6 @@
mod types;
mod base;
use types::{ChunkProof, BatchProof, BlockTrace, ChunkHash};
use anyhow::Result;
use std::collections::HashMap;