1+ #![ allow( non_snake_case) ]
2+
13use crate :: v1:: da_source_adapter:: {
24 service:: {
35 DaBlockCostsSource ,
@@ -6,57 +8,330 @@ use crate::v1::da_source_adapter::{
68 DaBlockCosts ,
79} ;
810use anyhow:: anyhow;
9- use reqwest :: Url ;
11+ use fuel_core_types :: blockchain :: primitives :: DaBlockHeight ;
1012use 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 ) ]
2340struct 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