Skip to content

Commit b47bf54

Browse files
committed
feat(gas_price_service): update block committer da source with established contract
1 parent fb926c4 commit b47bf54

File tree

3 files changed

+304
-29
lines changed

3 files changed

+304
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111
- [2131](https://github.com/FuelLabs/fuel-core/pull/2131): Add flow in TxPool in order to ask to newly connected peers to share their transaction pool
1212
- [2182](https://github.com/FuelLabs/fuel-core/pull/2151): Limit number of transactions that can be fetched via TxSource::next
1313
- [2189](https://github.com/FuelLabs/fuel-core/pull/2151): Select next DA height to never include more than u16::MAX -1 transactions from L1.
14-
14+
- [2265](https://github.com/FuelLabs/fuel-core/pull/2265): Integrate Block Committer API for DA Block Costs.
1515

1616
### Changed
1717

crates/services/gas_price_service/src/v1/da_source_adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub const POLLING_INTERVAL_MS: u64 = 10_000;
2424

2525
#[derive(Debug, Default, Clone, Eq, Hash, PartialEq)]
2626
pub struct DaBlockCosts {
27-
pub l2_block_range: core::ops::Range<u32>,
27+
pub l2_block_range: core::ops::Range<u64>,
2828
pub blob_size_bytes: u32,
2929
pub blob_cost_wei: u128,
3030
}
Lines changed: 302 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(non_snake_case)]
2+
13
use crate::v1::da_source_adapter::{
24
service::{
35
DaBlockCostsSource,
@@ -6,57 +8,330 @@ use crate::v1::da_source_adapter::{
68
DaBlockCosts,
79
};
810
use anyhow::anyhow;
9-
use reqwest::Url;
11+
use fuel_core_types::blockchain::primitives::DaBlockHeight;
1012
use serde::{
1113
Deserialize,
1214
Serialize,
1315
};
1416

15-
/// This struct is used to denote the block committer da gas price source,,
17+
#[async_trait::async_trait]
18+
trait BlockCommitterApi: Send + Sync {
19+
// Used on first run to get the latest costs and seqno
20+
async fn get_latest_costs(&self) -> DaBlockCostsResult<Option<RawDaBlockCosts>>;
21+
// Used to get the costs for a specific seqno
22+
async fn get_costs_by_seqno(
23+
&self,
24+
number: u64,
25+
) -> DaBlockCostsResult<Option<RawDaBlockCosts>>;
26+
async fn get_bundles_by_range(
27+
&self,
28+
range: core::ops::Range<u64>,
29+
) -> DaBlockCostsResult<Vec<Option<RawDaBlockCosts>>>;
30+
}
31+
32+
/// This struct is used to denote the block committer da block costs source
1633
/// which receives data from the block committer (only http api for now)
17-
pub struct BlockCommitterDaBlockCosts {
18-
client: reqwest::Client,
19-
url: Url,
34+
pub struct BlockCommitterDaBlockCosts<BlockCommitter> {
35+
client: BlockCommitter,
36+
last_value: Option<RawDaBlockCosts>,
2037
}
2138

2239
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq)]
2340
struct RawDaBlockCosts {
24-
pub l2_block_range: core::ops::Range<u32>,
25-
pub blob_size_bytes: u32,
26-
pub blob_cost: u128,
41+
// Sequence number (Monotonically increasing nonce)
42+
pub sequence_number: u64,
43+
// The range of blocks that the costs apply to
44+
pub blocks_range: core::ops::Range<u64>,
45+
// The DA block height of the last transaction for the range of blocks
46+
pub da_block_height: DaBlockHeight,
47+
// Rolling sum cost of posting blobs in wei
48+
pub total_cost: u128,
49+
// Rolling sum size of blobs (bytes)
50+
pub total_size_bytes: u32,
2751
}
2852

29-
impl From<RawDaBlockCosts> for DaBlockCosts {
30-
fn from(raw: RawDaBlockCosts) -> Self {
31-
DaBlockCosts {
32-
l2_block_range: raw.l2_block_range,
33-
blob_size_bytes: raw.blob_size_bytes,
34-
blob_cost_wei: raw.blob_cost,
53+
impl<BlockCommitter> BlockCommitterDaBlockCosts<BlockCommitter> {
54+
/// Create a new instance of the block committer da block costs source
55+
pub fn new(client: BlockCommitter) -> Self {
56+
Self {
57+
client,
58+
last_value: None,
3559
}
3660
}
3761
}
3862

39-
impl BlockCommitterDaBlockCosts {
40-
/// Create a new instance of the block committer da gas price source
63+
#[async_trait::async_trait]
64+
impl<BlockCommitter> DaBlockCostsSource for BlockCommitterDaBlockCosts<BlockCommitter>
65+
where
66+
BlockCommitter: BlockCommitterApi,
67+
{
68+
async fn request_da_block_cost(&mut self) -> DaBlockCostsResult<DaBlockCosts> {
69+
let response;
70+
71+
if let Some(last_value) = &self.last_value {
72+
response = self
73+
.client
74+
.get_costs_by_seqno(last_value.sequence_number + 1)
75+
.await?;
76+
} else {
77+
// we have to error if we cannot find the first set of costs
78+
response = self.client.get_latest_costs().await?;
79+
}
80+
81+
if let Some(response) = response {
82+
let res;
83+
if let Some(last_value) = &self.last_value {
84+
res = DaBlockCosts {
85+
l2_block_range: response.blocks_range.clone(),
86+
blob_size_bytes: response
87+
.total_size_bytes
88+
.checked_sub(last_value.total_size_bytes)
89+
.ok_or(anyhow!("Blob size bytes underflow"))?,
90+
blob_cost_wei: response
91+
.total_cost
92+
.checked_sub(last_value.total_cost)
93+
.ok_or(anyhow!("Blob cost wei underflow"))?,
94+
};
95+
} else {
96+
res = DaBlockCosts {
97+
l2_block_range: response.blocks_range.clone(),
98+
blob_size_bytes: response.total_size_bytes,
99+
blob_cost_wei: response.total_cost,
100+
};
101+
}
102+
self.last_value = Some(response.clone());
103+
Ok(res)
104+
} else {
105+
Err(anyhow!("No response from block committer"))
106+
}
107+
}
108+
}
109+
110+
pub struct BlockCommitterHttpApi {
111+
client: reqwest::Client,
112+
url: String,
113+
}
114+
115+
impl BlockCommitterHttpApi {
41116
pub fn new(url: String) -> Self {
42117
Self {
43118
client: reqwest::Client::new(),
44-
url: Url::parse(&url).unwrap(),
119+
url,
45120
}
46121
}
47122
}
48123

49124
#[async_trait::async_trait]
50-
impl DaBlockCostsSource for BlockCommitterDaBlockCosts {
51-
async fn request_da_block_cost(&mut self) -> DaBlockCostsResult<DaBlockCosts> {
52-
let response = self.client.get(self.url.clone()).send().await?;
53-
if !response.status().is_success() {
54-
return Err(anyhow!("failed with response: {}", response.status()));
55-
}
56-
let response = response
125+
impl BlockCommitterApi for BlockCommitterHttpApi {
126+
async fn get_latest_costs(&self) -> DaBlockCostsResult<Option<RawDaBlockCosts>> {
127+
let response = self
128+
.client
129+
.get(&self.url)
130+
.send()
131+
.await?
132+
.json::<RawDaBlockCosts>()
133+
.await?;
134+
Ok(Some(response))
135+
}
136+
137+
async fn get_costs_by_seqno(
138+
&self,
139+
number: u64,
140+
) -> DaBlockCostsResult<Option<RawDaBlockCosts>> {
141+
let response = self
142+
.client
143+
.get(&format!("{}/{}", self.url, number))
144+
.send()
145+
.await?
57146
.json::<RawDaBlockCosts>()
58-
.await
59-
.map_err(|err| anyhow!(err))?;
60-
Ok(response.into())
147+
.await?;
148+
Ok(Some(response))
149+
}
150+
151+
async fn get_bundles_by_range(
152+
&self,
153+
range: core::ops::Range<u64>,
154+
) -> DaBlockCostsResult<Vec<Option<RawDaBlockCosts>>> {
155+
let response = self
156+
.client
157+
.get(&format!("{}/{}-{}", self.url, range.start, range.end))
158+
.send()
159+
.await?
160+
.json::<Vec<RawDaBlockCosts>>()
161+
.await?;
162+
Ok(response.into_iter().map(Some).collect())
163+
}
164+
}
165+
166+
#[cfg(test)]
167+
mod tests {
168+
use super::*;
169+
170+
struct MockBlockCommitterApi {
171+
value: Option<RawDaBlockCosts>,
172+
}
173+
174+
impl MockBlockCommitterApi {
175+
fn new(value: Option<RawDaBlockCosts>) -> Self {
176+
Self { value }
177+
}
178+
}
179+
180+
#[async_trait::async_trait]
181+
impl BlockCommitterApi for MockBlockCommitterApi {
182+
async fn get_latest_costs(&self) -> DaBlockCostsResult<Option<RawDaBlockCosts>> {
183+
Ok(self.value.clone())
184+
}
185+
async fn get_costs_by_seqno(
186+
&self,
187+
seq_no: u64,
188+
) -> DaBlockCostsResult<Option<RawDaBlockCosts>> {
189+
// arbitrary logic to generate a new value
190+
let mut value = self.value.clone();
191+
if let Some(value) = &mut value {
192+
value.sequence_number = seq_no;
193+
value.blocks_range = value.blocks_range.end..value.blocks_range.end + 10;
194+
value.da_block_height = value.da_block_height + 1u64.into();
195+
value.total_cost += 1;
196+
value.total_size_bytes += 1;
197+
}
198+
Ok(value)
199+
}
200+
async fn get_bundles_by_range(
201+
&self,
202+
_: core::ops::Range<u64>,
203+
) -> DaBlockCostsResult<Vec<Option<RawDaBlockCosts>>> {
204+
Ok(vec![self.value.clone()])
205+
}
206+
}
207+
208+
fn test_da_block_costs() -> RawDaBlockCosts {
209+
RawDaBlockCosts {
210+
sequence_number: 1,
211+
blocks_range: 0..10,
212+
da_block_height: 1u64.into(),
213+
total_cost: 1,
214+
total_size_bytes: 1,
215+
}
216+
}
217+
218+
#[tokio::test]
219+
async fn request_da_block_cost__when_last_value_is_none__then_get_latest_costs_is_called(
220+
) {
221+
// given
222+
let da_block_costs = test_da_block_costs();
223+
let mock_block_committer =
224+
MockBlockCommitterApi::new(Some(da_block_costs.clone()));
225+
let mut block_committer = BlockCommitterDaBlockCosts::new(mock_block_committer);
226+
227+
// when
228+
let block_committer_da_block_costs =
229+
block_committer.request_da_block_cost().await.unwrap();
230+
231+
// then
232+
assert_eq!(
233+
block_committer_da_block_costs,
234+
DaBlockCosts {
235+
l2_block_range: da_block_costs.blocks_range.clone(),
236+
blob_size_bytes: da_block_costs.total_size_bytes,
237+
blob_cost_wei: da_block_costs.total_cost,
238+
}
239+
);
240+
assert!(block_committer.last_value.is_some());
241+
}
242+
243+
#[tokio::test]
244+
async fn request_da_block_cost__when_last_value_is_some__then_get_costs_by_seqno_is_called(
245+
) {
246+
// given
247+
let mut da_block_costs = test_da_block_costs();
248+
let mock_block_committer =
249+
MockBlockCommitterApi::new(Some(da_block_costs.clone()));
250+
let mut block_committer = BlockCommitterDaBlockCosts::new(mock_block_committer);
251+
252+
// when
253+
let initial = block_committer.request_da_block_cost().await.unwrap();
254+
let updated = block_committer.request_da_block_cost().await.unwrap();
255+
256+
// then
257+
assert_eq!(initial.blob_size_bytes, updated.blob_size_bytes);
258+
assert_eq!(initial.blob_cost_wei, updated.blob_cost_wei);
259+
assert_ne!(initial.l2_block_range, updated.l2_block_range);
260+
}
261+
262+
#[tokio::test]
263+
async fn request_da_block_cost__when_response_is_none__then_error() {
264+
// given
265+
let mock_block_committer = MockBlockCommitterApi::new(None);
266+
let mut block_committer = BlockCommitterDaBlockCosts::new(mock_block_committer);
267+
268+
// when
269+
let result = block_committer.request_da_block_cost().await;
270+
271+
// then
272+
assert!(result.is_err());
273+
assert!(block_committer.last_value.is_none());
274+
}
275+
276+
struct UnderflowingMockBlockCommitterApi {
277+
value: Option<RawDaBlockCosts>,
278+
}
279+
280+
impl UnderflowingMockBlockCommitterApi {
281+
fn new(value: Option<RawDaBlockCosts>) -> Self {
282+
Self { value }
283+
}
284+
}
285+
286+
#[async_trait::async_trait]
287+
impl BlockCommitterApi for UnderflowingMockBlockCommitterApi {
288+
async fn get_latest_costs(&self) -> DaBlockCostsResult<Option<RawDaBlockCosts>> {
289+
Ok(self.value.clone())
290+
}
291+
async fn get_costs_by_seqno(
292+
&self,
293+
seq_no: u64,
294+
) -> DaBlockCostsResult<Option<RawDaBlockCosts>> {
295+
// arbitrary logic to generate a new value
296+
let mut value = self.value.clone();
297+
if let Some(value) = &mut value {
298+
value.sequence_number = seq_no;
299+
value.blocks_range = value.blocks_range.end..value.blocks_range.end + 10;
300+
value.da_block_height = value.da_block_height + 1u64.into();
301+
value.total_cost = value.total_cost - 1;
302+
value.total_size_bytes = value.total_size_bytes - 1;
303+
}
304+
Ok(value)
305+
}
306+
async fn get_bundles_by_range(
307+
&self,
308+
_: core::ops::Range<u64>,
309+
) -> DaBlockCostsResult<Vec<Option<RawDaBlockCosts>>> {
310+
Ok(vec![self.value.clone()])
311+
}
312+
}
313+
314+
#[tokio::test]
315+
async fn request_da_block_cost__when_underflow__then_error() {
316+
// given
317+
let da_block_costs = test_da_block_costs();
318+
let mock_block_committer =
319+
UnderflowingMockBlockCommitterApi::new(Some(da_block_costs.clone()));
320+
let mut block_committer = BlockCommitterDaBlockCosts::new(mock_block_committer);
321+
322+
// when
323+
let initial = block_committer.request_da_block_cost().await.unwrap();
324+
let result = block_committer.request_da_block_cost().await;
325+
326+
// then
327+
assert!(result.is_err());
328+
assert_eq!(
329+
initial,
330+
DaBlockCosts {
331+
l2_block_range: da_block_costs.blocks_range.clone(),
332+
blob_size_bytes: da_block_costs.total_size_bytes,
333+
blob_cost_wei: da_block_costs.total_cost,
334+
}
335+
);
61336
}
62337
}

0 commit comments

Comments
 (0)