Skip to content

Commit acfcd1c

Browse files
committed
Add workload id as metric to builder
1 parent b473f4e commit acfcd1c

File tree

3 files changed

+123
-1
lines changed

3 files changed

+123
-1
lines changed

crates/op-rbuilder/src/flashtestations/attestation.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
use reqwest::Client;
2+
use sha3::{Digest, Keccak256};
23
use tracing::info;
34

45
const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/attest";
56

7+
// Raw TDX v4 quote structure constants
8+
// Raw quote has a 48-byte header before the TD10ReportBody
9+
const HEADER_LENGTH: usize = 48;
10+
const TD_REPORT10_LENGTH: usize = 584;
11+
12+
// TDX workload constants
13+
const TD_XFAM_FPU: u64 = 0x0000000000000001;
14+
const TD_XFAM_SSE: u64 = 0x0000000000000002;
15+
const TD_TDATTRS_VE_DISABLED: u64 = 0x0000000010000000;
16+
const TD_TDATTRS_PKS: u64 = 0x0000000040000000;
17+
const TD_TDATTRS_KL: u64 = 0x0000000080000000;
18+
619
/// Configuration for attestation
720
#[derive(Default)]
821
pub struct AttestationConfig {
@@ -63,3 +76,95 @@ pub fn get_attestation_provider(config: AttestationConfig) -> RemoteAttestationP
6376
)
6477
}
6578
}
79+
80+
/// ComputeWorkloadID computes the workload ID from Automata's serialized verifier output
81+
/// This corresponds to QuoteParser.parseV4VerifierOutput in Solidity implementation
82+
/// https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216
83+
/// The workload ID uniquely identifies a TEE workload based on its measurement registers
84+
pub fn compute_workload_id(raw_quote: &[u8]) -> eyre::Result<[u8; 32]> {
85+
// Validate quote length
86+
if raw_quote.len() < HEADER_LENGTH + TD_REPORT10_LENGTH {
87+
eyre::bail!(
88+
"invalid quote length: {}, expected at least {}",
89+
raw_quote.len(),
90+
HEADER_LENGTH + TD_REPORT10_LENGTH
91+
);
92+
}
93+
94+
// Skip the 48-byte header to get to the TD10ReportBody
95+
let report_body = &raw_quote[HEADER_LENGTH..];
96+
97+
// Extract fields exactly as parseRawReportBody does in Solidity
98+
// Using hardcoded offsets to match Solidity implementation exactly
99+
let mr_td = &report_body[136..136 + 48];
100+
let rt_mr0 = &report_body[328..328 + 48];
101+
let rt_mr1 = &report_body[376..376 + 48];
102+
let rt_mr2 = &report_body[424..424 + 48];
103+
let rt_mr3 = &report_body[472..472 + 48];
104+
let mr_config_id = &report_body[184..184 + 48];
105+
106+
// Extract xFAM and tdAttributes (8 bytes each)
107+
// In Solidity, bytes8 is treated as big-endian for bitwise operations
108+
let xfam = u64::from_be_bytes(report_body[128..128 + 8].try_into().unwrap());
109+
let td_attributes = u64::from_be_bytes(report_body[120..120 + 8].try_into().unwrap());
110+
111+
// Apply transformations as per the Solidity implementation
112+
// expectedXfamBits = TD_XFAM_FPU | TD_XFAM_SSE
113+
let expected_xfam_bits = TD_XFAM_FPU | TD_XFAM_SSE;
114+
115+
// ignoredTdAttributesBitmask = TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL
116+
let ignored_td_attributes_bitmask = TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL;
117+
118+
// Transform xFAM: xFAM ^ expectedXfamBits
119+
let transformed_xfam = xfam ^ expected_xfam_bits;
120+
121+
// Transform tdAttributes: tdAttributes & ~ignoredTdAttributesBitmask
122+
let transformed_td_attributes = td_attributes & !ignored_td_attributes_bitmask;
123+
124+
// Convert transformed values to bytes (big-endian, to match Solidity bytes8)
125+
let xfam_bytes = transformed_xfam.to_be_bytes();
126+
let td_attributes_bytes = transformed_td_attributes.to_be_bytes();
127+
128+
// Concatenate all fields
129+
let mut concatenated = Vec::new();
130+
concatenated.extend_from_slice(mr_td);
131+
concatenated.extend_from_slice(rt_mr0);
132+
concatenated.extend_from_slice(rt_mr1);
133+
concatenated.extend_from_slice(rt_mr2);
134+
concatenated.extend_from_slice(rt_mr3);
135+
concatenated.extend_from_slice(mr_config_id);
136+
concatenated.extend_from_slice(&xfam_bytes);
137+
concatenated.extend_from_slice(&td_attributes_bytes);
138+
139+
// Compute keccak256 hash
140+
let mut hasher = Keccak256::new();
141+
hasher.update(&concatenated);
142+
let result = hasher.finalize();
143+
144+
let mut workload_id = [0u8; 32];
145+
workload_id.copy_from_slice(&result);
146+
147+
Ok(workload_id)
148+
}
149+
150+
#[cfg(test)]
151+
mod tests {
152+
use crate::tests::WORKLOAD_ID;
153+
154+
use super::*;
155+
156+
#[test]
157+
fn test_compute_workload_id_from_test_quote() {
158+
// Load the test quote output used in integration tests
159+
let quote_output = include_bytes!("../tests/framework/artifacts/test-quote.bin");
160+
161+
// Compute the workload ID
162+
let workload_id = compute_workload_id(quote_output)
163+
.expect("failed to compute workload ID from test quote");
164+
165+
assert_eq!(
166+
workload_id, WORKLOAD_ID,
167+
"workload ID mismatch for test quote"
168+
);
169+
}
170+
}

crates/op-rbuilder/src/flashtestations/service.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ use super::{
1313
tx_manager::TxManager,
1414
};
1515
use crate::{
16-
flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs},
16+
flashtestations::{
17+
attestation::compute_workload_id,
18+
builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs},
19+
},
20+
metrics::record_workload_id_metrics,
1721
tx_signer::{Signer, generate_key_from_seed, generate_signer},
1822
};
1923
use std::fmt::Debug;
@@ -74,6 +78,10 @@ where
7478
info!(target: "flashtestations", "requesting TDX attestation");
7579
let attestation = attestation_provider.get_attestation(report_data).await?;
7680

81+
// Compute workload id and record metrics
82+
let workload_id = compute_workload_id(&attestation)?;
83+
record_workload_id_metrics(workload_id);
84+
7785
// Use an external rpc when the builder is not the same as the builder actively building blocks onchain
7886
let registered = if let Some(rpc_url) = args.rpc_url {
7987
let tx_manager = TxManager::new(

crates/op-rbuilder/src/metrics.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,15 @@ pub fn record_flag_gauge_metrics(builder_args: &OpRbuilderArgs) {
202202
.set(builder_args.enable_revert_protection as i32);
203203
}
204204

205+
/// Set the workload id metrics
206+
pub fn record_workload_id_metrics(workload_id: [u8; 32]) {
207+
let encoded = hex::encode(workload_id);
208+
let encoded_static: &'static str = Box::leak(encoded.into_boxed_str());
209+
let labels: [(&str, &str); 1] = [("workload_id", encoded_static)];
210+
let gauge = gauge!("op_rbuilder_workload_id", &labels);
211+
gauge.set(1);
212+
}
213+
205214
/// Contains version information for the application.
206215
#[derive(Debug, Clone)]
207216
pub struct VersionInfo {

0 commit comments

Comments
 (0)