1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>.
16
use {
17
    cumulus_primitives_core::{ParaId, XcmpMessageFormat},
18
    jsonrpsee::{core::RpcResult, proc_macros::rpc},
19
    parity_scale_codec::Encode,
20
    xcm::{latest::prelude::*, latest::Weight},
21
};
22

            
23
const DEFAULT_PROOF_SIZE: u64 = 64 * 1024;
24

            
25
/// This RPC interface is used to manually submit XCM messages that will be injected into a
26
/// parachain-enabled runtime. This allows testing XCM logic in a controlled way in integration
27
/// tests.
28
1016
#[rpc(server)]
29
#[jsonrpsee::core::async_trait]
30
pub trait ManualXcmApi {
31
    /// Inject a downward xcm message - A message that comes from the relay chain.
32
    /// You may provide an arbitrary message, or if you provide an emtpy byte array,
33
    /// Then a default message (DOT transfer down to ALITH) will be injected
34
    #[method(name = "xcm_injectDownwardMessage")]
35
    async fn inject_downward_message(&self, message: Vec<u8>) -> RpcResult<()>;
36

            
37
    /// Inject an HRMP message - A message that comes from a dedicated channel to a sibling
38
    /// parachain.
39
    ///
40
    /// Cumulus Parachain System seems to have a constraint that at most one hrmp message will be
41
    /// sent on a channel per block. At least that's what this comment implies:
42
    /// https://github.com/paritytech/cumulus/blob/c308c01b/pallets/parachain-system/src/lib.rs#L204
43
    /// Neither this RPC, nor the mock inherent data provider make any attempt to enforce this
44
    /// constraint. In fact, violating it may be useful for testing.
45
    /// The method accepts a sending paraId and a bytearray representing an arbitrary message as
46
    /// parameters. If you provide an emtpy byte array, then a default message representing a
47
    /// transfer of the sending paraId's native token will be injected.
48
    #[method(name = "xcm_injectHrmpMessage")]
49
    async fn inject_hrmp_message(&self, sender: ParaId, message: Vec<u8>) -> RpcResult<()>;
50
}
51

            
52
pub struct ManualXcm {
53
    pub downward_message_channel: flume::Sender<Vec<u8>>,
54
    pub hrmp_message_channel: flume::Sender<(ParaId, Vec<u8>)>,
55
}
56

            
57
#[jsonrpsee::core::async_trait]
58
impl ManualXcmApiServer for ManualXcm {
59
210
    async fn inject_downward_message(&self, msg: Vec<u8>) -> RpcResult<()> {
60
210
        let downward_message_channel = self.downward_message_channel.clone();
61
        // If no message is supplied, inject a default one.
62
210
        let msg = if msg.is_empty() {
63
            xcm::VersionedXcm::<()>::from(Xcm(vec![
64
                ReserveAssetDeposited((Parent, 10000000000000u128).into()),
65
                ClearOrigin,
66
                BuyExecution {
67
                    fees: (Parent, 10000000000000u128).into(),
68
                    weight_limit: Limited(Weight::from_parts(
69
                        4_000_000_000u64,
70
                        DEFAULT_PROOF_SIZE * 2,
71
                    )),
72
                },
73
                DepositAsset {
74
                    assets: AllCounted(1).into(),
75
                    beneficiary: Location::new(
76
                        0,
77
                        [AccountKey20 {
78
                            network: None,
79
                            key: hex_literal::hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"),
80
                        }],
81
                    ),
82
                },
83
            ]))
84
            .encode()
85
        } else {
86
210
            msg
87
        };
88

            
89
        // Push the message to the shared channel where it will be queued up
90
        // to be injected in to an upcoming block.
91
210
        downward_message_channel
92
210
            .send_async(msg)
93
            .await
94
210
            .map_err(|err| internal_err(err.to_string()))?;
95

            
96
210
        Ok(())
97
420
    }
98

            
99
90
    async fn inject_hrmp_message(&self, sender: ParaId, msg: Vec<u8>) -> RpcResult<()> {
100
90
        let hrmp_message_channel = self.hrmp_message_channel.clone();
101

            
102
        // If no message is supplied, inject a default one.
103
90
        let msg = if msg.is_empty() {
104
            let mut mes = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
105
            mes.append(
106
                &mut (xcm::VersionedXcm::<()>::from(Xcm(vec![
107
                    ReserveAssetDeposited(
108
                        ((Parent, Parachain(sender.into())), 10000000000000u128).into(),
109
                    ),
110
                    ClearOrigin,
111
                    BuyExecution {
112
                        fees: ((Parent, Parachain(sender.into())), 10000000000000u128).into(),
113
                        weight_limit: Limited(Weight::from_parts(
114
                            4_000_000_000u64,
115
                            DEFAULT_PROOF_SIZE,
116
                        )),
117
                    },
118
                    DepositAsset {
119
                        assets: AllCounted(1).into(),
120
                        beneficiary: Location::new(
121
                            0,
122
                            [AccountKey20 {
123
                                network: None,
124
                                key: hex_literal::hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"),
125
                            }],
126
                        ),
127
                    },
128
                ]))
129
                .encode()),
130
            );
131
            mes
132
        } else {
133
90
            msg
134
        };
135

            
136
        // Push the message to the shared channel where it will be queued up
137
        // to be injected in to an upcoming block.
138
90
        hrmp_message_channel
139
90
            .send_async((sender, msg))
140
            .await
141
90
            .map_err(|err| internal_err(err.to_string()))?;
142

            
143
90
        Ok(())
144
180
    }
145
}
146

            
147
// This bit cribbed from frontier.
148
pub fn internal_err<T: AsRef<str>>(message: T) -> jsonrpsee::types::ErrorObjectOwned {
149
    jsonrpsee::types::error::ErrorObject::borrowed(
150
        jsonrpsee::types::error::INTERNAL_ERROR_CODE,
151
        message.as_ref(),
152
        None,
153
    )
154
    .into_owned()
155
}