|
3 | 3 | // TODO: Restore once https://github.com/rust-lang/rust/issues/122105 is resolved |
4 | 4 | // #![deny(unused_crate_dependencies)] |
5 | 5 |
|
| 6 | +mod aux_schema; |
6 | 7 | pub mod worker; |
7 | 8 |
|
| 9 | +use crate::aux_schema::get_channel_processed_state; |
8 | 10 | use async_channel::TrySendError; |
9 | 11 | use cross_domain_message_gossip::{ |
10 | 12 | can_allow_xdm_submission, get_channel_state, get_xdm_processed_block_number, |
11 | 13 | set_xdm_message_processed_at, BlockId, Message as GossipMessage, |
12 | 14 | MessageData as GossipMessageData, RELAYER_PREFIX, |
13 | 15 | }; |
14 | 16 | use parity_scale_codec::{Codec, Encode}; |
| 17 | +use rand::seq::SliceRandom; |
15 | 18 | use sc_client_api::{AuxStore, HeaderBackend, ProofProvider, StorageProof}; |
16 | 19 | use sc_utils::mpsc::TracingUnboundedSender; |
17 | 20 | use sp_api::{ApiRef, ProvideRuntimeApi}; |
18 | 21 | use sp_core::{H256, U256}; |
19 | | -use sp_domains::DomainsApi; |
| 22 | +use sp_domains::{ChannelId, DomainsApi}; |
20 | 23 | use sp_messenger::messages::{ |
21 | | - BlockMessageWithStorageKey, BlockMessagesWithStorageKey, ChainId, CrossDomainMessage, Proof, |
| 24 | + BlockMessageWithStorageKey, BlockMessagesQuery, BlockMessagesWithStorageKey, ChainId, |
| 25 | + ChannelState, CrossDomainMessage, Nonce, Proof, |
22 | 26 | }; |
23 | 27 | use sp_messenger::{MessengerApi, RelayerApi, XdmId, MAX_FUTURE_ALLOWED_NONCES}; |
24 | 28 | use sp_mmr_primitives::MmrApi; |
25 | 29 | use sp_runtime::traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, One}; |
26 | | -use sp_runtime::ArithmeticError; |
| 30 | +use sp_runtime::{ArithmeticError, SaturatedConversion, Saturating}; |
27 | 31 | use sp_subspace_mmr::ConsensusChainMmrLeafProof; |
| 32 | +use std::cmp::max; |
28 | 33 | use std::marker::PhantomData; |
29 | 34 | use std::sync::Arc; |
30 | 35 | use subspace_runtime_primitives::BlockHashFor; |
31 | 36 | use tracing::log; |
32 | 37 |
|
| 38 | +const CHANNEL_PROCESSED_STATE_CACHE_LIMIT: u32 = 15; |
| 39 | + |
33 | 40 | /// The logging target. |
34 | 41 | const LOG_TARGET: &str = "message::relayer"; |
35 | 42 |
|
@@ -644,3 +651,190 @@ where |
644 | 651 | Ok(proof) |
645 | 652 | } |
646 | 653 | } |
| 654 | + |
| 655 | +// Fetch the unprocessed XDMs at a given block |
| 656 | +fn fetch_messages<Backend, Client, Block, CBlock>( |
| 657 | + backend: &Backend, |
| 658 | + client: &Arc<Client>, |
| 659 | + fetch_message_at: Block::Hash, |
| 660 | + self_chain_id: ChainId, |
| 661 | +) -> Result<Vec<BlockMessagesQuery>, Error> |
| 662 | +where |
| 663 | + Block: BlockT, |
| 664 | + Block::Hash: From<H256>, |
| 665 | + CBlock: BlockT, |
| 666 | + Client: ProvideRuntimeApi<Block> + HeaderBackend<Block>, |
| 667 | + Client::Api: RelayerApi<Block, NumberFor<Block>, NumberFor<CBlock>, CBlock::Hash>, |
| 668 | + Backend: AuxStore, |
| 669 | +{ |
| 670 | + let runtime_api = client.runtime_api(); |
| 671 | + let mut queries = runtime_api |
| 672 | + .channels_and_state(fetch_message_at)? |
| 673 | + .into_iter() |
| 674 | + .filter_map(|(dst_chain_id, channel_id, channel_state)| { |
| 675 | + get_channel_state_query( |
| 676 | + backend, |
| 677 | + client, |
| 678 | + fetch_message_at, |
| 679 | + self_chain_id, |
| 680 | + dst_chain_id, |
| 681 | + channel_id, |
| 682 | + channel_state, |
| 683 | + ) |
| 684 | + .ok() |
| 685 | + .flatten() |
| 686 | + }) |
| 687 | + .collect::<Vec<_>>(); |
| 688 | + |
| 689 | + // pick random 15 queries |
| 690 | + Ok(if queries.len() <= 15 { |
| 691 | + queries |
| 692 | + } else { |
| 693 | + let mut rng = rand::thread_rng(); |
| 694 | + queries.shuffle(&mut rng); |
| 695 | + queries.truncate(15); |
| 696 | + queries |
| 697 | + }) |
| 698 | +} |
| 699 | + |
| 700 | +fn get_channel_state_query<Backend, Client, Block>( |
| 701 | + backend: &Backend, |
| 702 | + client: &Arc<Client>, |
| 703 | + fetch_message_at: Block::Hash, |
| 704 | + self_chain_id: ChainId, |
| 705 | + dst_chain_id: ChainId, |
| 706 | + channel_id: ChannelId, |
| 707 | + local_channel_state: ChannelState, |
| 708 | +) -> Result<Option<BlockMessagesQuery>, Error> |
| 709 | +where |
| 710 | + Backend: AuxStore, |
| 711 | + Block: BlockT, |
| 712 | + Block::Hash: From<H256>, |
| 713 | + Client: HeaderBackend<Block>, |
| 714 | +{ |
| 715 | + let maybe_dst_channel_state = |
| 716 | + get_channel_state(backend, dst_chain_id, self_chain_id, channel_id) |
| 717 | + .ok() |
| 718 | + .flatten(); |
| 719 | + |
| 720 | + let maybe_channel_processed_state = |
| 721 | + if let Some(state) = get_channel_processed_state(backend, dst_chain_id, channel_id)? { |
| 722 | + match client.hash(state.block_number.into()).ok().flatten() { |
| 723 | + // there is no block at this number, could be due to re-org |
| 724 | + None => None, |
| 725 | + Some(hash) => { |
| 726 | + if hash != state.block_hash.into() { |
| 727 | + // client re-org'ed, allow xdm submission |
| 728 | + None |
| 729 | + } else { |
| 730 | + // check if the state is still valid from the current block |
| 731 | + match client.number(fetch_message_at)? { |
| 732 | + None => Some(state), |
| 733 | + Some(current_block_number) => { |
| 734 | + let block_limit: NumberFor<Block> = |
| 735 | + CHANNEL_PROCESSED_STATE_CACHE_LIMIT.saturated_into(); |
| 736 | + |
| 737 | + if state.block_number |
| 738 | + >= current_block_number |
| 739 | + .saturating_sub(block_limit) |
| 740 | + .saturated_into() |
| 741 | + { |
| 742 | + Some(state) |
| 743 | + } else { |
| 744 | + None |
| 745 | + } |
| 746 | + } |
| 747 | + } |
| 748 | + } |
| 749 | + } |
| 750 | + } |
| 751 | + } else { |
| 752 | + None |
| 753 | + }; |
| 754 | + |
| 755 | + Ok( |
| 756 | + match (maybe_dst_channel_state, maybe_channel_processed_state) { |
| 757 | + // don't have any info on channel, so assume from the beginning |
| 758 | + (None, None) => Some(BlockMessagesQuery { |
| 759 | + chain_id: dst_chain_id, |
| 760 | + channel_id, |
| 761 | + outbox_from: Nonce::zero(), |
| 762 | + inbox_responses_from: Nonce::zero(), |
| 763 | + }), |
| 764 | + // don't have channel processed state, so use the dst_channel state for query |
| 765 | + (Some(dst_channel_state), None) => Some(BlockMessagesQuery { |
| 766 | + chain_id: dst_chain_id, |
| 767 | + channel_id, |
| 768 | + outbox_from: dst_channel_state.next_inbox_nonce, |
| 769 | + inbox_responses_from: dst_channel_state |
| 770 | + .latest_response_received_message_nonce |
| 771 | + // pick the next inbox message response nonce or default to zero |
| 772 | + .map(|nonce| nonce.saturating_add(One::one())) |
| 773 | + .unwrap_or(Nonce::zero()), |
| 774 | + }), |
| 775 | + // don't have dst channel state, so use the last processed channel state |
| 776 | + (None, Some(channel_state)) => Some(BlockMessagesQuery { |
| 777 | + chain_id: dst_chain_id, |
| 778 | + channel_id, |
| 779 | + outbox_from: channel_state.last_outbox_nonce.saturating_add(One::one()), |
| 780 | + inbox_responses_from: channel_state |
| 781 | + .last_inbox_message_response_nonce |
| 782 | + .saturating_add(One::one()), |
| 783 | + }), |
| 784 | + (Some(dst_channel_state), Some(channel_state)) => { |
| 785 | + let next_outbox_nonce = max( |
| 786 | + dst_channel_state.next_inbox_nonce, |
| 787 | + channel_state.last_outbox_nonce.saturating_add(One::one()), |
| 788 | + ); |
| 789 | + |
| 790 | + let next_inbox_response_nonce = max( |
| 791 | + dst_channel_state |
| 792 | + .latest_response_received_message_nonce |
| 793 | + .map(|nonce| nonce.saturating_add(One::one())) |
| 794 | + .unwrap_or(Nonce::zero()), |
| 795 | + channel_state |
| 796 | + .last_inbox_message_response_nonce |
| 797 | + .saturating_add(One::one()), |
| 798 | + ); |
| 799 | + |
| 800 | + // if the local channel is closed, and |
| 801 | + // last outbox message is already included |
| 802 | + // and |
| 803 | + // if the dst_channel is closed, and |
| 804 | + // last inbox message response is already included |
| 805 | + // we can safely skip the channel as the there is nothing further to send. |
| 806 | + if local_channel_state == ChannelState::Closed |
| 807 | + && dst_channel_state.state == ChannelState::Closed |
| 808 | + { |
| 809 | + let is_last_outbox_nonce = dst_channel_state |
| 810 | + .next_inbox_nonce |
| 811 | + .saturating_sub(One::one()) |
| 812 | + == channel_state.last_outbox_nonce; |
| 813 | + |
| 814 | + let is_last_inbox_message_response_nonce = dst_channel_state |
| 815 | + .latest_response_received_message_nonce |
| 816 | + .unwrap_or(Nonce::zero()) |
| 817 | + == channel_state.last_inbox_message_response_nonce; |
| 818 | + |
| 819 | + if is_last_outbox_nonce && is_last_inbox_message_response_nonce { |
| 820 | + None |
| 821 | + } else { |
| 822 | + Some(BlockMessagesQuery { |
| 823 | + chain_id: dst_chain_id, |
| 824 | + channel_id, |
| 825 | + outbox_from: next_outbox_nonce, |
| 826 | + inbox_responses_from: next_inbox_response_nonce, |
| 827 | + }) |
| 828 | + } |
| 829 | + } else { |
| 830 | + Some(BlockMessagesQuery { |
| 831 | + chain_id: dst_chain_id, |
| 832 | + channel_id, |
| 833 | + outbox_from: next_outbox_nonce, |
| 834 | + inbox_responses_from: next_inbox_response_nonce, |
| 835 | + }) |
| 836 | + } |
| 837 | + } |
| 838 | + }, |
| 839 | + ) |
| 840 | +} |
0 commit comments