feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+78
View File
@@ -0,0 +1,78 @@
[package]
description = "Bizinikiwi offchain workers"
name = "pezsc-offchain"
version = "29.0.0"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
bytes = { workspace = true, default-features = true }
codec = { features = ["derive"], workspace = true, default-features = true }
fnv = { workspace = true }
futures = { workspace = true }
futures-timer = { workspace = true }
http-body-util = { workspace = true }
hyper = { features = [
"http1",
"http2",
], workspace = true, default-features = true }
hyper-rustls = { workspace = true }
hyper-util = { features = [
"client-legacy",
"http1",
"http2",
], workspace = true }
num_cpus = { workspace = true }
once_cell = { workspace = true }
parking_lot = { workspace = true, default-features = true }
rand = { workspace = true, default-features = true }
rustls = { workspace = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-network-types = { workspace = true, default-features = true }
pezsc-transaction-pool-api = { workspace = true, default-features = true }
pezsc-utils = { workspace = true, default-features = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-externalities = { workspace = true, default-features = true }
pezsp-keystore = { workspace = true, default-features = true }
pezsp-offchain = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
threadpool = { workspace = true }
tracing = { workspace = true, default-features = true }
[dev-dependencies]
async-trait = { workspace = true }
pezsc-block-builder = { workspace = true, default-features = true }
pezsc-client-db = { default-features = true, workspace = true }
pezsc-transaction-pool = { workspace = true, default-features = true }
pezsp-consensus = { workspace = true, default-features = true }
pezsp-tracing = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
tokio = { workspace = true, default-features = true }
[features]
default = []
runtime-benchmarks = [
"pezsc-block-builder/runtime-benchmarks",
"pezsc-client-api/runtime-benchmarks",
"pezsc-client-db/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsc-transaction-pool-api/runtime-benchmarks",
"pezsc-transaction-pool/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-consensus/runtime-benchmarks",
"pezsp-offchain/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
+18
View File
@@ -0,0 +1,18 @@
Bizinikiwi offchain workers.
The offchain workers is a special function of the runtime that
gets executed after block is imported. During execution
it's able to asynchronously submit extrinsics that will either
be propagated to other nodes or added to the next block
produced by the node as unsigned transactions.
Offchain workers can be used for computation-heavy tasks
that are not feasible for execution during regular block processing.
It can either be tasks that no consensus is required for,
or some form of consensus over the data can be built on-chain
for instance via:
1. Challenge period for incorrect computations
2. Majority voting for results
3. etc
License: GPL-3.0-or-later WITH Classpath-exception-2.0
+457
View File
@@ -0,0 +1,457 @@
// This file is part of Bizinikiwi.
// 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/>.
use std::{collections::HashSet, str::FromStr, sync::Arc, thread::sleep};
use crate::NetworkProvider;
use codec::{Decode, Encode};
use futures::Future;
pub use http::SharedClient;
use pezsc_network::Multiaddr;
use pezsc_network_types::PeerId;
use pezsp_core::{
offchain::{
self, HttpError, HttpRequestId, HttpRequestStatus, OpaqueMultiaddr, OpaqueNetworkState,
Timestamp,
},
OpaquePeerId,
};
mod http;
mod timestamp;
/// Asynchronous offchain API.
///
/// NOTE this is done to prevent recursive calls into the runtime
/// (which are not supported currently).
pub(crate) struct Api {
/// A provider for bizinikiwi networking.
network_provider: Arc<dyn NetworkProvider + Send + Sync>,
/// Is this node a potential validator?
is_validator: bool,
/// Everything HTTP-related is handled by a different struct.
http: http::HttpApi,
}
impl offchain::Externalities for Api {
fn is_validator(&self) -> bool {
self.is_validator
}
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
let external_addresses = self.network_provider.external_addresses();
let state = NetworkState::new(self.network_provider.local_peer_id(), external_addresses);
Ok(OpaqueNetworkState::from(state))
}
fn timestamp(&mut self) -> Timestamp {
timestamp::now()
}
fn sleep_until(&mut self, deadline: Timestamp) {
sleep(timestamp::timestamp_from_now(deadline));
}
fn random_seed(&mut self) -> [u8; 32] {
rand::random()
}
fn http_request_start(
&mut self,
method: &str,
uri: &str,
_meta: &[u8],
) -> Result<HttpRequestId, ()> {
self.http.request_start(method, uri)
}
fn http_request_add_header(
&mut self,
request_id: HttpRequestId,
name: &str,
value: &str,
) -> Result<(), ()> {
self.http.request_add_header(request_id, name, value)
}
fn http_request_write_body(
&mut self,
request_id: HttpRequestId,
chunk: &[u8],
deadline: Option<Timestamp>,
) -> Result<(), HttpError> {
self.http.request_write_body(request_id, chunk, deadline)
}
fn http_response_wait(
&mut self,
ids: &[HttpRequestId],
deadline: Option<Timestamp>,
) -> Vec<HttpRequestStatus> {
self.http.response_wait(ids, deadline)
}
fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
self.http.response_headers(request_id)
}
fn http_response_read_body(
&mut self,
request_id: HttpRequestId,
buffer: &mut [u8],
deadline: Option<Timestamp>,
) -> Result<usize, HttpError> {
self.http.response_read_body(request_id, buffer, deadline)
}
fn set_authorized_nodes(&mut self, nodes: Vec<OpaquePeerId>, authorized_only: bool) {
let peer_ids: HashSet<PeerId> =
nodes.into_iter().filter_map(|node| PeerId::from_bytes(&node.0).ok()).collect();
self.network_provider.set_authorized_peers(peer_ids);
self.network_provider.set_authorized_only(authorized_only);
}
}
/// Information about the local node's network state.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct NetworkState {
peer_id: PeerId,
external_addresses: Vec<Multiaddr>,
}
impl NetworkState {
fn new(peer_id: PeerId, external_addresses: Vec<Multiaddr>) -> Self {
NetworkState { peer_id, external_addresses }
}
}
impl From<NetworkState> for OpaqueNetworkState {
fn from(state: NetworkState) -> OpaqueNetworkState {
let enc = Encode::encode(&state.peer_id.to_bytes());
let peer_id = OpaquePeerId::new(enc);
let external_addresses: Vec<OpaqueMultiaddr> = state
.external_addresses
.iter()
.map(|multiaddr| {
let e = Encode::encode(&multiaddr.to_string());
OpaqueMultiaddr::new(e)
})
.collect();
OpaqueNetworkState { peer_id, external_addresses }
}
}
impl TryFrom<OpaqueNetworkState> for NetworkState {
type Error = ();
fn try_from(state: OpaqueNetworkState) -> Result<Self, Self::Error> {
let inner_vec = state.peer_id.0;
let bytes: Vec<u8> = Decode::decode(&mut &inner_vec[..]).map_err(|_| ())?;
let peer_id = PeerId::from_bytes(&bytes).map_err(|_| ())?;
let external_addresses: Result<Vec<Multiaddr>, Self::Error> = state
.external_addresses
.iter()
.map(|enc_multiaddr| -> Result<Multiaddr, Self::Error> {
let inner_vec = &enc_multiaddr.0;
let bytes = <Vec<u8>>::decode(&mut &inner_vec[..]).map_err(|_| ())?;
let multiaddr_str = String::from_utf8(bytes).map_err(|_| ())?;
let multiaddr = Multiaddr::from_str(&multiaddr_str).map_err(|_| ())?;
Ok(multiaddr)
})
.collect();
let external_addresses = external_addresses?;
Ok(NetworkState { peer_id, external_addresses })
}
}
/// Offchain extensions implementation API
///
/// This is the asynchronous processing part of the API.
pub(crate) struct AsyncApi {
/// Everything HTTP-related is handled by a different struct.
http: Option<http::HttpWorker>,
}
impl AsyncApi {
/// Creates new Offchain extensions API implementation and the asynchronous processing part.
pub fn new(
network_provider: Arc<dyn NetworkProvider + Send + Sync>,
is_validator: bool,
shared_http_client: SharedClient,
) -> (Api, Self) {
let (http_api, http_worker) = http::http(shared_http_client);
let api = Api { network_provider, is_validator, http: http_api };
let async_api = Self { http: Some(http_worker) };
(api, async_api)
}
/// Run a processing task for the API
pub fn process(self) -> impl Future<Output = ()> {
self.http.expect("`process` is only called once; qed")
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsc_client_db::offchain::LocalStorage;
use pezsc_network::{
config::MultiaddrWithPeerId, types::ProtocolName, NetworkPeers, NetworkStateInfo,
ObservedRole, ReputationChange,
};
use pezsp_core::offchain::{storage::OffchainDb, DbExternalities, Externalities, StorageKind};
use std::time::SystemTime;
pub(super) struct TestNetwork();
#[async_trait::async_trait]
impl NetworkPeers for TestNetwork {
fn set_authorized_peers(&self, _peers: HashSet<PeerId>) {
unimplemented!();
}
fn set_authorized_only(&self, _reserved_only: bool) {
unimplemented!();
}
fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) {
unimplemented!();
}
fn report_peer(&self, _peer_id: PeerId, _cost_benefit: ReputationChange) {
unimplemented!();
}
fn peer_reputation(&self, _peer_id: &PeerId) -> i32 {
unimplemented!()
}
fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) {
unimplemented!();
}
fn accept_unreserved_peers(&self) {
unimplemented!();
}
fn deny_unreserved_peers(&self) {
unimplemented!();
}
fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> {
unimplemented!();
}
fn remove_reserved_peer(&self, _peer_id: PeerId) {
unimplemented!();
}
fn set_reserved_peers(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn add_peers_to_reserved_set(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn remove_peers_from_reserved_set(
&self,
_protocol: ProtocolName,
_peers: Vec<PeerId>,
) -> Result<(), String> {
unimplemented!();
}
fn sync_num_connected(&self) -> usize {
unimplemented!();
}
fn peer_role(&self, _peer_id: PeerId, _handshake: Vec<u8>) -> Option<ObservedRole> {
None
}
async fn reserved_peers(&self) -> Result<Vec<PeerId>, ()> {
unimplemented!();
}
}
impl NetworkStateInfo for TestNetwork {
fn external_addresses(&self) -> Vec<Multiaddr> {
Vec::new()
}
fn local_peer_id(&self) -> PeerId {
PeerId::random()
}
fn listen_addresses(&self) -> Vec<Multiaddr> {
Vec::new()
}
}
fn offchain_api() -> (Api, AsyncApi) {
pezsp_tracing::try_init_simple();
let mock = Arc::new(TestNetwork());
let shared_client = SharedClient::new().unwrap();
AsyncApi::new(mock, false, shared_client)
}
fn offchain_db() -> OffchainDb<LocalStorage> {
OffchainDb::new(LocalStorage::new_test())
}
#[test]
fn should_get_timestamp() {
let mut api = offchain_api().0;
// Get timestamp from std.
let now = SystemTime::now();
let d: u64 = now
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis()
.try_into()
.unwrap();
// Get timestamp from offchain api.
let timestamp = api.timestamp();
// Compare.
assert!(timestamp.unix_millis() > 0);
assert!(timestamp.unix_millis() >= d);
}
#[test]
fn should_sleep() {
let mut api = offchain_api().0;
// Arrange.
let now = api.timestamp();
let delta = pezsp_core::offchain::Duration::from_millis(100);
let deadline = now.add(delta);
// Act.
api.sleep_until(deadline);
let new_now = api.timestamp();
// Assert.
// The diff could be more than the sleep duration.
assert!(new_now.unix_millis() - 100 >= now.unix_millis());
}
#[test]
fn should_set_get_and_clear_local_storage() {
// given
let kind = StorageKind::PERSISTENT;
let mut api = offchain_db();
let key = b"test";
// when
assert_eq!(api.local_storage_get(kind, key), None);
api.local_storage_set(kind, key, b"value");
// then
assert_eq!(api.local_storage_get(kind, key), Some(b"value".to_vec()));
// when
api.local_storage_clear(kind, key);
// then
assert_eq!(api.local_storage_get(kind, key), None);
}
#[test]
fn should_compare_and_set_local_storage() {
// given
let kind = StorageKind::PERSISTENT;
let mut api = offchain_db();
let key = b"test";
api.local_storage_set(kind, key, b"value");
// when
assert_eq!(api.local_storage_compare_and_set(kind, key, Some(b"val"), b"xxx"), false);
assert_eq!(api.local_storage_get(kind, key), Some(b"value".to_vec()));
// when
assert_eq!(api.local_storage_compare_and_set(kind, key, Some(b"value"), b"xxx"), true);
assert_eq!(api.local_storage_get(kind, key), Some(b"xxx".to_vec()));
}
#[test]
fn should_compare_and_set_local_storage_with_none() {
// given
let kind = StorageKind::PERSISTENT;
let mut api = offchain_db();
let key = b"test";
// when
let res = api.local_storage_compare_and_set(kind, key, None, b"value");
// then
assert_eq!(res, true);
assert_eq!(api.local_storage_get(kind, key), Some(b"value".to_vec()));
}
#[test]
fn should_convert_network_states() {
// given
let state = NetworkState::new(
PeerId::random(),
vec![
Multiaddr::try_from("/ip4/127.0.0.1/tcp/1234".to_string()).unwrap(),
Multiaddr::try_from("/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21").unwrap(),
],
);
// when
let opaque_state = OpaqueNetworkState::from(state.clone());
let converted_back_state = NetworkState::try_from(opaque_state).unwrap();
// then
assert_eq!(state, converted_back_state);
}
#[test]
fn should_get_random_seed() {
// given
let mut api = offchain_api().0;
let seed = api.random_seed();
// then
assert_ne!(seed, [0; 32]);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
// This file is part of Bizinikiwi.
// 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/>.
//! Helper methods dedicated to timestamps.
use pezsp_core::offchain::Timestamp;
use std::time::{Duration, SystemTime};
/// Returns the current time as a `Timestamp`.
pub fn now() -> Timestamp {
let now = SystemTime::now();
let epoch_duration = now.duration_since(SystemTime::UNIX_EPOCH);
match epoch_duration {
Err(_) => {
// Current time is earlier than UNIX_EPOCH.
Timestamp::from_unix_millis(0)
},
Ok(d) => {
let duration = d.as_millis();
// Assuming overflow won't happen for a few hundred years.
Timestamp::from_unix_millis(
duration
.try_into()
.expect("epoch milliseconds won't overflow u64 for hundreds of years; qed"),
)
},
}
}
/// Returns how a `Timestamp` compares to "now".
///
/// In other words, returns `timestamp - now()`.
pub fn timestamp_from_now(timestamp: Timestamp) -> Duration {
Duration::from_millis(timestamp.diff(&now()).millis())
}
/// Converts the deadline into a `Future` that resolves when the deadline is reached.
///
/// If `None`, returns a never-ending `Future`.
pub fn deadline_to_future(
deadline: Option<Timestamp>,
) -> futures::future::MaybeDone<impl futures::Future<Output = ()>> {
use futures::future::{self, Either};
future::maybe_done(match deadline.map(timestamp_from_now) {
None => Either::Left(future::pending()),
// Only apply delay if we need to wait a non-zero duration
Some(duration) if duration <= Duration::from_secs(0) =>
Either::Right(Either::Left(future::ready(()))),
Some(duration) => Either::Right(Either::Right(futures_timer::Delay::new(duration))),
})
}
+521
View File
@@ -0,0 +1,521 @@
// This file is part of Bizinikiwi.
// 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/>.
//! Bizinikiwi offchain workers.
//!
//! The offchain workers is a special function of the runtime that
//! gets executed after block is imported. During execution
//! it's able to asynchronously submit extrinsics that will either
//! be propagated to other nodes or added to the next block
//! produced by the node as unsigned transactions.
//!
//! Offchain workers can be used for computation-heavy tasks
//! that are not feasible for execution during regular block processing.
//! It can either be tasks that no consensus is required for,
//! or some form of consensus over the data can be built on-chain
//! for instance via:
//! 1. Challenge period for incorrect computations
//! 2. Majority voting for results
//! 3. etc
#![warn(missing_docs)]
use std::{fmt, sync::Arc};
use futures::{
future::{ready, Future},
prelude::*,
};
use parking_lot::Mutex;
use pezsc_client_api::BlockchainEvents;
use pezsc_network::{NetworkPeers, NetworkStateInfo};
use pezsc_transaction_pool_api::OffchainTransactionPoolFactory;
use pezsp_api::{ApiExt, ProvideRuntimeApi};
use pezsp_core::{offchain, traits::SpawnNamed};
use pezsp_externalities::Extension;
use pezsp_keystore::{KeystoreExt, KeystorePtr};
use pezsp_runtime::traits::{self, Header};
use threadpool::ThreadPool;
mod api;
pub use pezsp_core::offchain::storage::OffchainDb;
pub use pezsp_offchain::{OffchainWorkerApi, STORAGE_PREFIX};
const LOG_TARGET: &str = "offchain-worker";
/// NetworkProvider provides [`OffchainWorkers`] with all necessary hooks into the
/// underlying Bizinikiwi networking.
pub trait NetworkProvider: NetworkStateInfo + NetworkPeers {}
impl<T> NetworkProvider for T where T: NetworkStateInfo + NetworkPeers {}
/// Special type that implements [`OffchainStorage`](offchain::OffchainStorage).
///
/// This type can not be constructed and should only be used when passing `None` as `offchain_db` to
/// [`OffchainWorkerOptions`] to make the compiler happy.
#[derive(Clone)]
pub enum NoOffchainStorage {}
impl offchain::OffchainStorage for NoOffchainStorage {
fn set(&mut self, _: &[u8], _: &[u8], _: &[u8]) {
unimplemented!("`NoOffchainStorage` can not be constructed!")
}
fn remove(&mut self, _: &[u8], _: &[u8]) {
unimplemented!("`NoOffchainStorage` can not be constructed!")
}
fn get(&self, _: &[u8], _: &[u8]) -> Option<Vec<u8>> {
unimplemented!("`NoOffchainStorage` can not be constructed!")
}
fn compare_and_set(&mut self, _: &[u8], _: &[u8], _: Option<&[u8]>, _: &[u8]) -> bool {
unimplemented!("`NoOffchainStorage` can not be constructed!")
}
}
/// Options for [`OffchainWorkers`]
pub struct OffchainWorkerOptions<RA, Block: traits::Block, Storage, CE> {
/// Provides access to the runtime api.
pub runtime_api_provider: Arc<RA>,
/// Provides access to the keystore.
pub keystore: Option<KeystorePtr>,
/// Provides access to the offchain database.
///
/// Use [`NoOffchainStorage`] as type when passing `None` to have some type that works.
pub offchain_db: Option<Storage>,
/// Provides access to the transaction pool.
pub transaction_pool: Option<OffchainTransactionPoolFactory<Block>>,
/// Provides access to network information.
pub network_provider: Arc<dyn NetworkProvider + Send + Sync>,
/// Is the node running as validator?
pub is_validator: bool,
/// Enable http requests from offchain workers?
///
/// If not enabled, any http request will panic.
pub enable_http_requests: bool,
/// Callback to create custom [`Extension`]s that should be registered for the
/// `offchain_worker` runtime call.
///
/// These [`Extension`]s are registered along-side the default extensions and are accessible in
/// the host functions.
///
/// # Example:
///
/// ```nocompile
/// custom_extensions: |block_hash| {
/// vec![MyCustomExtension::new()]
/// }
/// ```
pub custom_extensions: CE,
}
/// An offchain workers manager.
pub struct OffchainWorkers<RA, Block: traits::Block, Storage> {
runtime_api_provider: Arc<RA>,
thread_pool: Mutex<ThreadPool>,
shared_http_client: api::SharedClient,
enable_http_requests: bool,
keystore: Option<KeystorePtr>,
offchain_db: Option<OffchainDb<Storage>>,
transaction_pool: Option<OffchainTransactionPoolFactory<Block>>,
network_provider: Arc<dyn NetworkProvider + Send + Sync>,
is_validator: bool,
custom_extensions: Box<dyn Fn(Block::Hash) -> Vec<Box<dyn Extension>> + Send>,
}
impl<RA, Block: traits::Block, Storage> OffchainWorkers<RA, Block, Storage> {
/// Creates new [`OffchainWorkers`].
pub fn new<CE: Fn(Block::Hash) -> Vec<Box<dyn Extension>> + Send + 'static>(
OffchainWorkerOptions {
runtime_api_provider,
keystore,
offchain_db,
transaction_pool,
network_provider,
is_validator,
enable_http_requests,
custom_extensions,
}: OffchainWorkerOptions<RA, Block, Storage, CE>,
) -> std::io::Result<Self> {
Ok(Self {
runtime_api_provider,
thread_pool: Mutex::new(ThreadPool::with_name(
"offchain-worker".into(),
num_cpus::get(),
)),
shared_http_client: api::SharedClient::new()?,
enable_http_requests,
keystore,
offchain_db: offchain_db.map(OffchainDb::new),
transaction_pool,
is_validator,
network_provider,
custom_extensions: Box::new(custom_extensions),
})
}
}
impl<RA, Block: traits::Block, Storage: offchain::OffchainStorage> fmt::Debug
for OffchainWorkers<RA, Block, Storage>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OffchainWorkers").finish()
}
}
impl<RA, Block, Storage> OffchainWorkers<RA, Block, Storage>
where
Block: traits::Block,
RA: ProvideRuntimeApi<Block> + Send + Sync + 'static,
RA::Api: OffchainWorkerApi<Block>,
Storage: offchain::OffchainStorage + 'static,
{
/// Run the offchain workers on every block import.
pub async fn run<BE: BlockchainEvents<Block>>(
self,
import_events: Arc<BE>,
spawner: impl SpawnNamed,
) {
import_events
.import_notification_stream()
.for_each(move |n| {
if n.is_new_best {
spawner.spawn(
"offchain-on-block",
Some("offchain-worker"),
self.on_block_imported(&n.header).boxed(),
);
} else {
tracing::debug!(
target: LOG_TARGET,
"Skipping offchain workers for non-canon block: {:?}",
n.header,
)
}
ready(())
})
.await;
}
/// Start the offchain workers after given block.
#[must_use]
fn on_block_imported(&self, header: &Block::Header) -> impl Future<Output = ()> {
let runtime = self.runtime_api_provider.runtime_api();
let hash = header.hash();
let has_api_v1 = runtime.has_api_with::<dyn OffchainWorkerApi<Block>, _>(hash, |v| v == 1);
let has_api_v2 = runtime.has_api_with::<dyn OffchainWorkerApi<Block>, _>(hash, |v| v == 2);
let version = match (has_api_v1, has_api_v2) {
(_, Ok(true)) => 2,
(Ok(true), _) => 1,
err => {
let help =
"Consider turning off offchain workers if they are not part of your runtime.";
tracing::error!(
target: LOG_TARGET,
"Unsupported Offchain Worker API version: {:?}. {}.",
err,
help
);
0
},
};
tracing::debug!(
target: LOG_TARGET,
"Checking offchain workers at {hash:?}: version: {version}",
);
let process = (version > 0).then(|| {
let (api, runner) = api::AsyncApi::new(
self.network_provider.clone(),
self.is_validator,
self.shared_http_client.clone(),
);
tracing::debug!(target: LOG_TARGET, "Spawning offchain workers at {hash:?}");
let header = header.clone();
let client = self.runtime_api_provider.clone();
let mut capabilities = offchain::Capabilities::all();
capabilities.set(offchain::Capabilities::HTTP, self.enable_http_requests);
let keystore = self.keystore.clone();
let db = self.offchain_db.clone();
let tx_pool = self.transaction_pool.clone();
let custom_extensions = (*self.custom_extensions)(hash);
self.spawn_worker(move || {
let mut runtime = client.runtime_api();
let api = Box::new(api);
tracing::debug!(target: LOG_TARGET, "Running offchain workers at {hash:?}");
if let Some(keystore) = keystore {
runtime.register_extension(KeystoreExt(keystore.clone()));
}
if let Some(pool) = tx_pool {
runtime.register_extension(pool.offchain_transaction_pool(hash));
}
if let Some(offchain_db) = db {
runtime.register_extension(offchain::OffchainDbExt::new(
offchain::LimitedExternalities::new(capabilities, offchain_db.clone()),
));
}
runtime.register_extension(offchain::OffchainWorkerExt::new(
offchain::LimitedExternalities::new(capabilities, api),
));
custom_extensions.into_iter().for_each(|ext| runtime.register_extension(ext));
let run = if version == 2 {
runtime.offchain_worker(hash, &header)
} else {
#[allow(deprecated)]
runtime.offchain_worker_before_version_2(hash, *header.number())
};
if let Err(e) = run {
tracing::error!(
target: LOG_TARGET,
"Error running offchain workers at {:?}: {}",
hash,
e
);
}
});
runner.process()
});
async move {
futures::future::OptionFuture::from(process).await;
}
}
/// Spawns a new offchain worker.
///
/// We spawn offchain workers for each block in a separate thread,
/// since they can run for a significant amount of time
/// in a blocking fashion and we don't want to block the runtime.
///
/// Note that we should avoid that if we switch to future-based runtime in the future,
/// alternatively:
fn spawn_worker(&self, f: impl FnOnce() -> () + Send + 'static) {
self.thread_pool.lock().execute(f);
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::executor::block_on;
use pezsc_block_builder::BlockBuilderBuilder;
use pezsc_client_api::Backend as _;
use pezsc_network::{
config::MultiaddrWithPeerId, types::ProtocolName, Multiaddr, ObservedRole, ReputationChange,
};
use pezsc_network_types::PeerId;
use pezsc_transaction_pool::BasicPool;
use pezsc_transaction_pool_api::{InPoolTransaction, TransactionPool};
use pezsp_consensus::BlockOrigin;
use pezsp_runtime::traits::Block as BlockT;
use std::{collections::HashSet, sync::Arc};
use bizinikiwi_test_runtime_client::{
runtime::{
bizinikiwi_test_pallet::pallet::Call as PalletCall, ExtrinsicBuilder, RuntimeCall,
},
ClientBlockImportExt, DefaultTestClientBuilderExt, TestClientBuilderExt,
};
struct TestNetwork();
impl NetworkStateInfo for TestNetwork {
fn external_addresses(&self) -> Vec<Multiaddr> {
Vec::new()
}
fn local_peer_id(&self) -> PeerId {
PeerId::random()
}
fn listen_addresses(&self) -> Vec<Multiaddr> {
Vec::new()
}
}
#[async_trait::async_trait]
impl NetworkPeers for TestNetwork {
fn set_authorized_peers(&self, _peers: HashSet<PeerId>) {
unimplemented!();
}
fn set_authorized_only(&self, _reserved_only: bool) {
unimplemented!();
}
fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) {
unimplemented!();
}
fn report_peer(&self, _peer_id: PeerId, _cost_benefit: ReputationChange) {
unimplemented!();
}
fn peer_reputation(&self, _peer_id: &PeerId) -> i32 {
unimplemented!()
}
fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) {
unimplemented!();
}
fn accept_unreserved_peers(&self) {
unimplemented!();
}
fn deny_unreserved_peers(&self) {
unimplemented!();
}
fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> {
unimplemented!();
}
fn remove_reserved_peer(&self, _peer_id: PeerId) {
unimplemented!();
}
fn set_reserved_peers(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn add_peers_to_reserved_set(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn remove_peers_from_reserved_set(
&self,
_protocol: ProtocolName,
_peers: Vec<PeerId>,
) -> Result<(), String> {
unimplemented!();
}
fn sync_num_connected(&self) -> usize {
unimplemented!();
}
fn peer_role(&self, _peer_id: PeerId, _handshake: Vec<u8>) -> Option<ObservedRole> {
None
}
async fn reserved_peers(&self) -> Result<Vec<PeerId>, ()> {
unimplemented!();
}
}
#[test]
fn should_call_into_runtime_and_produce_extrinsic() {
pezsp_tracing::try_init_simple();
let client = Arc::new(bizinikiwi_test_runtime_client::new());
let spawner = pezsp_core::testing::TaskExecutor::new();
let pool = Arc::from(BasicPool::new_full(
Default::default(),
true.into(),
None,
spawner,
client.clone(),
));
let network = Arc::new(TestNetwork());
let header = client.header(client.chain_info().genesis_hash).unwrap().unwrap();
// when
let offchain = OffchainWorkers::new(OffchainWorkerOptions {
runtime_api_provider: client,
keystore: None,
offchain_db: None::<NoOffchainStorage>,
transaction_pool: Some(OffchainTransactionPoolFactory::new(pool.clone())),
network_provider: network,
is_validator: false,
enable_http_requests: false,
custom_extensions: |_| Vec::new(),
})
.unwrap();
futures::executor::block_on(offchain.on_block_imported(&header));
// then
assert_eq!(pool.status().ready, 1);
assert!(matches!(
pool.ready().next().unwrap().data().function,
RuntimeCall::BizinikiwiTest(PalletCall::storage_change { .. })
));
}
#[test]
fn offchain_index_set_and_clear_works() {
use pezsp_core::offchain::OffchainStorage;
pezsp_tracing::try_init_simple();
let (client, backend) = bizinikiwi_test_runtime_client::TestClientBuilder::new()
.enable_offchain_indexing_api()
.build_with_backend();
let client = Arc::new(client);
let offchain_db = backend.offchain_storage().unwrap();
let key = &b"hello"[..];
let value = &b"world"[..];
let mut block_builder = BlockBuilderBuilder::new(&*client)
.on_parent_block(client.chain_info().genesis_hash)
.with_parent_block_number(0)
.build()
.unwrap();
let ext = ExtrinsicBuilder::new_offchain_index_set(key.to_vec(), value.to_vec()).build();
block_builder.push(ext).unwrap();
let block = block_builder.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block.clone())).unwrap();
assert_eq!(value, &offchain_db.get(pezsp_offchain::STORAGE_PREFIX, &key).unwrap());
let mut block_builder = BlockBuilderBuilder::new(&*client)
.on_parent_block(block.hash())
.with_parent_block_number(1)
.build()
.unwrap();
let ext = ExtrinsicBuilder::new_offchain_index_clear(key.to_vec()).nonce(1).build();
block_builder.push(ext).unwrap();
let block = block_builder.build().unwrap().block;
block_on(client.import(BlockOrigin::Own, block)).unwrap();
assert!(offchain_db.get(pezsp_offchain::STORAGE_PREFIX, &key).is_none());
}
}