aztec_pxe/stores/
anchor_block_store.rs1use std::sync::Arc;
7
8use aztec_core::error::Error;
9use serde::{Deserialize, Serialize};
10
11use super::kv::KvStore;
12
13const ANCHOR_HEADER_KEY: &[u8] = b"anchor:header";
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct AnchorBlockHeader {
19 pub data: serde_json::Value,
21 pub block_number: u64,
23 pub block_hash: String,
25}
26
27impl AnchorBlockHeader {
28 pub fn get_block_number(&self) -> u64 {
30 self.block_number
31 }
32
33 pub fn get_block_hash(&self) -> &str {
35 &self.block_hash
36 }
37
38 pub fn from_header_json(data: serde_json::Value) -> Self {
40 let block_number = data
41 .pointer("/globalVariables/blockNumber")
42 .and_then(|v| {
43 v.as_u64().or_else(|| {
44 v.as_str().and_then(|s| {
45 if let Some(hex) = s.strip_prefix("0x") {
46 u64::from_str_radix(hex, 16).ok()
47 } else {
48 s.parse::<u64>().ok()
49 }
50 })
51 })
52 })
53 .unwrap_or(0);
54
55 let block_hash = data
56 .pointer("/blockHash")
57 .or_else(|| data.get("blockHash"))
58 .and_then(|v| v.as_str())
59 .unwrap_or("0x0")
60 .to_owned();
61
62 Self {
63 data,
64 block_number,
65 block_hash,
66 }
67 }
68}
69
70pub struct AnchorBlockStore {
76 kv: Arc<dyn KvStore>,
77}
78
79impl AnchorBlockStore {
80 pub fn new(kv: Arc<dyn KvStore>) -> Self {
81 Self { kv }
82 }
83
84 pub async fn set_header(&self, header: &AnchorBlockHeader) -> Result<(), Error> {
86 let value = serde_json::to_vec(header)?;
87 self.kv.put(ANCHOR_HEADER_KEY, &value).await
88 }
89
90 pub async fn get_block_header(&self) -> Result<Option<AnchorBlockHeader>, Error> {
94 match self.kv.get(ANCHOR_HEADER_KEY).await? {
95 Some(bytes) => Ok(Some(serde_json::from_slice(&bytes)?)),
96 None => Ok(None),
97 }
98 }
99
100 pub async fn get_block_header_or_err(&self) -> Result<AnchorBlockHeader, Error> {
102 self.get_block_header()
103 .await?
104 .ok_or_else(|| Error::InvalidData("anchor block header not set".into()))
105 }
106
107 pub async fn get_block_number(&self) -> Result<u64, Error> {
109 match self.get_block_header().await? {
110 Some(header) => Ok(header.block_number),
111 None => Ok(0),
112 }
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::stores::InMemoryKvStore;
120
121 #[tokio::test]
122 async fn set_and_get_header() {
123 let kv = Arc::new(InMemoryKvStore::new());
124 let store = AnchorBlockStore::new(kv);
125
126 let header = AnchorBlockHeader::from_header_json(serde_json::json!({
127 "globalVariables": {"blockNumber": 42},
128 "blockHash": "0xabc123"
129 }));
130
131 store.set_header(&header).await.unwrap();
132 let retrieved = store.get_block_header().await.unwrap().unwrap();
133 assert_eq!(retrieved.block_number, 42);
134 assert_eq!(retrieved.block_hash, "0xabc123");
135 }
136
137 #[tokio::test]
138 async fn get_header_returns_none_when_unset() {
139 let kv = Arc::new(InMemoryKvStore::new());
140 let store = AnchorBlockStore::new(kv);
141 assert!(store.get_block_header().await.unwrap().is_none());
142 }
143
144 #[tokio::test]
145 async fn get_header_or_err_fails_when_unset() {
146 let kv = Arc::new(InMemoryKvStore::new());
147 let store = AnchorBlockStore::new(kv);
148 assert!(store.get_block_header_or_err().await.is_err());
149 }
150
151 #[tokio::test]
152 async fn from_header_json_extracts_block_number() {
153 let header = AnchorBlockHeader::from_header_json(serde_json::json!({
154 "globalVariables": {"blockNumber": 100}
155 }));
156 assert_eq!(header.block_number, 100);
157 }
158
159 #[tokio::test]
160 async fn from_header_json_handles_hex_block_number() {
161 let header = AnchorBlockHeader::from_header_json(serde_json::json!({
162 "globalVariables": {"blockNumber": "0x0a"}
163 }));
164 assert_eq!(header.block_number, 10);
165 }
166
167 #[tokio::test]
168 async fn set_header_overwrites_previous() {
169 let kv = Arc::new(InMemoryKvStore::new());
170 let store = AnchorBlockStore::new(kv);
171
172 let h1 = AnchorBlockHeader::from_header_json(serde_json::json!({
173 "globalVariables": {"blockNumber": 1}
174 }));
175 let h2 = AnchorBlockHeader::from_header_json(serde_json::json!({
176 "globalVariables": {"blockNumber": 2}
177 }));
178
179 store.set_header(&h1).await.unwrap();
180 store.set_header(&h2).await.unwrap();
181
182 let retrieved = store.get_block_header().await.unwrap().unwrap();
183 assert_eq!(retrieved.block_number, 2);
184 }
185}