-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtarpaulin-report.html
660 lines (618 loc) · 589 KB
/
tarpaulin-report.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>html, body {
margin: 0;
padding: 0;
}
.app {
margin: 10px;
padding: 0;
}
.files-list {
margin: 10px 0 0;
width: 100%;
border-collapse: collapse;
}
.files-list__head {
border: 1px solid #999;
}
.files-list__head > tr > th {
padding: 10px;
border: 1px solid #999;
text-align: left;
font-weight: normal;
background: #ddd;
}
.files-list__body {
}
.files-list__file {
cursor: pointer;
}
.files-list__file:hover {
background: #ccf;
}
.files-list__file > td {
padding: 10px;
border: 1px solid #999;
}
.files-list__file > td:first-child::before {
content: '\01F4C4';
margin-right: 1em;
}
.files-list__file_low {
background: #fcc;
}
.files-list__file_medium {
background: #ffc;
}
.files-list__file_high {
background: #cfc;
}
.files-list__file_folder > td:first-child::before {
content: '\01F4C1';
margin-right: 1em;
}
.file-header {
border: 1px solid #999;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-header__back {
margin: 10px;
cursor: pointer;
flex-shrink: 0;
flex-grow: 0;
text-decoration: underline;
color: #338;
}
.file-header__name {
margin: 10px;
flex-shrink: 2;
flex-grow: 2;
}
.file-header__stat {
margin: 10px;
flex-shrink: 0;
flex-grow: 0;
}
.file-content {
margin: 10px 0 0;
border: 1px solid #999;
padding: 10px;
}
.code-line {
margin: 0;
padding: 0.3em;
height: 1em;
}
.code-line_covered {
background: #cfc;
}
.code-line_uncovered {
background: #fcc;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
var data = {"files":[{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","bin","schema.rs"],"content":"// use cosmwasm_schema::write_api;\n\n// use light_client::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};\n\nfn main() {\n // write_api! {\n // instantiate: InstantiateMsg,\n // execute: ExecuteMsg,\n // query: QueryMsg,\n // }\n}\n","traces":[{"line":5,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":0,"coverable":1},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","bls","lib.rs"],"content":"#![cfg_attr(not(feature = \"std\"), no_std)]\n\n#[cfg(not(feature = \"std\"))]\nextern crate alloc;\n\nextern crate amcl;\n#[cfg(feature = \"std\")]\n#[macro_use]\nextern crate lazy_static;\nextern crate rand;\n\nmod aggregates;\nmod amcl_utils;\nmod keys;\nmod signature;\n\nuse self::amcl::bls381 as BLSCurve;\n\npub use aggregates::{AggregatePublicKey, AggregateSignature};\npub use amcl_utils::{AmclError, G1_BYTES, G2_BYTES, SECRET_KEY_BYTES};\npub use keys::{Keypair, PublicKey, SecretKey};\npub use signature::Signature;\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","contract.rs"],"content":"#[cfg(not(feature = \"library\"))]\nuse cosmwasm_std::entry_point;\nuse cosmwasm_std::{\n to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult,\n};\nuse cw_ownable::{assert_owner, get_ownership, update_ownership};\n\nuse crate::error::ContractError;\nuse crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};\nuse crate::{lightclient::LightClient, state::*};\nuse eyre::Result;\n\nuse crate::execute::{self, process_batch_data};\nuse crate::lightclient::helpers::LowerCaseFields;\nuse types::common::{ContentVariant, PrimaryKey, VerificationResult};\nuse types::connection_router::Message;\n\n#[cfg_attr(not(feature = \"library\"), entry_point)]\npub fn instantiate(\n deps: DepsMut,\n env: Env,\n info: MessageInfo,\n msg: InstantiateMsg,\n) -\u003e Result\u003cResponse, ContractError\u003e {\n cw_ownable::initialize_owner(deps.storage, deps.api, Some(info.sender.as_str()))?;\n\n let mut lc = LightClient::new(\u0026msg.config.chain_config, None, \u0026env);\n lc.bootstrap(\u0026msg.bootstrap).unwrap();\n\n LIGHT_CLIENT_STATE.save(deps.storage, \u0026lc.state)?;\n CONFIG.save(deps.storage, \u0026msg.config)?;\n\n Ok(Response::new())\n}\n\n#[cfg_attr(not(feature = \"library\"), entry_point)]\npub fn execute(\n deps: DepsMut,\n env: Env,\n info: MessageInfo,\n msg: ExecuteMsg,\n) -\u003e Result\u003cResponse, ContractError\u003e {\n use ExecuteMsg::*;\n\n match msg {\n LightClientUpdate(update) =\u003e execute::light_client_update(deps, \u0026env, update),\n UpdateConfig(config) =\u003e {\n assert_owner(deps.storage, \u0026info.sender)\n .map_err(|e| ContractError::Std(StdError::GenericErr { msg: e.to_string() }))?;\n CONFIG.save(deps.storage, \u0026config)?;\n Ok(Response::new())\n }\n BatchVerificationData(payload) =\u003e {\n let state = LIGHT_CLIENT_STATE.load(deps.storage)?;\n let config = CONFIG.load(deps.storage)?;\n let lc = LightClient::new(\u0026config.chain_config, Some(state), \u0026env);\n\n let results = process_batch_data(deps, \u0026lc, \u0026payload, \u0026config)\n .map_err(|e| ContractError::Std(StdError::GenericErr { msg: e.to_string() }))?;\n\n Ok(Response::new().set_data(to_json_binary(\n \u0026results\n .iter()\n .map(|result| {\n let key = match \u0026result.0 {\n ContentVariant::Message(message) =\u003e message.key(),\n ContentVariant::WorkerSet(message) =\u003e message.key(),\n };\n let status = result\n .1\n .as_ref()\n .map(|_| String::from(\"OK\"))\n .unwrap_or_else(|e| e.to_string());\n\n (key, status)\n })\n .collect::\u003cVerificationResult\u003e(),\n )?))\n }\n UpdateOwnership(action) =\u003e {\n update_ownership(deps, \u0026env.block, \u0026info.sender, action)\n .map_err(|e| ContractError::Std(StdError::GenericErr { msg: e.to_string() }))?;\n Ok(Response::new())\n }\n VerifyMessages { messages: _ } =\u003e Ok(Response::new()),\n }\n}\n\n#[cfg_attr(not(feature = \"library\"), entry_point)]\npub fn query(deps: Deps, _env: Env, msg: QueryMsg) -\u003e StdResult\u003cBinary\u003e {\n use QueryMsg::*;\n\n match msg {\n LightClientState {} =\u003e to_json_binary(\u0026LIGHT_CLIENT_STATE.load(deps.storage)?),\n Config {} =\u003e to_json_binary(\u0026CONFIG.load(deps.storage)?),\n IsVerified { messages } =\u003e to_json_binary(\n \u0026messages\n .into_iter()\n .map(|message| {\n let result =\n VERIFIED_MESSAGES.load(deps.storage, message.to_lowercase().hash());\n (message, result.is_ok())\n })\n .collect::\u003cVec\u003c(Message, bool)\u003e\u003e(),\n ),\n IsWorkerSetVerified { message } =\u003e {\n let result = VERIFIED_WORKER_SETS.load(deps.storage, message.to_lowercase().key());\n to_json_binary(\u0026result.is_ok())\n }\n Ownership {} =\u003e to_json_binary(\u0026get_ownership(deps.storage)?),\n }\n}\n\n#[cfg(test)]\nmod tests {\n use std::time::{SystemTime, UNIX_EPOCH};\n\n use crate::execute::tests::extract_content_from_block;\n use crate::{\n contract::{execute, instantiate, query},\n lightclient::helpers::test_helpers::*,\n lightclient::LightClient,\n msg::ExecuteMsg,\n };\n use cosmwasm_std::{from_json, testing::mock_env, Addr, Timestamp};\n use cw_multi_test::{App, ContractWrapper, Executor};\n use cw_ownable::{Action, Ownership};\n use types::common::{Config, ContentVariant, PrimaryKey, VerificationResult};\n use types::connection_router::Message;\n use types::consensus::{Bootstrap, FinalityUpdate};\n use types::lightclient::LightClientState;\n use types::proofs::UpdateVariant;\n use types::ssz_rs::Node;\n use types::sync_committee_rs::constants::BlsSignature;\n\n use crate::msg::{InstantiateMsg, QueryMsg};\n\n fn deploy(bootstrap: Option\u003cBootstrap\u003e) -\u003e (App, Addr) {\n let mut app = App::default();\n\n let code = ContractWrapper::new(execute, instantiate, query);\n let code_id = app.store_code(Box::new(code));\n\n app.update_block(|block| {\n block.time = Timestamp::from_seconds(\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .unwrap()\n .as_secs(),\n );\n });\n\n let addr = app\n .instantiate_contract(\n code_id,\n Addr::unchecked(\"deployer\"),\n \u0026InstantiateMsg {\n bootstrap: bootstrap.unwrap_or(get_bootstrap()),\n config: get_config(),\n },\n \u0026[],\n \"Contract\",\n None,\n )\n .unwrap();\n\n (app, addr)\n }\n\n fn get_lc_state(app: \u0026App, addr: \u0026Addr) -\u003e LightClientState {\n app.wrap()\n .query_wasm_smart(addr, \u0026QueryMsg::LightClientState {})\n .unwrap()\n }\n\n #[test]\n fn test_initialize() {\n let (app, addr) = deploy(None);\n let env = mock_env();\n let bootstrap = get_bootstrap();\n\n let state = get_lc_state(\u0026app, \u0026addr);\n\n let mut lc = LightClient::new(\u0026get_config().chain_config, None, \u0026env);\n lc.bootstrap(\u0026bootstrap).unwrap();\n assert_eq!(state, lc.state);\n }\n\n #[test]\n fn test_light_client_update() {\n let (mut app, addr) = deploy(None);\n\n let state_before = get_lc_state(\u0026app, \u0026addr);\n let update = get_update(862);\n let resp = app.execute_contract(\n Addr::unchecked(\"owner\"),\n addr.to_owned(),\n \u0026ExecuteMsg::LightClientUpdate(update.clone()),\n \u0026[],\n );\n let state_after_862 = get_lc_state(\u0026app, \u0026addr);\n\n assert!(resp.is_ok());\n assert_eq!(\n state_after_862,\n LightClientState {\n update_slot: update.finalized_header.beacon.slot,\n next_sync_committee: Some(update.next_sync_committee), // update is from the same period as the bootstrap\n current_sync_committee: state_before.current_sync_committee\n }\n );\n\n let update = get_update(863);\n let resp = app.execute_contract(\n Addr::unchecked(\"owner\"),\n addr.to_owned(),\n \u0026ExecuteMsg::LightClientUpdate(update.clone()),\n \u0026[],\n );\n let state_after_863 = get_lc_state(\u0026app, \u0026addr);\n\n assert!(resp.is_ok());\n assert_eq!(\n state_after_863,\n LightClientState {\n update_slot: update.finalized_header.beacon.slot,\n next_sync_committee: Some(update.next_sync_committee),\n current_sync_committee: state_after_862.next_sync_committee.unwrap()\n }\n );\n }\n\n #[test]\n fn test_invalid_update() {\n let (mut app, addr) = deploy(None);\n let mut update = get_update(862);\n update.sync_aggregate.sync_committee_signature = BlsSignature::default();\n\n //Call update\n let resp = app.execute_contract(\n Addr::unchecked(\"owner\"),\n addr.to_owned(),\n \u0026ExecuteMsg::LightClientUpdate(update.clone()),\n \u0026[],\n );\n\n assert!(resp.is_err());\n }\n\n #[test]\n fn test_batch_verification_success() {\n let (bootstrap, verification_data) = get_batched_data(false, \"finality\");\n let (mut app, addr) = deploy(Some(bootstrap));\n let contents = verification_data\n .target_blocks\n .iter()\n .flat_map(|target_block| extract_content_from_block(target_block))\n .collect::\u003cVec\u003cContentVariant\u003e\u003e();\n\n let resp = app.execute_contract(\n Addr::unchecked(\"owner\"),\n addr.to_owned(),\n \u0026ExecuteMsg::BatchVerificationData(verification_data),\n \u0026[],\n );\n assert!(resp.is_ok());\n let result: VerificationResult = from_json(\u0026resp.unwrap().data.unwrap()).unwrap();\n assert_eq!(\n result,\n vec![\n (String::from(\"message:ethereum:0x9b7b2617eef7734bbb6e83464e955c205838790c1806ecf2a864d0c16395fe20:0\"), String::from(\"OK\")),\n (String::from(\"workersetmessage:0x9b7b2617eef7734bbb6e83464e955c205838790c1806ecf2a864d0c16395fe20:2\"), String::from(\"OK\")),\n (String::from(\"message:ethereum:0xabdb1b254e1f6b779611ae699e1c48188cb1f58013f51474e58a1842fe9d782e:0\"), String::from(\"OK\")),\n ]\n );\n for content in contents {\n match content {\n ContentVariant::Message(m) =\u003e {\n let res: Vec\u003c(Message, bool)\u003e = app\n .wrap()\n .query_wasm_smart(\n \u0026addr,\n \u0026QueryMsg::IsVerified {\n messages: vec![m.clone()],\n },\n )\n .unwrap();\n assert_eq!(res, vec![(m, true)]);\n }\n ContentVariant::WorkerSet(m) =\u003e {\n let res: bool = app\n .wrap()\n .query_wasm_smart(\u0026addr, \u0026QueryMsg::IsWorkerSetVerified { message: m })\n .unwrap();\n assert!(res);\n }\n };\n }\n }\n\n #[test]\n fn test_batch_verification_high_level_failure() {\n let (bootstrap, mut verification_data) = get_batched_data(false, \"finality\");\n let (mut app, addr) = deploy(Some(bootstrap));\n let contents = verification_data\n .target_blocks\n .iter_mut()\n .flat_map(|target_block| extract_content_from_block(target_block))\n .collect::\u003cVec\u003cContentVariant\u003e\u003e();\n\n // break the update\n let mut corrupt_data = verification_data.clone();\n corrupt_data.update = UpdateVariant::Finality(FinalityUpdate::default());\n\n // execute\n let resp = app.execute_contract(\n Addr::unchecked(\"owner\"),\n addr.to_owned(),\n \u0026ExecuteMsg::BatchVerificationData(corrupt_data),\n \u0026[],\n );\n\n // assert\n assert!(resp.is_err());\n for content in contents {\n match content {\n ContentVariant::Message(m) =\u003e {\n let res: Vec\u003c(Message, bool)\u003e = app\n .wrap()\n .query_wasm_smart(\n \u0026addr,\n \u0026QueryMsg::IsVerified {\n messages: vec![m.clone()],\n },\n )\n .unwrap();\n assert_eq!(res, vec![(m.clone(), false)]);\n }\n ContentVariant::WorkerSet(m) =\u003e {\n let res: bool = app\n .wrap()\n .query_wasm_smart(\n \u0026addr,\n \u0026QueryMsg::IsWorkerSetVerified { message: m.clone() },\n )\n .unwrap();\n assert_eq!(res, false);\n }\n };\n }\n }\n\n #[test]\n fn test_batch_verification_content_failure() {\n let (bootstrap, mut verification_data) = get_batched_data(false, \"finality\");\n let (mut app, addr) = deploy(Some(bootstrap));\n let contents = verification_data\n .target_blocks\n .iter_mut()\n .flat_map(|target_block| extract_content_from_block(target_block))\n .collect::\u003cVec\u003cContentVariant\u003e\u003e();\n\n // break the transaction proof of the first content\n verification_data.target_blocks[0].transactions_proofs[0]\n .transaction_proof\n .transaction_proof = vec![Node::default(); 32];\n\n // execute\n let resp = app.execute_contract(\n Addr::unchecked(\"owner\"),\n addr.to_owned(),\n \u0026ExecuteMsg::BatchVerificationData(verification_data),\n \u0026[],\n );\n\n // assert\n assert!(resp.is_ok());\n let result: VerificationResult = from_json(\u0026resp.unwrap().data.unwrap()).unwrap();\n assert_eq!(\n result,\n vec![\n (String::from(\"message:ethereum:0x9b7b2617eef7734bbb6e83464e955c205838790c1806ecf2a864d0c16395fe20:0\"), String::from(\"Invalid transaction proof\")),\n (String::from(\"workersetmessage:0x9b7b2617eef7734bbb6e83464e955c205838790c1806ecf2a864d0c16395fe20:2\"), String::from(\"Invalid transaction proof\")),\n (String::from(\"message:ethereum:0xabdb1b254e1f6b779611ae699e1c48188cb1f58013f51474e58a1842fe9d782e:0\"), String::from(\"OK\")),\n ]\n );\n for (_index, content) in contents.iter().enumerate() {\n match content {\n ContentVariant::Message(m) =\u003e {\n let res: Vec\u003c(Message, bool)\u003e = app\n .wrap()\n .query_wasm_smart(\n \u0026addr,\n \u0026QueryMsg::IsVerified {\n messages: vec![m.clone()],\n },\n )\n .unwrap();\n assert_eq!(res, vec![(m.clone(), m.key() == \"message:ethereum:0xabdb1b254e1f6b779611ae699e1c48188cb1f58013f51474e58a1842fe9d782e:0\")]);\n }\n ContentVariant::WorkerSet(m) =\u003e {\n let res: bool = app\n .wrap()\n .query_wasm_smart(\n \u0026addr,\n \u0026QueryMsg::IsWorkerSetVerified { message: m.clone() },\n )\n .unwrap();\n assert_eq!(res, m.key() != \"workersetmessage:0x9b7b2617eef7734bbb6e83464e955c205838790c1806ecf2a864d0c16395fe20:2\");\n }\n };\n }\n }\n\n #[test]\n fn test_config_query() {\n let (app, addr) = deploy(None);\n\n let config: Config = app\n .wrap()\n .query_wasm_smart(addr, \u0026QueryMsg::Config {})\n .unwrap();\n assert_eq!(config, get_config());\n }\n\n #[test]\n fn test_ownership() {\n let (mut app, addr) = deploy(None);\n\n let res: Ownership\u003cAddr\u003e = app\n .wrap()\n .query_wasm_smart(\u0026addr, \u0026QueryMsg::Ownership {})\n .unwrap();\n assert_eq!(res.owner.unwrap(), \"deployer\");\n\n // old owner proposes new owner\n let resp = app.execute_contract(\n Addr::unchecked(\"deployer\"),\n addr.to_owned(),\n \u0026ExecuteMsg::UpdateOwnership(Action::TransferOwnership {\n new_owner: String::from(\"new_owner\"),\n expiry: None,\n }),\n \u0026[],\n );\n assert!(resp.is_ok());\n\n // new owner accepts\n let resp = app.execute_contract(\n Addr::unchecked(\"new_owner\"),\n addr.to_owned(),\n \u0026ExecuteMsg::UpdateOwnership(Action::AcceptOwnership {}),\n \u0026[],\n );\n assert!(resp.is_ok());\n\n let res: Ownership\u003cAddr\u003e = app\n .wrap()\n .query_wasm_smart(\u0026addr, \u0026QueryMsg::Ownership {})\n .unwrap();\n assert_eq!(res.owner.unwrap(), \"new_owner\");\n }\n\n #[test]\n fn test_update_config() {\n let (mut app, addr) = deploy(None);\n let default_config = get_config();\n let mut new_config = get_config();\n\n let config_query: Config = app\n .wrap()\n .query_wasm_smart(\u0026addr, \u0026QueryMsg::Config {})\n .unwrap();\n assert_eq!(config_query, default_config);\n\n // random use should not be able to update config\n new_config.gateway_address = String::from(\"new_gateway\");\n assert!(app\n .execute_contract(\n Addr::unchecked(\"some_user\"),\n addr.to_owned(),\n \u0026ExecuteMsg::UpdateConfig(new_config.clone()),\n \u0026[],\n )\n .is_err());\n\n // verify that config was not updated\n let config_query: Config = app\n .wrap()\n .query_wasm_smart(\u0026addr, \u0026QueryMsg::Config {})\n .unwrap();\n assert_eq!(config_query, default_config);\n\n // owner should be able to update config\n assert!(app\n .execute_contract(\n Addr::unchecked(\"deployer\"),\n addr.to_owned(),\n \u0026ExecuteMsg::UpdateConfig(new_config.clone()),\n \u0026[],\n )\n .is_ok());\n\n // verify that config was updated\n let config_query: Config = app\n .wrap()\n .query_wasm_smart(\u0026addr, \u0026QueryMsg::Config {})\n .unwrap();\n assert_eq!(config_query, new_config.clone());\n }\n}\n","traces":[{"line":19,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":25,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":27,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":28,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":30,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":45,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":47,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":54,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":67,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":2},"fn_name":null}],"covered":45,"coverable":46},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","error.rs"],"content":"use cosmwasm_std::StdError;\nuse lightclient::error::ConsensusError;\nuse thiserror::Error;\n\nuse crate::lightclient;\n\n#[derive(Error, Debug)]\npub enum ContractError {\n #[error(\"{0}\")]\n Std(#[from] StdError),\n\n #[error(\"Unauthorized\")]\n Unauthorized {},\n\n #[error(\"Update for this period already exists\")]\n UpdateAlreadyExists {},\n\n #[error(\"No sync committee for this period\")]\n NoSyncCommittee { period: u64 },\n\n #[error(\"Consensus error: {:?}\", error)]\n ConsensusError { error: ConsensusError },\n\n #[error(\"Invalid BlockRoots proof\")]\n InvalidBlockRootsProof,\n\n #[error(\"Invalid Block Summary Root proof\")]\n InvalidBlockSummaryRootProof,\n\n #[error(\"Invalid BlockRoots branch\")]\n InvalidBlockRootsBranch,\n\n #[error(\"Invalid receipt proof\")]\n InvalidReceiptProof,\n\n #[error(\"Invalid receipts_root proof\")]\n InvalidReceiptsBranchProof,\n\n #[error(\"Invalid transaction proof\")]\n InvalidTransactionProof,\n\n #[error(\"Invalid verification data\")]\n InvalidVerificationData,\n\n #[error(\"Invalid message\")]\n InvalidMessage,\n // Add any other custom errors you like here.\n // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details.\n}\n\n// Wrap consensus error to contract error\nimpl From\u003cConsensusError\u003e for ContractError {\n fn from(err: ConsensusError) -\u003e Self {\n ContractError::Std(StdError::GenericErr {\n msg: err.to_string(),\n })\n }\n}\n","traces":[{"line":53,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":54,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":1},"fn_name":null}],"covered":3,"coverable":3},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","execute.rs"],"content":"use crate::ContractError;\nuse cosmwasm_std::{DepsMut, Env, Response};\nuse eyre::{eyre, Result};\nuse hasher::{Hasher, HasherKeccak};\nuse types::common::{Config, FinalizationVariant, PrimaryKey};\nuse types::consensus::BeaconBlockHeader;\nuse types::execution::ReceiptLogs;\nuse types::proofs::{\n BatchVerificationData, BlockProofsBatch, TransactionProofsBatch, UpdateVariant,\n};\nuse types::ssz_rs::{Merkleized, Node};\nuse types::sync_committee_rs::consensus_types::Transaction;\nuse types::sync_committee_rs::constants::MAX_BYTES_PER_TRANSACTION;\nuse types::{common::ContentVariant, consensus::Update};\n\nuse crate::lightclient::helpers::{\n compare_content_with_log, extract_logs_from_receipt_proof, parse_message_id,\n verify_ancestry_proof, verify_transaction_proof, LowerCaseFields,\n};\nuse crate::lightclient::LightClient;\nuse crate::state::{CONFIG, LIGHT_CLIENT_STATE, VERIFIED_MESSAGES, VERIFIED_WORKER_SETS};\n\n/// Finds the necessary log from a list of logs and then verifies the provided content.\nfn verify_content(\n content: ContentVariant,\n transaction: \u0026Transaction\u003cMAX_BYTES_PER_TRANSACTION\u003e,\n logs: \u0026ReceiptLogs,\n gateway_address: \u0026str,\n) -\u003e Result\u003c()\u003e {\n let gateway_address = hex::decode(\n gateway_address\n .strip_prefix(\"0x\")\n .ok_or_else(|| eyre!(\"Invalid gateway address in .env\"))?,\n )?;\n let hasher = HasherKeccak::new();\n let transaction_hash = hex::encode(hasher.digest(transaction.as_slice()));\n let (message_tx_hash, log_index) = match \u0026content {\n ContentVariant::Message(message) =\u003e parse_message_id(\u0026message.cc_id.id),\n ContentVariant::WorkerSet(message) =\u003e parse_message_id(\u0026message.message_id),\n }?;\n\n if message_tx_hash != transaction_hash {\n return Err(eyre!(\"Invalid content transaction hash\"));\n }\n\n let log = logs\n .0\n .get(log_index)\n .ok_or_else(|| eyre!(\"Log index out of bounds\"))?;\n\n if gateway_address != log.address {\n return Err(eyre!(\"Invalid log address\"));\n }\n\n compare_content_with_log(content, log)\n}\n\n/// Verifies that the transaction and the receipt are included in the target block, and then verifies the messages for the given transaction.\nfn process_transaction_proofs(\n data: \u0026TransactionProofsBatch,\n target_block_root: \u0026Node,\n gateway_address: \u0026str,\n) -\u003e Vec\u003c(ContentVariant, Result\u003c()\u003e)\u003e {\n let result = verify_transaction_proof(\u0026data.transaction_proof, target_block_root)\n .and_then(|_| {\n extract_logs_from_receipt_proof(\n \u0026data.receipt_proof,\n data.transaction_proof.transaction_index,\n target_block_root,\n )\n })\n .map(|logs| {\n data.content\n .iter()\n .map(|content_variant| {\n (\n content_variant.to_owned(),\n verify_content(\n content_variant.clone(),\n \u0026data.transaction_proof.transaction,\n \u0026logs,\n gateway_address,\n ),\n )\n })\n .collect::\u003cVec\u003c(ContentVariant, Result\u003c()\u003e)\u003e\u003e()\n });\n\n result.unwrap_or_else(|err| {\n data.content\n .iter()\n .map(|content_variant| (content_variant.to_owned(), Err(eyre!(err.to_string()))))\n .collect()\n })\n}\n\n/// Verifies that a target block is an ancestor of the given recent block, and then proceeds with the verification of the transactions inside the target block.\nfn process_block_proofs(\n recent_block: \u0026BeaconBlockHeader,\n data: \u0026BlockProofsBatch,\n gateway_address: \u0026str,\n) -\u003e Vec\u003c(ContentVariant, Result\u003c()\u003e)\u003e {\n let transactions_proofs = \u0026data.transactions_proofs;\n\n let mut target_block_root = Node::default();\n let mut ancestry_proof_verification = || -\u003e Result\u003c()\u003e {\n target_block_root = data.target_block.clone().hash_tree_root()?;\n\n verify_ancestry_proof(\u0026data.ancestry_proof, \u0026data.target_block, recent_block)?;\n Ok(())\n };\n\n match ancestry_proof_verification() {\n Ok(_) =\u003e transactions_proofs\n .iter()\n .flat_map(|proof| {\n process_transaction_proofs(proof, \u0026target_block_root, gateway_address)\n })\n .collect(),\n Err(err) =\u003e transactions_proofs\n .iter()\n .flat_map(|proof| {\n proof\n .content\n .iter()\n .map(|content_variant| (content_variant.clone(), Err(eyre!(err.to_string()))))\n })\n .collect(),\n }\n}\n\n/// Processes a complete verification request\npub fn process_batch_data(\n deps: DepsMut,\n lightclient: \u0026LightClient,\n data: \u0026BatchVerificationData,\n config: \u0026Config,\n) -\u003e Result\u003cVec\u003c(ContentVariant, Result\u003c()\u003e)\u003e\u003e {\n match \u0026data.update {\n UpdateVariant::Finality(update) =\u003e {\n if matches!(config.finalization, FinalizationVariant::Optimistic()) {\n return Err(eyre!(\n \"Optimistic verification enabled but provided Finality update\"\n ));\n }\n lightclient.verify_finality_update(update)?\n }\n UpdateVariant::Optimistic(update) =\u003e {\n if matches!(config.finalization, FinalizationVariant::Finality()) {\n return Err(eyre!(\n \"Finality verification enabled but provided Optimistic update\"\n ));\n }\n lightclient.verify_optimistic_update(update)?\n }\n }\n let recent_block = data.update.recent_block();\n let gateway_address = config.gateway_address.clone();\n\n let results = data\n .target_blocks\n .iter()\n .flat_map(|block_proofs_batch| {\n process_block_proofs(\u0026recent_block, block_proofs_batch, \u0026gateway_address)\n })\n .collect::\u003cVec\u003c(ContentVariant, Result\u003c()\u003e)\u003e\u003e();\n\n for content_variant_result in results.iter() {\n if content_variant_result.1.is_ok() {\n match \u0026content_variant_result.0 {\n ContentVariant::Message(message) =\u003e {\n VERIFIED_MESSAGES.save(deps.storage, message.to_lowercase().hash(), message)?\n }\n ContentVariant::WorkerSet(message) =\u003e VERIFIED_WORKER_SETS.save(\n deps.storage,\n message.to_lowercase().key(),\n message,\n )?,\n }\n }\n }\n\n Ok(results)\n}\n\npub fn light_client_update(\n deps: DepsMut,\n env: \u0026Env,\n update: Update,\n) -\u003e Result\u003cResponse, ContractError\u003e {\n let state = LIGHT_CLIENT_STATE.load(deps.storage)?;\n let config = CONFIG.load(deps.storage)?;\n let mut lc = LightClient::new(\u0026config.chain_config, Some(state), env);\n\n let res = lc.apply_update(\u0026update);\n if res.is_err() {\n return Err(ContractError::from(res.err().unwrap()));\n }\n\n LIGHT_CLIENT_STATE.save(deps.storage, \u0026lc.state)?;\n\n Ok(Response::new())\n}\n\n#[cfg(test)]\npub mod tests {\n use crate::execute::{process_block_proofs, process_transaction_proofs, verify_content};\n use crate::lightclient::helpers::test_helpers::{\n filter_message_variants, filter_workerset_variants, get_batched_data, get_config,\n get_gindex_overflow_data,\n };\n use crate::lightclient::helpers::{\n extract_logs_from_receipt_proof, parse_message_id, LowerCaseFields,\n };\n use crate::lightclient::tests::tests::init_lightclient;\n use crate::state::{CONFIG, VERIFIED_MESSAGES, VERIFIED_WORKER_SETS};\n use cosmwasm_std::testing::mock_dependencies;\n use eyre::Result;\n use types::alloy_primitives::Address;\n use types::common::{ContentVariant, FinalizationVariant, PrimaryKey, WorkerSetMessage};\n use types::consensus::{FinalityUpdate, OptimisticUpdate};\n use types::execution::{ReceiptLog, ReceiptLogs};\n use types::proofs::{\n AncestryProof, BlockProofsBatch, Message, TransactionProofsBatch, UpdateVariant,\n };\n use types::ssz_rs::{Merkleized, Node};\n\n use super::process_batch_data;\n\n fn filter_variants_as_mutref(\n proofs_batch: \u0026mut TransactionProofsBatch,\n ) -\u003e (Vec\u003c\u0026mut WorkerSetMessage\u003e, Vec\u003c\u0026mut Message\u003e) {\n let mut workerset_messages: Vec\u003c\u0026mut WorkerSetMessage\u003e = vec![];\n let mut messages: Vec\u003c\u0026mut Message\u003e = vec![];\n\n for content in proofs_batch.content.iter_mut() {\n match content {\n ContentVariant::WorkerSet(m) =\u003e workerset_messages.push(m),\n ContentVariant::Message(m) =\u003e messages.push(m),\n }\n }\n (workerset_messages, messages)\n }\n\n #[test]\n fn test_verify_content_failures() {\n let gateway_address = String::from(\"0xAba4D993188008F665C972d79fc59AB2381eCe94\");\n let verification_data = get_batched_data(false, \"finality\").1;\n let target_block_proofs = verification_data.target_blocks.get(0).unwrap();\n let proofs = target_block_proofs.transactions_proofs.get(0).unwrap();\n\n let messages = filter_message_variants(proofs);\n\n let mut message = messages.get(0).unwrap().clone();\n message.cc_id.id = String::from(\"broken_id\").try_into().unwrap();\n assert_eq!(\n verify_content(\n ContentVariant::Message(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026ReceiptLogs::default(),\n \u0026gateway_address\n )\n .unwrap_err()\n .to_string(),\n \"Invalid message id format\"\n );\n\n message.cc_id.id = String::from(\"foo:bar\").try_into().unwrap();\n assert_eq!(\n verify_content(\n ContentVariant::Message(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026ReceiptLogs::default(),\n \u0026gateway_address,\n )\n .unwrap_err()\n .to_string(),\n \"Invalid transaction hash in message id\"\n );\n\n message = messages.get(0).unwrap().clone();\n assert_eq!(\n verify_content(\n ContentVariant::Message(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026ReceiptLogs::default(),\n \u0026gateway_address,\n )\n .unwrap_err()\n .to_string(),\n \"Log index out of bounds\"\n );\n\n message = messages.get(0).unwrap().clone();\n message.cc_id.id =\n String::from(\"0xa92d426734f1f7054b89a68b2a71f2f19f8150716bf046c59a3cd819413afd13:0\")\n .try_into()\n .unwrap();\n assert_eq!(\n verify_content(\n ContentVariant::Message(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026ReceiptLogs::default(),\n \u0026gateway_address,\n )\n .unwrap_err()\n .to_string(),\n \"Invalid content transaction hash\"\n );\n\n message = messages.get(0).unwrap().clone();\n message.cc_id.id = String::from(format!(\n \"{}:0\",\n parse_message_id(\u0026message.cc_id.id).unwrap().0\n ))\n .try_into()\n .unwrap();\n let mut logs = extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026target_block_proofs\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap(),\n )\n .unwrap();\n logs.0[0].address = Address::ZERO.to_vec().try_into().unwrap();\n assert_eq!(\n verify_content(\n ContentVariant::Message(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026logs,\n \u0026gateway_address,\n )\n .unwrap_err()\n .to_string(),\n \"Invalid log address\"\n );\n }\n\n #[test]\n fn test_verify_message() {\n let gateway_address = String::from(\"0xAba4D993188008F665C972d79fc59AB2381eCe94\");\n let verification_data = get_batched_data(false, \"finality\").1;\n let target_block_proofs = verification_data.target_blocks.get(0).unwrap();\n let proofs = target_block_proofs.transactions_proofs.get(0).unwrap();\n\n let messages = filter_message_variants(proofs);\n\n let message = messages.get(0).unwrap().clone();\n let logs = extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026target_block_proofs\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap(),\n )\n .unwrap();\n // valid comparison\n assert!(verify_content(\n ContentVariant::Message(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026logs,\n \u0026gateway_address\n )\n .is_ok());\n\n let logs = ReceiptLogs(vec![ReceiptLog::default()]);\n // invalid comparison\n assert!(verify_content(\n ContentVariant::Message(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026logs,\n \u0026gateway_address,\n )\n .is_err());\n }\n\n #[test]\n fn test_verify_workerset_message() {\n let gateway_address = String::from(\"0xAba4D993188008F665C972d79fc59AB2381eCe94\");\n let verification_data = get_batched_data(false, \"finality\").1;\n let target_block_proofs = verification_data.target_blocks.get(0).unwrap();\n let proofs = target_block_proofs.transactions_proofs.get(0).unwrap();\n\n let messages = filter_workerset_variants(proofs);\n\n let message = messages.get(0).unwrap().clone();\n let logs = extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026target_block_proofs\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap(),\n )\n .unwrap();\n // valid comparison\n assert!(verify_content(\n ContentVariant::WorkerSet(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026logs,\n \u0026gateway_address\n )\n .is_ok());\n\n let logs = ReceiptLogs(vec![ReceiptLog::default()]);\n // invalid comparison\n assert!(verify_content(\n ContentVariant::WorkerSet(message.clone()).clone(),\n \u0026proofs.transaction_proof.transaction,\n \u0026logs,\n \u0026gateway_address,\n )\n .is_err());\n }\n\n #[test]\n fn test_process_transaction_proofs() {\n let gateway_address = String::from(\"0xAba4D993188008F665C972d79fc59AB2381eCe94\");\n let data = get_batched_data(false, \"finality\").1;\n\n let block_proofs = data\n .target_blocks\n .get(0)\n .expect(\"No block proofs available\");\n let transaction_proofs = block_proofs\n .transactions_proofs\n .get(0)\n .expect(\"No transaction proofs available\")\n .clone();\n let content = transaction_proofs.content.clone();\n\n let target_block_root = block_proofs.target_block.clone().hash_tree_root().unwrap();\n\n let res =\n process_transaction_proofs(\u0026transaction_proofs, \u0026target_block_root, \u0026gateway_address);\n assert_valid_contents(\u0026content, \u0026res);\n\n // test error from content\n let mut corrupted_proofs = transaction_proofs.clone();\n let (workerset_messages, messages) = filter_variants_as_mutref(\u0026mut corrupted_proofs);\n for message in messages {\n message.cc_id.id = \"invalid\".to_string().try_into().unwrap();\n }\n for message in workerset_messages {\n message.message_id = \"invalid\".to_string().try_into().unwrap();\n }\n let res =\n process_transaction_proofs(\u0026corrupted_proofs, \u0026target_block_root, \u0026gateway_address);\n assert_invalid_contents(\u0026corrupted_proofs.content, \u0026res);\n\n // test error in transaction proof\n let mut corrupted_proofs = transaction_proofs.clone();\n corrupted_proofs.transaction_proof.transaction_proof = vec![Node::default(); 32];\n let res =\n process_transaction_proofs(\u0026corrupted_proofs, \u0026target_block_root, \u0026gateway_address);\n assert_invalid_contents(\u0026extract_content_from_block(block_proofs), \u0026res);\n }\n\n pub fn extract_content_from_block(target_block: \u0026BlockProofsBatch) -\u003e Vec\u003cContentVariant\u003e {\n target_block\n .transactions_proofs\n .iter()\n .flat_map(|transaction_proofs| transaction_proofs.content.clone())\n .collect()\n }\n\n fn assert_valid_contents(contents: \u0026[ContentVariant], res: \u0026Vec\u003c(ContentVariant, Result\u003c()\u003e)\u003e) {\n assert!(res.len() \u003e 0);\n assert_eq!(res.len(), contents.len());\n for (index, content_variant) in contents.iter().enumerate() {\n assert!(res[index].1.is_ok());\n assert_eq!(res[index].0, *content_variant);\n }\n }\n\n fn corrupt_contents(target_block: \u0026mut BlockProofsBatch) {\n for tx in target_block.transactions_proofs.iter_mut() {\n let (mut workerset_messaages, mut messages) = filter_variants_as_mutref(tx);\n for message in messages.iter_mut() {\n message.cc_id.id = \"invalid\".to_string().try_into().unwrap();\n }\n\n for message in workerset_messaages.iter_mut() {\n message.message_id = \"invalid\".to_string().try_into().unwrap();\n }\n }\n }\n\n fn assert_invalid_contents(\n contents: \u0026[ContentVariant],\n res: \u0026Vec\u003c(ContentVariant, Result\u003c()\u003e)\u003e,\n ) {\n assert!(res.len() \u003e 0);\n assert_eq!(res.len(), contents.len());\n for (index, content_variant) in contents.iter().enumerate() {\n assert!(res[index].1.is_err());\n assert_eq!(res[index].0, *content_variant);\n }\n }\n\n #[test]\n fn test_process_block_proofs() {\n let gateway_address = String::from(\"0xAba4D993188008F665C972d79fc59AB2381eCe94\");\n let mut data = get_batched_data(false, \"finality\").1;\n let recent_block = data.update.recent_block();\n\n for target_block in data.target_blocks.iter_mut() {\n let content = extract_content_from_block(target_block);\n\n let res = process_block_proofs(\u0026recent_block, target_block, \u0026gateway_address);\n assert_valid_contents(\u0026content, \u0026res);\n\n corrupt_contents(target_block);\n let contents = extract_content_from_block(target_block);\n let res = process_block_proofs(\u0026recent_block, target_block, \u0026gateway_address);\n assert_invalid_contents(\u0026contents, \u0026res);\n }\n\n // test error on ancestry proof\n for target_block in data.target_blocks.iter_mut() {\n let contents = extract_content_from_block(\u0026target_block.clone());\n\n let AncestryProof::BlockRoots {\n block_root_proof,\n block_roots_index: _,\n } = \u0026mut target_block.ancestry_proof\n else {\n panic!(\"\")\n };\n *block_root_proof = vec![Node::default(); 18];\n let res = process_block_proofs(\u0026recent_block, target_block, \u0026gateway_address);\n assert_invalid_contents(\u0026contents, \u0026res);\n }\n }\n\n #[test]\n fn test_process_batch_data() {\n for historical in [true, false] {\n for finalization in [\"finality\", \"optimistic\"] {\n let (bootstrap, mut data) = get_batched_data(historical, finalization);\n let lc = init_lightclient(Some(bootstrap));\n let mut config = get_config();\n config.finalization = match finalization {\n \"finality\" =\u003e FinalizationVariant::Finality(),\n \"optimistic\" =\u003e FinalizationVariant::Optimistic(),\n _ =\u003e config.finalization,\n };\n let mut deps = mock_dependencies();\n CONFIG.save(\u0026mut deps.storage, \u0026get_config()).unwrap();\n\n let res = process_batch_data(deps.as_mut(), \u0026lc, \u0026data, \u0026config);\n let contents = data\n .target_blocks\n .iter()\n .flat_map(|target_block| extract_content_from_block(target_block))\n .collect::\u003cVec\u003cContentVariant\u003e\u003e();\n\n println!(\"{}, {}\", historical, finalization);\n assert!(res.is_ok());\n assert_valid_contents(\u0026contents, \u0026res.unwrap());\n for content in contents {\n match content {\n ContentVariant::Message(m) =\u003e {\n assert!(VERIFIED_MESSAGES\n .load(\u0026mut deps.storage, m.to_lowercase().hash())\n .is_ok());\n }\n ContentVariant::WorkerSet(m) =\u003e {\n assert!(VERIFIED_WORKER_SETS\n .load(\u0026mut deps.storage, m.key())\n .is_ok());\n }\n }\n }\n\n // finalization type of update is different than config\n let mut corrupt_config = config.clone();\n corrupt_config.finalization = match finalization {\n \"finality\" =\u003e FinalizationVariant::Optimistic(),\n \"optimistic\" =\u003e FinalizationVariant::Finality(),\n _ =\u003e corrupt_config.finalization,\n };\n assert!(process_batch_data(deps.as_mut(), \u0026lc, \u0026data, \u0026corrupt_config).is_err());\n\n // Corrupt the update\n let mut corrupt_data = data.clone();\n match finalization {\n \"finality\" =\u003e {\n corrupt_data.update = UpdateVariant::Finality(FinalityUpdate::default())\n }\n \"optimistic\" =\u003e {\n corrupt_data.update = UpdateVariant::Optimistic(OptimisticUpdate::default())\n }\n _ =\u003e panic!(\"Unknown finalization\"),\n };\n assert!(process_batch_data(deps.as_mut(), \u0026lc, \u0026corrupt_data, \u0026config).is_err(),);\n\n // Corrupt the contents\n for target_block in data.target_blocks.iter_mut() {\n corrupt_contents(target_block);\n }\n let contents = data\n .target_blocks\n .iter()\n .flat_map(|target_block| extract_content_from_block(target_block))\n .collect::\u003cVec\u003cContentVariant\u003e\u003e();\n let res = process_batch_data(deps.as_mut(), \u0026lc, \u0026data, \u0026config);\n assert!(res.is_ok());\n assert_invalid_contents(\u0026contents, \u0026res.unwrap());\n }\n }\n }\n\n #[test]\n fn generalized_index_overflow() {\n let data = get_gindex_overflow_data();\n let recent_block = data.update.recent_block();\n\n let gateway_address = String::from(\"0x557b0dc16d07cb297412a7da40a48615f7559765\");\n let results = data\n .target_blocks\n .iter()\n .flat_map(|block_proofs_batch| {\n process_block_proofs(\u0026recent_block, block_proofs_batch, \u0026gateway_address)\n })\n .collect::\u003cVec\u003c(ContentVariant, Result\u003c()\u003e)\u003e\u003e();\n\n for content_result in results {\n assert!(content_result.1.is_ok());\n }\n }\n}\n","traces":[{"line":24,"address":[],"length":0,"stats":{"Line":52},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":52},"fn_name":null},{"line":32,"address":[],"length":0,"stats":{"Line":52},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":104},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":52},"fn_name":null},{"line":36,"address":[],"length":0,"stats":{"Line":52},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":83},"fn_name":null},{"line":38,"address":[],"length":0,"stats":{"Line":33},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":46},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":67,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":46},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":65},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":89,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":46},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":126,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":139,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":141,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":142,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":143,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":146,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":148,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":149,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":150,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":151,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":154,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":157,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":158,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":160,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":161,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":163,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":164,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":168,"address":[],"length":0,"stats":{"Line":34},"fn_name":null},{"line":169,"address":[],"length":0,"stats":{"Line":34},"fn_name":null},{"line":170,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":171,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":172,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":174,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":175,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":176,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":177,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":183,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":186,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":191,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":192,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":197,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":200,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":202,"address":[],"length":0,"stats":{"Line":2},"fn_name":null}],"covered":89,"coverable":89},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","lib.rs"],"content":"pub mod contract;\npub mod error;\npub mod execute;\npub mod lightclient;\npub mod msg;\npub mod state;\n\npub use crate::error::ContractError;\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","lightclient","error.rs"],"content":"use thiserror::Error;\n\n#[derive(Error, Debug)]\npub enum ConsensusError {\n #[error(\"insufficient participation\")]\n InsufficientParticipation,\n #[error(\"invalid timestamp\")]\n InvalidTimestamp,\n #[error(\"invalid sync committee period\")]\n InvalidPeriod,\n #[error(\"update not relevant\")]\n NotRelevant,\n #[error(\"invalid finality proof\")]\n InvalidFinalityProof,\n #[error(\"invalid next sync committee proof\")]\n InvalidNextSyncCommitteeProof,\n #[error(\"invalid current sync committee proof\")]\n InvalidCurrentSyncCommitteeProof,\n #[error(\"invalid sync committee signature\")]\n InvalidSignature,\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","lightclient","helpers.rs"],"content":"use std::sync::Arc;\n\nuse alloy_dyn_abi::EventExt;\nuse alloy_json_abi::{AbiItem, JsonAbi};\nuse cita_trie::{MemoryDB, PatriciaTrie, Trie};\nuse eyre::{eyre, Result};\nuse types::alloy_primitives::{Bytes, FixedBytes, Log};\n\nuse crate::ContractError;\nuse hasher::{Hasher, HasherKeccak};\nuse types::alloy_rlp::encode;\nuse types::common::{ContentVariant, WorkerSetMessage};\nuse types::connection_router::state::{Message, ID_SEPARATOR};\nuse types::execution::{ContractCallBase, ReceiptLog};\nuse types::execution::{GatewayEvent, OperatorshipTransferredBase, ReceiptLogs};\nuse types::proofs::{nonempty, AncestryProof, CrossChainId, ReceiptProof, TransactionProof};\nuse types::ssz_rs::{\n get_generalized_index, is_valid_merkle_branch, verify_merkle_proof, GeneralizedIndex64,\n Merkleized, Node, SszVariableOrIndex, Vector,\n};\nuse types::sync_committee_rs::consensus_types::BeaconBlockHeader;\nuse types::sync_committee_rs::constants::{Bytes32, Root, SLOTS_PER_HISTORICAL_ROOT};\n\n/// Trait implemented from messages to compare with the appropriate event\npub trait Comparison\u003cE\u003e {\n fn compare_with_event(\u0026self, event: E) -\u003e Result\u003c()\u003e;\n}\n\n/// Trait implemented from messages to convert string fields to lowercase\npub trait LowerCaseFields: Sized {\n fn to_lowercase(\u0026self) -\u003e Self;\n}\n\npub fn is_proof_valid\u003cL: Merkleized\u003e(\n state_root: \u0026Node,\n leaf_object: \u0026mut L,\n branch: \u0026[Bytes32],\n depth: usize,\n index: usize,\n) -\u003e bool {\n let res: Result\u003cbool\u003e = (move || {\n let leaf_hash = leaf_object.hash_tree_root()?;\n let branch = branch_to_nodes(branch.to_vec())?;\n\n let is_valid = is_valid_merkle_branch(\u0026leaf_hash, branch.iter(), depth, index, state_root);\n Ok(is_valid)\n })();\n\n if let Ok(is_valid) = res {\n is_valid\n } else {\n false\n }\n}\n\n/// Verifies an MPT proof. Used to verify that a receipt is included in the receipts MPT.\npub fn verify_trie_proof(root: Root, key: u64, proof: Vec\u003cVec\u003cu8\u003e\u003e) -\u003e Option\u003cVec\u003cu8\u003e\u003e {\n let memdb = Arc::new(MemoryDB::new(true));\n let hasher = Arc::new(HasherKeccak::new());\n\n let trie = PatriciaTrie::new(Arc::clone(\u0026memdb), Arc::clone(\u0026hasher));\n if let Ok(res) = trie.verify_proof(root.as_bytes(), \u0026encode(key), proof) {\n return res;\n }\n None\n}\n\n/// Verifies that the target block is an ancestor of the recent block. It will use either a historical proof or a block roots proof, depending on how old the target block is.\npub fn verify_ancestry_proof(\n proof: \u0026AncestryProof,\n target_block: \u0026BeaconBlockHeader,\n recent_block: \u0026BeaconBlockHeader,\n) -\u003e Result\u003c()\u003e {\n let target_block_root = target_block.clone().hash_tree_root()?;\n\n match proof {\n AncestryProof::BlockRoots {\n block_roots_index,\n block_root_proof,\n } =\u003e verify_block_roots_proof(\n block_roots_index,\n block_root_proof,\n \u0026target_block_root,\n \u0026recent_block.state_root,\n ),\n AncestryProof::HistoricalRoots {\n block_root_proof,\n block_summary_root_proof,\n block_summary_root,\n block_summary_root_gindex,\n } =\u003e verify_historical_roots_proof(\n block_root_proof,\n block_summary_root_proof,\n block_summary_root,\n block_summary_root_gindex,\n target_block,\n \u0026recent_block.state_root,\n ),\n }\n}\n\npub fn verify_block_roots_proof(\n block_roots_index: \u0026u64,\n block_root_proof: \u0026Vec\u003cNode\u003e,\n leaf_root: \u0026Node,\n root: \u0026Node,\n) -\u003e Result\u003c()\u003e {\n if !verify_merkle_proof(\n leaf_root,\n block_root_proof.as_slice(),\n \u0026GeneralizedIndex64(*block_roots_index),\n root,\n ) {\n return Err(ContractError::InvalidBlockRootsProof.into());\n }\n Ok(())\n}\n\npub fn verify_historical_roots_proof(\n block_root_proof: \u0026Vec\u003cNode\u003e,\n block_summary_root_proof: \u0026Vec\u003cNode\u003e,\n block_summary_root: \u0026Root,\n block_summary_root_gindex: \u0026u64,\n target_block: \u0026BeaconBlockHeader,\n recent_block_state_root: \u0026Node,\n) -\u003e Result\u003c()\u003e {\n let target_block_root = target_block.clone().hash_tree_root()?;\n\n let block_root_index = target_block.slot as usize % SLOTS_PER_HISTORICAL_ROOT;\n let block_root_gindex = get_generalized_index(\n // TODO: check if it overflows, maybe use u64\n \u0026Vector::\u003cNode, SLOTS_PER_HISTORICAL_ROOT\u003e::default(),\n \u0026[SszVariableOrIndex::Index(block_root_index)],\n );\n\n verify_block_roots_proof(\n \u0026(block_root_gindex as u64),\n block_root_proof,\n \u0026target_block_root,\n block_summary_root,\n )?;\n\n let valid_block_summary_root_proof = verify_merkle_proof(\n block_summary_root,\n block_summary_root_proof.as_slice(),\n \u0026GeneralizedIndex64(*block_summary_root_gindex),\n recent_block_state_root,\n );\n\n if !valid_block_summary_root_proof {\n return Err(ContractError::InvalidBlockSummaryRootProof.into());\n }\n Ok(())\n}\n\n/// Extracts the logs from a receipt after checking that this receipt is part of the target block.\npub fn extract_logs_from_receipt_proof(\n proof: \u0026ReceiptProof,\n transaction_index: u64,\n target_block_root: \u0026Node,\n) -\u003e Result\u003cReceiptLogs\u003e {\n if !verify_merkle_proof(\n \u0026proof.receipts_root,\n \u0026proof.receipts_root_proof,\n \u0026GeneralizedIndex64(proof.receipts_root_gindex),\n target_block_root,\n ) {\n return Err(ContractError::InvalidReceiptsBranchProof.into());\n }\n\n let receipt_option = verify_trie_proof(\n proof.receipts_root,\n transaction_index,\n proof.receipt_proof.clone(),\n );\n\n let receipt = match receipt_option {\n Some(s) =\u003e s,\n None =\u003e return Err(ContractError::InvalidReceiptProof.into()),\n };\n\n parse_logs_from_receipt(\u0026receipt)\n}\n\n/// Parse logs from a given encoded receipt.\npub fn parse_logs_from_receipt(receipt: \u0026[u8]) -\u003e Result\u003cReceiptLogs\u003e {\n let logs: ReceiptLogs = types::alloy_rlp::Decodable::decode(\u0026mut \u0026receipt[..])?;\n Ok(logs)\n}\n\n/// Verify that the transaction is included in the block.\npub fn verify_transaction_proof(proof: \u0026TransactionProof, target_block_root: \u0026Node) -\u003e Result\u003c()\u003e {\n if !verify_merkle_proof(\n \u0026proof.transaction.clone().hash_tree_root()?,\n proof.transaction_proof.as_slice(),\n \u0026GeneralizedIndex64(proof.transaction_gindex),\n target_block_root,\n ) {\n return Err(ContractError::InvalidTransactionProof.into());\n }\n Ok(())\n}\n\n/// Given a receipt log and an event, it parses the log into a ContractCall event struct.\npub fn parse_contract_call_event(\n log: \u0026ReceiptLog,\n e: \u0026alloy_json_abi::Event,\n) -\u003e Result\u003cGatewayEvent\u003e {\n // let AbiItem::Event(e) = event_item;\n let topics: Vec\u003cFixedBytes\u003c32\u003e\u003e = log.topics.iter().map(FixedBytes::from).collect();\n let alloy_log = Log::new(topics, Bytes::from(log.data.clone()))\n .ok_or_else(|| eyre!(\"Failed to create log\"))?;\n let decoded = e.decode_log(\u0026alloy_log, true)?;\n\n let mut indexed_consumed = 0;\n let mut base = ContractCallBase::default();\n for (idx, param) in e.inputs.iter().enumerate() {\n let value = if param.indexed {\n decoded.indexed.get(indexed_consumed).cloned()\n } else {\n decoded.body.get(idx - indexed_consumed).cloned()\n };\n\n if let Some(value) = value {\n match param.name.as_str() {\n \"sender\" =\u003e {\n let parsed_value = value\n .as_address()\n .ok_or_else(|| eyre!(\"Can't parse 'sender' from topics\"))?;\n base.source_address = Some(parsed_value);\n }\n \"destinationChain\" =\u003e {\n let parsed_value = value\n .as_str()\n .ok_or_else(|| eyre!(\"Can't parse 'destinationChain' from topics\"))?;\n base.destination_chain = Some(parsed_value.to_string());\n }\n \"destinationContractAddress\" =\u003e {\n let parsed_value = value.as_str().ok_or_else(|| {\n eyre!(\"Can't parse 'destinationContractAddress' from topics\")\n })?;\n base.destination_address = Some(parsed_value.to_string());\n }\n \"payloadHash\" =\u003e {\n let (payload_bytes, _) = value\n .as_fixed_bytes()\n .ok_or_else(|| eyre!(\"Can't parse 'payloadHash' from topics\"))?;\n let payload: [u8; 32] = payload_bytes.try_into()?;\n base.payload_hash = Some(payload);\n }\n _ =\u003e {}\n }\n }\n\n if param.indexed {\n indexed_consumed += 1\n }\n }\n Ok(GatewayEvent::ContactCall(base))\n}\n\n/// Given a receipt log and an event, it parses the log into an OperatorshipTransferred event struct.\npub fn parse_operatorship_transferred_event(\n log: \u0026ReceiptLog,\n e: \u0026alloy_json_abi::Event,\n) -\u003e Result\u003cGatewayEvent\u003e {\n let topics: Vec\u003cFixedBytes\u003c32\u003e\u003e = log.topics.iter().map(FixedBytes::from).collect();\n let alloy_log = Log::new(topics, Bytes::from(log.data.clone()))\n .ok_or_else(|| eyre!(\"Failed to create log\"))?;\n let decoded = e.decode_log(\u0026alloy_log, true)?;\n let new_operators_data = decoded\n .body\n .first()\n .and_then(|value| value.as_bytes())\n .ok_or_else(|| eyre!(\"Can't parse 'newOperatorsData' from topics\"))?;\n\n Ok(GatewayEvent::OperatorshipTransferred(\n OperatorshipTransferredBase {\n new_operators_data: Some(hex::encode(new_operators_data)),\n },\n ))\n}\n\n/// Parses a log into either a ContractCall or OperatorshipTransferred event struct.\npub fn parse_log(log: \u0026ReceiptLog) -\u003e Result\u003cGatewayEvent\u003e {\n let abi = JsonAbi::parse([\n \"event ContractCall(address indexed sender,string destinationChain,string destinationContractAddress,bytes32 indexed payloadHash,bytes payload)\",\n \"event ContractCallWithToken(address indexed sender,string destinationChain,string destinationContractAddress,bytes32 indexed payloadHash,bytes payload,string symbol,uint256 amount)\",\n \"event OperatorshipTransferred(bytes newOperatorsData)\"]).map_err(|err| eyre!(\"Failed to parse ABI: {}\", err))?;\n let hasher = HasherKeccak::new();\n let first_topic = log\n .topics\n .first()\n .ok_or_else(|| eyre!(\"No topics in log\"))?;\n\n for item in abi.items() {\n if let AbiItem::Event(e) = item {\n let event_signature_hash = hasher.digest(e.signature().as_bytes());\n if first_topic == event_signature_hash.as_slice() {\n if e.signature().starts_with(\"ContractCall\") {\n return parse_contract_call_event(log, \u0026e);\n } else if e.signature().starts_with(\"OperatorshipTransferred\") {\n return parse_operatorship_transferred_event(log, \u0026e);\n }\n }\n }\n }\n Err(eyre!(\"Couldn't match an event to decode the log\"))\n}\n\n/// Parses a message id of the format \"\u003ctransaction hash\u003e:\u003crelative log index\u003e\".\npub fn parse_message_id(id: \u0026nonempty::String) -\u003e Result\u003c(String, usize)\u003e {\n let components = id.split(ID_SEPARATOR).collect::\u003cVec\u003c_\u003e\u003e();\n\n if components.len() != 2 {\n return Err(eyre!(\"Invalid message id format\"));\n }\n\n let tx_hash = components[0].strip_prefix(\"0x\").unwrap_or(components[0]);\n if tx_hash.len() != 64 {\n return Err(eyre!(\"Invalid transaction hash in message id\"));\n }\n\n Ok((tx_hash.to_string(), components[1].parse::\u003cusize\u003e()?))\n}\n\nimpl LowerCaseFields for Message {\n fn to_lowercase(\u0026self) -\u003e Message {\n Message {\n cc_id: CrossChainId {\n id: self.cc_id.id.to_lowercase().try_into().unwrap(),\n chain: self\n .cc_id\n .chain\n .to_string()\n .to_lowercase()\n .try_into()\n .unwrap(),\n },\n source_address: self.source_address.to_lowercase().try_into().unwrap(),\n destination_address: self.destination_address.to_lowercase().try_into().unwrap(),\n destination_chain: self\n .destination_chain\n .to_string()\n .to_lowercase()\n .try_into()\n .unwrap(),\n payload_hash: self.payload_hash,\n }\n }\n}\n\nimpl LowerCaseFields for WorkerSetMessage {\n fn to_lowercase(\u0026self) -\u003e WorkerSetMessage {\n WorkerSetMessage {\n message_id: self.message_id.to_lowercase().try_into().unwrap(),\n new_operators_data: self.new_operators_data.to_owned(),\n }\n }\n}\n\nimpl Comparison\u003cContractCallBase\u003e for Message {\n fn compare_with_event(\u0026self, event: ContractCallBase) -\u003e Result\u003c()\u003e {\n if event.source_address.is_none()\n || event.destination_address.is_none()\n || event.destination_chain.is_none()\n || event.payload_hash.is_none()\n {\n return Err(eyre!(\"Event could not be parsed\"));\n }\n\n if !(self.source_address.to_string().to_lowercase()\n == event.source_address.unwrap().to_string().to_lowercase()\n \u0026\u0026 String::from(self.destination_chain.clone()).to_lowercase()\n == event.destination_chain.unwrap().to_lowercase()\n \u0026\u0026 self.destination_address.to_string().to_lowercase()\n == event.destination_address.unwrap().to_lowercase()\n \u0026\u0026 self.payload_hash == event.payload_hash.unwrap())\n {\n return Err(eyre!(\"Invalid message\"));\n }\n Ok(())\n }\n}\n\nimpl Comparison\u003cOperatorshipTransferredBase\u003e for WorkerSetMessage {\n fn compare_with_event(\u0026self, event: OperatorshipTransferredBase) -\u003e Result\u003c()\u003e {\n if event.new_operators_data.is_none() {\n return Err(eyre!(\"Event could not be parsed\"));\n }\n\n if self.new_operators_data != event.new_operators_data.unwrap() {\n return Err(eyre!(\"Invalid workerset message\"));\n }\n Ok(())\n }\n}\n\npub fn compare_content_with_log(content: ContentVariant, log: \u0026ReceiptLog) -\u003e Result\u003c()\u003e {\n let gateway_event = parse_log(log)?;\n\n match gateway_event {\n GatewayEvent::ContactCall(event) =\u003e {\n let ContentVariant::Message(message) = content else {\n return Err(eyre!(\"Invalid content variant\"));\n };\n message.compare_with_event(event)\n }\n GatewayEvent::OperatorshipTransferred(event) =\u003e {\n let ContentVariant::WorkerSet(message) = content else {\n return Err(eyre!(\"Invalid content variant\"));\n };\n message.compare_with_event(event)\n }\n }\n}\n\npub fn branch_to_nodes(branch: Vec\u003cBytes32\u003e) -\u003e Result\u003cVec\u003cNode\u003e\u003e {\n branch\n .iter()\n .map(bytes32_to_node)\n .collect::\u003cResult\u003cVec\u003cNode\u003e\u003e\u003e()\n}\n\npub fn bytes32_to_node(bytes: \u0026Bytes32) -\u003e Result\u003cNode\u003e {\n Ok(Node::try_from(bytes.as_slice())?)\n}\n\npub fn hex_str_to_bytes(s: \u0026str) -\u003e Result\u003cVec\u003cu8\u003e\u003e {\n let stripped = s.strip_prefix(\"0x\").unwrap_or(s);\n Ok(hex::decode(stripped)?)\n}\n\npub fn calc_sync_period(slot: u64) -\u003e u64 {\n let epoch = slot / 32; // 32 slots per epoch\n epoch / 256 // 256 epochs per sync committee\n}\n\n#[cfg(test)]\npub mod test_helpers {\n use ethabi::{decode, ParamType};\n use std::fs::File;\n use types::sync_committee_rs::constants::FORKS;\n\n use types::common::{Config, FinalizationVariant, WorkerSetMessage};\n use types::connection_router::state::Message;\n use types::execution::ReceiptLog;\n use types::proofs::{\n BatchVerificationData, CrossChainId, TransactionProofsBatch, UpdateVariant,\n };\n use types::ssz_rs::Node;\n use types::{\n common::{ChainConfig, ContentVariant},\n consensus::{Bootstrap, Update},\n };\n\n use super::hex_str_to_bytes;\n\n pub fn get_bootstrap() -\u003e Bootstrap {\n let file = File::open(\"testdata/bootstrap.json\").unwrap();\n let bootstrap: Bootstrap = serde_json::from_reader(file).unwrap();\n\n bootstrap\n }\n\n // Currently have in testdata: 767, 862, 863\n pub fn get_update(period: u64) -\u003e Update {\n let path = format!(\"testdata/{}.json\", period);\n let file = File::open(path).unwrap();\n let update: Update = serde_json::from_reader(file).unwrap();\n\n update\n }\n\n pub fn get_legacy_verification_data() -\u003e Vec\u003cu8\u003e {\n let file_name = \"testdata/legacy_receipt.json\";\n let file = File::open(file_name).unwrap();\n\n serde_json::from_reader(file).unwrap()\n }\n\n pub fn get_batched_data(\n historical: bool,\n finalization: \u0026str,\n ) -\u003e (Bootstrap, BatchVerificationData) {\n let file_name = if historical {\n format!(\n \"testdata/verification/{}_historical_roots.json\",\n finalization\n )\n } else {\n format!(\"testdata/verification/{}_block_roots.json\", finalization)\n };\n let verification_file = File::open(file_name).unwrap();\n let verification_data: BatchVerificationData =\n serde_json::from_reader(verification_file).unwrap();\n\n let bootstrap_file = File::open(format!(\"testdata/verification/bootstrap.json\")).unwrap();\n let bootstrap: Bootstrap = serde_json::from_reader(bootstrap_file).unwrap();\n (bootstrap, verification_data)\n }\n\n pub fn get_gindex_overflow_data() -\u003e BatchVerificationData {\n let file_name = \"testdata/verification/overflow_gindex.json\";\n let verification_file = File::open(file_name).unwrap();\n\n serde_json::from_reader(verification_file).unwrap()\n }\n\n pub fn get_finality_update() -\u003e UpdateVariant {\n let verification_file = File::open(format!(\"testdata/verification/update.json\")).unwrap();\n serde_json::from_reader(verification_file).unwrap()\n }\n\n pub fn get_transaction_proofs() -\u003e TransactionProofsBatch {\n let verification_file = File::open(format!(\n \"testdata/verification/transaction_proofs_batch.json\"\n ))\n .unwrap();\n serde_json::from_reader(verification_file).unwrap()\n }\n\n pub fn get_config() -\u003e Config {\n let genesis_root_bytes: [u8; 32] =\n hex_str_to_bytes(\"0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95\")\n .unwrap()\n .try_into()\n .unwrap();\n\n Config {\n chain_config: ChainConfig {\n chain_id: 1,\n genesis_time: 1606824023,\n genesis_root: Node::from_bytes(genesis_root_bytes),\n forks: FORKS.to_vec(),\n },\n gateway_address: String::from(\"0xAba4D993188008F665C972d79fc59AB2381eCe94\"),\n finalization: FinalizationVariant::Finality(),\n }\n }\n\n pub fn filter_message_variants(proofs_batch: \u0026TransactionProofsBatch) -\u003e Vec\u003cMessage\u003e {\n proofs_batch\n .content\n .iter()\n .filter_map(|c| match c {\n ContentVariant::Message(m) =\u003e Some((*m).clone()),\n ContentVariant::WorkerSet(..) =\u003e None,\n })\n .collect()\n }\n\n pub fn filter_workerset_variants(\n proofs_batch: \u0026TransactionProofsBatch,\n ) -\u003e Vec\u003cWorkerSetMessage\u003e {\n proofs_batch\n .content\n .iter()\n .filter_map(|c| match c {\n ContentVariant::WorkerSet(m) =\u003e Some((*m).clone()),\n ContentVariant::Message(..) =\u003e None,\n })\n .collect()\n }\n\n pub fn filter_workeset_message_variants(\n proofs_batch: \u0026TransactionProofsBatch,\n ) -\u003e Vec\u003cWorkerSetMessage\u003e {\n proofs_batch\n .content\n .iter()\n .filter_map(|c| match c {\n ContentVariant::Message(..) =\u003e None,\n ContentVariant::WorkerSet(m) =\u003e Some((*m).clone()),\n })\n .collect()\n }\n\n pub fn mock_contractcall_message_with_log() -\u003e (Message, ReceiptLog) {\n let file = File::open(\"testdata/receipt_log_contractcall.json\").unwrap();\n let log: ReceiptLog = serde_json::from_reader(file).unwrap();\n let message = Message {\n cc_id: CrossChainId {\n chain: String::from(\"ethereum\").try_into().unwrap(),\n id: String::from(\"foo:bar\").try_into().unwrap(),\n },\n source_address: String::from(\"0xce16f69375520ab01377ce7b88f5ba8c48f8d666\")\n .try_into()\n .unwrap(),\n destination_chain: String::from(\"fantom\").try_into().unwrap(),\n destination_address: String::from(\"0xce16f69375520ab01377ce7b88f5ba8c48f8d666\")\n .try_into()\n .unwrap(),\n payload_hash: [\n 68, 249, 93, 245, 6, 157, 169, 86, 138, 243, 82, 53, 145, 70, 138, 171, 153, 223,\n 14, 249, 200, 50, 140, 182, 107, 223, 224, 230, 18, 217, 208, 55,\n ],\n };\n (message, log)\n }\n\n pub fn mock_workerset_message_with_log() -\u003e (WorkerSetMessage, ReceiptLog) {\n let file = File::open(\"testdata/receipt_log_operatorship.json\").unwrap();\n let log: ReceiptLog = serde_json::from_reader(file).unwrap();\n let message = WorkerSetMessage {\n new_operators_data: hex::encode(\n decode(\u0026[ParamType::Bytes], log.data.as_slice()).unwrap()[0]\n .clone()\n .into_bytes()\n .unwrap(),\n ),\n message_id: String::from(\"foo:bar\").try_into().unwrap(),\n };\n (message, log)\n }\n}\n","traces":[{"line":34,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":41,"address":[],"length":0,"stats":{"Line":116},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":116},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":45,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":116},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":34},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":34},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":34},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":34},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":66},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":46},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":19},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":89,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":35},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":35},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":35},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":35},"fn_name":null},{"line":111,"address":[],"length":0,"stats":{"Line":35},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":35},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":127,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":144,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":145,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":146,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":147,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":150,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":151,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":153,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":157,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":162,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":163,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":164,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":165,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":166,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":168,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":172,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":173,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":174,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":177,"address":[],"length":0,"stats":{"Line":57},"fn_name":null},{"line":178,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":179,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":182,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":186,"address":[],"length":0,"stats":{"Line":33},"fn_name":null},{"line":187,"address":[],"length":0,"stats":{"Line":66},"fn_name":null},{"line":192,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":193,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":194,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":195,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":196,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":197,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":199,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":201,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":205,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":210,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":211,"address":[],"length":0,"stats":{"Line":46},"fn_name":null},{"line":212,"address":[],"length":0,"stats":{"Line":46},"fn_name":null},{"line":213,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":217,"address":[],"length":0,"stats":{"Line":113},"fn_name":null},{"line":218,"address":[],"length":0,"stats":{"Line":226},"fn_name":null},{"line":219,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":221,"address":[],"length":0,"stats":{"Line":71},"fn_name":null},{"line":224,"address":[],"length":0,"stats":{"Line":113},"fn_name":null},{"line":226,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":229,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":230,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":232,"address":[],"length":0,"stats":{"Line":113},"fn_name":null},{"line":233,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":235,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":236,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":238,"address":[],"length":0,"stats":{"Line":92},"fn_name":null},{"line":239,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":240,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":242,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":244,"address":[],"length":0,"stats":{"Line":50},"fn_name":null},{"line":245,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":247,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":248,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":249,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":251,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":255,"address":[],"length":0,"stats":{"Line":113},"fn_name":null},{"line":256,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":259,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":263,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":267,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":268,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":269,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":271,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":274,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":275,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":277,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":278,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":279,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":285,"address":[],"length":0,"stats":{"Line":39},"fn_name":null},{"line":286,"address":[],"length":0,"stats":{"Line":78},"fn_name":null},{"line":287,"address":[],"length":0,"stats":{"Line":39},"fn_name":null},{"line":288,"address":[],"length":0,"stats":{"Line":39},"fn_name":null},{"line":289,"address":[],"length":0,"stats":{"Line":78},"fn_name":null},{"line":291,"address":[],"length":0,"stats":{"Line":38},"fn_name":null},{"line":294,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":74},"fn_name":null},{"line":297,"address":[],"length":0,"stats":{"Line":148},"fn_name":null},{"line":300,"address":[],"length":0,"stats":{"Line":37},"fn_name":null},{"line":301,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":302,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":303,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":308,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":312,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":313,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":315,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":316,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":319,"address":[],"length":0,"stats":{"Line":37},"fn_name":null},{"line":320,"address":[],"length":0,"stats":{"Line":37},"fn_name":null},{"line":321,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":324,"address":[],"length":0,"stats":{"Line":36},"fn_name":null},{"line":328,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":330,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":340,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":341,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":342,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":348,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":354,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":356,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":357,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":363,"address":[],"length":0,"stats":{"Line":27},"fn_name":null},{"line":364,"address":[],"length":0,"stats":{"Line":27},"fn_name":null},{"line":365,"address":[],"length":0,"stats":{"Line":27},"fn_name":null},{"line":366,"address":[],"length":0,"stats":{"Line":27},"fn_name":null},{"line":367,"address":[],"length":0,"stats":{"Line":27},"fn_name":null},{"line":369,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":372,"address":[],"length":0,"stats":{"Line":27},"fn_name":null},{"line":373,"address":[],"length":0,"stats":{"Line":27},"fn_name":null},{"line":374,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":375,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":376,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":377,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":378,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":380,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":382,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":387,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":388,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":389,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":392,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":393,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":395,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":399,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":400,"address":[],"length":0,"stats":{"Line":60},"fn_name":null},{"line":403,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":404,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":405,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":407,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":409,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":410,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":411,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":413,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":418,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":419,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":421,"address":[],"length":0,"stats":{"Line":58},"fn_name":null},{"line":425,"address":[],"length":0,"stats":{"Line":302},"fn_name":null},{"line":426,"address":[],"length":0,"stats":{"Line":302},"fn_name":null},{"line":429,"address":[],"length":0,"stats":{"Line":40},"fn_name":null},{"line":430,"address":[],"length":0,"stats":{"Line":40},"fn_name":null},{"line":431,"address":[],"length":0,"stats":{"Line":41},"fn_name":null},{"line":434,"address":[],"length":0,"stats":{"Line":106},"fn_name":null},{"line":435,"address":[],"length":0,"stats":{"Line":106},"fn_name":null},{"line":436,"address":[],"length":0,"stats":{"Line":106},"fn_name":null}],"covered":179,"coverable":189},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","lightclient","mod.rs"],"content":"pub mod error;\npub mod helpers;\npub mod tests;\n\nuse cosmwasm_std::Env;\nuse error::ConsensusError;\nuse eyre::Result;\nuse helpers::is_proof_valid;\nuse milagro_bls::{AggregateSignature, PublicKey};\nuse types::common::ChainConfig;\nuse types::ssz_rs::prelude::*;\nuse types::sync_committee_rs::constants::{Version, GENESIS_FORK_VERSION};\nuse types::sync_committee_rs::{\n consensus_types::{BeaconBlockHeader, ForkData, SyncCommittee},\n constants::{BlsSignature, Bytes32, SYNC_COMMITTEE_SIZE},\n util::SigningData,\n};\nuse types::{consensus::*, lightclient::LightClientState};\n\nuse self::helpers::calc_sync_period;\n\npub struct LightClient {\n pub state: LightClientState,\n pub chain_config: ChainConfig,\n env: Env,\n}\n\nimpl LightClient {\n pub fn new(chain_config: \u0026ChainConfig, state: Option\u003cLightClientState\u003e, env: \u0026Env) -\u003e Self {\n let state = state.unwrap_or_default();\n Self {\n state,\n chain_config: chain_config.clone(),\n env: env.clone(),\n }\n }\n\n /// Initializes the state of the Light Client using a LightClientBootstrap message.\n pub fn bootstrap(\u0026mut self, bootstrap: \u0026Bootstrap) -\u003e Result\u003c(), ConsensusError\u003e {\n let committee_valid = self.is_current_committee_proof_valid(\n \u0026bootstrap.header.beacon,\n \u0026bootstrap.current_sync_committee,\n \u0026bootstrap.current_sync_committee_branch,\n );\n\n if !committee_valid {\n return Err(ConsensusError::InvalidCurrentSyncCommitteeProof);\n }\n\n self.state = LightClientState {\n update_slot: bootstrap.header.beacon.slot,\n current_sync_committee: bootstrap.current_sync_committee.clone(),\n next_sync_committee: None,\n };\n\n Ok(())\n }\n\n pub fn verify_update(\u0026self, update: \u0026Update) -\u003e Result\u003c(), ConsensusError\u003e {\n self.verify_finality_update(\u0026FinalityUpdate::from(update))?;\n\n // Check that next committe in attested header\n let is_valid = self.is_next_committee_proof_valid(\n \u0026update.attested_header.beacon,\n \u0026mut update.next_sync_committee.clone(),\n \u0026update.next_sync_committee_branch,\n );\n\n if !is_valid {\n return Err(ConsensusError::InvalidNextSyncCommitteeProof);\n }\n\n Ok(())\n }\n\n pub fn verify_finality_update(\u0026self, update: \u0026FinalityUpdate) -\u003e Result\u003c(), ConsensusError\u003e {\n self.verify_optimistic_update(\u0026OptimisticUpdate::from(update))?;\n\n // Check for valid timestamp conditions:\n // 1. The attested header's slot should be equal or greater than the finalized header's slot.\n if update.attested_header.beacon.slot \u003c update.finalized_header.beacon.slot {\n return Err(ConsensusError::InvalidTimestamp);\n }\n\n let is_valid = self.is_finality_proof_valid(\n \u0026update.attested_header.beacon,\n \u0026mut update.finalized_header.beacon.clone(),\n \u0026update.finality_branch,\n );\n\n if !is_valid {\n return Err(ConsensusError::InvalidFinalityProof);\n }\n\n Ok(())\n }\n\n pub fn verify_optimistic_update(\n \u0026self,\n update: \u0026OptimisticUpdate,\n ) -\u003e Result\u003c(), ConsensusError\u003e {\n // Check if there's any participation in the sync committee at all.\n let bits = self.get_bits(\u0026update.sync_aggregate.sync_committee_bits);\n if bits == 0 {\n return Err(ConsensusError::InsufficientParticipation);\n }\n\n // Check for valid timestamp conditions:\n // 1. The expected current slot given the genesis time should be equal or greater than the update's signature slot.\n // 2. The slot of the update's signature should be greater than the slot of the attested header.\n let valid_time = self.expected_current_slot() \u003e= update.signature_slot\n \u0026\u0026 update.signature_slot \u003e update.attested_header.beacon.slot;\n\n if !valid_time {\n return Err(ConsensusError::InvalidTimestamp);\n }\n\n let store_period = calc_sync_period(self.state.update_slot);\n let update_sig_period = calc_sync_period(update.signature_slot);\n\n let valid_period = if self.state.next_sync_committee.is_some() {\n update_sig_period == store_period || update_sig_period == store_period + 1\n } else {\n update_sig_period == store_period\n };\n\n if !valid_period {\n return Err(ConsensusError::InvalidPeriod);\n }\n\n // Calculate the period for the attested header and check its relevance.\n // Ensure the attested header isn't already finalized unless the update introduces a new sync committee.\n let update_attested_period = calc_sync_period(update.attested_header.beacon.slot);\n let update_has_next_committee =\n self.state.next_sync_committee.is_none() \u0026\u0026 update_attested_period == store_period;\n\n if update.attested_header.beacon.slot \u003c= self.state.update_slot\n \u0026\u0026 !update_has_next_committee\n {\n return Err(ConsensusError::NotRelevant);\n }\n\n // Verify the sync committee's aggregate signature for the attested header.\n let sync_committee = if update_sig_period == store_period {\n self.state.current_sync_committee.clone()\n } else {\n self.state.next_sync_committee.clone().unwrap()\n };\n\n let pks = self\n .get_participating_keys(\u0026sync_committee, \u0026update.sync_aggregate.sync_committee_bits);\n\n let is_valid_sig = self.verify_sync_committee_signature(\n \u0026self.chain_config,\n \u0026pks,\n \u0026update.attested_header.beacon,\n \u0026update.sync_aggregate.sync_committee_signature,\n update.signature_slot,\n );\n\n if !is_valid_sig {\n return Err(ConsensusError::InvalidSignature);\n }\n\n Ok(())\n }\n\n pub fn apply_update(\u0026mut self, update: \u0026Update) -\u003e Result\u003c(), ConsensusError\u003e {\n self.verify_update(update)?;\n\n let committee_bits = self.get_bits(\u0026update.sync_aggregate.sync_committee_bits);\n\n let update_finalized_slot = update.finalized_header.beacon.slot;\n let update_attested_period = calc_sync_period(update.attested_header.beacon.slot);\n let update_finalized_period = calc_sync_period(update_finalized_slot);\n\n let update_has_finalized_next_committee = update_finalized_period == update_attested_period;\n\n let should_apply_update = {\n let has_majority = committee_bits * 3 \u003e= 512 * 2;\n let update_is_newer = update_finalized_slot \u003e self.state.update_slot;\n let good_update = update_is_newer || update_has_finalized_next_committee;\n has_majority \u0026\u0026 good_update\n };\n\n if should_apply_update {\n let store_period = calc_sync_period(self.state.update_slot);\n\n if self.state.next_sync_committee.is_none() {\n self.state.next_sync_committee = Some(update.next_sync_committee.clone());\n } else if update_finalized_period == store_period + 1 {\n self.state.current_sync_committee = self.state.next_sync_committee.clone().unwrap();\n self.state.next_sync_committee = Some(update.next_sync_committee.clone());\n }\n\n if update_finalized_slot \u003e self.state.update_slot {\n self.state.update_slot = update.finalized_header.beacon.slot;\n self.log_finality_update(update);\n }\n }\n\n Ok(())\n }\n\n fn is_current_committee_proof_valid(\n \u0026self,\n attested_header: \u0026BeaconBlockHeader,\n current_committee: \u0026SyncCommittee\u003cSYNC_COMMITTEE_SIZE\u003e,\n current_committee_branch: \u0026[Bytes32],\n ) -\u003e bool {\n is_proof_valid(\n \u0026attested_header.state_root,\n \u0026mut current_committee.clone(),\n current_committee_branch,\n 5,\n 22,\n )\n }\n\n pub fn get_bits(\u0026self, bitfield: \u0026Bitvector\u003c512\u003e) -\u003e u64 {\n let mut count = 0;\n bitfield.iter().for_each(|bit| {\n if bit == true {\n count += 1;\n }\n });\n\n count\n }\n\n fn expected_current_slot(\u0026self) -\u003e u64 {\n let since_genesis = self.env.block.time.seconds() - self.chain_config.genesis_time;\n\n since_genesis / 12\n }\n\n fn is_finality_proof_valid(\n \u0026self,\n attested_header: \u0026BeaconBlockHeader,\n finality_header: \u0026mut BeaconBlockHeader,\n finality_branch: \u0026[Bytes32],\n ) -\u003e bool {\n is_proof_valid(\n \u0026attested_header.state_root,\n finality_header,\n finality_branch,\n 6,\n 41,\n )\n }\n\n fn is_next_committee_proof_valid(\n \u0026self,\n attested_header: \u0026BeaconBlockHeader,\n next_committee: \u0026mut SyncCommittee\u003cSYNC_COMMITTEE_SIZE\u003e,\n next_committee_branch: \u0026[Bytes32],\n ) -\u003e bool {\n is_proof_valid(\n \u0026attested_header.state_root,\n next_committee,\n next_committee_branch,\n 5,\n 23,\n )\n }\n\n /**\n * Returns the fork version for a given slot.\n */\n fn get_fork_version(\u0026self, slot: u64) -\u003e Version {\n let epoch = slot / 32;\n\n let mut forks = self.chain_config.forks.clone();\n\n // sort in descending order by epoch\n forks.sort_by(|a, b| b.0.cmp(\u0026a.0));\n\n return forks\n .iter()\n .find_map(|\u0026(fork_epoch, fork_version)| {\n if fork_epoch \u003c= epoch {\n Some(fork_version)\n } else {\n None\n }\n })\n .unwrap_or(GENESIS_FORK_VERSION);\n }\n\n fn get_participating_keys(\n \u0026self,\n committee: \u0026SyncCommittee\u003cSYNC_COMMITTEE_SIZE\u003e,\n bitfield: \u0026Bitvector\u003c512\u003e,\n ) -\u003e Vec\u003cPublicKey\u003e {\n let mut pks: Vec\u003cPublicKey\u003e = Vec::new();\n bitfield.iter().enumerate().for_each(|(i, bit)| {\n if bit == true {\n let pk = \u0026committee.public_keys[i];\n let pk = PublicKey::from_bytes_unchecked(pk).unwrap();\n pks.push(pk);\n }\n });\n\n pks\n }\n\n fn verify_sync_committee_signature\u003cT\u003e(\n \u0026self,\n config: \u0026ChainConfig,\n pks: \u0026[PublicKey],\n attested_block: \u0026T,\n signature: \u0026BlsSignature,\n signature_slot: u64,\n ) -\u003e bool\n where\n T: ssz_rs::Merkleized + Clone,\n {\n let res: Result\u003cbool\u003e = (move || {\n let pks: Vec\u003c\u0026PublicKey\u003e = pks.iter().collect();\n let header_root = attested_block.clone().hash_tree_root()?;\n let signing_root =\n self.compute_committee_sign_root(config, header_root, signature_slot)?;\n\n Ok(self.is_aggregate_valid(signature, signing_root.as_ref(), \u0026pks))\n })();\n\n if let Ok(is_valid) = res {\n is_valid\n } else {\n false\n }\n }\n\n pub fn is_aggregate_valid(\n \u0026self,\n sig_bytes: \u0026BlsSignature,\n msg: \u0026[u8],\n pks: \u0026[\u0026PublicKey],\n ) -\u003e bool {\n let sig_res = AggregateSignature::from_bytes(sig_bytes);\n match sig_res {\n Ok(sig) =\u003e sig.fast_aggregate_verify(msg, pks),\n Err(_) =\u003e false,\n }\n }\n\n fn compute_committee_sign_root(\n \u0026self,\n config: \u0026ChainConfig,\n header: Node,\n slot: u64,\n ) -\u003e Result\u003cNode\u003e {\n let genesis_root = config.genesis_root;\n\n let domain_type = \u0026hex::decode(\"07000000\")?[..];\n let fork_version = self.get_fork_version(slot);\n let domain = self.compute_domain(domain_type, fork_version, genesis_root)?;\n self.compute_signing_root(header, domain)\n }\n\n pub fn compute_signing_root(\u0026self, object_root: Node, domain: [u8; 32]) -\u003e Result\u003cNode\u003e {\n let mut data = SigningData {\n object_root,\n domain,\n };\n Ok(data.hash_tree_root()?)\n }\n\n pub fn compute_domain(\n \u0026self,\n domain_type: \u0026[u8],\n fork_version: [u8; 4],\n genesis_root: Node,\n ) -\u003e Result\u003c[u8; 32]\u003e {\n let fork_data_root = self.compute_fork_data_root(fork_version, genesis_root)?;\n let start = domain_type;\n let end = \u0026fork_data_root.as_ref()[..28];\n let d = [start, end].concat();\n Ok(d.to_vec().try_into().unwrap())\n }\n\n fn compute_fork_data_root(\n \u0026self,\n current_version: [u8; 4],\n genesis_validators_root: Node,\n ) -\u003e Result\u003cNode\u003e {\n let mut fork_data = ForkData {\n current_version,\n genesis_validators_root,\n };\n Ok(fork_data.hash_tree_root()?)\n }\n\n fn log_finality_update(\u0026self, update: \u0026Update) {\n println!(\n \"finalized slot slot={}\",\n update.finalized_header.beacon.slot,\n );\n }\n}\n","traces":[{"line":29,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":30,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":34,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":40,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":41,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":47,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":56,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":33},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":15},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":36},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":36},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":36},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":111,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":135,"address":[],"length":0,"stats":{"Line":23},"fn_name":null},{"line":138,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":144,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":145,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":147,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":162,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":165,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":168,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":169,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":171,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":173,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":174,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":175,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":177,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":179,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":182,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":183,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":187,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":189,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":190,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":191,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":192,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":193,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":196,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":197,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":198,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":202,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":205,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":212,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":213,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":214,"address":[],"length":0,"stats":{"Line":26},"fn_name":null},{"line":220,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":221,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":222,"address":[],"length":0,"stats":{"Line":22059},"fn_name":null},{"line":223,"address":[],"length":0,"stats":{"Line":40742},"fn_name":null},{"line":224,"address":[],"length":0,"stats":{"Line":18726},"fn_name":null},{"line":228,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":231,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":232,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":234,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":237,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":244,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":245,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":246,"address":[],"length":0,"stats":{"Line":17},"fn_name":null},{"line":252,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":259,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":260,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":261,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":271,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":273,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":276,"address":[],"length":0,"stats":{"Line":192},"fn_name":null},{"line":278,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":279,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":280,"address":[],"length":0,"stats":{"Line":72},"fn_name":null},{"line":281,"address":[],"length":0,"stats":{"Line":48},"fn_name":null},{"line":282,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":284,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":287,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":290,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":295,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":12312},"fn_name":null},{"line":297,"address":[],"length":0,"stats":{"Line":24368},"fn_name":null},{"line":298,"address":[],"length":0,"stats":{"Line":12080},"fn_name":null},{"line":299,"address":[],"length":0,"stats":{"Line":12080},"fn_name":null},{"line":300,"address":[],"length":0,"stats":{"Line":12080},"fn_name":null},{"line":304,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":307,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":318,"address":[],"length":0,"stats":{"Line":48},"fn_name":null},{"line":319,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":320,"address":[],"length":0,"stats":{"Line":48},"fn_name":null},{"line":321,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":322,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":324,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":327,"address":[],"length":0,"stats":{"Line":48},"fn_name":null},{"line":328,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":330,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":334,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":340,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":341,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":342,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":343,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":347,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":353,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":355,"address":[],"length":0,"stats":{"Line":48},"fn_name":null},{"line":357,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":358,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":361,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":366,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":369,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":375,"address":[],"length":0,"stats":{"Line":48},"fn_name":null},{"line":382,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":391,"address":[],"length":0,"stats":{"Line":24},"fn_name":null},{"line":394,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":395,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":397,"address":[],"length":0,"stats":{"Line":7},"fn_name":null}],"covered":144,"coverable":148},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","lightclient","tests.rs"],"content":"#[cfg(test)]\npub mod tests {\n use std::time::{SystemTime, UNIX_EPOCH};\n\n use crate::lightclient::helpers::test_helpers::{\n filter_message_variants, get_batched_data, get_legacy_verification_data,\n mock_contractcall_message_with_log, mock_workerset_message_with_log,\n };\n use crate::lightclient::helpers::{\n calc_sync_period, compare_content_with_log, extract_logs_from_receipt_proof,\n hex_str_to_bytes, is_proof_valid, parse_log, parse_logs_from_receipt, parse_message_id,\n verify_block_roots_proof, verify_historical_roots_proof, verify_transaction_proof,\n verify_trie_proof, Comparison,\n };\n use crate::{\n lightclient::error::ConsensusError,\n lightclient::helpers::test_helpers::{get_bootstrap, get_config, get_update},\n lightclient::LightClient,\n lightclient::{self},\n };\n use cosmwasm_std::testing::mock_env;\n use cosmwasm_std::Timestamp;\n use ethabi::{decode, ParamType};\n use types::alloy_primitives::Address;\n use types::common::ContentVariant;\n use types::consensus::{Bootstrap, OptimisticUpdate};\n use types::execution::{ContractCallBase, GatewayEvent};\n use types::lightclient::LightClientState;\n use types::proofs::{nonempty, AncestryProof, UpdateVariant};\n use types::ssz_rs::{Bitvector, Merkleized, Node};\n use types::sync_committee_rs::consensus_types::Transaction;\n use types::sync_committee_rs::constants::{Bytes32, Root};\n use types::sync_committee_rs::{\n consensus_types::BeaconBlockHeader,\n constants::{BlsPublicKey, BlsSignature},\n };\n\n pub fn init_lightclient(bootstrap: Option\u003cBootstrap\u003e) -\u003e LightClient {\n let bootstrap = if bootstrap.is_some() {\n bootstrap.unwrap()\n } else {\n get_bootstrap()\n };\n let config = get_config();\n let mut env = mock_env();\n env.block.time = Timestamp::from_seconds(\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .unwrap()\n .as_secs(),\n );\n\n let mut client = LightClient::new(\u0026config.chain_config, None, \u0026env);\n let res = client.bootstrap(\u0026bootstrap);\n if let Err(e) = res {\n panic!(\"Error bootstrapping: {}\", e);\n }\n\n client\n }\n\n #[test]\n fn test_apply_bootstrap() {\n let config = get_config();\n let env = mock_env();\n let mut client = LightClient::new(\u0026config.chain_config, None, \u0026env);\n\n // test corrupt current committee branch\n let mut bootstrap = get_bootstrap();\n bootstrap.current_sync_committee_branch = vec![];\n assert!(client.bootstrap(\u0026bootstrap).is_err());\n\n // test normal bootstrap\n let bootstrap = get_bootstrap();\n assert!(client.bootstrap(\u0026bootstrap).is_ok());\n\n assert_eq!(\n client.state,\n LightClientState {\n update_slot: bootstrap.header.beacon.slot,\n current_sync_committee: bootstrap.current_sync_committee,\n next_sync_committee: None\n }\n );\n }\n\n #[test]\n fn test_is_proof_valid() {\n let mut update = get_update(862);\n\n // success\n assert!(is_proof_valid(\n \u0026update.attested_header.beacon.state_root,\n \u0026mut update.finalized_header.beacon,\n \u0026update.finality_branch,\n 6,\n 41\n ));\n\n // change depth, fail\n assert!(!is_proof_valid(\n \u0026update.attested_header.beacon.state_root,\n \u0026mut update.finalized_header.beacon,\n \u0026update.finality_branch,\n 5,\n 41\n ));\n\n // change index, fail\n assert!(!is_proof_valid(\n \u0026update.attested_header.beacon.state_root,\n \u0026mut update.finalized_header.beacon,\n \u0026update.finality_branch,\n 6,\n 40\n ));\n\n // tamper with the state root, fail\n let mut invalid_update = update.clone();\n invalid_update.attested_header.beacon.state_root.0[0] = 0;\n assert!(!is_proof_valid(\n \u0026invalid_update.attested_header.beacon.state_root,\n \u0026mut invalid_update.finalized_header.beacon,\n \u0026invalid_update.finality_branch,\n 6,\n 40\n ));\n\n // tamper with the body of the finalized header, fail\n let mut invalid_update = update.clone();\n invalid_update.finalized_header.beacon.body_root.0[0] = 0;\n assert!(!is_proof_valid(\n \u0026invalid_update.attested_header.beacon.state_root,\n \u0026mut invalid_update.finalized_header.beacon,\n \u0026invalid_update.finality_branch,\n 6,\n 40\n ));\n\n // tamper with the proof, fail\n let mut invalid_update = update.clone();\n invalid_update.finality_branch[0] = Bytes32::default();\n assert!(!is_proof_valid(\n \u0026invalid_update.attested_header.beacon.state_root,\n \u0026mut invalid_update.finalized_header.beacon,\n \u0026invalid_update.finality_branch,\n 6,\n 40\n ));\n }\n\n #[test]\n fn test_verify_trie_proof() {\n let verification_data = get_batched_data(false, \"finality\").1;\n let proofs = verification_data.target_blocks[0].transactions_proofs[0].clone();\n let receipt_proof = proofs.receipt_proof.clone();\n let transaction_proof = proofs.transaction_proof;\n\n let res = verify_trie_proof(\n receipt_proof.receipts_root,\n transaction_proof.transaction_index,\n receipt_proof.receipt_proof,\n );\n assert!(res.is_some());\n\n let receipt_result = parse_logs_from_receipt(\u0026res.unwrap());\n // verify_trie_proof returns the leaf, a receipt with logs in this case\n assert!(receipt_result.is_ok());\n\n // break the receipts_root, fail\n let mut invalid_receipt_proof = proofs.receipt_proof.clone();\n invalid_receipt_proof.receipts_root = Node::default();\n assert!(verify_trie_proof(\n invalid_receipt_proof.receipts_root,\n transaction_proof.transaction_index,\n invalid_receipt_proof.receipt_proof,\n )\n .is_none());\n\n // change the transaction index, fail\n let invalid_receipt_proof = proofs.receipt_proof.clone();\n assert!(verify_trie_proof(\n invalid_receipt_proof.receipts_root,\n transaction_proof.transaction_index + 1,\n invalid_receipt_proof.receipt_proof,\n )\n .is_none());\n\n // change the proof, fail\n let mut invalid_receipt_proof = proofs.receipt_proof.clone();\n invalid_receipt_proof.receipt_proof[0] = vec![];\n assert!(verify_trie_proof(\n invalid_receipt_proof.receipts_root,\n transaction_proof.transaction_index,\n invalid_receipt_proof.receipt_proof,\n )\n .is_none());\n }\n\n #[test]\n fn test_verify_block_roots_proof() {\n let data = get_batched_data(false, \"finality\").1;\n let (block_roots_index, block_root_proof) = match \u0026data.target_blocks[0].ancestry_proof {\n AncestryProof::BlockRoots {\n block_roots_index,\n block_root_proof,\n } =\u003e (block_roots_index, block_root_proof),\n AncestryProof::HistoricalRoots { .. } =\u003e {\n panic!(\"Unexpected.\")\n }\n };\n let update = match data.update {\n UpdateVariant::Finality(update) =\u003e update,\n UpdateVariant::Optimistic(..) =\u003e {\n panic!(\"Unexpected\")\n }\n };\n\n let recent_block = update.finalized_header.beacon;\n let target_block_root = data.target_blocks[0]\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap();\n\n assert!(verify_block_roots_proof(\n \u0026block_roots_index,\n \u0026block_root_proof,\n \u0026target_block_root,\n \u0026recent_block.state_root,\n )\n .is_ok());\n\n // change block roots index, fail\n assert!(verify_block_roots_proof(\n \u0026(block_roots_index + 1),\n \u0026block_root_proof,\n \u0026target_block_root,\n \u0026recent_block.state_root,\n )\n .is_err());\n\n // change block roots proof, fail\n let mut invalid_block_root_proof = block_root_proof.clone();\n invalid_block_root_proof[0] = Node::default();\n assert!(verify_block_roots_proof(\n \u0026block_roots_index,\n \u0026invalid_block_root_proof,\n \u0026target_block_root,\n \u0026recent_block.state_root,\n )\n .is_err());\n\n // change target block, fail\n assert!(verify_block_roots_proof(\n \u0026block_roots_index,\n \u0026block_root_proof,\n \u0026Node::default(),\n \u0026recent_block.state_root,\n )\n .is_err());\n\n // change recent block state_root, fail\n assert!(verify_block_roots_proof(\n \u0026block_roots_index,\n \u0026block_root_proof,\n \u0026target_block_root,\n \u0026Node::default(),\n )\n .is_err());\n }\n\n #[test]\n fn test_verify_historical_roots_proof() {\n let verification_data = get_batched_data(true, \"finality\").1;\n let (\n block_root_proof,\n block_summary_root,\n block_summary_root_proof,\n block_summary_root_gindex,\n ) = match \u0026verification_data.target_blocks[0].ancestry_proof {\n AncestryProof::BlockRoots { .. } =\u003e {\n panic!(\"Unexpected.\")\n }\n AncestryProof::HistoricalRoots {\n block_root_proof,\n block_summary_root,\n block_summary_root_proof,\n block_summary_root_gindex,\n } =\u003e (\n block_root_proof,\n block_summary_root,\n block_summary_root_proof,\n block_summary_root_gindex,\n ),\n };\n\n let recent_block = verification_data.update.recent_block();\n let target_block = verification_data.target_blocks[0].target_block.clone();\n\n assert!(verify_historical_roots_proof(\n \u0026block_root_proof,\n \u0026block_summary_root_proof,\n \u0026block_summary_root,\n \u0026block_summary_root_gindex,\n \u0026target_block,\n \u0026recent_block.state_root\n )\n .is_ok());\n\n // change block roots proof, fail\n let mut invalid_proof = block_root_proof.clone();\n invalid_proof[0] = Node::default();\n assert!(verify_historical_roots_proof(\n \u0026invalid_proof,\n \u0026block_summary_root_proof,\n \u0026block_summary_root,\n \u0026block_summary_root_gindex,\n \u0026target_block,\n \u0026recent_block.state_root\n )\n .is_err());\n\n // change the block_summary_root_proof, fail\n let mut invalid_proof = block_summary_root_proof.clone();\n invalid_proof[0] = Node::default();\n assert!(verify_historical_roots_proof(\n \u0026block_root_proof,\n \u0026invalid_proof,\n \u0026block_summary_root,\n \u0026block_summary_root_gindex,\n \u0026target_block,\n \u0026recent_block.state_root\n )\n .is_err());\n\n // change the block_summary_root, fail\n assert!(verify_historical_roots_proof(\n \u0026block_root_proof,\n \u0026block_summary_root_proof,\n \u0026Root::default(),\n \u0026block_summary_root_gindex,\n \u0026target_block,\n \u0026recent_block.state_root\n )\n .is_err());\n\n // change the block_summary_root_gindex, fail\n assert!(verify_historical_roots_proof(\n \u0026block_root_proof,\n \u0026block_summary_root_proof,\n \u0026block_summary_root,\n \u0026(block_summary_root_gindex + 1),\n \u0026target_block,\n \u0026recent_block.state_root\n )\n .is_err());\n\n // change the target_block, fail\n assert!(verify_historical_roots_proof(\n \u0026block_root_proof,\n \u0026block_summary_root_proof,\n \u0026block_summary_root,\n \u0026block_summary_root_gindex,\n \u0026BeaconBlockHeader::default(),\n \u0026recent_block.state_root\n )\n .is_err());\n\n // change the state_root, fail\n assert!(verify_historical_roots_proof(\n \u0026block_root_proof,\n \u0026block_summary_root_proof,\n \u0026block_summary_root,\n \u0026(block_summary_root_gindex + 1),\n \u0026target_block,\n \u0026Root::default()\n )\n .is_err());\n }\n\n #[test]\n fn test_parse_logs_from_legacy_receipt() {\n let legacy_receipt = get_legacy_verification_data();\n\n let logs_result = parse_logs_from_receipt(\u0026legacy_receipt);\n assert!(logs_result.is_ok());\n\n let parse_result = parse_log(\u0026logs_result.unwrap().0[7]);\n assert!(parse_result.is_ok());\n\n assert_eq!(\n parse_result.unwrap(),\n GatewayEvent::ContactCall(ContractCallBase {\n source_address: Some(\n hex::decode(\"481a2aae41cd34832ddcf5a79404538bb2c02bc8\")\n .unwrap()\n .as_slice()\n .try_into()\n .unwrap()\n ),\n destination_chain: Some(String::from(\"osmosis-7\")),\n destination_address: Some(String::from(\n \"osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw\"\n )),\n payload_hash: Some(\n vec![\n 229, 110, 107, 115, 37, 22, 199, 64, 219, 239, 95, 60, 169, 125, 156, 99,\n 142, 37, 17, 70, 214, 194, 31, 64, 39, 194, 58, 132, 172, 220, 90, 201\n ]\n .try_into()\n .unwrap()\n )\n })\n );\n }\n\n #[test]\n fn test_parse_logs_from_receipt() {\n let verification_data = get_batched_data(false, \"finality\").1;\n let proofs = verification_data.target_blocks[0].transactions_proofs[0].clone();\n let mut receipt = verify_trie_proof(\n proofs.receipt_proof.receipts_root,\n proofs.transaction_proof.transaction_index,\n proofs.receipt_proof.receipt_proof.clone(),\n )\n .unwrap();\n\n let logs_result = parse_logs_from_receipt(\u0026receipt);\n assert!(logs_result.is_ok());\n\n let logs = logs_result.unwrap().0;\n let first_log = logs.get(0).unwrap();\n let expected_address: [u8; 20] = vec![\n 171, 164, 217, 147, 24, 128, 8, 246, 101, 201, 114, 215, 159, 197, 154, 178, 56, 30,\n 206, 148,\n ]\n .try_into()\n .unwrap();\n let expected_topics: Vec\u003c[u8; 32]\u003e = vec![\n vec![\n 48, 174, 108, 199, 140, 39, 230, 81, 116, 91, 242, 173, 8, 161, 29, 232, 57, 16,\n 172, 30, 52, 122, 82, 247, 172, 137, 140, 15, 190, 249, 77, 174,\n ]\n .try_into()\n .unwrap(),\n vec![\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 197, 90, 211, 221, 179, 134, 51, 93, 90, 243,\n 56, 35, 227, 168, 69, 192, 219, 212, 69, 92,\n ]\n .try_into()\n .unwrap(),\n vec![\n 235, 200, 76, 189, 117, 186, 85, 22, 191, 69, 231, 2, 74, 158, 18, 188, 60, 92,\n 136, 15, 115, 227, 165, 190, 202, 126, 187, 165, 43, 40, 103, 167,\n ]\n .try_into()\n .unwrap(),\n ];\n let expected_data: Vec\u003cu8\u003e = vec![\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 112, 111, 108, 121, 103, 111, 110, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 116, 104,\n 105, 115, 32, 105, 115, 32, 116, 104, 101, 32, 97, 100, 100, 114, 101, 115, 115, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 112, 97, 121, 108, 111, 97, 100, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n ];\n\n assert_eq!(first_log.address, expected_address);\n assert_eq!(first_log.topics, expected_topics);\n assert_eq!(first_log.data, expected_data);\n\n let logs_result = extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026verification_data.target_blocks[0]\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap(),\n );\n let logs = logs_result.unwrap().0;\n let first_log = logs.get(0).unwrap();\n\n assert_eq!(first_log.address, expected_address);\n assert_eq!(first_log.topics, expected_topics);\n assert_eq!(first_log.data, expected_data);\n\n // providing an empty arrary should return error\n assert!(parse_logs_from_receipt(\u0026vec![]).is_err());\n\n // providing invalid receipt should return error\n receipt[0] = 0;\n receipt[1] = 0;\n receipt[2] = 0;\n assert!(parse_logs_from_receipt(\u0026receipt).is_err());\n }\n\n #[test]\n fn test_extract_logs_from_receipt_proof() {\n let verification_data = get_batched_data(false, \"finality\").1;\n let proofs = verification_data.target_blocks[0].transactions_proofs[0].clone();\n let target_block_root = verification_data.target_blocks[0]\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap();\n\n assert!(extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026target_block_root,\n )\n .is_ok());\n\n // change the receipt proof, fail\n let mut proof = proofs.receipt_proof.clone();\n proof.receipts_root = Root::default();\n assert!(extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026Root::default()\n )\n .is_err());\n\n let mut proof = proofs.receipt_proof.clone();\n proof.receipt_proof[0] = vec![];\n assert!(extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026Root::default()\n )\n .is_err());\n\n // change transaction index, fail\n assert!(extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index + 1,\n \u0026target_block_root,\n )\n .is_err());\n\n // change the target_block root, fail\n assert!(extract_logs_from_receipt_proof(\n \u0026proofs.receipt_proof,\n proofs.transaction_proof.transaction_index,\n \u0026Root::default()\n )\n .is_err());\n }\n\n #[test]\n fn test_verify_transaction_proof() {\n let verification_data = get_batched_data(false, \"finality\").1;\n let transaction_proof = verification_data.target_blocks[0].transactions_proofs[0]\n .transaction_proof\n .clone();\n let target_block_root = \u0026verification_data.target_blocks[0]\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap();\n\n assert!(verify_transaction_proof(\u0026transaction_proof, \u0026target_block_root).is_ok());\n\n // change the transaction bytecode, fail\n let mut invalid_proof = transaction_proof.clone();\n invalid_proof.transaction = Transaction::default();\n assert!(verify_transaction_proof(\u0026invalid_proof, \u0026target_block_root).is_err());\n\n // change the transaction proof, fail\n let mut invalid_proof = transaction_proof.clone();\n invalid_proof.transaction_proof[0] = Node::default();\n assert!(verify_transaction_proof(\u0026invalid_proof, \u0026target_block_root).is_err());\n\n // change the transaction gindex, fail\n let mut invalid_proof = transaction_proof.clone();\n invalid_proof.transaction_gindex = invalid_proof.transaction_gindex + 1;\n assert!(verify_transaction_proof(\u0026invalid_proof, \u0026target_block_root).is_err());\n }\n\n #[test]\n fn test_compare_message_with_event() {\n let verification_data = get_batched_data(false, \"finality\").1;\n let transaction_proofs = verification_data.target_blocks[0].transactions_proofs[0].clone();\n let message = filter_message_variants(\u0026transaction_proofs)[0].clone();\n let receipt_proof = transaction_proofs.receipt_proof;\n let transaction_proof = transaction_proofs.transaction_proof;\n\n let (_, log_index) = parse_message_id(\u0026message.cc_id.id).unwrap();\n let logs = extract_logs_from_receipt_proof(\n \u0026receipt_proof,\n transaction_proof.transaction_index,\n \u0026verification_data.target_blocks[0]\n .target_block\n .clone()\n .hash_tree_root()\n .unwrap(),\n )\n .unwrap();\n let log = logs.0[log_index].clone();\n let GatewayEvent::ContactCall(event) = parse_log(\u0026log).unwrap() else {\n panic!(\"Invalid event type\");\n };\n\n assert!(message.compare_with_event(event.clone()).is_ok());\n\n // test source_address check\n let mut modified_message = message.clone();\n assert_eq!(\n modified_message.source_address.to_string().to_lowercase(),\n \"0xc55ad3ddb386335d5af33823e3a845c0dbd4455c\"\n );\n assert!(modified_message.compare_with_event(event.clone()).is_ok());\n modified_message.source_address = Address::ZERO.to_string().try_into().unwrap();\n assert!(modified_message.compare_with_event(event.clone()).is_err());\n\n // test destination_chain check\n let mut modified_message = message.clone();\n assert_eq!(\n modified_message\n .destination_chain\n .to_string()\n .to_lowercase(),\n \"polygon\"\n );\n assert!(modified_message.compare_with_event(event.clone()).is_ok());\n modified_message.destination_chain = String::from(\"none\").try_into().unwrap();\n assert!(modified_message.compare_with_event(event.clone()).is_err());\n\n // test destination_address check\n let mut modified_message = message.clone();\n assert_eq!(\n modified_message.destination_address.to_string(),\n \"this is the address\"\n );\n assert!(modified_message.compare_with_event(event.clone()).is_ok());\n modified_message.destination_address = Address::ZERO.to_string().try_into().unwrap();\n assert!(modified_message.compare_with_event(event.clone()).is_err());\n\n // test payload_hash check\n let mut modified_message = message.clone();\n assert_eq!(\n hex::encode(modified_message.payload_hash),\n \"ebc84cbd75ba5516bf45e7024a9e12bc3c5c880f73e3a5beca7ebba52b2867a7\"\n );\n assert!(modified_message.compare_with_event(event.clone()).is_ok());\n modified_message.payload_hash = Default::default();\n assert!(modified_message.compare_with_event(event.clone()).is_err());\n }\n\n #[test]\n fn test_compare_workerset_message_with_event() {\n let (message, log) = mock_workerset_message_with_log();\n\n let GatewayEvent::OperatorshipTransferred(event) = parse_log(\u0026log).unwrap() else {\n panic!(\"Invalid event type\")\n };\n\n assert!(message.compare_with_event(event.clone()).is_ok());\n let mut modified_message = message.clone();\n modified_message.new_operators_data = String::from(\"\");\n assert!(modified_message.compare_with_event(event.clone()).is_err());\n }\n\n #[test]\n fn test_compare_content_with_log() {\n let (message, contractcall_log) = mock_contractcall_message_with_log();\n let (workerset_message, operatorship_log) = mock_workerset_message_with_log();\n\n // assert happy path\n assert!(compare_content_with_log(\n ContentVariant::Message(message.clone()),\n \u0026contractcall_log\n )\n .is_ok());\n assert!(compare_content_with_log(\n ContentVariant::WorkerSet(workerset_message.clone()),\n \u0026operatorship_log\n )\n .is_ok());\n\n // assert failure in either of the messages\n let mut modified_message = message.clone();\n let mut modified_workerset_message = workerset_message.clone();\n\n modified_message.payload_hash = \u003c[u8; 32]\u003e::default();\n modified_workerset_message.new_operators_data = String::from(\"\");\n assert!(compare_content_with_log(\n ContentVariant::Message(modified_message.clone()),\n \u0026contractcall_log\n )\n .is_err());\n assert!(compare_content_with_log(\n ContentVariant::WorkerSet(modified_workerset_message.clone()),\n \u0026operatorship_log\n )\n .is_err());\n }\n\n #[test]\n fn test_parse_log_contractcall() {\n let (message, log) = mock_contractcall_message_with_log();\n\n let parsing_result = parse_log(\u0026log);\n assert!(parsing_result.is_ok());\n let GatewayEvent::ContactCall(event) = parsing_result.unwrap() else {\n panic!(\"Unexpected log\")\n };\n assert_eq!(\n event.source_address.unwrap().to_string().to_lowercase(),\n message.source_address.to_string()\n );\n assert_eq!(\n event.destination_chain.unwrap().to_string().to_lowercase(),\n message.destination_chain.to_string()\n );\n assert_eq!(\n event\n .destination_address\n .unwrap()\n .to_string()\n .to_lowercase(),\n message.destination_address.to_string()\n );\n assert_eq!(event.payload_hash.unwrap(), message.payload_hash);\n }\n\n #[test]\n fn test_parse_log_operatorship() {\n let (_, log) = mock_workerset_message_with_log();\n\n let parsing_result = parse_log(\u0026log);\n assert!(parsing_result.is_ok());\n let GatewayEvent::OperatorshipTransferred(event) = parsing_result.unwrap() else {\n panic!(\"Unexpected log\")\n };\n assert_eq!(\n event.new_operators_data.unwrap(),\n hex::encode(\n decode(\u0026[ParamType::Bytes], log.data.as_slice()).unwrap()[0]\n .clone()\n .into_bytes()\n .unwrap()\n )\n );\n }\n\n #[test]\n fn test_parse_log_failure() {\n let (_, log) = mock_contractcall_message_with_log();\n\n let mut broken_log = log.clone();\n broken_log.topics.remove(0);\n assert!(parse_log(\u0026broken_log).is_err());\n\n // fails to decode with malformed topics\n let mut broken_log = log.clone();\n broken_log.topics.remove(broken_log.topics.len() - 1);\n assert!(parse_log(\u0026broken_log).is_err());\n broken_log.topics = vec![];\n assert!(parse_log(\u0026broken_log).is_err());\n\n // fails to decode with malformed data\n let mut broken_log = log.clone();\n broken_log.data = vec![1, 2, 3];\n assert!(parse_log(\u0026broken_log).is_err());\n }\n\n #[test]\n fn test_parse_message_id() {\n let id = nonempty::String::try_from(\n \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef:123\",\n )\n .unwrap();\n let result = parse_message_id(\u0026id).unwrap();\n assert_eq!(\n result.0,\n \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"\n );\n assert_eq!(result.1, 123);\n\n let id = nonempty::String::try_from(\"invalid_format\").unwrap();\n assert!(parse_message_id(\u0026id).is_err());\n\n let id = nonempty::String::try_from(\"0123:123\").unwrap();\n assert!(parse_message_id(\u0026id).is_err());\n\n let id = nonempty::String::try_from(\n \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef:abc\",\n )\n .unwrap();\n assert!(parse_message_id(\u0026id).is_err());\n }\n\n #[test]\n fn test_recent_block() {\n let data = get_batched_data(false, \"finality\").1;\n let UpdateVariant::Finality(update) = data.update.clone() else {\n panic!(\"Invalid update type\")\n };\n let optimistic = OptimisticUpdate::from(\u0026update);\n\n assert_eq!(update.finalized_header.beacon, data.update.recent_block());\n\n assert_eq!(\n update.attested_header.beacon,\n UpdateVariant::Optimistic(optimistic).recent_block()\n );\n }\n\n #[test]\n fn test_hex_str_to_bytes_with_prefix() {\n let mut hex_str = \"0x1a2b3c\";\n let mut expected_bytes = vec![0x1a, 0x2b, 0x3c];\n let mut result =\n hex_str_to_bytes(hex_str).expect(\"Failed to convert hex string with prefix\");\n assert_eq!(\n result, expected_bytes,\n \"Bytes do not match expected output for prefixed hex string\"\n );\n\n hex_str = \"1a2b3c\";\n expected_bytes = vec![0x1a, 0x2b, 0x3c];\n result = hex_str_to_bytes(hex_str).expect(\"Failed to convert hex string without prefix\");\n assert_eq!(\n result, expected_bytes,\n \"Bytes do not match expected output for non-prefixed hex string\"\n );\n\n let invalid_hex_str = \"zzz\";\n let result = hex_str_to_bytes(invalid_hex_str);\n assert!(result.is_err(), \"Expected an error for invalid hex string\");\n\n let empty_str = \"\";\n let result = hex_str_to_bytes(empty_str).expect(\"Failed to convert empty string\");\n assert!(result.is_empty(), \"Result should be empty for empty string\");\n }\n\n #[test]\n fn test_calc_sync_period() {\n assert_eq!(calc_sync_period(8191), 0);\n assert_eq!(calc_sync_period(8192), 1);\n assert_eq!(calc_sync_period(7930324), 968);\n }\n\n #[test]\n fn test_verify_update_participation() {\n let lightclient = init_lightclient(None);\n\n let mut update = get_update(862);\n update.sync_aggregate.sync_committee_bits = Bitvector::default();\n\n let err = lightclient.verify_update(\u0026update).unwrap_err();\n\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InsufficientParticipation.to_string()\n );\n }\n\n #[test]\n fn test_verify_update_time() {\n let lightclient = init_lightclient(None);\n\n let mut update = get_update(862);\n update.signature_slot = SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .unwrap()\n .as_secs()\n + 12;\n let mut err = lightclient.verify_update(\u0026update).unwrap_err();\n\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidTimestamp.to_string()\n );\n\n update = get_update(862);\n update.signature_slot = update.attested_header.beacon.slot;\n err = lightclient.verify_update(\u0026update).unwrap_err();\n\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidTimestamp.to_string()\n );\n\n update = get_update(862);\n update.finalized_header.beacon.slot = update.attested_header.beacon.slot + 1;\n err = lightclient.verify_update(\u0026update).unwrap_err();\n\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidTimestamp.to_string()\n );\n }\n\n #[test]\n fn test_verify_update_period() {\n let mut lightclient = init_lightclient(None);\n // current period is 862, without a sync committee\n let mut update = get_update(863);\n\n let mut err = lightclient.verify_update(\u0026update).unwrap_err();\n\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidPeriod.to_string()\n );\n\n // properly sync with period 862, store sync committee\n update = get_update(862);\n lightclient.apply_update(\u0026update).unwrap();\n\n // update was applied\n assert!(lightclient.state.next_sync_committee.is_some());\n\n // update period \u003e current period + 1\n update = get_update(864);\n err = lightclient.verify_update(\u0026update).unwrap_err();\n\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidPeriod.to_string()\n );\n }\n\n #[test]\n fn test_verify_update_relevance() {\n let mut lightclient = init_lightclient(None);\n let mut update = get_update(862);\n lightclient.apply_update(\u0026update).unwrap();\n\n update.attested_header.beacon.slot = lightclient.state.update_slot;\n update.finalized_header.beacon.slot = lightclient.state.update_slot;\n assert!(lightclient.state.next_sync_committee.is_some());\n let mut err = lightclient.verify_update(\u0026update).unwrap_err();\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::NotRelevant.to_string()\n );\n\n update = get_update(862);\n update.attested_header.beacon.slot = lightclient.state.update_slot - (256 * 32);\n update.finalized_header.beacon.slot = lightclient.state.update_slot - (256 * 32) - 1; // subtracting 1 for a regression bug\n lightclient.state.next_sync_committee = None;\n err = lightclient.verify_update(\u0026update).unwrap_err();\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::NotRelevant.to_string()\n );\n }\n\n #[test]\n fn test_verify_update_finality_proof() {\n let lightclient = init_lightclient(None);\n let mut update = get_update(862);\n\n update.finality_branch = vec![];\n let mut err = lightclient.verify_update(\u0026update).unwrap_err();\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidFinalityProof.to_string()\n );\n\n update = get_update(862);\n update.finalized_header.beacon.state_root = Node::default();\n err = lightclient.verify_update(\u0026update).unwrap_err();\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidFinalityProof.to_string()\n );\n }\n\n #[test]\n fn test_verify_update_invalid_committee() {\n let lightclient = init_lightclient(None);\n\n let mut update = get_update(862);\n update.next_sync_committee.public_keys[0] = BlsPublicKey::default();\n let err = lightclient.verify_update(\u0026update).unwrap_err();\n\n assert_eq!(\n err.to_string(),\n lightclient::ConsensusError::InvalidNextSyncCommitteeProof.to_string()\n );\n }\n\n #[test]\n fn test_verify_update_invalid_sig() {\n let lightclient = init_lightclient(None);\n\n let mut update = get_update(862);\n update.sync_aggregate.sync_committee_signature = BlsSignature::default();\n\n let err = lightclient.verify_update(\u0026update).err().unwrap();\n assert_eq!(\n err.to_string(),\n ConsensusError::InvalidSignature.to_string()\n );\n }\n\n #[test]\n fn test_verify_update() {\n let lightclient = init_lightclient(None);\n\n let update = get_update(862);\n let res = lightclient.verify_update(\u0026update);\n assert!(res.is_ok());\n }\n\n #[test]\n fn test_apply_first_update() {\n let mut lightclient = init_lightclient(None);\n let update = get_update(862);\n let bootstrap = get_bootstrap();\n\n let res = lightclient.apply_update(\u0026update);\n assert!(res.is_ok());\n assert_eq!(\n lightclient.state.update_slot, update.finalized_header.beacon.slot,\n \"update_slot should be set after applying update\"\n );\n assert_eq!(\n lightclient.state.current_sync_committee, bootstrap.current_sync_committee,\n \"current_sync_committee should be unchanged\"\n );\n assert_eq!(\n lightclient.state.next_sync_committee.unwrap(),\n update.next_sync_committee,\n \"next_sync_committee should be set after applying update\"\n );\n }\n\n #[test]\n fn test_apply_next_period_update() {\n let mut lightclient = init_lightclient(None);\n\n assert!(lightclient.apply_update(\u0026get_update(862)).is_ok());\n let state_before_update = lightclient.state.clone();\n\n let update = get_update(863);\n assert!(lightclient.apply_update(\u0026update).is_ok());\n\n assert_eq!(\n lightclient.state.update_slot, update.finalized_header.beacon.slot,\n \"update_slot should be set after applying update\"\n );\n assert_eq!(\n lightclient.state.current_sync_committee,\n state_before_update.next_sync_committee.unwrap(),\n \"current_sync_committee was updated with previous next_sync_committee\"\n );\n assert_eq!(\n lightclient.state.next_sync_committee.clone().unwrap(),\n update.next_sync_committee,\n \"next_sync_committee was updated\"\n );\n }\n\n #[test]\n #[ignore]\n // TODO: need two updates from the same period\n fn test_apply_same_period_update() {\n let mut lightclient = init_lightclient(None);\n let update = get_update(862);\n\n assert!(lightclient.apply_update(\u0026update).is_ok());\n let state_before_update = lightclient.state.clone();\n // apply again\n assert!(lightclient.apply_update(\u0026update).is_ok());\n\n assert_ne!(\n lightclient.state.update_slot,\n state_before_update.update_slot,\n );\n assert_eq!(\n lightclient.state.update_slot, update.finalized_header.beacon.slot,\n \"update_slot should be set after applying update\"\n );\n assert_eq!(\n lightclient.state.current_sync_committee, state_before_update.current_sync_committee,\n \"current_sync_committee should be unchanged\"\n );\n assert_eq!(\n lightclient.state.next_sync_committee, state_before_update.next_sync_committee,\n \"next_sync_committee should be unchanged\"\n );\n }\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","msg.rs"],"content":"use cosmwasm_std::Response;\nuse cw_ownable::{cw_ownable_execute, cw_ownable_query};\nuse types::common::{Config, WorkerSetMessage};\nuse types::connection_router::state::Message;\nuse types::consensus::{Bootstrap, Update};\nuse types::cosmwasm_schema::*;\nuse types::proofs::BatchVerificationData;\n\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\npub struct InstantiateMsg {\n pub bootstrap: Bootstrap,\n pub config: Config,\n}\n\n#[cw_ownable_execute]\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\n#[allow(clippy::large_enum_variant)] // TODO: Properly fix this\npub enum ExecuteMsg {\n LightClientUpdate(Update),\n BatchVerificationData(BatchVerificationData),\n UpdateConfig(Config),\n VerifyMessages { messages: Vec\u003cMessage\u003e },\n}\n\n#[cw_ownable_query]\n#[cw_serde]\n#[derive(QueryResponses)]\npub enum QueryMsg {\n #[returns(Response)]\n LightClientState {},\n #[returns(Response)]\n Config {},\n #[returns(Vec\u003c(Message, bool)\u003e)]\n IsVerified { messages: Vec\u003cMessage\u003e },\n #[returns(bool)]\n IsWorkerSetVerified { message: WorkerSetMessage },\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","contracts","light-client","src","state.rs"],"content":"use cw_storage_plus::{Item, Map};\nuse types::axelar_wasm_std::hash::Hash;\nuse types::common::{Config, WorkerSetMessage};\nuse types::connection_router::state::Message;\nuse types::lightclient::LightClientState;\n\npub const CONFIG: Item\u003cConfig\u003e = Item::new(\"config\");\npub const LIGHT_CLIENT_STATE: Item\u003cLightClientState\u003e = Item::new(\"light_client_state\");\npub const VERIFIED_MESSAGES: Map\u003cHash, Message\u003e = Map::new(\"verified_messages\");\n\npub const VERIFIED_WORKER_SETS: Map\u003cString, WorkerSetMessage\u003e = Map::new(\"worker_sets\");\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","eth","src","consensus.rs"],"content":"use crate::{error::RPCError, types::*};\nuse async_trait::async_trait;\nuse mockall::automock;\nuse reqwest_middleware::{ClientBuilder, ClientWithMiddleware};\nuse reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};\nuse std::{cmp, time::Duration};\nuse types::consensus::{BeaconBlockAlias, Bootstrap, FinalityUpdate, OptimisticUpdate, Update};\nuse types::ssz_rs::Vector;\nuse types::sync_committee_rs::{\n consensus_types::BeaconBlockHeader,\n constants::{Root, SLOTS_PER_HISTORICAL_ROOT},\n};\n\n/// The thin wrapper around the BeaconAPI overloaded with custom methods\n#[async_trait]\npub trait EthBeaconAPI: Sync + Send + 'static {\n /// Get the block root for a given slot.\n async fn get_block_root(\u0026self, slot: u64) -\u003e Result\u003cRoot, RPCError\u003e;\n /// Get the light client bootstrap for a given block root.\n async fn get_bootstrap(\u0026self, block_root: \u0026'_ [u8]) -\u003e Result\u003cBootstrap, RPCError\u003e;\n /// Get the light client updates for a given period range.\n async fn get_updates(\u0026self, period: u64, count: u8) -\u003e Result\u003cVec\u003cUpdate\u003e, RPCError\u003e;\n /// Get the latest light client finality update.\n async fn get_finality_update(\u0026self) -\u003e Result\u003cFinalityUpdate, RPCError\u003e;\n /// Get the latest light client optimistic update.\n async fn get_optimistic_update(\u0026self) -\u003e Result\u003cOptimisticUpdate, RPCError\u003e;\n /// Get the beacon block header for a given slot.\n async fn get_latest_beacon_block_header(\u0026self) -\u003e Result\u003cBeaconBlockHeader, RPCError\u003e;\n /// Get the beacon block header for a given slot.\n async fn get_beacon_block_header(\u0026self, slot: u64) -\u003e Result\u003cBeaconBlockHeader, RPCError\u003e;\n /// Get the beacon block for a given slot.\n async fn get_beacon_block(\u0026self, slot: u64) -\u003e Result\u003cBeaconBlockAlias, RPCError\u003e;\n /// Get the block roots tree for a given period. This will return a vector of length\n /// `SLOTS_PER_HISTORICAL_ROOT`. If any of the block roots fail to resolve,\n /// the previous root will be used instead. It uses the Block Roots Archive.\n async fn get_block_roots_for_period(\n \u0026self,\n period: u64,\n ) -\u003e Result\u003cVector\u003cRoot, SLOTS_PER_HISTORICAL_ROOT\u003e, RPCError\u003e;\n}\n\n/// A client for interacting with the Ethereum consensus layer.\npub struct ConsensusRPC {\n rpc: String,\n block_roots_rpc: String,\n client: ClientWithMiddleware,\n}\n\n#[allow(dead_code)]\nimpl ConsensusRPC {\n /// Create a new consensus rpc client. The client is configured with a\n /// retry policy that will retry transient errors up to 3 times.\n pub fn new(rpc: String, block_roots_rpc: String, config: EthConfig) -\u003e Self {\n let retry_policy =\n ExponentialBackoff::builder().build_with_max_retries(config.rpc_max_retries as u32);\n\n let client = reqwest::Client::builder()\n .pool_max_idle_per_host(config.pool_max_idle_per_host)\n .connect_timeout(Duration::from_secs(config.timeout_secs))\n .timeout(Duration::from_secs(60))\n .build()\n .unwrap();\n\n let client = ClientBuilder::new(client)\n .with(RetryTransientMiddleware::new_with_policy(retry_policy))\n .build();\n\n ConsensusRPC {\n rpc,\n block_roots_rpc,\n client,\n }\n }\n}\n\n#[automock]\n#[async_trait]\nimpl EthBeaconAPI for ConsensusRPC {\n async fn get_block_root(\u0026self, slot: u64) -\u003e Result\u003cRoot, RPCError\u003e {\n let req = format!(\"{}/eth/v1/beacon/blocks/{}/root\", self.rpc, slot);\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() == reqwest::StatusCode::NOT_FOUND {\n return Err(RPCError::NotFoundError(slot.to_string()));\n }\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cBlockRootResponse\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.data.root)\n }\n\n async fn get_bootstrap(\u0026self, block_root: \u0026'_ [u8]) -\u003e Result\u003cBootstrap, RPCError\u003e {\n let root_hex = hex::encode(block_root);\n let req = format!(\n \"{}/eth/v1/beacon/light_client/bootstrap/0x{}\",\n self.rpc, root_hex\n );\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cBootstrapResponse\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.data)\n }\n\n async fn get_updates(\u0026self, period: u64, count: u8) -\u003e Result\u003cVec\u003cUpdate\u003e, RPCError\u003e {\n let count = cmp::min(count, 10);\n let req = format!(\n \"{}/eth/v1/beacon/light_client/updates?start_period={}\u0026count={}\",\n self.rpc, period, count\n );\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cVec\u003cUpdateData\u003e\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.into_iter().map(|d| d.data).collect())\n }\n\n async fn get_finality_update(\u0026self) -\u003e Result\u003cFinalityUpdate, RPCError\u003e {\n let req = format!(\"{}/eth/v1/beacon/light_client/finality_update\", self.rpc);\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cFinalityUpdateData\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.data)\n }\n\n async fn get_optimistic_update(\u0026self) -\u003e Result\u003cOptimisticUpdate, RPCError\u003e {\n let req = format!(\"{}/eth/v1/beacon/light_client/optimistic_update\", self.rpc);\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cOptimisticUpdateData\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.data)\n }\n\n async fn get_latest_beacon_block_header(\u0026self) -\u003e Result\u003cBeaconBlockHeader, RPCError\u003e {\n let req = format!(\"{}/eth/v1/beacon/headers/head\", self.rpc);\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cBeaconBlockHeaderResponse\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.data.header.message)\n }\n\n async fn get_beacon_block_header(\u0026self, slot: u64) -\u003e Result\u003cBeaconBlockHeader, RPCError\u003e {\n let req = format!(\"{}/eth/v1/beacon/headers/{}\", self.rpc, slot);\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cBeaconBlockHeaderResponse\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.data.header.message)\n }\n\n async fn get_beacon_block(\u0026self, slot: u64) -\u003e Result\u003cBeaconBlockAlias, RPCError\u003e {\n let req = format!(\"{}/eth/v2/beacon/blocks/{}\", self.rpc, slot);\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let data = res\n .json::\u003cBeaconBlockResponse\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(data.data.message)\n }\n\n async fn get_block_roots_for_period(\n \u0026self,\n period: u64,\n ) -\u003e Result\u003cVector\u003cRoot, SLOTS_PER_HISTORICAL_ROOT\u003e, RPCError\u003e {\n let req = format!(\"{}/block_summary?period={}\", self.block_roots_rpc, period);\n\n let res = self\n .client\n .get(\u0026req)\n .send()\n .await\n .map_err(|e| RPCError::RequestError(e.to_string()))?;\n\n if res.status() == reqwest::StatusCode::NOT_FOUND {\n return Err(RPCError::NotFoundError(period.to_string()));\n }\n\n if res.status() != reqwest::StatusCode::OK {\n return Err(RPCError::RequestError(format!(\n \"Unexpected status code: {}\",\n res.status()\n )));\n }\n\n let response = res\n .json::\u003cBlockRootsArchiveResponse\u003e()\n .await\n .map_err(|e| RPCError::DeserializationError(req, e.to_string()))?;\n\n Ok(response.data)\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use httptest::{\n matchers::{request::query, *},\n responders::*,\n Expectation, Server,\n };\n\n fn setup_server_and_rpc() -\u003e (Server, ConsensusRPC) {\n let server = Server::run();\n let url = server.url(\"\");\n let rpc = ConsensusRPC::new(url.to_string(), url.to_string(), EthConfig::default());\n (server, rpc)\n }\n #[tokio::test]\n async fn test_get_block_root() {\n let (server, rpc) = setup_server_and_rpc();\n let result = BlockRootResponse::default();\n let json_res = serde_json::to_string(\u0026result).unwrap();\n\n server.expect(\n Expectation::matching(request::path(matches(\"/eth/v1/beacon/blocks/12345/root\")))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_block_root(12345).await;\n assert_eq!(result.unwrap(), Root::default());\n }\n\n #[tokio::test]\n async fn test_get_updates() {\n let (server, rpc) = setup_server_and_rpc();\n let expected_updates = vec![Update::default(); 3];\n let period = 10u64;\n let count = 3u8;\n\n let response = expected_updates\n .iter()\n .map(|u| UpdateData { data: u.clone() })\n .collect::\u003cVec\u003c_\u003e\u003e();\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n server.expect(\n Expectation::matching(all_of(vec![\n Box::new(request::path(matches(\n \"/eth/v1/beacon/light_client/updates\",\n ))),\n Box::new(request::query(url_decoded(contains((\n \"count\",\n count.to_string(),\n ))))),\n Box::new(request::query(url_decoded(contains((\n \"start_period\",\n period.to_string(),\n ))))),\n ]))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_updates(period, count).await;\n assert_eq!(result.unwrap(), expected_updates);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cVec\u003cUpdate\u003e, _\u003e = rpc.get_updates(period, count).await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_finality_update() {\n let (server, rpc) = setup_server_and_rpc();\n let expected_update = FinalityUpdate::default();\n\n let response = FinalityUpdateData {\n data: expected_update.clone(),\n };\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n server.expect(\n Expectation::matching(request::path(matches(\n \"/eth/v1/beacon/light_client/finality_update\",\n )))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_finality_update().await;\n assert_eq!(result.unwrap(), expected_update);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cFinalityUpdate, _\u003e = rpc.get_finality_update().await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_optimistic_update() {\n let (server, rpc) = setup_server_and_rpc();\n let expected_update = OptimisticUpdate::default();\n\n let response = OptimisticUpdateData {\n data: expected_update.clone(),\n };\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n server.expect(\n Expectation::matching(request::path(matches(\n \"/eth/v1/beacon/light_client/optimistic_update\",\n )))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_optimistic_update().await;\n assert_eq!(result.unwrap(), expected_update);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cOptimisticUpdate, _\u003e = rpc.get_optimistic_update().await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_beacon_block_header() {\n let (server, rpc) = setup_server_and_rpc();\n\n let slot = 12345;\n let expected_header = BeaconBlockHeader::default();\n let response = BeaconBlockHeaderResponse {\n data: BeaconBlockHeaderContainer {\n header: BeaconBlockHeaderMessage {\n message: expected_header.clone(),\n },\n },\n };\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n server.expect(\n Expectation::matching(request::path(matches(format!(\n \"/eth/v1/beacon/headers/{}\",\n slot\n ))))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_beacon_block_header(slot).await;\n assert_eq!(result.unwrap(), expected_header);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cBeaconBlockHeader, _\u003e = rpc.get_beacon_block_header(slot).await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_latest_beacon_block_header() {\n let (server, rpc) = setup_server_and_rpc();\n\n let expected_header = BeaconBlockHeader::default();\n let response = BeaconBlockHeaderResponse {\n data: BeaconBlockHeaderContainer {\n header: BeaconBlockHeaderMessage {\n message: expected_header.clone(),\n },\n },\n };\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n server.expect(\n Expectation::matching(request::path(matches(\"/eth/v1/beacon/headers/head\")))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_latest_beacon_block_header().await;\n assert_eq!(result.unwrap(), expected_header);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cBeaconBlockHeader, _\u003e = rpc.get_latest_beacon_block_header().await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_bootstrap() {\n let (server, rpc) = setup_server_and_rpc();\n\n let expected_bootstrap = Bootstrap::default();\n let block_root = vec![0u8; 32]; // Mock block root\n\n let response = BootstrapResponse {\n data: expected_bootstrap.clone(),\n };\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n server.expect(\n Expectation::matching(request::path(matches(format!(\n \"/eth/v1/beacon/light_client/bootstrap/0x{}\",\n hex::encode(block_root.clone())\n ))))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_bootstrap(\u0026block_root).await;\n assert_eq!(result.unwrap(), expected_bootstrap);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cBootstrap, _\u003e = rpc.get_bootstrap(\u0026block_root).await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_beacon_block() {\n let (server, rpc) = setup_server_and_rpc();\n\n let slot = 12345;\n let expected_block = BeaconBlockAlias::default();\n let response = BeaconBlockResponse {\n data: BeaconBlockContainer {\n message: expected_block.clone(),\n },\n };\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n server.expect(\n Expectation::matching(request::path(matches(format!(\n \"/eth/v2/beacon/blocks/{}\",\n slot\n ))))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_beacon_block(slot).await;\n assert_eq!(result.unwrap(), expected_block);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cBeaconBlockAlias, _\u003e = rpc.get_beacon_block(slot).await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_block_roots_for_period() {\n let (server, rpc) = setup_server_and_rpc();\n\n let response = BlockRootsArchiveResponse::default();\n let json_res = serde_json::to_string(\u0026response).unwrap();\n\n let period = 1000;\n server.expect(\n Expectation::matching(all_of(vec![\n Box::new(request::path(matches(\"block_summary\"))),\n Box::new(request::query(url_decoded(contains((\n \"period\",\n period.to_string(),\n ))))),\n ]))\n .respond_with(status_code(200).body(json_res)),\n );\n\n let result = rpc.get_block_roots_for_period(period).await;\n assert_eq!(result.unwrap(), response.data);\n\n server.expect(Expectation::matching(any()).respond_with(status_code(404)));\n\n let res: Result\u003cVector\u003cRoot, 8192\u003e, _\u003e = rpc.get_block_roots_for_period(period).await;\n assert!(res.is_err());\n }\n}\n","traces":[{"line":53,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":54,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":111,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":130,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":136,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":137,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":138,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":143,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":144,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":145,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":147,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":148,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":151,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":152,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":153,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":157,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":159,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":160,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":162,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":165,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":166,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":168,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":169,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":170,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":172,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":173,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":176,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":177,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":178,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":182,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":184,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":185,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":187,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":190,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":191,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":193,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":194,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":195,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":197,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":198,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":201,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":202,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":203,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":207,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":209,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":210,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":212,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":215,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":216,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":218,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":219,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":220,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":222,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":223,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":226,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":228,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":232,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":234,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":235,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":237,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":240,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":241,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":243,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":244,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":245,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":247,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":248,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":251,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":252,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":253,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":259,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":260,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":262,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":265,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":266,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":268,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":269,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":272,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":273,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":276,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":277,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":278,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":282,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":284,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":285,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":287,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":290,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":294,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":297,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":298,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":300,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":301,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":304,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":307,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":308,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":309,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":310,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":314,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":316,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":317,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":319,"address":[],"length":0,"stats":{"Line":1},"fn_name":null}],"covered":130,"coverable":143},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","eth","src","error.rs"],"content":"use thiserror::Error;\n\n#[derive(Debug, Error, Clone)]\npub enum RPCError {\n #[error(\"Resource not found on request: {0}\")]\n NotFoundError(String),\n #[error(\"Rate limit error on request: {0}\")]\n RateLimitError(String),\n #[error(\"Error sending request: {0}\")]\n RequestError(String),\n #[error(\"UnknownError for request: {0}\")]\n UnknownError(String),\n #[error(\"Error deserializing response: {0} {1}\")]\n DeserializationError(String, String),\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","eth","src","execution.rs"],"content":"use std::str::FromStr;\n\nuse async_trait::async_trait;\nuse ethers::prelude::Http;\nuse ethers::providers::{\n HttpRateLimitRetryPolicy, Middleware, Provider, ProviderError, RetryClient,\n};\nuse ethers::types::{Block, Filter, Log, Transaction, TransactionReceipt, H256, U64};\nuse eyre::Result;\nuse mockall::automock;\n\n/// A trait describing a limited set of methods for the execution layer.\n#[automock]\n#[async_trait]\npub trait EthExecutionAPI {\n /// Get the receipts for a block\n async fn get_block_receipts(\n \u0026self,\n block_number: u64,\n ) -\u003e Result\u003cVec\u003cTransactionReceipt\u003e, ProviderError\u003e;\n /// Get a block by its block number. This method returns the block without\n /// the full transactions.\n async fn get_block(\u0026self, block_number: u64) -\u003e Result\u003cOption\u003cBlock\u003cH256\u003e\u003e, ProviderError\u003e;\n /// Get a block by its block number. This method returns the block with\n /// the full transactions.\n async fn get_block_with_txs(\n \u0026self,\n block_number: u64,\n ) -\u003e Result\u003cOption\u003cBlock\u003cTransaction\u003e\u003e, ProviderError\u003e;\n /// Get multiple blocks by their block numbers.\n async fn get_blocks(\n \u0026self,\n block_numbers: \u0026[u64],\n ) -\u003e Result\u003cVec\u003cOption\u003cBlock\u003cH256\u003e\u003e\u003e, ProviderError\u003e;\n /// Get the latest block number.\n async fn get_latest_block_number(\u0026self) -\u003e Result\u003cU64, ProviderError\u003e;\n /// Get logs for a given filter.\n async fn get_logs(\u0026self, filter: \u0026Filter) -\u003e Result\u003cVec\u003cLog\u003e, ProviderError\u003e;\n}\n\n/// A client for interacting with the Ethereum execution layer.\npub struct ExecutionRPC {\n pub provider: Provider\u003cRetryClient\u003cHttp\u003e\u003e,\n pub rpc: String,\n}\n\nimpl ExecutionRPC {\n pub fn new(rpc: String) -\u003e Self {\n let http = Http::from_str(rpc.as_str()).expect(\"Could not initialize HTTP provider\");\n let mut client = RetryClient::new(http, Box::new(HttpRateLimitRetryPolicy), 100, 50);\n client.set_compute_units(300);\n\n let provider = Provider::new(client);\n\n ExecutionRPC { rpc, provider }\n }\n}\n\n#[cfg(not(tarpaulin_include))]\n#[automock]\n#[async_trait]\nimpl EthExecutionAPI for ExecutionRPC {\n async fn get_block_receipts(\n \u0026self,\n block_number: u64,\n ) -\u003e Result\u003cVec\u003cTransactionReceipt\u003e, ProviderError\u003e {\n self.provider.get_block_receipts(block_number).await\n }\n\n async fn get_block(\u0026self, block_number: u64) -\u003e Result\u003cOption\u003cBlock\u003cH256\u003e\u003e, ProviderError\u003e {\n self.provider.get_block(block_number).await\n }\n\n async fn get_block_with_txs(\n \u0026self,\n block_number: u64,\n ) -\u003e Result\u003cOption\u003cBlock\u003cTransaction\u003e\u003e, ProviderError\u003e {\n self.provider.get_block_with_txs(block_number).await\n }\n\n async fn get_blocks(\n \u0026self,\n block_numbers: \u0026[u64],\n ) -\u003e Result\u003cVec\u003cOption\u003cBlock\u003cH256\u003e\u003e\u003e, ProviderError\u003e {\n let mut futures = vec![];\n for \u0026block_number in block_numbers {\n futures.push(async move { self.get_block(block_number).await });\n }\n\n let results: Result\u003cVec\u003c_\u003e, _\u003e = futures::future::try_join_all(futures).await;\n results\n }\n\n async fn get_latest_block_number(\u0026self) -\u003e Result\u003cU64, ProviderError\u003e {\n self.provider.get_block_number().await\n }\n\n async fn get_logs(\u0026self, filter: \u0026Filter) -\u003e Result\u003cVec\u003cLog\u003e, ProviderError\u003e {\n self.provider.get_logs(filter).await\n }\n}\n\n#[cfg(test)]\nmod tests {\n use crate::execution::ExecutionRPC;\n\n #[test]\n fn test_init() {\n let rpc = ExecutionRPC::new(\"http://localhost:8545\".to_string());\n assert_eq!(rpc.rpc, \"http://localhost:8545\")\n }\n}\n","traces":[{"line":48,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":1},"fn_name":null}],"covered":5,"coverable":5},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","eth","src","lib.rs"],"content":"//! A library for interacting with the Ethereum blockchain.\n//! Provides both Consensus and Execution clients.\n//!\n//! Supports the basic Ethereum JSON-RPC methods, as well as some additional\n//! methods for interacting with the Beacon API like `get_block_roots_tree` for\n//! a given range\n\npub mod consensus;\npub mod error;\npub mod execution;\npub mod types;\npub mod utils;\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","eth","src","types.rs"],"content":"use ethers::types::{Block, Transaction, TransactionReceipt};\nuse types::consensus::{BeaconBlockAlias, Bootstrap, FinalityUpdate, OptimisticUpdate, Update};\nuse types::ssz_rs::{Node, Vector};\nuse types::sync_committee_rs::consensus_types::BeaconBlockHeader;\nuse types::sync_committee_rs::constants::{Root, SLOTS_PER_HISTORICAL_ROOT};\npub type UpdateResponse = Vec\u003cUpdateData\u003e;\n\n/// The basic configuration of the Beacon ETH client\npub struct EthConfig {\n pub pool_max_idle_per_host: usize,\n pub rpc_max_retries: u64,\n pub timeout_secs: u64,\n}\n\nimpl Default for EthConfig {\n fn default() -\u003e Self {\n EthConfig {\n pool_max_idle_per_host: 10,\n timeout_secs: 10,\n rpc_max_retries: 5,\n }\n }\n}\n\n/// A type wrapping all block details in a single structure.\n/// This is used to avoid having to pass around multiple\n/// parameters.\n#[derive(Debug, Default)]\npub struct FullBlockDetails {\n pub exec_block: Block\u003cTransaction\u003e,\n pub beacon_block: BeaconBlockAlias,\n pub receipts: Vec\u003cTransactionReceipt\u003e,\n}\n\n#[derive(serde::Deserialize, serde::Serialize)]\npub struct UpdateData {\n pub data: Update,\n}\n\n#[derive(serde::Deserialize, serde::Serialize)]\npub struct BootstrapResponse {\n pub data: Bootstrap,\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug)]\npub struct BeaconBlockHeaderResponse {\n pub data: BeaconBlockHeaderContainer,\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug)]\npub struct BeaconBlockHeaderContainer {\n pub header: BeaconBlockHeaderMessage,\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug)]\npub struct BeaconBlockHeaderMessage {\n pub message: BeaconBlockHeader,\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug)]\npub struct BeaconBlockResponse {\n pub data: BeaconBlockContainer,\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug)]\npub struct BeaconBlockContainer {\n pub message: BeaconBlockAlias,\n}\n#[derive(serde::Deserialize, serde::Serialize)]\npub struct FinalityUpdateData {\n pub data: FinalityUpdate,\n}\n\n#[derive(serde::Deserialize, serde::Serialize)]\npub struct OptimisticUpdateData {\n pub data: OptimisticUpdate,\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]\npub struct BlockRootResponse {\n pub data: BlockRoot,\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]\npub struct BlockRoot {\n pub root: Node,\n}\n\n#[derive(Debug, serde::Deserialize, serde::Serialize, Default)]\npub struct BlockRootsArchiveResponse {\n pub data: Vector\u003cRoot, SLOTS_PER_HISTORICAL_ROOT\u003e,\n}\n","traces":[{"line":16,"address":[],"length":0,"stats":{"Line":9},"fn_name":null}],"covered":1,"coverable":1},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","eth","src","utils.rs"],"content":"use crate::{consensus::EthBeaconAPI, execution::EthExecutionAPI, types::FullBlockDetails};\nuse eyre::{eyre, Context, Result};\nuse std::sync::Arc;\n\n/// Calculate the slot from a timestamp.\npub fn calc_slot_from_timestamp(genesis_time: u64, timestamp: u64) -\u003e u64 {\n (timestamp - genesis_time) / 12\n}\n\n/// Get the full block details for a given block number.\n/// Returns the execution block, beacon block, and receipts for a given block number.\npub async fn get_full_block_details\u003cCR: EthBeaconAPI, ER: EthExecutionAPI\u003e(\n consensus: Arc\u003cCR\u003e,\n execution: Arc\u003cER\u003e,\n block_number: u64,\n genesis_time: u64,\n) -\u003e Result\u003cFullBlockDetails\u003e {\n let exec_block = execution\n .get_block_with_txs(block_number)\n .await\n .wrap_err(format!(\"failed to get exec block {}\", block_number))?\n .ok_or_else(|| eyre!(\"could not find execution block {:?}\", block_number))?;\n\n println!(\n \"Got execution block with timestamp {}\",\n exec_block.timestamp\n );\n let block_slot = calc_slot_from_timestamp(genesis_time, exec_block.timestamp.as_u64());\n\n let beacon_block = consensus\n .get_beacon_block(block_slot)\n .await\n .wrap_err(eyre!(\"failed to get beacon block {}\", block_number))?;\n\n let receipts = execution.get_block_receipts(block_number).await?;\n\n let full_block = FullBlockDetails {\n exec_block,\n beacon_block,\n receipts,\n };\n\n Ok(full_block)\n}\n\n#[cfg(test)]\nmod tests {\n use crate::utils::calc_slot_from_timestamp;\n\n #[test]\n fn test_calc_slot_from_timestamp() {\n let timestamp = 1000 + 24;\n let expected_slot = 2;\n assert_eq!(calc_slot_from_timestamp(1000, timestamp), expected_slot);\n }\n}\n","traces":[{"line":6,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":7,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":12,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":18,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":19,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":20,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":21,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":22,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":24,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":28,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":30,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":32,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":2},"fn_name":null}],"covered":11,"coverable":17},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","lib.rs"],"content":"pub mod prover;\npub mod types;\n\npub use prover::Prover;\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","prover","errors.rs"],"content":"use cita_trie::TrieError;\nuse consensus_types::ssz_rs::MerkleizationError;\nuse eth::error::RPCError;\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\npub enum ProverError {\n #[error(\"TrieProof proof generation error\")]\n TrieProofError(#[from] TrieError),\n\n #[error(\"State prover error: {0}\")]\n StateProverError(#[from] StateProverError),\n\n #[error(\"Merkle proof generation error: {0}\")]\n MerkleProofGenerationError(#[from] MerkleizationError),\n\n #[error(\"Invalid data error {0}\")]\n InvalidDataError(String),\n\n #[error(\"RPC request failed {0}\")]\n RPCError(#[from] RPCError),\n}\n\n#[derive(Error, Debug)]\npub enum StateProverError {\n #[error(\"Request failed: {0}\")]\n RequestError(String),\n #[error(\"Failed to parse response: {0}\")]\n ParseError(String),\n #[error(\"State or block not found: {0}\")]\n NotFoundError(String),\n #[error(\"Network error: {0}\")]\n NetworkError(#[from] reqwest::Error),\n #[error(\"Serialization error: {0}\")]\n SerializationError(#[from] serde_json::Error),\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","prover","mod.rs"],"content":"pub mod errors;\npub mod proof_generator;\npub mod state_prover;\nmod test_helpers;\npub mod types;\npub mod utils;\n\nuse self::{\n errors::ProverError,\n proof_generator::{ProofGenerator, ProofGeneratorAPI},\n state_prover::StateProver,\n types::ProverConfig,\n utils::get_tx_index,\n};\nuse async_trait::async_trait;\nuse consensus_types::ssz_rs::{Merkleized, Node};\nuse consensus_types::sync_committee_rs::{\n consensus_types::BeaconBlockHeader,\n constants::{Root, SLOTS_PER_HISTORICAL_ROOT},\n};\nuse consensus_types::{\n consensus::{to_beacon_header, BeaconBlockAlias},\n proofs::{\n AncestryProof, BatchVerificationData, BlockProofsBatch, ReceiptProof, TransactionProof,\n TransactionProofsBatch, UpdateVariant,\n },\n};\nuse eth::consensus::ConsensusRPC;\nuse ethers::types::{Block, Transaction, TransactionReceipt, H256};\nuse eyre::{eyre, Context, Result};\nuse indexmap::IndexMap;\nuse log::{debug, error, info};\nuse mockall::automock;\nuse std::sync::Arc;\nuse types::BatchContentGroups;\nuse types::EnrichedContent;\n\n#[async_trait]\npub trait ProverAPI {\n /// Receives a batch of messages and returns a batched data structure containing\n /// the messages grouped by block number and tx hash.\n fn batch_messages(\u0026self, contents: \u0026[EnrichedContent]) -\u003e BatchContentGroups;\n /// Receives a batch of contents and returns a batched data structure with\n /// the same groupping (per block and per transaction) enhanced with all the neccessary\n /// proofs up to the provided light client update. If a proof could not be generated for a \n /// message then this messsage is ommitted from the returning structure.\n async fn batch_generate_proofs(\n \u0026self,\n batch_content_groups: BatchContentGroups,\n update: UpdateVariant,\n ) -\u003e Result\u003cBatchVerificationData, ProverError\u003e;\n}\n\n/// This is the basic prover implemntation. It uses the proof generator to\n/// generate a set of proofs from a given update up to a set of batched events.\n///\n/// It employs 2 levels of batching:\n/// - batching per block (i.e. all messages in a batch belong to the same block\n/// share the same ancestry proof from the recent block to the target block) .\n///\n/// - batching per transaction (i.e. all messages in a batch that belong to the\n/// same transaction share the same transaction and receipt_root proof to the\n/// target block, as well as the same ancestry proof.\npub struct Prover\u003cPG\u003e {\n proof_generator: PG,\n}\n\nimpl Prover\u003cProofGenerator\u003cConsensusRPC, StateProver\u003e\u003e {\n pub fn with_config(consensus_rpc: Arc\u003cConsensusRPC\u003e, prover_config: ProverConfig) -\u003e Self {\n let state_prover = StateProver::new(\n prover_config.network,\n prover_config.state_prover_rpc.clone(),\n );\n let proof_generator = ProofGenerator::new(consensus_rpc, state_prover.clone());\n\n Prover { proof_generator }\n }\n}\n\n#[automock]\n#[async_trait]\nimpl\u003cPG: ProofGeneratorAPI + Sync\u003e ProverAPI for Prover\u003cPG\u003e {\n fn batch_messages(\u0026self, contents: \u0026[EnrichedContent]) -\u003e BatchContentGroups {\n let mut groups: BatchContentGroups = IndexMap::new();\n\n for content in contents {\n groups\n .entry(content.exec_block.number.unwrap().as_u64())\n .or_default()\n .entry(content.tx_hash)\n .or_default()\n .push(content.clone());\n }\n\n debug!(\"Batched messages into {} block groups\", groups.len());\n groups\n }\n\n async fn batch_generate_proofs(\n \u0026self,\n batch_content_groups: BatchContentGroups,\n update: UpdateVariant,\n ) -\u003e Result\u003cBatchVerificationData, ProverError\u003e {\n let recent_block = update.recent_block();\n\n let mut block_proofs_batch: Vec\u003cBlockProofsBatch\u003e = vec![];\n for (_, block_groups) in \u0026batch_content_groups {\n let (mut beacon_block, exec_block, receipts) =\n Self::get_block_of_batch(block_groups).unwrap();\n let block_hash = beacon_block.hash_tree_root()?;\n\n let ancestry_proof = self\n .get_ancestry_proof(beacon_block.slot, \u0026recent_block)\n .await;\n if ancestry_proof.is_err() {\n error!(\"Error generating ancestry proof {:?}\", ancestry_proof.err());\n continue;\n }\n\n let mut block_proof = BlockProofsBatch {\n ancestry_proof: ancestry_proof.unwrap(),\n target_block: to_beacon_header(\u0026beacon_block)\n .map_err(|e| ProverError::InvalidDataError(e.to_string()))?,\n transactions_proofs: vec![],\n };\n\n for (tx_hash, contents) in block_groups {\n let tx_index = get_tx_index(\u0026receipts, tx_hash)\n .map_err(|e| ProverError::InvalidDataError(e.to_string()))?;\n\n let transaction_proof = self\n .get_transaction_proof(\u0026beacon_block, block_hash, tx_index)\n .await;\n if transaction_proof.is_err() {\n error!(\n \"Error generating tx proof {} {:?}\",\n tx_hash,\n transaction_proof.err()\n );\n continue;\n }\n\n let receipt_proof = self\n .get_receipt_proof(\u0026exec_block, block_hash, \u0026receipts, tx_index)\n .await;\n if receipt_proof.is_err() {\n error!(\n \"Error generating receipt proof {} {:?}\",\n tx_hash,\n receipt_proof.err()\n );\n continue;\n }\n\n let tx_level_verification = TransactionProofsBatch {\n transaction_proof: transaction_proof.unwrap(),\n receipt_proof: receipt_proof.unwrap(),\n content: contents.iter().map(|m| m.content.clone()).collect(),\n };\n\n block_proof.transactions_proofs.push(tx_level_verification);\n }\n\n block_proofs_batch.push(block_proof);\n }\n\n Ok(BatchVerificationData {\n update,\n target_blocks: block_proofs_batch,\n })\n }\n}\n\nimpl\u003cPG: ProofGeneratorAPI\u003e Prover\u003cPG\u003e {\n pub fn new(proof_generator: PG) -\u003e Self {\n Prover { proof_generator }\n }\n\n /// Returns the first block of a batch of messages. Used to get the block\n /// that this group of messages is related to.\n pub fn get_block_of_batch(\n batch: \u0026IndexMap\u003cH256, Vec\u003cEnrichedContent\u003e\u003e,\n ) -\u003e Result\u003c\n (\n BeaconBlockAlias,\n Block\u003cTransaction\u003e,\n Vec\u003cTransactionReceipt\u003e,\n ),\n \u0026'static str,\n \u003e {\n let messages = batch.values().next().ok_or(\"Batch is empty\")?;\n let first_content = messages.first().ok_or(\"No messages in the batch\")?;\n\n let exec_block = first_content.exec_block.clone();\n let beacon_block = first_content.beacon_block.clone();\n let receipts = first_content.receipts.clone();\n\n Ok((beacon_block, exec_block, receipts))\n }\n\n /// Fetches an ancestry proof from the recent block state to the target block\n /// using either the block_roots or the historical_roots beacon state property.\n pub async fn get_ancestry_proof(\n \u0026self,\n target_block_slot: u64,\n recent_block: \u0026BeaconBlockHeader,\n ) -\u003e Result\u003cAncestryProof\u003e {\n info!(\n \"Will create proof from {} to {}\",\n recent_block.slot, target_block_slot\n );\n if target_block_slot \u003e= recent_block.slot {\n return Err(eyre!(\n \"Target block slot {} is greater than recent block slot {}\",\n target_block_slot,\n recent_block.slot\n ));\n }\n\n let is_in_block_roots_range = target_block_slot \u003c recent_block.slot\n \u0026\u0026 recent_block.slot \u003c= target_block_slot + SLOTS_PER_HISTORICAL_ROOT as u64;\n\n let recent_block_state_id = recent_block.state_root.to_string();\n\n let proof = if is_in_block_roots_range {\n self.proof_generator\n .prove_ancestry_with_block_roots(\u0026target_block_slot, recent_block_state_id.as_str())\n } else {\n self.proof_generator\n .prove_ancestry_with_historical_summaries(\n \u0026target_block_slot,\n \u0026recent_block_state_id,\n )\n }\n .await\n .wrap_err(format!(\n \"Failed to generate ancestry proof for block {:?}\",\n target_block_slot\n ))?;\n\n debug!(\n \"Got full ancestry proof from {} to {}\",\n recent_block.slot, target_block_slot\n );\n Ok(proof)\n }\n\n /// Fetches a proof from a specific receipt to the beacon block root\n pub async fn get_receipt_proof(\n \u0026self,\n exec_block: \u0026Block\u003cTransaction\u003e,\n block_hash: Root,\n receipts: \u0026[TransactionReceipt],\n tx_index: u64,\n ) -\u003e Result\u003cReceiptProof\u003e {\n let receipt_proof = self\n .proof_generator\n .generate_receipt_proof(receipts, tx_index)\n .wrap_err(format!(\n \"Failed to generate receipt proof for block {} and tx: {}\",\n block_hash, tx_index\n ))?;\n\n let receipts_root_proof = self\n .proof_generator\n .generate_receipts_root_proof(block_hash.to_string().as_str())\n .await\n .wrap_err(format!(\n \"Failed to generate receipts root proof for block {} and tx: {}\",\n block_hash, tx_index\n ))?;\n\n let receipt_proof = ReceiptProof {\n receipt_proof,\n receipts_root_proof: receipts_root_proof.witnesses,\n receipts_root_gindex: receipts_root_proof.gindex,\n receipts_root: Node::from_bytes(exec_block.receipts_root.as_bytes().try_into()?),\n };\n\n debug!(\n \"Got receipt proof for block {} and tx: {}\",\n block_hash, tx_index\n );\n Ok(receipt_proof)\n }\n\n /// Fetches a proof from a specific transaction to the beacon block root\n pub async fn get_transaction_proof(\n \u0026self,\n beacon_block: \u0026BeaconBlockAlias,\n block_hash: Root,\n tx_index: u64,\n ) -\u003e Result\u003cTransactionProof\u003e {\n let transaction =\n beacon_block.body.execution_payload().transactions()[tx_index as usize].clone();\n\n let proof = self\n .proof_generator\n .generate_transaction_proof(block_hash.to_string().as_str(), tx_index)\n .await\n .wrap_err(format!(\n \"Failed to generate tx proof for block {}, and tx: {}\",\n block_hash, tx_index\n ))?;\n\n let transaction_proof: TransactionProof = TransactionProof {\n transaction_index: tx_index,\n transaction_gindex: proof.gindex,\n transaction_proof: proof.witnesses,\n transaction,\n };\n\n debug!(\"Got tx proof for block {} and tx: {}\", block_hash, tx_index);\n Ok(transaction_proof)\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::state_prover::MockStateProver;\n use crate::prover::proof_generator::MockProofGenerator;\n use crate::prover::test_helpers::test_utils::*;\n use crate::prover::{Prover, ProverAPI};\n use consensus_types::common::ContentVariant;\n use consensus_types::consensus::to_beacon_header;\n use consensus_types::proofs::{AncestryProof, BatchVerificationData};\n use eth::consensus::MockConsensusRPC;\n use eth::execution::MockExecutionRPC;\n use ethers::types::H256;\n\n fn setup() -\u003e (MockConsensusRPC, MockExecutionRPC, MockStateProver) {\n let consensus_rpc = MockConsensusRPC::new();\n let execution_rpc = MockExecutionRPC::new();\n let state_prover = MockStateProver::new();\n\n (consensus_rpc, execution_rpc, state_prover)\n }\n\n #[tokio::test]\n async fn test_batch_generate_proofs() {\n let mock_update = get_mock_update(true, 1000, 505);\n let batch_content_groups = get_mock_batch_message_groups();\n\n let (_consensus_rpc, _execution_rpc, _state_prover) = setup();\n\n let mut proof_generator = MockProofGenerator::\u003cMockConsensusRPC, MockStateProver\u003e::new();\n proof_generator\n .expect_prove_ancestry_with_block_roots()\n .returning(|_, _| {\n Ok(AncestryProof::BlockRoots {\n block_roots_index: 0,\n block_root_proof: vec![],\n })\n });\n proof_generator\n .expect_generate_transaction_proof()\n .returning(|_, _| Ok(Default::default()));\n proof_generator\n .expect_generate_receipts_root_proof()\n .returning(|_| Ok(Default::default()));\n proof_generator\n .expect_generate_receipt_proof()\n .returning(|_, _| Ok(Default::default()));\n\n let prover = Prover::new(proof_generator);\n\n let res = prover\n .batch_generate_proofs(batch_content_groups, mock_update.clone())\n .await;\n assert!(res.is_ok());\n\n let BatchVerificationData {\n update,\n target_blocks,\n } = res.unwrap();\n\n assert_eq!(update, mock_update);\n assert_eq!(target_blocks.len(), 3);\n assert_eq!(target_blocks[0].transactions_proofs.len(), 1);\n assert_eq!(target_blocks[1].transactions_proofs.len(), 2);\n assert_eq!(target_blocks[2].transactions_proofs.len(), 1);\n\n for (i, target_block) in target_blocks.iter().enumerate() {\n for j in 0..target_block.transactions_proofs.len() {\n let content_count = target_blocks[i].transactions_proofs[j]\n .content\n .iter()\n .filter(|c| matches!(c, ContentVariant::Message(_)))\n .count();\n if i == 1 \u0026\u0026 j == 0 {\n assert_eq!(content_count, 2);\n } else {\n assert_eq!(content_count, 1);\n }\n }\n }\n }\n\n #[tokio::test]\n async fn test_get_ancestry_proof_block_roots() {\n let mut proof_generator = MockProofGenerator::\u003cMockConsensusRPC, MockStateProver\u003e::new();\n\n let recent_block = get_mock_beacon_block(1000);\n let recent_block_header = to_beacon_header(\u0026recent_block).unwrap();\n let target_block_slot = 505;\n\n let proof = AncestryProof::BlockRoots {\n block_roots_index: 0,\n block_root_proof: vec![],\n };\n\n proof_generator\n .expect_prove_ancestry_with_block_roots()\n .returning(move |_, _| Ok(proof.clone()));\n\n let prover = Prover::new(proof_generator);\n\n let res = prover\n .get_ancestry_proof(target_block_slot, \u0026recent_block_header)\n .await;\n assert!(res.is_ok());\n // Assert is blockroots\n assert!(matches!(res.unwrap(), AncestryProof::BlockRoots { .. }));\n }\n\n #[tokio::test]\n async fn test_get_ancestry_proof_failure() {\n let proof_generator = MockProofGenerator::\u003cMockConsensusRPC, MockStateProver\u003e::new();\n let prover = Prover::new(proof_generator);\n\n let recent_block = get_mock_beacon_block(10000);\n let recent_block_header = to_beacon_header(\u0026recent_block).unwrap();\n\n let res = prover\n .get_ancestry_proof(recent_block_header.slot, \u0026recent_block_header)\n .await;\n assert!(res.is_err());\n }\n\n #[tokio::test]\n async fn test_get_ancestry_proof_historical_roots() {\n let mut proof_generator = MockProofGenerator::\u003cMockConsensusRPC, MockStateProver\u003e::new();\n\n let recent_block = get_mock_beacon_block(10000);\n let recent_block_header = to_beacon_header(\u0026recent_block).unwrap();\n let target_block_slot = 1000;\n\n let proof = AncestryProof::HistoricalRoots {\n block_root_proof: Default::default(),\n block_summary_root: Default::default(),\n block_summary_root_proof: Default::default(),\n block_summary_root_gindex: Default::default(),\n };\n\n proof_generator\n .expect_prove_ancestry_with_historical_summaries()\n .returning(move |_, _| Ok(proof.clone()));\n\n let prover = Prover::new(proof_generator);\n\n let res = prover\n .get_ancestry_proof(target_block_slot, \u0026recent_block_header)\n .await;\n assert!(res.is_ok());\n // Assert is blockroots\n assert!(matches!(\n res.unwrap(),\n AncestryProof::HistoricalRoots { .. }\n ));\n }\n\n #[tokio::test]\n async fn test_batch_contents() {\n let _consensus_rpc = MockConsensusRPC::new();\n let mut execution_rpc = MockExecutionRPC::new();\n\n execution_rpc.expect_get_blocks().returning(move |_| {\n Ok(vec![\n Some(get_mock_exec_block(1)),\n Some(get_mock_exec_block(2)),\n Some(get_mock_exec_block(2)),\n Some(get_mock_exec_block(2)),\n Some(get_mock_exec_block(3)),\n ])\n });\n\n let get_tx_hash = H256::from_low_u64_be;\n\n let mock_message1 = get_mock_message(1, 1, get_tx_hash(1));\n let mock_message2 = get_mock_message(2, 2, get_tx_hash(2));\n let mock_message3 = get_mock_message(2, 2, get_tx_hash(2));\n let mock_message4 = get_mock_message(2, 2, get_tx_hash(3));\n let mock_message5 = get_mock_message(3, 3, get_tx_hash(4));\n\n let messages = [\n mock_message1.clone(),\n mock_message2.clone(),\n mock_message3.clone(),\n mock_message4.clone(),\n mock_message5.clone(),\n ];\n\n let proof_generator = MockProofGenerator::\u003cMockConsensusRPC, MockStateProver\u003e::new();\n let prover = Prover::new(proof_generator);\n\n let result = prover.batch_messages(messages.as_ref());\n\n assert_eq!(result.len(), 3);\n assert_eq!(result.get(\u00261).unwrap().len(), 1);\n assert_eq!(result.get(\u00262).unwrap().len(), 2);\n assert_eq!(result.get(\u00263).unwrap().len(), 1);\n assert_eq!(\n result.get(\u00261).unwrap().get(\u0026get_tx_hash(1)).unwrap(),\n \u0026vec![mock_message1]\n );\n assert_eq!(\n result.get(\u00262).unwrap().get(\u0026get_tx_hash(2)).unwrap(),\n \u0026vec![mock_message2, mock_message3]\n );\n assert_eq!(\n result.get(\u00262).unwrap().get(\u0026get_tx_hash(3)).unwrap(),\n \u0026vec![mock_message4]\n );\n assert_eq!(\n result.get(\u00263).unwrap().get(\u0026get_tx_hash(4)).unwrap(),\n \u0026vec![mock_message5]\n );\n }\n}\n","traces":[{"line":69,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":127,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":129,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":132,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":135,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":137,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":138,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":143,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":144,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":145,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":146,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":147,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":149,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":150,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":152,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":156,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":157,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":158,"address":[],"length":0,"stats":{"Line":13},"fn_name":null},{"line":161,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":164,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":167,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":168,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":169,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":175,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":181,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":191,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":192,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":194,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":195,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":196,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":198,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":203,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":208,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":210,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":212,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":213,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":214,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":215,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":216,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":220,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":221,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":223,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":225,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":226,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":229,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":231,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":232,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":235,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":236,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":237,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":238,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":241,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":243,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":245,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":249,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":256,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":258,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":259,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":260,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":261,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":264,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":265,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":266,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":267,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":268,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":269,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":275,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":276,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":277,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":280,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":282,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":284,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":288,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":294,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":295,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":297,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":298,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":299,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":300,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":301,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":302,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":303,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":308,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":309,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":313,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":314,"address":[],"length":0,"stats":{"Line":4},"fn_name":null}],"covered":78,"coverable":122},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","prover","proof_generator.rs"],"content":"use std::sync::Arc;\n\nuse super::utils;\nuse crate::prover::{\n errors::ProverError,\n state_prover::StateProverAPI,\n types::{GindexOrPath, ProofResponse},\n};\nuse async_trait::async_trait;\nuse cita_trie::Trie;\nuse consensus_types::ssz_rs::{\n generate_proof, get_generalized_index, Node, SszVariableOrIndex, Vector,\n};\nuse consensus_types::sync_committee_rs::constants::{\n CAPELLA_FORK_EPOCH, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT,\n};\nuse consensus_types::{consensus::BeaconStateType, proofs::AncestryProof};\nuse eth::consensus::EthBeaconAPI;\nuse ethers::{types::TransactionReceipt, utils::rlp::encode};\nuse log::debug;\nuse mockall::automock;\n\nconst CAPELLA_FORK_SLOT: u64 = CAPELLA_FORK_EPOCH * SLOTS_PER_EPOCH;\n\n#[async_trait]\npub trait ProofGeneratorAPI {\n /// Generates a merkle proof from a specific transaction to the beacon block root.\n async fn generate_transaction_proof(\n \u0026self,\n block_id: \u0026str,\n tx_index: u64,\n ) -\u003e Result\u003cProofResponse, ProverError\u003e;\n /// Generates a merkle proof from the receipts_root to the beacon block root.\n async fn generate_receipts_root_proof(\n \u0026self,\n block_id: \u0026str,\n ) -\u003e Result\u003cProofResponse, ProverError\u003e;\n /// Generates an ancestry proof from the target block to the beacon\n /// state root using the block roots state property. This proof is easy\n /// to be generated but it can only prove blocks up to a period old (~27\n /// hours)\n async fn prove_ancestry_with_block_roots(\n \u0026self,\n target_block_slot: \u0026u64,\n recent_block_state_id: \u0026str,\n ) -\u003e Result\u003cAncestryProof, ProverError\u003e;\n /// Generates an ancestry proof from the target block to the beacon state root\n /// using the historical roots state property. This proof needs the\n /// block_roots tree to be reconstructed (i.e. fetching all 8196 block roots\n /// of a period and then generating the proofs).\n async fn prove_historical_summaries_proof(\n \u0026self,\n target_block_slot: \u0026u64,\n recent_block_state_id: \u0026str,\n ) -\u003e Result\u003cProofResponse, ProverError\u003e;\n /// Generates a proof from the block root to the block summary root state property.\n async fn prove_block_root_to_block_summary_root(\n \u0026self,\n target_block_slot: \u0026u64,\n ) -\u003e Result\u003cVec\u003cNode\u003e, ProverError\u003e;\n /// Generates an ancestry proof from the recent block state to the target block\n /// using the historical_roots beacon state property. The target block should be\n /// in a slot less than recent_block_slot - SLOTS_PER_HISTORICAL_ROOT.\n async fn prove_ancestry_with_historical_summaries(\n \u0026self,\n target_block_slot: \u0026u64,\n recent_block_state_id: \u0026str,\n ) -\u003e Result\u003cAncestryProof, ProverError\u003e;\n /// Generates an Merkle Patricia tree proof from a receipt to the receipts_root.\n fn generate_receipt_proof(\n \u0026self,\n receipts: \u0026[TransactionReceipt],\n index: u64,\n ) -\u003e Result\u003cVec\u003cVec\u003cu8\u003e\u003e, ProverError\u003e;\n}\n\n/// Main proving mechanism for generating proofs from both the execution and the\n/// consensus block to the beacon state or block root.\n#[derive(Clone)]\npub struct ProofGenerator\u003cCR: EthBeaconAPI, SP: StateProverAPI\u003e {\n consensus_rpc: Arc\u003cCR\u003e,\n state_prover: SP,\n}\n\nimpl\u003cCR: EthBeaconAPI, SP: StateProverAPI\u003e ProofGenerator\u003cCR, SP\u003e {\n pub fn new(consensus_rpc: Arc\u003cCR\u003e, state_prover: SP) -\u003e Self {\n ProofGenerator {\n consensus_rpc,\n state_prover,\n }\n }\n}\n\n#[automock]\n#[async_trait]\nimpl\u003cCR: EthBeaconAPI, SP: StateProverAPI\u003e ProofGeneratorAPI for ProofGenerator\u003cCR, SP\u003e {\n /// This implementation generates a merkle proof from a specific transaction\n /// to the beacon block root by calling the state prover (a wrapper around\n /// the proofs endpoint of lodestar).\n async fn generate_transaction_proof(\n \u0026self,\n block_id: \u0026str,\n tx_index: u64,\n ) -\u003e Result\u003cProofResponse, ProverError\u003e {\n let path = vec![\n SszVariableOrIndex::Name(\"body\"),\n SszVariableOrIndex::Name(\"execution_payload\"),\n SszVariableOrIndex::Name(\"transactions\"),\n SszVariableOrIndex::Index(tx_index as usize),\n ];\n\n let proof = self\n .state_prover\n .get_block_proof(block_id, GindexOrPath::Path(path))\n .await?;\n\n debug!(\n \"Got transaction proof from state prover {} {}\",\n block_id, tx_index\n );\n Ok(proof)\n }\n\n /// This implementation generates a merkle proof from the receipts_root to\n /// the beacon block root by calling the state prover's /block_proof endpoint.\n async fn generate_receipts_root_proof(\n \u0026self,\n block_id: \u0026str,\n ) -\u003e Result\u003cProofResponse, ProverError\u003e {\n let path = vec![\n SszVariableOrIndex::Name(\"body\"),\n SszVariableOrIndex::Name(\"execution_payload\"),\n SszVariableOrIndex::Name(\"receipts_root\"),\n ];\n\n let proof = self\n .state_prover\n .get_block_proof(block_id, GindexOrPath::Path(path))\n .await?;\n\n debug!(\"Got receipts_root proof from state prover {}\", block_id);\n Ok(proof)\n }\n\n /// This implementation generates an ancestry proof from the target block to a recent block.\n /// The target block cannot be older than SLOTS_PER_HISTORICAL_ROOT (8192 blocks, ~27 hours).\n async fn prove_ancestry_with_block_roots(\n \u0026self,\n target_block_slot: \u0026u64,\n recent_block_state_id: \u0026str,\n ) -\u003e Result\u003cAncestryProof, ProverError\u003e {\n let index = target_block_slot % SLOTS_PER_HISTORICAL_ROOT as u64;\n let g_index_from_state_root = get_generalized_index(\n \u0026BeaconStateType::default(),\n \u0026[\n SszVariableOrIndex::Name(\"block_roots\"),\n SszVariableOrIndex::Index(index as usize),\n ],\n );\n\n let res = self\n .state_prover\n .get_state_proof(\n recent_block_state_id,\n \u0026GindexOrPath::Gindex(g_index_from_state_root),\n )\n .await?;\n\n let ancestry_proof = AncestryProof::BlockRoots {\n block_roots_index: g_index_from_state_root as u64,\n block_root_proof: res.witnesses,\n };\n\n debug!(\n \"Got ancestry proof with block roots from {} to {}\",\n target_block_slot, recent_block_state_id\n );\n Ok(ancestry_proof)\n }\n\n /// This implementation uses the state prover to fetch a proof from the\n /// state root to the specific historical summary of the period we're\n /// interested in.\n async fn prove_historical_summaries_proof(\n \u0026self,\n target_block_slot: \u0026u64,\n recent_block_state_id: \u0026str,\n ) -\u003e Result\u003cProofResponse, ProverError\u003e {\n let historical_summaries_index =\n (target_block_slot - CAPELLA_FORK_SLOT) / SLOTS_PER_HISTORICAL_ROOT as u64;\n\n let path = vec![\n SszVariableOrIndex::Name(\"historical_summaries\"),\n SszVariableOrIndex::Index(historical_summaries_index as usize),\n SszVariableOrIndex::Name(\"block_summary_root\"),\n ];\n\n let res = self\n .state_prover\n .get_state_proof(recent_block_state_id, \u0026GindexOrPath::Path(path))\n .await?;\n\n debug!(\n \"Got historical summaries proof from {} to {}\",\n target_block_slot, recent_block_state_id\n );\n\n Ok(res)\n }\n\n async fn prove_block_root_to_block_summary_root(\n \u0026self,\n target_block_slot: \u0026u64,\n ) -\u003e Result\u003cVec\u003cNode\u003e, ProverError\u003e {\n let block_root_index = *target_block_slot as usize % SLOTS_PER_HISTORICAL_ROOT;\n let start_slot = target_block_slot - block_root_index as u64;\n\n let mut block_roots = self\n .consensus_rpc\n .get_block_roots_for_period(start_slot / 32 / 256)\n .await?;\n\n let gindex = get_generalized_index(\n \u0026Vector::\u003cNode, SLOTS_PER_HISTORICAL_ROOT\u003e::default(),\n \u0026[SszVariableOrIndex::Index(block_root_index)],\n );\n let proof = generate_proof(\u0026mut block_roots, \u0026[gindex])?;\n\n debug!(\n \"Got block root to block summary root proof from {}\",\n target_block_slot\n );\n Ok(proof)\n }\n\n async fn prove_ancestry_with_historical_summaries(\n \u0026self,\n target_block_slot: \u0026u64,\n recent_block_state_id: \u0026str,\n ) -\u003e Result\u003cAncestryProof, ProverError\u003e {\n if *target_block_slot \u003c CAPELLA_FORK_SLOT {\n return Err(ProverError::InvalidDataError(\n \"Target block epoch is less than CAPELLA_FORK_EPOCH\".to_string(),\n ));\n }\n let historical_summaries_proof = self\n .prove_historical_summaries_proof(target_block_slot, recent_block_state_id)\n .await?;\n\n let block_root_to_block_summary_root = self\n .prove_block_root_to_block_summary_root(target_block_slot)\n .await?;\n\n let res = AncestryProof::HistoricalRoots {\n block_root_proof: block_root_to_block_summary_root,\n block_summary_root_proof: historical_summaries_proof.witnesses,\n block_summary_root: historical_summaries_proof.leaf,\n block_summary_root_gindex: historical_summaries_proof.gindex,\n };\n\n debug!(\n \"Got ancestry proof with historical summaries from {} to {}\",\n target_block_slot, recent_block_state_id\n );\n Ok(res)\n }\n\n fn generate_receipt_proof(\n \u0026self,\n receipts: \u0026[TransactionReceipt],\n index: u64,\n ) -\u003e Result\u003cVec\u003cVec\u003cu8\u003e\u003e, ProverError\u003e {\n let mut trie = utils::generate_trie(receipts.to_owned(), utils::encode_receipt);\n let _trie_root = trie.root().unwrap();\n\n let receipt_index = encode(\u0026index);\n let proof = trie.get_proof(receipt_index.to_vec().as_slice())?;\n\n debug!(\"Got receipt proof for {}\", index);\n Ok(proof)\n }\n}\n\n#[cfg(test)]\nmod tests {\n use std::fs::File;\n use std::sync::Arc;\n\n use super::{ProofGenerator, ProofGeneratorAPI};\n use crate::prover::state_prover::MockStateProver;\n use crate::prover::test_helpers::test_utils::*;\n use crate::prover::types::GindexOrPath;\n use crate::prover::types::ProofResponse;\n use crate::prover::utils::parse_path;\n use consensus_types::consensus::BeaconBlockAlias;\n use consensus_types::proofs::AncestryProof;\n use consensus_types::ssz_rs::{\n get_generalized_index, verify_merkle_proof, GeneralizedIndex, Merkleized, Node,\n SszVariableOrIndex, Vector,\n };\n use consensus_types::sync_committee_rs::constants::Root;\n use consensus_types::sync_committee_rs::{\n consensus_types::BeaconBlockHeader, constants::SLOTS_PER_HISTORICAL_ROOT,\n };\n use eth::consensus::EthBeaconAPI;\n use eth::consensus::MockConsensusRPC;\n use tokio::test as tokio_test;\n\n async fn setup_block_and_provers(\n consensus_block_number: u64,\n ) -\u003e (\n Arc\u003cMockConsensusRPC\u003e,\n MockStateProver,\n BeaconBlockAlias,\n Node,\n ) {\n let mut consensus = MockConsensusRPC::new();\n\n consensus\n .expect_get_beacon_block_header()\n .returning(|slot| {\n let filename = format!(\"./src/prover/testdata/beacon_block_headers/{}.json\", slot);\n let file = File::open(filename).unwrap();\n let res: BeaconBlockHeader = serde_json::from_reader(file).unwrap();\n Ok(res)\n });\n\n consensus.expect_get_beacon_block().returning(|slot| {\n let filename = format!(\"./src/prover/testdata/beacon_blocks/{}.json\", slot);\n let file = File::open(filename).unwrap();\n let res: BeaconBlockAlias = serde_json::from_reader(file).unwrap();\n Ok(res)\n });\n\n consensus\n .expect_get_block_roots_for_period()\n .returning(|_| {\n let file = File::open(\"./src/prover/testdata/block_roots.json\").unwrap();\n let tree: Vector\u003c_, SLOTS_PER_HISTORICAL_ROOT\u003e =\n serde_json::from_reader(file).unwrap();\n Ok(tree)\n });\n\n let mut state_prover = MockStateProver::new();\n\n state_prover\n .expect_get_state_proof()\n .returning(|state_id, gindex_or_path| {\n let filename = match gindex_or_path {\n GindexOrPath::Gindex(gindex) =\u003e {\n format!(\"state_proof_{}_g{}.json\", state_id, gindex)\n }\n GindexOrPath::Path(path) =\u003e {\n let path = parse_path(path);\n format!(\"state_proof_{}_{}.json\", state_id, path)\n }\n };\n\n let filename = format!(\"./src/prover/testdata/state_prover/{}\", filename);\n let file = File::open(filename).unwrap();\n\n let res: ProofResponse = serde_json::from_reader(file).unwrap();\n\n Ok(res)\n });\n\n state_prover\n .expect_get_block_proof()\n .returning(|block_id, gindex_or_path| {\n let filename = match gindex_or_path {\n GindexOrPath::Gindex(gindex) =\u003e {\n format!(\"block_proof_{}_g{}.json\", block_id, gindex)\n }\n GindexOrPath::Path(path) =\u003e {\n let path = parse_path(\u0026path);\n format!(\"block_proof_{}_{}.json\", block_id, path)\n }\n };\n\n let filename = format!(\"./src/prover/testdata/state_prover/{}\", filename);\n let file = File::open(filename).unwrap();\n\n let res: ProofResponse = serde_json::from_reader(file).unwrap();\n\n Ok(res)\n });\n\n let mut block = consensus\n .get_beacon_block(consensus_block_number)\n .await\n .unwrap();\n let block_root = block.hash_tree_root().unwrap();\n\n (Arc::new(consensus), state_prover, block, block_root)\n }\n\n #[tokio_test]\n async fn test_transactions_proof_valid() {\n let (consensus, state_prover, mut block, block_root) =\n setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n let tx_index = 15;\n\n let transaction = \u0026mut block.body.execution_payload_mut().transactions_mut()[tx_index];\n let node = transaction.hash_tree_root().unwrap();\n\n let proof = proof_generator\n .generate_transaction_proof(\u0026block_root.to_string(), tx_index as u64)\n .await\n .unwrap();\n\n let is_proof_valid = verify_merkle_proof(\n \u0026node,\n proof.witnesses.as_slice(),\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026block_root,\n );\n\n assert!(is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_transactions_proof_invalid_transaction() {\n let (consensus, state_prover, mut block, block_root) =\n setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let tx_index = 15;\n\n let transaction = \u0026mut block.body.execution_payload_mut().transactions_mut()[tx_index];\n transaction[0] = 0;\n\n let node = transaction.hash_tree_root().unwrap();\n\n let proof = proof_generator\n .generate_transaction_proof(\u0026block_root.to_string(), tx_index as u64)\n .await\n .unwrap();\n\n let mut invalid_block_root = block_root;\n invalid_block_root.0[31] = 0;\n\n let is_proof_valid = verify_merkle_proof(\n \u0026node,\n \u0026proof.witnesses,\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026invalid_block_root,\n );\n\n assert!(!is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_transactions_proof_wrong_transaction() {\n let (consensus, state_prover, mut block, block_root) =\n setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n let tx_index = 15;\n\n // Different transaction\n let transaction = \u0026mut block.body.execution_payload_mut().transactions_mut()[16];\n let node = transaction.hash_tree_root().unwrap();\n\n let proof = proof_generator\n .generate_transaction_proof(\u0026block_root.to_string(), tx_index as u64)\n .await\n .unwrap();\n\n let is_proof_valid = verify_merkle_proof(\n \u0026node,\n \u0026proof.witnesses,\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026block_root,\n );\n\n assert!(!is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_transactions_proof_invalid_block_root() {\n let (consensus, state_prover, mut block, block_root) =\n setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let tx_index = 15;\n let transaction = \u0026mut block.body.execution_payload_mut().transactions_mut()[tx_index];\n let node = transaction.hash_tree_root().unwrap();\n\n let proof = proof_generator\n .generate_transaction_proof(\u0026block_root.to_string(), tx_index as u64)\n .await\n .unwrap();\n\n let mut invalid_block_root = block_root;\n invalid_block_root.0[31] = 0;\n\n let is_proof_valid = verify_merkle_proof(\n \u0026node,\n \u0026proof.witnesses,\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026invalid_block_root,\n );\n\n assert!(!is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_transactions_proof_invalid_proof() {\n let (consensus, state_prover, mut block, block_root) =\n setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n let tx_index = 15;\n let transaction = \u0026mut block.body.execution_payload_mut().transactions_mut()[tx_index];\n let node = transaction.hash_tree_root().unwrap();\n\n let proof = proof_generator\n .generate_transaction_proof(\u0026block_root.to_string(), tx_index as u64)\n .await\n .unwrap();\n\n let mut invalid_proof = proof.witnesses.clone();\n invalid_proof[0] = Node::default();\n\n let is_proof_valid = verify_merkle_proof(\n \u0026node,\n \u0026invalid_proof,\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026block_root,\n );\n\n assert!(!is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_receipts_root_proof_valid() {\n let (consensus, state_prover, mut block, block_root) =\n setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let proof = proof_generator\n .generate_receipts_root_proof(\u0026block_root.to_string())\n .await\n .unwrap();\n\n let is_proof_valid = verify_merkle_proof(\n \u0026block\n .body\n .execution_payload_mut()\n .receipts_root_mut()\n .hash_tree_root()\n .unwrap(),\n proof.witnesses.as_slice(),\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026block_root,\n );\n\n assert!(is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_receipts_root_proof_invalid_proof() {\n let (consensus, state_prover, mut block, block_root) =\n setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let proof = proof_generator\n .generate_receipts_root_proof(\u0026block_root.to_string())\n .await\n .unwrap();\n\n let mut invalid_proof = proof.witnesses.clone();\n invalid_proof[0] = Node::default();\n\n let is_proof_valid = verify_merkle_proof(\n \u0026block\n .body\n .execution_payload_mut()\n .receipts_root_mut()\n .hash_tree_root()\n .unwrap(),\n \u0026invalid_proof,\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026block_root,\n );\n\n assert!(!is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_receipts_root_proof_invalid_receipts_root() {\n let (consensus, state_prover, _, block_root) = setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let proof = proof_generator\n .generate_receipts_root_proof(\u0026block_root.to_string())\n .await\n .unwrap();\n\n let is_proof_valid = verify_merkle_proof(\n \u0026Node::default(),\n \u0026proof.witnesses,\n \u0026GeneralizedIndex(proof.gindex as usize),\n \u0026block_root,\n );\n\n assert!(!is_proof_valid)\n }\n\n #[tokio_test]\n async fn test_prove_ancestry_with_block_roots() {\n let (consensus, state_prover, _, _) = setup_block_and_provers(7807119).await;\n let latest_block_7878867 = consensus.get_beacon_block_header(7878867).await.unwrap();\n let latest_block_7879376 = consensus.get_beacon_block_header(7879376).await.unwrap();\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let old_block_slot = 7878867 - 1000;\n let proof = proof_generator\n .prove_ancestry_with_block_roots(\n \u0026old_block_slot,\n \u0026latest_block_7878867.state_root.to_string(),\n )\n .await\n .unwrap();\n\n if let AncestryProof::HistoricalRoots {\n block_root_proof: _,\n block_summary_root: _,\n block_summary_root_proof: _,\n block_summary_root_gindex: _,\n } = proof\n {\n panic!(\"Expected block roots proof\")\n }\n\n let old_block_slot = 7870916 - 8196;\n let proof = proof_generator\n .prove_ancestry_with_historical_summaries(\n \u0026old_block_slot,\n \u0026latest_block_7879376.state_root.to_string(),\n )\n .await\n .unwrap();\n\n if let AncestryProof::BlockRoots {\n block_roots_index: _,\n block_root_proof: _,\n } = proof\n {\n panic!(\"Expected historical summaries proof\")\n }\n }\n\n #[tokio_test]\n async fn test_block_roots_proof_valid() {\n let (consensus, state_prover, _, _) = setup_block_and_provers(7807119).await;\n let latest_block = consensus.get_beacon_block_header(7878867).await.unwrap();\n let mut old_block = consensus\n .get_beacon_block_header(7878867 - 1000)\n .await\n .unwrap();\n\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let proof = proof_generator\n .prove_ancestry_with_block_roots(\u0026old_block.slot, \u0026latest_block.state_root.to_string())\n .await\n .unwrap();\n\n match proof {\n AncestryProof::BlockRoots {\n block_roots_index,\n block_root_proof,\n } =\u003e {\n let is_valid_proof = verify_merkle_proof(\n \u0026old_block.hash_tree_root().unwrap(),\n block_root_proof.as_slice(),\n \u0026GeneralizedIndex(block_roots_index as usize),\n \u0026latest_block.state_root,\n );\n assert!(is_valid_proof)\n }\n _ =\u003e panic!(\"Expected block roots proof\"),\n }\n }\n\n #[tokio_test]\n async fn test_block_roots_proof_invalid_proof() {\n let (consensus, state_prover, _, _) = setup_block_and_provers(7807119).await;\n let latest_block = consensus.get_beacon_block_header(7878867).await.unwrap();\n let mut old_block = consensus\n .get_beacon_block_header(7878867 - 1000)\n .await\n .unwrap();\n\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let proof = proof_generator\n .prove_ancestry_with_block_roots(\u0026old_block.slot, \u0026latest_block.state_root.to_string())\n .await\n .unwrap();\n\n match proof {\n AncestryProof::BlockRoots {\n block_roots_index,\n mut block_root_proof,\n } =\u003e {\n // Make proof invalid\n block_root_proof[0] = Node::default();\n\n let is_valid_proof = verify_merkle_proof(\n \u0026old_block.hash_tree_root().unwrap(),\n block_root_proof.as_slice(),\n \u0026GeneralizedIndex(block_roots_index as usize),\n \u0026latest_block.state_root,\n );\n assert!(!is_valid_proof)\n }\n _ =\u003e panic!(\"Expected block roots proof\"),\n }\n }\n\n #[tokio_test]\n async fn test_historical_proof_valid() {\n let (consensus, state_prover, _, _) = setup_block_and_provers(7807119).await;\n let latest_block = consensus.get_beacon_block_header(7879376).await.unwrap();\n let mut old_block = consensus\n .get_beacon_block_header(7870916 - 8196)\n .await\n .unwrap();\n\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let proof = proof_generator\n .prove_ancestry_with_historical_summaries(\n \u0026(old_block.slot),\n \u0026latest_block.state_root.to_string(),\n )\n .await\n .unwrap();\n\n match proof {\n AncestryProof::HistoricalRoots {\n block_summary_root_proof,\n block_root_proof,\n block_summary_root_gindex,\n block_summary_root,\n } =\u003e {\n // Proof from state root to the specific block_summary_root of the historical_summaries\n let is_valid_proof = verify_merkle_proof(\n \u0026block_summary_root,\n \u0026block_summary_root_proof,\n \u0026GeneralizedIndex(block_summary_root_gindex as usize),\n \u0026latest_block.state_root,\n );\n assert!(is_valid_proof);\n\n // Proof from block_summary_root to the target block\n\n let block_root_index = old_block.slot as usize % SLOTS_PER_HISTORICAL_ROOT;\n let gindex = get_generalized_index(\n \u0026Vector::\u003cNode, SLOTS_PER_HISTORICAL_ROOT\u003e::default(),\n \u0026[SszVariableOrIndex::Index(block_root_index)],\n );\n\n let is_valid_proof = verify_merkle_proof(\n \u0026old_block.hash_tree_root().unwrap(),\n \u0026block_root_proof,\n \u0026GeneralizedIndex(gindex),\n \u0026block_summary_root,\n );\n assert!(is_valid_proof)\n }\n _ =\u003e panic!(\"Expected block roots proof\"),\n }\n }\n\n #[tokio_test]\n async fn test_historical_proof_invalid_proofs() {\n let (consensus, state_prover, _, _) = setup_block_and_provers(7807119).await;\n let latest_block = consensus.get_beacon_block_header(7879376).await.unwrap();\n let mut old_block = consensus\n .get_beacon_block_header(7870916 - 8196)\n .await\n .unwrap();\n\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n\n let proof = proof_generator\n .prove_ancestry_with_historical_summaries(\n \u0026(old_block.slot),\n \u0026latest_block.state_root.to_string(),\n )\n .await\n .unwrap();\n\n match proof {\n AncestryProof::HistoricalRoots {\n mut block_summary_root_proof,\n mut block_root_proof,\n block_summary_root_gindex,\n block_summary_root,\n } =\u003e {\n // Make proofs invalid\n block_summary_root_proof[0] = Node::default();\n block_root_proof[0] = Node::default();\n\n // Proof from state root to the specific block_summary_root of the historical_summaries\n let is_valid_proof = verify_merkle_proof(\n \u0026block_summary_root,\n \u0026block_summary_root_proof,\n \u0026GeneralizedIndex(block_summary_root_gindex as usize),\n \u0026latest_block.state_root,\n );\n assert!(!is_valid_proof);\n\n // Proof from block_summary_root to the target block\n\n let block_root_index = old_block.slot as usize % SLOTS_PER_HISTORICAL_ROOT;\n let gindex = get_generalized_index(\n \u0026Vector::\u003cNode, SLOTS_PER_HISTORICAL_ROOT\u003e::default(),\n \u0026[SszVariableOrIndex::Index(block_root_index)],\n );\n\n let is_valid_proof = verify_merkle_proof(\n \u0026old_block.hash_tree_root().unwrap(),\n \u0026block_root_proof,\n \u0026GeneralizedIndex(gindex),\n \u0026block_summary_root,\n );\n assert!(!is_valid_proof)\n }\n _ =\u003e panic!(\"Expected block roots proof\"),\n }\n }\n\n #[tokio_test]\n async fn test_receipts_proof_valid() {\n let execution_block = get_mock_block_with_txs(18615160);\n let receipts = get_mock_block_receipts(18615160);\n\n let (consensus, state_prover, _, _) = setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n let proof = proof_generator\n .generate_receipt_proof(\u0026receipts, 1)\n .unwrap();\n\n let bytes: Result\u003c[u8; 32], _\u003e = execution_block.receipts_root[0..32].try_into();\n let root = Root::from_bytes(bytes.unwrap());\n\n let valid_proof = verify_trie_proof(root, 1, proof.clone());\n\n assert!(valid_proof.is_ok());\n }\n\n #[tokio_test]\n async fn test_receipts_proof_invalid() {\n let execution_block = get_mock_block_with_txs(18615160);\n let receipts = get_mock_block_receipts(18615160);\n\n let (consensus, state_prover, _, _) = setup_block_and_provers(7807119).await;\n let proof_generator = ProofGenerator::new(consensus, state_prover);\n let proof = proof_generator\n .generate_receipt_proof(\u0026receipts, 1)\n .unwrap();\n\n let bytes: Result\u003c[u8; 32], _\u003e = execution_block.receipts_root[0..32].try_into();\n let root = Root::from_bytes(bytes.unwrap());\n\n let invalid_proof = verify_trie_proof(root, 2, proof);\n\n assert!(invalid_proof.is_err());\n }\n}\n","traces":[{"line":86,"address":[],"length":0,"stats":{"Line":15},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":126,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":130,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":132,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":136,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":137,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":138,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":139,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":141,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":142,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":147,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":152,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":154,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":155,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":156,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":157,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":161,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":162,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":164,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":165,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":167,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":170,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":171,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":174,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":176,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":178,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":184,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":189,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":190,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":192,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":193,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":194,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":195,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":198,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":199,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":200,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":201,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":203,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":205,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":208,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":211,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":215,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":216,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":218,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":219,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":220,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":221,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":224,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":225,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":229,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":231,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":233,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":236,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":241,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":242,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":243,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":246,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":247,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":248,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":250,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":251,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":252,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":256,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":258,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":261,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":263,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":265,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":268,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":273,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":274,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":276,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":277,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":279,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":280,"address":[],"length":0,"stats":{"Line":2},"fn_name":null}],"covered":74,"coverable":91},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","prover","state_prover.rs"],"content":"use crate::prover::types::{GindexOrPath, ProofResponse};\nuse async_trait::async_trait;\nuse mockall::automock;\n\nuse super::{errors::StateProverError, utils::parse_path};\n\n/// A wrapper around the state [`prover`](https://github.com/commonprefix/state-prover)\n#[automock]\n#[async_trait]\npub trait StateProverAPI: Sync + Send + 'static {\n /// Fetches a proof from a specific g_index or a path to the beacon state of a specific block.\n async fn get_state_proof(\n \u0026self,\n state_id: \u0026str,\n gindex_or_path: \u0026GindexOrPath,\n ) -\u003e Result\u003cProofResponse, StateProverError\u003e;\n\n /// Fetches a proof from a specific g_index or a path to the beacon root of a specific block.\n async fn get_block_proof(\n \u0026self,\n block_id: \u0026str,\n gindex_or_path: GindexOrPath,\n ) -\u003e Result\u003cProofResponse, StateProverError\u003e;\n}\n\n#[derive(Clone)]\npub struct StateProver {\n network: String,\n rpc: String,\n}\n\nimpl StateProver {\n pub fn new(network: String, rpc: String) -\u003e Self {\n StateProver { network, rpc }\n }\n\n async fn get(\u0026self, req: \u0026str) -\u003e Result\u003cProofResponse, StateProverError\u003e {\n let response = reqwest::get(req)\n .await\n .map_err(StateProverError::NetworkError)?;\n\n if response.status() == reqwest::StatusCode::NOT_FOUND {\n return Err(StateProverError::NotFoundError(req.into()));\n }\n\n let bytes = response\n .bytes()\n .await\n .map_err(StateProverError::NetworkError)?;\n\n serde_json::from_slice(\u0026bytes).map_err(StateProverError::SerializationError)\n }\n}\n\n#[automock]\n#[async_trait]\nimpl StateProverAPI for StateProver {\n async fn get_state_proof(\n \u0026self,\n state_id: \u0026str,\n gindex_or_path: \u0026GindexOrPath,\n ) -\u003e Result\u003cProofResponse, StateProverError\u003e {\n let req = match gindex_or_path {\n GindexOrPath::Gindex(gindex) =\u003e format!(\n \"{}/state_proof?state_id={}\u0026gindex={}\u0026network={}\",\n self.rpc, state_id, gindex, self.network\n ),\n GindexOrPath::Path(path) =\u003e format!(\n \"{}/state_proof?state_id={}\u0026path={}\u0026network={}\",\n self.rpc,\n state_id,\n parse_path(path),\n self.network\n ),\n };\n\n self.get(\u0026req).await\n }\n\n async fn get_block_proof(\n \u0026self,\n block_id: \u0026str,\n gindex_or_path: GindexOrPath,\n ) -\u003e Result\u003cProofResponse, StateProverError\u003e {\n let req = match gindex_or_path {\n GindexOrPath::Gindex(gindex) =\u003e format!(\n \"{}/block_proof/?block_id={}\u0026gindex={}\u0026network={}\",\n self.rpc, block_id, gindex, self.network\n ),\n GindexOrPath::Path(path) =\u003e format!(\n \"{}/block_proof/?block_id={}\u0026path={}\u0026network={}\",\n self.rpc,\n block_id,\n parse_path(\u0026path),\n self.network\n ),\n };\n\n self.get(\u0026req).await\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use consensus_types::ssz_rs::SszVariableOrIndex;\n use httptest::{matchers::*, responders::*, Expectation, Server};\n\n fn setup_server_and_prover() -\u003e (Server, StateProver) {\n let server = Server::run();\n let url = server.url(\"\");\n let rpc = StateProver::new(\"mainnet\".to_string(), url.to_string());\n (server, rpc)\n }\n\n #[tokio::test]\n async fn test_get_state_proof() {\n let (server, prover) = setup_server_and_prover();\n let expected_response = ProofResponse::default();\n let json_response = serde_json::to_string(\u0026expected_response).unwrap();\n\n server.expect(\n Expectation::matching(all_of![\n request::query(url_decoded(contains((\"state_id\", \"state_id\")))),\n request::query(url_decoded(contains((\"gindex\", \"1\")))),\n ])\n .respond_with(status_code(200).body(json_response)),\n );\n\n let result = prover\n .get_state_proof(\"state_id\", \u0026GindexOrPath::Gindex(1))\n .await\n .unwrap();\n assert_eq!(result, expected_response);\n }\n\n #[tokio::test]\n async fn test_get_state_proof_with_path() {\n let (server, prover) = setup_server_and_prover();\n let expected_response = ProofResponse::default();\n let json_response = serde_json::to_string(\u0026expected_response).unwrap();\n\n server.expect(\n Expectation::matching(all_of![\n request::query(url_decoded(contains((\"state_id\", \"state_id\")))),\n request::query(url_decoded(contains((\"path\", \"test,lala\")))),\n ])\n .respond_with(status_code(200).body(json_response)),\n );\n\n let path = vec![\n SszVariableOrIndex::Name(\"test\"),\n SszVariableOrIndex::Name(\"lala\"),\n ];\n let result = prover\n .get_state_proof(\"state_id\", \u0026GindexOrPath::Path(path))\n .await\n .unwrap();\n assert_eq!(result, expected_response);\n }\n\n #[tokio::test]\n async fn test_get_state_proof_error() {\n let (server, prover) = setup_server_and_prover();\n\n server.expect(\n Expectation::matching(all_of![\n request::query(url_decoded(contains((\"state_id\", \"state_id\")))),\n request::query(url_decoded(contains((\"gindex\", \"1\")))),\n ])\n .respond_with(status_code(400).body(\"Error\")),\n );\n\n let result = prover\n .get_state_proof(\"state_id\", \u0026GindexOrPath::Gindex(1))\n .await;\n assert!(result.is_err());\n }\n\n #[tokio::test]\n async fn test_get_block_proof() {\n let (server, prover) = setup_server_and_prover();\n let json_response = serde_json::to_string(\u0026ProofResponse::default()).unwrap();\n\n server.expect(\n Expectation::matching(all_of![\n request::query(url_decoded(contains((\"block_id\", \"block_id\")))),\n request::query(url_decoded(contains((\"gindex\", \"1\")))),\n ])\n .respond_with(status_code(200).body(json_response)),\n );\n\n let result = prover\n .get_block_proof(\"block_id\", GindexOrPath::Gindex(1))\n .await\n .unwrap();\n assert_eq!(result, ProofResponse::default());\n }\n\n #[tokio::test]\n async fn test_get_block_proof_with_path() {\n let (server, prover) = setup_server_and_prover();\n let json_response = serde_json::to_string(\u0026ProofResponse::default()).unwrap();\n\n server.expect(\n Expectation::matching(all_of![\n request::query(url_decoded(contains((\"block_id\", \"block_id\")))),\n request::query(url_decoded(contains((\"path\", \"test,lala\")))),\n ])\n .respond_with(status_code(200).body(json_response)),\n );\n\n let path = vec![\n SszVariableOrIndex::Name(\"test\"),\n SszVariableOrIndex::Name(\"lala\"),\n ];\n let result = prover\n .get_block_proof(\"block_id\", GindexOrPath::Path(path))\n .await\n .unwrap();\n assert_eq!(result, ProofResponse::default());\n }\n\n #[tokio::test]\n async fn test_get_block_proof_error() {\n let (server, prover) = setup_server_and_prover();\n\n server.expect(\n Expectation::matching(all_of![\n request::query(url_decoded(contains((\"block_id\", \"block_id\")))),\n request::query(url_decoded(contains((\"gindex\", \"1\")))),\n ])\n .respond_with(status_code(400)),\n );\n\n let result = prover\n .get_block_proof(\"block_id\", GindexOrPath::Gindex(1))\n .await;\n\n assert!(result.is_err());\n }\n}\n","traces":[{"line":33,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":38,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":40,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":9},"fn_name":null}],"covered":28,"coverable":30},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","prover","test_helpers.rs"],"content":"#[cfg(test)]\npub mod test_utils {\n use cita_trie::{MemoryDB, PatriciaTrie, Trie};\n use consensus_types::sync_committee_rs::consensus_types::BeaconBlock;\n use consensus_types::sync_committee_rs::constants::Root;\n use consensus_types::{\n common::ContentVariant,\n consensus::{BeaconBlockAlias, FinalityUpdate, OptimisticUpdate},\n proofs::{CrossChainId, Message, UpdateVariant},\n };\n use ethers::{\n types::{Block, Transaction, TransactionReceipt, H256},\n utils::rlp::encode,\n };\n use eyre::{anyhow, Result};\n use hasher::HasherKeccak;\n use indexmap::IndexMap;\n use std::{fs::File, sync::Arc};\n\n use crate::prover::types::{BatchContentGroups, EnrichedContent};\n\n pub fn verify_trie_proof(root: Root, key: u64, proof_bytes: Vec\u003cVec\u003cu8\u003e\u003e) -\u003e Result\u003cVec\u003cu8\u003e\u003e {\n let memdb = Arc::new(MemoryDB::new(true));\n let hasher = Arc::new(HasherKeccak::new());\n\n let trie = PatriciaTrie::new(Arc::clone(\u0026memdb), Arc::clone(\u0026hasher));\n let proof = trie.verify_proof(\n root.as_bytes(),\n encode(\u0026key).to_vec().as_slice(),\n proof_bytes,\n );\n\n if proof.is_err() {\n return Err(anyhow!(\"Invalid proof\"));\n }\n\n match proof.unwrap() {\n Some(value) =\u003e Ok(value),\n None =\u003e Err(anyhow!(\"Invalid proof\")),\n }\n }\n\n pub fn get_mock_block_with_txs(block_number: u64) -\u003e Block\u003cTransaction\u003e {\n let filename = format!(\n \"./src/prover/testdata/execution_blocks/{}.json\",\n block_number\n );\n let file = File::open(filename).unwrap();\n let res: Option\u003cBlock\u003cTransaction\u003e\u003e = serde_json::from_reader(file).unwrap();\n res.unwrap()\n }\n\n pub fn get_mock_block_receipts(block_number: u64) -\u003e Vec\u003cTransactionReceipt\u003e {\n let filename = format!(\n \"./src/prover/testdata/execution_blocks/receipts/{}.json\",\n block_number\n );\n let file = File::open(filename).unwrap();\n let res: Vec\u003cTransactionReceipt\u003e = serde_json::from_reader(file).unwrap();\n res\n }\n\n pub fn get_mock_update(\n is_optimistic: bool,\n attested_slot: u64,\n finality_slot: u64,\n ) -\u003e UpdateVariant {\n if is_optimistic {\n let mut update = OptimisticUpdate::default();\n update.attested_header.beacon.slot = attested_slot;\n UpdateVariant::Optimistic(update)\n } else {\n let mut update = FinalityUpdate::default();\n update.finalized_header.beacon.slot = finality_slot;\n update.attested_header.beacon.slot = attested_slot;\n UpdateVariant::Finality(update)\n }\n }\n\n pub fn get_mock_message(slot: u64, block_number: u64, tx_hash: H256) -\u003e EnrichedContent {\n let message = Message {\n cc_id: CrossChainId {\n chain: \"ethereum\".parse().unwrap(),\n id: format!(\"{:x}:test\", tx_hash).parse().unwrap(),\n },\n source_address: \"0x0000000\".parse().unwrap(),\n destination_chain: \"polygon\".parse().unwrap(),\n destination_address: \"0x0000000\".parse().unwrap(),\n payload_hash: Default::default(),\n };\n\n EnrichedContent {\n content: ContentVariant::Message(message),\n tx_hash,\n exec_block: get_mock_exec_block_with_txs(block_number),\n beacon_block: get_mock_beacon_block(slot),\n receipts: (1..100)\n .map(|i| TransactionReceipt {\n transaction_hash: H256::from_low_u64_be(i),\n ..Default::default()\n })\n .collect(),\n id: \"id1\".to_string(),\n delivery_tag: 1,\n }\n }\n\n /*\n Setup the following batch scenario:\n\n * block 1 -\u003e tx 1 -\u003e message 1\n * block 2 -\u003e tx 2 -\u003e message 2\n * \\ \\\n * \\ -\u003e message 3\n * \\\n * ---\u003e tx 3 -\u003e message 4\n *\n * block 3 -\u003e tx 4 -\u003e message 5\n */\n pub fn get_mock_batch_message_groups() -\u003e BatchContentGroups {\n let mut messages = vec![];\n for i in 0..6 {\n let m = get_mock_message(i, i, H256::from_low_u64_be(i));\n messages.push(m);\n }\n\n let mut groups: BatchContentGroups = IndexMap::new();\n let mut blockgroup1 = IndexMap::new();\n let mut blockgroup2 = IndexMap::new();\n let mut blockgroup3 = IndexMap::new();\n\n blockgroup1.insert(messages[1].tx_hash, vec![messages[1].clone()]);\n blockgroup2.insert(\n messages[2].tx_hash,\n vec![messages[2].clone(), messages[3].clone()],\n );\n blockgroup2.insert(messages[4].tx_hash, vec![messages[4].clone()]);\n blockgroup3.insert(messages[5].tx_hash, vec![messages[5].clone()]);\n\n groups.insert(1, blockgroup1);\n groups.insert(2, blockgroup2);\n groups.insert(3, blockgroup3);\n\n groups\n }\n\n pub fn get_mock_beacon_block(slot: u64) -\u003e BeaconBlockAlias {\n let mut block = BeaconBlock {\n slot,\n ..Default::default()\n };\n\n for _ in 1..10 {\n block\n .body\n .execution_payload_mut()\n .transactions_mut()\n .push(consensus_types::sync_committee_rs::consensus_types::Transaction::default());\n }\n block\n }\n\n pub fn get_mock_exec_block(block_number: u64) -\u003e Block\u003cH256\u003e {\n Block {\n number: Some(ethers::types::U64::from(block_number)),\n ..Default::default()\n }\n }\n\n pub fn get_mock_exec_block_with_txs(block_number: u64) -\u003e Block\u003cTransaction\u003e {\n Block {\n number: Some(ethers::types::U64::from(block_number)),\n ..Default::default()\n }\n }\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","prover","types.rs"],"content":"use consensus_types::ssz_rs::{Node, SszVariableOrIndex};\nuse consensus_types::sync_committee_rs::consensus_types::BeaconBlockHeader;\nuse consensus_types::{common::ContentVariant, consensus::BeaconBlockAlias};\nuse ethers::types::{Block, Transaction, TransactionReceipt, H256};\nuse indexmap::IndexMap;\nuse serde::{Deserialize, Serialize};\n\n// Neccessary data for proving a message\n#[derive(Debug)]\npub struct ProofAuxiliaryData {\n // Target execution block that contains the transaction/log.\n pub target_execution_block: Block\u003cTransaction\u003e,\n // Target beacon block that contains the target execution block.\n pub target_beacon_block: BeaconBlockAlias,\n // Receipts of the target execution block.\n pub receipts: Vec\u003cTransactionReceipt\u003e,\n // Block header of the most recent block. (Either finalized or attested depending or the UpdateVariant)\n pub recent_block_header: BeaconBlockHeader,\n}\n\n#[derive(PartialEq, Deserialize, Debug, Serialize, Default, Clone)]\npub struct ProofResponse {\n pub gindex: u64,\n pub witnesses: Vec\u003cNode\u003e,\n pub leaf: Node,\n}\n\n#[derive(Clone, Debug, PartialEq, Default)]\npub struct EnrichedContent {\n pub id: String,\n pub content: ContentVariant,\n pub tx_hash: H256,\n pub exec_block: Block\u003cTransaction\u003e,\n pub beacon_block: BeaconBlockAlias,\n pub receipts: Vec\u003cTransactionReceipt\u003e,\n pub delivery_tag: u64,\n}\n\n#[derive(Debug, PartialEq)]\npub enum GindexOrPath {\n Gindex(usize),\n Path(Vec\u003cSszVariableOrIndex\u003e),\n}\n\npub struct ProverConfig {\n pub network: String,\n pub consensus_rpc: String,\n pub execution_rpc: String,\n pub state_prover_rpc: String,\n pub reject_historical_roots: bool,\n pub historical_roots_block_roots_batch_size: u64,\n}\n\n// A map from block number to a map from tx hash to messages\npub type BatchContentGroups = IndexMap\u003cu64, IndexMap\u003cH256, Vec\u003cEnrichedContent\u003e\u003e\u003e;\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","prover","utils.rs"],"content":"use std::{str::FromStr, sync::Arc};\n\nuse cita_trie::{MemoryDB, PatriciaTrie, Trie};\nuse consensus_types::proofs::CrossChainId;\nuse consensus_types::ssz_rs::SszVariableOrIndex;\nuse ethers::{\n types::{TransactionReceipt, H256},\n utils::rlp::{encode, RlpStream},\n};\nuse eyre::{anyhow, Result};\nuse hasher::HasherKeccak;\n\nuse super::types::BatchContentGroups;\n\n// Used by the state prover module. It accepts a path of `SSZVariableOrIndex`\n// and generates a string containing the path, compatible with what the\n// state_prover accepts.\npub fn parse_path(path: \u0026Vec\u003cSszVariableOrIndex\u003e) -\u003e String {\n let mut path_str = String::new();\n for p in path {\n match p {\n SszVariableOrIndex::Name(name) =\u003e path_str.push_str(\u0026format!(\",{}\", name)),\n SszVariableOrIndex::Index(index) =\u003e path_str.push_str(\u0026format!(\",{}\", index)),\n }\n }\n path_str[1..].to_string() // remove first comma\n}\n\n/// Gets the transaction index of specific transaction in a block.\npub fn get_tx_index(receipts: \u0026[TransactionReceipt], tx_hash: \u0026H256) -\u003e Result\u003cu64\u003e {\n let tx_index = receipts\n .iter()\n .position(|r| format!(\"{:x}\", r.transaction_hash) == format!(\"{:x}\", tx_hash));\n\n match tx_index {\n Some(index) =\u003e Ok(index as u64),\n None =\u003e Err(anyhow!(\"Transaction not found in receipts. {:?}\", tx_hash)),\n }\n}\n\n/// Fetches the tx_hash from a Message.cc_id.\npub fn get_tx_hash_from_cc_id(cc_id: \u0026CrossChainId) -\u003e Result\u003cH256\u003e {\n let tx_hash = cc_id\n .id\n .split_once(':')\n .ok_or_else(|| anyhow!(\"Invalid CrossChainId format. {:?}\", cc_id))?\n .0;\n\n Ok(H256::from_str(tx_hash)?)\n}\n\n/// Generates a Merkle Patricia trie out of a set of leaves\npub fn generate_trie\u003cT\u003e(\n leaves: Vec\u003cT\u003e,\n encode_fn: fn(\u0026T) -\u003e Vec\u003cu8\u003e,\n) -\u003e PatriciaTrie\u003cMemoryDB, HasherKeccak\u003e {\n let memdb = Arc::new(MemoryDB::new(true));\n let hasher = Arc::new(HasherKeccak::new());\n let mut trie = PatriciaTrie::new(Arc::clone(\u0026memdb), Arc::clone(\u0026hasher));\n for (i, leaf) in leaves.iter().enumerate() {\n let key = encode(\u0026i);\n let value = encode_fn(leaf);\n trie.insert(key.to_vec(), value).unwrap();\n }\n\n trie\n}\n\n/// RLP encodes a specific receipt.\npub fn encode_receipt(receipt: \u0026TransactionReceipt) -\u003e Vec\u003cu8\u003e {\n let mut stream = RlpStream::new();\n stream.begin_list(4);\n stream.append(\u0026receipt.status.unwrap());\n stream.append(\u0026receipt.cumulative_gas_used);\n stream.append(\u0026receipt.logs_bloom);\n stream.append_list(\u0026receipt.logs);\n\n let legacy_receipt_encoded = stream.out();\n let tx_type = receipt.transaction_type.unwrap().as_u64();\n\n match tx_type {\n 0 =\u003e legacy_receipt_encoded.to_vec(),\n _ =\u003e [\u0026tx_type.to_be_bytes()[7..8], \u0026legacy_receipt_encoded].concat(),\n }\n}\n\n/// A helper function for printing batched messages.\n#[cfg(not(tarpaulin_include))]\npub fn debug_print_batch_message_groups(batch_message_groups: \u0026BatchContentGroups) {\n for (block_number, message_groups) in batch_message_groups {\n let block_count = message_groups.len();\n for (tx_hash, messages) in message_groups {\n let message_count = messages.len();\n println!(\n \"Block number: {}, Block count: {}, Tx hash: {}, Message count: {}\",\n block_number, block_count, tx_hash, message_count\n );\n }\n }\n}\n\n#[cfg(test)]\nmod tests {\n use consensus_types::proofs::CrossChainId;\n use consensus_types::ssz_rs::SszVariableOrIndex;\n use ethers::types::{TransactionReceipt, H256};\n\n use crate::prover::utils::{get_tx_hash_from_cc_id, get_tx_index, parse_path};\n\n fn get_mock_receipt() -\u003e TransactionReceipt {\n TransactionReceipt {\n transaction_hash: H256::random(),\n ..Default::default()\n }\n }\n\n #[test]\n fn test_get_tx_index_valid() {\n let receipts = vec![get_mock_receipt(), get_mock_receipt(), get_mock_receipt()];\n\n for (i, receipt) in receipts.iter().enumerate() {\n let tx_hash = receipt.transaction_hash;\n\n let index = get_tx_index(\u0026receipts, \u0026tx_hash).unwrap();\n assert_eq!(index, i as u64);\n }\n }\n\n #[test]\n fn test_get_tx_index_invalid() {\n let receipts = vec![get_mock_receipt(), get_mock_receipt(), get_mock_receipt()];\n let random_tx_hash = H256::random();\n\n let index = get_tx_index(\u0026receipts, \u0026random_tx_hash);\n assert!(index.is_err())\n }\n\n #[test]\n fn test_get_tx_index_invalid_cc_id_format() {\n let cc_id = CrossChainId {\n id: \"invalid_format\".parse().unwrap(),\n chain: \"ethereum\".parse().unwrap(),\n };\n\n let result = get_tx_hash_from_cc_id(\u0026cc_id);\n assert!(result.is_err());\n }\n\n #[test]\n fn test_parse_path_names_only() {\n let path = vec![SszVariableOrIndex::Name(\"a\"), SszVariableOrIndex::Name(\"b\")];\n assert_eq!(parse_path(\u0026path), \"a,b\");\n }\n\n #[test]\n fn test_parse_path_indexes_only() {\n let path = vec![SszVariableOrIndex::Index(1), SszVariableOrIndex::Index(2)];\n assert_eq!(parse_path(\u0026path), \"1,2\");\n }\n\n #[test]\n fn test_parse_path_mixed() {\n let path = vec![SszVariableOrIndex::Name(\"a\"), SszVariableOrIndex::Index(1)];\n assert_eq!(parse_path(\u0026path), \"a,1\");\n }\n}\n","traces":[{"line":18,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":19,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":20,"address":[],"length":0,"stats":{"Line":112},"fn_name":null},{"line":22,"address":[],"length":0,"stats":{"Line":37},"fn_name":null},{"line":23,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":26,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":30,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":37},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":36,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":44,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":45,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":734},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":366},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":92},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":274},"fn_name":null}],"covered":38,"coverable":39},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","prover","src","types.rs"],"content":"use consensus_types::consensus::{\n BeaconBlockAlias, Bootstrap, FinalityUpdate, OptimisticUpdate, Update,\n};\n\nuse consensus_types::ssz_rs::Node;\nuse consensus_types::sync_committee_rs::consensus_types::BeaconBlockHeader;\n\n#[derive(serde::Deserialize, Debug)]\npub struct UpdateData {\n pub data: Update,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BootstrapResponse {\n pub data: Bootstrap,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BeaconBlockHeaderResponse {\n pub data: BeaconBlockHeaderContainer,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BeaconBlockHeaderContainer {\n pub header: BeaconBlockHeaderMessage,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BeaconBlockHeaderMessage {\n pub message: BeaconBlockHeader,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BeaconBlockResponse {\n pub data: BeaconBlockContainer,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BeaconBlockContainer {\n pub message: BeaconBlockAlias,\n}\n#[derive(serde::Deserialize, Debug)]\npub struct FinalityUpdateData {\n pub data: FinalityUpdate,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct OptimisticUpdateData {\n pub data: OptimisticUpdate,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BlockRootResponse {\n pub data: BlockRoot,\n}\n\n#[derive(serde::Deserialize, Debug)]\npub struct BlockRoot {\n pub root: Node,\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","bin","feed.rs"],"content":"extern crate relayer;\n\nuse eth::{\n consensus::{ConsensusRPC, EthBeaconAPI},\n types::EthConfig,\n};\nuse log::{debug, error, info};\nuse relayer::{\n utils::{calc_sync_period, load_config},\n verifier::{Verifier, VerifierAPI},\n};\nuse std::time::Duration;\nuse tokio::time::sleep;\n\nconst MAX_UPDATES_PER_LOOP: u8 = 100;\n\npub struct Feeder\u003cV, CR\u003e {\n verifier: V,\n consensus: CR,\n}\n\nimpl\u003cV: VerifierAPI, CR: EthBeaconAPI\u003e Feeder\u003cV, CR\u003e {\n pub fn new(verifier: V, consensus: CR) -\u003e Self {\n Feeder {\n verifier,\n consensus,\n }\n }\n\n pub async fn tick(\u0026self) -\u003e () {\n let latest_header = self.consensus.get_latest_beacon_block_header().await;\n if latest_header.is_err() {\n error!(\n \"Error getting latest header from consensus: {:?}\",\n latest_header.err()\n );\n return ();\n }\n let latest_header = latest_header.unwrap();\n let latest_period = calc_sync_period(latest_header.slot);\n\n let state = self.verifier.get_state().await;\n if state.is_err() {\n error!(\"Error getting state from wasm: {:?}\", state.err());\n return ();\n }\n let state = state.unwrap();\n let verifier_period = calc_sync_period(state.update_slot);\n let is_on_bootstrap = state.next_sync_committee.is_none();\n\n println!(\"{} {}\", latest_period, verifier_period);\n info!(\n \"Latest period: {}, Verifier period: {}\",\n latest_period, verifier_period\n );\n if latest_period == verifier_period {\n debug!(\"No updates to process\");\n return ();\n }\n\n let start_update_period = if is_on_bootstrap {\n info!(\n \"Verifier is on bootstrap. Will apply updates starting from period {}\",\n verifier_period\n );\n verifier_period\n } else {\n verifier_period + 1\n };\n\n let updates = self\n .consensus\n .get_updates(start_update_period, MAX_UPDATES_PER_LOOP)\n .await;\n if updates.is_err() {\n error!(\"Error getting updates from consensus: {:?}\", updates.err());\n return ();\n }\n let updates = updates.unwrap();\n info!(\n \"Processing {} updates starting from period {}\",\n updates.len(),\n start_update_period\n );\n\n for update in updates {\n let update_period = calc_sync_period(update.attested_header.beacon.slot);\n let result = self.verifier.update(update).await;\n if result.is_err() {\n error!(\"Error updating wasm: {:?}\", result.err());\n break;\n }\n let new_verifier_period = self.verifier.get_period().await.unwrap();\n\n info!(\n \"Update {} succeeded. New verifier period: {}\",\n update_period, new_verifier_period\n );\n }\n }\n\n pub async fn start(\u0026self, sleep_duration: Duration) -\u003e () {\n loop {\n self.tick().await;\n\n debug!(\"Sleeping for {}\", sleep_duration.as_secs());\n sleep(sleep_duration).await;\n }\n }\n}\n\n#[tokio::main]\nasync fn main() {\n env_logger::init();\n\n let config = load_config();\n let eth_config = EthConfig::from(config.clone());\n let sleep_duration = Duration::from_secs(config.feed_interval);\n\n let consensus = ConsensusRPC::new(\n config.consensus_rpc.clone(),\n config.block_roots_rpc,\n eth_config,\n );\n let verifier = Verifier::new(config.wasm_rpc, config.verifier_addr, config.wasm_wallet);\n\n let feeder = Feeder::new(verifier, consensus);\n feeder.start(sleep_duration).await;\n}\n\n#[cfg(test)]\nmod tests {\n use consensus_types::{\n consensus::{BeaconBlockHeader, Update},\n lightclient::LightClientState,\n sync_committee_rs::consensus_types::SyncCommittee,\n };\n use eth::consensus::MockConsensusRPC;\n use mockall::predicate;\n use relayer::verifier::MockVerifier;\n\n use crate::{Feeder, MAX_UPDATES_PER_LOOP};\n\n fn mock_dependencies(\n verifier_period: u64,\n beacon_period: u64,\n ) -\u003e (\n MockVerifier,\n MockConsensusRPC,\n BeaconBlockHeader,\n LightClientState,\n ) {\n let mock_verifier = MockVerifier::new();\n let mock_consensus = MockConsensusRPC::new();\n\n let mut mock_header = BeaconBlockHeader::default();\n mock_header.slot = beacon_period * 256 * 32;\n\n let mut mock_state = LightClientState::default();\n mock_state.update_slot = verifier_period * 256 * 32;\n mock_state.next_sync_committee = None;\n\n (mock_verifier, mock_consensus, mock_header, mock_state)\n }\n\n #[tokio::test]\n async fn test_verifier_updated() {\n let (mut mock_verifier, mut mock_consensus, mock_header, mock_state) =\n mock_dependencies(1000, 1000);\n\n mock_consensus\n .expect_get_latest_beacon_block_header()\n .times(1)\n .returning(move || Ok(mock_header.clone()));\n\n mock_verifier\n .expect_get_state()\n .times(1)\n .returning(move || Ok(mock_state.clone()));\n\n let feeder = Feeder::new(mock_verifier, mock_consensus);\n\n let res = feeder.tick().await;\n assert_eq!(res, ()); // does not proceed if verifier slot == beacon slot\n }\n\n #[tokio::test]\n async fn test_verifier_on_bootstrap() {\n let verifier_period = 900;\n let beacon_period = 1000;\n let (mut mock_verifier, mut mock_consensus, mock_header, mock_state) =\n mock_dependencies(verifier_period, beacon_period);\n\n mock_consensus\n .expect_get_latest_beacon_block_header()\n .times(1)\n .returning(move || Ok(mock_header.clone()));\n\n let updates = vec![\n Update {\n signature_slot: 1,\n ..Update::default()\n },\n Update {\n signature_slot: 2,\n ..Update::default()\n },\n ];\n\n mock_verifier\n .expect_get_state()\n .times(1)\n .returning(move || Ok(mock_state.clone()));\n\n mock_verifier\n .expect_get_period()\n .times(2)\n .returning(|| Ok(0)); // ignore this value\n\n mock_verifier\n .expect_update()\n .with(predicate::eq(updates[0].clone()))\n .times(1)\n .returning(|_| Ok(()));\n mock_verifier\n .expect_update()\n .with(predicate::eq(updates[1].clone()))\n .times(1)\n .returning(|_| Ok(()));\n\n // will apply update from same period\n mock_consensus\n .expect_get_updates()\n .with(\n predicate::eq(verifier_period),\n predicate::eq(MAX_UPDATES_PER_LOOP),\n )\n .times(1)\n .returning(move |_, _| Ok(updates.clone()));\n\n let feeder = Feeder::new(mock_verifier, mock_consensus);\n\n let res = feeder.tick().await;\n assert_eq!(res, ());\n }\n\n #[tokio::test]\n async fn test_verifier_normal_update() {\n let verifier_period = 900;\n let beacon_period = 1000;\n let (mut mock_verifier, mut mock_consensus, mock_header, mut mock_state) =\n mock_dependencies(verifier_period, beacon_period);\n mock_state.next_sync_committee = Some(SyncCommittee::default());\n\n mock_consensus\n .expect_get_latest_beacon_block_header()\n .times(1)\n .returning(move || Ok(mock_header.clone()));\n\n let updates = vec![\n Update {\n signature_slot: 1,\n ..Update::default()\n },\n Update {\n signature_slot: 2,\n ..Update::default()\n },\n ];\n\n mock_verifier\n .expect_get_period()\n .times(2)\n .returning(|| Ok(0)); // ignore this value\n\n mock_verifier\n .expect_get_state()\n .times(1)\n .returning(move || Ok(mock_state.clone()));\n\n mock_verifier\n .expect_update()\n .with(predicate::eq(updates[0].clone()))\n .times(1)\n .returning(|_| Ok(()));\n mock_verifier\n .expect_update()\n .with(predicate::eq(updates[1].clone()))\n .times(1)\n .returning(|_| Ok(()));\n\n // will apply update from same period\n mock_consensus\n .expect_get_updates()\n .with(\n predicate::eq(verifier_period + 1),\n predicate::eq(MAX_UPDATES_PER_LOOP),\n )\n .times(1)\n .returning(move |_, _| Ok(updates.clone()));\n\n let feeder = Feeder::new(mock_verifier, mock_consensus);\n\n let res = feeder.tick().await;\n assert_eq!(res, ());\n }\n}\n","traces":[{"line":23,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":30,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":32,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":40,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":44,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":45,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":47,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":54,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":56,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":89,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":127,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":24,"coverable":63},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","bin","relay.rs"],"content":"extern crate relayer;\n\nuse eth::{consensus::ConsensusRPC, execution::ExecutionRPC, types::EthConfig};\nuse prover::{prover::types::ProverConfig, Prover};\nuse relayer::{consumers::LapinConsumer, relayer::Relayer, utils::load_config, verifier::Verifier};\nuse std::sync::Arc;\n\n/// Main entry point for the relayer.\n#[tokio::main]\nasync fn main() {\n env_logger::init();\n rlimit::increase_nofile_limit(u64::MAX).unwrap();\n\n let config = load_config();\n let prover_config = ProverConfig::from(config.clone());\n let eth_config = EthConfig::from(config.clone());\n\n let consensus = Arc::new(ConsensusRPC::new(\n config.consensus_rpc.clone(),\n config.block_roots_rpc.clone(),\n eth_config,\n ));\n let execution = Arc::new(ExecutionRPC::new(config.execution_rpc.clone()));\n let prover = Arc::new(Prover::with_config(consensus.clone(), prover_config));\n let verifier = Verifier::new(\n config.wasm_rpc.clone(),\n config.verifier_addr.clone(),\n config.wasm_wallet.clone(),\n );\n\n let consumer = LapinConsumer::new(\n config.sentinel_queue_addr.clone(),\n config.sentinel_queue_name.clone(),\n )\n .await\n .unwrap();\n\n let mut relayer = Relayer::new(\n config.clone(),\n consumer,\n consensus.clone(),\n execution.clone(),\n prover,\n verifier,\n )\n .await;\n\n relayer.start().await;\n}\n","traces":[{"line":10,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":11,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":12,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":14,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":15,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":16,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":18,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":19,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":20,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":21,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":23,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":24,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":27,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":28,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":32,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":40,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":41,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":44,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":0,"coverable":26},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","consumers","ethers_consumer.rs"],"content":"use crate::types::{ContractCall, EnrichedLog, OperatorshipTransferred};\nuse async_trait::async_trait;\nuse eth::execution::{EthExecutionAPI, ExecutionRPC};\nuse ethers::{\n contract::parse_log,\n types::{Address, Filter, H160},\n};\nuse eyre::Result;\nuse std::sync::Arc;\n\nuse super::Amqp;\n\npub struct EthersConsumer {\n execution: Arc\u003cExecutionRPC\u003e,\n address: Address,\n}\n\nimpl EthersConsumer {\n pub fn new(execution: Arc\u003cExecutionRPC\u003e, address: String) -\u003e Self {\n let address = address.parse::\u003cAddress\u003e().unwrap();\n\n Self { execution, address }\n }\n\n async fn get_logs(\u0026self, from_block: u64, to_block: u64) -\u003e Result\u003cVec\u003cEnrichedLog\u003e\u003e {\n let signatures = vec![\n \"ContractCall(address,string,string,bytes32,bytes)\",\n \"OperatorshipTransferred(bytes)\",\n ];\n\n let filter = Filter::new()\n .address(self.address)\n .events(signatures)\n .from_block(from_block)\n .to_block(to_block);\n\n let logs = self.execution.get_logs(\u0026filter).await?;\n\n let mut enriched_logs = vec![];\n for log in \u0026logs {\n let event_name = if parse_log::\u003cContractCall\u003e(log.clone()).is_ok() {\n \"ContractCall\"\n } else if parse_log::\u003cOperatorshipTransferred\u003e(log.clone()).is_ok() {\n \"OperatorshipTransferred\"\n } else {\n continue;\n };\n\n enriched_logs.push(EnrichedLog {\n log: log.clone(),\n event_name: event_name.to_string(),\n contract_name: \"gateway\".to_string(),\n chain: \"ethereum\".to_string(),\n source: \"source\".to_string(),\n tx_to: H160::default(),\n });\n }\n\n Ok(enriched_logs)\n }\n}\n\n#[async_trait]\nimpl Amqp for EthersConsumer {\n async fn consume(\u0026mut self, limit: usize) -\u003e Result\u003cVec\u003c(u64, String)\u003e\u003e {\n let latest_block = self.execution.get_latest_block_number().await?;\n let start_block = latest_block - 9000;\n\n let mut contents: Vec\u003c_\u003e = self\n .get_logs(start_block.as_u64(), latest_block.as_u64())\n .await?;\n\n // Sort logs by block number in descending order\n contents.sort_by(|a, b| b.log.block_number.cmp(\u0026a.log.block_number));\n\n let res = contents\n .iter()\n .enumerate()\n .map(|(i, c)| (i as u64, serde_json::to_string(c).unwrap()))\n .take(limit)\n .collect();\n\n Ok(res)\n }\n\n async fn ack_delivery(\u0026self, _delivery_id: u64) -\u003e Result\u003c()\u003e {\n Ok(())\n }\n\n async fn nack_delivery(\u0026self, _delivery_id: u64) -\u003e Result\u003c()\u003e {\n Ok(())\n }\n}\n","traces":[{"line":19,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":20,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":32,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":34,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":40,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":41,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":44,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":67,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":0,"coverable":33},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","consumers","lapin_consumer.rs"],"content":"use std::time::Duration;\n\nuse super::Amqp;\nuse async_trait::async_trait;\nuse eyre::{eyre, Result};\nuse lapin::{\n options::{BasicAckOptions, BasicGetOptions, BasicNackOptions},\n Channel, Connection, ConnectionProperties,\n};\nuse log::{debug, error, info};\nuse mockall::automock;\n\npub struct LapinConsumer {\n queue_addr: String,\n queue_name: String,\n channel: Channel,\n}\n\nimpl LapinConsumer {\n pub async fn new(queue_addr: String, queue_name: String) -\u003e Result\u003cSelf\u003e {\n let connection =\n Connection::connect(queue_addr.as_str(), ConnectionProperties::default()).await?;\n let channel = connection.create_channel().await?;\n\n Ok(Self {\n queue_addr,\n queue_name,\n channel,\n })\n }\n}\n\n#[automock]\n#[async_trait]\nimpl Amqp for LapinConsumer {\n async fn consume(\u0026mut self, max_deliveries: usize) -\u003e Result\u003cVec\u003c(u64, String)\u003e\u003e {\n let mut deliveries = Vec::with_capacity(max_deliveries);\n\n while deliveries.len() \u003c max_deliveries {\n let message = self\n .channel\n .basic_get(\u0026self.queue_name, BasicGetOptions::default())\n .await;\n\n match message {\n Ok(Some(message)) =\u003e {\n println!(\"Got message: {:?}\", message.delivery);\n deliveries.push(message.delivery);\n }\n Ok(None) =\u003e {\n debug!(\"Queue is empty\");\n break;\n }\n Err(e) =\u003e {\n error!(\"Connection is lost due to {:?}. Will retry in 10 secs\", e);\n tokio::time::sleep(Duration::from_secs(10)).await;\n\n // Clear the deliveries vector, since the delivery tags are no longer valid\n deliveries.clear();\n\n let new_instance =\n LapinConsumer::new(self.queue_addr.clone(), self.queue_name.clone()).await;\n if let Ok(new_instance) = new_instance {\n *self = new_instance;\n }\n }\n }\n }\n\n info!(\"Got {} logs from sentinel\", deliveries.len());\n\n let result = deliveries\n .iter()\n .map(|delivery| {\n (\n delivery.delivery_tag,\n std::str::from_utf8(\u0026delivery.data).unwrap().to_string(),\n )\n })\n .collect();\n\n Ok(result)\n }\n\n async fn nack_delivery(\u0026self, delivery_tag: u64) -\u003e Result\u003c()\u003e {\n debug!(\"Nacking delivery {}\", delivery_tag);\n\n let requeue_nack = BasicNackOptions {\n requeue: true,\n ..Default::default()\n };\n\n self.channel\n .basic_nack(delivery_tag, requeue_nack)\n .await\n .map_err(|e| eyre!(\"Error nacking delivery {} {}\", delivery_tag, e))?;\n\n Ok(())\n }\n\n async fn ack_delivery(\u0026self, delivery_tag: u64) -\u003e Result\u003c()\u003e {\n debug!(\"Acking delivery {}\", delivery_tag);\n self.channel\n .basic_ack(delivery_tag, BasicAckOptions::default())\n .await\n .map_err(|e| eyre!(\"Error nacking delivery {} {}\", delivery_tag, e))?;\n\n Ok(())\n }\n}\n","traces":[{"line":20,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":21,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":22,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":23,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":27,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":28,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":36,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":40,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":41,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":45,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":47,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":54,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":56,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":0,"coverable":48},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","consumers","mod.rs"],"content":"mod ethers_consumer;\nmod lapin_consumer;\n\nuse async_trait::async_trait;\nuse eyre::Result;\n\npub use ethers_consumer::EthersConsumer;\npub use lapin_consumer::{LapinConsumer, MockLapinConsumer};\n\n// The basic RabbitMQ consumer.\n#[async_trait]\npub trait Amqp {\n // It consumes a set of messages from the queue up to a given limit.\n // Returns a vector of tuples containing the delivery tag and the message.\n async fn consume(\u0026mut self, max_deliveries: usize) -\u003e Result\u003cVec\u003c(u64, String)\u003e\u003e;\n // It acks a delivery.\n async fn ack_delivery(\u0026self, delivery_tag: u64) -\u003e Result\u003c()\u003e;\n // It nacks a delivery with a nacking strategy forcing a requeue.\n async fn nack_delivery(\u0026self, delivery_tag: u64) -\u003e Result\u003c()\u003e;\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","lib.rs"],"content":"pub mod consumers;\npub mod parser;\npub mod relayer;\npub mod types;\npub mod utils;\npub mod verifier;\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","parser.rs"],"content":"use crate::types::{ContractCall, EnrichedLog, OperatorshipTransferred};\nuse consensus_types::{\n common::{ContentVariant, PrimaryKey, WorkerSetMessage},\n proofs::{CrossChainId, Message},\n};\nuse eth::types::FullBlockDetails;\nuse ethers::{\n abi::RawLog,\n contract::EthEvent,\n types::{Log, TransactionReceipt},\n utils::hex,\n};\nuse eyre::{eyre, Result};\nuse prover::prover::types::EnrichedContent;\n\n/// The driver function for parsing an enriched log into an enriched content.\n/// This is the only place where we need to know about the different enriched log variants.\n/// Currenlty we support only ContractCall and OperatorshipTransferred events.\npub fn parse_enriched_log(\n enriched_log: \u0026EnrichedLog,\n block_details: \u0026FullBlockDetails,\n delivery_tag: u64,\n) -\u003e Result\u003cEnrichedContent\u003e {\n let log = \u0026enriched_log.log;\n match enriched_log.event_name.as_str() {\n \"ContractCall\" =\u003e {\n let event: ContractCall = EthEvent::decode_log(\u0026RawLog::from(log.clone()))\n .map_err(|e| eyre!(\"Error decoding log {:?}\", e))?;\n let message = Message {\n cc_id: CrossChainId {\n chain: \"ethereum\".parse().unwrap(),\n id: generate_id(log, block_details.receipts.as_slice())\n .try_into()\n .unwrap(),\n },\n source_address: format!(\"0x{:x}\", event.sender).parse().unwrap(),\n destination_chain: event.destination_chain.parse().unwrap(),\n destination_address: event.destination_contract_address.parse().unwrap(),\n payload_hash: event.payload_hash.into(),\n };\n\n Ok(ContentVariant::Message(message))\n }\n \"OperatorshipTransferred\" =\u003e {\n let event: OperatorshipTransferred =\n EthEvent::decode_log(\u0026RawLog::from(log.clone()))\n .map_err(|e| eyre!(\"Error decoding log {:?}\", e))?;\n let message = WorkerSetMessage {\n message_id: generate_id(log, block_details.receipts.as_slice()).try_into()?,\n new_operators_data: hex::encode(event.new_operators_data),\n };\n\n Ok(ContentVariant::WorkerSet(message))\n }\n _ =\u003e Err(eyre!(\n \"Enriched log variant is not supported block_hash: {:x} tx_hash: {:x} log_index: {}\",\n enriched_log.log.block_hash.unwrap_or_default(),\n enriched_log.log.transaction_hash.unwrap_or_default(),\n enriched_log.log.log_index.unwrap_or_default(),\n )),\n }\n .and_then(|content| enrich_content(\u0026content, log, block_details, delivery_tag))\n}\n\n/// Enriches the content with the block details and the transaction hash.\nfn enrich_content(\n content: \u0026ContentVariant,\n log: \u0026Log,\n block_details: \u0026FullBlockDetails,\n delivery_tag: u64,\n) -\u003e Result\u003cEnrichedContent\u003e {\n let msg = EnrichedContent {\n content: content.clone(),\n exec_block: block_details.exec_block.clone(),\n beacon_block: block_details.beacon_block.clone(),\n receipts: block_details.receipts.clone(),\n tx_hash: log.transaction_hash.unwrap(),\n id: match content {\n ContentVariant::Message(message) =\u003e message.key(),\n ContentVariant::WorkerSet(message) =\u003e message.key(),\n },\n delivery_tag,\n };\n\n Ok(msg)\n}\n\n/// Generates an id from a log and a list of receipts. This is used to both the\n/// Message and the worker set and it's format is \"tx_hash:tx_log_index\"\nfn generate_id(log: \u0026Log, receipts: \u0026[TransactionReceipt]) -\u003e String {\n let tx_log_index = calculate_tx_log_index(log, receipts);\n\n format!(\"0x{:x}:{}\", log.transaction_hash.unwrap(), tx_log_index)\n}\n\n/// Helper function to calculate the log index specific to a transaction. It is\n/// used to translate the global log index (index in the block) to a transaction\n/// specific index.\nfn calculate_tx_log_index(log: \u0026Log, receipts: \u0026[TransactionReceipt]) -\u003e u64 {\n if let Some(tx_log_index) = log.transaction_log_index {\n return tx_log_index.as_u64();\n }\n\n let log_index = log.log_index.unwrap().as_u64();\n let tx_index = log.transaction_index.unwrap().as_u64();\n\n let mut logs_before_tx = 0;\n for idx in 0..tx_index {\n logs_before_tx += receipts.get(idx as usize).unwrap().logs.len();\n }\n\n log_index - logs_before_tx as u64\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use consensus_types::consensus::BeaconBlockAlias;\n use ethers::types::{Block, Transaction, H256, U256, U64};\n use std::{fs::File, str::FromStr};\n\n fn create_test_log(tx_index: u64, log_index: u64) -\u003e Log {\n Log {\n transaction_index: Some(U64::from(tx_index)),\n transaction_hash: Some(H256::from_low_u64_be(tx_index)),\n log_index: Some(U256::from(log_index)),\n ..Default::default()\n }\n }\n\n fn create_test_receipts() -\u003e Vec\u003cTransactionReceipt\u003e {\n fn generate_logs(n: u64) -\u003e Vec\u003cLog\u003e {\n let mut logs = Vec::new();\n for _ in 0..n {\n logs.push(Default::default())\n }\n logs\n }\n\n vec![\n TransactionReceipt {\n transaction_hash: H256::from_low_u64_be(1),\n transaction_index: U64::from_str(\"1\").unwrap(),\n logs: generate_logs(10),\n ..Default::default()\n },\n TransactionReceipt {\n transaction_hash: H256::from_low_u64_be(2),\n transaction_index: U64::from_str(\"2\").unwrap(),\n logs: generate_logs(20),\n ..Default::default()\n },\n TransactionReceipt {\n transaction_hash: H256::from_low_u64_be(3),\n transaction_index: U64::from_str(\"3\").unwrap(),\n logs: generate_logs(30),\n ..Default::default()\n },\n ]\n }\n\n fn create_test_block_details() -\u003e FullBlockDetails {\n FullBlockDetails {\n exec_block: Block::\u003cTransaction\u003e::default(),\n beacon_block: BeaconBlockAlias::default(),\n receipts: create_test_receipts(),\n }\n }\n\n #[test]\n fn test_parse_enriched_log_contract_call() {\n let file = File::open(\"testdata/contract_call.json\").unwrap();\n let enriched_log = serde_json::from_reader(file).unwrap();\n let block_details = create_test_block_details();\n\n let result = parse_enriched_log(\u0026enriched_log, \u0026block_details, 1);\n assert!(result.is_ok());\n let enriched_content = result.unwrap();\n\n assert_eq!(enriched_content.exec_block, block_details.exec_block);\n assert_eq!(enriched_content.beacon_block, block_details.beacon_block);\n assert_eq!(enriched_content.receipts, block_details.receipts);\n assert_eq!(\n enriched_content.tx_hash,\n enriched_log.log.transaction_hash.unwrap()\n );\n match enriched_content.content {\n ContentVariant::Message(message) =\u003e {\n assert_eq!(message.cc_id.chain.to_string(), \"ethereum\");\n assert_eq!(\n message.cc_id.id.to_string(),\n format!(\"0x{:x}:{}\", enriched_log.log.transaction_hash.unwrap(), 5)\n );\n }\n _ =\u003e panic!(\"Unexpected content variant\"),\n }\n }\n\n #[test]\n fn test_parse_enriched_log_operatorship_transferred() {\n let file = File::open(\"testdata/operatorship_transferred.json\").unwrap();\n let enriched_log = serde_json::from_reader(file).unwrap();\n let block_details = create_test_block_details();\n\n let result = parse_enriched_log(\u0026enriched_log, \u0026block_details, 1);\n assert!(result.is_ok());\n let enriched_content = result.unwrap();\n\n assert_eq!(enriched_content.exec_block, block_details.exec_block);\n assert_eq!(enriched_content.beacon_block, block_details.beacon_block);\n assert_eq!(enriched_content.receipts, block_details.receipts);\n assert_eq!(\n enriched_content.tx_hash,\n enriched_log.log.transaction_hash.unwrap()\n );\n match enriched_content.content {\n ContentVariant::WorkerSet(message) =\u003e {\n assert_eq!(\n message.message_id.to_string(),\n format!(\"0x{:x}:{}\", enriched_log.log.transaction_hash.unwrap(), 5)\n );\n assert_eq!(message.new_operators_data, \"6f70657261746f7273\");\n }\n _ =\u003e panic!(\"Unexpected content variant\"),\n }\n }\n\n #[test]\n fn test_parse_enriched_log_failure() {\n let enriched_log = EnrichedLog::default();\n let block_details = create_test_block_details();\n\n let result = parse_enriched_log(\u0026enriched_log, \u0026block_details, 1);\n assert!(result.is_err());\n }\n\n #[test]\n fn test_enrich_content() {\n let content = ContentVariant::Message(Message {\n cc_id: CrossChainId {\n chain: \"ethereum\".parse().unwrap(),\n id: \"0x1234\".parse().unwrap(),\n },\n source_address: \"0x00\".parse().unwrap(),\n destination_address: \"0x01\".parse().unwrap(),\n payload_hash: Default::default(),\n destination_chain: \"polygon\".parse().unwrap(),\n });\n let block_details = create_test_block_details();\n let log = create_test_log(0, 5);\n let enriched_content = enrich_content(\u0026content, \u0026log, \u0026block_details, 1).unwrap();\n\n assert_eq!(enriched_content.exec_block, block_details.exec_block);\n assert_eq!(enriched_content.beacon_block, block_details.beacon_block);\n assert_eq!(enriched_content.receipts, block_details.receipts);\n assert_eq!(enriched_content.tx_hash, log.transaction_hash.unwrap());\n assert_eq!(enriched_content.content, content);\n }\n\n #[test]\n fn test_calculate_tx_log_index() {\n let receipts = create_test_receipts();\n\n let log = create_test_log(0, 2);\n let tx_log_index = calculate_tx_log_index(\u0026log, \u0026receipts);\n assert_eq!(tx_log_index, 2);\n\n let log = create_test_log(1, 15);\n let tx_log_index = calculate_tx_log_index(\u0026log, \u0026receipts);\n assert_eq!(tx_log_index, 5);\n\n let log = create_test_log(2, 39);\n let tx_log_index = calculate_tx_log_index(\u0026log, \u0026receipts);\n assert_eq!(tx_log_index, 9);\n\n let log = create_test_log(3, 60);\n let tx_log_index = calculate_tx_log_index(\u0026log, \u0026receipts);\n assert_eq!(tx_log_index, 0);\n }\n\n #[test]\n fn test_generate_cc_id() {\n let receipts = create_test_receipts();\n\n let log = create_test_log(0, 2);\n let id = generate_id(\u0026log, \u0026receipts);\n assert_eq!(id, format!(\"0x{:x}:{}\", log.transaction_hash.unwrap(), 2));\n\n let log = create_test_log(1, 12);\n let id = generate_id(\u0026log, \u0026receipts);\n assert_eq!(id, format!(\"0x{:x}:{}\", log.transaction_hash.unwrap(), 2));\n\n let log = create_test_log(2, 35);\n let id = generate_id(\u0026log, \u0026receipts);\n assert_eq!(id, format!(\"0x{:x}:{}\", log.transaction_hash.unwrap(), 5))\n }\n}\n","traces":[{"line":19,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":24,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":25,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":26,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":27,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":28,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":30,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":36,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":38,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":39,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":44,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":45,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":46,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":47,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":56,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":14},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":28},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":10},"fn_name":null}],"covered":42,"coverable":43},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","relayer.rs"],"content":"use crate::{\n consumers::Amqp,\n parser::parse_enriched_log,\n types::{Config, EnrichedLog, VerificationMethod},\n verifier::VerifierAPI,\n};\nuse consensus_types::{\n common::ContentVariant,\n proofs::{BatchVerificationData, UpdateVariant},\n};\nuse eth::{consensus::EthBeaconAPI, execution::EthExecutionAPI, utils::get_full_block_details};\nuse eyre::{eyre, Result};\nuse log::{debug, error, info, warn};\nuse prover::prover::{errors::StateProverError, types::EnrichedContent, ProverAPI};\n\nuse std::{collections::HashMap, sync::Arc, time::Duration};\nuse tokio::time::sleep;\n\n/// This is the main module of the relayer. It fetches logs from the rabbitMQ\n/// consumer, generates the proofs and forwards them to the verifier.\npub struct Relayer\u003cP, C, CR, ER, V\u003e {\n config: Config,\n consensus: Arc\u003cCR\u003e,\n consumer: C,\n execution: Arc\u003cER\u003e,\n prover: Arc\u003cP\u003e,\n verifier: V,\n}\n\nimpl\u003cC: Amqp, P: ProverAPI, CR: EthBeaconAPI, ER: EthExecutionAPI, V: VerifierAPI\u003e\n Relayer\u003cP, C, CR, ER, V\u003e\n{\n pub async fn new(\n config: Config,\n consumer: C,\n consensus: Arc\u003cCR\u003e,\n execution: Arc\u003cER\u003e,\n prover: Arc\u003cP\u003e,\n verifier: V,\n ) -\u003e Self {\n Relayer {\n config,\n consumer,\n consensus,\n execution,\n prover,\n verifier,\n }\n }\n\n /// This is the main function of the relayer. It runs in a loop and relays\n /// new events that come from the consumer to the verifier after generating\n /// the neccessary proofs.\n pub async fn start(\u0026mut self) {\n let interval = Duration::from_secs(self.config.process_interval);\n\n loop {\n let res = self.relay().await;\n if let Err(e) = res {\n error!(\"Relay failed {:?}\", e);\n }\n\n sleep(interval).await;\n }\n }\n\n /// This function does a single round of relaying. It fetches a set of logs\n /// from the consumer, parses them accordingly, generates the needed proofs\n /// and relays them to the verifier. If the relay failed for any reason, the\n /// message is being requeued to the RabbitMQ otherwise it's being acked.\n pub async fn relay(\u0026mut self) -\u003e Result\u003c()\u003e {\n let update = self\n .get_update(\u0026self.config.verification_method)\n .await\n .map_err(|e| eyre!(\"Error fetching update {}\", e))?;\n\n if self.config.state_prover_check\n \u0026\u0026 !self\n .has_state(\u0026update.recent_block().state_root.to_string())\n .await?\n {\n error!(\n \"State {} not found in state_prover (lodestar) cache. Requeuing\",\n update.recent_block().state_root\n );\n }\n\n let fetched_logs = self.collect_messages(self.config.max_batch_size).await;\n if fetched_logs.is_empty() {\n info!(\"No new logs to process\");\n return Ok(());\n }\n\n let contents = self.process_logs(fetched_logs).await;\n let contents = self\n .filter_applicable_content(contents, update.clone())\n .await?;\n\n let (proof_contents, batched_proofs) = self\n .get_proofs(contents, \u0026update)\n .await\n .map_err(|e| eyre!(\"Error generating proofs {}\", e))?;\n if proof_contents.is_empty() {\n info!(\"No new contents to process\");\n return Ok(());\n }\n\n let _ = self.submit_proofs(\u0026proof_contents, \u0026batched_proofs).await?;\n\n Ok(())\n }\n\n async fn submit_proofs(\n \u0026self,\n contents: \u0026Vec\u003cEnrichedContent\u003e,\n proofs: \u0026BatchVerificationData,\n ) -\u003e Result\u003cVec\u003cString\u003e\u003e {\n let result = self.verifier.verify_data(proofs.clone()).await?;\n\n let mut successful_ids = vec![];\n\n for content in contents {\n let delivery_tag = content.delivery_tag;\n let (id, res) = result.iter().find(|(key, _)| *key == content.id).unwrap(); // Will definitely succeed\n if res == \"OK\" {\n info!(\"Content with id={:?} succeeded\", id);\n successful_ids.push(id.clone());\n self.consumer.ack_delivery(delivery_tag).await?;\n } else {\n error!(\"Content {} failed by the verifer with err: {}\", id, res);\n self.consumer.nack_delivery(delivery_tag).await?;\n }\n }\n\n info!(\"Verified the following events {:#?}\", successful_ids);\n Ok(successful_ids)\n }\n\n /// This function generates the batched proofs for a set of contents.\n /// - Will nack (requeue) if the batch_verification_data generation failed for a message\n async fn get_proofs(\n \u0026self,\n contents: Vec\u003cEnrichedContent\u003e,\n update: \u0026UpdateVariant,\n ) -\u003e Result\u003c(Vec\u003cEnrichedContent\u003e, BatchVerificationData)\u003e {\n let batched = self.prover.batch_messages(\u0026contents);\n\n let batch_verification_data = self\n .prover\n .batch_generate_proofs(batched, update.clone())\n .await?;\n let successful = self.extract_all_contents(\u0026batch_verification_data);\n let mut successful_enriched = vec![];\n\n // Reject the contents that were not included in the batch_verification_data\n for content in contents {\n if !successful.contains(\u0026content.content) {\n warn!(\n \"Content {:?} was not included in the batch_verification_data. Requeuing\",\n content.content\n );\n self.consumer.nack_delivery(content.delivery_tag).await?;\n continue;\n }\n successful_enriched.push(content);\n }\n\n info!(\"Generated {} proofs\", successful_enriched.len());\n Ok((successful_enriched, batch_verification_data))\n }\n\n /// Filters out the contents that are not applicable to the current update.\n /// - Will nack (requeue) if the content is more recent than the recent block of the latest update\n async fn filter_applicable_content(\n \u0026self,\n contents: Vec\u003cEnrichedContent\u003e,\n update: UpdateVariant,\n ) -\u003e Result\u003cVec\u003cEnrichedContent\u003e\u003e {\n let recent_block_slot = update.recent_block().slot;\n let mut applicable = vec![];\n let contents_len = contents.len();\n\n for content in contents {\n if content.beacon_block.slot \u003e= recent_block_slot {\n warn!(\n \"Message {:?} is too recent. Update slot: {}, content slot: {}. Requeuing\",\n content.content, recent_block_slot, content.beacon_block.slot\n );\n self.consumer.nack_delivery(content.delivery_tag).await?;\n continue;\n }\n\n if self.config.reject_historical_roots\n \u0026\u0026 content.beacon_block.slot \u003c recent_block_slot - 7000\n {\n warn!(\n \"Message {:?} would be in historical. Update slot: {}, content slot: {}. Removing\",\n content.content, recent_block_slot, content.beacon_block.slot\n );\n self.consumer.ack_delivery(content.delivery_tag).await?;\n continue;\n }\n\n applicable.push(content);\n }\n\n info!(\n \"Filtered {} out of {} contents\",\n applicable.len(),\n contents_len\n );\n Ok(applicable)\n }\n\n /// The function that generates contents compatible with the prover out of a\n /// set of logs provided by the consumer.\n /// - Will nack (requeue) if full blockchain data could not be received\n /// - Will ack (remove from queue) if the log could not be parsed\n async fn process_logs(\n \u0026mut self,\n fetched_logs: HashMap\u003cu64, EnrichedLog\u003e,\n ) -\u003e Vec\u003cEnrichedContent\u003e {\n let mut contents: Vec\u003cEnrichedContent\u003e = vec![];\n\n for (delivery_tag, enriched_log) in fetched_logs {\n debug!(\"Working on log {}\", enriched_log.event_name);\n let block_details = get_full_block_details(\n self.consensus.clone(),\n self.execution.clone(),\n enriched_log.log.block_number.unwrap().as_u64(),\n self.config.genesis_timestamp,\n )\n .await;\n\n if block_details.is_err() {\n error!(\n \"Error fetching block details {:?}. Requeuing\",\n block_details\n );\n self.consumer.nack_delivery(delivery_tag).await.unwrap();\n continue;\n }\n\n let content = parse_enriched_log(\u0026enriched_log, \u0026block_details.unwrap(), delivery_tag);\n if content.is_err() {\n error!(\n \"Error parsing enriched log {:?}. {:?}. Will not requeue\",\n enriched_log,\n content.err().unwrap()\n );\n\n self.consumer.ack_delivery(delivery_tag).await.unwrap();\n continue;\n }\n\n let content = content.unwrap();\n contents.push(content)\n }\n\n info!(\"Generated {} contents\", contents.len());\n contents\n }\n\n /// Fetches a set of messages from the consumer up to a limit.\n /// If the message cannot be parsed, it's being taken out of the queue.\n async fn collect_messages(\u0026mut self, max_messages: usize) -\u003e HashMap\u003cu64, EnrichedLog\u003e {\n let deliveries = self.consumer.consume(max_messages).await;\n if deliveries.is_err() {\n error!(\"Error consuming messages {:?}\", deliveries);\n return HashMap::new();\n }\n let deliveries = deliveries.unwrap();\n\n let mut enriched_logs: HashMap\u003cu64, EnrichedLog\u003e = HashMap::new();\n for (delivery_tag, data_str) in \u0026deliveries {\n debug!(\"Working on delivery_tag={:?}\", delivery_tag);\n let enriched_log = serde_json::from_str(data_str);\n if enriched_log.is_err() {\n error!(\"Error parsing log {:?}\", enriched_log);\n\n self.consumer.ack_delivery(*delivery_tag).await.unwrap();\n continue;\n }\n enriched_logs.insert(*delivery_tag, enriched_log.unwrap());\n }\n\n info!(\n \"Generated {} enriched_logs from {} deliveries\",\n enriched_logs.len(),\n \u0026deliveries.len()\n );\n enriched_logs\n }\n\n /// Fetches either a finality or an optimistic light client update, provided a verification method.\n async fn get_update(\u0026self, verification_method: \u0026VerificationMethod) -\u003e Result\u003cUpdateVariant\u003e {\n match verification_method {\n VerificationMethod::Finality =\u003e match self.consensus.get_finality_update().await {\n Ok(update) =\u003e {\n info!(\n \"Got finality update with slot {}\",\n update.finalized_header.beacon.slot\n );\n Ok(UpdateVariant::Finality(update))\n }\n Err(e) =\u003e Err(eyre!(\"Error fetching finality update {}\", e)),\n },\n VerificationMethod::Optimistic =\u003e match self.consensus.get_optimistic_update().await {\n Ok(update) =\u003e {\n info!(\n \"Got optimistic update with slot {}\",\n update.attested_header.beacon.slot\n );\n Ok(UpdateVariant::Optimistic(update))\n }\n Err(e) =\u003e Err(eyre!(\"Error fetching finality update {}\", e)),\n },\n }\n }\n\n /// A helper function that extracts all messages out of the main structure of batched proofs.\n /// Used to see which messages succeeded and which not, in order to ack or nack accordingly.\n pub fn extract_all_contents(\u0026self, data: \u0026BatchVerificationData) -\u003e Vec\u003cContentVariant\u003e {\n data.target_blocks\n .iter()\n .flat_map(|block| \u0026block.transactions_proofs)\n .flat_map(|transaction| \u0026transaction.content)\n .cloned() // Clone each ContentVariant, required if ContentVariant does not implement Copy\n .collect()\n }\n\n pub async fn has_state(\u0026self, state_id: \u0026str) -\u003e Result\u003cbool, StateProverError\u003e {\n debug!(\"Checking state for {}\", state_id);\n let req = format!(\n \"{}/has_state?state_id={}\u0026network={}\",\n self.config.state_prover_rpc, state_id, self.config.network\n );\n\n let response = reqwest::get(\u0026req).await?;\n\n if response.status() == reqwest::StatusCode::NOT_FOUND {\n return Ok(false);\n }\n\n info!(\"State is in the cache of the state_prover\");\n Ok(true)\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use crate::consumers::MockLapinConsumer;\n use crate::types::{Config, VerificationMethod};\n use crate::verifier::MockVerifierAPI;\n use consensus_types::consensus::{BeaconBlockAlias, FinalityUpdate};\n use consensus_types::proofs::{\n AncestryProof, BlockProofsBatch, CrossChainId, Message, TransactionProofsBatch,\n };\n use consensus_types::sync_committee_rs::consensus_types::{BeaconBlock, BeaconBlockHeader};\n use eth::consensus::MockConsensusRPC;\n use eth::execution::MockExecutionRPC;\n use eth::types::FullBlockDetails;\n use ethers::types::{Block, Transaction, H256};\n use indexmap::IndexMap;\n use mockall::predicate;\n use prover::prover::proof_generator::MockProofGenerator;\n use prover::prover::state_prover::MockStateProver;\n use prover::prover::types::BatchContentGroups;\n use prover::prover::MockProver;\n use prover::Prover;\n use std::fs::{self, File};\n use std::sync::Arc;\n\n type MockProverAlias = MockProver\u003cMockProofGenerator\u003cMockConsensusRPC, MockStateProver\u003e\u003e;\n\n fn setup_test() -\u003e (\n Config,\n MockLapinConsumer,\n MockConsensusRPC,\n MockExecutionRPC,\n MockProverAlias,\n MockVerifierAPI,\n ) {\n let config = Config {\n max_batch_size: 1,\n process_interval: 1,\n verification_method: VerificationMethod::Finality,\n genesis_timestamp: 0,\n state_prover_check: false,\n ..Default::default()\n };\n let consumer = MockLapinConsumer::new();\n let consensus = MockConsensusRPC::new();\n let execution = MockExecutionRPC::new();\n let prover = MockProver::new();\n let verifier = MockVerifierAPI::new();\n\n (config, consumer, consensus, execution, prover, verifier)\n }\n\n fn get_content(tx_hash_n: u64, log_index: u64) -\u003e ContentVariant {\n ContentVariant::Message(Message {\n cc_id: CrossChainId {\n chain: \"ethereum\".parse().unwrap(),\n id: format!(\"{:x}:{}\", H256::from_low_u64_be(tx_hash_n), log_index)\n .parse()\n .unwrap(),\n },\n source_address: \"0x1234\".parse().unwrap(),\n destination_chain: \"ethereum\".parse().unwrap(),\n destination_address: \"0x1234\".parse().unwrap(),\n payload_hash: Default::default(),\n })\n }\n\n fn get_mock_ver_data() -\u003e BatchVerificationData {\n BatchVerificationData {\n update: UpdateVariant::default(),\n target_blocks: vec![\n BlockProofsBatch {\n ancestry_proof: AncestryProof::default(),\n target_block: BeaconBlockHeader {\n slot: 1,\n ..Default::default()\n },\n transactions_proofs: vec![\n TransactionProofsBatch {\n transaction_proof: Default::default(),\n receipt_proof: Default::default(),\n content: vec![get_content(1, 0), get_content(1, 1)],\n },\n TransactionProofsBatch {\n transaction_proof: Default::default(),\n receipt_proof: Default::default(),\n content: vec![get_content(2, 0), get_content(2, 1)],\n },\n ],\n },\n BlockProofsBatch {\n ancestry_proof: AncestryProof::default(),\n target_block: BeaconBlockHeader {\n slot: 2,\n ..Default::default()\n },\n transactions_proofs: vec![\n TransactionProofsBatch {\n transaction_proof: Default::default(),\n receipt_proof: Default::default(),\n content: vec![get_content(3, 0), get_content(3, 1)],\n },\n TransactionProofsBatch {\n transaction_proof: Default::default(),\n receipt_proof: Default::default(),\n content: vec![get_content(4, 0), get_content(4, 1)],\n },\n ],\n },\n ],\n }\n }\n\n fn get_mock_finality_update(recent_block_slot: u64) -\u003e FinalityUpdate {\n let mut finality_update = FinalityUpdate::default();\n finality_update.finalized_header.beacon.slot = recent_block_slot;\n finality_update\n }\n\n fn get_mock_exec_block(block_number: u64) -\u003e Block\u003cTransaction\u003e {\n Block {\n timestamp: ethers::types::U256::from(1),\n number: Some(block_number.into()),\n ..Default::default()\n }\n }\n\n fn get_mock_enriched_log(block_number: u64, tx_hash_n: u64, log_index: u64) -\u003e EnrichedLog {\n let file = File::open(\"testdata/contract_call.json\").unwrap();\n let mut enriched_log: EnrichedLog = serde_json::from_reader(file).unwrap();\n enriched_log.log.transaction_hash = Some(H256::from_low_u64_be(tx_hash_n));\n enriched_log.log.block_number = Some(block_number.into());\n enriched_log.log.transaction_log_index = Some(log_index.into());\n enriched_log\n }\n\n fn get_mock_enriched_content(\n slot: u64,\n block_num: u64,\n delivery_tag: u64,\n id: \u0026str,\n ) -\u003e EnrichedContent {\n EnrichedContent {\n id: id.to_string(),\n content: get_content(1, 1),\n tx_hash: H256::default(),\n exec_block: Block {\n number: Some(block_num.into()),\n ..Default::default()\n },\n beacon_block: BeaconBlockAlias {\n slot,\n ..Default::default()\n },\n receipts: Default::default(),\n delivery_tag,\n }\n }\n\n pub fn get_mock_batched_data(\n update: UpdateVariant,\n contents: Vec\u003cEnrichedContent\u003e,\n ) -\u003e (BatchContentGroups, BatchVerificationData) {\n let mut groups: BatchContentGroups = IndexMap::new();\n for content in contents {\n groups\n .entry(content.exec_block.number.unwrap().as_u64())\n .or_default()\n .entry(content.tx_hash)\n .or_default()\n .push(content.clone());\n }\n\n let mut block_proofs_batch: Vec\u003cBlockProofsBatch\u003e = vec![];\n for (_, block_groups) in \u0026groups {\n let mut block_proof = BlockProofsBatch {\n ancestry_proof: Default::default(),\n target_block: Default::default(),\n transactions_proofs: vec![],\n };\n\n for (_, contents) in block_groups {\n let tx_level_verification = TransactionProofsBatch {\n transaction_proof: Default::default(),\n receipt_proof: Default::default(),\n content: contents.iter().map(|m| m.content.clone()).collect(),\n };\n\n block_proof.transactions_proofs.push(tx_level_verification);\n }\n\n block_proofs_batch.push(block_proof);\n }\n\n (\n groups,\n BatchVerificationData {\n update,\n target_blocks: block_proofs_batch,\n },\n )\n }\n\n #[tokio::test]\n async fn test_filter_applicable_content() {\n let (config, mut consumer, consensus, execution, prover, verifier) = setup_test();\n\n consumer\n .expect_nack_delivery()\n .times(3)\n .returning(|_| Ok(()));\n\n let relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let update = UpdateVariant::Finality(get_mock_finality_update(10));\n let contents = vec![\n get_mock_enriched_content(8, 8, 0, \"id1\"),\n get_mock_enriched_content(10, 10, 1, \"id2\"),\n get_mock_enriched_content(15, 15, 2, \"id3\"),\n get_mock_enriched_content(12, 12, 3, \"id4\"),\n ];\n\n let filtered = relayer\n .filter_applicable_content(contents, update)\n .await\n .unwrap();\n assert_eq!(filtered.len(), 1);\n }\n\n #[tokio::test]\n async fn test_submit_proofs() {\n let (config, mut consumer, consensus, execution, prover, mut verifier) = setup_test();\n\n let contents = vec![get_mock_enriched_content(1, 1, 1, \"id1\")];\n let finality_update = get_mock_finality_update(10);\n let (_, ver_data) = get_mock_batched_data(\n UpdateVariant::Finality(finality_update.clone()),\n contents.clone(),\n );\n\n consumer\n .expect_ack_delivery()\n .with(predicate::eq(1))\n .returning(|_| Ok(()));\n\n consumer.expect_nack_delivery().never();\n\n verifier\n .expect_verify_data()\n .returning(move |_| Ok(vec![(\"id1\".to_string(), \"OK\".to_string())]));\n\n let relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let proofs = relayer.submit_proofs(\u0026contents, \u0026ver_data).await;\n assert!(proofs.is_ok());\n assert_eq!(proofs.unwrap(), vec![\"id1\".to_string()]);\n }\n\n #[tokio::test]\n async fn test_submit_proofs_with_nack() {\n let (config, mut consumer, consensus, execution, prover, mut verifier) = setup_test();\n\n let c1 = get_mock_enriched_content(1, 1, 1, \"id1\");\n let c2 = get_mock_enriched_content(2, 2, 2, \"id2\");\n let contents = vec![c1, c2];\n\n let finality_update = get_mock_finality_update(10);\n let (_, mut ver_data) = get_mock_batched_data(\n UpdateVariant::Finality(finality_update.clone()),\n contents.clone(),\n );\n ver_data.target_blocks.remove(1);\n\n consumer\n .expect_ack_delivery()\n .with(predicate::eq(1))\n .once()\n .returning(|_| Ok(()));\n consumer\n .expect_nack_delivery()\n .with(predicate::eq(2))\n .once()\n .returning(|_| Ok(()));\n verifier.expect_verify_data().returning(move |_| {\n Ok(vec![\n (\"id1\".to_string(), \"OK\".to_string()),\n (\"id2\".to_string(), \"ERR\".to_string()),\n ])\n });\n\n let relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let proofs = relayer.submit_proofs(\u0026contents, \u0026ver_data).await;\n assert!(proofs.is_ok());\n assert_eq!(proofs.unwrap(), vec![\"id1\".to_string()]);\n }\n\n #[tokio::test]\n async fn test_get_proofs() {\n let (config, mut consumer, consensus, execution, mut prover, _verifier) = setup_test();\n\n let enriched_log = get_mock_enriched_log(5, 1, 0);\n let block_details = FullBlockDetails {\n exec_block: get_mock_exec_block(5),\n beacon_block: BeaconBlockAlias {\n slot: 5,\n ..Default::default()\n },\n receipts: vec![],\n };\n\n let mut contents = vec![parse_enriched_log(\u0026enriched_log, \u0026block_details, 1).unwrap()];\n let finality_update = get_mock_finality_update(10);\n let (batched_contents, ver_data) = get_mock_batched_data(\n UpdateVariant::Finality(finality_update.clone()),\n contents.clone(),\n );\n\n prover\n .expect_batch_messages()\n .returning(move |_| batched_contents.clone());\n prover\n .expect_batch_generate_proofs()\n .returning(move |_, _| Ok(ver_data.clone()));\n\n consumer\n .expect_nack_delivery()\n .with(predicate::eq(1))\n .times(1)\n .returning(|_| Ok(()));\n\n let relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n MockVerifierAPI::new(),\n )\n .await;\n\n contents.push(get_mock_enriched_content(2, 2, 1, \"id2\"));\n let update = UpdateVariant::Finality(finality_update);\n let proofs = relayer.get_proofs(contents, \u0026update).await;\n assert!(proofs.is_ok());\n let (enriched_content, batched_proofs) = proofs.unwrap();\n assert_eq!(enriched_content.len(), 1);\n\n let contents_of_verdata = relayer.extract_all_contents(\u0026batched_proofs);\n assert_eq!(contents_of_verdata.len(), 1)\n }\n\n #[tokio::test]\n async fn test_relay_valid() {\n let (config, mut consumer, mut consensus, mut execution, mut prover, mut verifier) =\n setup_test();\n\n let content = get_mock_enriched_content(5, 5, 1, \"id1\");\n let enriched_log = get_mock_enriched_log(5, 1, 0);\n let block_details = FullBlockDetails {\n exec_block: get_mock_exec_block(5),\n beacon_block: BeaconBlock {\n slot: 5,\n ..Default::default()\n },\n receipts: vec![],\n };\n\n let finality_update = get_mock_finality_update(10);\n\n let (batched, ver_data) = get_mock_batched_data(\n UpdateVariant::Finality(finality_update.clone()),\n vec![content],\n );\n\n consensus\n .expect_get_finality_update()\n .returning(move || Ok(finality_update.clone()));\n execution\n .expect_get_block_with_txs()\n .returning(move |_| Ok(Some(block_details.exec_block.clone())));\n consensus\n .expect_get_beacon_block()\n .returning(move |_| Ok(block_details.beacon_block.clone()));\n execution\n .expect_get_block_receipts()\n .returning(move |_| Ok(block_details.receipts.clone()));\n\n consumer\n .expect_consume()\n .with(predicate::eq(1))\n .returning(move |_| Ok(vec![(1, serde_json::to_string(\u0026enriched_log).unwrap())]));\n prover\n .expect_batch_messages()\n .returning(move |_| batched.clone());\n prover\n .expect_batch_generate_proofs()\n .returning(move |_, _| Ok(ver_data.clone()));\n\n consumer\n .expect_ack_delivery()\n .with(predicate::eq(1))\n .returning(|_| Ok(()));\n\n consumer.expect_nack_delivery().returning(|_| Ok(()));\n\n verifier\n .expect_verify_data()\n .returning(move |_| Ok(vec![(\"id1\".to_string(), \"OK\".to_string())]));\n\n let mut relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let relayed = relayer.relay().await;\n println!(\"{:?}\", relayed);\n assert!(relayed.is_ok());\n }\n\n #[tokio::test]\n async fn test_process_logs_valid() {\n let (config, consumer, mut consensus, mut execution, _, verifier) = setup_test();\n let file = File::open(\"testdata/contract_call.json\").unwrap();\n let enriched_log: EnrichedLog = serde_json::from_reader(file).unwrap();\n let prover = Prover::new(MockProofGenerator::\u003cMockConsensusRPC, MockStateProver\u003e::new());\n\n execution.expect_get_block_with_txs().returning(|_| {\n Ok(Some(Block {\n timestamp: ethers::types::U256::from(1),\n ..Default::default()\n }))\n });\n consensus\n .expect_get_beacon_block()\n .returning(|_| Ok(Default::default()));\n execution\n .expect_get_block_receipts()\n .returning(|_| Ok(Default::default()));\n\n let mut relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let mut fetched_logs = HashMap::new();\n fetched_logs.insert(0, enriched_log.clone());\n\n let res = relayer.process_logs(fetched_logs).await;\n\n let content = res.first().unwrap();\n assert_eq!(\n content.exec_block,\n Block {\n timestamp: ethers::types::U256::from(1),\n ..Default::default()\n }\n );\n assert_eq!(content.beacon_block, Default::default());\n\n match \u0026content.content {\n ContentVariant::Message(message) =\u003e {\n assert_eq!(\n message.cc_id.id,\n format!(\n \"0x{:x}:{}\",\n enriched_log.log.transaction_hash.unwrap(),\n enriched_log.log.log_index.unwrap()\n )\n .parse()\n .unwrap()\n );\n }\n _ =\u003e panic!(\"Wrong content type\"),\n }\n }\n\n #[tokio::test]\n async fn test_collect_messages_valid() {\n let (config, mut consumer, consensus, execution, prover, verifier) = setup_test();\n let path = \"testdata/contract_call.json\";\n let contents = fs::read_to_string(path).unwrap();\n let expected_log = serde_json::from_str::\u003cEnrichedLog\u003e(\u0026contents).unwrap();\n\n consumer\n .expect_consume()\n .returning(move |_| Ok(vec![(0, contents.clone())]));\n\n let mut relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let fetched_logs = relayer.collect_messages(1).await;\n assert_eq!(fetched_logs.len(), 1);\n assert_eq!(fetched_logs.get(\u00260).unwrap(), \u0026expected_log);\n }\n\n #[tokio::test]\n async fn test_collect_messages_invalid_delivery() {\n let (config, mut consumer, consensus, execution, prover, verifier) = setup_test();\n\n consumer\n .expect_consume()\n .returning(move |_| Ok(vec![(0, \"invalid message\".to_string())]));\n consumer.expect_ack_delivery().once().returning(|_| Ok(()));\n\n let mut relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let fetched_logs = relayer.collect_messages(1).await;\n assert_eq!(fetched_logs.len(), 0);\n }\n\n #[tokio::test]\n async fn test_collect_messages_consumer_failure() {\n let (config, mut consumer, consensus, execution, prover, verifier) = setup_test();\n\n consumer\n .expect_consume()\n .returning(move |_| Err(eyre!(\"Consumer failed\")));\n\n let mut relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let fetched_logs = relayer.collect_messages(1).await;\n assert_eq!(fetched_logs.len(), 0);\n }\n\n #[tokio::test]\n async fn test_get_update() {\n let (config, consumer, mut consensus, execution, prover, verifier) = setup_test();\n consensus\n .expect_get_finality_update()\n .returning(|| Ok(Default::default()));\n consensus\n .expect_get_optimistic_update()\n .returning(|| Ok(Default::default()));\n let relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let update = relayer.get_update(\u0026VerificationMethod::Finality).await;\n assert!(update.is_ok());\n assert_eq!(update.unwrap(), UpdateVariant::Finality(Default::default()));\n\n let update = relayer.get_update(\u0026VerificationMethod::Optimistic).await;\n assert!(update.is_ok());\n assert_eq!(\n update.unwrap(),\n UpdateVariant::Optimistic(Default::default())\n );\n }\n\n #[tokio::test]\n async fn test_extract_all_contents() {\n let (config, consumer, consensus, execution, prover, verifier) = setup_test();\n let relayer = Relayer::new(\n config,\n consumer,\n Arc::new(consensus),\n Arc::new(execution),\n Arc::new(prover),\n verifier,\n )\n .await;\n\n let ver_data = get_mock_ver_data();\n let content = relayer.extract_all_contents(\u0026ver_data);\n let expected_content = vec![\n get_content(1, 0),\n get_content(1, 1),\n get_content(2, 0),\n get_content(2, 1),\n get_content(3, 0),\n get_content(3, 1),\n get_content(4, 0),\n get_content(4, 1),\n ];\n assert_eq!(content, expected_content);\n }\n}\n","traces":[{"line":33,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":54,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":74,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":89,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":126,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":127,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":130,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":135,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":136,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":141,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":146,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":148,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":149,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":150,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":151,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":152,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":153,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":156,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":157,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":158,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":160,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":162,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":163,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":165,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":168,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":169,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":174,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":179,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":180,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":181,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":183,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":184,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":185,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":187,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":189,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":190,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":193,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":194,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":196,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":198,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":200,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":201,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":204,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":207,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":209,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":210,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":212,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":219,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":223,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":225,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":226,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":228,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":229,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":230,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":231,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":233,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":235,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":236,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":238,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":240,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":241,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":244,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":245,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":246,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":248,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":249,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":252,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":253,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":256,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":260,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":261,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":266,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":267,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":268,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":269,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":272,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":274,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":275,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":276,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":277,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":278,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":279,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":281,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":282,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":284,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":287,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":289,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":290,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":292,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":297,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":298,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":299,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":300,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":302,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":304,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":306,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":308,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":309,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":310,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":312,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":314,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":316,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":323,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":324,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":326,"address":[],"length":0,"stats":{"Line":13},"fn_name":null},{"line":327,"address":[],"length":0,"stats":{"Line":15},"fn_name":null},{"line":332,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":333,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":334,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":336,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":339,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":341,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":342,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":345,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":346,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":83,"coverable":159},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","types.rs"],"content":"use consensus_types::{\n common::WorkerSetMessage,\n consensus::Update,\n lightclient::LightClientState,\n proofs::{BatchVerificationData, Message},\n};\nuse eth::types::EthConfig;\nuse ethers::{\n contract::EthEvent,\n types::{Address, Bytes, Log, H256},\n};\nuse prover::prover::types::ProverConfig;\nuse serde::{Deserialize, Serialize};\npub use std::str::FromStr;\n\n// Step 1: Define the enum\n#[derive(Debug, Clone, Default)]\npub enum VerificationMethod {\n Optimistic,\n #[default]\n Finality,\n}\n\nimpl FromStr for VerificationMethod {\n type Err = ();\n\n fn from_str(input: \u0026str) -\u003e Result\u003cVerificationMethod, Self::Err\u003e {\n match input {\n \"optimistic\" =\u003e Ok(VerificationMethod::Optimistic),\n \"finality\" =\u003e Ok(VerificationMethod::Finality),\n _ =\u003e Err(()),\n }\n }\n}\n\n/// Main configuration structure of the relayer.\n#[derive(Debug, Clone, Default)]\npub struct Config {\n /// Network configuration. This will be used to generate the content id, as\n /// well as tweak the request in the state_prover accordingly.\n pub network: String,\n /// RPC endpoint for the consensus layer.\n pub consensus_rpc: String,\n /// RPC endpoint for the execution layer.\n pub execution_rpc: String,\n /// The RPC of the WASM chain. In our case it's Axelar devnet\n pub wasm_rpc: String,\n /// The API url of the state prover\n pub state_prover_rpc: String,\n /// The API url of the block roots archive\n pub block_roots_rpc: String,\n /// The Axelar Gateway address in the Ethereum chain\n pub gateway_addr: String,\n /// The verifier address in the wasm chain\n pub verifier_addr: String,\n\n /// Should the historical roots be rejected from the queue? Enabling it\n /// might drain the beacon API quota\n pub reject_historical_roots: bool,\n pub historical_roots_block_roots_batch_size: u64,\n\n /// Determines whether the verification will occur using sync_committee optimistic\n /// or finality updates. Use with caution, optimistic verification might lead to\n /// re-orgs.\n pub verification_method: VerificationMethod,\n\n /// Sentinel rabbitMQ details\n pub sentinel_queue_addr: String,\n pub sentinel_queue_name: String,\n\n /// RPC options for the eth package\n pub rpc_pool_max_idle_per_host: usize,\n pub rpc_timeout_secs: u64,\n pub rpc_max_retries: u64,\n\n /// What is the genesis timestamp of the ETH chain. Used to calculate slots out of timestmaps\n pub genesis_timestamp: u64,\n\n /// How many contents should the relayer process in one round/batch\n pub max_batch_size: usize,\n /// How many seconds should the relayer wait before processing the next batch\n pub process_interval: u64,\n /// How many seconds should the feeder wait before feeding the verifier with new\n /// update messages\n pub feed_interval: u64,\n /// Used to run execute messages in the wasm using wasmd. Will be deprecated\n pub wasm_wallet: String,\n /// Should the state prover implement\n pub state_prover_check: bool,\n}\n\nimpl From\u003cConfig\u003e for ProverConfig {\n fn from(config: Config) -\u003e Self {\n ProverConfig {\n network: config.network,\n consensus_rpc: config.consensus_rpc,\n execution_rpc: config.execution_rpc,\n state_prover_rpc: config.state_prover_rpc,\n reject_historical_roots: config.reject_historical_roots,\n historical_roots_block_roots_batch_size: config.historical_roots_block_roots_batch_size,\n }\n }\n}\n\nimpl From\u003cConfig\u003e for EthConfig {\n fn from(config: Config) -\u003e Self {\n EthConfig {\n pool_max_idle_per_host: config.rpc_pool_max_idle_per_host,\n timeout_secs: config.rpc_timeout_secs,\n rpc_max_retries: config.rpc_max_retries,\n }\n }\n}\n\n// Events\n#[derive(Debug, Clone, EthEvent, PartialEq)]\npub struct ContractCall {\n #[ethevent(indexed)]\n pub sender: Address,\n pub destination_chain: String,\n pub destination_contract_address: String,\n #[ethevent(indexed)]\n pub payload_hash: H256,\n pub payload: Bytes,\n}\n\n#[derive(Debug, Clone, EthEvent, PartialEq)]\npub struct OperatorshipTransferred {\n pub new_operators_data: Bytes,\n}\n\n#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)]\npub struct EnrichedLog {\n pub event_name: String,\n pub contract_name: String,\n pub chain: String, // Assuming ChainName is a simple string, replace with actual type if not\n pub log: Log,\n pub source: String,\n pub tx_to: Address,\n}\n\n#[derive(Debug, serde::Deserialize)]\npub struct LightClientStateResult {\n pub data: LightClientState,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct UpdateExecuteMsg {\n #[serde(rename = \"LightClientUpdate\")]\n pub light_client_update: Update,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct BatchVerificationDataRequest {\n #[serde(rename = \"BatchVerificationData\")]\n pub batch_verification_data: BatchVerificationData,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct IsWorkerSetVerifiedRequest {\n pub is_worker_set_verified: WorkerSetMessage,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct IsWorkerSetVerifiedResult {\n pub data: bool,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct IsVerifiedMessages {\n pub messages: Vec\u003cMessage\u003e,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct IsVerifiedRequest {\n pub is_verified: IsVerifiedMessages,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct IsVerifiedResponse {\n pub data: Vec\u003c(Message, bool)\u003e,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct VerifyDataResponse {\n pub data: String,\n}\n","traces":[{"line":27,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":28,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":29,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":30,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":108,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":0,"coverable":16},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","utils.rs"],"content":"use dotenv::dotenv;\nuse std::{env, str::FromStr};\n\nuse crate::types::{Config, VerificationMethod};\n\npub fn is_in_slot_range(slot: u64, start_slot: u64, end_slot: u64) -\u003e bool {\n slot \u003e= start_slot \u0026\u0026 slot \u003c end_slot\n}\n\n/// Loads the full relayer configuration from the environment variables.\npub fn load_config() -\u003e Config {\n dotenv().ok();\n\n Config {\n network: env::var(\"NETWORK\").expect(\"Missing NETWORK from .env\"),\n consensus_rpc: env::var(\"CONSENSUS_RPC\").expect(\"Missing CONSENSUS_RPC from .env\"),\n execution_rpc: env::var(\"EXECUTION_RPC\").expect(\"Missing EXECUTION_RPC from .env\"),\n wasm_rpc: env::var(\"WASM_RPC\").expect(\"Missing WASM_RPC from .env\"),\n state_prover_rpc: env::var(\"STATE_PROVER_RPC\").expect(\"Missing STATE_PROVER from .env\"),\n block_roots_rpc: env::var(\"BLOCK_ROOTS_RPC\").expect(\"Missing BLOCK_ROOTS from .env\"),\n gateway_addr: env::var(\"GATEWAY_ADDR\").expect(\"Missing GATEWAY_ADDR from .env\"),\n verifier_addr: env::var(\"VERIFIER_ADDR\").expect(\"Missing VERIFIER_ADDR from .env\"),\n sentinel_queue_addr: env::var(\"SENTINEL_QUEUE_ADDR\")\n .expect(\"Missing SENTINEL_QUEUE_ADDR from .env\"),\n sentinel_queue_name: env::var(\"SENTINEL_QUEUE_NAME\")\n .expect(\"Missing SENTINEL_QUEUE_NAME from .env\"),\n reject_historical_roots: true,\n historical_roots_block_roots_batch_size: 1000,\n verification_method: VerificationMethod::from_str(\n env::var(\"VERIFICATION_METHOD\")\n .expect(\"VERIFICATION not found\")\n .as_str(),\n )\n .unwrap(),\n rpc_pool_max_idle_per_host: usize::from_str(\n env::var(\"RPC_POOL_MAX_IDLE_PER_HOST\")\n .expect(\"Missing RPC_POOL_MAX_IDLE_PER_HOST from .env\")\n .as_str(),\n )\n .unwrap(),\n rpc_timeout_secs: u64::from_str(\n env::var(\"RPC_TIMEOUT_SECS\")\n .expect(\"Missing RPC_TIMEOUT_SECS from .env\")\n .as_str(),\n )\n .unwrap(),\n rpc_max_retries: u64::from_str(\n env::var(\"RPC_MAX_RETRIES\")\n .expect(\"Missing RPC_MAX_RETRIES from .env\")\n .as_str(),\n )\n .unwrap(),\n genesis_timestamp: u64::from_str(\n env::var(\"GENESIS_TIMESTAMP\")\n .expect(\"Missing GENESIS_TIMESTAMP from .env\")\n .as_str(),\n )\n .unwrap(),\n max_batch_size: usize::from_str(\n env::var(\"MAX_BATCH_SIZE\")\n .expect(\"Missing MAX_BATCH_SIZE from .env\")\n .as_str(),\n )\n .unwrap(),\n process_interval: u64::from_str(\n env::var(\"PROCESS_INTERVAL\")\n .expect(\"Missing PROCESS_INTERVAL from .env\")\n .as_str(),\n )\n .unwrap(),\n feed_interval: u64::from_str(\n env::var(\"FEED_INTERVAL\")\n .expect(\"Missing FEED_INTERVAL from .env\")\n .as_str(),\n )\n .unwrap(),\n wasm_wallet: env::var(\"WASM_WALLET\").expect(\"Missing WASM_WALLET from .env\"),\n state_prover_check: true,\n }\n}\n\npub fn calc_sync_period(slot: u64) -\u003e u64 {\n let epoch = slot / 32; // 32 slots per epoch\n epoch / 256 // 256 epochs per sync committee\n}\n","traces":[{"line":6,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":7,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":11,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":12,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":15,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":16,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":17,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":18,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":19,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":20,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":21,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":22,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":23,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":29,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":41,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":47,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":77,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":10},"fn_name":null}],"covered":3,"coverable":26},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","relayer","src","verifier.rs"],"content":"use std::process::Command;\n\nuse crate::{\n types::{\n BatchVerificationDataRequest, IsVerifiedMessages, IsVerifiedRequest, IsVerifiedResponse,\n IsWorkerSetVerifiedRequest, IsWorkerSetVerifiedResult, LightClientStateResult,\n UpdateExecuteMsg, VerifyDataResponse,\n },\n utils::calc_sync_period,\n};\nuse async_trait::async_trait;\nuse consensus_types::{\n common::{VerificationResult, WorkerSetMessage},\n consensus::Update,\n lightclient::LightClientState,\n proofs::{BatchVerificationData, Message},\n};\nuse ethers::utils::hex;\nuse eyre::Result;\nuse log::{debug, error};\n// use log::debug;\nuse mockall::automock;\n\n#[derive(Debug)]\n#[allow(dead_code)]\npub struct Verifier {\n rpc: String,\n address: String,\n wasm_wallet: String,\n}\n\n#[automock]\n#[async_trait]\npub trait VerifierAPI {\n async fn get_period(\u0026self) -\u003e Result\u003cu64\u003e;\n async fn is_message_verified(\u0026mut self, messages: Vec\u003cMessage\u003e)\n -\u003e Result\u003cVec\u003c(Message, bool)\u003e\u003e;\n async fn is_worker_set_verified(\u0026mut self, worker_set_msg: WorkerSetMessage) -\u003e Result\u003cbool\u003e;\n async fn update(\u0026self, update: Update) -\u003e Result\u003c()\u003e;\n async fn verify_data(\n \u0026self,\n verification_data: BatchVerificationData,\n ) -\u003e Result\u003cVerificationResult\u003e;\n async fn get_state(\u0026self) -\u003e Result\u003cLightClientState\u003e;\n}\n\nimpl Verifier {\n pub fn new(rpc: String, address: String, wasm_wallet: String) -\u003e Self {\n Self {\n rpc,\n address,\n wasm_wallet,\n }\n }\n}\n\n#[automock]\n#[async_trait]\nimpl VerifierAPI for Verifier {\n async fn get_state(\u0026self) -\u003e Result\u003cLightClientState\u003e {\n let cmd = \"axelard\";\n let args = [\n \"query\",\n \"wasm\",\n \"contract-state\",\n \"smart\",\n self.address.as_str(),\n \"{\\\"light_client_state\\\": {}}\",\n \"--node\",\n self.rpc.as_str(),\n \"--output\",\n \"json\",\n ];\n\n // let command_line = format!(\"{} {}\", cmd, args.join(\" \"));\n // debug!(\"Command to be executed: {}\", command_line);\n\n let output = Command::new(cmd).args(args).output()?;\n // debug!(\"Output: {:?}\", output);\n\n let state = serde_json::from_slice::\u003cLightClientStateResult\u003e(\u0026output.stdout)?;\n\n Ok(state.data)\n }\n\n async fn get_period(\u0026self) -\u003e Result\u003cu64\u003e {\n let state = self.get_state().await?;\n let period = calc_sync_period(state.update_slot);\n Ok(period)\n }\n\n async fn is_message_verified(\n \u0026mut self,\n messages: Vec\u003cMessage\u003e,\n ) -\u003e Result\u003cVec\u003c(Message, bool)\u003e\u003e {\n let is_verified = IsVerifiedRequest {\n is_verified: IsVerifiedMessages { messages },\n };\n\n let cmd = \"axelard\";\n let args = [\n \"query\",\n \"wasm\",\n \"contract-state\",\n \"smart\",\n self.address.as_str(),\n \u0026serde_json::to_string(\u0026is_verified)?,\n \"--node\",\n self.rpc.as_str(),\n \"--output\",\n \"json\",\n ];\n\n let _command_line = format!(\"{} {}\", cmd, args.join(\" \"));\n // debug!(\"Command to be executed: {}\", command_line);\n\n let output = Command::new(cmd).args(args).output()?;\n // debug!(\"Output: {:?}\", output);\n\n let is_verified = serde_json::from_slice::\u003cIsVerifiedResponse\u003e(\u0026output.stdout)?;\n\n Ok(is_verified.data)\n }\n\n async fn is_worker_set_verified(\u0026mut self, worker_set_msg: WorkerSetMessage) -\u003e Result\u003cbool\u003e {\n let message = IsWorkerSetVerifiedRequest {\n is_worker_set_verified: worker_set_msg,\n };\n\n let cmd = \"axelard\";\n let args = [\n \"query\",\n \"wasm\",\n \"contract-state\",\n \"smart\",\n self.address.as_str(),\n \u0026serde_json::to_string(\u0026message)?,\n \"--node\",\n self.rpc.as_str(),\n \"--output\",\n \"json\",\n ];\n\n let _command_line = format!(\"{} {}\", cmd, args.join(\" \"));\n // debug!(\"Command to be executed: {}\", command_line);\n\n let output = Command::new(cmd).args(args).output()?;\n // debug!(\"Output: {:?}\", output);\n\n let state = serde_json::from_slice::\u003cIsWorkerSetVerifiedResult\u003e(\u0026output.stdout)?;\n\n Ok(state.data)\n }\n\n // Placeholder function which will be substituted with the API that will be provided\n async fn update(\u0026self, update: Update) -\u003e Result\u003c()\u003e {\n let cmd = \"axelard\";\n\n let message = UpdateExecuteMsg {\n light_client_update: update,\n };\n\n let args = [\n \"tx\",\n \"wasm\",\n \"execute\",\n self.address.as_str(),\n \u0026serde_json::to_string(\u0026message)?,\n \"--from\",\n self.wasm_wallet.as_str(),\n \"--node\",\n self.rpc.as_str(),\n \"--gas-prices\",\n \"0.0001uwasm\",\n \"--gas\",\n \"100000000\",\n \"-y\",\n ];\n\n // let command_line = format!(\"{} {}\", cmd, args.join(\" \"));\n // debug!(\"Command to be executed: {}\", command_line);\n\n let output = Command::new(cmd).args(args).output()?;\n // debug!(\"Output: {:?}\", output);\n\n if !output.status.success() {\n error!(\"Error updating light client: {:?}\", output);\n return Err(eyre::eyre!(\"Error updating light client\"));\n }\n\n Ok(())\n }\n\n async fn verify_data(\n \u0026self,\n verification_data: BatchVerificationData,\n ) -\u003e Result\u003cVerificationResult\u003e {\n let cmd = \"axelard\";\n\n let message = BatchVerificationDataRequest {\n batch_verification_data: verification_data,\n };\n\n let args = [\n \"tx\",\n \"wasm\",\n \"execute\",\n self.address.as_str(),\n \u0026serde_json::to_string(\u0026message)?,\n \"--from\",\n self.wasm_wallet.as_str(),\n \"--node\",\n self.rpc.as_str(),\n \"--gas-prices\",\n \"0.0001uwasm\",\n \"--gas\",\n \"100000000\",\n \"-y\",\n ];\n\n let output = Command::new(cmd).args(args).output()?;\n debug!(\"Output: {:?}\", output);\n\n if !output.status.success() {\n error!(\"Error updating light client: {:?}\", output);\n return Err(eyre::eyre!(\"Error updating light client\"));\n }\n\n let result: VerifyDataResponse = serde_json::from_slice(\u0026output.stdout)?;\n let decoded = hex::decode(result.data)?;\n let str = String::from_utf8_lossy(\u0026decoded);\n\n if let Some(json_start_index) = str.find(\"[[\") {\n let json_string = \u0026str[json_start_index..];\n let res: VerificationResult = cosmwasm_std::from_json(json_string).unwrap();\n return Ok(res);\n }\n\n Err(eyre::eyre!(\"Error decoding verification result\"))\n }\n}\n","traces":[{"line":48,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":66,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":67,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":89,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":130,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":135,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":136,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":137,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":147,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":150,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":152,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":156,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":157,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":163,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":165,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":166,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":167,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":168,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":183,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":187,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":188,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":191,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":194,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":198,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":204,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":206,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":207,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":208,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":209,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":221,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":222,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":224,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":225,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":226,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":229,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":230,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":233,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":234,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":235,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":236,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":239,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":0,"coverable":74},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","types","src","common.rs"],"content":"use core::fmt;\n\nuse crate::cosmwasm_schema::schemars;\nuse axelar_wasm_std::nonempty;\nuse connection_router::state::{CrossChainId, Message};\nuse cosmwasm_schema::schemars::JsonSchema;\nuse serde::{\n de::{self, Visitor},\n Deserializer, Serializer,\n};\nuse ssz_rs::prelude::*;\nuse sync_committee_rs::constants::{Epoch, Version};\n\n/// Trait used to create the keys of the map which contains the verification results\npub trait PrimaryKey {\n fn key(\u0026self) -\u003e String;\n}\n\ntype Fork = (Epoch, Version);\n\n/// Chain configuration that is used from the Light Client module for the verification of signatures\n#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]\npub struct ChainConfig {\n pub chain_id: u64,\n pub genesis_time: u64,\n pub genesis_root: Node,\n #[serde(\n serialize_with = \"serialize_forks\",\n deserialize_with = \"deserialize_forks\"\n )]\n pub forks: Vec\u003cFork\u003e,\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]\npub enum FinalizationVariant {\n Optimistic(),\n Finality(),\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]\npub struct Config {\n pub chain_config: ChainConfig,\n pub gateway_address: String,\n pub finalization: FinalizationVariant,\n}\n\n/// Message describing an update of the operators set\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone, JsonSchema)]\npub struct WorkerSetMessage {\n pub message_id: nonempty::String,\n pub new_operators_data: String,\n}\n\n/// Message variants that the Light Client can verify\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub enum ContentVariant {\n Message(Message),\n WorkerSet(WorkerSetMessage),\n}\n\nimpl Default for ContentVariant {\n fn default() -\u003e Self {\n let message = Message {\n cc_id: CrossChainId {\n chain: String::from(\"ethereum\").try_into().unwrap(),\n id: String::from(\"foo:bar\").try_into().unwrap(),\n },\n source_address: String::from(\"0x0000000000000000000000000000000000000000\")\n .try_into()\n .unwrap(),\n destination_chain: String::from(\"fantom\").try_into().unwrap(),\n destination_address: String::from(\"0x0000000000000000000000000000000000000\")\n .try_into()\n .unwrap(),\n payload_hash: Default::default(),\n };\n\n ContentVariant::Message(message)\n }\n}\n\nimpl PrimaryKey for WorkerSetMessage {\n fn key(\u0026self) -\u003e String {\n format!(\"workersetmessage:{}\", *self.message_id)\n }\n}\n\nimpl PrimaryKey for Message {\n fn key(\u0026self) -\u003e String {\n format!(\"message:{}\", self.cc_id)\n }\n}\n\npub type VerificationResult = Vec\u003c(String, String)\u003e;\n\nfn serialize_forks\u003cS\u003e(forks: \u0026[Fork], serializer: S) -\u003e Result\u003cS::Ok, S::Error\u003e\nwhere\n S: Serializer,\n{\n let formatted_forks: Vec\u003c(u64, String)\u003e = forks\n .iter()\n .map(|\u0026(epoch, version)| (epoch, format!(\"0x{}\", hex::encode(version))))\n .collect();\n serializer.collect_seq(formatted_forks)\n}\n\nfn deserialize_forks\u003c'de, D\u003e(deserializer: D) -\u003e Result\u003cVec\u003cFork\u003e, D::Error\u003e\nwhere\n D: Deserializer\u003c'de\u003e,\n{\n struct ForksVisitor;\n\n impl\u003c'de\u003e Visitor\u003c'de\u003e for ForksVisitor {\n type Value = Vec\u003c(Epoch, Version)\u003e;\n\n fn expecting(\u0026self, formatter: \u0026mut fmt::Formatter) -\u003e fmt::Result {\n formatter.write_str(\"an array of (Epoch, Version) pairs\")\n }\n\n fn visit_seq\u003cA\u003e(self, mut seq: A) -\u003e Result\u003cSelf::Value, A::Error\u003e\n where\n A: de::SeqAccess\u003c'de\u003e,\n {\n let mut forks: Vec\u003cFork\u003e = Vec::new();\n\n while let Some((epoch, hex_version)) = seq.next_element::\u003c(u64, String)\u003e()? {\n let version_bytes =\n hex::decode(hex_version.trim_start_matches(\"0x\")).map_err(de::Error::custom)?;\n let version = version_bytes\n .as_slice()\n .try_into()\n .map_err(|_| de::Error::custom(\"Version should be 4 bytes long\"))?;\n forks.push((epoch, version));\n }\n\n Ok(forks)\n }\n }\n\n deserializer.deserialize_seq(ForksVisitor)\n}\n","traces":[{"line":62,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":20},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":20},"fn_name":null},{"line":89,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":174},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":50},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":50},"fn_name":null},{"line":126,"address":[],"length":0,"stats":{"Line":250},"fn_name":null},{"line":127,"address":[],"length":0,"stats":{"Line":125},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":125},"fn_name":null},{"line":129,"address":[],"length":0,"stats":{"Line":125},"fn_name":null},{"line":132,"address":[],"length":0,"stats":{"Line":25},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":125},"fn_name":null},{"line":136,"address":[],"length":0,"stats":{"Line":50},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":25},"fn_name":null}],"covered":23,"coverable":30},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","types","src","consensus.rs"],"content":"use ssz_rs::prelude::*;\nuse sync_committee_rs::consensus_types::{BeaconBlock, BeaconState, SyncAggregate, SyncCommittee};\nuse sync_committee_rs::constants::{\n Bytes32, BYTES_PER_LOGS_BLOOM, EPOCHS_PER_HISTORICAL_VECTOR, EPOCHS_PER_SLASHINGS_VECTOR,\n ETH1_DATA_VOTES_BOUND, HISTORICAL_ROOTS_LIMIT, MAX_ATTESTATIONS, MAX_ATTESTER_SLASHINGS,\n MAX_BLS_TO_EXECUTION_CHANGES, MAX_BYTES_PER_TRANSACTION, MAX_DEPOSITS, MAX_EXTRA_DATA_BYTES,\n MAX_PROPOSER_SLASHINGS, MAX_TRANSACTIONS_PER_PAYLOAD, MAX_VALIDATORS_PER_COMMITTEE,\n MAX_VOLUNTARY_EXITS, MAX_WITHDRAWALS_PER_PAYLOAD, SLOTS_PER_HISTORICAL_ROOT,\n SYNC_COMMITTEE_SIZE, VALIDATOR_REGISTRY_LIMIT,\n};\n\npub use sync_committee_rs::consensus_types::BeaconBlockHeader;\n\n#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Clone, Default, Eq)]\npub struct BeaconHeader {\n pub beacon: BeaconBlockHeader,\n}\n\npub type BeaconBlockAlias = BeaconBlock\u003c\n MAX_PROPOSER_SLASHINGS,\n MAX_VALIDATORS_PER_COMMITTEE,\n MAX_ATTESTER_SLASHINGS,\n MAX_ATTESTATIONS,\n MAX_DEPOSITS,\n MAX_VOLUNTARY_EXITS,\n SYNC_COMMITTEE_SIZE,\n BYTES_PER_LOGS_BLOOM,\n MAX_EXTRA_DATA_BYTES,\n MAX_BYTES_PER_TRANSACTION,\n MAX_TRANSACTIONS_PER_PAYLOAD,\n MAX_WITHDRAWALS_PER_PAYLOAD,\n MAX_BLS_TO_EXECUTION_CHANGES,\n\u003e;\n\npub type BeaconStateType = BeaconState\u003c\n SLOTS_PER_HISTORICAL_ROOT,\n HISTORICAL_ROOTS_LIMIT,\n ETH1_DATA_VOTES_BOUND,\n VALIDATOR_REGISTRY_LIMIT,\n EPOCHS_PER_HISTORICAL_VECTOR,\n EPOCHS_PER_SLASHINGS_VECTOR,\n MAX_VALIDATORS_PER_COMMITTEE,\n SYNC_COMMITTEE_SIZE,\n BYTES_PER_LOGS_BLOOM,\n MAX_EXTRA_DATA_BYTES,\n MAX_BYTES_PER_TRANSACTION,\n MAX_TRANSACTIONS_PER_PAYLOAD,\n\u003e;\n\npub type BlockRootsType = Vector\u003cNode, SLOTS_PER_HISTORICAL_ROOT\u003e;\n\npub fn to_beacon_header(block: \u0026BeaconBlockAlias) -\u003e eyre::Result\u003cBeaconBlockHeader\u003e {\n Ok(BeaconBlockHeader {\n slot: block.slot,\n parent_root: block.parent_root,\n proposer_index: block.proposer_index,\n state_root: block.state_root,\n body_root: block.body.clone().hash_tree_root()?,\n })\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, PartialEq)]\npub struct Bootstrap {\n pub header: BeaconHeader,\n pub current_sync_committee: SyncCommittee\u003cSYNC_COMMITTEE_SIZE\u003e,\n pub current_sync_committee_branch: Vec\u003cBytes32\u003e,\n}\n\n#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Clone, Default)]\npub struct Update {\n pub attested_header: BeaconHeader,\n pub next_sync_committee: SyncCommittee\u003cSYNC_COMMITTEE_SIZE\u003e,\n pub next_sync_committee_branch: Vec\u003cBytes32\u003e,\n pub finalized_header: BeaconHeader,\n pub finality_branch: Vec\u003cBytes32\u003e,\n pub sync_aggregate: SyncAggregate\u003cSYNC_COMMITTEE_SIZE\u003e,\n #[serde(with = \"sync_committee_rs::serde::as_string\")]\n pub signature_slot: u64,\n}\n\n#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Clone, Default, Eq)]\npub struct FinalityUpdate {\n pub attested_header: BeaconHeader,\n pub finalized_header: BeaconHeader,\n pub finality_branch: Vec\u003cBytes32\u003e,\n pub sync_aggregate: SyncAggregate\u003cSYNC_COMMITTEE_SIZE\u003e,\n #[serde(with = \"sync_committee_rs::serde::as_string\")]\n pub signature_slot: u64,\n}\n\nimpl From\u003c\u0026Update\u003e for FinalityUpdate {\n fn from(value: \u0026Update) -\u003e Self {\n FinalityUpdate {\n attested_header: value.attested_header.clone(),\n finalized_header: value.finalized_header.clone(),\n finality_branch: value.finality_branch.clone(),\n sync_aggregate: value.sync_aggregate.clone(),\n signature_slot: value.signature_slot,\n }\n }\n}\n\n#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Clone, Default, Eq)]\npub struct OptimisticUpdate {\n pub attested_header: BeaconHeader,\n pub sync_aggregate: SyncAggregate\u003cSYNC_COMMITTEE_SIZE\u003e,\n #[serde(with = \"sync_committee_rs::serde::as_string\")]\n pub signature_slot: u64,\n}\n\nimpl From\u003c\u0026FinalityUpdate\u003e for OptimisticUpdate {\n fn from(value: \u0026FinalityUpdate) -\u003e Self {\n OptimisticUpdate {\n attested_header: value.attested_header.clone(),\n sync_aggregate: value.sync_aggregate.clone(),\n signature_slot: value.signature_slot,\n }\n }\n}\n","traces":[{"line":52,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":54,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":56,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":31},"fn_name":null}],"covered":17,"coverable":17},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","types","src","execution.rs"],"content":"use alloy_primitives::Address;\nuse alloy_rlp::{Buf, Decodable};\nuse eyre::Result;\nuse serde::Deserialize;\nuse ssz_rs::prelude::*;\nuse std::cmp::Ordering;\n\n#[derive(Default, Debug, Clone, Deserialize)]\npub struct ReceiptLog {\n pub address: [u8; 20],\n pub topics: Vec\u003c[u8; 32]\u003e,\n pub data: Vec\u003cu8\u003e,\n}\n\n/// Structure of a ContractCall event, emitted from the Gateway\n#[derive(Default, Debug, Clone, PartialEq)]\npub struct ContractCallBase {\n pub source_address: Option\u003cAddress\u003e,\n pub destination_chain: Option\u003cString\u003e,\n pub destination_address: Option\u003cString\u003e,\n pub payload_hash: Option\u003c[u8; 32]\u003e,\n}\n\n/// Structure of an OperatorshipTransferred event, emitted from the Gateway\n#[derive(Default, Debug, Clone, PartialEq)]\npub struct OperatorshipTransferredBase {\n pub new_operators_data: Option\u003cString\u003e,\n}\n\n#[derive(Debug, PartialEq)]\npub enum GatewayEvent {\n ContactCall(ContractCallBase),\n OperatorshipTransferred(OperatorshipTransferredBase),\n}\n\n#[derive(Default, Debug)]\npub struct ReceiptLogs(pub Vec\u003cReceiptLog\u003e);\n\nimpl Decodable for ReceiptLogs {\n fn decode(buf: \u0026mut \u0026[u8]) -\u003e Result\u003cSelf, alloy_rlp::Error\u003e {\n let rlp_type = *buf.first().ok_or(alloy_rlp::Error::Custom(\n \"cannot decode a receipt from empty bytes\",\n ))?;\n\n match rlp_type.cmp(\u0026alloy_rlp::EMPTY_LIST_CODE) {\n Ordering::Less =\u003e {\n let _header = alloy_rlp::Header::decode(buf)?;\n let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(\n \"typed receipt cannot be decoded from an empty slice\",\n ))?;\n\n if receipt_type \u003e 3 {\n return Err(alloy_rlp::Error::Custom(\"Invalid Receipt Type\"));\n }\n buf.advance(1);\n }\n Ordering::Equal =\u003e {\n return Err(alloy_rlp::Error::Custom(\n \"an empty list is not a valid receipt encoding\",\n ));\n }\n _ =\u003e {}\n };\n\n let mut logs_list: ReceiptLogs = ReceiptLogs::default();\n\n let b = \u0026mut \u0026**buf;\n let rlp_head = alloy_rlp::Header::decode(b)?;\n if !rlp_head.list {\n return Err(alloy_rlp::Error::UnexpectedString);\n }\n\n for _i in 0..3 {\n // skip the first 3 fields: success, cumulative_gas_used, bloom\n let head = alloy_rlp::Header::decode(b)?;\n b.advance(head.payload_length);\n }\n\n let logs_head = alloy_rlp::Header::decode(b)?;\n if !logs_head.list {\n return Err(alloy_rlp::Error::UnexpectedString);\n }\n\n while !b.is_empty() {\n let mut log: ReceiptLog = ReceiptLog::default();\n let item_head = alloy_rlp::Header::decode(b)?;\n if !item_head.list {\n return Err(alloy_rlp::Error::UnexpectedString);\n }\n\n log.address = alloy_rlp::Decodable::decode(b)?;\n\n let topic_list_head = alloy_rlp::Header::decode(b)?;\n for _i in 0..(topic_list_head.payload_length / 32) {\n log.topics.push(alloy_rlp::Decodable::decode(b)?);\n }\n\n log.data = Vec::from(alloy_rlp::Header::decode_bytes(b, false)?);\n\n logs_list.0.push(log);\n }\n\n Ok(logs_list)\n }\n}\n","traces":[{"line":40,"address":[],"length":0,"stats":{"Line":33},"fn_name":null},{"line":41,"address":[],"length":0,"stats":{"Line":65},"fn_name":null},{"line":42,"address":[],"length":0,"stats":{"Line":33},"fn_name":null},{"line":47,"address":[],"length":0,"stats":{"Line":62},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":55,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":67,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":32},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":310},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":93},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":93},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":62},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":113},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":82},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":82},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":82},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":164},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":199},"fn_name":null},{"line":95,"address":[],"length":0,"stats":{"Line":199},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":164},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":82},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":31},"fn_name":null}],"covered":25,"coverable":30},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","types","src","lib.rs"],"content":"pub mod common;\npub mod consensus;\npub mod execution;\npub mod lightclient;\npub mod primitives;\npub mod proofs;\npub use alloy_primitives;\npub use alloy_rlp;\npub use axelar_wasm_std;\npub use connection_router;\npub use cosmwasm_schema;\npub use ssz_rs;\npub use sync_committee_rs;\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","types","src","lightclient.rs"],"content":"use sync_committee_rs::{consensus_types::SyncCommittee, constants::SYNC_COMMITTEE_SIZE};\n\n#[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Debug, Clone)]\npub struct LightClientState {\n pub update_slot: u64,\n pub current_sync_committee: SyncCommittee\u003cSYNC_COMMITTEE_SIZE\u003e,\n pub next_sync_committee: Option\u003cSyncCommittee\u003cSYNC_COMMITTEE_SIZE\u003e\u003e,\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","types","src","primitives.rs"],"content":"use core::{\n hash::{Hash, Hasher},\n ops::{Deref, DerefMut},\n};\nuse serde::{self};\nuse ssz_rs::prelude::*;\n\n#[derive(Default, Clone, Eq, SimpleSerialize)]\npub struct ByteVector\u003cconst N: usize\u003e(\n #[cfg_attr(feature = \"serde\", serde(with = \"crate::serde::as_hex\"))] Vector\u003cu8, N\u003e,\n);\n\nimpl\u003cconst N: usize\u003e TryFrom\u003c\u0026[u8]\u003e for ByteVector\u003cN\u003e {\n type Error = ssz_rs::DeserializeError;\n\n fn try_from(bytes: \u0026[u8]) -\u003e Result\u003cSelf, Self::Error\u003e {\n ByteVector::\u003cN\u003e::deserialize(bytes)\n }\n}\n\nimpl\u003cconst N: usize\u003e TryFrom\u003cVec\u003cu8\u003e\u003e for ByteVector\u003cN\u003e {\n type Error = ssz_rs::DeserializeError;\n\n fn try_from(bytes: Vec\u003cu8\u003e) -\u003e Result\u003cSelf, Self::Error\u003e {\n ByteVector::\u003cN\u003e::deserialize(\u0026bytes)\n }\n}\n\n// impl here to satisfy clippy\nimpl\u003cconst N: usize\u003e PartialEq for ByteVector\u003cN\u003e {\n fn eq(\u0026self, other: \u0026Self) -\u003e bool {\n self.0 == other.0\n }\n}\n\nimpl\u003cconst N: usize\u003e Hash for ByteVector\u003cN\u003e {\n fn hash\u003cH: Hasher\u003e(\u0026self, state: \u0026mut H) {\n self.as_ref().hash(state);\n }\n}\n\nimpl\u003cconst N: usize\u003e AsRef\u003c[u8]\u003e for ByteVector\u003cN\u003e {\n fn as_ref(\u0026self) -\u003e \u0026[u8] {\n \u0026self.0\n }\n}\n\nimpl\u003cconst N: usize\u003e Deref for ByteVector\u003cN\u003e {\n type Target = Vector\u003cu8, N\u003e;\n\n fn deref(\u0026self) -\u003e \u0026Self::Target {\n \u0026self.0\n }\n}\n\nimpl\u003cconst N: usize\u003e DerefMut for ByteVector\u003cN\u003e {\n fn deref_mut(\u0026mut self) -\u003e \u0026mut Self::Target {\n \u0026mut self.0\n }\n}\n\n/**\n * U64: a 64-bit unsigned integer.\n */\n\n#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]\npub struct U64 {\n inner: u64,\n}\n\nimpl U64 {\n pub fn as_u64(\u0026self) -\u003e u64 {\n self.inner\n }\n}\n\nimpl From\u003cU64\u003e for u64 {\n fn from(value: U64) -\u003e Self {\n value.inner\n }\n}\n\nimpl From\u003cu64\u003e for U64 {\n fn from(value: u64) -\u003e Self {\n Self { inner: value }\n }\n}\n\nimpl ssz_rs::Merkleized for U64 {\n fn hash_tree_root(\u0026mut self) -\u003e std::result::Result\u003cNode, MerkleizationError\u003e {\n self.inner.hash_tree_root()\n }\n}\n\n// impl ssz_rs::Sized for U64 {\n// fn size_hint() -\u003e usize {\n// 0\n// }\n\n// fn is_variable_size() -\u003e bool {\n// false\n// }\n// }\n\nimpl ssz_rs::Serialize for U64 {\n fn serialize(\u0026self, buffer: \u0026mut Vec\u003cu8\u003e) -\u003e std::result::Result\u003cusize, SerializeError\u003e {\n self.inner.serialize(buffer)\n }\n}\n\nimpl ssz_rs::Deserialize for U64 {\n fn deserialize(encoding: \u0026[u8]) -\u003e std::result::Result\u003cSelf, DeserializeError\u003e\n where\n Self: std::marker::Sized,\n {\n Ok(Self {\n inner: u64::deserialize(encoding)?,\n })\n }\n}\n\n// impl ssz_rs::SimpleSerialize for U64 {}\n\nimpl serde::Serialize for U64 {\n fn serialize\u003cS\u003e(\u0026self, serializer: S) -\u003e std::result::Result\u003cS::Ok, S::Error\u003e\n where\n S: serde::Serializer,\n {\n serializer.serialize_str(\u0026self.inner.to_string())\n }\n}\n\nimpl\u003c'de\u003e serde::Deserialize\u003c'de\u003e for U64 {\n fn deserialize\u003cD\u003e(deserializer: D) -\u003e std::result::Result\u003cSelf, D::Error\u003e\n where\n D: serde::Deserializer\u003c'de\u003e,\n {\n let val: String = serde::Deserialize::deserialize(deserializer)?;\n Ok(Self {\n inner: val.parse().unwrap(),\n })\n }\n}\n","traces":[{"line":16,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":17,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":24,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":32,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":38,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":43,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":44,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":57,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":72,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":129,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":138,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":139,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":0,"coverable":32},{"path":["/","Users","themicp","dev","common_prefix","axelar","axelar-light-client","types","src","proofs.rs"],"content":"use crate::common::ContentVariant;\nuse crate::consensus::{FinalityUpdate, OptimisticUpdate};\npub use axelar_wasm_std::{nonempty, operators::Operators};\npub use connection_router::state::{Address as AddressType, ChainName, CrossChainId, Message};\nuse eyre::Result;\nuse serde::de::Error as SerdeError;\nuse serde::{Deserialize, Deserializer, Serializer};\nuse ssz_rs::Node;\nuse sync_committee_rs::{\n consensus_types::{BeaconBlockHeader, Transaction},\n constants::{Root, MAX_BYTES_PER_TRANSACTION},\n};\n\n#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Clone, Eq)]\npub enum UpdateVariant {\n /// LightClientFinalityUpdate from the beacon API spec.\n Finality(FinalityUpdate),\n /// LightClientOptimisticUpdate from the beacon API spec.\n Optimistic(OptimisticUpdate),\n}\n\nimpl Default for UpdateVariant {\n fn default() -\u003e Self {\n UpdateVariant::Finality(FinalityUpdate::default())\n }\n}\n\nimpl UpdateVariant {\n /// Extracts the most recent (maybe) finalized block from the LightClientUpdate message.\n /// In the case of a FinalityUpdate, we are using the most recent finalized block stored in finalized_header.\n /// In the case of an OptimisticUpdate, we trust (optimistically) that the attested_header will be finalized.\n pub fn recent_block(\u0026self) -\u003e BeaconBlockHeader {\n match \u0026self {\n UpdateVariant::Finality(update) =\u003e update.finalized_header.beacon.clone(),\n UpdateVariant::Optimistic(update) =\u003e update.attested_header.beacon.clone(),\n }\n }\n}\n\n/// Necessary proofs to verify that a given block is an ancestor of another block.\n/// In our case, it proves that the block that contains the event we are looking for, is an ancestor of the recent block that we got from the LightClientUpdate message.\n#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]\npub enum AncestryProof {\n /// This variant defines the proof data to verify that a beacon chain block header is in the `state.block_roots` of another block.\n BlockRoots {\n /// Generalized index from a block_root that we care to the block_root to the state root.\n // No need to provide that, since it can be calculated on-chain.\n block_roots_index: u64,\n block_root_proof: Vec\u003cNode\u003e,\n },\n /// This variant defines the necessary proofs to verify that a beacon chain block header in the `state.historical_summaries` of another block.\n HistoricalRoots {\n /// Proof for the target_block in the historical_summaries[index].block_summary_root\n block_root_proof: Vec\u003cNode\u003e,\n /// The hash of the specific block summary root that has the block\n block_summary_root: Root,\n /// Proof that historical_summaries[index].block_summary_root is in recent block state\n block_summary_root_proof: Vec\u003cNode\u003e,\n /// The generalized index for the historical_batch in state.historical_summaries.\n block_summary_root_gindex: u64,\n },\n}\n\nimpl Default for AncestryProof {\n fn default() -\u003e Self {\n AncestryProof::BlockRoots {\n block_roots_index: 0,\n block_root_proof: vec![],\n }\n }\n}\n\n/// Proofs to verify that a transaction is part of a block\n#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Clone, Default)]\npub struct TransactionProof {\n pub transaction_index: u64,\n /// Generalized index of transaction in target block\n pub transaction_gindex: u64,\n /// Proof that a transaction is part of the block's transactions trie\n pub transaction_proof: Vec\u003cNode\u003e,\n /// Encoded transaction\n pub transaction: Transaction\u003cMAX_BYTES_PER_TRANSACTION\u003e,\n}\n\n/// Proofs to verify that a receipt is part of a block\n#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default)]\npub struct ReceiptProof {\n /// Proof that a receipts_root is part of a block\n pub receipts_root_proof: Vec\u003cNode\u003e,\n /// Generalized index of receipts_root in target block\n pub receipts_root_gindex: u64,\n /// Proof that a receipt is part of the receipts_root\n #[serde(\n serialize_with = \"hex_array_serializer\",\n deserialize_with = \"hex_array_deserializer\"\n )]\n pub receipt_proof: Vec\u003cVec\u003cu8\u003e\u003e,\n /// The root of the receipts trie\n pub receipts_root: Root,\n}\n\n/// High-level structure that contains the messages that need verification, along with the necessary proofs\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\npub struct BatchVerificationData {\n pub update: UpdateVariant,\n pub target_blocks: Vec\u003cBlockProofsBatch\u003e,\n}\n\n/// Batch containing the proofs and messages to verify from a specific block.\n/// Each block might have multiple transactions with multiple messages for verification.\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\npub struct BlockProofsBatch {\n pub ancestry_proof: AncestryProof,\n pub target_block: BeaconBlockHeader,\n pub transactions_proofs: Vec\u003cTransactionProofsBatch\u003e,\n}\n\n/// Batch containing the proofs and messages to verify from a specific transaction.\n/// Each transaction might have multiple messages for verification.\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\npub struct TransactionProofsBatch {\n pub transaction_proof: TransactionProof,\n pub receipt_proof: ReceiptProof,\n pub content: Vec\u003cContentVariant\u003e,\n}\n\nfn hex_array_deserializer\u003c'de, D\u003e(deserializer: D) -\u003e Result\u003cVec\u003cVec\u003cu8\u003e\u003e, D::Error\u003e\nwhere\n D: Deserializer\u003c'de\u003e,\n{\n let strs = Vec::\u003cString\u003e::deserialize(deserializer)?;\n strs.into_iter()\n .map(|s| hex::decode(s).map_err(SerdeError::custom))\n .collect()\n}\n\nfn hex_array_serializer\u003cS\u003e(bytes_array: \u0026[Vec\u003cu8\u003e], serializer: S) -\u003e Result\u003cS::Ok, S::Error\u003e\nwhere\n S: Serializer,\n{\n let hex_strings: Vec\u003cString\u003e = bytes_array.iter().map(hex::encode).collect();\n serializer.collect_seq(hex_strings)\n}\n","traces":[{"line":23,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":24,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":32,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":33,"address":[],"length":0,"stats":{"Line":18},"fn_name":null},{"line":34,"address":[],"length":0,"stats":{"Line":11},"fn_name":null},{"line":35,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":127,"address":[],"length":0,"stats":{"Line":44},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":88},"fn_name":null},{"line":132,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":132},"fn_name":null},{"line":137,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":141,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":142,"address":[],"length":0,"stats":{"Line":6},"fn_name":null}],"covered":14,"coverable":15}]};
var previousData = null;
</script>
<script crossorigin>/** @license React v16.13.1
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';(function(d,r){"object"===typeof exports&&"undefined"!==typeof module?r(exports):"function"===typeof define&&define.amd?define(["exports"],r):(d=d||self,r(d.React={}))})(this,function(d){function r(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
function w(a,b,c){this.props=a;this.context=b;this.refs=ba;this.updater=c||ca}function da(){}function L(a,b,c){this.props=a;this.context=b;this.refs=ba;this.updater=c||ca}function ea(a,b,c){var g,e={},fa=null,d=null;if(null!=b)for(g in void 0!==b.ref&&(d=b.ref),void 0!==b.key&&(fa=""+b.key),b)ha.call(b,g)&&!ia.hasOwnProperty(g)&&(e[g]=b[g]);var h=arguments.length-2;if(1===h)e.children=c;else if(1<h){for(var k=Array(h),f=0;f<h;f++)k[f]=arguments[f+2];e.children=k}if(a&&a.defaultProps)for(g in h=a.defaultProps,
h)void 0===e[g]&&(e[g]=h[g]);return{$$typeof:x,type:a,key:fa,ref:d,props:e,_owner:M.current}}function va(a,b){return{$$typeof:x,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}}function N(a){return"object"===typeof a&&null!==a&&a.$$typeof===x}function wa(a){var b={"=":"=0",":":"=2"};return"$"+(""+a).replace(/[=:]/g,function(a){return b[a]})}function ja(a,b,c,g){if(C.length){var e=C.pop();e.result=a;e.keyPrefix=b;e.func=c;e.context=g;e.count=0;return e}return{result:a,keyPrefix:b,func:c,
context:g,count:0}}function ka(a){a.result=null;a.keyPrefix=null;a.func=null;a.context=null;a.count=0;10>C.length&&C.push(a)}function O(a,b,c,g){var e=typeof a;if("undefined"===e||"boolean"===e)a=null;var d=!1;if(null===a)d=!0;else switch(e){case "string":case "number":d=!0;break;case "object":switch(a.$$typeof){case x:case xa:d=!0}}if(d)return c(g,a,""===b?"."+P(a,0):b),1;d=0;b=""===b?".":b+":";if(Array.isArray(a))for(var f=0;f<a.length;f++){e=a[f];var h=b+P(e,f);d+=O(e,h,c,g)}else if(null===a||
"object"!==typeof a?h=null:(h=la&&a[la]||a["@@iterator"],h="function"===typeof h?h:null),"function"===typeof h)for(a=h.call(a),f=0;!(e=a.next()).done;)e=e.value,h=b+P(e,f++),d+=O(e,h,c,g);else if("object"===e)throw c=""+a,Error(r(31,"[object Object]"===c?"object with keys {"+Object.keys(a).join(", ")+"}":c,""));return d}function Q(a,b,c){return null==a?0:O(a,"",b,c)}function P(a,b){return"object"===typeof a&&null!==a&&null!=a.key?wa(a.key):b.toString(36)}function ya(a,b,c){a.func.call(a.context,b,
a.count++)}function za(a,b,c){var g=a.result,e=a.keyPrefix;a=a.func.call(a.context,b,a.count++);Array.isArray(a)?R(a,g,c,function(a){return a}):null!=a&&(N(a)&&(a=va(a,e+(!a.key||b&&b.key===a.key?"":(""+a.key).replace(ma,"$&/")+"/")+c)),g.push(a))}function R(a,b,c,g,e){var d="";null!=c&&(d=(""+c).replace(ma,"$&/")+"/");b=ja(b,d,g,e);Q(a,za,b);ka(b)}function t(){var a=na.current;if(null===a)throw Error(r(321));return a}function S(a,b){var c=a.length;a.push(b);a:for(;;){var g=c-1>>>1,e=a[g];if(void 0!==
e&&0<D(e,b))a[g]=b,a[c]=e,c=g;else break a}}function n(a){a=a[0];return void 0===a?null:a}function E(a){var b=a[0];if(void 0!==b){var c=a.pop();if(c!==b){a[0]=c;a:for(var g=0,e=a.length;g<e;){var d=2*(g+1)-1,f=a[d],h=d+1,k=a[h];if(void 0!==f&&0>D(f,c))void 0!==k&&0>D(k,f)?(a[g]=k,a[h]=c,g=h):(a[g]=f,a[d]=c,g=d);else if(void 0!==k&&0>D(k,c))a[g]=k,a[h]=c,g=h;else break a}}return b}return null}function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function F(a){for(var b=n(u);null!==
b;){if(null===b.callback)E(u);else if(b.startTime<=a)E(u),b.sortIndex=b.expirationTime,S(p,b);else break;b=n(u)}}function T(a){y=!1;F(a);if(!v)if(null!==n(p))v=!0,z(U);else{var b=n(u);null!==b&&G(T,b.startTime-a)}}function U(a,b){v=!1;y&&(y=!1,V());H=!0;var c=m;try{F(b);for(l=n(p);null!==l&&(!(l.expirationTime>b)||a&&!W());){var g=l.callback;if(null!==g){l.callback=null;m=l.priorityLevel;var e=g(l.expirationTime<=b);b=q();"function"===typeof e?l.callback=e:l===n(p)&&E(p);F(b)}else E(p);l=n(p)}if(null!==
l)var d=!0;else{var f=n(u);null!==f&&G(T,f.startTime-b);d=!1}return d}finally{l=null,m=c,H=!1}}function oa(a){switch(a){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1E4;default:return 5E3}}var f="function"===typeof Symbol&&Symbol.for,x=f?Symbol.for("react.element"):60103,xa=f?Symbol.for("react.portal"):60106,Aa=f?Symbol.for("react.fragment"):60107,Ba=f?Symbol.for("react.strict_mode"):60108,Ca=f?Symbol.for("react.profiler"):60114,Da=f?Symbol.for("react.provider"):60109,
Ea=f?Symbol.for("react.context"):60110,Fa=f?Symbol.for("react.forward_ref"):60112,Ga=f?Symbol.for("react.suspense"):60113,Ha=f?Symbol.for("react.memo"):60115,Ia=f?Symbol.for("react.lazy"):60116,la="function"===typeof Symbol&&Symbol.iterator,pa=Object.getOwnPropertySymbols,Ja=Object.prototype.hasOwnProperty,Ka=Object.prototype.propertyIsEnumerable,I=function(){try{if(!Object.assign)return!1;var a=new String("abc");a[5]="de";if("5"===Object.getOwnPropertyNames(a)[0])return!1;var b={};for(a=0;10>a;a++)b["_"+
String.fromCharCode(a)]=a;if("0123456789"!==Object.getOwnPropertyNames(b).map(function(a){return b[a]}).join(""))return!1;var c={};"abcdefghijklmnopqrst".split("").forEach(function(a){c[a]=a});return"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},c)).join("")?!1:!0}catch(g){return!1}}()?Object.assign:function(a,b){if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined");var c=Object(a);for(var g,e=1;e<arguments.length;e++){var d=Object(arguments[e]);
for(var f in d)Ja.call(d,f)&&(c[f]=d[f]);if(pa){g=pa(d);for(var h=0;h<g.length;h++)Ka.call(d,g[h])&&(c[g[h]]=d[g[h]])}}return c},ca={isMounted:function(a){return!1},enqueueForceUpdate:function(a,b,c){},enqueueReplaceState:function(a,b,c,d){},enqueueSetState:function(a,b,c,d){}},ba={};w.prototype.isReactComponent={};w.prototype.setState=function(a,b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error(r(85));this.updater.enqueueSetState(this,a,b,"setState")};w.prototype.forceUpdate=
function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};da.prototype=w.prototype;f=L.prototype=new da;f.constructor=L;I(f,w.prototype);f.isPureReactComponent=!0;var M={current:null},ha=Object.prototype.hasOwnProperty,ia={key:!0,ref:!0,__self:!0,__source:!0},ma=/\/+/g,C=[],na={current:null},X;if("undefined"===typeof window||"function"!==typeof MessageChannel){var A=null,qa=null,ra=function(){if(null!==A)try{var a=q();A(!0,a);A=null}catch(b){throw setTimeout(ra,0),b;}},La=Date.now();var q=
function(){return Date.now()-La};var z=function(a){null!==A?setTimeout(z,0,a):(A=a,setTimeout(ra,0))};var G=function(a,b){qa=setTimeout(a,b)};var V=function(){clearTimeout(qa)};var W=function(){return!1};f=X=function(){}}else{var Y=window.performance,sa=window.Date,Ma=window.setTimeout,Na=window.clearTimeout;"undefined"!==typeof console&&(f=window.cancelAnimationFrame,"function"!==typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),
"function"!==typeof f&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"));if("object"===typeof Y&&"function"===typeof Y.now)q=function(){return Y.now()};else{var Oa=sa.now();q=function(){return sa.now()-Oa}}var J=!1,K=null,Z=-1,ta=5,ua=0;W=function(){return q()>=ua};f=function(){};X=function(a){0>a||125<a?console.error("forceFrameRate takes a positive int between 0 and 125, forcing framerates higher than 125 fps is not unsupported"):
ta=0<a?Math.floor(1E3/a):5};var B=new MessageChannel,aa=B.port2;B.port1.onmessage=function(){if(null!==K){var a=q();ua=a+ta;try{K(!0,a)?aa.postMessage(null):(J=!1,K=null)}catch(b){throw aa.postMessage(null),b;}}else J=!1};z=function(a){K=a;J||(J=!0,aa.postMessage(null))};G=function(a,b){Z=Ma(function(){a(q())},b)};V=function(){Na(Z);Z=-1}}var p=[],u=[],Pa=1,l=null,m=3,H=!1,v=!1,y=!1,Qa=0;B={ReactCurrentDispatcher:na,ReactCurrentOwner:M,IsSomeRendererActing:{current:!1},assign:I};I(B,{Scheduler:{__proto__:null,
unstable_ImmediatePriority:1,unstable_UserBlockingPriority:2,unstable_NormalPriority:3,unstable_IdlePriority:5,unstable_LowPriority:4,unstable_runWithPriority:function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=m;m=a;try{return b()}finally{m=c}},unstable_next:function(a){switch(m){case 1:case 2:case 3:var b=3;break;default:b=m}var c=m;m=b;try{return a()}finally{m=c}},unstable_scheduleCallback:function(a,b,c){var d=q();if("object"===typeof c&&null!==c){var e=c.delay;
e="number"===typeof e&&0<e?d+e:d;c="number"===typeof c.timeout?c.timeout:oa(a)}else c=oa(a),e=d;c=e+c;a={id:Pa++,callback:b,priorityLevel:a,startTime:e,expirationTime:c,sortIndex:-1};e>d?(a.sortIndex=e,S(u,a),null===n(p)&&a===n(u)&&(y?V():y=!0,G(T,e-d))):(a.sortIndex=c,S(p,a),v||H||(v=!0,z(U)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=m;return function(){var c=m;m=b;try{return a.apply(this,arguments)}finally{m=c}}},unstable_getCurrentPriorityLevel:function(){return m},
unstable_shouldYield:function(){var a=q();F(a);var b=n(p);return b!==l&&null!==l&&null!==b&&null!==b.callback&&b.startTime<=a&&b.expirationTime<l.expirationTime||W()},unstable_requestPaint:f,unstable_continueExecution:function(){v||H||(v=!0,z(U))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return n(p)},get unstable_now(){return q},get unstable_forceFrameRate(){return X},unstable_Profiling:null},SchedulerTracing:{__proto__:null,__interactionsRef:null,__subscriberRef:null,
unstable_clear:function(a){return a()},unstable_getCurrent:function(){return null},unstable_getThreadID:function(){return++Qa},unstable_trace:function(a,b,c){return c()},unstable_wrap:function(a){return a},unstable_subscribe:function(a){},unstable_unsubscribe:function(a){}}});d.Children={map:function(a,b,c){if(null==a)return a;var d=[];R(a,d,null,b,c);return d},forEach:function(a,b,c){if(null==a)return a;b=ja(null,null,b,c);Q(a,ya,b);ka(b)},count:function(a){return Q(a,function(){return null},null)},
toArray:function(a){var b=[];R(a,b,null,function(a){return a});return b},only:function(a){if(!N(a))throw Error(r(143));return a}};d.Component=w;d.Fragment=Aa;d.Profiler=Ca;d.PureComponent=L;d.StrictMode=Ba;d.Suspense=Ga;d.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=B;d.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(r(267,a));var d=I({},a.props),e=a.key,f=a.ref,m=a._owner;if(null!=b){void 0!==b.ref&&(f=b.ref,m=M.current);void 0!==b.key&&(e=""+b.key);if(a.type&&a.type.defaultProps)var h=
a.type.defaultProps;for(k in b)ha.call(b,k)&&!ia.hasOwnProperty(k)&&(d[k]=void 0===b[k]&&void 0!==h?h[k]:b[k])}var k=arguments.length-2;if(1===k)d.children=c;else if(1<k){h=Array(k);for(var l=0;l<k;l++)h[l]=arguments[l+2];d.children=h}return{$$typeof:x,type:a.type,key:e,ref:f,props:d,_owner:m}};d.createContext=function(a,b){void 0===b&&(b=null);a={$$typeof:Ea,_calculateChangedBits:b,_currentValue:a,_currentValue2:a,_threadCount:0,Provider:null,Consumer:null};a.Provider={$$typeof:Da,_context:a};return a.Consumer=
a};d.createElement=ea;d.createFactory=function(a){var b=ea.bind(null,a);b.type=a;return b};d.createRef=function(){return{current:null}};d.forwardRef=function(a){return{$$typeof:Fa,render:a}};d.isValidElement=N;d.lazy=function(a){return{$$typeof:Ia,_ctor:a,_status:-1,_result:null}};d.memo=function(a,b){return{$$typeof:Ha,type:a,compare:void 0===b?null:b}};d.useCallback=function(a,b){return t().useCallback(a,b)};d.useContext=function(a,b){return t().useContext(a,b)};d.useDebugValue=function(a,b){};
d.useEffect=function(a,b){return t().useEffect(a,b)};d.useImperativeHandle=function(a,b,c){return t().useImperativeHandle(a,b,c)};d.useLayoutEffect=function(a,b){return t().useLayoutEffect(a,b)};d.useMemo=function(a,b){return t().useMemo(a,b)};d.useReducer=function(a,b,c){return t().useReducer(a,b,c)};d.useRef=function(a){return t().useRef(a)};d.useState=function(a){return t().useState(a)};d.version="16.13.1"});
</script>
<script crossorigin>/** @license React v16.13.1
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
Modernizr 3.0.0pre (Custom Build) | MIT
*/
'use strict';(function(I,ea){"object"===typeof exports&&"undefined"!==typeof module?ea(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],ea):(I=I||self,ea(I.ReactDOM={},I.React))})(this,function(I,ea){function k(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
function ji(a,b,c,d,e,f,g,h,m){yb=!1;gc=null;ki.apply(li,arguments)}function mi(a,b,c,d,e,f,g,h,m){ji.apply(this,arguments);if(yb){if(yb){var n=gc;yb=!1;gc=null}else throw Error(k(198));hc||(hc=!0,pd=n)}}function lf(a,b,c){var d=a.type||"unknown-event";a.currentTarget=mf(c);mi(d,b,void 0,a);a.currentTarget=null}function nf(){if(ic)for(var a in cb){var b=cb[a],c=ic.indexOf(a);if(!(-1<c))throw Error(k(96,a));if(!jc[c]){if(!b.extractEvents)throw Error(k(97,a));jc[c]=b;c=b.eventTypes;for(var d in c){var e=
void 0;var f=c[d],g=b,h=d;if(qd.hasOwnProperty(h))throw Error(k(99,h));qd[h]=f;var m=f.phasedRegistrationNames;if(m){for(e in m)m.hasOwnProperty(e)&&of(m[e],g,h);e=!0}else f.registrationName?(of(f.registrationName,g,h),e=!0):e=!1;if(!e)throw Error(k(98,d,a));}}}}function of(a,b,c){if(db[a])throw Error(k(100,a));db[a]=b;rd[a]=b.eventTypes[c].dependencies}function pf(a){var b=!1,c;for(c in a)if(a.hasOwnProperty(c)){var d=a[c];if(!cb.hasOwnProperty(c)||cb[c]!==d){if(cb[c])throw Error(k(102,c));cb[c]=
d;b=!0}}b&&nf()}function qf(a){if(a=rf(a)){if("function"!==typeof sd)throw Error(k(280));var b=a.stateNode;b&&(b=td(b),sd(a.stateNode,a.type,b))}}function sf(a){eb?fb?fb.push(a):fb=[a]:eb=a}function tf(){if(eb){var a=eb,b=fb;fb=eb=null;qf(a);if(b)for(a=0;a<b.length;a++)qf(b[a])}}function ud(){if(null!==eb||null!==fb)vd(),tf()}function uf(a,b,c){if(wd)return a(b,c);wd=!0;try{return vf(a,b,c)}finally{wd=!1,ud()}}function ni(a){if(wf.call(xf,a))return!0;if(wf.call(yf,a))return!1;if(oi.test(a))return xf[a]=
!0;yf[a]=!0;return!1}function pi(a,b,c,d){if(null!==c&&0===c.type)return!1;switch(typeof b){case "function":case "symbol":return!0;case "boolean":if(d)return!1;if(null!==c)return!c.acceptsBooleans;a=a.toLowerCase().slice(0,5);return"data-"!==a&&"aria-"!==a;default:return!1}}function qi(a,b,c,d){if(null===b||"undefined"===typeof b||pi(a,b,c,d))return!0;if(d)return!1;if(null!==c)switch(c.type){case 3:return!b;case 4:return!1===b;case 5:return isNaN(b);case 6:return isNaN(b)||1>b}return!1}function L(a,
b,c,d,e,f){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f}function xd(a,b,c,d){var e=E.hasOwnProperty(b)?E[b]:null;var f=null!==e?0===e.type:d?!1:!(2<b.length)||"o"!==b[0]&&"O"!==b[0]||"n"!==b[1]&&"N"!==b[1]?!1:!0;f||(qi(b,c,e,d)&&(c=null),d||null===e?ni(b)&&(null===c?a.removeAttribute(b):a.setAttribute(b,""+c)):e.mustUseProperty?a[e.propertyName]=null===c?3===e.type?!1:"":c:(b=e.attributeName,
d=e.attributeNamespace,null===c?a.removeAttribute(b):(e=e.type,c=3===e||4===e&&!0===c?"":""+c,d?a.setAttributeNS(d,b,c):a.setAttribute(b,c))))}function zb(a){if(null===a||"object"!==typeof a)return null;a=zf&&a[zf]||a["@@iterator"];return"function"===typeof a?a:null}function ri(a){if(-1===a._status){a._status=0;var b=a._ctor;b=b();a._result=b;b.then(function(b){0===a._status&&(b=b.default,a._status=1,a._result=b)},function(b){0===a._status&&(a._status=2,a._result=b)})}}function na(a){if(null==a)return null;
if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Ma:return"Fragment";case gb:return"Portal";case kc:return"Profiler";case Af:return"StrictMode";case lc:return"Suspense";case yd:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case Bf:return"Context.Consumer";case Cf:return"Context.Provider";case zd:var b=a.render;b=b.displayName||b.name||"";return a.displayName||(""!==b?"ForwardRef("+b+")":"ForwardRef");case Ad:return na(a.type);
case Df:return na(a.render);case Ef:if(a=1===a._status?a._result:null)return na(a)}return null}function Bd(a){var b="";do{a:switch(a.tag){case 3:case 4:case 6:case 7:case 10:case 9:var c="";break a;default:var d=a._debugOwner,e=a._debugSource,f=na(a.type);c=null;d&&(c=na(d.type));d=f;f="";e?f=" (at "+e.fileName.replace(si,"")+":"+e.lineNumber+")":c&&(f=" (created by "+c+")");c="\n in "+(d||"Unknown")+f}b+=c;a=a.return}while(a);return b}function va(a){switch(typeof a){case "boolean":case "number":case "object":case "string":case "undefined":return a;
default:return""}}function Ff(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===b)}function ti(a){var b=Ff(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,
b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=null;delete a[b]}}}}function mc(a){a._valueTracker||(a._valueTracker=ti(a))}function Gf(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=Ff(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Cd(a,b){var c=b.checked;return M({},b,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=
c?c:a._wrapperState.initialChecked})}function Hf(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=va(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function If(a,b){b=b.checked;null!=b&&xd(a,"checked",b,!1)}function Dd(a,b){If(a,b);var c=va(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=c)a.value=""+c}else a.value!==
""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?Ed(a,b.type,c):b.hasOwnProperty("defaultValue")&&Ed(a,b.type,va(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function Jf(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;c||b===a.value||(a.value=
b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function Ed(a,b,c){if("number"!==b||a.ownerDocument.activeElement!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function ui(a){var b="";ea.Children.forEach(a,function(a){null!=a&&(b+=a)});return b}function Fd(a,b){a=M({children:void 0},b);if(b=ui(b.children))a.children=b;return a}function hb(a,b,c,d){a=a.options;if(b){b={};
for(var e=0;e<c.length;e++)b["$"+c[e]]=!0;for(c=0;c<a.length;c++)e=b.hasOwnProperty("$"+a[c].value),a[c].selected!==e&&(a[c].selected=e),e&&d&&(a[c].defaultSelected=!0)}else{c=""+va(c);b=null;for(e=0;e<a.length;e++){if(a[e].value===c){a[e].selected=!0;d&&(a[e].defaultSelected=!0);return}null!==b||a[e].disabled||(b=a[e])}null!==b&&(b.selected=!0)}}function Gd(a,b){if(null!=b.dangerouslySetInnerHTML)throw Error(k(91));return M({},b,{value:void 0,defaultValue:void 0,children:""+a._wrapperState.initialValue})}
function Kf(a,b){var c=b.value;if(null==c){c=b.children;b=b.defaultValue;if(null!=c){if(null!=b)throw Error(k(92));if(Array.isArray(c)){if(!(1>=c.length))throw Error(k(93));c=c[0]}b=c}null==b&&(b="");c=b}a._wrapperState={initialValue:va(c)}}function Lf(a,b){var c=va(b.value),d=va(b.defaultValue);null!=c&&(c=""+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!==c&&(a.defaultValue=c));null!=d&&(a.defaultValue=""+d)}function Mf(a,b){b=a.textContent;b===a._wrapperState.initialValue&&""!==
b&&null!==b&&(a.value=b)}function Nf(a){switch(a){case "svg":return"http://www.w3.org/2000/svg";case "math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function Hd(a,b){return null==a||"http://www.w3.org/1999/xhtml"===a?Nf(b):"http://www.w3.org/2000/svg"===a&&"foreignObject"===b?"http://www.w3.org/1999/xhtml":a}function nc(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c["Webkit"+a]="webkit"+b;c["Moz"+a]="moz"+b;return c}function oc(a){if(Id[a])return Id[a];
if(!ib[a])return a;var b=ib[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in Of)return Id[a]=b[c];return a}function Jd(a){var b=Pf.get(a);void 0===b&&(b=new Map,Pf.set(a,b));return b}function Na(a){var b=a,c=a;if(a.alternate)for(;b.return;)b=b.return;else{a=b;do b=a,0!==(b.effectTag&1026)&&(c=b.return),a=b.return;while(a)}return 3===b.tag?c:null}function Qf(a){if(13===a.tag){var b=a.memoizedState;null===b&&(a=a.alternate,null!==a&&(b=a.memoizedState));if(null!==b)return b.dehydrated}return null}function Rf(a){if(Na(a)!==
a)throw Error(k(188));}function vi(a){var b=a.alternate;if(!b){b=Na(a);if(null===b)throw Error(k(188));return b!==a?null:a}for(var c=a,d=b;;){var e=c.return;if(null===e)break;var f=e.alternate;if(null===f){d=e.return;if(null!==d){c=d;continue}break}if(e.child===f.child){for(f=e.child;f;){if(f===c)return Rf(e),a;if(f===d)return Rf(e),b;f=f.sibling}throw Error(k(188));}if(c.return!==d.return)c=e,d=f;else{for(var g=!1,h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=
f.child;h;){if(h===c){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}if(!g)throw Error(k(189));}}if(c.alternate!==d)throw Error(k(190));}if(3!==c.tag)throw Error(k(188));return c.stateNode.current===c?a:b}function Sf(a){a=vi(a);if(!a)return null;for(var b=a;;){if(5===b.tag||6===b.tag)return b;if(b.child)b.child.return=b,b=b.child;else{if(b===a)break;for(;!b.sibling;){if(!b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}}return null}function jb(a,b){if(null==
b)throw Error(k(30));if(null==a)return b;if(Array.isArray(a)){if(Array.isArray(b))return a.push.apply(a,b),a;a.push(b);return a}return Array.isArray(b)?[a].concat(b):[a,b]}function Kd(a,b,c){Array.isArray(a)?a.forEach(b,c):a&&b.call(c,a)}function pc(a){null!==a&&(Ab=jb(Ab,a));a=Ab;Ab=null;if(a){Kd(a,wi);if(Ab)throw Error(k(95));if(hc)throw a=pd,hc=!1,pd=null,a;}}function Ld(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement);return 3===a.nodeType?a.parentNode:
a}function Tf(a){if(!wa)return!1;a="on"+a;var b=a in document;b||(b=document.createElement("div"),b.setAttribute(a,"return;"),b="function"===typeof b[a]);return b}function Uf(a){a.topLevelType=null;a.nativeEvent=null;a.targetInst=null;a.ancestors.length=0;10>qc.length&&qc.push(a)}function Vf(a,b,c,d){if(qc.length){var e=qc.pop();e.topLevelType=a;e.eventSystemFlags=d;e.nativeEvent=b;e.targetInst=c;return e}return{topLevelType:a,eventSystemFlags:d,nativeEvent:b,targetInst:c,ancestors:[]}}function Wf(a){var b=
a.targetInst,c=b;do{if(!c){a.ancestors.push(c);break}var d=c;if(3===d.tag)d=d.stateNode.containerInfo;else{for(;d.return;)d=d.return;d=3!==d.tag?null:d.stateNode.containerInfo}if(!d)break;b=c.tag;5!==b&&6!==b||a.ancestors.push(c);c=Bb(d)}while(c);for(c=0;c<a.ancestors.length;c++){b=a.ancestors[c];var e=Ld(a.nativeEvent);d=a.topLevelType;var f=a.nativeEvent,g=a.eventSystemFlags;0===c&&(g|=64);for(var h=null,m=0;m<jc.length;m++){var n=jc[m];n&&(n=n.extractEvents(d,b,f,e,g))&&(h=jb(h,n))}pc(h)}}function Md(a,
b,c){if(!c.has(a)){switch(a){case "scroll":Cb(b,"scroll",!0);break;case "focus":case "blur":Cb(b,"focus",!0);Cb(b,"blur",!0);c.set("blur",null);c.set("focus",null);break;case "cancel":case "close":Tf(a)&&Cb(b,a,!0);break;case "invalid":case "submit":case "reset":break;default:-1===Db.indexOf(a)&&w(a,b)}c.set(a,null)}}function xi(a,b){var c=Jd(b);Nd.forEach(function(a){Md(a,b,c)});yi.forEach(function(a){Md(a,b,c)})}function Od(a,b,c,d,e){return{blockedOn:a,topLevelType:b,eventSystemFlags:c|32,nativeEvent:e,
container:d}}function Xf(a,b){switch(a){case "focus":case "blur":xa=null;break;case "dragenter":case "dragleave":ya=null;break;case "mouseover":case "mouseout":za=null;break;case "pointerover":case "pointerout":Eb.delete(b.pointerId);break;case "gotpointercapture":case "lostpointercapture":Fb.delete(b.pointerId)}}function Gb(a,b,c,d,e,f){if(null===a||a.nativeEvent!==f)return a=Od(b,c,d,e,f),null!==b&&(b=Hb(b),null!==b&&Yf(b)),a;a.eventSystemFlags|=d;return a}function zi(a,b,c,d,e){switch(b){case "focus":return xa=
Gb(xa,a,b,c,d,e),!0;case "dragenter":return ya=Gb(ya,a,b,c,d,e),!0;case "mouseover":return za=Gb(za,a,b,c,d,e),!0;case "pointerover":var f=e.pointerId;Eb.set(f,Gb(Eb.get(f)||null,a,b,c,d,e));return!0;case "gotpointercapture":return f=e.pointerId,Fb.set(f,Gb(Fb.get(f)||null,a,b,c,d,e)),!0}return!1}function Ai(a){var b=Bb(a.target);if(null!==b){var c=Na(b);if(null!==c)if(b=c.tag,13===b){if(b=Qf(c),null!==b){a.blockedOn=b;Pd(a.priority,function(){Bi(c)});return}}else if(3===b&&c.stateNode.hydrate){a.blockedOn=
3===c.tag?c.stateNode.containerInfo:null;return}}a.blockedOn=null}function rc(a){if(null!==a.blockedOn)return!1;var b=Qd(a.topLevelType,a.eventSystemFlags,a.container,a.nativeEvent);if(null!==b){var c=Hb(b);null!==c&&Yf(c);a.blockedOn=b;return!1}return!0}function Zf(a,b,c){rc(a)&&c.delete(b)}function Ci(){for(Rd=!1;0<fa.length;){var a=fa[0];if(null!==a.blockedOn){a=Hb(a.blockedOn);null!==a&&Di(a);break}var b=Qd(a.topLevelType,a.eventSystemFlags,a.container,a.nativeEvent);null!==b?a.blockedOn=b:fa.shift()}null!==
xa&&rc(xa)&&(xa=null);null!==ya&&rc(ya)&&(ya=null);null!==za&&rc(za)&&(za=null);Eb.forEach(Zf);Fb.forEach(Zf)}function Ib(a,b){a.blockedOn===b&&(a.blockedOn=null,Rd||(Rd=!0,$f(ag,Ci)))}function bg(a){if(0<fa.length){Ib(fa[0],a);for(var b=1;b<fa.length;b++){var c=fa[b];c.blockedOn===a&&(c.blockedOn=null)}}null!==xa&&Ib(xa,a);null!==ya&&Ib(ya,a);null!==za&&Ib(za,a);b=function(b){return Ib(b,a)};Eb.forEach(b);Fb.forEach(b);for(b=0;b<Jb.length;b++)c=Jb[b],c.blockedOn===a&&(c.blockedOn=null);for(;0<Jb.length&&
(b=Jb[0],null===b.blockedOn);)Ai(b),null===b.blockedOn&&Jb.shift()}function Sd(a,b){for(var c=0;c<a.length;c+=2){var d=a[c],e=a[c+1],f="on"+(e[0].toUpperCase()+e.slice(1));f={phasedRegistrationNames:{bubbled:f,captured:f+"Capture"},dependencies:[d],eventPriority:b};Td.set(d,b);cg.set(d,f);dg[e]=f}}function w(a,b){Cb(b,a,!1)}function Cb(a,b,c){var d=Td.get(b);switch(void 0===d?2:d){case 0:d=Ei.bind(null,b,1,a);break;case 1:d=Fi.bind(null,b,1,a);break;default:d=sc.bind(null,b,1,a)}c?a.addEventListener(b,
d,!0):a.addEventListener(b,d,!1)}function Ei(a,b,c,d){Oa||vd();var e=sc,f=Oa;Oa=!0;try{eg(e,a,b,c,d)}finally{(Oa=f)||ud()}}function Fi(a,b,c,d){Gi(Hi,sc.bind(null,a,b,c,d))}function sc(a,b,c,d){if(tc)if(0<fa.length&&-1<Nd.indexOf(a))a=Od(null,a,b,c,d),fa.push(a);else{var e=Qd(a,b,c,d);if(null===e)Xf(a,d);else if(-1<Nd.indexOf(a))a=Od(e,a,b,c,d),fa.push(a);else if(!zi(e,a,b,c,d)){Xf(a,d);a=Vf(a,d,null,b);try{uf(Wf,a)}finally{Uf(a)}}}}function Qd(a,b,c,d){c=Ld(d);c=Bb(c);if(null!==c){var e=Na(c);if(null===
e)c=null;else{var f=e.tag;if(13===f){c=Qf(e);if(null!==c)return c;c=null}else if(3===f){if(e.stateNode.hydrate)return 3===e.tag?e.stateNode.containerInfo:null;c=null}else e!==c&&(c=null)}}a=Vf(a,d,c,b);try{uf(Wf,a)}finally{Uf(a)}return null}function fg(a,b,c){return null==b||"boolean"===typeof b||""===b?"":c||"number"!==typeof b||0===b||Kb.hasOwnProperty(a)&&Kb[a]?(""+b).trim():b+"px"}function gg(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--"),e=fg(c,b[c],d);"float"===
c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}function Ud(a,b){if(b){if(Ii[a]&&(null!=b.children||null!=b.dangerouslySetInnerHTML))throw Error(k(137,a,""));if(null!=b.dangerouslySetInnerHTML){if(null!=b.children)throw Error(k(60));if(!("object"===typeof b.dangerouslySetInnerHTML&&"__html"in b.dangerouslySetInnerHTML))throw Error(k(61));}if(null!=b.style&&"object"!==typeof b.style)throw Error(k(62,""));}}function Vd(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;
default:return!0}}function oa(a,b){a=9===a.nodeType||11===a.nodeType?a:a.ownerDocument;var c=Jd(a);b=rd[b];for(var d=0;d<b.length;d++)Md(b[d],a,c)}function uc(){}function Wd(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function hg(a){for(;a&&a.firstChild;)a=a.firstChild;return a}function ig(a,b){var c=hg(a);a=0;for(var d;c;){if(3===c.nodeType){d=a+c.textContent.length;if(a<=b&&d>=b)return{node:c,
offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=hg(c)}}function jg(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?jg(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function kg(){for(var a=window,b=Wd();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;b=Wd(a.document)}return b}
function Xd(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function lg(a,b){switch(a){case "button":case "input":case "select":case "textarea":return!!b.autoFocus}return!1}function Yd(a,b){return"textarea"===a||"option"===a||"noscript"===a||"string"===typeof b.children||"number"===typeof b.children||"object"===typeof b.dangerouslySetInnerHTML&&
null!==b.dangerouslySetInnerHTML&&null!=b.dangerouslySetInnerHTML.__html}function kb(a){for(;null!=a;a=a.nextSibling){var b=a.nodeType;if(1===b||3===b)break}return a}function mg(a){a=a.previousSibling;for(var b=0;a;){if(8===a.nodeType){var c=a.data;if(c===ng||c===Zd||c===$d){if(0===b)return a;b--}else c===og&&b++}a=a.previousSibling}return null}function Bb(a){var b=a[Aa];if(b)return b;for(var c=a.parentNode;c;){if(b=c[Lb]||c[Aa]){c=b.alternate;if(null!==b.child||null!==c&&null!==c.child)for(a=mg(a);null!==
a;){if(c=a[Aa])return c;a=mg(a)}return b}a=c;c=a.parentNode}return null}function Hb(a){a=a[Aa]||a[Lb];return!a||5!==a.tag&&6!==a.tag&&13!==a.tag&&3!==a.tag?null:a}function Pa(a){if(5===a.tag||6===a.tag)return a.stateNode;throw Error(k(33));}function ae(a){return a[vc]||null}function pa(a){do a=a.return;while(a&&5!==a.tag);return a?a:null}function pg(a,b){var c=a.stateNode;if(!c)return null;var d=td(c);if(!d)return null;c=d[b];a:switch(b){case "onClick":case "onClickCapture":case "onDoubleClick":case "onDoubleClickCapture":case "onMouseDown":case "onMouseDownCapture":case "onMouseMove":case "onMouseMoveCapture":case "onMouseUp":case "onMouseUpCapture":case "onMouseEnter":(d=
!d.disabled)||(a=a.type,d=!("button"===a||"input"===a||"select"===a||"textarea"===a));a=!d;break a;default:a=!1}if(a)return null;if(c&&"function"!==typeof c)throw Error(k(231,b,typeof c));return c}function qg(a,b,c){if(b=pg(a,c.dispatchConfig.phasedRegistrationNames[b]))c._dispatchListeners=jb(c._dispatchListeners,b),c._dispatchInstances=jb(c._dispatchInstances,a)}function Ji(a){if(a&&a.dispatchConfig.phasedRegistrationNames){for(var b=a._targetInst,c=[];b;)c.push(b),b=pa(b);for(b=c.length;0<b--;)qg(c[b],
"captured",a);for(b=0;b<c.length;b++)qg(c[b],"bubbled",a)}}function be(a,b,c){a&&c&&c.dispatchConfig.registrationName&&(b=pg(a,c.dispatchConfig.registrationName))&&(c._dispatchListeners=jb(c._dispatchListeners,b),c._dispatchInstances=jb(c._dispatchInstances,a))}function Ki(a){a&&a.dispatchConfig.registrationName&&be(a._targetInst,null,a)}function lb(a){Kd(a,Ji)}function rg(){if(wc)return wc;var a,b=ce,c=b.length,d,e="value"in Ba?Ba.value:Ba.textContent,f=e.length;for(a=0;a<c&&b[a]===e[a];a++);var g=
c-a;for(d=1;d<=g&&b[c-d]===e[f-d];d++);return wc=e.slice(a,1<d?1-d:void 0)}function xc(){return!0}function yc(){return!1}function R(a,b,c,d){this.dispatchConfig=a;this._targetInst=b;this.nativeEvent=c;a=this.constructor.Interface;for(var e in a)a.hasOwnProperty(e)&&((b=a[e])?this[e]=b(c):"target"===e?this.target=d:this[e]=c[e]);this.isDefaultPrevented=(null!=c.defaultPrevented?c.defaultPrevented:!1===c.returnValue)?xc:yc;this.isPropagationStopped=yc;return this}function Li(a,b,c,d){if(this.eventPool.length){var e=
this.eventPool.pop();this.call(e,a,b,c,d);return e}return new this(a,b,c,d)}function Mi(a){if(!(a instanceof this))throw Error(k(279));a.destructor();10>this.eventPool.length&&this.eventPool.push(a)}function sg(a){a.eventPool=[];a.getPooled=Li;a.release=Mi}function tg(a,b){switch(a){case "keyup":return-1!==Ni.indexOf(b.keyCode);case "keydown":return 229!==b.keyCode;case "keypress":case "mousedown":case "blur":return!0;default:return!1}}function ug(a){a=a.detail;return"object"===typeof a&&"data"in
a?a.data:null}function Oi(a,b){switch(a){case "compositionend":return ug(b);case "keypress":if(32!==b.which)return null;vg=!0;return wg;case "textInput":return a=b.data,a===wg&&vg?null:a;default:return null}}function Pi(a,b){if(mb)return"compositionend"===a||!de&&tg(a,b)?(a=rg(),wc=ce=Ba=null,mb=!1,a):null;switch(a){case "paste":return null;case "keypress":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1<b.char.length)return b.char;if(b.which)return String.fromCharCode(b.which)}return null;
case "compositionend":return xg&&"ko"!==b.locale?null:b.data;default:return null}}function yg(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return"input"===b?!!Qi[a.type]:"textarea"===b?!0:!1}function zg(a,b,c){a=R.getPooled(Ag.change,a,b,c);a.type="change";sf(c);lb(a);return a}function Ri(a){pc(a)}function zc(a){var b=Pa(a);if(Gf(b))return a}function Si(a,b){if("change"===a)return b}function Bg(){Mb&&(Mb.detachEvent("onpropertychange",Cg),Nb=Mb=null)}function Cg(a){if("value"===a.propertyName&&
zc(Nb))if(a=zg(Nb,a,Ld(a)),Oa)pc(a);else{Oa=!0;try{ee(Ri,a)}finally{Oa=!1,ud()}}}function Ti(a,b,c){"focus"===a?(Bg(),Mb=b,Nb=c,Mb.attachEvent("onpropertychange",Cg)):"blur"===a&&Bg()}function Ui(a,b){if("selectionchange"===a||"keyup"===a||"keydown"===a)return zc(Nb)}function Vi(a,b){if("click"===a)return zc(b)}function Wi(a,b){if("input"===a||"change"===a)return zc(b)}function Xi(a){var b=this.nativeEvent;return b.getModifierState?b.getModifierState(a):(a=Yi[a])?!!b[a]:!1}function fe(a){return Xi}
function Zi(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}function Ob(a,b){if(Qa(a,b))return!0;if("object"!==typeof a||null===a||"object"!==typeof b||null===b)return!1;var c=Object.keys(a),d=Object.keys(b);if(c.length!==d.length)return!1;for(d=0;d<c.length;d++)if(!$i.call(b,c[d])||!Qa(a[c[d]],b[c[d]]))return!1;return!0}function Dg(a,b){var c=b.window===b?b.document:9===b.nodeType?b:b.ownerDocument;if(ge||null==nb||nb!==Wd(c))return null;c=nb;"selectionStart"in c&&Xd(c)?c={start:c.selectionStart,
end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset});return Pb&&Ob(Pb,c)?null:(Pb=c,a=R.getPooled(Eg.select,he,a,b),a.type="select",a.target=nb,lb(a),a)}function Ac(a){var b=a.keyCode;"charCode"in a?(a=a.charCode,0===a&&13===b&&(a=13)):a=b;10===a&&(a=13);return 32<=a||13===a?a:0}function q(a,b){0>ob||(a.current=ie[ob],ie[ob]=null,ob--)}function y(a,b,c){ob++;
ie[ob]=a.current;a.current=b}function pb(a,b){var c=a.type.contextTypes;if(!c)return Ca;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function N(a){a=a.childContextTypes;return null!==a&&void 0!==a}function Fg(a,b,c){if(B.current!==Ca)throw Error(k(168));y(B,b);y(G,c)}
function Gg(a,b,c){var d=a.stateNode;a=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in a))throw Error(k(108,na(b)||"Unknown",e));return M({},c,{},d)}function Bc(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||Ca;Ra=B.current;y(B,a);y(G,G.current);return!0}function Hg(a,b,c){var d=a.stateNode;if(!d)throw Error(k(169));c?(a=Gg(a,b,Ra),d.__reactInternalMemoizedMergedChildContext=a,q(G),q(B),y(B,a)):q(G);y(G,c)}function Cc(){switch(aj()){case Dc:return 99;
case Ig:return 98;case Jg:return 97;case Kg:return 96;case Lg:return 95;default:throw Error(k(332));}}function Mg(a){switch(a){case 99:return Dc;case 98:return Ig;case 97:return Jg;case 96:return Kg;case 95:return Lg;default:throw Error(k(332));}}function Da(a,b){a=Mg(a);return bj(a,b)}function Ng(a,b,c){a=Mg(a);return je(a,b,c)}function Og(a){null===qa?(qa=[a],Ec=je(Dc,Pg)):qa.push(a);return Qg}function ha(){if(null!==Ec){var a=Ec;Ec=null;Rg(a)}Pg()}function Pg(){if(!ke&&null!==qa){ke=!0;var a=0;
try{var b=qa;Da(99,function(){for(;a<b.length;a++){var c=b[a];do c=c(!0);while(null!==c)}});qa=null}catch(c){throw null!==qa&&(qa=qa.slice(a+1)),je(Dc,ha),c;}finally{ke=!1}}}function Fc(a,b,c){c/=10;return 1073741821-(((1073741821-a+b/10)/c|0)+1)*c}function aa(a,b){if(a&&a.defaultProps){b=M({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c])}return b}function le(){Gc=qb=Hc=null}function me(a){var b=Ic.current;q(Ic);a.type._context._currentValue=b}function Sg(a,b){for(;null!==a;){var c=
a.alternate;if(a.childExpirationTime<b)a.childExpirationTime=b,null!==c&&c.childExpirationTime<b&&(c.childExpirationTime=b);else if(null!==c&&c.childExpirationTime<b)c.childExpirationTime=b;else break;a=a.return}}function rb(a,b){Hc=a;Gc=qb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&(a.expirationTime>=b&&(ia=!0),a.firstContext=null)}function W(a,b){if(Gc!==a&&!1!==b&&0!==b){if("number"!==typeof b||1073741823===b)Gc=a,b=1073741823;b={context:a,observedBits:b,next:null};if(null===qb){if(null===
Hc)throw Error(k(308));qb=b;Hc.dependencies={expirationTime:0,firstContext:b,responders:null}}else qb=qb.next=b}return a._currentValue}function ne(a){a.updateQueue={baseState:a.memoizedState,baseQueue:null,shared:{pending:null},effects:null}}function oe(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue={baseState:a.baseState,baseQueue:a.baseQueue,shared:a.shared,effects:a.effects})}function Ea(a,b){a={expirationTime:a,suspenseConfig:b,tag:Tg,payload:null,callback:null,next:null};return a.next=
a}function Fa(a,b){a=a.updateQueue;if(null!==a){a=a.shared;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}}function Ug(a,b){var c=a.alternate;null!==c&&oe(c,a);a=a.updateQueue;c=a.baseQueue;null===c?(a.baseQueue=b.next=b,b.next=b):(b.next=c.next,c.next=b)}function Qb(a,b,c,d){var e=a.updateQueue;Ga=!1;var f=e.baseQueue,g=e.shared.pending;if(null!==g){if(null!==f){var h=f.next;f.next=g.next;g.next=h}f=g;e.shared.pending=null;h=a.alternate;null!==h&&(h=h.updateQueue,null!==h&&
(h.baseQueue=g))}if(null!==f){h=f.next;var m=e.baseState,n=0,k=null,ba=null,l=null;if(null!==h){var p=h;do{g=p.expirationTime;if(g<d){var t={expirationTime:p.expirationTime,suspenseConfig:p.suspenseConfig,tag:p.tag,payload:p.payload,callback:p.callback,next:null};null===l?(ba=l=t,k=m):l=l.next=t;g>n&&(n=g)}else{null!==l&&(l=l.next={expirationTime:1073741823,suspenseConfig:p.suspenseConfig,tag:p.tag,payload:p.payload,callback:p.callback,next:null});Vg(g,p.suspenseConfig);a:{var q=a,r=p;g=b;t=c;switch(r.tag){case 1:q=
r.payload;if("function"===typeof q){m=q.call(t,m,g);break a}m=q;break a;case 3:q.effectTag=q.effectTag&-4097|64;case Tg:q=r.payload;g="function"===typeof q?q.call(t,m,g):q;if(null===g||void 0===g)break a;m=M({},m,g);break a;case Jc:Ga=!0}}null!==p.callback&&(a.effectTag|=32,g=e.effects,null===g?e.effects=[p]:g.push(p))}p=p.next;if(null===p||p===h)if(g=e.shared.pending,null===g)break;else p=f.next=g.next,g.next=h,e.baseQueue=f=g,e.shared.pending=null}while(1)}null===l?k=m:l.next=ba;e.baseState=k;e.baseQueue=
l;Kc(n);a.expirationTime=n;a.memoizedState=m}}function Wg(a,b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;b<a.length;b++){var d=a[b],e=d.callback;if(null!==e){d.callback=null;d=e;e=c;if("function"!==typeof d)throw Error(k(191,d));d.call(e)}}}function Lc(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:M({},b,c);a.memoizedState=c;0===a.expirationTime&&(a.updateQueue.baseState=c)}function Xg(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,
f,g):b.prototype&&b.prototype.isPureReactComponent?!Ob(c,d)||!Ob(e,f):!0}function Yg(a,b,c){var d=!1,e=Ca;var f=b.contextType;"object"===typeof f&&null!==f?f=W(f):(e=N(b)?Ra:B.current,d=b.contextTypes,f=(d=null!==d&&void 0!==d)?pb(a,e):Ca);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=Mc;a.stateNode=b;b._reactInternalFiber=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function Zg(a,
b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&&b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&Mc.enqueueReplaceState(b,b.state,null)}function pe(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs=$g;ne(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=W(f):(f=N(b)?Ra:B.current,e.context=pb(a,f));Qb(a,c,e,d);e.state=a.memoizedState;f=b.getDerivedStateFromProps;
"function"===typeof f&&(Lc(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!==typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&Mc.enqueueReplaceState(e,e.state,null),Qb(a,c,e,d),e.state=a.memoizedState);"function"===
typeof e.componentDidMount&&(a.effectTag|=4)}function Rb(a,b,c){a=c.ref;if(null!==a&&"function"!==typeof a&&"object"!==typeof a){if(c._owner){c=c._owner;if(c){if(1!==c.tag)throw Error(k(309));var d=c.stateNode}if(!d)throw Error(k(147,a));var e=""+a;if(null!==b&&null!==b.ref&&"function"===typeof b.ref&&b.ref._stringRef===e)return b.ref;b=function(a){var b=d.refs;b===$g&&(b=d.refs={});null===a?delete b[e]:b[e]=a};b._stringRef=e;return b}if("string"!==typeof a)throw Error(k(284));if(!c._owner)throw Error(k(290,
a));}return a}function Nc(a,b){if("textarea"!==a.type)throw Error(k(31,"[object Object]"===Object.prototype.toString.call(b)?"object with keys {"+Object.keys(b).join(", ")+"}":b,""));}function ah(a){function b(b,c){if(a){var d=b.lastEffect;null!==d?(d.nextEffect=c,b.lastEffect=c):b.firstEffect=b.lastEffect=c;c.nextEffect=null;c.effectTag=8}}function c(c,d){if(!a)return null;for(;null!==d;)b(c,d),d=d.sibling;return null}function d(a,b){for(a=new Map;null!==b;)null!==b.key?a.set(b.key,b):a.set(b.index,
b),b=b.sibling;return a}function e(a,b){a=Sa(a,b);a.index=0;a.sibling=null;return a}function f(b,c,d){b.index=d;if(!a)return c;d=b.alternate;if(null!==d)return d=d.index,d<c?(b.effectTag=2,c):d;b.effectTag=2;return c}function g(b){a&&null===b.alternate&&(b.effectTag=2);return b}function h(a,b,c,d){if(null===b||6!==b.tag)return b=qe(c,a.mode,d),b.return=a,b;b=e(b,c);b.return=a;return b}function m(a,b,c,d){if(null!==b&&b.elementType===c.type)return d=e(b,c.props),d.ref=Rb(a,b,c),d.return=a,d;d=Oc(c.type,
c.key,c.props,null,a.mode,d);d.ref=Rb(a,b,c);d.return=a;return d}function n(a,b,c,d){if(null===b||4!==b.tag||b.stateNode.containerInfo!==c.containerInfo||b.stateNode.implementation!==c.implementation)return b=re(c,a.mode,d),b.return=a,b;b=e(b,c.children||[]);b.return=a;return b}function l(a,b,c,d,f){if(null===b||7!==b.tag)return b=Ha(c,a.mode,d,f),b.return=a,b;b=e(b,c);b.return=a;return b}function ba(a,b,c){if("string"===typeof b||"number"===typeof b)return b=qe(""+b,a.mode,c),b.return=a,b;if("object"===
typeof b&&null!==b){switch(b.$$typeof){case Pc:return c=Oc(b.type,b.key,b.props,null,a.mode,c),c.ref=Rb(a,null,b),c.return=a,c;case gb:return b=re(b,a.mode,c),b.return=a,b}if(Qc(b)||zb(b))return b=Ha(b,a.mode,c,null),b.return=a,b;Nc(a,b)}return null}function p(a,b,c,d){var e=null!==b?b.key:null;if("string"===typeof c||"number"===typeof c)return null!==e?null:h(a,b,""+c,d);if("object"===typeof c&&null!==c){switch(c.$$typeof){case Pc:return c.key===e?c.type===Ma?l(a,b,c.props.children,d,e):m(a,b,c,
d):null;case gb:return c.key===e?n(a,b,c,d):null}if(Qc(c)||zb(c))return null!==e?null:l(a,b,c,d,null);Nc(a,c)}return null}function t(a,b,c,d,e){if("string"===typeof d||"number"===typeof d)return a=a.get(c)||null,h(b,a,""+d,e);if("object"===typeof d&&null!==d){switch(d.$$typeof){case Pc:return a=a.get(null===d.key?c:d.key)||null,d.type===Ma?l(b,a,d.props.children,e,d.key):m(b,a,d,e);case gb:return a=a.get(null===d.key?c:d.key)||null,n(b,a,d,e)}if(Qc(d)||zb(d))return a=a.get(c)||null,l(b,a,d,e,null);
Nc(b,d)}return null}function q(e,g,h,m){for(var n=null,k=null,l=g,r=g=0,C=null;null!==l&&r<h.length;r++){l.index>r?(C=l,l=null):C=l.sibling;var O=p(e,l,h[r],m);if(null===O){null===l&&(l=C);break}a&&l&&null===O.alternate&&b(e,l);g=f(O,g,r);null===k?n=O:k.sibling=O;k=O;l=C}if(r===h.length)return c(e,l),n;if(null===l){for(;r<h.length;r++)l=ba(e,h[r],m),null!==l&&(g=f(l,g,r),null===k?n=l:k.sibling=l,k=l);return n}for(l=d(e,l);r<h.length;r++)C=t(l,e,r,h[r],m),null!==C&&(a&&null!==C.alternate&&l.delete(null===
C.key?r:C.key),g=f(C,g,r),null===k?n=C:k.sibling=C,k=C);a&&l.forEach(function(a){return b(e,a)});return n}function w(e,g,h,n){var m=zb(h);if("function"!==typeof m)throw Error(k(150));h=m.call(h);if(null==h)throw Error(k(151));for(var l=m=null,r=g,C=g=0,O=null,v=h.next();null!==r&&!v.done;C++,v=h.next()){r.index>C?(O=r,r=null):O=r.sibling;var q=p(e,r,v.value,n);if(null===q){null===r&&(r=O);break}a&&r&&null===q.alternate&&b(e,r);g=f(q,g,C);null===l?m=q:l.sibling=q;l=q;r=O}if(v.done)return c(e,r),m;
if(null===r){for(;!v.done;C++,v=h.next())v=ba(e,v.value,n),null!==v&&(g=f(v,g,C),null===l?m=v:l.sibling=v,l=v);return m}for(r=d(e,r);!v.done;C++,v=h.next())v=t(r,e,C,v.value,n),null!==v&&(a&&null!==v.alternate&&r.delete(null===v.key?C:v.key),g=f(v,g,C),null===l?m=v:l.sibling=v,l=v);a&&r.forEach(function(a){return b(e,a)});return m}return function(a,d,f,h){var m="object"===typeof f&&null!==f&&f.type===Ma&&null===f.key;m&&(f=f.props.children);var n="object"===typeof f&&null!==f;if(n)switch(f.$$typeof){case Pc:a:{n=
f.key;for(m=d;null!==m;){if(m.key===n){switch(m.tag){case 7:if(f.type===Ma){c(a,m.sibling);d=e(m,f.props.children);d.return=a;a=d;break a}break;default:if(m.elementType===f.type){c(a,m.sibling);d=e(m,f.props);d.ref=Rb(a,m,f);d.return=a;a=d;break a}}c(a,m);break}else b(a,m);m=m.sibling}f.type===Ma?(d=Ha(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=Oc(f.type,f.key,f.props,null,a.mode,h),h.ref=Rb(a,d,f),h.return=a,a=h)}return g(a);case gb:a:{for(m=f.key;null!==d;){if(d.key===m)if(4===d.tag&&d.stateNode.containerInfo===
f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=re(f,a.mode,h);d.return=a;a=d}return g(a)}if("string"===typeof f||"number"===typeof f)return f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=qe(f,a.mode,h),d.return=a,a=d),g(a);if(Qc(f))return q(a,d,f,h);if(zb(f))return w(a,d,f,h);n&&Nc(a,f);if("undefined"===typeof f&&!m)switch(a.tag){case 1:case 0:throw a=
a.type,Error(k(152,a.displayName||a.name||"Component"));}return c(a,d)}}function Ta(a){if(a===Sb)throw Error(k(174));return a}function se(a,b){y(Tb,b);y(Ub,a);y(ja,Sb);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:Hd(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=Hd(b,a)}q(ja);y(ja,b)}function tb(a){q(ja);q(Ub);q(Tb)}function bh(a){Ta(Tb.current);var b=Ta(ja.current);var c=Hd(b,a.type);b!==c&&(y(Ub,a),y(ja,c))}function te(a){Ub.current===
a&&(q(ja),q(Ub))}function Rc(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||c.data===$d||c.data===Zd))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.effectTag&64))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}function ue(a,b){return{responder:a,props:b}}
function S(){throw Error(k(321));}function ve(a,b){if(null===b)return!1;for(var c=0;c<b.length&&c<a.length;c++)if(!Qa(a[c],b[c]))return!1;return!0}function we(a,b,c,d,e,f){Ia=f;z=b;b.memoizedState=null;b.updateQueue=null;b.expirationTime=0;Sc.current=null===a||null===a.memoizedState?dj:ej;a=c(d,e);if(b.expirationTime===Ia){f=0;do{b.expirationTime=0;if(!(25>f))throw Error(k(301));f+=1;J=K=null;b.updateQueue=null;Sc.current=fj;a=c(d,e)}while(b.expirationTime===Ia)}Sc.current=Tc;b=null!==K&&null!==K.next;
Ia=0;J=K=z=null;Uc=!1;if(b)throw Error(k(300));return a}function ub(){var a={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};null===J?z.memoizedState=J=a:J=J.next=a;return J}function vb(){if(null===K){var a=z.alternate;a=null!==a?a.memoizedState:null}else a=K.next;var b=null===J?z.memoizedState:J.next;if(null!==b)J=b,K=a;else{if(null===a)throw Error(k(310));K=a;a={memoizedState:K.memoizedState,baseState:K.baseState,baseQueue:K.baseQueue,queue:K.queue,next:null};null===J?z.memoizedState=
J=a:J=J.next=a}return J}function Ua(a,b){return"function"===typeof b?b(a):b}function Vc(a,b,c){b=vb();c=b.queue;if(null===c)throw Error(k(311));c.lastRenderedReducer=a;var d=K,e=d.baseQueue,f=c.pending;if(null!==f){if(null!==e){var g=e.next;e.next=f.next;f.next=g}d.baseQueue=e=f;c.pending=null}if(null!==e){e=e.next;d=d.baseState;var h=g=f=null,m=e;do{var n=m.expirationTime;if(n<Ia){var l={expirationTime:m.expirationTime,suspenseConfig:m.suspenseConfig,action:m.action,eagerReducer:m.eagerReducer,eagerState:m.eagerState,
next:null};null===h?(g=h=l,f=d):h=h.next=l;n>z.expirationTime&&(z.expirationTime=n,Kc(n))}else null!==h&&(h=h.next={expirationTime:1073741823,suspenseConfig:m.suspenseConfig,action:m.action,eagerReducer:m.eagerReducer,eagerState:m.eagerState,next:null}),Vg(n,m.suspenseConfig),d=m.eagerReducer===a?m.eagerState:a(d,m.action);m=m.next}while(null!==m&&m!==e);null===h?f=d:h.next=g;Qa(d,b.memoizedState)||(ia=!0);b.memoizedState=d;b.baseState=f;b.baseQueue=h;c.lastRenderedState=d}return[b.memoizedState,
c.dispatch]}function Wc(a,b,c){b=vb();c=b.queue;if(null===c)throw Error(k(311));c.lastRenderedReducer=a;var d=c.dispatch,e=c.pending,f=b.memoizedState;if(null!==e){c.pending=null;var g=e=e.next;do f=a(f,g.action),g=g.next;while(g!==e);Qa(f,b.memoizedState)||(ia=!0);b.memoizedState=f;null===b.baseQueue&&(b.baseState=f);c.lastRenderedState=f}return[f,d]}function xe(a){var b=ub();"function"===typeof a&&(a=a());b.memoizedState=b.baseState=a;a=b.queue={pending:null,dispatch:null,lastRenderedReducer:Ua,
lastRenderedState:a};a=a.dispatch=ch.bind(null,z,a);return[b.memoizedState,a]}function ye(a,b,c,d){a={tag:a,create:b,destroy:c,deps:d,next:null};b=z.updateQueue;null===b?(b={lastEffect:null},z.updateQueue=b,b.lastEffect=a.next=a):(c=b.lastEffect,null===c?b.lastEffect=a.next=a:(d=c.next,c.next=a,a.next=d,b.lastEffect=a));return a}function dh(a){return vb().memoizedState}function ze(a,b,c,d){var e=ub();z.effectTag|=a;e.memoizedState=ye(1|b,c,void 0,void 0===d?null:d)}function Ae(a,b,c,d){var e=vb();
d=void 0===d?null:d;var f=void 0;if(null!==K){var g=K.memoizedState;f=g.destroy;if(null!==d&&ve(d,g.deps)){ye(b,c,f,d);return}}z.effectTag|=a;e.memoizedState=ye(1|b,c,f,d)}function eh(a,b){return ze(516,4,a,b)}function Xc(a,b){return Ae(516,4,a,b)}function fh(a,b){return Ae(4,2,a,b)}function gh(a,b){if("function"===typeof b)return a=a(),b(a),function(){b(null)};if(null!==b&&void 0!==b)return a=a(),b.current=a,function(){b.current=null}}function hh(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;
return Ae(4,2,gh.bind(null,b,a),c)}function Be(a,b){}function ih(a,b){ub().memoizedState=[a,void 0===b?null:b];return a}function Yc(a,b){var c=vb();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&ve(b,d[1]))return d[0];c.memoizedState=[a,b];return a}function jh(a,b){var c=vb();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&ve(b,d[1]))return d[0];a=a();c.memoizedState=[a,b];return a}function Ce(a,b,c){var d=Cc();Da(98>d?98:d,function(){a(!0)});Da(97<d?97:d,function(){var d=
X.suspense;X.suspense=void 0===b?null:b;try{a(!1),c()}finally{X.suspense=d}})}function ch(a,b,c){var d=ka(),e=Vb.suspense;d=Va(d,a,e);e={expirationTime:d,suspenseConfig:e,action:c,eagerReducer:null,eagerState:null,next:null};var f=b.pending;null===f?e.next=e:(e.next=f.next,f.next=e);b.pending=e;f=a.alternate;if(a===z||null!==f&&f===z)Uc=!0,e.expirationTime=Ia,z.expirationTime=Ia;else{if(0===a.expirationTime&&(null===f||0===f.expirationTime)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,
h=f(g,c);e.eagerReducer=f;e.eagerState=h;if(Qa(h,g))return}catch(m){}finally{}Ja(a,d)}}function kh(a,b){var c=la(5,null,null,0);c.elementType="DELETED";c.type="DELETED";c.stateNode=b;c.return=a;c.effectTag=8;null!==a.lastEffect?(a.lastEffect.nextEffect=c,a.lastEffect=c):a.firstEffect=a.lastEffect=c}function lh(a,b){switch(a.tag){case 5:var c=a.type;b=1!==b.nodeType||c.toLowerCase()!==b.nodeName.toLowerCase()?null:b;return null!==b?(a.stateNode=b,!0):!1;case 6:return b=""===a.pendingProps||3!==b.nodeType?
null:b,null!==b?(a.stateNode=b,!0):!1;case 13:return!1;default:return!1}}function De(a){if(Wa){var b=Ka;if(b){var c=b;if(!lh(a,b)){b=kb(c.nextSibling);if(!b||!lh(a,b)){a.effectTag=a.effectTag&-1025|2;Wa=!1;ra=a;return}kh(ra,c)}ra=a;Ka=kb(b.firstChild)}else a.effectTag=a.effectTag&-1025|2,Wa=!1,ra=a}}function mh(a){for(a=a.return;null!==a&&5!==a.tag&&3!==a.tag&&13!==a.tag;)a=a.return;ra=a}function Zc(a){if(a!==ra)return!1;if(!Wa)return mh(a),Wa=!0,!1;var b=a.type;if(5!==a.tag||"head"!==b&&"body"!==
b&&!Yd(b,a.memoizedProps))for(b=Ka;b;)kh(a,b),b=kb(b.nextSibling);mh(a);if(13===a.tag){a=a.memoizedState;a=null!==a?a.dehydrated:null;if(!a)throw Error(k(317));a:{a=a.nextSibling;for(b=0;a;){if(8===a.nodeType){var c=a.data;if(c===og){if(0===b){Ka=kb(a.nextSibling);break a}b--}else c!==ng&&c!==Zd&&c!==$d||b++}a=a.nextSibling}Ka=null}}else Ka=ra?kb(a.stateNode.nextSibling):null;return!0}function Ee(){Ka=ra=null;Wa=!1}function T(a,b,c,d){b.child=null===a?Fe(b,null,c,d):wb(b,a.child,c,d)}function nh(a,
b,c,d,e){c=c.render;var f=b.ref;rb(b,e);d=we(a,b,c,d,f,e);if(null!==a&&!ia)return b.updateQueue=a.updateQueue,b.effectTag&=-517,a.expirationTime<=e&&(a.expirationTime=0),sa(a,b,e);b.effectTag|=1;T(a,b,d,e);return b.child}function oh(a,b,c,d,e,f){if(null===a){var g=c.type;if("function"===typeof g&&!Ge(g)&&void 0===g.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=g,ph(a,b,g,d,e,f);a=Oc(c.type,null,d,null,b.mode,f);a.ref=b.ref;a.return=b;return b.child=a}g=a.child;if(e<
f&&(e=g.memoizedProps,c=c.compare,c=null!==c?c:Ob,c(e,d)&&a.ref===b.ref))return sa(a,b,f);b.effectTag|=1;a=Sa(g,d);a.ref=b.ref;a.return=b;return b.child=a}function ph(a,b,c,d,e,f){return null!==a&&Ob(a.memoizedProps,d)&&a.ref===b.ref&&(ia=!1,e<f)?(b.expirationTime=a.expirationTime,sa(a,b,f)):He(a,b,c,d,f)}function qh(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.effectTag|=128}function He(a,b,c,d,e){var f=N(c)?Ra:B.current;f=pb(b,f);rb(b,e);c=we(a,b,c,d,f,e);if(null!==a&&!ia)return b.updateQueue=
a.updateQueue,b.effectTag&=-517,a.expirationTime<=e&&(a.expirationTime=0),sa(a,b,e);b.effectTag|=1;T(a,b,c,e);return b.child}function rh(a,b,c,d,e){if(N(c)){var f=!0;Bc(b)}else f=!1;rb(b,e);if(null===b.stateNode)null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2),Yg(b,c,d),pe(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var m=g.context,n=c.contextType;"object"===typeof n&&null!==n?n=W(n):(n=N(c)?Ra:B.current,n=pb(b,n));var l=c.getDerivedStateFromProps,k="function"===
typeof l||"function"===typeof g.getSnapshotBeforeUpdate;k||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||m!==n)&&Zg(b,g,d,n);Ga=!1;var p=b.memoizedState;g.state=p;Qb(b,d,g,e);m=b.memoizedState;h!==d||p!==m||G.current||Ga?("function"===typeof l&&(Lc(b,c,l,d),m=b.memoizedState),(h=Ga||Xg(b,c,h,d,p,m,n))?(k||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount||("function"===typeof g.componentWillMount&&
g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.effectTag|=4)):("function"===typeof g.componentDidMount&&(b.effectTag|=4),b.memoizedProps=d,b.memoizedState=m),g.props=d,g.state=m,g.context=n,d=h):("function"===typeof g.componentDidMount&&(b.effectTag|=4),d=!1)}else g=b.stateNode,oe(a,b),h=b.memoizedProps,g.props=b.type===b.elementType?h:aa(b.type,h),m=g.context,n=c.contextType,"object"===typeof n&&null!==
n?n=W(n):(n=N(c)?Ra:B.current,n=pb(b,n)),l=c.getDerivedStateFromProps,(k="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||m!==n)&&Zg(b,g,d,n),Ga=!1,m=b.memoizedState,g.state=m,Qb(b,d,g,e),p=b.memoizedState,h!==d||m!==p||G.current||Ga?("function"===typeof l&&(Lc(b,c,l,d),p=b.memoizedState),(l=Ga||Xg(b,c,h,d,m,p,n))?(k||"function"!==typeof g.UNSAFE_componentWillUpdate&&
"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,p,n),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,p,n)),"function"===typeof g.componentDidUpdate&&(b.effectTag|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.effectTag|=256)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&m===
a.memoizedState||(b.effectTag|=256),b.memoizedProps=d,b.memoizedState=p),g.props=d,g.state=p,g.context=n,d=l):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=256),d=!1);return Ie(a,b,c,d,f,e)}function Ie(a,b,c,d,e,f){qh(a,b);var g=0!==(b.effectTag&64);if(!d&&!g)return e&&Hg(b,c,!1),sa(a,b,f);d=b.stateNode;gj.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?
null:d.render();b.effectTag|=1;null!==a&&g?(b.child=wb(b,a.child,null,f),b.child=wb(b,null,h,f)):T(a,b,h,f);b.memoizedState=d.state;e&&Hg(b,c,!0);return b.child}function sh(a){var b=a.stateNode;b.pendingContext?Fg(a,b.pendingContext,b.pendingContext!==b.context):b.context&&Fg(a,b.context,!1);se(a,b.containerInfo)}function th(a,b,c){var d=b.mode,e=b.pendingProps,f=D.current,g=!1,h;(h=0!==(b.effectTag&64))||(h=0!==(f&2)&&(null===a||null!==a.memoizedState));h?(g=!0,b.effectTag&=-65):null!==a&&null===
a.memoizedState||void 0===e.fallback||!0===e.unstable_avoidThisFallback||(f|=1);y(D,f&1);if(null===a){void 0!==e.fallback&&De(b);if(g){g=e.fallback;e=Ha(null,d,0,null);e.return=b;if(0===(b.mode&2))for(a=null!==b.memoizedState?b.child.child:b.child,e.child=a;null!==a;)a.return=e,a=a.sibling;c=Ha(g,d,c,null);c.return=b;e.sibling=c;b.memoizedState=Je;b.child=e;return c}d=e.children;b.memoizedState=null;return b.child=Fe(b,null,d,c)}if(null!==a.memoizedState){a=a.child;d=a.sibling;if(g){e=e.fallback;
c=Sa(a,a.pendingProps);c.return=b;if(0===(b.mode&2)&&(g=null!==b.memoizedState?b.child.child:b.child,g!==a.child))for(c.child=g;null!==g;)g.return=c,g=g.sibling;d=Sa(d,e);d.return=b;c.sibling=d;c.childExpirationTime=0;b.memoizedState=Je;b.child=c;return d}c=wb(b,a.child,e.children,c);b.memoizedState=null;return b.child=c}a=a.child;if(g){g=e.fallback;e=Ha(null,d,0,null);e.return=b;e.child=a;null!==a&&(a.return=e);if(0===(b.mode&2))for(a=null!==b.memoizedState?b.child.child:b.child,e.child=a;null!==
a;)a.return=e,a=a.sibling;c=Ha(g,d,c,null);c.return=b;e.sibling=c;c.effectTag|=2;e.childExpirationTime=0;b.memoizedState=Je;b.child=e;return c}b.memoizedState=null;return b.child=wb(b,a,e.children,c)}function uh(a,b){a.expirationTime<b&&(a.expirationTime=b);var c=a.alternate;null!==c&&c.expirationTime<b&&(c.expirationTime=b);Sg(a.return,b)}function Ke(a,b,c,d,e,f){var g=a.memoizedState;null===g?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailExpiration:0,tailMode:e,
lastEffect:f}:(g.isBackwards=b,g.rendering=null,g.renderingStartTime=0,g.last=d,g.tail=c,g.tailExpiration=0,g.tailMode=e,g.lastEffect=f)}function vh(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;T(a,b,d.children,c);d=D.current;if(0!==(d&2))d=d&1|2,b.effectTag|=64;else{if(null!==a&&0!==(a.effectTag&64))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&&uh(a,c);else if(19===a.tag)uh(a,c);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===
a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(D,d);if(0===(b.mode&2))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===Rc(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ke(b,!1,e,c,f,b.lastEffect);break;case "backwards":c=null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===Rc(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ke(b,
!0,c,null,f,b.lastEffect);break;case "together":Ke(b,!1,null,null,void 0,b.lastEffect);break;default:b.memoizedState=null}return b.child}function sa(a,b,c){null!==a&&(b.dependencies=a.dependencies);var d=b.expirationTime;0!==d&&Kc(d);if(b.childExpirationTime<c)return null;if(null!==a&&b.child!==a.child)throw Error(k(153));if(null!==b.child){a=b.child;c=Sa(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=Sa(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}
function $c(a,b){switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!==b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function hj(a,b,c){var d=b.pendingProps;switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:return N(b.type)&&(q(G),q(B)),
null;case 3:return tb(),q(G),q(B),c=b.stateNode,c.pendingContext&&(c.context=c.pendingContext,c.pendingContext=null),null!==a&&null!==a.child||!Zc(b)||(b.effectTag|=4),wh(b),null;case 5:te(b);c=Ta(Tb.current);var e=b.type;if(null!==a&&null!=b.stateNode)ij(a,b,e,d,c),a.ref!==b.ref&&(b.effectTag|=128);else{if(!d){if(null===b.stateNode)throw Error(k(166));return null}a=Ta(ja.current);if(Zc(b)){d=b.stateNode;e=b.type;var f=b.memoizedProps;d[Aa]=b;d[vc]=f;switch(e){case "iframe":case "object":case "embed":w("load",
d);break;case "video":case "audio":for(a=0;a<Db.length;a++)w(Db[a],d);break;case "source":w("error",d);break;case "img":case "image":case "link":w("error",d);w("load",d);break;case "form":w("reset",d);w("submit",d);break;case "details":w("toggle",d);break;case "input":Hf(d,f);w("invalid",d);oa(c,"onChange");break;case "select":d._wrapperState={wasMultiple:!!f.multiple};w("invalid",d);oa(c,"onChange");break;case "textarea":Kf(d,f),w("invalid",d),oa(c,"onChange")}Ud(e,f);a=null;for(var g in f)if(f.hasOwnProperty(g)){var h=
f[g];"children"===g?"string"===typeof h?d.textContent!==h&&(a=["children",h]):"number"===typeof h&&d.textContent!==""+h&&(a=["children",""+h]):db.hasOwnProperty(g)&&null!=h&&oa(c,g)}switch(e){case "input":mc(d);Jf(d,f,!0);break;case "textarea":mc(d);Mf(d);break;case "select":case "option":break;default:"function"===typeof f.onClick&&(d.onclick=uc)}c=a;b.updateQueue=c;null!==c&&(b.effectTag|=4)}else{g=9===c.nodeType?c:c.ownerDocument;"http://www.w3.org/1999/xhtml"===a&&(a=Nf(e));"http://www.w3.org/1999/xhtml"===
a?"script"===e?(a=g.createElement("div"),a.innerHTML="<script>\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(e,{is:d.is}):(a=g.createElement(e),"select"===e&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,e);a[Aa]=b;a[vc]=d;jj(a,b,!1,!1);b.stateNode=a;g=Vd(e,d);switch(e){case "iframe":case "object":case "embed":w("load",a);h=d;break;case "video":case "audio":for(h=0;h<Db.length;h++)w(Db[h],a);h=d;break;case "source":w("error",a);
h=d;break;case "img":case "image":case "link":w("error",a);w("load",a);h=d;break;case "form":w("reset",a);w("submit",a);h=d;break;case "details":w("toggle",a);h=d;break;case "input":Hf(a,d);h=Cd(a,d);w("invalid",a);oa(c,"onChange");break;case "option":h=Fd(a,d);break;case "select":a._wrapperState={wasMultiple:!!d.multiple};h=M({},d,{value:void 0});w("invalid",a);oa(c,"onChange");break;case "textarea":Kf(a,d);h=Gd(a,d);w("invalid",a);oa(c,"onChange");break;default:h=d}Ud(e,h);var m=h;for(f in m)if(m.hasOwnProperty(f)){var n=
m[f];"style"===f?gg(a,n):"dangerouslySetInnerHTML"===f?(n=n?n.__html:void 0,null!=n&&xh(a,n)):"children"===f?"string"===typeof n?("textarea"!==e||""!==n)&&Wb(a,n):"number"===typeof n&&Wb(a,""+n):"suppressContentEditableWarning"!==f&&"suppressHydrationWarning"!==f&&"autoFocus"!==f&&(db.hasOwnProperty(f)?null!=n&&oa(c,f):null!=n&&xd(a,f,n,g))}switch(e){case "input":mc(a);Jf(a,d,!1);break;case "textarea":mc(a);Mf(a);break;case "option":null!=d.value&&a.setAttribute("value",""+va(d.value));break;case "select":a.multiple=
!!d.multiple;c=d.value;null!=c?hb(a,!!d.multiple,c,!1):null!=d.defaultValue&&hb(a,!!d.multiple,d.defaultValue,!0);break;default:"function"===typeof h.onClick&&(a.onclick=uc)}lg(e,d)&&(b.effectTag|=4)}null!==b.ref&&(b.effectTag|=128)}return null;case 6:if(a&&null!=b.stateNode)kj(a,b,a.memoizedProps,d);else{if("string"!==typeof d&&null===b.stateNode)throw Error(k(166));c=Ta(Tb.current);Ta(ja.current);Zc(b)?(c=b.stateNode,d=b.memoizedProps,c[Aa]=b,c.nodeValue!==d&&(b.effectTag|=4)):(c=(9===c.nodeType?
c:c.ownerDocument).createTextNode(d),c[Aa]=b,b.stateNode=c)}return null;case 13:q(D);d=b.memoizedState;if(0!==(b.effectTag&64))return b.expirationTime=c,b;c=null!==d;d=!1;null===a?void 0!==b.memoizedProps.fallback&&Zc(b):(e=a.memoizedState,d=null!==e,c||null===e||(e=a.child.sibling,null!==e&&(f=b.firstEffect,null!==f?(b.firstEffect=e,e.nextEffect=f):(b.firstEffect=b.lastEffect=e,e.nextEffect=null),e.effectTag=8)));if(c&&!d&&0!==(b.mode&2))if(null===a&&!0!==b.memoizedProps.unstable_avoidThisFallback||
0!==(D.current&1))F===Xa&&(F=ad);else{if(F===Xa||F===ad)F=bd;0!==Xb&&null!==U&&(Ya(U,P),yh(U,Xb))}if(c||d)b.effectTag|=4;return null;case 4:return tb(),wh(b),null;case 10:return me(b),null;case 17:return N(b.type)&&(q(G),q(B)),null;case 19:q(D);d=b.memoizedState;if(null===d)return null;e=0!==(b.effectTag&64);f=d.rendering;if(null===f)if(e)$c(d,!1);else{if(F!==Xa||null!==a&&0!==(a.effectTag&64))for(f=b.child;null!==f;){a=Rc(f);if(null!==a){b.effectTag|=64;$c(d,!1);e=a.updateQueue;null!==e&&(b.updateQueue=
e,b.effectTag|=4);null===d.lastEffect&&(b.firstEffect=null);b.lastEffect=d.lastEffect;for(d=b.child;null!==d;)e=d,f=c,e.effectTag&=2,e.nextEffect=null,e.firstEffect=null,e.lastEffect=null,a=e.alternate,null===a?(e.childExpirationTime=0,e.expirationTime=f,e.child=null,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null):(e.childExpirationTime=a.childExpirationTime,e.expirationTime=a.expirationTime,e.child=a.child,e.memoizedProps=a.memoizedProps,e.memoizedState=a.memoizedState,
e.updateQueue=a.updateQueue,f=a.dependencies,e.dependencies=null===f?null:{expirationTime:f.expirationTime,firstContext:f.firstContext,responders:f.responders}),d=d.sibling;y(D,D.current&1|2);return b.child}f=f.sibling}}else{if(!e)if(a=Rc(f),null!==a){if(b.effectTag|=64,e=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.effectTag|=4),$c(d,!0),null===d.tail&&"hidden"===d.tailMode&&!f.alternate)return b=b.lastEffect=d.lastEffect,null!==b&&(b.nextEffect=null),null}else 2*Y()-d.renderingStartTime>d.tailExpiration&&
1<c&&(b.effectTag|=64,e=!0,$c(d,!1),b.expirationTime=b.childExpirationTime=c-1);d.isBackwards?(f.sibling=b.child,b.child=f):(c=d.last,null!==c?c.sibling=f:b.child=f,d.last=f)}return null!==d.tail?(0===d.tailExpiration&&(d.tailExpiration=Y()+500),c=d.tail,d.rendering=c,d.tail=c.sibling,d.lastEffect=b.lastEffect,d.renderingStartTime=Y(),c.sibling=null,b=D.current,y(D,e?b&1|2:b&1),c):null}throw Error(k(156,b.tag));}function lj(a,b){switch(a.tag){case 1:return N(a.type)&&(q(G),q(B)),b=a.effectTag,b&4096?
(a.effectTag=b&-4097|64,a):null;case 3:tb();q(G);q(B);b=a.effectTag;if(0!==(b&64))throw Error(k(285));a.effectTag=b&-4097|64;return a;case 5:return te(a),null;case 13:return q(D),b=a.effectTag,b&4096?(a.effectTag=b&-4097|64,a):null;case 19:return q(D),null;case 4:return tb(),null;case 10:return me(a),null;default:return null}}function Le(a,b){return{value:a,source:b,stack:Bd(b)}}function Me(a,b){var c=b.source,d=b.stack;null===d&&null!==c&&(d=Bd(c));null!==c&&na(c.type);b=b.value;null!==a&&1===a.tag&&
na(a.type);try{console.error(b)}catch(e){setTimeout(function(){throw e;})}}function mj(a,b){try{b.props=a.memoizedProps,b.state=a.memoizedState,b.componentWillUnmount()}catch(c){Za(a,c)}}function zh(a){var b=a.ref;if(null!==b)if("function"===typeof b)try{b(null)}catch(c){Za(a,c)}else b.current=null}function nj(a,b){switch(b.tag){case 0:case 11:case 15:case 22:return;case 1:if(b.effectTag&256&&null!==a){var c=a.memoizedProps,d=a.memoizedState;a=b.stateNode;b=a.getSnapshotBeforeUpdate(b.elementType===
b.type?c:aa(b.type,c),d);a.__reactInternalSnapshotBeforeUpdate=b}return;case 3:case 5:case 6:case 4:case 17:return}throw Error(k(163));}function Ah(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.destroy;c.destroy=void 0;void 0!==d&&d()}c=c.next}while(c!==b)}}function Bh(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function oj(a,b,c,d){switch(c.tag){case 0:case 11:case 15:case 22:Bh(3,
c);return;case 1:a=c.stateNode;c.effectTag&4&&(null===b?a.componentDidMount():(d=c.elementType===c.type?b.memoizedProps:aa(c.type,b.memoizedProps),a.componentDidUpdate(d,b.memoizedState,a.__reactInternalSnapshotBeforeUpdate)));b=c.updateQueue;null!==b&&Wg(c,b,a);return;case 3:b=c.updateQueue;if(null!==b){a=null;if(null!==c.child)switch(c.child.tag){case 5:a=c.child.stateNode;break;case 1:a=c.child.stateNode}Wg(c,b,a)}return;case 5:a=c.stateNode;null===b&&c.effectTag&4&&lg(c.type,c.memoizedProps)&&
a.focus();return;case 6:return;case 4:return;case 12:return;case 13:null===c.memoizedState&&(c=c.alternate,null!==c&&(c=c.memoizedState,null!==c&&(c=c.dehydrated,null!==c&&bg(c))));return;case 19:case 17:case 20:case 21:return}throw Error(k(163));}function Ch(a,b,c){"function"===typeof Ne&&Ne(b);switch(b.tag){case 0:case 11:case 14:case 15:case 22:a=b.updateQueue;if(null!==a&&(a=a.lastEffect,null!==a)){var d=a.next;Da(97<c?97:c,function(){var a=d;do{var c=a.destroy;if(void 0!==c){var g=b;try{c()}catch(h){Za(g,
h)}}a=a.next}while(a!==d)})}break;case 1:zh(b);c=b.stateNode;"function"===typeof c.componentWillUnmount&&mj(b,c);break;case 5:zh(b);break;case 4:Dh(a,b,c)}}function Eh(a){var b=a.alternate;a.return=null;a.child=null;a.memoizedState=null;a.updateQueue=null;a.dependencies=null;a.alternate=null;a.firstEffect=null;a.lastEffect=null;a.pendingProps=null;a.memoizedProps=null;a.stateNode=null;null!==b&&Eh(b)}function Fh(a){return 5===a.tag||3===a.tag||4===a.tag}function Gh(a){a:{for(var b=a.return;null!==
b;){if(Fh(b)){var c=b;break a}b=b.return}throw Error(k(160));}b=c.stateNode;switch(c.tag){case 5:var d=!1;break;case 3:b=b.containerInfo;d=!0;break;case 4:b=b.containerInfo;d=!0;break;default:throw Error(k(161));}c.effectTag&16&&(Wb(b,""),c.effectTag&=-17);a:b:for(c=a;;){for(;null===c.sibling;){if(null===c.return||Fh(c.return)){c=null;break a}c=c.return}c.sibling.return=c.return;for(c=c.sibling;5!==c.tag&&6!==c.tag&&18!==c.tag;){if(c.effectTag&2)continue b;if(null===c.child||4===c.tag)continue b;
else c.child.return=c,c=c.child}if(!(c.effectTag&2)){c=c.stateNode;break a}}d?Oe(a,c,b):Pe(a,c,b)}function Oe(a,b,c){var d=a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=uc));else if(4!==d&&(a=a.child,null!==a))for(Oe(a,b,c),a=a.sibling;null!==a;)Oe(a,b,c),a=a.sibling}
function Pe(a,b,c){var d=a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?c.insertBefore(a,b):c.appendChild(a);else if(4!==d&&(a=a.child,null!==a))for(Pe(a,b,c),a=a.sibling;null!==a;)Pe(a,b,c),a=a.sibling}function Dh(a,b,c){for(var d=b,e=!1,f,g;;){if(!e){e=d.return;a:for(;;){if(null===e)throw Error(k(160));f=e.stateNode;switch(e.tag){case 5:g=!1;break a;case 3:f=f.containerInfo;g=!0;break a;case 4:f=f.containerInfo;g=!0;break a}e=e.return}e=!0}if(5===d.tag||6===d.tag){a:for(var h=
a,m=d,n=c,l=m;;)if(Ch(h,l,n),null!==l.child&&4!==l.tag)l.child.return=l,l=l.child;else{if(l===m)break a;for(;null===l.sibling;){if(null===l.return||l.return===m)break a;l=l.return}l.sibling.return=l.return;l=l.sibling}g?(h=f,m=d.stateNode,8===h.nodeType?h.parentNode.removeChild(m):h.removeChild(m)):f.removeChild(d.stateNode)}else if(4===d.tag){if(null!==d.child){f=d.stateNode.containerInfo;g=!0;d.child.return=d;d=d.child;continue}}else if(Ch(a,d,c),null!==d.child){d.child.return=d;d=d.child;continue}if(d===
b)break;for(;null===d.sibling;){if(null===d.return||d.return===b)return;d=d.return;4===d.tag&&(e=!1)}d.sibling.return=d.return;d=d.sibling}}function Qe(a,b){switch(b.tag){case 0:case 11:case 14:case 15:case 22:Ah(3,b);return;case 1:return;case 5:var c=b.stateNode;if(null!=c){var d=b.memoizedProps,e=null!==a?a.memoizedProps:d;a=b.type;var f=b.updateQueue;b.updateQueue=null;if(null!==f){c[vc]=d;"input"===a&&"radio"===d.type&&null!=d.name&&If(c,d);Vd(a,e);b=Vd(a,d);for(e=0;e<f.length;e+=2){var g=f[e],
h=f[e+1];"style"===g?gg(c,h):"dangerouslySetInnerHTML"===g?xh(c,h):"children"===g?Wb(c,h):xd(c,g,h,b)}switch(a){case "input":Dd(c,d);break;case "textarea":Lf(c,d);break;case "select":b=c._wrapperState.wasMultiple,c._wrapperState.wasMultiple=!!d.multiple,a=d.value,null!=a?hb(c,!!d.multiple,a,!1):b!==!!d.multiple&&(null!=d.defaultValue?hb(c,!!d.multiple,d.defaultValue,!0):hb(c,!!d.multiple,d.multiple?[]:"",!1))}}}return;case 6:if(null===b.stateNode)throw Error(k(162));b.stateNode.nodeValue=b.memoizedProps;
return;case 3:b=b.stateNode;b.hydrate&&(b.hydrate=!1,bg(b.containerInfo));return;case 12:return;case 13:c=b;null===b.memoizedState?d=!1:(d=!0,c=b.child,Re=Y());if(null!==c)a:for(a=c;;){if(5===a.tag)f=a.stateNode,d?(f=f.style,"function"===typeof f.setProperty?f.setProperty("display","none","important"):f.display="none"):(f=a.stateNode,e=a.memoizedProps.style,e=void 0!==e&&null!==e&&e.hasOwnProperty("display")?e.display:null,f.style.display=fg("display",e));else if(6===a.tag)a.stateNode.nodeValue=d?
"":a.memoizedProps;else if(13===a.tag&&null!==a.memoizedState&&null===a.memoizedState.dehydrated){f=a.child.sibling;f.return=a;a=f;continue}else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===c)break;for(;null===a.sibling;){if(null===a.return||a.return===c)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}Hh(b);return;case 19:Hh(b);return;case 17:return}throw Error(k(163));}function Hh(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=
new pj);b.forEach(function(b){var d=qj.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Ih(a,b,c){c=Ea(c,null);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){cd||(cd=!0,Se=d);Me(a,b)};return c}function Jh(a,b,c){c=Ea(c,null);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){Me(a,b);return d(e)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){"function"!==typeof d&&
(null===La?La=new Set([this]):La.add(this),Me(a,b));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ka(){return(p&(ca|ma))!==H?1073741821-(Y()/10|0):0!==dd?dd:dd=1073741821-(Y()/10|0)}function Va(a,b,c){b=b.mode;if(0===(b&2))return 1073741823;var d=Cc();if(0===(b&4))return 99===d?1073741823:1073741822;if((p&ca)!==H)return P;if(null!==c)a=Fc(a,c.timeoutMs|0||5E3,250);else switch(d){case 99:a=1073741823;break;case 98:a=Fc(a,150,100);break;case 97:case 96:a=
Fc(a,5E3,250);break;case 95:a=2;break;default:throw Error(k(326));}null!==U&&a===P&&--a;return a}function ed(a,b){a.expirationTime<b&&(a.expirationTime=b);var c=a.alternate;null!==c&&c.expirationTime<b&&(c.expirationTime=b);var d=a.return,e=null;if(null===d&&3===a.tag)e=a.stateNode;else for(;null!==d;){c=d.alternate;d.childExpirationTime<b&&(d.childExpirationTime=b);null!==c&&c.childExpirationTime<b&&(c.childExpirationTime=b);if(null===d.return&&3===d.tag){e=d.stateNode;break}d=d.return}null!==e&&
(U===e&&(Kc(b),F===bd&&Ya(e,P)),yh(e,b));return e}function fd(a){var b=a.lastExpiredTime;if(0!==b)return b;b=a.firstPendingTime;if(!Kh(a,b))return b;var c=a.lastPingedTime;a=a.nextKnownPendingLevel;a=c>a?c:a;return 2>=a&&b!==a?0:a}function V(a){if(0!==a.lastExpiredTime)a.callbackExpirationTime=1073741823,a.callbackPriority=99,a.callbackNode=Og(Te.bind(null,a));else{var b=fd(a),c=a.callbackNode;if(0===b)null!==c&&(a.callbackNode=null,a.callbackExpirationTime=0,a.callbackPriority=90);else{var d=ka();
1073741823===b?d=99:1===b||2===b?d=95:(d=10*(1073741821-b)-10*(1073741821-d),d=0>=d?99:250>=d?98:5250>=d?97:95);if(null!==c){var e=a.callbackPriority;if(a.callbackExpirationTime===b&&e>=d)return;c!==Qg&&Rg(c)}a.callbackExpirationTime=b;a.callbackPriority=d;b=1073741823===b?Og(Te.bind(null,a)):Ng(d,Lh.bind(null,a),{timeout:10*(1073741821-b)-Y()});a.callbackNode=b}}}function Lh(a,b){dd=0;if(b)return b=ka(),Ue(a,b),V(a),null;var c=fd(a);if(0!==c){b=a.callbackNode;if((p&(ca|ma))!==H)throw Error(k(327));
xb();a===U&&c===P||$a(a,c);if(null!==t){var d=p;p|=ca;var e=Mh();do try{rj();break}catch(h){Nh(a,h)}while(1);le();p=d;gd.current=e;if(F===hd)throw b=id,$a(a,c),Ya(a,c),V(a),b;if(null===t)switch(e=a.finishedWork=a.current.alternate,a.finishedExpirationTime=c,d=F,U=null,d){case Xa:case hd:throw Error(k(345));case Oh:Ue(a,2<c?2:c);break;case ad:Ya(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Ve(e));if(1073741823===ta&&(e=Re+Ph-Y(),10<e)){if(jd){var f=a.lastPingedTime;if(0===f||f>=c){a.lastPingedTime=
c;$a(a,c);break}}f=fd(a);if(0!==f&&f!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}a.timeoutHandle=We(ab.bind(null,a),e);break}ab(a);break;case bd:Ya(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Ve(e));if(jd&&(e=a.lastPingedTime,0===e||e>=c)){a.lastPingedTime=c;$a(a,c);break}e=fd(a);if(0!==e&&e!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}1073741823!==Yb?d=10*(1073741821-Yb)-Y():1073741823===ta?d=0:(d=10*(1073741821-ta)-5E3,e=Y(),c=10*(1073741821-c)-e,d=e-d,0>d&&(d=0),d=
(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*sj(d/1960))-d,c<d&&(d=c));if(10<d){a.timeoutHandle=We(ab.bind(null,a),d);break}ab(a);break;case Xe:if(1073741823!==ta&&null!==kd){f=ta;var g=kd;d=g.busyMinDurationMs|0;0>=d?d=0:(e=g.busyDelayMs|0,f=Y()-(10*(1073741821-f)-(g.timeoutMs|0||5E3)),d=f<=e?0:e+d-f);if(10<d){Ya(a,c);a.timeoutHandle=We(ab.bind(null,a),d);break}}ab(a);break;default:throw Error(k(329));}V(a);if(a.callbackNode===b)return Lh.bind(null,a)}}return null}function Te(a){var b=
a.lastExpiredTime;b=0!==b?b:1073741823;if((p&(ca|ma))!==H)throw Error(k(327));xb();a===U&&b===P||$a(a,b);if(null!==t){var c=p;p|=ca;var d=Mh();do try{tj();break}catch(e){Nh(a,e)}while(1);le();p=c;gd.current=d;if(F===hd)throw c=id,$a(a,b),Ya(a,b),V(a),c;if(null!==t)throw Error(k(261));a.finishedWork=a.current.alternate;a.finishedExpirationTime=b;U=null;ab(a);V(a)}return null}function uj(){if(null!==bb){var a=bb;bb=null;a.forEach(function(a,c){Ue(c,a);V(c)});ha()}}function Qh(a,b){var c=p;p|=1;try{return a(b)}finally{p=
c,p===H&&ha()}}function Rh(a,b){var c=p;p&=-2;p|=Ye;try{return a(b)}finally{p=c,p===H&&ha()}}function $a(a,b){a.finishedWork=null;a.finishedExpirationTime=0;var c=a.timeoutHandle;-1!==c&&(a.timeoutHandle=-1,vj(c));if(null!==t)for(c=t.return;null!==c;){var d=c;switch(d.tag){case 1:d=d.type.childContextTypes;null!==d&&void 0!==d&&(q(G),q(B));break;case 3:tb();q(G);q(B);break;case 5:te(d);break;case 4:tb();break;case 13:q(D);break;case 19:q(D);break;case 10:me(d)}c=c.return}U=a;t=Sa(a.current,null);
P=b;F=Xa;id=null;Yb=ta=1073741823;kd=null;Xb=0;jd=!1}function Nh(a,b){do{try{le();Sc.current=Tc;if(Uc)for(var c=z.memoizedState;null!==c;){var d=c.queue;null!==d&&(d.pending=null);c=c.next}Ia=0;J=K=z=null;Uc=!1;if(null===t||null===t.return)return F=hd,id=b,t=null;a:{var e=a,f=t.return,g=t,h=b;b=P;g.effectTag|=2048;g.firstEffect=g.lastEffect=null;if(null!==h&&"object"===typeof h&&"function"===typeof h.then){var m=h;if(0===(g.mode&2)){var n=g.alternate;n?(g.updateQueue=n.updateQueue,g.memoizedState=
n.memoizedState,g.expirationTime=n.expirationTime):(g.updateQueue=null,g.memoizedState=null)}var l=0!==(D.current&1),k=f;do{var p;if(p=13===k.tag){var q=k.memoizedState;if(null!==q)p=null!==q.dehydrated?!0:!1;else{var w=k.memoizedProps;p=void 0===w.fallback?!1:!0!==w.unstable_avoidThisFallback?!0:l?!1:!0}}if(p){var y=k.updateQueue;if(null===y){var r=new Set;r.add(m);k.updateQueue=r}else y.add(m);if(0===(k.mode&2)){k.effectTag|=64;g.effectTag&=-2981;if(1===g.tag)if(null===g.alternate)g.tag=17;else{var O=
Ea(1073741823,null);O.tag=Jc;Fa(g,O)}g.expirationTime=1073741823;break a}h=void 0;g=b;var v=e.pingCache;null===v?(v=e.pingCache=new wj,h=new Set,v.set(m,h)):(h=v.get(m),void 0===h&&(h=new Set,v.set(m,h)));if(!h.has(g)){h.add(g);var x=xj.bind(null,e,m,g);m.then(x,x)}k.effectTag|=4096;k.expirationTime=b;break a}k=k.return}while(null!==k);h=Error((na(g.type)||"A React component")+" suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."+
Bd(g))}F!==Xe&&(F=Oh);h=Le(h,g);k=f;do{switch(k.tag){case 3:m=h;k.effectTag|=4096;k.expirationTime=b;var A=Ih(k,m,b);Ug(k,A);break a;case 1:m=h;var u=k.type,B=k.stateNode;if(0===(k.effectTag&64)&&("function"===typeof u.getDerivedStateFromError||null!==B&&"function"===typeof B.componentDidCatch&&(null===La||!La.has(B)))){k.effectTag|=4096;k.expirationTime=b;var H=Jh(k,m,b);Ug(k,H);break a}}k=k.return}while(null!==k)}t=Sh(t)}catch(cj){b=cj;continue}break}while(1)}function Mh(a){a=gd.current;gd.current=
Tc;return null===a?Tc:a}function Vg(a,b){a<ta&&2<a&&(ta=a);null!==b&&a<Yb&&2<a&&(Yb=a,kd=b)}function Kc(a){a>Xb&&(Xb=a)}function tj(){for(;null!==t;)t=Th(t)}function rj(){for(;null!==t&&!yj();)t=Th(t)}function Th(a){var b=zj(a.alternate,a,P);a.memoizedProps=a.pendingProps;null===b&&(b=Sh(a));Uh.current=null;return b}function Sh(a){t=a;do{var b=t.alternate;a=t.return;if(0===(t.effectTag&2048)){b=hj(b,t,P);if(1===P||1!==t.childExpirationTime){for(var c=0,d=t.child;null!==d;){var e=d.expirationTime,
f=d.childExpirationTime;e>c&&(c=e);f>c&&(c=f);d=d.sibling}t.childExpirationTime=c}if(null!==b)return b;null!==a&&0===(a.effectTag&2048)&&(null===a.firstEffect&&(a.firstEffect=t.firstEffect),null!==t.lastEffect&&(null!==a.lastEffect&&(a.lastEffect.nextEffect=t.firstEffect),a.lastEffect=t.lastEffect),1<t.effectTag&&(null!==a.lastEffect?a.lastEffect.nextEffect=t:a.firstEffect=t,a.lastEffect=t))}else{b=lj(t);if(null!==b)return b.effectTag&=2047,b;null!==a&&(a.firstEffect=a.lastEffect=null,a.effectTag|=
2048)}b=t.sibling;if(null!==b)return b;t=a}while(null!==t);F===Xa&&(F=Xe);return null}function Ve(a){var b=a.expirationTime;a=a.childExpirationTime;return b>a?b:a}function ab(a){var b=Cc();Da(99,Aj.bind(null,a,b));return null}function Aj(a,b){do xb();while(null!==Zb);if((p&(ca|ma))!==H)throw Error(k(327));var c=a.finishedWork,d=a.finishedExpirationTime;if(null===c)return null;a.finishedWork=null;a.finishedExpirationTime=0;if(c===a.current)throw Error(k(177));a.callbackNode=null;a.callbackExpirationTime=
0;a.callbackPriority=90;a.nextKnownPendingLevel=0;var e=Ve(c);a.firstPendingTime=e;d<=a.lastSuspendedTime?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:d<=a.firstSuspendedTime&&(a.firstSuspendedTime=d-1);d<=a.lastPingedTime&&(a.lastPingedTime=0);d<=a.lastExpiredTime&&(a.lastExpiredTime=0);a===U&&(t=U=null,P=0);1<c.effectTag?null!==c.lastEffect?(c.lastEffect.nextEffect=c,e=c.firstEffect):e=c:e=c.firstEffect;if(null!==e){var f=p;p|=ma;Uh.current=null;Ze=tc;var g=kg();if(Xd(g)){if("selectionStart"in
g)var h={start:g.selectionStart,end:g.selectionEnd};else a:{h=(h=g.ownerDocument)&&h.defaultView||window;var m=h.getSelection&&h.getSelection();if(m&&0!==m.rangeCount){h=m.anchorNode;var n=m.anchorOffset,q=m.focusNode;m=m.focusOffset;try{h.nodeType,q.nodeType}catch(sb){h=null;break a}var ba=0,w=-1,y=-1,B=0,D=0,r=g,z=null;b:for(;;){for(var v;;){r!==h||0!==n&&3!==r.nodeType||(w=ba+n);r!==q||0!==m&&3!==r.nodeType||(y=ba+m);3===r.nodeType&&(ba+=r.nodeValue.length);if(null===(v=r.firstChild))break;z=r;
r=v}for(;;){if(r===g)break b;z===h&&++B===n&&(w=ba);z===q&&++D===m&&(y=ba);if(null!==(v=r.nextSibling))break;r=z;z=r.parentNode}r=v}h=-1===w||-1===y?null:{start:w,end:y}}else h=null}h=h||{start:0,end:0}}else h=null;$e={activeElementDetached:null,focusedElem:g,selectionRange:h};tc=!1;l=e;do try{Bj()}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);l=e;do try{for(g=a,h=b;null!==l;){var x=l.effectTag;x&16&&Wb(l.stateNode,"");if(x&128){var A=l.alternate;if(null!==A){var u=
A.ref;null!==u&&("function"===typeof u?u(null):u.current=null)}}switch(x&1038){case 2:Gh(l);l.effectTag&=-3;break;case 6:Gh(l);l.effectTag&=-3;Qe(l.alternate,l);break;case 1024:l.effectTag&=-1025;break;case 1028:l.effectTag&=-1025;Qe(l.alternate,l);break;case 4:Qe(l.alternate,l);break;case 8:n=l,Dh(g,n,h),Eh(n)}l=l.nextEffect}}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);u=$e;A=kg();x=u.focusedElem;h=u.selectionRange;if(A!==x&&x&&x.ownerDocument&&jg(x.ownerDocument.documentElement,
x)){null!==h&&Xd(x)&&(A=h.start,u=h.end,void 0===u&&(u=A),"selectionStart"in x?(x.selectionStart=A,x.selectionEnd=Math.min(u,x.value.length)):(u=(A=x.ownerDocument||document)&&A.defaultView||window,u.getSelection&&(u=u.getSelection(),n=x.textContent.length,g=Math.min(h.start,n),h=void 0===h.end?g:Math.min(h.end,n),!u.extend&&g>h&&(n=h,h=g,g=n),n=ig(x,g),q=ig(x,h),n&&q&&(1!==u.rangeCount||u.anchorNode!==n.node||u.anchorOffset!==n.offset||u.focusNode!==q.node||u.focusOffset!==q.offset)&&(A=A.createRange(),
A.setStart(n.node,n.offset),u.removeAllRanges(),g>h?(u.addRange(A),u.extend(q.node,q.offset)):(A.setEnd(q.node,q.offset),u.addRange(A))))));A=[];for(u=x;u=u.parentNode;)1===u.nodeType&&A.push({element:u,left:u.scrollLeft,top:u.scrollTop});"function"===typeof x.focus&&x.focus();for(x=0;x<A.length;x++)u=A[x],u.element.scrollLeft=u.left,u.element.scrollTop=u.top}tc=!!Ze;$e=Ze=null;a.current=c;l=e;do try{for(x=a;null!==l;){var F=l.effectTag;F&36&&oj(x,l.alternate,l);if(F&128){A=void 0;var E=l.ref;if(null!==
E){var G=l.stateNode;switch(l.tag){case 5:A=G;break;default:A=G}"function"===typeof E?E(A):E.current=A}}l=l.nextEffect}}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);l=null;Cj();p=f}else a.current=c;if(ld)ld=!1,Zb=a,$b=b;else for(l=e;null!==l;)b=l.nextEffect,l.nextEffect=null,l=b;b=a.firstPendingTime;0===b&&(La=null);1073741823===b?a===af?ac++:(ac=0,af=a):ac=0;"function"===typeof bf&&bf(c.stateNode,d);V(a);if(cd)throw cd=!1,a=Se,Se=null,a;if((p&Ye)!==H)return null;
ha();return null}function Bj(){for(;null!==l;){var a=l.effectTag;0!==(a&256)&&nj(l.alternate,l);0===(a&512)||ld||(ld=!0,Ng(97,function(){xb();return null}));l=l.nextEffect}}function xb(){if(90!==$b){var a=97<$b?97:$b;$b=90;return Da(a,Dj)}}function Dj(){if(null===Zb)return!1;var a=Zb;Zb=null;if((p&(ca|ma))!==H)throw Error(k(331));var b=p;p|=ma;for(a=a.current.firstEffect;null!==a;){try{var c=a;if(0!==(c.effectTag&512))switch(c.tag){case 0:case 11:case 15:case 22:Ah(5,c),Bh(5,c)}}catch(d){if(null===
a)throw Error(k(330));Za(a,d)}c=a.nextEffect;a.nextEffect=null;a=c}p=b;ha();return!0}function Vh(a,b,c){b=Le(c,b);b=Ih(a,b,1073741823);Fa(a,b);a=ed(a,1073741823);null!==a&&V(a)}function Za(a,b){if(3===a.tag)Vh(a,a,b);else for(var c=a.return;null!==c;){if(3===c.tag){Vh(c,a,b);break}else if(1===c.tag){var d=c.stateNode;if("function"===typeof c.type.getDerivedStateFromError||"function"===typeof d.componentDidCatch&&(null===La||!La.has(d))){a=Le(b,a);a=Jh(c,a,1073741823);Fa(c,a);c=ed(c,1073741823);null!==
c&&V(c);break}}c=c.return}}function xj(a,b,c){var d=a.pingCache;null!==d&&d.delete(b);U===a&&P===c?F===bd||F===ad&&1073741823===ta&&Y()-Re<Ph?$a(a,P):jd=!0:Kh(a,c)&&(b=a.lastPingedTime,0!==b&&b<c||(a.lastPingedTime=c,V(a)))}function qj(a,b){var c=a.stateNode;null!==c&&c.delete(b);b=0;0===b&&(b=ka(),b=Va(b,a,null));a=ed(a,b);null!==a&&V(a)}function Ej(a){if("undefined"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)return!1;var b=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(b.isDisabled||!b.supportsFiber)return!0;try{var c=
b.inject(a);bf=function(a,e){try{b.onCommitFiberRoot(c,a,void 0,64===(a.current.effectTag&64))}catch(f){}};Ne=function(a){try{b.onCommitFiberUnmount(c,a)}catch(e){}}}catch(d){}return!0}function Fj(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.effectTag=0;this.lastEffect=this.firstEffect=this.nextEffect=
null;this.childExpirationTime=this.expirationTime=0;this.alternate=null}function Ge(a){a=a.prototype;return!(!a||!a.isReactComponent)}function Gj(a){if("function"===typeof a)return Ge(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===zd)return 11;if(a===Ad)return 14}return 2}function Sa(a,b){var c=a.alternate;null===c?(c=la(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.effectTag=0,c.nextEffect=null,c.firstEffect=
null,c.lastEffect=null);c.childExpirationTime=a.childExpirationTime;c.expirationTime=a.expirationTime;c.child=a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{expirationTime:b.expirationTime,firstContext:b.firstContext,responders:b.responders};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function Oc(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)Ge(a)&&(g=1);else if("string"===typeof a)g=
5;else a:switch(a){case Ma:return Ha(c.children,e,f,b);case Hj:g=8;e|=7;break;case Af:g=8;e|=1;break;case kc:return a=la(12,c,b,e|8),a.elementType=kc,a.type=kc,a.expirationTime=f,a;case lc:return a=la(13,c,b,e),a.type=lc,a.elementType=lc,a.expirationTime=f,a;case yd:return a=la(19,c,b,e),a.elementType=yd,a.expirationTime=f,a;default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case Cf:g=10;break a;case Bf:g=9;break a;case zd:g=11;break a;case Ad:g=14;break a;case Ef:g=16;d=null;break a;case Df:g=
22;break a}throw Error(k(130,null==a?a:typeof a,""));}b=la(g,c,b,e);b.elementType=a;b.type=d;b.expirationTime=f;return b}function Ha(a,b,c,d){a=la(7,a,d,b);a.expirationTime=c;return a}function qe(a,b,c){a=la(6,a,null,b);a.expirationTime=c;return a}function re(a,b,c){b=la(4,null!==a.children?a.children:[],a.key,b);b.expirationTime=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Ij(a,b,c){this.tag=b;this.current=null;this.containerInfo=
a;this.pingCache=this.pendingChildren=null;this.finishedExpirationTime=0;this.finishedWork=null;this.timeoutHandle=-1;this.pendingContext=this.context=null;this.hydrate=c;this.callbackNode=null;this.callbackPriority=90;this.lastExpiredTime=this.lastPingedTime=this.nextKnownPendingLevel=this.lastSuspendedTime=this.firstSuspendedTime=this.firstPendingTime=0}function Kh(a,b){var c=a.firstSuspendedTime;a=a.lastSuspendedTime;return 0!==c&&c>=b&&a<=b}function Ya(a,b){var c=a.firstSuspendedTime,d=a.lastSuspendedTime;
c<b&&(a.firstSuspendedTime=b);if(d>b||0===c)a.lastSuspendedTime=b;b<=a.lastPingedTime&&(a.lastPingedTime=0);b<=a.lastExpiredTime&&(a.lastExpiredTime=0)}function yh(a,b){b>a.firstPendingTime&&(a.firstPendingTime=b);var c=a.firstSuspendedTime;0!==c&&(b>=c?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:b>=a.lastSuspendedTime&&(a.lastSuspendedTime=b+1),b>a.nextKnownPendingLevel&&(a.nextKnownPendingLevel=b))}function Ue(a,b){var c=a.lastExpiredTime;if(0===c||c>b)a.lastExpiredTime=b}
function md(a,b,c,d){var e=b.current,f=ka(),g=Vb.suspense;f=Va(f,e,g);a:if(c){c=c._reactInternalFiber;b:{if(Na(c)!==c||1!==c.tag)throw Error(k(170));var h=c;do{switch(h.tag){case 3:h=h.stateNode.context;break b;case 1:if(N(h.type)){h=h.stateNode.__reactInternalMemoizedMergedChildContext;break b}}h=h.return}while(null!==h);throw Error(k(171));}if(1===c.tag){var m=c.type;if(N(m)){c=Gg(c,m,h);break a}}c=h}else c=Ca;null===b.context?b.context=c:b.pendingContext=c;b=Ea(f,g);b.payload={element:a};d=void 0===
d?null:d;null!==d&&(b.callback=d);Fa(e,b);Ja(e,f);return f}function cf(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return a.child.stateNode;default:return a.child.stateNode}}function Wh(a,b){a=a.memoizedState;null!==a&&null!==a.dehydrated&&a.retryTime<b&&(a.retryTime=b)}function df(a,b){Wh(a,b);(a=a.alternate)&&Wh(a,b)}function ef(a,b,c){c=null!=c&&!0===c.hydrate;var d=new Ij(a,b,c),e=la(3,null,null,2===b?7:1===b?3:0);d.current=e;e.stateNode=d;ne(e);a[Lb]=d.current;c&&0!==b&&
xi(a,9===a.nodeType?a:a.ownerDocument);this._internalRoot=d}function bc(a){return!(!a||1!==a.nodeType&&9!==a.nodeType&&11!==a.nodeType&&(8!==a.nodeType||" react-mount-point-unstable "!==a.nodeValue))}function Jj(a,b){b||(b=a?9===a.nodeType?a.documentElement:a.firstChild:null,b=!(!b||1!==b.nodeType||!b.hasAttribute("data-reactroot")));if(!b)for(var c;c=a.lastChild;)a.removeChild(c);return new ef(a,0,b?{hydrate:!0}:void 0)}function nd(a,b,c,d,e){var f=c._reactRootContainer;if(f){var g=f._internalRoot;
if("function"===typeof e){var h=e;e=function(){var a=cf(g);h.call(a)}}md(b,g,a,e)}else{f=c._reactRootContainer=Jj(c,d);g=f._internalRoot;if("function"===typeof e){var m=e;e=function(){var a=cf(g);m.call(a)}}Rh(function(){md(b,g,a,e)})}return cf(g)}function Kj(a,b,c){var d=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:gb,key:null==d?null:""+d,children:a,containerInfo:b,implementation:c}}function Xh(a,b){var c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;
if(!bc(b))throw Error(k(200));return Kj(a,b,null,c)}if(!ea)throw Error(k(227));var ki=function(a,b,c,d,e,f,g,h,m){var n=Array.prototype.slice.call(arguments,3);try{b.apply(c,n)}catch(C){this.onError(C)}},yb=!1,gc=null,hc=!1,pd=null,li={onError:function(a){yb=!0;gc=a}},td=null,rf=null,mf=null,ic=null,cb={},jc=[],qd={},db={},rd={},wa=!("undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement),M=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.assign,
sd=null,eb=null,fb=null,ee=function(a,b){return a(b)},eg=function(a,b,c,d,e){return a(b,c,d,e)},vd=function(){},vf=ee,Oa=!1,wd=!1,Z=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler,Lj=Z.unstable_cancelCallback,ff=Z.unstable_now,$f=Z.unstable_scheduleCallback,Mj=Z.unstable_shouldYield,Yh=Z.unstable_requestPaint,Pd=Z.unstable_runWithPriority,Nj=Z.unstable_getCurrentPriorityLevel,Oj=Z.unstable_ImmediatePriority,Zh=Z.unstable_UserBlockingPriority,ag=Z.unstable_NormalPriority,Pj=Z.unstable_LowPriority,
Qj=Z.unstable_IdlePriority,oi=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,wf=Object.prototype.hasOwnProperty,yf={},xf={},E={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(a){E[a]=
new L(a,0,!1,a,null,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(a){var b=a[0];E[b]=new L(b,1,!1,a[1],null,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(a){E[a]=new L(a,2,!1,a.toLowerCase(),null,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(a){E[a]=new L(a,2,!1,a,null,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(a){E[a]=
new L(a,3,!1,a.toLowerCase(),null,!1)});["checked","multiple","muted","selected"].forEach(function(a){E[a]=new L(a,3,!0,a,null,!1)});["capture","download"].forEach(function(a){E[a]=new L(a,4,!1,a,null,!1)});["cols","rows","size","span"].forEach(function(a){E[a]=new L(a,6,!1,a,null,!1)});["rowSpan","start"].forEach(function(a){E[a]=new L(a,5,!1,a.toLowerCase(),null,!1)});var gf=/[\-:]([a-z])/g,hf=function(a){return a[1].toUpperCase()};"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(a){var b=
a.replace(gf,hf);E[b]=new L(b,1,!1,a,null,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(a){var b=a.replace(gf,hf);E[b]=new L(b,1,!1,a,"http://www.w3.org/1999/xlink",!1)});["xml:base","xml:lang","xml:space"].forEach(function(a){var b=a.replace(gf,hf);E[b]=new L(b,1,!1,a,"http://www.w3.org/XML/1998/namespace",!1)});["tabIndex","crossOrigin"].forEach(function(a){E[a]=new L(a,1,!1,a.toLowerCase(),null,!1)});E.xlinkHref=new L("xlinkHref",1,
!1,"xlink:href","http://www.w3.org/1999/xlink",!0);["src","href","action","formAction"].forEach(function(a){E[a]=new L(a,1,!1,a.toLowerCase(),null,!0)});var da=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;da.hasOwnProperty("ReactCurrentDispatcher")||(da.ReactCurrentDispatcher={current:null});da.hasOwnProperty("ReactCurrentBatchConfig")||(da.ReactCurrentBatchConfig={suspense:null});var si=/^(.*)[\\\/]/,Q="function"===typeof Symbol&&Symbol.for,Pc=Q?Symbol.for("react.element"):60103,gb=Q?Symbol.for("react.portal"):
60106,Ma=Q?Symbol.for("react.fragment"):60107,Af=Q?Symbol.for("react.strict_mode"):60108,kc=Q?Symbol.for("react.profiler"):60114,Cf=Q?Symbol.for("react.provider"):60109,Bf=Q?Symbol.for("react.context"):60110,Hj=Q?Symbol.for("react.concurrent_mode"):60111,zd=Q?Symbol.for("react.forward_ref"):60112,lc=Q?Symbol.for("react.suspense"):60113,yd=Q?Symbol.for("react.suspense_list"):60120,Ad=Q?Symbol.for("react.memo"):60115,Ef=Q?Symbol.for("react.lazy"):60116,Df=Q?Symbol.for("react.block"):60121,zf="function"===
typeof Symbol&&Symbol.iterator,od,xh=function(a){return"undefined"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,c,d,e)})}:a}(function(a,b){if("http://www.w3.org/2000/svg"!==a.namespaceURI||"innerHTML"in a)a.innerHTML=b;else{od=od||document.createElement("div");od.innerHTML="<svg>"+b.valueOf().toString()+"</svg>";for(b=od.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}),Wb=function(a,
b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b},ib={animationend:nc("Animation","AnimationEnd"),animationiteration:nc("Animation","AnimationIteration"),animationstart:nc("Animation","AnimationStart"),transitionend:nc("Transition","TransitionEnd")},Id={},Of={};wa&&(Of=document.createElement("div").style,"AnimationEvent"in window||(delete ib.animationend.animation,delete ib.animationiteration.animation,delete ib.animationstart.animation),"TransitionEvent"in
window||delete ib.transitionend.transition);var $h=oc("animationend"),ai=oc("animationiteration"),bi=oc("animationstart"),ci=oc("transitionend"),Db="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Pf=new ("function"===typeof WeakMap?WeakMap:Map),Ab=null,wi=function(a){if(a){var b=a._dispatchListeners,c=a._dispatchInstances;
if(Array.isArray(b))for(var d=0;d<b.length&&!a.isPropagationStopped();d++)lf(a,b[d],c[d]);else b&&lf(a,b,c);a._dispatchListeners=null;a._dispatchInstances=null;a.isPersistent()||a.constructor.release(a)}},qc=[],Rd=!1,fa=[],xa=null,ya=null,za=null,Eb=new Map,Fb=new Map,Jb=[],Nd="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit".split(" "),
yi="focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture".split(" "),dg={},cg=new Map,Td=new Map,Rj=["abort","abort",$h,"animationEnd",ai,"animationIteration",bi,"animationStart","canplay","canPlay","canplaythrough","canPlayThrough","durationchange","durationChange","emptied","emptied","encrypted","encrypted","ended","ended","error","error","gotpointercapture","gotPointerCapture","load","load","loadeddata","loadedData","loadedmetadata","loadedMetadata",
"loadstart","loadStart","lostpointercapture","lostPointerCapture","playing","playing","progress","progress","seeking","seeking","stalled","stalled","suspend","suspend","timeupdate","timeUpdate",ci,"transitionEnd","waiting","waiting"];Sd("blur blur cancel cancel click click close close contextmenu contextMenu copy copy cut cut auxclick auxClick dblclick doubleClick dragend dragEnd dragstart dragStart drop drop focus focus input input invalid invalid keydown keyDown keypress keyPress keyup keyUp mousedown mouseDown mouseup mouseUp paste paste pause pause play play pointercancel pointerCancel pointerdown pointerDown pointerup pointerUp ratechange rateChange reset reset seeked seeked submit submit touchcancel touchCancel touchend touchEnd touchstart touchStart volumechange volumeChange".split(" "),
0);Sd("drag drag dragenter dragEnter dragexit dragExit dragleave dragLeave dragover dragOver mousemove mouseMove mouseout mouseOut mouseover mouseOver pointermove pointerMove pointerout pointerOut pointerover pointerOver scroll scroll toggle toggle touchmove touchMove wheel wheel".split(" "),1);Sd(Rj,2);(function(a,b){for(var c=0;c<a.length;c++)Td.set(a[c],b)})("change selectionchange textInput compositionstart compositionend compositionupdate".split(" "),0);var Hi=Zh,Gi=Pd,tc=!0,Kb={animationIterationCount:!0,
borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,
strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Sj=["Webkit","ms","Moz","O"];Object.keys(Kb).forEach(function(a){Sj.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);Kb[b]=Kb[a]})});var Ii=M({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),ng="$",og="/$",$d="$?",Zd="$!",Ze=null,$e=null,We="function"===typeof setTimeout?setTimeout:void 0,vj="function"===
typeof clearTimeout?clearTimeout:void 0,jf=Math.random().toString(36).slice(2),Aa="__reactInternalInstance$"+jf,vc="__reactEventHandlers$"+jf,Lb="__reactContainere$"+jf,Ba=null,ce=null,wc=null;M(R.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():"unknown"!==typeof a.returnValue&&(a.returnValue=!1),this.isDefaultPrevented=xc)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():"unknown"!==
typeof a.cancelBubble&&(a.cancelBubble=!0),this.isPropagationStopped=xc)},persist:function(){this.isPersistent=xc},isPersistent:yc,destructor:function(){var a=this.constructor.Interface,b;for(b in a)this[b]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null;this.isPropagationStopped=this.isDefaultPrevented=yc;this._dispatchInstances=this._dispatchListeners=null}});R.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(a){return a.timeStamp||
Date.now()},defaultPrevented:null,isTrusted:null};R.extend=function(a){function b(){return c.apply(this,arguments)}var c=this,d=function(){};d.prototype=c.prototype;d=new d;M(d,b.prototype);b.prototype=d;b.prototype.constructor=b;b.Interface=M({},c.Interface,a);b.extend=c.extend;sg(b);return b};sg(R);var Tj=R.extend({data:null}),Uj=R.extend({data:null}),Ni=[9,13,27,32],de=wa&&"CompositionEvent"in window,cc=null;wa&&"documentMode"in document&&(cc=document.documentMode);var Vj=wa&&"TextEvent"in window&&
!cc,xg=wa&&(!de||cc&&8<cc&&11>=cc),wg=String.fromCharCode(32),ua={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},
dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},vg=!1,mb=!1,Wj={eventTypes:ua,extractEvents:function(a,b,c,d,e){var f;if(de)b:{switch(a){case "compositionstart":var g=ua.compositionStart;break b;case "compositionend":g=ua.compositionEnd;break b;case "compositionupdate":g=
ua.compositionUpdate;break b}g=void 0}else mb?tg(a,c)&&(g=ua.compositionEnd):"keydown"===a&&229===c.keyCode&&(g=ua.compositionStart);g?(xg&&"ko"!==c.locale&&(mb||g!==ua.compositionStart?g===ua.compositionEnd&&mb&&(f=rg()):(Ba=d,ce="value"in Ba?Ba.value:Ba.textContent,mb=!0)),e=Tj.getPooled(g,b,c,d),f?e.data=f:(f=ug(c),null!==f&&(e.data=f)),lb(e),f=e):f=null;(a=Vj?Oi(a,c):Pi(a,c))?(b=Uj.getPooled(ua.beforeInput,b,c,d),b.data=a,lb(b)):b=null;return null===f?b:null===b?f:[f,b]}},Qi={color:!0,date:!0,
datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0},Ag={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:"blur change click focus input keydown keyup selectionchange".split(" ")}},Mb=null,Nb=null,kf=!1;wa&&(kf=Tf("input")&&(!document.documentMode||9<document.documentMode));var Xj={eventTypes:Ag,_isInputEventSupported:kf,extractEvents:function(a,b,c,d,e){e=b?Pa(b):window;var f=
e.nodeName&&e.nodeName.toLowerCase();if("select"===f||"input"===f&&"file"===e.type)var g=Si;else if(yg(e))if(kf)g=Wi;else{g=Ui;var h=Ti}else(f=e.nodeName)&&"input"===f.toLowerCase()&&("checkbox"===e.type||"radio"===e.type)&&(g=Vi);if(g&&(g=g(a,b)))return zg(g,c,d);h&&h(a,e,b);"blur"===a&&(a=e._wrapperState)&&a.controlled&&"number"===e.type&&Ed(e,"number",e.value)}},dc=R.extend({view:null,detail:null}),Yi={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"},di=0,ei=0,fi=!1,gi=!1,ec=dc.extend({screenX:null,
screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:fe,button:null,buttons:null,relatedTarget:function(a){return a.relatedTarget||(a.fromElement===a.srcElement?a.toElement:a.fromElement)},movementX:function(a){if("movementX"in a)return a.movementX;var b=di;di=a.screenX;return fi?"mousemove"===a.type?a.screenX-b:0:(fi=!0,0)},movementY:function(a){if("movementY"in a)return a.movementY;var b=ei;ei=a.screenY;return gi?"mousemove"===
a.type?a.screenY-b:0:(gi=!0,0)}}),hi=ec.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),fc={mouseEnter:{registrationName:"onMouseEnter",dependencies:["mouseout","mouseover"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["mouseout","mouseover"]},pointerEnter:{registrationName:"onPointerEnter",dependencies:["pointerout","pointerover"]},pointerLeave:{registrationName:"onPointerLeave",dependencies:["pointerout",
"pointerover"]}},Yj={eventTypes:fc,extractEvents:function(a,b,c,d,e){var f="mouseover"===a||"pointerover"===a,g="mouseout"===a||"pointerout"===a;if(f&&0===(e&32)&&(c.relatedTarget||c.fromElement)||!g&&!f)return null;f=d.window===d?d:(f=d.ownerDocument)?f.defaultView||f.parentWindow:window;if(g){if(g=b,b=(b=c.relatedTarget||c.toElement)?Bb(b):null,null!==b){var h=Na(b);if(b!==h||5!==b.tag&&6!==b.tag)b=null}}else g=null;if(g===b)return null;if("mouseout"===a||"mouseover"===a){var m=ec;var n=fc.mouseLeave;
var l=fc.mouseEnter;var k="mouse"}else if("pointerout"===a||"pointerover"===a)m=hi,n=fc.pointerLeave,l=fc.pointerEnter,k="pointer";a=null==g?f:Pa(g);f=null==b?f:Pa(b);n=m.getPooled(n,g,c,d);n.type=k+"leave";n.target=a;n.relatedTarget=f;c=m.getPooled(l,b,c,d);c.type=k+"enter";c.target=f;c.relatedTarget=a;d=g;k=b;if(d&&k)a:{m=d;l=k;g=0;for(a=m;a;a=pa(a))g++;a=0;for(b=l;b;b=pa(b))a++;for(;0<g-a;)m=pa(m),g--;for(;0<a-g;)l=pa(l),a--;for(;g--;){if(m===l||m===l.alternate)break a;m=pa(m);l=pa(l)}m=null}else m=
null;l=m;for(m=[];d&&d!==l;){g=d.alternate;if(null!==g&&g===l)break;m.push(d);d=pa(d)}for(d=[];k&&k!==l;){g=k.alternate;if(null!==g&&g===l)break;d.push(k);k=pa(k)}for(k=0;k<m.length;k++)be(m[k],"bubbled",n);for(k=d.length;0<k--;)be(d[k],"captured",c);return 0===(e&64)?[n]:[n,c]}},Qa="function"===typeof Object.is?Object.is:Zi,$i=Object.prototype.hasOwnProperty,Zj=wa&&"documentMode"in document&&11>=document.documentMode,Eg={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},
dependencies:"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange".split(" ")}},nb=null,he=null,Pb=null,ge=!1,ak={eventTypes:Eg,extractEvents:function(a,b,c,d,e,f){e=f||(d.window===d?d.document:9===d.nodeType?d:d.ownerDocument);if(!(f=!e)){a:{e=Jd(e);f=rd.onSelect;for(var g=0;g<f.length;g++)if(!e.has(f[g])){e=!1;break a}e=!0}f=!e}if(f)return null;e=b?Pa(b):window;switch(a){case "focus":if(yg(e)||"true"===e.contentEditable)nb=e,he=b,Pb=null;break;case "blur":Pb=he=nb=null;
break;case "mousedown":ge=!0;break;case "contextmenu":case "mouseup":case "dragend":return ge=!1,Dg(c,d);case "selectionchange":if(Zj)break;case "keydown":case "keyup":return Dg(c,d)}return null}},bk=R.extend({animationName:null,elapsedTime:null,pseudoElement:null}),ck=R.extend({clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}}),dk=dc.extend({relatedTarget:null}),ek={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",
Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},fk={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",
224:"Meta"},gk=dc.extend({key:function(a){if(a.key){var b=ek[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=Ac(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?fk[a.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:fe,charCode:function(a){return"keypress"===a.type?Ac(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===
a.type?Ac(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}}),hk=ec.extend({dataTransfer:null}),ik=dc.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:fe}),jk=R.extend({propertyName:null,elapsedTime:null,pseudoElement:null}),kk=ec.extend({deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?
-a.wheelDelta:0},deltaZ:null,deltaMode:null}),lk={eventTypes:dg,extractEvents:function(a,b,c,d,e){e=cg.get(a);if(!e)return null;switch(a){case "keypress":if(0===Ac(c))return null;case "keydown":case "keyup":a=gk;break;case "blur":case "focus":a=dk;break;case "click":if(2===c.button)return null;case "auxclick":case "dblclick":case "mousedown":case "mousemove":case "mouseup":case "mouseout":case "mouseover":case "contextmenu":a=ec;break;case "drag":case "dragend":case "dragenter":case "dragexit":case "dragleave":case "dragover":case "dragstart":case "drop":a=
hk;break;case "touchcancel":case "touchend":case "touchmove":case "touchstart":a=ik;break;case $h:case ai:case bi:a=bk;break;case ci:a=jk;break;case "scroll":a=dc;break;case "wheel":a=kk;break;case "copy":case "cut":case "paste":a=ck;break;case "gotpointercapture":case "lostpointercapture":case "pointercancel":case "pointerdown":case "pointermove":case "pointerout":case "pointerover":case "pointerup":a=hi;break;default:a=R}b=a.getPooled(e,b,c,d);lb(b);return b}};(function(a){if(ic)throw Error(k(101));
ic=Array.prototype.slice.call(a);nf()})("ResponderEventPlugin SimpleEventPlugin EnterLeaveEventPlugin ChangeEventPlugin SelectEventPlugin BeforeInputEventPlugin".split(" "));(function(a,b,c){td=a;rf=b;mf=c})(ae,Hb,Pa);pf({SimpleEventPlugin:lk,EnterLeaveEventPlugin:Yj,ChangeEventPlugin:Xj,SelectEventPlugin:ak,BeforeInputEventPlugin:Wj});var ie=[],ob=-1,Ca={},B={current:Ca},G={current:!1},Ra=Ca,bj=Pd,je=$f,Rg=Lj,aj=Nj,Dc=Oj,Ig=Zh,Jg=ag,Kg=Pj,Lg=Qj,Qg={},yj=Mj,Cj=void 0!==Yh?Yh:function(){},qa=null,
Ec=null,ke=!1,ii=ff(),Y=1E4>ii?ff:function(){return ff()-ii},Ic={current:null},Hc=null,qb=null,Gc=null,Tg=0,Jc=2,Ga=!1,Vb=da.ReactCurrentBatchConfig,$g=(new ea.Component).refs,Mc={isMounted:function(a){return(a=a._reactInternalFiber)?Na(a)===a:!1},enqueueSetState:function(a,b,c){a=a._reactInternalFiber;var d=ka(),e=Vb.suspense;d=Va(d,a,e);e=Ea(d,e);e.payload=b;void 0!==c&&null!==c&&(e.callback=c);Fa(a,e);Ja(a,d)},enqueueReplaceState:function(a,b,c){a=a._reactInternalFiber;var d=ka(),e=Vb.suspense;
d=Va(d,a,e);e=Ea(d,e);e.tag=1;e.payload=b;void 0!==c&&null!==c&&(e.callback=c);Fa(a,e);Ja(a,d)},enqueueForceUpdate:function(a,b){a=a._reactInternalFiber;var c=ka(),d=Vb.suspense;c=Va(c,a,d);d=Ea(c,d);d.tag=Jc;void 0!==b&&null!==b&&(d.callback=b);Fa(a,d);Ja(a,c)}},Qc=Array.isArray,wb=ah(!0),Fe=ah(!1),Sb={},ja={current:Sb},Ub={current:Sb},Tb={current:Sb},D={current:0},Sc=da.ReactCurrentDispatcher,X=da.ReactCurrentBatchConfig,Ia=0,z=null,K=null,J=null,Uc=!1,Tc={readContext:W,useCallback:S,useContext:S,
useEffect:S,useImperativeHandle:S,useLayoutEffect:S,useMemo:S,useReducer:S,useRef:S,useState:S,useDebugValue:S,useResponder:S,useDeferredValue:S,useTransition:S},dj={readContext:W,useCallback:ih,useContext:W,useEffect:eh,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return ze(4,2,gh.bind(null,b,a),c)},useLayoutEffect:function(a,b){return ze(4,2,a,b)},useMemo:function(a,b){var c=ub();b=void 0===b?null:b;a=a();c.memoizedState=[a,b];return a},useReducer:function(a,b,c){var d=
ub();b=void 0!==c?c(b):b;d.memoizedState=d.baseState=b;a=d.queue={pending:null,dispatch:null,lastRenderedReducer:a,lastRenderedState:b};a=a.dispatch=ch.bind(null,z,a);return[d.memoizedState,a]},useRef:function(a){var b=ub();a={current:a};return b.memoizedState=a},useState:xe,useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=xe(a),d=c[0],e=c[1];eh(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=
xe(!1),c=b[0];b=b[1];return[ih(Ce.bind(null,b,a),[b,a]),c]}},ej={readContext:W,useCallback:Yc,useContext:W,useEffect:Xc,useImperativeHandle:hh,useLayoutEffect:fh,useMemo:jh,useReducer:Vc,useRef:dh,useState:function(a){return Vc(Ua)},useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=Vc(Ua),d=c[0],e=c[1];Xc(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=Vc(Ua),c=b[0];b=b[1];return[Yc(Ce.bind(null,
b,a),[b,a]),c]}},fj={readContext:W,useCallback:Yc,useContext:W,useEffect:Xc,useImperativeHandle:hh,useLayoutEffect:fh,useMemo:jh,useReducer:Wc,useRef:dh,useState:function(a){return Wc(Ua)},useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=Wc(Ua),d=c[0],e=c[1];Xc(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=Wc(Ua),c=b[0];b=b[1];return[Yc(Ce.bind(null,b,a),[b,a]),c]}},ra=null,Ka=null,Wa=
!1,gj=da.ReactCurrentOwner,ia=!1,Je={dehydrated:null,retryTime:0};var jj=function(a,b,c,d){for(c=b.child;null!==c;){if(5===c.tag||6===c.tag)a.appendChild(c.stateNode);else if(4!==c.tag&&null!==c.child){c.child.return=c;c=c.child;continue}if(c===b)break;for(;null===c.sibling;){if(null===c.return||c.return===b)return;c=c.return}c.sibling.return=c.return;c=c.sibling}};var wh=function(a){};var ij=function(a,b,c,d,e){var f=a.memoizedProps;if(f!==d){var g=b.stateNode;Ta(ja.current);a=null;switch(c){case "input":f=
Cd(g,f);d=Cd(g,d);a=[];break;case "option":f=Fd(g,f);d=Fd(g,d);a=[];break;case "select":f=M({},f,{value:void 0});d=M({},d,{value:void 0});a=[];break;case "textarea":f=Gd(g,f);d=Gd(g,d);a=[];break;default:"function"!==typeof f.onClick&&"function"===typeof d.onClick&&(g.onclick=uc)}Ud(c,d);var h,m;c=null;for(h in f)if(!d.hasOwnProperty(h)&&f.hasOwnProperty(h)&&null!=f[h])if("style"===h)for(m in g=f[h],g)g.hasOwnProperty(m)&&(c||(c={}),c[m]="");else"dangerouslySetInnerHTML"!==h&&"children"!==h&&"suppressContentEditableWarning"!==
h&&"suppressHydrationWarning"!==h&&"autoFocus"!==h&&(db.hasOwnProperty(h)?a||(a=[]):(a=a||[]).push(h,null));for(h in d){var k=d[h];g=null!=f?f[h]:void 0;if(d.hasOwnProperty(h)&&k!==g&&(null!=k||null!=g))if("style"===h)if(g){for(m in g)!g.hasOwnProperty(m)||k&&k.hasOwnProperty(m)||(c||(c={}),c[m]="");for(m in k)k.hasOwnProperty(m)&&g[m]!==k[m]&&(c||(c={}),c[m]=k[m])}else c||(a||(a=[]),a.push(h,c)),c=k;else"dangerouslySetInnerHTML"===h?(k=k?k.__html:void 0,g=g?g.__html:void 0,null!=k&&g!==k&&(a=a||
[]).push(h,k)):"children"===h?g===k||"string"!==typeof k&&"number"!==typeof k||(a=a||[]).push(h,""+k):"suppressContentEditableWarning"!==h&&"suppressHydrationWarning"!==h&&(db.hasOwnProperty(h)?(null!=k&&oa(e,h),a||g===k||(a=[])):(a=a||[]).push(h,k))}c&&(a=a||[]).push("style",c);e=a;if(b.updateQueue=e)b.effectTag|=4}};var kj=function(a,b,c,d){c!==d&&(b.effectTag|=4)};var pj="function"===typeof WeakSet?WeakSet:Set,wj="function"===typeof WeakMap?WeakMap:Map,sj=Math.ceil,gd=da.ReactCurrentDispatcher,
Uh=da.ReactCurrentOwner,H=0,Ye=8,ca=16,ma=32,Xa=0,hd=1,Oh=2,ad=3,bd=4,Xe=5,p=H,U=null,t=null,P=0,F=Xa,id=null,ta=1073741823,Yb=1073741823,kd=null,Xb=0,jd=!1,Re=0,Ph=500,l=null,cd=!1,Se=null,La=null,ld=!1,Zb=null,$b=90,bb=null,ac=0,af=null,dd=0,Ja=function(a,b){if(50<ac)throw ac=0,af=null,Error(k(185));a=ed(a,b);if(null!==a){var c=Cc();1073741823===b?(p&Ye)!==H&&(p&(ca|ma))===H?Te(a):(V(a),p===H&&ha()):V(a);(p&4)===H||98!==c&&99!==c||(null===bb?bb=new Map([[a,b]]):(c=bb.get(a),(void 0===c||c>b)&&bb.set(a,
b)))}};var zj=function(a,b,c){var d=b.expirationTime;if(null!==a){var e=b.pendingProps;if(a.memoizedProps!==e||G.current)ia=!0;else{if(d<c){ia=!1;switch(b.tag){case 3:sh(b);Ee();break;case 5:bh(b);if(b.mode&4&&1!==c&&e.hidden)return b.expirationTime=b.childExpirationTime=1,null;break;case 1:N(b.type)&&Bc(b);break;case 4:se(b,b.stateNode.containerInfo);break;case 10:d=b.memoizedProps.value;e=b.type._context;y(Ic,e._currentValue);e._currentValue=d;break;case 13:if(null!==b.memoizedState){d=b.child.childExpirationTime;
if(0!==d&&d>=c)return th(a,b,c);y(D,D.current&1);b=sa(a,b,c);return null!==b?b.sibling:null}y(D,D.current&1);break;case 19:d=b.childExpirationTime>=c;if(0!==(a.effectTag&64)){if(d)return vh(a,b,c);b.effectTag|=64}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null);y(D,D.current);if(!d)return null}return sa(a,b,c)}ia=!1}}else ia=!1;b.expirationTime=0;switch(b.tag){case 2:d=b.type;null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;e=pb(b,B.current);rb(b,c);e=we(null,
b,d,a,e,c);b.effectTag|=1;if("object"===typeof e&&null!==e&&"function"===typeof e.render&&void 0===e.$$typeof){b.tag=1;b.memoizedState=null;b.updateQueue=null;if(N(d)){var f=!0;Bc(b)}else f=!1;b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null;ne(b);var g=d.getDerivedStateFromProps;"function"===typeof g&&Lc(b,d,g,a);e.updater=Mc;b.stateNode=e;e._reactInternalFiber=b;pe(b,d,a,c);b=Ie(null,b,d,!0,f,c)}else b.tag=0,T(null,b,e,c),b=b.child;return b;case 16:a:{e=b.elementType;null!==a&&(a.alternate=
null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;ri(e);if(1!==e._status)throw e._result;e=e._result;b.type=e;f=b.tag=Gj(e);a=aa(e,a);switch(f){case 0:b=He(null,b,e,a,c);break a;case 1:b=rh(null,b,e,a,c);break a;case 11:b=nh(null,b,e,a,c);break a;case 14:b=oh(null,b,e,aa(e.type,a),d,c);break a}throw Error(k(306,e,""));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),He(a,b,d,e,c);case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),rh(a,b,d,e,c);
case 3:sh(b);d=b.updateQueue;if(null===a||null===d)throw Error(k(282));d=b.pendingProps;e=b.memoizedState;e=null!==e?e.element:null;oe(a,b);Qb(b,d,null,c);d=b.memoizedState.element;if(d===e)Ee(),b=sa(a,b,c);else{if(e=b.stateNode.hydrate)Ka=kb(b.stateNode.containerInfo.firstChild),ra=b,e=Wa=!0;if(e)for(c=Fe(b,null,d,c),b.child=c;c;)c.effectTag=c.effectTag&-3|1024,c=c.sibling;else T(a,b,d,c),Ee();b=b.child}return b;case 5:return bh(b),null===a&&De(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:
null,g=e.children,Yd(d,e)?g=null:null!==f&&Yd(d,f)&&(b.effectTag|=16),qh(a,b),b.mode&4&&1!==c&&e.hidden?(b.expirationTime=b.childExpirationTime=1,b=null):(T(a,b,g,c),b=b.child),b;case 6:return null===a&&De(b),null;case 13:return th(a,b,c);case 4:return se(b,b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=wb(b,null,d,c):T(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),nh(a,b,d,e,c);case 7:return T(a,b,b.pendingProps,c),b.child;case 8:return T(a,
b,b.pendingProps.children,c),b.child;case 12:return T(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;e=b.pendingProps;g=b.memoizedProps;f=e.value;var h=b.type._context;y(Ic,h._currentValue);h._currentValue=f;if(null!==g)if(h=g.value,f=Qa(h,f)?0:("function"===typeof d._calculateChangedBits?d._calculateChangedBits(h,f):1073741823)|0,0===f){if(g.children===e.children&&!G.current){b=sa(a,b,c);break a}}else for(h=b.child,null!==h&&(h.return=b);null!==h;){var m=h.dependencies;if(null!==
m){g=h.child;for(var l=m.firstContext;null!==l;){if(l.context===d&&0!==(l.observedBits&f)){1===h.tag&&(l=Ea(c,null),l.tag=Jc,Fa(h,l));h.expirationTime<c&&(h.expirationTime=c);l=h.alternate;null!==l&&l.expirationTime<c&&(l.expirationTime=c);Sg(h.return,c);m.expirationTime<c&&(m.expirationTime=c);break}l=l.next}}else g=10===h.tag?h.type===b.type?null:h.child:h.child;if(null!==g)g.return=h;else for(g=h;null!==g;){if(g===b){g=null;break}h=g.sibling;if(null!==h){h.return=g.return;g=h;break}g=g.return}h=
g}T(a,b,e.children,c);b=b.child}return b;case 9:return e=b.type,f=b.pendingProps,d=f.children,rb(b,c),e=W(e,f.unstable_observedBits),d=d(e),b.effectTag|=1,T(a,b,d,c),b.child;case 14:return e=b.type,f=aa(e,b.pendingProps),f=aa(e.type,f),oh(a,b,e,f,d,c);case 15:return ph(a,b,b.type,b.pendingProps,d,c);case 17:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2),b.tag=1,N(d)?(a=!0,Bc(b)):a=!1,rb(b,c),Yg(b,d,e),pe(b,d,e,c),Ie(null,
b,d,!0,a,c);case 19:return vh(a,b,c)}throw Error(k(156,b.tag));};var bf=null,Ne=null,la=function(a,b,c,d){return new Fj(a,b,c,d)};ef.prototype.render=function(a){md(a,this._internalRoot,null,null)};ef.prototype.unmount=function(){var a=this._internalRoot,b=a.containerInfo;md(null,a,null,function(){b[Lb]=null})};var Di=function(a){if(13===a.tag){var b=Fc(ka(),150,100);Ja(a,b);df(a,b)}};var Yf=function(a){13===a.tag&&(Ja(a,3),df(a,3))};var Bi=function(a){if(13===a.tag){var b=ka();b=Va(b,a,null);Ja(a,
b);df(a,b)}};sd=function(a,b,c){switch(b){case "input":Dd(a,c);b=c.name;if("radio"===c.type&&null!=b){for(c=a;c.parentNode;)c=c.parentNode;c=c.querySelectorAll("input[name="+JSON.stringify(""+b)+'][type="radio"]');for(b=0;b<c.length;b++){var d=c[b];if(d!==a&&d.form===a.form){var e=ae(d);if(!e)throw Error(k(90));Gf(d);Dd(d,e)}}}break;case "textarea":Lf(a,c);break;case "select":b=c.value,null!=b&&hb(a,!!c.multiple,b,!1)}};(function(a,b,c,d){ee=a;eg=b;vd=c;vf=d})(Qh,function(a,b,c,d,e){var f=p;p|=4;
try{return Da(98,a.bind(null,b,c,d,e))}finally{p=f,p===H&&ha()}},function(){(p&(1|ca|ma))===H&&(uj(),xb())},function(a,b){var c=p;p|=2;try{return a(b)}finally{p=c,p===H&&ha()}});var mk={Events:[Hb,Pa,ae,pf,qd,lb,function(a){Kd(a,Ki)},sf,tf,sc,pc,xb,{current:!1}]};(function(a){var b=a.findFiberByHostInstance;return Ej(M({},a,{overrideHookState:null,overrideProps:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:da.ReactCurrentDispatcher,findHostInstanceByFiber:function(a){a=Sf(a);
return null===a?null:a.stateNode},findFiberByHostInstance:function(a){return b?b(a):null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null}))})({findFiberByHostInstance:Bb,bundleType:0,version:"16.13.1",rendererPackageName:"react-dom"});I.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=mk;I.createPortal=Xh;I.findDOMNode=function(a){if(null==a)return null;if(1===a.nodeType)return a;var b=a._reactInternalFiber;if(void 0===
b){if("function"===typeof a.render)throw Error(k(188));throw Error(k(268,Object.keys(a)));}a=Sf(b);a=null===a?null:a.stateNode;return a};I.flushSync=function(a,b){if((p&(ca|ma))!==H)throw Error(k(187));var c=p;p|=1;try{return Da(99,a.bind(null,b))}finally{p=c,ha()}};I.hydrate=function(a,b,c){if(!bc(b))throw Error(k(200));return nd(null,a,b,!0,c)};I.render=function(a,b,c){if(!bc(b))throw Error(k(200));return nd(null,a,b,!1,c)};I.unmountComponentAtNode=function(a){if(!bc(a))throw Error(k(40));return a._reactRootContainer?
(Rh(function(){nd(null,null,a,!1,function(){a._reactRootContainer=null;a[Lb]=null})}),!0):!1};I.unstable_batchedUpdates=Qh;I.unstable_createPortal=function(a,b){return Xh(a,b,2<arguments.length&&void 0!==arguments[2]?arguments[2]:null)};I.unstable_renderSubtreeIntoContainer=function(a,b,c,d){if(!bc(c))throw Error(k(200));if(null==a||void 0===a._reactInternalFiber)throw Error(k(38));return nd(a,b,c,!1,d)};I.version="16.13.1"});
</script>
<script>const e = React.createElement;
function pathToString(path) {
if (path[0] === '/') {
return '/' + path.slice(1).join('/');
} else {
return path.join('/');
}
}
function findCommonPath(files) {
if (!files || !files.length) {
return [];
}
function isPrefix(arr, prefix) {
if (arr.length < prefix.length) {
return false;
}
for (let i = prefix.length - 1; i >= 0; --i) {
if (arr[i] !== prefix[i]) {
return false;
}
}
return true;
}
let commonPath = files[0].path.slice(0, -1);
while (commonPath.length) {
if (files.every(file => isPrefix(file.path, commonPath))) {
break;
}
commonPath.pop();
}
return commonPath;
}
function findFolders(files) {
if (!files || !files.length) {
return [];
}
let folders = files.filter(file => file.path.length > 1).map(file => file.path[0]);
folders = [...new Set(folders)]; // unique
folders.sort();
folders = folders.map(folder => {
let filesInFolder = files
.filter(file => file.path[0] === folder)
.map(file => ({
...file,
path: file.path.slice(1),
parent: [...file.parent, file.path[0]],
}));
const children = findFolders(filesInFolder); // recursion
return {
is_folder: true,
path: [folder],
parent: files[0].parent,
children,
covered: children.reduce((sum, file) => sum + file.covered, 0),
coverable: children.reduce((sum, file) => sum + file.coverable, 0),
prevRun: {
covered: children.reduce((sum, file) => sum + file.prevRun.covered, 0),
coverable: children.reduce((sum, file) => sum + file.prevRun.coverable, 0),
}
};
});
return [
...folders,
...files.filter(file => file.path.length === 1),
];
}
class App extends React.Component {
constructor(...args) {
super(...args);
this.state = {
current: [],
};
}
componentDidMount() {
this.updateStateFromLocation();
window.addEventListener("hashchange", () => this.updateStateFromLocation(), false);
}
updateStateFromLocation() {
if (window.location.hash.length > 1) {
const current = window.location.hash.substr(1).split('/');
this.setState({current});
} else {
this.setState({current: []});
}
}
getCurrentPath() {
let file = this.props.root;
let path = [file];
for (let p of this.state.current) {
file = file.children.find(file => file.path[0] === p);
if (!file) {
return path;
}
path.push(file);
}
return path;
}
render() {
const path = this.getCurrentPath();
const file = path[path.length - 1];
let w = null;
if (file.is_folder) {
w = e(FilesList, {
folder: file,
onSelectFile: this.selectFile.bind(this),
onBack: path.length > 1 ? this.back.bind(this) : null,
});
} else {
w = e(DisplayFile, {
file,
onBack: this.back.bind(this),
});
}
return e('div', {className: 'app'}, w);
}
selectFile(file) {
this.setState(({current}) => {
return {current: [...current, file.path[0]]};
}, () => this.updateHash());
}
back(file) {
this.setState(({current}) => {
return {current: current.slice(0, current.length - 1)};
}, () => this.updateHash());
}
updateHash() {
if (!this.state.current || !this.state.current.length) {
window.location = '#';
} else {
window.location = '#' + this.state.current.join('/');
}
}
}
function FilesList({folder, onSelectFile, onBack}) {
let files = folder.children;
return e('div', {className: 'display-folder'},
e(FileHeader, {file: folder, onBack}),
e('table', {className: 'files-list'},
e('thead', {className: 'files-list__head'},
e('tr', null,
e('th', null, "Path"),
e('th', null, "Coverage")
)
),
e('tbody', {className: 'files-list__body'},
files.map(file => e(File, {file, onClick: onSelectFile}))
)
)
);
}
function File({file, onClick}) {
const coverage = file.coverable ? file.covered / file.coverable * 100 : -1;
const coverageDelta = file.prevRun &&
(file.covered / file.coverable * 100 - file.prevRun.covered / file.prevRun.coverable * 100);
return e('tr', {
className: 'files-list__file'
+ (coverage >= 0 && coverage < 50 ? ' files-list__file_low': '')
+ (coverage >= 50 && coverage < 80 ? ' files-list__file_medium': '')
+ (coverage >= 80 ? ' files-list__file_high': '')
+ (file.is_folder ? ' files-list__file_folder': ''),
onClick: () => onClick(file),
},
e('td', null, e('a', null, pathToString(file.path))),
e('td', null,
file.covered + ' / ' + file.coverable +
(coverage >= 0 ? ' (' + coverage.toFixed(2) + '%)' : ''),
e('span', {title: 'Change from the previous run'},
(coverageDelta ? ` (${coverageDelta > 0 ? '+' : ''}${coverageDelta.toFixed(2)}%)` : ''))
)
);
}
function DisplayFile({file, onBack}) {
return e('div', {className: 'display-file'},
e(FileHeader, {file, onBack}),
e(FileContent, {file})
);
}
function FileHeader({file, onBack}) {
const coverage = file.covered / file.coverable * 100;
const coverageDelta = file.prevRun && (coverage - file.prevRun.covered / file.prevRun.coverable * 100);
return e('div', {className: 'file-header'},
onBack ? e('a', {className: 'file-header__back', onClick: onBack}, 'Back') : null,
e('div', {className: 'file-header__name'}, pathToString([...file.parent, ...file.path])),
e('div', {className: 'file-header__stat'},
'Covered: ' + file.covered + ' of ' + file.coverable +
(file.coverable ? ' (' + coverage.toFixed(2) + '%)' : ''),
e('span', {title: 'Change from the previous run'},
(coverageDelta ? ` (${coverageDelta > 0 ? '+' : ''}${coverageDelta.toFixed(2)}%)` : ''))
)
);
}
function FileContent({file}) {
return e('div', {className: 'file-content'},
file.content.split(/\r?\n/).map((line, index) => {
const trace = file.traces.find(trace => trace.line === index + 1);
const covered = trace && trace.stats.Line;
const uncovered = trace && !trace.stats.Line;
return e('pre', {
className: 'code-line'
+ (covered ? ' code-line_covered' : '')
+ (uncovered ? ' code-line_uncovered' : ''),
title: trace ? JSON.stringify(trace.stats, null, 2) : null,
}, line);
})
);
}
(function(){
const commonPath = findCommonPath(data.files);
const prevFilesMap = new Map();
previousData && previousData.files.forEach((file) => {
const path = file.path.slice(commonPath.length).join('/');
prevFilesMap.set(path, file);
});
const files = data.files.map((file) => {
const path = file.path.slice(commonPath.length);
const { covered = 0, coverable = 0 } = prevFilesMap.get(path.join('/')) || {};
return {
...file,
path,
parent: commonPath,
prevRun: { covered, coverable },
};
});
const children = findFolders(files);
const root = {
is_folder: true,
children,
path: commonPath,
parent: [],
covered: children.reduce((sum, file) => sum + file.covered, 0),
coverable: children.reduce((sum, file) => sum + file.coverable, 0),
prevRun: {
covered: children.reduce((sum, file) => sum + file.prevRun.covered, 0),
coverable: children.reduce((sum, file) => sum + file.prevRun.coverable, 0),
}
};
ReactDOM.render(e(App, {root, prevFilesMap}), document.getElementById('root'));
}());
</script>
</body>
</html>