mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-15 06:45:44 +00:00
002d9260f9
**Update:** Pushed additional changes based on the review comments. **This pull request fixes various spelling mistakes in this repository.** Most of the changes are contained in the first **3** commits: - `Fix spelling mistakes in comments and docs` - `Fix spelling mistakes in test names` - `Fix spelling mistakes in error messages, panic messages, logs and tracing` Other source code spelling mistakes are separated into individual commits for easier reviewing: - `Fix the spelling of 'authority'` - `Fix the spelling of 'REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY'` - `Fix the spelling of 'prev_enqueud_messages'` - `Fix the spelling of 'endpoint'` - `Fix the spelling of 'children'` - `Fix the spelling of 'PenpalSiblingSovereignAccount'` - `Fix the spelling of 'PenpalSudoAccount'` - `Fix the spelling of 'insufficient'` - `Fix the spelling of 'PalletXcmExtrinsicsBenchmark'` - `Fix the spelling of 'subtracted'` - `Fix the spelling of 'CandidatePendingAvailability'` - `Fix the spelling of 'exclusive'` - `Fix the spelling of 'until'` - `Fix the spelling of 'discriminator'` - `Fix the spelling of 'nonexistent'` - `Fix the spelling of 'subsystem'` - `Fix the spelling of 'indices'` - `Fix the spelling of 'committed'` - `Fix the spelling of 'topology'` - `Fix the spelling of 'response'` - `Fix the spelling of 'beneficiary'` - `Fix the spelling of 'formatted'` - `Fix the spelling of 'UNKNOWN_PROOF_REQUEST'` - `Fix the spelling of 'succeeded'` - `Fix the spelling of 'reopened'` - `Fix the spelling of 'proposer'` - `Fix the spelling of 'InstantiationNonce'` - `Fix the spelling of 'depositor'` - `Fix the spelling of 'expiration'` - `Fix the spelling of 'phantom'` - `Fix the spelling of 'AggregatedKeyValue'` - `Fix the spelling of 'randomness'` - `Fix the spelling of 'defendant'` - `Fix the spelling of 'AquaticMammal'` - `Fix the spelling of 'transactions'` - `Fix the spelling of 'PassingTracingSubscriber'` - `Fix the spelling of 'TxSignaturePayload'` - `Fix the spelling of 'versioning'` - `Fix the spelling of 'descendant'` - `Fix the spelling of 'overridden'` - `Fix the spelling of 'network'` Let me know if this structure is adequate. **Note:** The usage of the words `Merkle`, `Merkelize`, `Merklization`, `Merkelization`, `Merkleization`, is somewhat inconsistent but I left it as it is. ~~**Note:** In some places the term `Receival` is used to refer to message reception, IMO `Reception` is the correct word here, but I left it as it is.~~ ~~**Note:** In some places the term `Overlayed` is used instead of the more acceptable version `Overlaid` but I also left it as it is.~~ ~~**Note:** In some places the term `Applyable` is used instead of the correct version `Applicable` but I also left it as it is.~~ **Note:** Some usage of British vs American english e.g. `judgement` vs `judgment`, `initialise` vs `initialize`, `optimise` vs `optimize` etc. are both present in different places, but I suppose that's understandable given the number of contributors. ~~**Note:** There is a spelling mistake in `.github/CODEOWNERS` but it triggers errors in CI when I make changes to it, so I left it as it is.~~
252 lines
7.6 KiB
Rust
252 lines
7.6 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
//! API implementation for broadcasting transactions.
|
|
|
|
use crate::{transaction::api::TransactionBroadcastApiServer, SubscriptionTaskExecutor};
|
|
use codec::Decode;
|
|
use futures::{FutureExt, Stream, StreamExt};
|
|
use futures_util::stream::AbortHandle;
|
|
use jsonrpsee::core::{async_trait, RpcResult};
|
|
use parking_lot::RwLock;
|
|
use rand::{distributions::Alphanumeric, Rng};
|
|
use sc_client_api::BlockchainEvents;
|
|
use sc_transaction_pool_api::{
|
|
error::IntoPoolError, TransactionFor, TransactionPool, TransactionSource,
|
|
};
|
|
use sp_blockchain::HeaderBackend;
|
|
use sp_core::Bytes;
|
|
use sp_runtime::traits::Block as BlockT;
|
|
use std::{collections::HashMap, sync::Arc};
|
|
|
|
use super::error::ErrorBroadcast;
|
|
|
|
/// An API for transaction RPC calls.
|
|
pub struct TransactionBroadcast<Pool, Client> {
|
|
/// Substrate client.
|
|
client: Arc<Client>,
|
|
/// Transactions pool.
|
|
pool: Arc<Pool>,
|
|
/// Executor to spawn subscriptions.
|
|
executor: SubscriptionTaskExecutor,
|
|
/// The broadcast operation IDs.
|
|
broadcast_ids: Arc<RwLock<HashMap<String, BroadcastState>>>,
|
|
}
|
|
|
|
/// The state of a broadcast operation.
|
|
struct BroadcastState {
|
|
/// Handle to abort the running future that broadcasts the transaction.
|
|
handle: AbortHandle,
|
|
}
|
|
|
|
impl<Pool, Client> TransactionBroadcast<Pool, Client> {
|
|
/// Creates a new [`TransactionBroadcast`].
|
|
pub fn new(client: Arc<Client>, pool: Arc<Pool>, executor: SubscriptionTaskExecutor) -> Self {
|
|
TransactionBroadcast { client, pool, executor, broadcast_ids: Default::default() }
|
|
}
|
|
|
|
/// Generate an unique operation ID for the `transaction_broadcast` RPC method.
|
|
pub fn generate_unique_id(&self) -> String {
|
|
let generate_operation_id = || {
|
|
// The length of the operation ID.
|
|
const OPERATION_ID_LEN: usize = 16;
|
|
|
|
rand::thread_rng()
|
|
.sample_iter(Alphanumeric)
|
|
.take(OPERATION_ID_LEN)
|
|
.map(char::from)
|
|
.collect::<String>()
|
|
};
|
|
|
|
let mut id = generate_operation_id();
|
|
|
|
let broadcast_ids = self.broadcast_ids.read();
|
|
|
|
while broadcast_ids.contains_key(&id) {
|
|
id = generate_operation_id();
|
|
}
|
|
|
|
id
|
|
}
|
|
}
|
|
|
|
/// Currently we treat all RPC transactions as externals.
|
|
///
|
|
/// Possibly in the future we could allow opt-in for special treatment
|
|
/// of such transactions, so that the block authors can inject
|
|
/// some unique transactions via RPC and have them included in the pool.
|
|
const TX_SOURCE: TransactionSource = TransactionSource::External;
|
|
|
|
#[async_trait]
|
|
impl<Pool, Client> TransactionBroadcastApiServer for TransactionBroadcast<Pool, Client>
|
|
where
|
|
Pool: TransactionPool + Sync + Send + 'static,
|
|
Pool::Error: IntoPoolError,
|
|
<Pool::Block as BlockT>::Hash: Unpin,
|
|
Client: HeaderBackend<Pool::Block> + BlockchainEvents<Pool::Block> + Send + Sync + 'static,
|
|
{
|
|
fn broadcast(&self, bytes: Bytes) -> RpcResult<Option<String>> {
|
|
let pool = self.pool.clone();
|
|
|
|
// The unique ID of this operation.
|
|
let id = self.generate_unique_id();
|
|
|
|
let mut best_block_import_stream =
|
|
Box::pin(self.client.import_notification_stream().filter_map(
|
|
|notification| async move { notification.is_new_best.then_some(notification.hash) },
|
|
));
|
|
|
|
let broadcast_transaction_fut = async move {
|
|
// There is nothing we could do with an extrinsic of invalid format.
|
|
let Ok(decoded_extrinsic) = TransactionFor::<Pool>::decode(&mut &bytes[..]) else {
|
|
return;
|
|
};
|
|
|
|
// Flag to determine if the we should broadcast the transaction again.
|
|
let mut is_done = false;
|
|
|
|
while !is_done {
|
|
// Wait for the last block to become available.
|
|
let Some(best_block_hash) =
|
|
last_stream_element(&mut best_block_import_stream).await
|
|
else {
|
|
return;
|
|
};
|
|
|
|
let mut stream = match pool
|
|
.submit_and_watch(best_block_hash, TX_SOURCE, decoded_extrinsic.clone())
|
|
.await
|
|
{
|
|
Ok(stream) => stream,
|
|
// The transaction was not included to the pool.
|
|
Err(e) => {
|
|
let Ok(pool_err) = e.into_pool_error() else { return };
|
|
|
|
if pool_err.is_retriable() {
|
|
// Try to resubmit the transaction at a later block for
|
|
// recoverable errors.
|
|
continue
|
|
} else {
|
|
return;
|
|
}
|
|
},
|
|
};
|
|
|
|
while let Some(event) = stream.next().await {
|
|
// Check if the transaction could be submitted again
|
|
// at a later time.
|
|
if event.is_retriable() {
|
|
break;
|
|
}
|
|
|
|
// Stop if this is the final event of the transaction stream
|
|
// and the event is not retriable.
|
|
if event.is_final() {
|
|
is_done = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Convert the future into an abortable future, for easily terminating it from the
|
|
// `transaction_stop` method.
|
|
let (fut, handle) = futures::future::abortable(broadcast_transaction_fut);
|
|
let broadcast_ids = self.broadcast_ids.clone();
|
|
let drop_id = id.clone();
|
|
// The future expected by the executor must be `Future<Output = ()>` instead of
|
|
// `Future<Output = Result<(), Aborted>>`.
|
|
let fut = fut.map(move |_| {
|
|
// Remove the entry from the broadcast IDs map.
|
|
broadcast_ids.write().remove(&drop_id);
|
|
});
|
|
|
|
// Keep track of this entry and the abortable handle.
|
|
{
|
|
let mut broadcast_ids = self.broadcast_ids.write();
|
|
broadcast_ids.insert(id.clone(), BroadcastState { handle });
|
|
}
|
|
|
|
sc_rpc::utils::spawn_subscription_task(&self.executor, fut);
|
|
|
|
Ok(Some(id))
|
|
}
|
|
|
|
fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast> {
|
|
let mut broadcast_ids = self.broadcast_ids.write();
|
|
|
|
let Some(broadcast_state) = broadcast_ids.remove(&operation_id) else {
|
|
return Err(ErrorBroadcast::InvalidOperationID)
|
|
};
|
|
|
|
broadcast_state.handle.abort();
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Returns the last element of the provided stream, or `None` if the stream is closed.
|
|
async fn last_stream_element<S>(stream: &mut S) -> Option<S::Item>
|
|
where
|
|
S: Stream + Unpin,
|
|
{
|
|
let Some(mut element) = stream.next().await else { return None };
|
|
|
|
// We are effectively polling the stream for the last available item at this time.
|
|
// The `now_or_never` returns `None` if the stream is `Pending`.
|
|
//
|
|
// If the stream contains `Hash0x1 Hash0x2 Hash0x3 Hash0x4`, we want only `Hash0x4`.
|
|
while let Some(next) = stream.next().now_or_never() {
|
|
let Some(next) = next else {
|
|
// Nothing to do if the stream terminated.
|
|
return None
|
|
};
|
|
element = next;
|
|
}
|
|
|
|
Some(element)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tokio_stream::wrappers::ReceiverStream;
|
|
|
|
#[tokio::test]
|
|
async fn check_last_stream_element() {
|
|
let (tx, rx) = tokio::sync::mpsc::channel(16);
|
|
|
|
let mut stream = ReceiverStream::new(rx);
|
|
// Check the stream with one element queued.
|
|
tx.send(1).await.unwrap();
|
|
assert_eq!(last_stream_element(&mut stream).await, Some(1));
|
|
|
|
// Check the stream with multiple elements.
|
|
tx.send(1).await.unwrap();
|
|
tx.send(2).await.unwrap();
|
|
tx.send(3).await.unwrap();
|
|
assert_eq!(last_stream_element(&mut stream).await, Some(3));
|
|
|
|
// Drop the stream with some elements
|
|
tx.send(1).await.unwrap();
|
|
tx.send(2).await.unwrap();
|
|
drop(tx);
|
|
assert_eq!(last_stream_element(&mut stream).await, None);
|
|
}
|
|
}
|