|
| 1 | +// Copyright 2021 Parity Technologies (UK) Ltd. |
| 2 | +// This file is part of Parity Bridges Common. |
| 3 | + |
| 4 | +// Parity Bridges Common 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 | +// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. |
| 16 | + |
| 17 | +//! Bridge transaction priority calculator. |
| 18 | +//! |
| 19 | +//! We want to prioritize message delivery transactions with more messages over |
| 20 | +//! transactions with less messages. That's because we reject delivery transactions |
| 21 | +//! if it contains already delivered message. And if some transaction delivers |
| 22 | +//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will |
| 23 | +//! be rejected. This can lower bridge throughput down to one message per block. |
| 24 | +
|
| 25 | +use bp_messages::MessageNonce; |
| 26 | +use frame_support::traits::Get; |
| 27 | +use sp_runtime::transaction_validity::TransactionPriority; |
| 28 | + |
| 29 | +// reexport everything from `integrity_tests` module |
| 30 | +pub use integrity_tests::*; |
| 31 | + |
| 32 | +/// Compute priority boost for message delivery transaction that delivers |
| 33 | +/// given number of messages. |
| 34 | +pub fn compute_priority_boost<PriorityBoostPerMessage>( |
| 35 | + messages: MessageNonce, |
| 36 | +) -> TransactionPriority |
| 37 | +where |
| 38 | + PriorityBoostPerMessage: Get<TransactionPriority>, |
| 39 | +{ |
| 40 | + // we don't want any boost for transaction with single message => minus one |
| 41 | + PriorityBoostPerMessage::get().saturating_mul(messages - 1) |
| 42 | +} |
| 43 | + |
| 44 | +#[cfg(not(feature = "integrity-test"))] |
| 45 | +mod integrity_tests {} |
| 46 | + |
| 47 | +#[cfg(feature = "integrity-test")] |
| 48 | +mod integrity_tests { |
| 49 | + use super::compute_priority_boost; |
| 50 | + |
| 51 | + use bp_messages::MessageNonce; |
| 52 | + use bp_runtime::PreComputedSize; |
| 53 | + use frame_support::{ |
| 54 | + dispatch::{DispatchClass, DispatchInfo, Dispatchable, Pays, PostDispatchInfo}, |
| 55 | + traits::Get, |
| 56 | + }; |
| 57 | + use pallet_bridge_messages::WeightInfoExt; |
| 58 | + use pallet_transaction_payment::OnChargeTransaction; |
| 59 | + use sp_runtime::{ |
| 60 | + traits::{UniqueSaturatedInto, Zero}, |
| 61 | + transaction_validity::TransactionPriority, |
| 62 | + FixedPointOperand, SaturatedConversion, Saturating, |
| 63 | + }; |
| 64 | + |
| 65 | + type BalanceOf<T> = |
| 66 | + <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction< |
| 67 | + T, |
| 68 | + >>::Balance; |
| 69 | + |
| 70 | + /// Ensures that the value of `PriorityBoostPerMessage` matches the value of |
| 71 | + /// `tip_boost_per_message`. |
| 72 | + /// |
| 73 | + /// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have almost |
| 74 | + /// the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want to be sure |
| 75 | + /// that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the priority will be close |
| 76 | + /// to `TX2` as well. |
| 77 | + pub fn ensure_priority_boost_is_sane<Runtime, MessagesInstance, PriorityBoostPerMessage>( |
| 78 | + tip_boost_per_message: BalanceOf<Runtime>, |
| 79 | + ) where |
| 80 | + Runtime: |
| 81 | + pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>, |
| 82 | + MessagesInstance: 'static, |
| 83 | + PriorityBoostPerMessage: Get<TransactionPriority>, |
| 84 | + Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, |
| 85 | + BalanceOf<Runtime>: Send + Sync + FixedPointOperand, |
| 86 | + { |
| 87 | + let priority_boost_per_message = PriorityBoostPerMessage::get(); |
| 88 | + let maximal_messages_in_delivery_transaction = |
| 89 | + Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); |
| 90 | + for messages in 1..=maximal_messages_in_delivery_transaction { |
| 91 | + let base_priority = estimate_message_delivery_transaction_priority::< |
| 92 | + Runtime, |
| 93 | + MessagesInstance, |
| 94 | + >(messages, Zero::zero()); |
| 95 | + let priority_boost = compute_priority_boost::<PriorityBoostPerMessage>(messages); |
| 96 | + let priority_with_boost = base_priority + priority_boost; |
| 97 | + |
| 98 | + let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into()); |
| 99 | + let priority_with_tip = |
| 100 | + estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(1, tip); |
| 101 | + |
| 102 | + const ERROR_MARGIN: TransactionPriority = 5; // 5% |
| 103 | + if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) / |
| 104 | + priority_with_tip > |
| 105 | + ERROR_MARGIN |
| 106 | + { |
| 107 | + panic!( |
| 108 | + "The PriorityBoostPerMessage value ({}) must be fixed to: {}", |
| 109 | + priority_boost_per_message, |
| 110 | + compute_priority_boost_per_message::<Runtime, MessagesInstance>( |
| 111 | + tip_boost_per_message |
| 112 | + ), |
| 113 | + ); |
| 114 | + } |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + /// Compute priority boost that we give to message delivery transaction for additional message. |
| 119 | + #[cfg(feature = "integrity-test")] |
| 120 | + fn compute_priority_boost_per_message<Runtime, MessagesInstance>( |
| 121 | + tip_boost_per_message: BalanceOf<Runtime>, |
| 122 | + ) -> TransactionPriority |
| 123 | + where |
| 124 | + Runtime: |
| 125 | + pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>, |
| 126 | + MessagesInstance: 'static, |
| 127 | + Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, |
| 128 | + BalanceOf<Runtime>: Send + Sync + FixedPointOperand, |
| 129 | + { |
| 130 | + // esimate priority of transaction that delivers one message and has large tip |
| 131 | + let maximal_messages_in_delivery_transaction = |
| 132 | + Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); |
| 133 | + let small_with_tip_priority = |
| 134 | + estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>( |
| 135 | + 1, |
| 136 | + tip_boost_per_message |
| 137 | + .saturating_mul(maximal_messages_in_delivery_transaction.saturated_into()), |
| 138 | + ); |
| 139 | + // estimate priority of transaction that delivers maximal number of messages, but has no tip |
| 140 | + let large_without_tip_priority = estimate_message_delivery_transaction_priority::< |
| 141 | + Runtime, |
| 142 | + MessagesInstance, |
| 143 | + >(maximal_messages_in_delivery_transaction, Zero::zero()); |
| 144 | + |
| 145 | + small_with_tip_priority |
| 146 | + .saturating_sub(large_without_tip_priority) |
| 147 | + .saturating_div(maximal_messages_in_delivery_transaction - 1) |
| 148 | + } |
| 149 | + |
| 150 | + /// Estimate message delivery transaction priority. |
| 151 | + #[cfg(feature = "integrity-test")] |
| 152 | + fn estimate_message_delivery_transaction_priority<Runtime, MessagesInstance>( |
| 153 | + messages: MessageNonce, |
| 154 | + tip: BalanceOf<Runtime>, |
| 155 | + ) -> TransactionPriority |
| 156 | + where |
| 157 | + Runtime: |
| 158 | + pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>, |
| 159 | + MessagesInstance: 'static, |
| 160 | + Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, |
| 161 | + BalanceOf<Runtime>: Send + Sync + FixedPointOperand, |
| 162 | + { |
| 163 | + // just an estimation of extra transaction bytes that are added to every transaction |
| 164 | + // (including signature, signed extensions extra and etc + in our case it includes |
| 165 | + // all call arguments extept the proof itself) |
| 166 | + let base_tx_size = 512; |
| 167 | + // let's say we are relaying similar small messages and for every message we add more trie |
| 168 | + // nodes to the proof (x0.5 because we expect some nodes to be reused) |
| 169 | + let estimated_message_size = 512; |
| 170 | + // let's say all our messages have the same dispatch weight |
| 171 | + let estimated_message_dispatch_weight = |
| 172 | + Runtime::WeightInfo::message_dispatch_weight(estimated_message_size); |
| 173 | + // messages proof argument size is (for every message) messages size + some additional |
| 174 | + // trie nodes. Some of them are reused by different messages, so let's take 2/3 of default |
| 175 | + // "overhead" constant |
| 176 | + let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size() |
| 177 | + .saturating_mul(2) |
| 178 | + .saturating_div(3) |
| 179 | + .saturating_add(estimated_message_size) |
| 180 | + .saturating_mul(messages as _); |
| 181 | + |
| 182 | + // finally we are able to estimate transaction size and weight |
| 183 | + let transaction_size = base_tx_size.saturating_add(messages_proof_size); |
| 184 | + let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight( |
| 185 | + &PreComputedSize(transaction_size as _), |
| 186 | + messages as _, |
| 187 | + estimated_message_dispatch_weight.saturating_mul(messages), |
| 188 | + ); |
| 189 | + |
| 190 | + pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority( |
| 191 | + &DispatchInfo { |
| 192 | + weight: transaction_weight, |
| 193 | + class: DispatchClass::Normal, |
| 194 | + pays_fee: Pays::Yes, |
| 195 | + }, |
| 196 | + transaction_size as _, |
| 197 | + tip, |
| 198 | + Zero::zero(), |
| 199 | + ) |
| 200 | + } |
| 201 | +} |
0 commit comments