1use cfx_types::{
6 AddressSpaceUtil, BigEndianHash, Space, H160, H256, H520, U128, U256, U64,
7};
8use cfx_util_macros::bail;
9use cfxcore::{
10 block_data_manager::BlockDataManager,
11 errors::account_result_to_rpc_result,
12 light_protocol::{self, query_service::TxInfo, Error as LightError},
13 verification::EpochReceiptProof,
14 ConsensusGraph, LightQueryService, PeerInfo, SharedConsensusGraph,
15};
16use cfxcore_accounts::AccountProvider;
17use delegate::delegate;
18use diem_types::transaction::TransactionPayload;
19use futures::future::{self, FutureExt};
20use jsonrpc_core::{BoxFuture, Error as RpcError, Result as JsonRpcResult};
21use log::{debug, info};
22use network::{
23 node_table::{Node, NodeId},
24 throttling, SessionDetails, UpdateNodeOperation,
25};
26use primitives::{
27 Account, DepositInfo, StorageRoot, TransactionWithSignature, VoteStakeInfo,
28};
29use rlp::Encodable;
30use std::{collections::BTreeMap, net::SocketAddr, sync::Arc};
31use crate::{
33 common::delegate_convert,
34 rpc::{
35 errors::{self, invalid_params_check},
36 helpers::{build_block, MAX_FEE_HISTORY_CACHE_BLOCK_COUNT},
37 impls::common::{self, RpcImpl as CommonImpl},
38 traits::{cfx::Cfx, debug::LocalRpc, test::TestRpc},
39 types::{
40 cfx::check_rpc_address_network,
41 pos::{Block as PosBlock, PoSEpochReward},
42 Account as RpcAccount, AccountPendingInfo,
43 AccountPendingTransactions, BlameInfo, Block as RpcBlock,
44 BlockHashOrEpochNumber, Bytes, CfxFeeHistory, CfxRpcLogFilter,
45 CheckBalanceAgainstTransactionResponse, ConsensusGraphStates,
46 EpochNumber, EstimateGasAndCollateralResponse, FeeHistory,
47 Log as RpcLog, PoSEconomics, Receipt as RpcReceipt,
48 RewardInfo as RpcRewardInfo, RpcAddress, SponsorInfo,
49 StatOnGasLoad, Status as RpcStatus, StorageCollateralInfo,
50 SyncGraphStates, TokenSupplyInfo, Transaction as RpcTransaction,
51 TransactionRequest, VoteParamsInfo, WrapTransaction, U64 as HexU64,
52 },
53 CoreBoxFuture, CoreResult,
54 },
55};
56use cfx_addr::Network;
57use cfx_parameters::rpc::GAS_PRICE_DEFAULT_VALUE;
58use cfxcore::{errors::Error::LightProtocol, light_protocol::QueryService};
59use diem_types::account_address::AccountAddress;
60
61macro_rules! not_supported {
63 () => {};
64 ( fn $fn:ident ( &self $(, $name:ident : $type:ty)* ) $( -> BoxFuture<$ret:ty> )? ; $($tail:tt)* ) => {
65 #[allow(unused_variables)]
66 fn $fn ( &self $(, $name : $type)* ) $( -> BoxFuture<$ret> )? {
67 async {
68 Err(errors::unimplemented(Some("Tracking issue: https://github.com/Conflux-Chain/conflux-rust/issues/1461".to_string())))
69 }.boxed()
70 }
71
72 not_supported!($($tail)*);
73 };
74 ( fn $fn:ident ( &self $(, $name:ident : $type:ty)* ) $( -> $ret:ty )? ; $($tail:tt)* ) => {
75 #[allow(unused_variables)]
76 fn $fn ( &self $(, $name : $type)* ) $( -> $ret )? {
77 Err(errors::unimplemented(Some("Tracking issue: https://github.com/Conflux-Chain/conflux-rust/issues/1461".to_string())))
78 }
79
80 not_supported!($($tail)*);
81 };
82}
83
84pub struct RpcImpl {
85 accounts: Arc<AccountProvider>,
87
88 consensus: SharedConsensusGraph,
90
91 data_man: Arc<BlockDataManager>,
93
94 light: Arc<LightQueryService>,
96}
97
98impl RpcImpl {
99 pub fn new(
100 light: Arc<LightQueryService>, accounts: Arc<AccountProvider>,
101 consensus: SharedConsensusGraph, data_man: Arc<BlockDataManager>,
102 ) -> Self {
103 RpcImpl {
104 accounts,
105 consensus,
106 data_man,
107 light,
108 }
109 }
110
111 fn check_address_network(
112 network: Network, light: &QueryService,
113 ) -> CoreResult<()> {
114 invalid_params_check(
115 "address",
116 check_rpc_address_network(Some(network), light.get_network_type()),
117 )
118 .map_err(|e| e.into())
119 }
120
121 fn get_epoch_number_with_pivot_check(
122 consensus_graph: SharedConsensusGraph,
123 block_hash_or_epoch_number: Option<BlockHashOrEpochNumber>,
124 ) -> CoreResult<EpochNumber> {
125 match block_hash_or_epoch_number {
126 Some(BlockHashOrEpochNumber::BlockHashWithOption {
127 hash,
128 require_pivot,
129 }) => {
130 let epoch_number = consensus_graph
131 .get_block_epoch_number_with_pivot_check(
132 &hash,
133 require_pivot.unwrap_or(true),
134 )?;
135 Ok(EpochNumber::Num(U64::from(epoch_number)))
136 }
137 Some(BlockHashOrEpochNumber::EpochNumber(epoch_number)) => {
138 Ok(epoch_number)
139 }
140 None => Ok(EpochNumber::LatestState),
141 }
142 }
143
144 fn account(
145 &self, address: RpcAddress, num: Option<EpochNumber>,
146 ) -> CoreBoxFuture<RpcAccount> {
147 let epoch = num.unwrap_or(EpochNumber::LatestState).into();
148
149 info!(
150 "RPC Request: cfx_getAccount address={:?} epoch={:?}",
151 address, epoch
152 );
153
154 let light = self.light.clone();
156
157 let fut = async move {
158 Self::check_address_network(address.network, &light)?;
159 let network = address.network;
160
161 let account = invalid_params_check(
162 "epoch",
163 light.get_account(epoch, address.hex_address).await,
164 )?;
165
166 let account = account.unwrap_or(account_result_to_rpc_result(
167 "address",
168 Ok(Account::new_empty_with_balance(
169 &address.hex_address.with_native_space(),
170 &U256::zero(), &U256::zero(), )),
173 )?);
174
175 Ok(RpcAccount::try_from(account, network)?)
176 };
177
178 fut.boxed()
179 }
180
181 fn balance(
182 &self, address: RpcAddress,
183 block_hash_or_epoch_number: Option<BlockHashOrEpochNumber>,
184 ) -> CoreBoxFuture<U256> {
185 info!(
186 "RPC Request: cfx_getBalance address={:?} epoch={:?}",
187 address,
188 block_hash_or_epoch_number
189 .as_ref()
190 .ok_or(EpochNumber::LatestState)
191 );
192
193 let light = self.light.clone();
195 let consensus_graph = self.consensus.clone();
196
197 let fut = async move {
198 let epoch = Self::get_epoch_number_with_pivot_check(
199 consensus_graph,
200 block_hash_or_epoch_number,
201 )?
202 .into();
203 Self::check_address_network(address.network, &light)?;
204
205 let account = invalid_params_check(
206 "address",
207 light.get_account(epoch, address.into()).await,
208 )?;
209
210 Ok(account
211 .map(|account| account.balance.into())
212 .unwrap_or_default())
213 };
214
215 fut.boxed()
216 }
217
218 fn admin(
219 &self, address: RpcAddress, num: Option<EpochNumber>,
220 ) -> CoreBoxFuture<Option<RpcAddress>> {
221 let epoch = num.unwrap_or(EpochNumber::LatestState).into();
222 let network = address.network;
223
224 info!(
225 "RPC Request: cfx_getAdmin address={:?} epoch={:?}",
226 address, epoch
227 );
228
229 let light = self.light.clone();
231
232 let fut = async move {
233 Self::check_address_network(address.network, &light)?;
234
235 let account = invalid_params_check(
236 "address",
237 light.get_account(epoch, address.into()).await,
238 )?;
239
240 match account {
241 None => Ok(None),
242 Some(acc) => {
243 Ok(Some(RpcAddress::try_from_h160(acc.admin, network)?))
244 }
245 }
246 };
247
248 fut.boxed()
249 }
250
251 fn sponsor_info(
252 &self, address: RpcAddress, num: Option<EpochNumber>,
253 ) -> CoreBoxFuture<SponsorInfo> {
254 let epoch = num.unwrap_or(EpochNumber::LatestState).into();
255
256 info!(
257 "RPC Request: cfx_getSponsorInfo address={:?} epoch={:?}",
258 address, epoch
259 );
260
261 let light = self.light.clone();
263
264 let fut = async move {
265 Self::check_address_network(address.network, &light)?;
266 let network = address.network;
267
268 let account = invalid_params_check(
269 "address",
270 light.get_account(epoch, address.into()).await,
271 )?;
272
273 match account {
274 None => Ok(SponsorInfo::default(network)?),
275 Some(acc) => {
276 Ok(SponsorInfo::try_from(acc.sponsor_info, network)?)
277 }
278 }
279 };
280
281 fut.boxed()
282 }
283
284 fn staking_balance(
285 &self, address: RpcAddress, num: Option<EpochNumber>,
286 ) -> CoreBoxFuture<U256> {
287 let epoch = num.unwrap_or(EpochNumber::LatestState).into();
288
289 info!(
290 "RPC Request: cfx_getStakingBalance address={:?} epoch={:?}",
291 address, epoch
292 );
293
294 let light = self.light.clone();
296
297 let fut = async move {
298 Self::check_address_network(address.network, &light)?;
299
300 let account = invalid_params_check(
301 "address",
302 light.get_account(epoch, address.into()).await,
303 )?;
304
305 Ok(account
306 .map(|account| account.staking_balance.into())
307 .unwrap_or_default())
308 };
309
310 fut.boxed()
311 }
312
313 fn deposit_list(
314 &self, address: RpcAddress, num: Option<EpochNumber>,
315 ) -> CoreBoxFuture<Vec<DepositInfo>> {
316 let epoch = num.unwrap_or(EpochNumber::LatestState).into();
317
318 info!(
319 "RPC Request: cfx_getDepositList address={:?} epoch_num={:?}",
320 address, epoch
321 );
322
323 let light = self.light.clone();
325
326 let fut = async move {
327 Self::check_address_network(address.network, &light)?;
328
329 let maybe_list = invalid_params_check(
330 "address",
331 light.get_deposit_list(epoch, address.into()).await,
332 )?;
333
334 match maybe_list {
335 None => Ok(vec![]),
336 Some(deposit_list) => Ok(deposit_list.0),
337 }
338 };
339
340 fut.boxed()
341 }
342
343 pub fn account_pending_info(
344 &self, address: RpcAddress,
345 ) -> CoreBoxFuture<Option<AccountPendingInfo>> {
346 info!("RPC Request: cfx_getAccountPendingInfo({:?})", address);
347
348 let fut = async move {
349 Ok(None)
351 };
352 fut.boxed()
353 }
354
355 fn vote_list(
356 &self, address: RpcAddress, num: Option<EpochNumber>,
357 ) -> CoreBoxFuture<Vec<VoteStakeInfo>> {
358 let epoch = num.unwrap_or(EpochNumber::LatestState).into();
359
360 info!(
361 "RPC Request: cfx_getVoteList address={:?} epoch_num={:?}",
362 address, epoch
363 );
364
365 let light = self.light.clone();
367
368 let fut = async move {
369 Self::check_address_network(address.network, &light)?;
370
371 let maybe_list = invalid_params_check(
372 "address",
373 light.get_vote_list(epoch, address.into()).await,
374 )?;
375
376 match maybe_list {
377 None => Ok(vec![]),
378 Some(vote_list) => Ok(vote_list.0),
379 }
380 };
381
382 fut.boxed()
383 }
384
385 fn collateral_for_storage(
386 &self, address: RpcAddress, num: Option<EpochNumber>,
387 ) -> CoreBoxFuture<U256> {
388 let epoch = num.unwrap_or(EpochNumber::LatestState).into();
389
390 info!(
391 "RPC Request: cfx_getCollateralForStorage address={:?} epoch={:?}",
392 address, epoch
393 );
394
395 let light = self.light.clone();
397
398 let fut = async move {
399 Self::check_address_network(address.network, &light)?;
400
401 let account = invalid_params_check(
402 "address",
403 light.get_account(epoch, address.into()).await,
404 )?;
405
406 Ok(account
407 .map(|account| account.collateral_for_storage.into())
408 .unwrap_or_default())
409 };
410
411 fut.boxed()
412 }
413
414 fn code(
415 &self, address: RpcAddress,
416 block_hash_or_epoch_number: Option<BlockHashOrEpochNumber>,
417 ) -> CoreBoxFuture<Bytes> {
418 info!(
419 "RPC Request: cfx_getCode address={:?} epoch={:?}",
420 address,
421 block_hash_or_epoch_number
422 .as_ref()
423 .ok_or(EpochNumber::LatestState)
424 );
425
426 let light = self.light.clone();
428 let consensus_graph = self.consensus.clone();
429
430 let fut = async move {
431 let epoch = Self::get_epoch_number_with_pivot_check(
432 consensus_graph,
433 block_hash_or_epoch_number,
434 )?
435 .into();
436 Self::check_address_network(address.network, &light)?;
437
438 Ok(Bytes::new(
443 invalid_params_check(
444 "address",
445 light.get_code(epoch, address.into()).await,
446 )?
447 .unwrap_or_default(),
448 ))
449 };
450
451 fut.boxed()
452 }
453
454 fn get_logs(&self, filter: CfxRpcLogFilter) -> CoreBoxFuture<Vec<RpcLog>> {
455 info!("RPC Request: cfx_getLogs filter={:?}", filter);
456
457 let light = self.light.clone();
459
460 let fut = async move {
461 if let Some(addresses) = &filter.address {
463 for address in addresses.iter() {
464 invalid_params_check(
465 "filter.address",
466 check_rpc_address_network(
467 Some(address.network),
468 light.get_network_type(),
469 ),
470 )?;
471 }
472 }
473
474 let filter = filter.into_primitive()?;
475
476 let logs = light
477 .get_logs(filter)
478 .await
479 .map_err(|e| e.to_string()) .map_err(RpcError::invalid_params)?;
481
482 Ok(logs
483 .into_iter()
484 .map(|l| {
485 RpcLog::try_from_localized(l, *light.get_network_type())
486 })
487 .collect::<Result<_, _>>()?)
488 };
489
490 fut.boxed()
491 }
492
493 fn send_tx_helper(
494 light: Arc<LightQueryService>, raw: Bytes,
495 ) -> CoreResult<H256> {
496 let raw: Vec<u8> = raw.into_vec();
497
498 let tx: TransactionWithSignature =
501 TransactionWithSignature::from_raw(&raw.clone())
502 .map_err(|e| format!("Failed to decode tx: {:?}", e))
503 .map_err(RpcError::invalid_params)?;
504
505 debug!("Deserialized tx: {:?}", tx);
506
507 match light.send_raw_tx(raw) {
512 true => Ok(tx.hash().into()),
513 false => bail!(LightProtocol(light_protocol::Error::InternalError("Unable to relay tx".into()).into())),
514 }
515 }
516
517 fn send_raw_transaction(&self, raw: Bytes) -> CoreResult<H256> {
518 info!("RPC Request: cfx_sendRawTransaction bytes={:?}", raw);
519 Self::send_tx_helper(self.light.clone(), raw)
520 }
521
522 fn send_transaction(
523 &self, mut tx: TransactionRequest, password: Option<String>,
524 ) -> CoreBoxFuture<H256> {
525 info!("RPC Request: cfx_sendTransaction tx={:?}", tx);
526
527 let light = self.light.clone();
529 let accounts = self.accounts.clone();
530
531 let fut = async move {
532 tx.check_rpc_address_network("tx", light.get_network_type())?;
533
534 if tx.nonce.is_none() {
535 let address =
539 tx.from.clone().ok_or("from should exist")?.into();
540 let epoch = EpochNumber::LatestState.into_primitive();
541
542 let nonce = light
543 .get_account(epoch, address)
544 .await?
545 .map(|a| a.nonce)
546 .unwrap_or(U256::zero());
547
548 tx.nonce.replace(nonce.into());
549 debug!("after loading nonce in latest state, tx = {:?}", tx);
550 }
551
552 let epoch_height = light.get_latest_verifiable_epoch_number().map_err(|_| {
553 format!("the light client cannot retrieve/verify the latest mined pivot block.")
554 })?;
555 let chain_id = light.get_latest_verifiable_chain_id().map_err(|_| {
556 format!("the light client cannot retrieve/verify the latest chain_id.")
557 })?;
558 let tx = tx.sign_with(
559 epoch_height,
560 chain_id.in_native_space(),
561 password,
562 accounts,
563 )?;
564
565 Self::send_tx_helper(light, Bytes::new(tx.rlp_bytes()))
566 };
567
568 fut.boxed()
569 }
570
571 fn storage_root(
572 &self, address: RpcAddress, epoch_num: Option<EpochNumber>,
573 ) -> CoreBoxFuture<Option<StorageRoot>> {
574 let epoch_num = epoch_num.unwrap_or(EpochNumber::LatestState);
575
576 info!(
577 "RPC Request: cfx_getStorageRoot address={:?} epoch={:?})",
578 address, epoch_num
579 );
580
581 let light = self.light.clone();
583
584 let fut = async move {
585 Self::check_address_network(address.network, &light)?;
586
587 let root = invalid_params_check(
588 "address",
589 light
590 .get_storage_root(epoch_num.into(), address.into())
591 .await,
592 )?;
593
594 Ok(Some(root))
595 };
596
597 fut.boxed()
598 }
599
600 fn storage_at(
601 &self, address: RpcAddress, position: U256,
602 block_hash_or_epoch_number: Option<BlockHashOrEpochNumber>,
603 ) -> CoreBoxFuture<Option<H256>> {
604 let position: H256 = H256::from_uint(&position);
605 info!(
608 "RPC Request: cfx_getStorageAt address={:?} position={:?} epoch={:?})",
609 address,
610 position,
611 block_hash_or_epoch_number
612 .as_ref()
613 .ok_or(EpochNumber::LatestState)
614 );
615
616 let light = self.light.clone();
618 let consensus_graph = self.consensus.clone();
619
620 let fut = async move {
621 let epoch_num = Self::get_epoch_number_with_pivot_check(
622 consensus_graph,
623 block_hash_or_epoch_number,
624 )?;
625 Self::check_address_network(address.network, &light)?;
626
627 let maybe_entry = light
628 .get_storage(epoch_num.into(), address.into(), position)
629 .await
630 .map_err(|e| e.to_string()) .map_err(RpcError::invalid_params)?;
632
633 Ok(maybe_entry.map(Into::into))
634 };
635
636 fut.boxed()
637 }
638
639 fn transaction_by_hash(
640 &self, hash: H256,
641 ) -> CoreBoxFuture<Option<RpcTransaction>> {
642 info!("RPC Request: cfx_getTransactionByHash hash={:?}", hash);
643
644 let light = self.light.clone();
648
649 let fut = async move {
650 let tx = light
651 .get_tx(hash.into())
652 .await
653 .map_err(|e| e.to_string()) .map_err(RpcError::invalid_params)?;
655
656 Ok(Some(RpcTransaction::from_signed(
657 &tx,
658 None,
659 *light.get_network_type(),
660 )?))
661 };
662
663 fut.boxed()
664 }
665
666 fn transaction_receipt(
667 &self, tx_hash: H256,
668 ) -> CoreBoxFuture<Option<RpcReceipt>> {
669 let hash: H256 = tx_hash.into();
670 info!("RPC Request: cfx_getTransactionReceipt hash={:?}", hash);
671
672 let light = self.light.clone();
674 let data_man = self.data_man.clone();
675
676 let fut = async move {
677 let tx_info = match light.get_tx_info(hash).await {
684 Ok(t) => t,
685 Err(LightError::Timeout(_)) => return Ok(None),
686 Err(e) => {
687 bail!(RpcError::invalid_params(e.to_string()))
688 }
689 };
690
691 let TxInfo {
692 tx,
693 maybe_block_number,
694 receipt,
695 tx_index,
696 maybe_epoch,
697 maybe_state_root,
698 prior_gas_used,
699 } = tx_info;
700
701 if maybe_block_number.is_none() || tx_index.is_phantom {
702 return Ok(None);
703 }
704
705 let maybe_base_price = data_man
706 .block_header_by_hash(&tx_index.block_hash)
707 .and_then(|x| x.base_price());
708
709 let receipt = RpcReceipt::new(
710 tx,
711 receipt,
712 tx_index,
713 prior_gas_used,
714 maybe_epoch,
715 maybe_block_number.unwrap(),
716 maybe_base_price,
717 maybe_state_root,
718 None,
720 *light.get_network_type(),
721 false,
722 true,
723 )?;
724
725 Ok(Some(receipt))
726 };
727
728 fut.boxed()
729 }
730
731 pub fn epoch_number(&self, epoch: Option<EpochNumber>) -> CoreResult<U256> {
732 let epoch = epoch.unwrap_or(EpochNumber::LatestMined);
733 info!("RPC Request: cfx_epochNumber epoch={:?}", epoch);
734
735 invalid_params_check(
736 "epoch",
737 self.light
738 .get_height_from_epoch_number(epoch.into())
739 .map(|height| height.into()),
740 )
741 .map_err(|e| e.into())
742 }
743
744 pub fn next_nonce(
745 &self, address: RpcAddress, num: Option<BlockHashOrEpochNumber>,
746 ) -> CoreBoxFuture<U256> {
747 info!(
748 "RPC Request: cfx_getNextNonce address={:?} num={:?}",
749 address, num
750 );
751
752 let consensus_graph = self.consensus.clone();
754 let light = self.light.clone();
755
756 let fut = async move {
757 Self::check_address_network(address.network, &light)?;
758
759 let epoch =
760 Self::get_epoch_number_with_pivot_check(consensus_graph, num)?
761 .into();
762
763 let account = invalid_params_check(
764 "address",
765 light.get_account(epoch, address.into()).await,
766 )?;
767
768 Ok(account
769 .map(|account| account.nonce.into())
770 .unwrap_or_default())
771 };
772
773 fut.boxed()
774 }
775
776 pub fn block_by_hash(
777 &self, hash: H256, include_txs: bool,
778 ) -> CoreBoxFuture<Option<RpcBlock>> {
779 let hash = hash.into();
780
781 info!(
782 "RPC Request: cfx_getBlockByHash hash={:?} include_txs={:?}",
783 hash, include_txs
784 );
785
786 let consensus_graph = self.consensus.clone();
788 let data_man = self.data_man.clone();
789 let light = self.light.clone();
790
791 let fut = async move {
792 let block = match light.retrieve_block(hash).await? {
793 None => return Ok(None),
794 Some(b) => b,
795 };
796
797 let inner = consensus_graph.inner.read();
798
799 Ok(Some(build_block(
800 &block,
801 *light.get_network_type(),
802 &*consensus_graph,
803 &*inner,
804 &data_man,
805 include_txs,
806 Some(Space::Native),
807 )?))
808 };
809
810 fut.boxed()
811 }
812
813 pub fn block_by_hash_with_pivot_assumption(
814 &self, block_hash: H256, pivot_hash: H256, epoch_number: U64,
815 ) -> CoreBoxFuture<RpcBlock> {
816 let block_hash = block_hash.into();
817 let pivot_hash = pivot_hash.into();
818 let epoch_number = epoch_number.as_u64();
819
820 info!(
821 "RPC Request: cfx_getBlockByHashWithPivotAssumption block_hash={:?} pivot_hash={:?} epoch_number={:?}",
822 block_hash, pivot_hash, epoch_number
823 );
824
825 let consensus_graph = self.consensus.clone();
827 let data_man = self.data_man.clone();
828 let light = self.light.clone();
829
830 let fut = async move {
831 consensus_graph
834 .inner
835 .read()
836 .check_block_pivot_assumption(&pivot_hash, epoch_number)
837 .map_err(RpcError::invalid_params)?;
838
839 let block = light
841 .retrieve_block(block_hash)
842 .await?
843 .ok_or_else(|| RpcError::invalid_params("Block not found"))?;
844
845 let inner = consensus_graph.inner.read();
846
847 Ok(build_block(
848 &block,
849 *light.get_network_type(),
850 &*consensus_graph,
851 &*inner,
852 &data_man,
853 true,
854 Some(Space::Native),
855 )?)
856 };
857
858 fut.boxed()
859 }
860
861 pub fn block_by_epoch_number(
862 &self, epoch: EpochNumber, include_txs: bool,
863 ) -> CoreBoxFuture<Option<RpcBlock>> {
864 info!(
865 "RPC Request: cfx_getBlockByEpochNumber epoch={:?} include_txs={:?}",
866 epoch, include_txs
867 );
868
869 let consensus_graph = self.consensus.clone();
871 let data_man = self.data_man.clone();
872 let light = self.light.clone();
873
874 let fut = async move {
875 let epoch: u64 = light
876 .get_height_from_epoch_number(epoch.into())
877 .map_err(|e| e.to_string())
878 .map_err(RpcError::invalid_params)?;
879
880 let hash = consensus_graph
882 .inner
883 .read()
884 .get_pivot_hash_from_epoch_number(epoch)
885 .map_err(RpcError::invalid_params)?;
886
887 let block = match light.retrieve_block(hash).await? {
889 None => return Ok(None),
890 Some(b) => b,
891 };
892
893 let inner = consensus_graph.inner.read();
894
895 Ok(Some(build_block(
896 &block,
897 *light.get_network_type(),
898 &*consensus_graph,
899 &*inner,
900 &data_man,
901 include_txs,
902 Some(Space::Native),
903 )?))
904 };
905
906 fut.boxed()
907 }
908
909 pub fn blocks_by_epoch(&self, epoch: EpochNumber) -> CoreResult<Vec<H256>> {
910 info!("RPC Request: cfx_getBlocksByEpoch epoch_number={:?}", epoch);
911
912 let height = self
913 .light
914 .get_height_from_epoch_number(epoch.into())
915 .map_err(|e| e.to_string())
916 .map_err(RpcError::invalid_params)?;
917
918 let hashes = self
919 .consensus
920 .inner
921 .read()
922 .block_hashes_by_epoch(height)
923 .map_err(|e| e.to_string())
924 .map_err(RpcError::invalid_params)?;
925
926 Ok(hashes)
927 }
928
929 pub fn gas_price(&self) -> CoreBoxFuture<U256> {
930 info!("RPC Request: cfx_gasPrice");
931
932 let light = self.light.clone();
933
934 let fut = async move {
935 Ok(light
936 .gas_price()
937 .await
938 .map_err(|e| e.to_string())
939 .map_err(RpcError::invalid_params)?
940 .unwrap_or(GAS_PRICE_DEFAULT_VALUE.into()))
941 };
942
943 fut.boxed()
944 }
945
946 pub fn interest_rate(
947 &self, epoch: Option<EpochNumber>,
948 ) -> CoreBoxFuture<U256> {
949 let epoch = epoch.unwrap_or(EpochNumber::LatestState).into();
950 info!("RPC Request: cfx_getInterestRate epoch={:?}", epoch);
951
952 let light = self.light.clone();
954
955 let fut = async move {
956 Ok(light
957 .get_interest_rate(epoch)
958 .await
959 .map_err(|e| e.to_string())
960 .map_err(RpcError::invalid_params)?)
961 };
962
963 fut.boxed()
964 }
965
966 pub fn accumulate_interest_rate(
967 &self, epoch: Option<EpochNumber>,
968 ) -> CoreBoxFuture<U256> {
969 let epoch = epoch.unwrap_or(EpochNumber::LatestState).into();
970
971 info!(
972 "RPC Request: cfx_getAccumulateInterestRate epoch={:?}",
973 epoch
974 );
975
976 let light = self.light.clone();
978
979 let fut = async move {
980 Ok(light
981 .get_accumulate_interest_rate(epoch)
982 .await
983 .map_err(|e| e.to_string())
984 .map_err(RpcError::invalid_params)?)
985 };
986
987 fut.boxed()
988 }
989
990 pub fn pos_economics(
991 &self, epoch: Option<EpochNumber>,
992 ) -> CoreBoxFuture<PoSEconomics> {
993 let epoch = epoch.unwrap_or(EpochNumber::LatestState).into();
994
995 info!("RPC Request: cfx_getPoSEconomics epoch={:?}", epoch);
996
997 let light = self.light.clone();
999
1000 let fut = async move {
1001 Ok(light
1002 .get_pos_economics(epoch)
1003 .await
1004 .map(|ans| PoSEconomics {
1005 total_pos_staking_tokens: ans[0],
1006 distributable_pos_interest: ans[1],
1007 last_distribute_block: ans[2].as_u64().into(),
1008 })
1009 .map_err(|e| e.to_string())
1010 .map_err(RpcError::invalid_params)?)
1011 };
1012
1013 fut.boxed()
1014 }
1015
1016 fn check_balance_against_transaction(
1017 &self, account_addr: RpcAddress, contract_addr: RpcAddress,
1018 gas_limit: U256, gas_price: U256, storage_limit: U256,
1019 epoch: Option<EpochNumber>,
1020 ) -> CoreBoxFuture<CheckBalanceAgainstTransactionResponse> {
1021 let epoch: primitives::EpochNumber =
1022 epoch.unwrap_or(EpochNumber::LatestState).into();
1023
1024 info!(
1025 "RPC Request: cfx_checkBalanceAgainstTransaction account_addr={:?} contract_addr={:?} gas_limit={:?} gas_price={:?} storage_limit={:?} epoch={:?}",
1026 account_addr, contract_addr, gas_limit, gas_price, storage_limit, epoch
1027 );
1028
1029 let light = self.light.clone();
1031
1032 let fut = async move {
1033 Self::check_address_network(account_addr.network, &light)?;
1034 Self::check_address_network(contract_addr.network, &light)?;
1035
1036 let account_addr: H160 = account_addr.into();
1037 let contract_addr: H160 = contract_addr.into();
1038
1039 if storage_limit > U256::from(std::u64::MAX) {
1040 bail!(RpcError::invalid_params(format!("storage_limit has to be within the range of u64 but {} supplied!", storage_limit)));
1041 }
1042
1043 let (user_account, contract_account, is_sponsored) =
1045 future::try_join3(
1046 light.get_account(epoch.clone(), account_addr),
1047 light.get_account(epoch.clone(), contract_addr),
1048 light.is_user_sponsored(epoch, contract_addr, account_addr),
1049 )
1050 .await?;
1051
1052 Ok(common::check_balance_against_transaction(
1053 user_account,
1054 contract_account,
1055 is_sponsored,
1056 gas_limit,
1057 gas_price,
1058 storage_limit,
1059 ))
1060 };
1061
1062 fut.boxed()
1063 }
1064
1065 fn fee_history(
1066 &self, mut block_count: HexU64, newest_block: EpochNumber,
1067 reward_percentiles: Option<Vec<f64>>,
1068 ) -> CoreBoxFuture<CfxFeeHistory> {
1069 info!(
1070 "RPC Request: cfx_feeHistory: block_count={}, newest_block={:?}, reward_percentiles={:?}",
1071 block_count, newest_block, reward_percentiles
1072 );
1073
1074 if block_count.as_u64() == 0 {
1075 return async { Ok(FeeHistory::new().into()) }.boxed();
1076 }
1077
1078 if block_count.as_u64() > MAX_FEE_HISTORY_CACHE_BLOCK_COUNT {
1079 block_count = HexU64::from(MAX_FEE_HISTORY_CACHE_BLOCK_COUNT);
1080 }
1081
1082 let consensus_graph = self.consensus.clone();
1084 let light = self.light.clone();
1085 let reward_percentiles = reward_percentiles.unwrap_or_default();
1086
1087 let fut = async move {
1088 let start_height: u64 = light
1089 .get_height_from_epoch_number(newest_block.into())
1090 .map_err(|e| e.to_string())
1091 .map_err(RpcError::invalid_params)?;
1092
1093 let mut current_height = start_height;
1094
1095 let mut fee_history = FeeHistory::new();
1096
1097 while current_height
1098 >= start_height.saturating_sub(block_count.as_u64() - 1)
1099 {
1100 let block = fetch_block_for_fee_history(
1101 consensus_graph.clone(),
1102 light.clone(),
1103 current_height,
1104 )
1105 .await?;
1106
1107 let transactions = block
1108 .transactions
1109 .iter()
1110 .filter(|tx| tx.space() == Space::Native)
1111 .map(|x| &**x);
1112 fee_history
1115 .push_front_block(
1116 Space::Native,
1117 &reward_percentiles,
1118 &block.block_header,
1119 transactions,
1120 )
1121 .map_err(|_| RpcError::internal_error())?;
1122
1123 if current_height == 0 {
1124 break;
1125 } else {
1126 current_height -= 1;
1127 }
1128 }
1129
1130 let block = fetch_block_for_fee_history(
1131 consensus_graph.clone(),
1132 light.clone(),
1133 start_height + 1,
1134 )
1135 .await?;
1136 let oldest_block = if current_height == 0 {
1137 0
1138 } else {
1139 current_height + 1
1140 };
1141 fee_history.finish(
1142 oldest_block,
1143 block.block_header.base_price().as_ref(),
1144 Space::Native,
1145 );
1146 Ok(fee_history.into())
1147 };
1148
1149 fut.boxed()
1150 }
1151}
1152
1153async fn fetch_block_for_fee_history(
1154 consensus_graph: Arc<ConsensusGraph>, light: Arc<QueryService>, height: u64,
1155) -> cfxcore::errors::Result<primitives::Block> {
1156 let hash = consensus_graph
1157 .inner
1158 .read()
1159 .get_pivot_hash_from_epoch_number(height)
1160 .map_err(RpcError::invalid_params)?;
1161
1162 match light.retrieve_block(hash).await? {
1163 None => Err(RpcError::internal_error().into()),
1164 Some(b) => Ok(b),
1165 }
1166}
1167
1168pub struct CfxHandler {
1169 common: Arc<CommonImpl>,
1170 rpc_impl: Arc<RpcImpl>,
1171}
1172
1173impl CfxHandler {
1174 pub fn new(common: Arc<CommonImpl>, rpc_impl: Arc<RpcImpl>) -> Self {
1175 CfxHandler { common, rpc_impl }
1176 }
1177}
1178
1179impl Cfx for CfxHandler {
1180 delegate! {
1181 to self.common {
1182 fn best_block_hash(&self) -> JsonRpcResult<H256>;
1183 fn confirmation_risk_by_hash(&self, block_hash: H256) -> JsonRpcResult<Option<U256>>;
1184 fn get_client_version(&self) -> JsonRpcResult<String>;
1185 fn get_status(&self) -> JsonRpcResult<RpcStatus>;
1186 fn skipped_blocks_by_epoch(&self, num: EpochNumber) -> JsonRpcResult<Vec<H256>>;
1187 fn account_pending_info(&self, addr: RpcAddress) -> BoxFuture<JsonRpcResult<Option<AccountPendingInfo>>>;
1188 }
1189
1190 to self.rpc_impl {
1191 fn account(&self, address: RpcAddress, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<RpcAccount>>;
1192 fn accumulate_interest_rate(&self, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<U256>>;
1193 fn admin(&self, address: RpcAddress, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<Option<RpcAddress>>>;
1194 fn balance(&self, address: RpcAddress, block_hash_or_epoch_number: Option<BlockHashOrEpochNumber>) -> BoxFuture<JsonRpcResult<U256>>;
1195 fn block_by_epoch_number(&self, epoch_num: EpochNumber, include_txs: bool) -> BoxFuture<JsonRpcResult<Option<RpcBlock>>>;
1196 fn block_by_hash_with_pivot_assumption(&self, block_hash: H256, pivot_hash: H256, epoch_number: U64) -> BoxFuture<JsonRpcResult<RpcBlock>>;
1197 fn block_by_hash(&self, hash: H256, include_txs: bool) -> BoxFuture<JsonRpcResult<Option<RpcBlock>>>;
1198 fn blocks_by_epoch(&self, num: EpochNumber) -> JsonRpcResult<Vec<H256>>;
1199 fn check_balance_against_transaction(&self, account_addr: RpcAddress, contract_addr: RpcAddress, gas_limit: U256, gas_price: U256, storage_limit: U256, epoch: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<CheckBalanceAgainstTransactionResponse>>;
1200 fn code(&self, address: RpcAddress, block_hash_or_epoch_num: Option<BlockHashOrEpochNumber>) -> BoxFuture<JsonRpcResult<Bytes>>;
1201 fn collateral_for_storage(&self, address: RpcAddress, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<U256>>;
1202 fn deposit_list(&self, address: RpcAddress, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<Vec<DepositInfo>>>;
1203 fn epoch_number(&self, epoch_num: Option<EpochNumber>) -> JsonRpcResult<U256>;
1204 fn gas_price(&self) -> BoxFuture<JsonRpcResult<U256>>;
1205 fn get_logs(&self, filter: CfxRpcLogFilter) -> BoxFuture<JsonRpcResult<Vec<RpcLog>>>;
1206 fn interest_rate(&self, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<U256>>;
1207 fn next_nonce(&self, address: RpcAddress, num: Option<BlockHashOrEpochNumber>) -> BoxFuture<JsonRpcResult<U256>>;
1208 fn pos_economics(&self, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<PoSEconomics>>;
1209 fn send_raw_transaction(&self, raw: Bytes) -> JsonRpcResult<H256>;
1210 fn sponsor_info(&self, address: RpcAddress, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<SponsorInfo>>;
1211 fn staking_balance(&self, address: RpcAddress, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<U256>>;
1212 fn storage_at(&self, addr: RpcAddress, pos: U256, block_hash_or_epoch_number: Option<BlockHashOrEpochNumber>) -> BoxFuture<JsonRpcResult<Option<H256>>>;
1213 fn storage_root(&self, address: RpcAddress, epoch_num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<Option<StorageRoot>>>;
1214 fn transaction_by_hash(&self, hash: H256) -> BoxFuture<JsonRpcResult<Option<RpcTransaction>>>;
1215 fn transaction_receipt(&self, tx_hash: H256) -> BoxFuture<JsonRpcResult<Option<RpcReceipt>>>;
1216 fn vote_list(&self, address: RpcAddress, num: Option<EpochNumber>) -> BoxFuture<JsonRpcResult<Vec<VoteStakeInfo>>>;
1217 fn fee_history(&self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Option<Vec<f64>>) -> BoxFuture<JsonRpcResult<CfxFeeHistory>>;
1218 }
1219 }
1220
1221 not_supported! {
1223 fn account_pending_transactions(&self, address: RpcAddress, maybe_start_nonce: Option<U256>, maybe_limit: Option<U64>) -> BoxFuture<JsonRpcResult<AccountPendingTransactions>>;
1224 fn block_by_block_number(&self, block_number: U64, include_txs: bool) -> BoxFuture<JsonRpcResult<Option<RpcBlock>>>;
1225 fn call(&self, request: TransactionRequest, block_hash_or_epoch_number: Option<BlockHashOrEpochNumber>) -> JsonRpcResult<Bytes>;
1226 fn estimate_gas_and_collateral(&self, request: TransactionRequest, epoch_num: Option<EpochNumber>) -> JsonRpcResult<EstimateGasAndCollateralResponse>;
1227 fn get_block_reward_info(&self, num: EpochNumber) -> JsonRpcResult<Vec<RpcRewardInfo>>;
1228 fn get_supply_info(&self, epoch_num: Option<EpochNumber>) -> JsonRpcResult<TokenSupplyInfo>;
1229 fn get_collateral_info(&self, epoch_num: Option<EpochNumber>) -> JsonRpcResult<StorageCollateralInfo>;
1230 fn get_vote_params(&self, epoch_num: Option<EpochNumber>) -> JsonRpcResult<VoteParamsInfo>;
1231 fn get_pos_reward_by_epoch(&self, epoch: EpochNumber) -> JsonRpcResult<Option<PoSEpochReward>>;
1232 fn get_fee_burnt(&self, epoch: Option<EpochNumber>) -> JsonRpcResult<U256>;
1233 fn max_priority_fee_per_gas(&self) -> BoxFuture<JsonRpcResult<U256>>;
1234 }
1235}
1236
1237pub struct TestRpcImpl {
1238 common: Arc<CommonImpl>,
1239 }
1241
1242impl TestRpcImpl {
1243 pub fn new(common: Arc<CommonImpl>, _rpc_impl: Arc<RpcImpl>) -> Self {
1244 TestRpcImpl {
1245 common, }
1247 }
1248}
1249
1250impl TestRpc for TestRpcImpl {
1251 delegate! {
1252 to self.common {
1253 fn add_latency(&self, id: NodeId, latency_ms: f64) -> JsonRpcResult<()>;
1254 fn add_peer(&self, node_id: NodeId, address: SocketAddr) -> JsonRpcResult<()>;
1255 fn chain(&self) -> JsonRpcResult<Vec<RpcBlock>>;
1256 fn drop_peer(&self, node_id: NodeId, address: SocketAddr) -> JsonRpcResult<()>;
1257 fn get_block_count(&self) -> JsonRpcResult<u64>;
1258 fn get_goodput(&self) -> JsonRpcResult<String>;
1259 fn get_nodeid(&self, challenge: Vec<u8>) -> JsonRpcResult<Vec<u8>>;
1260 fn get_peer_info(&self) -> JsonRpcResult<Vec<PeerInfo>>;
1261 fn save_node_db(&self) -> JsonRpcResult<()>;
1262 fn say_hello(&self) -> JsonRpcResult<String>;
1263 fn stop(&self) -> JsonRpcResult<()>;
1264 fn pos_register(&self, voting_power: U64, version: Option<u8>) -> JsonRpcResult<(Bytes, AccountAddress)>;
1265 fn pos_update_voting_power(
1266 &self, pos_account: AccountAddress, increased_voting_power: U64,
1267 ) -> JsonRpcResult<()>;
1268 fn pos_stop_election(&self) -> JsonRpcResult<Option<u64>>;
1269 fn pos_start_voting(&self, initialize: bool) -> JsonRpcResult<()>;
1270 fn pos_stop_voting(&self) -> JsonRpcResult<()>;
1271 fn pos_voting_status(&self) -> JsonRpcResult<bool>;
1272 fn pos_start(&self) -> JsonRpcResult<()>;
1273 fn pos_force_vote_proposal(&self, block_id: H256) -> JsonRpcResult<()>;
1274 fn pos_force_propose(&self, round: U64, parent_block_id: H256, payload: Vec<TransactionPayload>) -> JsonRpcResult<()>;
1275 fn pos_trigger_timeout(&self, timeout_type: String) -> JsonRpcResult<()>;
1276 fn pos_force_sign_pivot_decision(&self, block_hash: H256, height: U64) -> JsonRpcResult<()>;
1277 fn pos_get_chosen_proposal(&self) -> JsonRpcResult<Option<PosBlock>>;
1278 }
1279 }
1280
1281 not_supported! {
1282 fn expire_block_gc(&self, timeout: u64) -> JsonRpcResult<()>;
1283 fn generate_block_with_blame_info(&self, num_txs: usize, block_size_limit: usize, blame_info: BlameInfo) -> JsonRpcResult<H256>;
1284 fn generate_block_with_fake_txs(&self, raw_txs_without_data: Bytes, adaptive: Option<bool>, tx_data_len: Option<usize>) -> JsonRpcResult<H256>;
1285 fn generate_block_with_nonce_and_timestamp(&self, parent: H256, referees: Vec<H256>, raw: Bytes, nonce: U256, timestamp: u64, adaptive: bool) -> JsonRpcResult<H256>;
1286 fn generate_custom_block(&self, parent_hash: H256, referee: Vec<H256>, raw_txs: Bytes, adaptive: Option<bool>, custom: Option<Vec<Bytes>>) -> JsonRpcResult<H256>;
1287 fn generate_empty_blocks(&self, num_blocks: usize) -> JsonRpcResult<Vec<H256>>;
1288 fn generate_fixed_block(&self, parent_hash: H256, referee: Vec<H256>, num_txs: usize, adaptive: bool, difficulty: Option<u64>, pos_reference: Option<H256>) -> JsonRpcResult<H256>;
1289 fn generate_one_block_with_direct_txgen(&self, num_txs: usize, block_size_limit: usize, num_txs_simple: usize, num_txs_erc20: usize) -> JsonRpcResult<H256>;
1290 fn generate_one_block(&self, num_txs: usize, block_size_limit: usize) -> JsonRpcResult<H256>;
1291 fn get_block_status(&self, block_hash: H256) -> JsonRpcResult<(u8, bool)>;
1292 fn get_executed_info(&self, block_hash: H256) -> JsonRpcResult<(H256, H256)> ;
1293 fn get_pivot_chain_and_weight(&self, height_range: Option<(u64, u64)>) -> JsonRpcResult<Vec<(H256, U256)>>;
1294 fn send_usable_genesis_accounts(&self, account_start_index: usize) -> JsonRpcResult<Bytes>;
1295 fn set_db_crash(&self, crash_probability: f64, crash_exit_code: i32) -> JsonRpcResult<()>;
1296 }
1297}
1298
1299pub struct DebugRpcImpl {
1300 common: Arc<CommonImpl>,
1301 rpc_impl: Arc<RpcImpl>,
1302}
1303
1304impl DebugRpcImpl {
1305 pub fn new(common: Arc<CommonImpl>, rpc_impl: Arc<RpcImpl>) -> Self {
1306 DebugRpcImpl { common, rpc_impl }
1307 }
1308}
1309
1310impl LocalRpc for DebugRpcImpl {
1311 delegate! {
1312 to self.common {
1313 fn txpool_content(&self, address: Option<RpcAddress>) -> JsonRpcResult<
1314 BTreeMap<String, BTreeMap<String, BTreeMap<usize, Vec<RpcTransaction>>>>>;
1315 fn txpool_inspect(&self, address: Option<RpcAddress>) -> JsonRpcResult<
1316 BTreeMap<String, BTreeMap<String, BTreeMap<usize, Vec<String>>>>>;
1317 fn txpool_get_account_transactions(&self, address: RpcAddress) -> JsonRpcResult<Vec<RpcTransaction>>;
1318 fn txpool_clear(&self) -> JsonRpcResult<()>;
1319 fn accounts(&self) -> JsonRpcResult<Vec<RpcAddress>>;
1320 fn lock_account(&self, address: RpcAddress) -> JsonRpcResult<bool>;
1321 fn net_disconnect_node(&self, id: NodeId, op: Option<UpdateNodeOperation>) -> JsonRpcResult<bool>;
1322 fn net_node(&self, id: NodeId) -> JsonRpcResult<Option<(String, Node)>>;
1323 fn net_sessions(&self, node_id: Option<NodeId>) -> JsonRpcResult<Vec<SessionDetails>>;
1324 fn net_throttling(&self) -> JsonRpcResult<throttling::Service>;
1325 fn new_account(&self, password: String) -> JsonRpcResult<RpcAddress>;
1326 fn sign(&self, data: Bytes, address: RpcAddress, password: Option<String>) -> JsonRpcResult<H520>;
1327 fn unlock_account(&self, address: RpcAddress, password: String, duration: Option<U128>) -> JsonRpcResult<bool>;
1328 }
1329
1330 to self.rpc_impl {
1331 fn send_transaction(&self, tx: TransactionRequest, password: Option<String>) -> BoxFuture<JsonRpcResult<H256>>;
1332 }
1333 }
1334
1335 not_supported! {
1336 fn consensus_graph_state(&self) -> JsonRpcResult<ConsensusGraphStates>;
1337 fn current_sync_phase(&self) -> JsonRpcResult<String>;
1338 fn epoch_receipts(&self, epoch: BlockHashOrEpochNumber, include_eth_recepits: Option<bool>) -> JsonRpcResult<Option<Vec<Vec<RpcReceipt>>>>;
1339 fn epoch_receipt_proof_by_transaction(&self, tx_hash: H256) -> JsonRpcResult<Option<EpochReceiptProof>>;
1340 fn stat_on_gas_load(&self, epoch: EpochNumber, time_window: U64) -> JsonRpcResult<Option<StatOnGasLoad>>;
1341 fn sign_transaction(&self, tx: TransactionRequest, password: Option<String>) -> JsonRpcResult<String>;
1342 fn sync_graph_state(&self) -> JsonRpcResult<SyncGraphStates>;
1343 fn transactions_by_epoch(&self, epoch_number: U64) -> JsonRpcResult<Vec<WrapTransaction>>;
1344 fn transactions_by_block(&self, block_hash: H256) -> JsonRpcResult<Vec<WrapTransaction>>;
1345 }
1346}