Skip to content

Commit 9c7ddbd

Browse files
authored
refactor(types): share HTTP commit certificate JSON types across evm-node and consensus (#46)
Follow-up to #40 (re-opened after `main` was force-pushed and GitHub auto-closed the previous PR). ## Summary Introduces `arc_consensus_types::commit_http` with `HttpCommitCertificate` / `HttpCommitSignature` and `try_into_commit_certificate()` so the execution RPC (`get_certificate`) and consensus RPC sync paths deserialize the same JSON shape without duplicated structs. ## Motivation The wire format (`height`, `round`, `block_hash`, base64-encoded signature bytes) lived in two places, which risks drift and inconsistent handling. One shared module keeps JSON alignment between `arc-evm-node` and `arc-node-consensus`. ## Changes - Add `crates/types/src/commit_http.rs` and `pub mod commit_http` in `arc-consensus-types`. - Add `serde_with` (with `base64`) to `arc-consensus-types`; update `Cargo.lock`. - `arc-evm-node`: re-export `RpcCommitCertificate` from `HttpCommitCertificate`; remove duplicate local types. - `arc-node-consensus`: deserialize via `HttpCommitCertificate` in RPC sync; use `try_into_commit_certificate()`. - `malachite-app` HTTP `RpcCommitCertificate` uses `Vec<HttpCommitSignature>` for signatures (serialization unchanged). ## Testing - `cargo test -p arc-consensus-types` - `cargo test -p arc-evm-node get_certificate` - `cargo test -p arc-node-consensus` (use `OPENSSL_NO_VENDOR=1` if vendored OpenSSL build fails locally) - `cargo clippy -p arc-consensus-types -p arc-node-consensus -p arc-evm-node --all-targets -- -D warnings`
2 parents d92c021 + 2d97232 commit 9c7ddbd

7 files changed

Lines changed: 164 additions & 101 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/evm-node/src/rpc/get_certificate.rs‎

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,20 @@
1717
//! getCertificate RPC API implementation
1818
1919
use crate::rpc::common::{codes, invalid_params};
20-
use arc_consensus_types::{Address, ValueId};
20+
pub use arc_consensus_types::commit_http::HttpCommitCertificate as RpcCommitCertificate;
2121
use async_trait::async_trait;
2222
use backon::{ExponentialBuilder, Retryable};
2323
use jsonrpsee::{
2424
core::RpcResult,
2525
types::{ErrorCode, ErrorObjectOwned},
2626
};
2727
use reqwest::StatusCode;
28-
use serde::{Deserialize, Serialize};
29-
use serde_with::{base64::Base64, serde_as};
3028
use std::time::Duration;
3129
use tracing::error;
3230

3331
/// Default maximum retry attempts for upstream certificate HTTP fetches.
3432
const HTTP_MAX_RETRIES: usize = 3;
3533

36-
//TODO: These are copied from malachite-app, should we move them to types?
37-
/// Signature entry within a commit certificate (base64 encoded signature bytes).
38-
#[serde_as]
39-
#[derive(Debug, Clone, Serialize, Deserialize)]
40-
pub struct RpcCommitSignature {
41-
pub address: Address,
42-
#[serde_as(as = "Base64")]
43-
pub signature: Vec<u8>,
44-
}
45-
46-
/// Commit certificate returned by `arc_getCertificate`.
47-
#[derive(Debug, Clone, Serialize, Deserialize)]
48-
pub struct RpcCommitCertificate {
49-
pub height: u64,
50-
pub round: i64,
51-
pub block_hash: ValueId,
52-
#[serde(rename = "signatures")]
53-
pub signatures: Vec<RpcCommitSignature>,
54-
}
55-
5634
/// Core logic for the `arc_getCertificate` method: validates params, performs fetch, maps errors.
5735
pub async fn rpc_get_certificate(
5836
source: &dyn CertificateSource,

‎crates/malachite-app/src/rpc/types.rs‎

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use tokio::sync::mpsc::Sender;
2828
use tracing::error;
2929

3030
use arc_consensus_types::{
31+
commit_http::HttpCommitSignature,
3132
evidence::{DoubleProposal, DoubleVote, StoredMisbehaviorEvidence, ValidatorEvidence},
3233
signing::PublicKey,
3334
Address, ArcContext, BlockHash, Height, Validator, ValidatorSet, Value, ValueId,
@@ -41,7 +42,7 @@ use malachitebft_app_channel::ConsensusRequest;
4142
use malachitebft_app_channel::ConsensusRequestError;
4243
use malachitebft_app_channel::NetworkRequest;
4344
use malachitebft_core_state_machine::state::{RoundValue, State as MState};
44-
use malachitebft_core_types::{CommitSignature, NilOrVal, VoteType};
45+
use malachitebft_core_types::{NilOrVal, VoteType};
4546
use malachitebft_network::PersistentPeerError;
4647

4748
use crate::request::{AppRequestError, CommitCertificateInfo, Status, TxAppReq};
@@ -353,29 +354,12 @@ struct RpcPendingProposalParts {
353354
count: usize,
354355
}
355356

356-
#[serde_as]
357-
#[derive(Serialize)]
358-
struct RpcCommitSignature {
359-
address: Address,
360-
#[serde_as(as = "Base64")]
361-
signature: Vec<u8>,
362-
}
363-
364-
impl From<CommitSignature<ArcContext>> for RpcCommitSignature {
365-
fn from(sig: CommitSignature<ArcContext>) -> Self {
366-
RpcCommitSignature {
367-
address: sig.address,
368-
signature: sig.signature.to_bytes().to_vec(),
369-
}
370-
}
371-
}
372-
373357
#[derive(Serialize)]
374358
pub(crate) struct RpcCommitCertificate {
375359
height: u64,
376360
round: i64,
377361
block_hash: ValueId,
378-
signatures: Vec<RpcCommitSignature>,
362+
signatures: Vec<HttpCommitSignature>,
379363
proposer: Address,
380364
extended: bool,
381365
}

‎crates/malachite-app/src/rpc_sync/client.rs‎

Lines changed: 6 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -31,72 +31,19 @@ use alloy_rpc_types_eth::Block;
3131
use bytes::Bytes;
3232
use eyre::Context;
3333
use reqwest::Client;
34-
use serde::{Deserialize, Serialize};
34+
use serde::Deserialize;
3535
use serde_json::{json, Value};
36-
use serde_with::{base64::Base64, serde_as};
3736
use tracing::debug;
3837
use url::Url;
3938

4039
use malachitebft_core_types::utils::height::HeightRangeExt;
41-
use malachitebft_core_types::{CommitCertificate, CommitSignature, Round};
40+
use malachitebft_core_types::CommitCertificate;
4241

43-
use arc_consensus_types::signing::Signature;
44-
use arc_consensus_types::{Address, ArcContext, Height, ValueId};
42+
use arc_consensus_types::commit_http::HttpCommitCertificate;
43+
use arc_consensus_types::{ArcContext, Height};
4544

4645
use crate::utils::pretty::Pretty;
4746

48-
/// RPC representation of a commit certificate
49-
#[derive(Debug, Clone, Serialize, Deserialize)]
50-
pub struct RpcCommitCertificate {
51-
pub height: u64,
52-
pub round: i64,
53-
pub block_hash: ValueId,
54-
pub signatures: Vec<RpcCommitSignature>,
55-
}
56-
57-
/// RPC representation of a commit signature
58-
#[serde_as]
59-
#[derive(Debug, Clone, Serialize, Deserialize)]
60-
pub struct RpcCommitSignature {
61-
pub address: Address,
62-
#[serde_as(as = "Base64")]
63-
pub signature: Vec<u8>,
64-
}
65-
66-
impl RpcCommitCertificate {
67-
/// Convert to internal CommitCertificate type
68-
pub fn into_commit_certificate(self) -> eyre::Result<CommitCertificate<ArcContext>> {
69-
let signatures: eyre::Result<Vec<CommitSignature<ArcContext>>> =
70-
self.signatures
71-
.into_iter()
72-
.map(|sig| {
73-
let sig_bytes: [u8; 64] = sig.signature.try_into().map_err(|v: Vec<u8>| {
74-
eyre::eyre!("Invalid signature length: {}", v.len())
75-
})?;
76-
Ok(CommitSignature {
77-
address: sig.address,
78-
signature: Signature::from_bytes(sig_bytes),
79-
})
80-
})
81-
.collect();
82-
83-
let round_u32: u32 = self.round.try_into().map_err(|e| {
84-
eyre::eyre!(
85-
"Invalid round value {}, cannot convert to u32: {}",
86-
self.round,
87-
e
88-
)
89-
})?;
90-
91-
Ok(CommitCertificate {
92-
height: Height::new(self.height),
93-
round: Round::new(round_u32),
94-
value_id: self.block_hash,
95-
commit_signatures: signatures?,
96-
})
97-
}
98-
}
99-
10047
/// A block with its associated data (certificate + payload)
10148
#[derive(Debug, Clone)]
10249
pub struct SyncedBlock {
@@ -176,7 +123,7 @@ impl RpcSyncClient {
176123
let start = Instant::now();
177124

178125
let (cert_result, payload_result) = tokio::join!(
179-
self.send_batch_request::<RpcCommitCertificate>(endpoint, &cert_requests),
126+
self.send_batch_request::<HttpCommitCertificate>(endpoint, &cert_requests),
180127
self.send_batch_request::<Block>(endpoint, &block_requests),
181128
);
182129

@@ -231,7 +178,7 @@ impl RpcSyncClient {
231178
}
232179

233180
let certificate = rpc_cert
234-
.into_commit_certificate()
181+
.try_into_commit_certificate()
235182
.wrap_err_with(|| format!("Failed to convert certificate at height {height}"))?;
236183

237184
// Convert Block to ExecutionPayloadV3, verifying hash consistency

‎crates/types/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ malachitebft-sync = { workspace = true }
4343
prost = { workspace = true }
4444
rand = { workspace = true }
4545
serde = { workspace = true, features = ["derive", "rc"] }
46+
serde_with = { workspace = true, features = ["macros", "base64"] }
4647
sha3 = { workspace = true }
4748
signature = { workspace = true }
4849
thiserror = { workspace = true }

‎crates/types/src/commit_http.rs‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2026 Circle Internet Group, Inc. All rights reserved.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
//! JSON types for commit certificates over HTTP or JSON-RPC.
18+
//!
19+
//! These structs match the minimal certificate payload returned by consensus
20+
//! `GET /commit?height=…` and by JSON-RPC methods such as `arc_getCertificate`.
21+
22+
use malachitebft_core_types::{CommitCertificate, CommitSignature, Round};
23+
use serde::{Deserialize, Serialize};
24+
use serde_with::{base64::Base64, serde_as};
25+
26+
use crate::signing::Signature;
27+
use crate::{Address, ArcContext, Height, ValueId};
28+
29+
/// Signature entry within a commit certificate (base64-encoded signature bytes).
30+
#[serde_as]
31+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32+
pub struct HttpCommitSignature {
33+
pub address: Address,
34+
#[serde_as(as = "Base64")]
35+
pub signature: Vec<u8>,
36+
}
37+
38+
impl From<CommitSignature<ArcContext>> for HttpCommitSignature {
39+
fn from(sig: CommitSignature<ArcContext>) -> Self {
40+
Self {
41+
address: sig.address,
42+
signature: sig.signature.to_bytes().to_vec(),
43+
}
44+
}
45+
}
46+
47+
/// Minimal commit certificate JSON (height, round, value id, signatures).
48+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49+
pub struct HttpCommitCertificate {
50+
pub height: u64,
51+
pub round: i64,
52+
pub block_hash: ValueId,
53+
pub signatures: Vec<HttpCommitSignature>,
54+
}
55+
56+
impl HttpCommitCertificate {
57+
/// Convert wire JSON into a [`CommitCertificate`].
58+
pub fn try_into_commit_certificate(self) -> eyre::Result<CommitCertificate<ArcContext>> {
59+
let signatures: eyre::Result<Vec<CommitSignature<ArcContext>>> =
60+
self.signatures
61+
.into_iter()
62+
.map(|sig| {
63+
let sig_bytes: [u8; 64] = sig.signature.try_into().map_err(|v: Vec<u8>| {
64+
eyre::eyre!("Invalid signature length: {}", v.len())
65+
})?;
66+
Ok(CommitSignature {
67+
address: sig.address,
68+
signature: Signature::from_bytes(sig_bytes),
69+
})
70+
})
71+
.collect();
72+
73+
let round_u32: u32 = self.round.try_into().map_err(|e| {
74+
eyre::eyre!(
75+
"Invalid round value {}, cannot convert to u32: {}",
76+
self.round,
77+
e
78+
)
79+
})?;
80+
81+
Ok(CommitCertificate {
82+
height: Height::new(self.height),
83+
round: Round::new(round_u32),
84+
value_id: self.block_hash,
85+
commit_signatures: signatures?,
86+
})
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
use crate::{Address, BlockHash};
94+
95+
#[test]
96+
fn http_commit_certificate_json_roundtrip() {
97+
let orig = HttpCommitCertificate {
98+
height: 42,
99+
round: 3,
100+
block_hash: ValueId::new(BlockHash::ZERO),
101+
signatures: vec![],
102+
};
103+
let json = serde_json::to_string(&orig).unwrap();
104+
let back: HttpCommitCertificate = serde_json::from_str(&json).unwrap();
105+
assert_eq!(orig, back);
106+
}
107+
108+
#[test]
109+
fn try_into_commit_certificate_empty_signatures() {
110+
let wire = HttpCommitCertificate {
111+
height: 10,
112+
round: 2,
113+
block_hash: ValueId::new(BlockHash::ZERO),
114+
signatures: vec![],
115+
};
116+
let cert = wire.try_into_commit_certificate().unwrap();
117+
assert_eq!(cert.height.as_u64(), 10);
118+
assert_eq!(cert.round.as_i64(), 2);
119+
assert_eq!(cert.value_id, ValueId::new(BlockHash::ZERO));
120+
assert!(cert.commit_signatures.is_empty());
121+
}
122+
123+
#[test]
124+
fn try_into_rejects_short_signature() {
125+
let wire = HttpCommitCertificate {
126+
height: 1,
127+
round: 0,
128+
block_hash: ValueId::new(BlockHash::ZERO),
129+
signatures: vec![HttpCommitSignature {
130+
address: Address::default(),
131+
signature: vec![0u8; 32],
132+
}],
133+
};
134+
assert!(wire.try_into_commit_certificate().is_err());
135+
}
136+
137+
#[test]
138+
fn http_commit_signature_json_roundtrip() {
139+
let mut b = [0u8; 64];
140+
b[0] = 1;
141+
b[63] = 2;
142+
let cs = CommitSignature {
143+
address: Address::default(),
144+
signature: Signature::from_bytes(b),
145+
};
146+
let http = HttpCommitSignature::from(cs);
147+
let json = serde_json::to_string(&http).unwrap();
148+
let back: HttpCommitSignature = serde_json::from_str(&json).unwrap();
149+
assert_eq!(http, back);
150+
}
151+
}

‎crates/types/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
mod address;
2222
mod aliases;
2323
mod certificate;
24+
pub mod commit_http;
2425
mod config;
2526
mod consensus_params;
2627
mod context;

0 commit comments

Comments
 (0)