fix: Convert vendor/pezkuwi-subxt from submodule to regular directory

This commit is contained in:
2025-12-19 16:45:24 +03:00
parent 9a52edf0df
commit fdd023c499
393 changed files with 154124 additions and 1 deletions
+37
View File
@@ -0,0 +1,37 @@
# subxt-historic changelog
This is separate from the Subxt changelog as subxt-historic is currently releasaed separately.
Eventually this project will merge with Subxt and no longer exist, but until then it's being maintained and updated where needed.
## 0.0.8 (2025-12-04)
Expose `ClientAtBlock::resolver()`. This hands back a type resolver which is capable of resolving type IDs given by the `.visit()` methods on extrinsic fields and storage values. The extrinsics example has been modified to show how this can be used.
## 0.0.7 (2025-12-03)
Expose `OfflineClientAtBlock`, `OfflineClientAtBlockT`, `OnlinelientAtBlock`, `OnlineClientAtBlockT`.
This is so that you can pass the `ClientAtBlock` into functions like so:
```rust
use subxt_historic::config::Config;
use subxt_historic::client::{ ClientAtBlock, OnlineClientAtBlock, OnlineClientAtBlockT };
fn accepts_client_at_block_concrete<T: Config>(client: &ClientAtBlock<OnlineClientAtBlock<'_, T>, T>) {
// ...
}
fn accepts_client_at_block_generic<'conf, T: Config + 'conf, C: OnlineClientAtBlockT<'conf, T>>(client: &ClientAtBlock<C, T>) {
// ...
}
```
## 0.0.6 (2025-12-01)
- Add `.metadata()` on `ClientAtBlock` to expose the current metadata at some block.
## 0.0.5 (2025-11-21)
- Rename some fields for consistency.
- Update versions of underlying libraries being used.
- Add `.visit()` methods to extrinsic fields and storage values, and examples of using this to our examples.
+63
View File
@@ -0,0 +1,63 @@
[package]
name = "subxt-historic"
version = "0.0.8"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
publish = true
license.workspace = true
readme = "README.md"
repository.workspace = true
documentation.workspace = true
homepage.workspace = true
description = "Download non head-of-chain blocks and state from Substrate based nodes"
keywords = ["parity", "substrate", "blockchain"]
[lints]
workspace = true
[features]
default = ["jsonrpsee", "native"]
# Enable this for native (ie non web/wasm builds).
# Exactly 1 of "web" and "native" is expected.
native = [
"subxt-rpcs/native",
]
# Enable this for web/wasm builds.
# Exactly 1 of "web" and "native" is expected.
web = [
"subxt-rpcs/web",
]
# Enable this to use the reconnecting rpc client
reconnecting-rpc-client = ["subxt-rpcs/reconnecting-rpc-client"]
# Enable this to use jsonrpsee, which enables the jsonrpsee RPC client, and
# a couple of util functions which rely on jsonrpsee.
jsonrpsee = [
"subxt-rpcs/jsonrpsee",
]
[dependencies]
pezkuwi-subxt-rpcs = { workspace = true }
frame-decode = { workspace = true, features = ["legacy", "legacy-types"] }
frame-metadata = { workspace = true, features = ["std", "legacy"] }
scale-type-resolver = { workspace = true, features = ["scale-info"] }
codec = { workspace = true }
primitive-types = { workspace = true }
scale-info = { workspace = true }
scale-info-legacy = { workspace = true }
scale-decode = { workspace = true }
thiserror = { workspace = true }
pezsp-crypto-hashing = { workspace = true }
url = { workspace = true }
futures = { workspace = true }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
scale-value = { workspace = true }
scale-decode = { workspace = true, features = ["derive"] }
hex = { workspace = true }
+16
View File
@@ -0,0 +1,16 @@
# subxt-historic
**This crate is a work in progress and currently is released only as a preview.**
While `subxt` is a library for working at the head of a chain (submitting transactions and obtaining the current state), `subxt-historic` is a library for decoding blocks and state that are anywhere in a chain. To broadly summarize the differences:
| Feature | subxt | subxt-historic |
|-----------------------------------------|------------------------------|-------------------------------|
| Block access | Head of chain | Any block in chain |
| Connection to chain | Light client or RPC node | Archive RPC nodes only |
| Transaction submission | Yes | No |
| Metadata compatibility | V14 and newer | Any version |
# Examples
See the [examples](https://github.com/paritytech/subxt/tree/master/historic/examples) folder for examples of how to use `subxt-historic`.
+495
View File
@@ -0,0 +1,495 @@
#![allow(missing_docs)]
use subxt_historic::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
// Configuration for the Polkadot relay chain.
let config = PolkadotConfig::new();
// Create an online client for the Polkadot relay chain, pointed at a Polkadot archive node.
let client = OnlineClient::from_url(config, "wss://rpc.polkadot.io").await?;
// Iterate through some randomly selected old blocks to show how to fetch and decode extrinsics.
for block_number in 1234567.. {
println!("=== Block {block_number} ===");
// Point the client at a specific block number. By default this will download and cache
// metadata for the required spec version (so it's cheaper to instantiate again), if it
// hasn't already, and borrow the relevant legacy types from the client.
let client_at_block = client.at(block_number).await?;
// Fetch the extrinsics at that block.
let extrinsics = client_at_block.extrinsics().fetch().await?;
// Now, we have various operations to work with them. Here we print out various details
// about each extrinsic.
for extrinsic in extrinsics.iter() {
println!(
"{}.{}",
extrinsic.call().pallet_name(),
extrinsic.call().name()
);
if let Some(signature) = extrinsic.signature_bytes() {
println!(" Signature: 0x{}", hex::encode(signature));
}
println!(" Call Data:");
// We can decode each of the fields (in this example we decode everything into a
// scale_value::Value type, which can represent any SCALE encoded data, but if you
// have an idea of the type then you can try to decode into that type instead):
for field in extrinsic.call().fields().iter() {
// We can visit fields, which gives us the ability to inspect and decode information
// from them selectively, returning whatever we like from it. Here we demo our
// type name visitor which is defined below:
let tn = field
.visit(type_name::GetTypeName::new())?
.unwrap_or_default();
// When visiting fields we can also decode into a custom shape like so:
let _custom_value =
field.visit(value::GetValue::new(&client_at_block.resolver()))?;
// We can also obtain and decode things without the complexity of the above:
println!(
" {}: {} {}",
field.name(),
field.decode_as::<scale_value::Value>().unwrap(),
if tn.is_empty() {
String::new()
} else {
format!("(type name: {tn})")
},
);
}
// Or, all of them at once:
println!(
" All: {}",
extrinsic
.call()
.fields()
.decode_as::<scale_value::Composite<_>>()
.unwrap()
);
// We can also look at things like the transaction extensions:
if let Some(extensions) = extrinsic.transaction_extensions() {
println!(" Transaction Extensions:");
// We can decode each of them:
for extension in extensions.iter() {
println!(
" {}: {}",
extension.name(),
extension.decode_as::<scale_value::Value>().unwrap()
);
}
// Or all of them at once:
println!(
" All: {}",
extensions.decode_as::<scale_value::Composite<_>>().unwrap()
);
}
}
}
Ok(())
}
/// This module defines an example visitor which retrieves the name of a type.
/// This is a more advanced use case and can typically be avoided.
mod type_name {
use scale_decode::{
Visitor,
visitor::types::{Composite, Sequence, Variant},
visitor::{TypeIdFor, Unexpected},
};
use scale_type_resolver::TypeResolver;
/// This is a visitor which obtains type names.
pub struct GetTypeName<R> {
marker: core::marker::PhantomData<R>,
}
impl<R> GetTypeName<R> {
/// Construct our TypeName visitor.
pub fn new() -> Self {
GetTypeName {
marker: core::marker::PhantomData,
}
}
}
impl<R: TypeResolver> Visitor for GetTypeName<R> {
type Value<'scale, 'resolver> = Option<&'resolver str>;
type Error = scale_decode::Error;
type TypeResolver = R;
// Look at the path of types that have paths and return the ident from that.
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_sequence<'scale, 'resolver>(
self,
value: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
// Else, we return nothing as we can't find a name for the type.
fn visit_unexpected<'scale, 'resolver>(
self,
_unexpected: Unexpected,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(None)
}
}
}
/// This visitor demonstrates how to decode and return a custom Value shape
mod value {
use scale_decode::{
Visitor,
visitor::TypeIdFor,
visitor::types::{Array, BitSequence, Composite, Sequence, Str, Tuple, Variant},
};
use scale_type_resolver::TypeResolver;
use std::collections::HashMap;
/// A value type we're decoding into.
#[allow(dead_code)]
pub enum Value {
Number(f64),
BigNumber(String),
Bool(bool),
Char(char),
Array(Vec<Value>),
String(String),
Address(Vec<u8>),
I256([u8; 32]),
U256([u8; 32]),
Struct(HashMap<String, Value>),
VariantWithoutData(String),
VariantWithData(String, VariantFields),
}
pub enum VariantFields {
Unnamed(Vec<Value>),
Named(HashMap<String, Value>),
}
/// An error we can encounter trying to decode things into a [`Value`]
#[derive(Debug, thiserror::Error)]
pub enum ValueError {
#[error("Decode error: {0}")]
Decode(#[from] scale_decode::visitor::DecodeError),
#[error("Cannot decode bit sequence: {0}")]
CannotDecodeBitSequence(codec::Error),
#[error("Cannot resolve variant type information: {0}")]
CannotResolveVariantType(String),
}
/// This is a visitor which obtains type names.
pub struct GetValue<'r, R> {
resolver: &'r R,
}
impl<'r, R> GetValue<'r, R> {
/// Construct our TypeName visitor.
pub fn new(resolver: &'r R) -> Self {
GetValue { resolver }
}
}
impl<'r, R: TypeResolver> Visitor for GetValue<'r, R> {
type Value<'scale, 'resolver> = Value;
type Error = ValueError;
type TypeResolver = R;
fn visit_i256<'resolver>(
self,
value: &[u8; 32],
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
Ok(Value::I256(*value))
}
fn visit_u256<'resolver>(
self,
value: &[u8; 32],
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
Ok(Value::U256(*value))
}
fn visit_i128<'scale, 'resolver>(
self,
value: i128,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let attempt = value as f64;
if attempt as i128 == value {
Ok(Value::Number(attempt))
} else {
Ok(Value::BigNumber(value.to_string()))
}
}
fn visit_i64<'scale, 'resolver>(
self,
value: i64,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i32<'scale, 'resolver>(
self,
value: i32,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i16<'scale, 'resolver>(
self,
value: i16,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i8<'scale, 'resolver>(
self,
value: i8,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_u128<'scale, 'resolver>(
self,
value: u128,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let attempt = value as f64;
if attempt as u128 == value {
Ok(Value::Number(attempt))
} else {
Ok(Value::BigNumber(value.to_string()))
}
}
fn visit_u64<'scale, 'resolver>(
self,
value: u64,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u32<'scale, 'resolver>(
self,
value: u32,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u16<'scale, 'resolver>(
self,
value: u16,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u8<'scale, 'resolver>(
self,
value: u8,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_bool<'scale, 'resolver>(
self,
value: bool,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Bool(value))
}
fn visit_char<'scale, 'resolver>(
self,
value: char,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Char(value))
}
fn visit_array<'scale, 'resolver>(
self,
values: &mut Array<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_sequence<'scale, 'resolver>(
self,
values: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_str<'scale, 'resolver>(
self,
value: &mut Str<'scale>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::String(value.as_str()?.to_owned()))
}
fn visit_tuple<'scale, 'resolver>(
self,
values: &mut Tuple<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_bitsequence<'scale, 'resolver>(
self,
value: &mut BitSequence<'scale>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let bits = value.decode()?;
let mut out = Vec::with_capacity(bits.len());
for b in bits {
let b = b.map_err(ValueError::CannotDecodeBitSequence)?;
out.push(Value::Bool(b));
}
Ok(Value::Array(out))
}
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
// Special case for ss58 addresses:
if let Some(n) = value.name()
&& n == "AccountId32"
&& value.bytes_from_start().len() == 32
{
return Ok(Value::Address(value.bytes_from_start().to_vec()));
}
// Reuse logic for decoding variant fields:
match to_variant_fieldish(self.resolver, value)? {
VariantFields::Named(s) => Ok(Value::Struct(s)),
VariantFields::Unnamed(a) => Ok(Value::Array(a)),
}
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
// Because we have access to a type resolver on self, we can
// look up the type IDs we're given back and base decode decisions
// on them. here we see whether the enum type has any data attached:
let has_data_visitor = scale_type_resolver::visitor::new((), |_, _| false)
.visit_variant(|_, _, variants| {
for mut variant in variants {
if variant.fields.next().is_some() {
return true;
}
}
false
});
// Do any variants have data in this enum type?
let has_data = self
.resolver
.resolve_type(type_id, has_data_visitor)
.map_err(|e| ValueError::CannotResolveVariantType(e.to_string()))?;
let name = value.name().to_owned();
// base our decoding on whether any data in enum type.
if has_data {
let fields = to_variant_fieldish(self.resolver, value.fields())?;
Ok(Value::VariantWithData(name, fields))
} else {
Ok(Value::VariantWithoutData(name))
}
}
}
fn to_variant_fieldish<'r, 'scale, 'resolver, R: TypeResolver>(
resolver: &'r R,
value: &mut Composite<'scale, 'resolver, R>,
) -> Result<VariantFields, ValueError> {
// If fields are unnamed, treat as array:
if value.fields().iter().all(|f| f.name.is_none()) {
return Ok(VariantFields::Unnamed(to_array(
resolver,
value.remaining(),
value,
)?));
}
// Otherwise object:
let mut out = HashMap::new();
for field in value {
let field = field?;
let name = field.name().unwrap().to_string();
let value = field.decode_with_visitor(GetValue::new(resolver))?;
out.insert(name, value);
}
Ok(VariantFields::Named(out))
}
fn to_array<'r, 'scale, 'resolver, R: TypeResolver>(
resolver: &'r R,
len: usize,
mut values: impl scale_decode::visitor::DecodeItemIterator<'scale, 'resolver, R>,
) -> Result<Vec<Value>, ValueError> {
let mut out = Vec::with_capacity(len);
while let Some(value) = values.decode_item(GetValue::new(resolver)) {
out.push(value?);
}
Ok(out)
}
}
+175
View File
@@ -0,0 +1,175 @@
#![allow(missing_docs)]
use subxt_historic::{OnlineClient, PolkadotConfig, ext::StreamExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
// Configuration for the Polkadot relay chain.
let config = PolkadotConfig::new();
// Create an online client for the Polkadot relay chain, pointed at a Polkadot archive node.
let client = OnlineClient::from_url(config, "wss://rpc.polkadot.io").await?;
// Iterate through some randomly selected blocks to show how to fetch and decode storage.
for block_number in 12345678.. {
println!("=== Block {block_number} ===");
// Point the client at a specific block number. By default this will download and cache
// metadata for the required spec version (so it's cheaper to instantiate again), if it
// hasn't already, and borrow the relevant legacy types from the client.
let client_at_block = client.at(block_number).await?;
// We'll work the account balances at the given block, for this example.
let account_balances = client_at_block.storage().entry("System", "Account")?;
// We can see the default value for this entry at this block, if one exists.
if let Some(default_value) = account_balances.default_value() {
let default_balance_info = default_value.decode_as::<scale_value::Value>()?;
println!(" Default balance info: {default_balance_info}");
}
// We can fetch a specific account balance by its key, like so (here I just picked a random key
// I knew to exist from iterating over storage entries):
let account_id_hex = "9a4d0faa2ba8c3cc5711852960940793acf55bf195b6eecf88fa78e961d0ce4a";
let account_id: [u8; 32] = hex::decode(account_id_hex).unwrap().try_into().unwrap();
if let Some(entry) = account_balances.fetch((account_id,)).await? {
// We can decode the value into our generic `scale_value::Value` type, which can
// represent any SCALE-encoded value, like so:
let _balance_info = entry.decode_as::<scale_value::Value>()?;
// We can visit the value, which is a more advanced use case and allows us to extract more
// data from the type, here the name of it, if it exists:
let tn = entry
.visit(type_name::GetTypeName::new())?
.unwrap_or("<unknown>");
// Or, if we know what shape to expect, we can decode the parts of the value that we care
// about directly into a static type, which is more efficient and allows easy type-safe
// access, like so:
#[derive(scale_decode::DecodeAsType)]
struct BalanceInfo {
data: BalanceInfoData,
}
#[derive(scale_decode::DecodeAsType)]
struct BalanceInfoData {
free: u128,
reserved: u128,
misc_frozen: u128,
fee_frozen: u128,
}
let balance_info = entry.decode_as::<BalanceInfo>()?;
println!(
" Single balance info from {account_id_hex} => free: {} reserved: {} misc_frozen: {} fee_frozen: {} (type name: {tn})",
balance_info.data.free,
balance_info.data.reserved,
balance_info.data.misc_frozen,
balance_info.data.fee_frozen,
);
}
// Or we can iterate over all of the account balances and print them out, like so. Here we provide an
// empty tuple, indicating that we want to iterate over everything and not only things under a certain key
// (in the case of account balances, there is only one key anyway, but other storage entries may map from
// several keys to a value, and for those we can choose which depth we iterate at by providing as many keys
// as we want and leaving the rest). Here I only take the first 10 accounts I find for the sake of the example.
let mut all_balances = account_balances.iter(()).await?.take(10);
while let Some(entry) = all_balances.next().await {
let entry = entry?;
let key = entry.key()?;
// Decode the account ID from the key (we know here that we're working
// with a map which has one value, an account ID, so we just decode that part:
let account_id = key
.part(0)
.unwrap()
.decode_as::<[u8; 32]>()?
.expect("We expect this key to decode into a 32 byte AccountId");
let account_id_hex = hex::encode(account_id);
// Decode these values into our generic scale_value::Value type. Less efficient than
// defining a static type as above, but easier for the sake of the example.
let balance_info = entry.value().decode_as::<scale_value::Value>()?;
println!(" {account_id_hex} => {balance_info}");
}
// We can also chain things together to fetch and decode a value in one go.
let _val = client_at_block
.storage()
.entry("System", "Account")?
.fetch((account_id,))
.await?
.unwrap()
.decode_as::<scale_value::Value>()?;
let _vals = client_at_block
.storage()
.entry("System", "Account")?
.iter(())
.await?;
}
Ok(())
}
/// This module defines an example visitor which retrieves the name of a type.
/// This is a more advanced use case and can typically be avoided.
mod type_name {
use scale_decode::{
Visitor,
visitor::types::{Composite, Sequence, Variant},
visitor::{TypeIdFor, Unexpected},
};
use scale_type_resolver::TypeResolver;
/// This is a visitor which obtains type names.
pub struct GetTypeName<R> {
marker: core::marker::PhantomData<R>,
}
impl<R> GetTypeName<R> {
/// Construct our TypeName visitor.
pub fn new() -> Self {
GetTypeName {
marker: core::marker::PhantomData,
}
}
}
impl<R: TypeResolver> Visitor for GetTypeName<R> {
type Value<'scale, 'resolver> = Option<&'resolver str>;
type Error = scale_decode::Error;
type TypeResolver = R;
// Look at the path of types that have paths and return the ident from that.
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_sequence<'scale, 'resolver>(
self,
value: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
// Else, we return nothing as we can't find a name for the type.
fn visit_unexpected<'scale, 'resolver>(
self,
_unexpected: Unexpected,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(None)
}
}
}
+73
View File
@@ -0,0 +1,73 @@
mod offline_client;
mod online_client;
use crate::config::Config;
use crate::extrinsics::ExtrinsicsClient;
use crate::storage::StorageClient;
use crate::utils::AnyResolver;
use frame_metadata::RuntimeMetadata;
use std::marker::PhantomData;
pub use offline_client::{OfflineClient, OfflineClientAtBlock, OfflineClientAtBlockT};
pub use online_client::{OnlineClient, OnlineClientAtBlock, OnlineClientAtBlockT};
/// This represents a client at a specific block number.
pub struct ClientAtBlock<Client, T> {
client: Client,
marker: PhantomData<T>,
}
impl<Client, T> ClientAtBlock<Client, T> {
/// Construct a new client at some block.
pub(crate) fn new(client: Client) -> Self {
Self {
client,
marker: PhantomData,
}
}
}
impl<'client, T, Client> ClientAtBlock<Client, T>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
{
/// Work with extrinsics.
pub fn extrinsics(&'_ self) -> ExtrinsicsClient<'_, Client, T> {
ExtrinsicsClient::new(&self.client)
}
/// Work with storage.
pub fn storage(&'_ self) -> StorageClient<'_, Client, T> {
StorageClient::new(&self.client)
}
/// Return the metadata in use at this block.
pub fn metadata(&self) -> &RuntimeMetadata {
self.client.metadata()
}
/// Return something which implements [`scale_type_resolver::TypeResolver`] and
/// can be used in conjnction with type IDs in `.visit` methods.
pub fn resolver(&self) -> AnyResolver<'_, 'client> {
match self.client.metadata() {
RuntimeMetadata::V0(_)
| RuntimeMetadata::V1(_)
| RuntimeMetadata::V2(_)
| RuntimeMetadata::V3(_)
| RuntimeMetadata::V4(_)
| RuntimeMetadata::V5(_)
| RuntimeMetadata::V6(_)
| RuntimeMetadata::V7(_)
| RuntimeMetadata::V8(_)
| RuntimeMetadata::V9(_)
| RuntimeMetadata::V10(_)
| RuntimeMetadata::V11(_)
| RuntimeMetadata::V12(_)
| RuntimeMetadata::V13(_) => AnyResolver::B(self.client.legacy_types()),
RuntimeMetadata::V14(m) => AnyResolver::A(&m.types),
RuntimeMetadata::V15(m) => AnyResolver::A(&m.types),
RuntimeMetadata::V16(m) => AnyResolver::A(&m.types),
}
}
}
@@ -0,0 +1,83 @@
use super::ClientAtBlock;
use crate::config::Config;
use crate::error::OfflineClientAtBlockError;
use frame_metadata::RuntimeMetadata;
use scale_info_legacy::TypeRegistrySet;
use std::sync::Arc;
/// A client which exposes the means to decode historic data on a chain offline.
#[derive(Clone, Debug)]
pub struct OfflineClient<T: Config> {
/// The configuration for this client.
config: Arc<T>,
}
impl<T: Config> OfflineClient<T> {
/// Create a new [`OfflineClient`] with the given configuration.
pub fn new(config: T) -> Self {
OfflineClient {
config: Arc::new(config),
}
}
/// Pick the block height at which to operate. This references data from the
/// [`OfflineClient`] it's called on, and so cannot outlive it.
pub fn at<'this>(
&'this self,
block_number: u64,
) -> Result<ClientAtBlock<OfflineClientAtBlock<'this, T>, T>, OfflineClientAtBlockError> {
let config = &self.config;
let spec_version = self
.config
.spec_version_for_block_number(block_number)
.ok_or(OfflineClientAtBlockError::SpecVersionNotFound { block_number })?;
let legacy_types = self.config.legacy_types_for_spec_version(spec_version);
let metadata = self
.config
.metadata_for_spec_version(spec_version)
.ok_or(OfflineClientAtBlockError::MetadataNotFound { spec_version })?;
Ok(ClientAtBlock::new(OfflineClientAtBlock {
config,
legacy_types,
metadata,
}))
}
}
/// This represents an offline-only client at a specific block.
pub trait OfflineClientAtBlockT<'client, T: Config + 'client> {
/// Get the configuration for this client.
fn config(&self) -> &'client T;
/// Get the legacy types that work at this block.
fn legacy_types(&'_ self) -> &TypeRegistrySet<'client>;
/// Get the metadata appropriate for this block.
fn metadata(&self) -> &RuntimeMetadata;
}
// Dev note: this shouldn't need to be exposed unless there is some
// need to explicitly name the ClientAAtBlock type. Rather keep it
// private to allow changes if possible.
pub struct OfflineClientAtBlock<'client, T: Config + 'client> {
/// The configuration for this chain.
config: &'client T,
/// Historic types to use at this block number.
legacy_types: TypeRegistrySet<'client>,
/// Metadata to use at this block number.
metadata: Arc<RuntimeMetadata>,
}
impl<'client, T: Config + 'client> OfflineClientAtBlockT<'client, T>
for OfflineClientAtBlock<'client, T>
{
fn config(&self) -> &'client T {
self.config
}
fn legacy_types(&self) -> &TypeRegistrySet<'client> {
&self.legacy_types
}
fn metadata(&self) -> &RuntimeMetadata {
&self.metadata
}
}
@@ -0,0 +1,331 @@
use super::ClientAtBlock;
use crate::client::OfflineClientAtBlockT;
use crate::config::Config;
use crate::error::OnlineClientAtBlockError;
use codec::{Compact, Decode, Encode};
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use scale_info_legacy::TypeRegistrySet;
use std::sync::Arc;
use pezkuwi_subxt_rpcs::methods::chain_head::ArchiveCallResult;
use pezkuwi_subxt_rpcs::{ChainHeadRpcMethods, RpcClient};
#[cfg(feature = "jsonrpsee")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonrpsee")))]
use crate::error::OnlineClientError;
/// A client which exposes the means to decode historic data on a chain online.
#[derive(Clone, Debug)]
pub struct OnlineClient<T: Config> {
inner: Arc<OnlineClientInner<T>>,
}
#[derive(Debug)]
struct OnlineClientInner<T: Config> {
/// The configuration for this client.
config: T,
/// The RPC methods used to communicate with the node.
rpc_methods: ChainHeadRpcMethods<T>,
}
// The default constructors assume Jsonrpsee.
#[cfg(feature = "jsonrpsee")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonrpsee")))]
impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] using default settings which
/// point to a locally running node on `ws://127.0.0.1:9944`.
///
/// **Note:** This will only work if the local node is an archive node.
pub async fn new(config: T) -> Result<OnlineClient<T>, OnlineClientError> {
let url = "ws://127.0.0.1:9944";
OnlineClient::from_url(config, url).await
}
/// Construct a new [`OnlineClient`], providing a URL to connect to.
pub async fn from_url(
config: T,
url: impl AsRef<str>,
) -> Result<OnlineClient<T>, OnlineClientError> {
let url_str = url.as_ref();
let url = url::Url::parse(url_str).map_err(|_| OnlineClientError::InvalidUrl {
url: url_str.to_string(),
})?;
if !Self::is_url_secure(&url) {
return Err(OnlineClientError::RpcClientError(
pezkuwi_subxt_rpcs::Error::InsecureUrl(url_str.to_string()),
));
}
OnlineClient::from_insecure_url(config, url).await
}
/// Construct a new [`OnlineClient`], providing a URL to connect to.
///
/// Allows insecure URLs without SSL encryption, e.g. (http:// and ws:// URLs).
pub async fn from_insecure_url(
config: T,
url: impl AsRef<str>,
) -> Result<OnlineClient<T>, OnlineClientError> {
let rpc_client = RpcClient::from_insecure_url(url).await?;
Ok(OnlineClient::from_rpc_client(config, rpc_client))
}
fn is_url_secure(url: &url::Url) -> bool {
let secure_scheme = url.scheme() == "https" || url.scheme() == "wss";
let is_localhost = url.host().is_some_and(|e| match e {
url::Host::Domain(e) => e == "localhost",
url::Host::Ipv4(e) => e.is_loopback(),
url::Host::Ipv6(e) => e.is_loopback(),
});
secure_scheme || is_localhost
}
}
impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] by providing an [`RpcClient`] to drive the connection,
/// and some configuration for the chain we're connecting to.
pub fn from_rpc_client(config: T, rpc_client: impl Into<RpcClient>) -> OnlineClient<T> {
let rpc_client = rpc_client.into();
let rpc_methods = ChainHeadRpcMethods::new(rpc_client);
OnlineClient {
inner: Arc::new(OnlineClientInner {
config,
rpc_methods,
}),
}
}
/// Pick the block height at which to operate. This references data from the
/// [`OnlineClient`] it's called on, and so cannot outlive it.
pub async fn at(
&'_ self,
block_number: u64,
) -> Result<ClientAtBlock<OnlineClientAtBlock<'_, T>, T>, OnlineClientAtBlockError> {
let config = &self.inner.config;
let rpc_methods = &self.inner.rpc_methods;
let block_hash = rpc_methods
.archive_v1_hash_by_height(block_number as usize)
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetBlockHash {
block_number,
reason: e,
})?
.pop()
.ok_or_else(|| OnlineClientAtBlockError::BlockNotFound { block_number })?
.into();
// Get our configuration, or fetch from the node if not available.
let spec_version =
if let Some(spec_version) = config.spec_version_for_block_number(block_number) {
spec_version
} else {
// Fetch spec version. Caching this doesn't really make sense, so either
// details are provided offline or we fetch them every time.
get_spec_version(rpc_methods, block_hash).await?
};
let metadata = if let Some(metadata) = config.metadata_for_spec_version(spec_version) {
metadata
} else {
// Fetch and then give our config the opportunity to cache this metadata.
let metadata = get_metadata(rpc_methods, block_hash).await?;
let metadata = Arc::new(metadata);
config.set_metadata_for_spec_version(spec_version, metadata.clone());
metadata
};
let mut historic_types = config.legacy_types_for_spec_version(spec_version);
// The metadata can be used to construct call and event types instead of us having to hardcode them all for every spec version:
let types_from_metadata = frame_decode::helpers::type_registry_from_metadata_any(&metadata)
.map_err(
|parse_error| OnlineClientAtBlockError::CannotInjectMetadataTypes { parse_error },
)?;
historic_types.prepend(types_from_metadata);
Ok(ClientAtBlock::new(OnlineClientAtBlock {
config,
historic_types,
metadata,
rpc_methods,
block_hash,
}))
}
}
/// This represents an online client at a specific block.
pub trait OnlineClientAtBlockT<'client, T: Config + 'client>:
OfflineClientAtBlockT<'client, T>
{
/// Return the RPC methods we'll use to interact with the node.
fn rpc_methods(&self) -> &ChainHeadRpcMethods<T>;
/// Return the block hash for the current block.
fn block_hash(&self) -> <T as Config>::Hash;
}
// Dev note: this shouldn't need to be exposed unless there is some
// need to explicitly name the ClientAAtBlock type. Rather keep it
// private to allow changes if possible.
pub struct OnlineClientAtBlock<'client, T: Config + 'client> {
/// The configuration for this chain.
config: &'client T,
/// Historic types to use at this block number.
historic_types: TypeRegistrySet<'client>,
/// Metadata to use at this block number.
metadata: Arc<RuntimeMetadata>,
/// We also need RPC methods for online interactions.
rpc_methods: &'client ChainHeadRpcMethods<T>,
/// The block hash at which this client is operating.
block_hash: <T as Config>::Hash,
}
impl<'client, T: Config + 'client> OnlineClientAtBlockT<'client, T>
for OnlineClientAtBlock<'client, T>
{
fn rpc_methods(&self) -> &ChainHeadRpcMethods<T> {
self.rpc_methods
}
fn block_hash(&self) -> <T as Config>::Hash {
self.block_hash
}
}
impl<'client, T: Config + 'client> OfflineClientAtBlockT<'client, T>
for OnlineClientAtBlock<'client, T>
{
fn config(&self) -> &'client T {
self.config
}
fn legacy_types(&'_ self) -> &TypeRegistrySet<'client> {
&self.historic_types
}
fn metadata(&self) -> &RuntimeMetadata {
&self.metadata
}
}
async fn get_spec_version<T: Config>(
rpc_methods: &ChainHeadRpcMethods<T>,
block_hash: <T as Config>::Hash,
) -> Result<u32, OnlineClientAtBlockError> {
use codec::Decode;
use pezkuwi_subxt_rpcs::methods::chain_head::ArchiveCallResult;
// make a runtime call to get the version information. This is also a constant
// in the metadata and so we could fetch it from there to avoid the call, but it would be a
// bit more effort.
let spec_version_bytes = {
let call_res = rpc_methods
.archive_v1_call(block_hash.into(), "Core_version", &[])
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetSpecVersion {
block_hash: block_hash.to_string(),
reason: format!("Error calling Core_version: {e}"),
})?;
match call_res {
ArchiveCallResult::Success(bytes) => bytes.0,
ArchiveCallResult::Error(e) => {
return Err(OnlineClientAtBlockError::CannotGetSpecVersion {
block_hash: block_hash.to_string(),
reason: format!("Core_version returned an error: {e}"),
});
}
}
};
// We only care about the spec version, so just decode enough of this version information
// to be able to pluck out what we want, and ignore the rest.
let spec_version = {
#[derive(codec::Decode)]
struct SpecVersionHeader {
_spec_name: String,
_impl_name: String,
_authoring_version: u32,
spec_version: u32,
}
SpecVersionHeader::decode(&mut &spec_version_bytes[..])
.map_err(|e| OnlineClientAtBlockError::CannotGetSpecVersion {
block_hash: block_hash.to_string(),
reason: format!("Error decoding Core_version response: {e}"),
})?
.spec_version
};
Ok(spec_version)
}
async fn get_metadata<T: Config>(
rpc_methods: &ChainHeadRpcMethods<T>,
block_hash: <T as Config>::Hash,
) -> Result<RuntimeMetadata, OnlineClientAtBlockError> {
// First, try to use the "modern" metadata APIs to get the most recent version we can.
let version_to_get = rpc_methods
.archive_v1_call(block_hash.into(), "Metadata_metadata_versions", &[])
.await
.ok()
.and_then(|res| res.as_success())
.and_then(|res| <Vec<u32>>::decode(&mut &res[..]).ok())
.and_then(|versions| {
// We want to filter out the "unstable" version, which is represented by u32::MAX.
versions.into_iter().filter(|v| *v != u32::MAX).max()
});
// We had success calling the above API, so we expect the "modern" metadata API to work.
if let Some(version_to_get) = version_to_get {
let version_bytes = version_to_get.encode();
let rpc_response = rpc_methods
.archive_v1_call(
block_hash.into(),
"Metadata_metadata_at_version",
&version_bytes,
)
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error calling Metadata_metadata_at_version: {e}"),
})
.and_then(|res| match res {
ArchiveCallResult::Success(bytes) => Ok(bytes.0),
ArchiveCallResult::Error(e) => Err(OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Calling Metadata_metadata_at_version returned an error: {e}"),
}),
})?;
// Option because we may have asked for a version that doesn't exist. Compact because we get back a Vec<u8>
// of the metadata bytes, and the Vec is preceded by it's compact encoded length. The actual bytes are then
// decoded as a `RuntimeMetadataPrefixed`, after this.
let (_, metadata) = <Option<(Compact<u32>, RuntimeMetadataPrefixed)>>::decode(&mut &rpc_response[..])
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error decoding response for Metadata_metadata_at_version: {e}"),
})?
.ok_or_else(|| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("No metadata returned for the latest version from Metadata_metadata_versions ({version_to_get})"),
})?;
return Ok(metadata.1);
}
// We didn't get a version from Metadata_metadata_versions, so fall back to the "old" API.
let metadata_bytes = rpc_methods
.archive_v1_call(block_hash.into(), "Metadata_metadata", &[])
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error calling Metadata_metadata: {e}"),
})
.and_then(|res| match res {
ArchiveCallResult::Success(bytes) => Ok(bytes.0),
ArchiveCallResult::Error(e) => Err(OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Calling Metadata_metadata returned an error: {e}"),
}),
})?;
let (_, metadata) = <(Compact<u32>, RuntimeMetadataPrefixed)>::decode(&mut &metadata_bytes[..])
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error decoding response for Metadata_metadata: {e}"),
})?;
Ok(metadata.1)
}
+56
View File
@@ -0,0 +1,56 @@
pub mod polkadot;
pub mod substrate;
use scale_info_legacy::TypeRegistrySet;
use std::fmt::Display;
use std::sync::Arc;
use pezkuwi_subxt_rpcs::RpcConfig;
pub use polkadot::PolkadotConfig;
pub use substrate::SubstrateConfig;
/// This represents the configuration needed for a specific chain. This includes
/// any hardcoded types we need to know about for that chain, as well as a means to
/// obtain historic types for that chain.
pub trait Config: RpcConfig {
/// The type of hashing used by the runtime.
type Hash: Clone
+ Copy
+ Display
+ Into<<Self as RpcConfig>::Hash>
+ From<<Self as RpcConfig>::Hash>;
/// Return the spec version for a given block number, if available.
///
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here,
/// but the [`crate::client::OfflineClient`] will error if this is not available for the required block number.
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32>;
/// Return the metadata for a given spec version, if available.
///
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here, and then
/// call [`Config::set_metadata_for_spec_version`] to give the configuration the opportunity to cache it.
/// The [`crate::client::OfflineClient`] will error if this is not available for the required spec version.
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<frame_metadata::RuntimeMetadata>>;
/// Set some metadata for a given spec version. the [`crate::client::OnlineClient`] will call this if it has
/// to retrieve metadata from the chain, to give this the opportunity to cache it. The configuration can
/// do nothing if it prefers.
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<frame_metadata::RuntimeMetadata>,
);
/// Return legacy types (ie types to use with Runtimes that return pre-V14 metadata) for a given spec version.
fn legacy_types_for_spec_version<'this>(
&'this self,
spec_version: u32,
) -> TypeRegistrySet<'this>;
/// Hash some bytes, for instance a block header or extrinsic, for this chain.
fn hash(s: &[u8]) -> <Self as Config>::Hash;
}
+88
View File
@@ -0,0 +1,88 @@
use super::Config;
use super::SubstrateConfig;
use scale_info_legacy::{ChainTypeRegistry, TypeRegistrySet};
use std::sync::Arc;
/// Configuration that's suitable for the Polkadot Relay Chain
pub struct PolkadotConfig(SubstrateConfig);
impl PolkadotConfig {
/// Create a new PolkadotConfig.
pub fn new() -> Self {
let config = SubstrateConfig::new()
.set_legacy_types(frame_decode::legacy_types::polkadot::relay_chain());
// TODO: Set spec versions as well with known spec version changes, to speed
// up accessing historic blocks within the known ranges. For now, we just let
// the online client look these up on chain.
Self(config)
}
/// Set the metadata to be used for decoding blocks at the given spec versions.
pub fn set_metadata_for_spec_versions(
mut self,
ranges: impl Iterator<Item = (u32, frame_metadata::RuntimeMetadata)>,
) -> Self {
self = Self(self.0.set_metadata_for_spec_versions(ranges));
self
}
/// Given an iterator of block ranges to spec version of the form `(start, end, spec_version)`, add them
/// to this configuration.
pub fn set_spec_version_for_block_ranges(
mut self,
ranges: impl Iterator<Item = (u64, u64, u32)>,
) -> Self {
self = Self(self.0.set_spec_version_for_block_ranges(ranges));
self
}
}
/// This hands back the legacy types for the Polkadot Relay Chain, which is what [`PolkadotConfig`] uses internally.
pub fn legacy_types() -> ChainTypeRegistry {
frame_decode::legacy_types::polkadot::relay_chain()
}
impl Default for PolkadotConfig {
fn default() -> Self {
Self::new()
}
}
impl Config for PolkadotConfig {
type Hash = <SubstrateConfig as Config>::Hash;
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> TypeRegistrySet<'_> {
self.0.legacy_types_for_spec_version(spec_version)
}
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32> {
self.0.spec_version_for_block_number(block_number)
}
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<frame_metadata::RuntimeMetadata>> {
self.0.metadata_for_spec_version(spec_version)
}
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<frame_metadata::RuntimeMetadata>,
) {
self.0.set_metadata_for_spec_version(spec_version, metadata)
}
fn hash(s: &[u8]) -> <Self as Config>::Hash {
SubstrateConfig::hash(s)
}
}
impl pezkuwi_subxt_rpcs::RpcConfig for PolkadotConfig {
type Hash = <SubstrateConfig as pezkuwi_subxt_rpcs::RpcConfig>::Hash;
type Header = <SubstrateConfig as pezkuwi_subxt_rpcs::RpcConfig>::Header;
type AccountId = <SubstrateConfig as pezkuwi_subxt_rpcs::RpcConfig>::AccountId;
}
+129
View File
@@ -0,0 +1,129 @@
use super::Config;
use crate::utils::RangeMap;
use primitive_types::H256;
use scale_info_legacy::{ChainTypeRegistry, TypeRegistrySet};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
/// Configuration that's suitable for standard Substrate chains (ie those
/// that have not customized the block hash type).
pub struct SubstrateConfig {
legacy_types: ChainTypeRegistry,
spec_version_for_block_number: RangeMap<u64, u32>,
metadata_for_spec_version: Mutex<HashMap<u32, Arc<frame_metadata::RuntimeMetadata>>>,
}
impl SubstrateConfig {
/// Create a new SubstrateConfig with no legacy types.
///
/// Without any further configuration, this will only work with
/// the [`crate::client::OnlineClient`] for blocks that were produced by Runtimes
/// that emit metadata V14 or later.
///
/// To support working at any block with the [`crate::client::OnlineClient`], you
/// must call [`SubstrateConfig::set_legacy_types`] with appropriate legacy type
/// definitions.
///
/// To support working with the [`crate::client::OfflineClient`] at any block,
/// you must also call:
/// - [`SubstrateConfig::set_metadata_for_spec_versions`] to set the metadata to
/// use at each spec version we might encounter.
/// - [`SubstrateConfig::set_spec_version_for_block_ranges`] to set the spec version
/// to use for each range of blocks we might encounter.
pub fn new() -> Self {
Self {
legacy_types: ChainTypeRegistry::empty(),
spec_version_for_block_number: RangeMap::empty(),
metadata_for_spec_version: Mutex::new(HashMap::new()),
}
}
/// Set the legacy types to use for this configuration. This enables support for
/// blocks produced by Runtimes that emit metadata older than V14.
pub fn set_legacy_types(mut self, legacy_types: ChainTypeRegistry) -> Self {
self.legacy_types = legacy_types;
self
}
/// Set the metadata to be used for decoding blocks at the given spec versions.
pub fn set_metadata_for_spec_versions(
self,
ranges: impl Iterator<Item = (u32, frame_metadata::RuntimeMetadata)>,
) -> Self {
let mut map = self.metadata_for_spec_version.lock().unwrap();
for (spec_version, metadata) in ranges {
map.insert(spec_version, Arc::new(metadata));
}
drop(map);
self
}
/// Given an iterator of block ranges to spec version of the form `(start, end, spec_version)`, add them
/// to this configuration.
pub fn set_spec_version_for_block_ranges(
mut self,
ranges: impl Iterator<Item = (u64, u64, u32)>,
) -> Self {
let mut m = RangeMap::builder();
for (start, end, spec_version) in ranges {
m = m.add_range(start, end, spec_version);
}
self.spec_version_for_block_number = m.build();
self
}
}
impl Default for SubstrateConfig {
fn default() -> Self {
Self::new()
}
}
impl Config for SubstrateConfig {
type Hash = H256;
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> TypeRegistrySet<'_> {
self.legacy_types.for_spec_version(spec_version as u64)
}
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32> {
self.spec_version_for_block_number
.get(block_number)
.copied()
}
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<frame_metadata::RuntimeMetadata>> {
self.metadata_for_spec_version
.lock()
.unwrap()
.get(&spec_version)
.cloned()
}
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<frame_metadata::RuntimeMetadata>,
) {
self.metadata_for_spec_version
.lock()
.unwrap()
.insert(spec_version, metadata);
}
fn hash(s: &[u8]) -> <Self as Config>::Hash {
pezsp_crypto_hashing::blake2_256(s).into()
}
}
impl pezkuwi_subxt_rpcs::RpcConfig for SubstrateConfig {
type Hash = <Self as Config>::Hash;
// We don't use these types in any of the RPC methods we call,
// so don't bother setting them up:
type Header = ();
type AccountId = ();
}
+325
View File
@@ -0,0 +1,325 @@
/// Any error emitted by this crate can convert into this.
// Dev Note: All errors here are transparent, because in many places
// the inner errors are returned and so need to provide enough context
// as-is, so there shouldn't be anything to add here.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
OnlineClientError(#[from] OnlineClientError),
#[error(transparent)]
OfflineClientAtBlockError(#[from] OfflineClientAtBlockError),
#[error(transparent)]
OnlineClientAtBlockError(#[from] OnlineClientAtBlockError),
#[error(transparent)]
ExtrinsicsError(#[from] ExtrinsicsError),
#[error(transparent)]
ExtrinsicTransactionExtensionError(#[from] ExtrinsicTransactionExtensionError),
#[error(transparent)]
ExtrinsicCallError(#[from] ExtrinsicCallError),
#[error(transparent)]
StorageError(#[from] StorageError),
#[error(transparent)]
StorageKeyError(#[from] StorageKeyError),
#[error(transparent)]
StorageValueError(#[from] StorageValueError),
}
/// Errors constructing an online client.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OnlineClientError {
#[error("Cannot construct OnlineClient: The URL provided is invalid: {url}")]
InvalidUrl {
/// The URL that was invalid.
url: String,
},
#[error("Cannot construct OnlineClient owing to an RPC client error: {0}")]
RpcClientError(#[from] pezkuwi_subxt_rpcs::Error),
}
/// Errors constructing an offline client at a specific block number.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OfflineClientAtBlockError {
#[error(
"Cannot construct OfflineClientAtBlock: spec version not found for block number {block_number}"
)]
SpecVersionNotFound {
/// The block number for which the spec version was not found.
block_number: u64,
},
#[error(
"Cannot construct OfflineClientAtBlock: metadata not found for spec version {spec_version}"
)]
MetadataNotFound {
/// The spec version for which the metadata was not found.
spec_version: u32,
},
}
/// Errors constructing an online client at a specific block number.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OnlineClientAtBlockError {
#[error(
"Cannot construct OnlineClientAtBlock: failed to get block hash from node for block {block_number}: {reason}"
)]
CannotGetBlockHash {
/// Block number we failed to get the hash for.
block_number: u64,
/// The error we encountered.
reason: pezkuwi_subxt_rpcs::Error,
},
#[error("Cannot construct OnlineClientAtBlock: block number {block_number} not found")]
BlockNotFound {
/// The block number for which a block was not found.
block_number: u64,
},
#[error(
"Cannot construct OnlineClientAtBlock: failed to get spec version for block hash {block_hash}: {reason}"
)]
CannotGetSpecVersion {
/// The block hash for which we failed to get the spec version.
block_hash: String,
/// The error we encountered.
reason: String,
},
#[error(
"Cannot construct OnlineClientAtBlock: failed to get metadata for block hash {block_hash}: {reason}"
)]
CannotGetMetadata {
/// The block hash for which we failed to get the metadata.
block_hash: String,
/// The error we encountered.
reason: String,
},
#[error(
"Cannot inject types from metadata: failure to parse a type found in the metadata: {parse_error}"
)]
CannotInjectMetadataTypes {
/// Error parsing a type found in the metadata.
parse_error: scale_info_legacy::lookup_name::ParseError,
},
}
/// Errors working with extrinsics.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ExtrinsicsError {
#[error("Could not fetch extrinsics: {reason}")]
FetchError {
/// The error that occurred while fetching the extrinsics.
reason: pezkuwi_subxt_rpcs::Error,
},
#[error("Could not decode extrinsic at index {index}: {reason}")]
DecodeError {
/// The extrinsic index that failed to decode.
index: usize,
/// The error that occurred during decoding.
reason: frame_decode::extrinsics::ExtrinsicDecodeError,
},
#[error(
"Could not decode extrinsic at index {index}: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The extrinsic index that had leftover bytes
index: usize,
/// The bytes that were left over after decoding the extrinsic.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode extrinsics: Unsupported metadata version ({version})")]
UnsupportedMetadataVersion {
/// The metadata version that is not supported.
version: u32,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ExtrinsicTransactionExtensionError {
#[error("Could not decode extrinsic transaction extensions: {reason}")]
AllDecodeError {
/// The error that occurred while decoding the transaction extensions.
reason: scale_decode::Error,
},
#[error(
"Could not decode extrinsic transaction extensions: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
AllLeftoverBytes {
/// The bytes that were left over after decoding the transaction extensions.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode extrinsic transaction extension {name}: {reason}")]
DecodeError {
/// The name of the transaction extension that failed to decode.
name: String,
/// The error that occurred during decoding.
reason: scale_decode::Error,
},
#[error(
"Could not decode extrinsic transaction extension {name}: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The name of the transaction extension that had leftover bytes.
name: String,
/// The bytes that were left over after decoding the transaction extension.
leftover_bytes: Vec<u8>,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ExtrinsicCallError {
#[error("Could not decode the fields in extrinsic call: {reason}")]
FieldsDecodeError {
/// The error that occurred while decoding the fields of the extrinsic call.
reason: scale_decode::Error,
},
#[error(
"Could not decode the fields in extrinsic call: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
FieldsLeftoverBytes {
/// The bytes that were left over after decoding the extrinsic call.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode field {name} in extrinsic call: {reason}")]
FieldDecodeError {
/// The name of the field that failed to decode.
name: String,
/// The error that occurred during decoding.
reason: scale_decode::Error,
},
#[error(
"Could not decode field {name} in extrinsic call: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
FieldLeftoverBytes {
/// The name of the field that had leftover bytes.
name: String,
/// The bytes that were left over after decoding the extrinsic call.
leftover_bytes: Vec<u8>,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[error("Storage entry is not a map: pallet {pallet_name}, storage {entry_name}")]
pub struct StorageEntryIsNotAMap {
/// The pallet containing the storage entry that was not found.
pub pallet_name: String,
/// The storage entry that was not found.
pub entry_name: String,
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[error("Storage entry is not a plain value: pallet {pallet_name}, storage {entry_name}")]
pub struct StorageEntryIsNotAPlainValue {
/// The pallet containing the storage entry that was not found.
pub pallet_name: String,
/// The storage entry that was not found.
pub entry_name: String,
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageError {
#[error("RPC error interacting with storage APIs: {reason}")]
RpcError {
/// The error that occurred while fetching the storage entry.
reason: pezkuwi_subxt_rpcs::Error,
},
#[error("Could not fetch next entry from storage subscription: {reason}")]
StorageEventError {
/// The error that occurred while fetching the next storage entry.
reason: String,
},
#[error("Could not construct storage key: {reason}")]
KeyEncodeError {
/// The error that occurred while constructing the storage key.
reason: frame_decode::storage::StorageKeyEncodeError,
},
#[error(
"Wrong number of keys provided to fetch a value: expected {num_keys_expected} keys, but got {num_keys_provided}"
)]
WrongNumberOfKeysProvidedForFetch {
/// The number of keys that were provided.
num_keys_provided: usize,
/// The number of keys expected.
num_keys_expected: usize,
},
#[error(
"too many keys were provided to iterate over a storage entry: expected at most {max_keys_expected} keys, but got {num_keys_provided}"
)]
TooManyKeysProvidedForIter {
/// The number of keys that were provided.
num_keys_provided: usize,
/// The maximum number of keys that we expect.
max_keys_expected: usize,
},
#[error(
"Could not extract storage information from metadata: Unsupported metadata version ({version})"
)]
UnsupportedMetadataVersion {
/// The metadata version that is not supported.
version: u32,
},
#[error("Could not extract storage information from metadata: {reason}")]
ExtractStorageInfoError {
/// The error that occurred while extracting storage information from the metadata.
reason: frame_decode::storage::StorageInfoError<'static>,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageKeyError {
#[error("Could not decode the storage key: {reason}")]
DecodeError {
/// The error that occurred while decoding the storage key information.
reason: frame_decode::storage::StorageKeyDecodeError<String>,
},
#[error(
"Could not decode the storage key: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The bytes that were left over after decoding the storage key.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode the part of the storage key at index {index}: {reason}")]
DecodePartError {
index: usize,
reason: scale_decode::Error,
},
#[error("Could not decode values out of the storage key: {reason}")]
DecodeKeyValueError {
reason: frame_decode::storage::StorageKeyValueDecodeError,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageValueError {
#[error("Could not decode storage value: {reason}")]
DecodeError {
/// The error that occurred while decoding the storage value.
reason: scale_decode::Error,
},
#[error(
"Could not decode storage value: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The bytes that were left over after decoding the storage value.
leftover_bytes: Vec<u8>,
},
}
+76
View File
@@ -0,0 +1,76 @@
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
use crate::config::Config;
use crate::error::ExtrinsicsError;
mod extrinsic_call;
mod extrinsic_info;
mod extrinsic_transaction_extensions;
mod extrinsics_type;
pub use extrinsic_transaction_extensions::ExtrinsicTransactionParams;
pub use extrinsics_type::{Extrinsic, Extrinsics};
/// Work with extrinsics.
pub struct ExtrinsicsClient<'atblock, Client, T> {
client: &'atblock Client,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> ExtrinsicsClient<'atblock, Client, T> {
/// Work with extrinsics.
pub(crate) fn new(client: &'atblock Client) -> Self {
Self {
client,
marker: std::marker::PhantomData,
}
}
}
// Things that we can do online with extrinsics.
impl<'atblock, 'client: 'atblock, Client, T> ExtrinsicsClient<'atblock, Client, T>
where
T: Config + 'client,
Client: OnlineClientAtBlockT<'client, T>,
{
/// Fetch the extrinsics for the current block. This is essentially a
/// combination of [`Self::fetch_bytes`] and [`Self::decode_from`].
pub async fn fetch(&self) -> Result<Extrinsics<'atblock>, ExtrinsicsError> {
let bytes: Vec<Vec<u8>> = self.fetch_bytes().await?;
// Small optimization; no need to decode anything if no bytes.
if bytes.is_empty() {
return Ok(Extrinsics::empty());
}
self.decode_from(bytes)
}
/// Fetch the bytes for the extrinsics in the current block.
pub async fn fetch_bytes(&self) -> Result<Vec<Vec<u8>>, ExtrinsicsError> {
let bytes: Vec<Vec<u8>> = self
.client
.rpc_methods()
.archive_v1_body(self.client.block_hash().into())
.await
.map_err(|e| ExtrinsicsError::FetchError { reason: e })?
.map(|body| body.into_iter().map(|b| b.0).collect())
.unwrap_or_default();
Ok(bytes)
}
}
// Things that we can do offline with extrinsics.
impl<'atblock, 'client: 'atblock, Client, T> ExtrinsicsClient<'atblock, Client, T>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
{
/// Given some bytes representing the extrinsics in this block, decode them into an [`Extrinsics`] type.
pub fn decode_from(
&self,
bytes: Vec<Vec<u8>>,
) -> Result<Extrinsics<'atblock>, ExtrinsicsError> {
Extrinsics::new(bytes, self.client)
}
}
@@ -0,0 +1,210 @@
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
use crate::error::ExtrinsicCallError;
use crate::utils::Either;
use crate::utils::{AnyResolver, AnyTypeId};
use scale_info_legacy::{LookupName, TypeRegistrySet};
/// This represents the call data in the extrinsic.
pub struct ExtrinsicCall<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
}
impl<'extrinsics, 'atblock> ExtrinsicCall<'extrinsics, 'atblock> {
pub(crate) fn new(
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
) -> Self {
Self { all_bytes, info }
}
/// The index of the pallet that this call is for
pub fn pallet_index(&self) -> u8 {
with_info!(&self.info => info.info.pallet_index())
}
/// The name of the pallet that this call is for.
pub fn pallet_name(&self) -> &str {
with_info!(&self.info => info.info.pallet_name())
}
/// The index of this call.
pub fn index(&self) -> u8 {
with_info!(&self.info => info.info.call_index())
}
/// The name of this call.
pub fn name(&self) -> &str {
with_info!(&self.info => info.info.call_name())
}
/// Get the raw bytes for the entire call, which includes the pallet and call index
/// bytes as well as the encoded arguments for each of the fields.
pub fn bytes(&self) -> &'extrinsics [u8] {
with_info!(&self.info => &self.all_bytes[info.info.call_data_range()])
}
/// Work with the fields in this call.
pub fn fields(&self) -> ExtrinsicCallFields<'extrinsics, 'atblock> {
ExtrinsicCallFields::new(self.all_bytes, self.info)
}
}
/// This represents the fields of the call.
pub struct ExtrinsicCallFields<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
resolver: AnyResolver<'atblock, 'atblock>,
}
impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
pub(crate) fn new(
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
) -> Self {
let resolver = match info {
AnyExtrinsicInfo::Legacy(info) => AnyResolver::B(info.resolver),
AnyExtrinsicInfo::Current(info) => AnyResolver::A(info.resolver),
};
Self {
all_bytes,
info,
resolver,
}
}
/// Return the bytes representing the fields stored in this extrinsic.
///
/// # Note
///
/// This is a subset of [`ExtrinsicCall::bytes`] that does not include the
/// first two bytes that denote the pallet index and the variant index.
pub fn bytes(&self) -> &'extrinsics [u8] {
with_info!(&self.info => &self.all_bytes[info.info.call_data_args_range()])
}
/// Iterate over each of the fields of the extrinsic call data.
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicCallField<'_, 'extrinsics, 'atblock>> {
match &self.info {
AnyExtrinsicInfo::Legacy(info) => {
Either::A(info.info.call_data().map(|named_arg| ExtrinsicCallField {
field_bytes: &self.all_bytes[named_arg.range()],
resolver: &self.resolver,
info: AnyExtrinsicCallFieldInfo::Legacy(ExtrinsicCallFieldInfo {
info: named_arg,
resolver: info.resolver,
}),
}))
}
AnyExtrinsicInfo::Current(info) => {
Either::B(info.info.call_data().map(|named_arg| ExtrinsicCallField {
field_bytes: &self.all_bytes[named_arg.range()],
resolver: &self.resolver,
info: AnyExtrinsicCallFieldInfo::Current(ExtrinsicCallFieldInfo {
info: named_arg,
resolver: info.resolver,
}),
}))
}
}
}
/// Attempt to decode the fields into the given type.
pub fn decode_as<T: scale_decode::DecodeAsFields>(&self) -> Result<T, ExtrinsicCallError> {
with_info!(&self.info => {
let cursor = &mut self.bytes();
let mut fields = &mut info.info.call_data().map(|named_arg| {
scale_decode::Field::new(named_arg.ty().clone(), Some(named_arg.name()))
});
let decoded = T::decode_as_fields(cursor, &mut fields, info.resolver)
.map_err(|e| ExtrinsicCallError::FieldsDecodeError { reason: e })?;
if !cursor.is_empty() {
return Err(ExtrinsicCallError::FieldsLeftoverBytes {
leftover_bytes: cursor.to_vec(),
})
}
Ok(decoded)
})
}
}
pub struct ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
field_bytes: &'extrinsics [u8],
info: AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock>,
resolver: &'fields AnyResolver<'atblock, 'atblock>,
}
enum AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock> {
Legacy(ExtrinsicCallFieldInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicCallFieldInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>),
}
struct ExtrinsicCallFieldInfo<'extrinsics, 'atblock, TypeId, Resolver> {
info: &'extrinsics frame_decode::extrinsics::NamedArg<'atblock, TypeId>,
resolver: &'atblock Resolver,
}
macro_rules! with_call_field_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicCallFieldInfo::Legacy($info) => $fn,
AnyExtrinsicCallFieldInfo::Current($info) => $fn,
}
};
}
impl<'fields, 'extrinsics, 'atblock> ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
/// Get the raw bytes for this field.
pub fn bytes(&self) -> &'extrinsics [u8] {
self.field_bytes
}
/// Get the name of this field.
pub fn name(&self) -> &'extrinsics str {
with_call_field_info!(&self.info => info.info.name())
}
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
/// version of [`ExtrinsicCallField::decode_as`], as the visitor is able to preserve lifetimes
/// and has access to more type information than is available via [`ExtrinsicCallField::decode_as`].
pub fn visit<
V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock, 'atblock>>,
>(
&self,
visitor: V,
) -> Result<V::Value<'extrinsics, 'fields>, V::Error> {
let type_id = match &self.info {
AnyExtrinsicCallFieldInfo::Current(info) => AnyTypeId::A(*info.info.ty()),
AnyExtrinsicCallFieldInfo::Legacy(info) => AnyTypeId::B(info.info.ty().clone()),
};
let cursor = &mut self.bytes();
scale_decode::visitor::decode_with_visitor(cursor, type_id, self.resolver, visitor)
}
/// Attempt to decode the value of this field into the given type.
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
with_call_field_info!(&self.info => {
let cursor = &mut &*self.field_bytes;
let decoded = T::decode_as_type(cursor, info.info.ty().clone(), info.resolver)
.map_err(|e| ExtrinsicCallError::FieldDecodeError {
name: info.info.name().to_string(),
reason: e,
})?;
if !cursor.is_empty() {
return Err(ExtrinsicCallError::FieldLeftoverBytes {
name: info.info.name().to_string(),
leftover_bytes: cursor.to_vec(),
});
}
Ok(decoded)
})
}
}
@@ -0,0 +1,109 @@
use crate::error::ExtrinsicsError;
use frame_metadata::RuntimeMetadata;
use scale_info_legacy::{LookupName, TypeRegistrySet};
// Extrinsic information for modern or legacy extrinsics.
#[allow(clippy::large_enum_variant)]
pub enum AnyExtrinsicInfo<'atblock> {
Legacy(ExtrinsicInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicInfo<'atblock, u32, scale_info::PortableRegistry>),
}
impl<'atblock> AnyExtrinsicInfo<'atblock> {
/// For a slice of extrinsics, return a vec of information about each one.
pub fn new(
bytes: &[Vec<u8>],
metadata: &'atblock RuntimeMetadata,
legacy_types: &'atblock TypeRegistrySet<'atblock>,
) -> Result<Vec<Self>, ExtrinsicsError> {
let infos = match metadata {
RuntimeMetadata::V8(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V9(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V10(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V11(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V12(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V13(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V14(m) => extrinsic_info_inner(bytes, m, &m.types),
RuntimeMetadata::V15(m) => extrinsic_info_inner(bytes, m, &m.types),
RuntimeMetadata::V16(m) => extrinsic_info_inner(bytes, m, &m.types),
unknown => {
return Err(ExtrinsicsError::UnsupportedMetadataVersion {
version: unknown.version(),
});
}
}?;
fn extrinsic_info_inner<'atblock, Info, Resolver>(
bytes: &[Vec<u8>],
args_info: &'atblock Info,
type_resolver: &'atblock Resolver,
) -> Result<Vec<AnyExtrinsicInfo<'atblock>>, ExtrinsicsError>
where
Info: frame_decode::extrinsics::ExtrinsicTypeInfo,
Info::TypeId: Clone + core::fmt::Display + core::fmt::Debug + Send + Sync + 'static,
Resolver: scale_type_resolver::TypeResolver<TypeId = Info::TypeId>,
AnyExtrinsicInfo<'atblock>: From<ExtrinsicInfo<'atblock, Info::TypeId, Resolver>>,
{
bytes
.iter()
.enumerate()
.map(|(index, bytes)| {
let cursor = &mut &**bytes;
let extrinsic_info = frame_decode::extrinsics::decode_extrinsic(
cursor,
args_info,
type_resolver,
)
.map_err(|reason| ExtrinsicsError::DecodeError { index, reason })?;
if !cursor.is_empty() {
return Err(ExtrinsicsError::LeftoverBytes {
index,
leftover_bytes: cursor.to_vec(),
});
}
Ok(ExtrinsicInfo {
info: extrinsic_info,
resolver: type_resolver,
}
.into())
})
.collect()
}
Ok(infos)
}
}
impl<'atblock> From<ExtrinsicInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyExtrinsicInfo<'atblock>
{
fn from(info: ExtrinsicInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>) -> Self {
AnyExtrinsicInfo::Legacy(info)
}
}
impl<'atblock> From<ExtrinsicInfo<'atblock, u32, scale_info::PortableRegistry>>
for AnyExtrinsicInfo<'atblock>
{
fn from(info: ExtrinsicInfo<'atblock, u32, scale_info::PortableRegistry>) -> Self {
AnyExtrinsicInfo::Current(info)
}
}
// Extrinsic information for a specific type ID and resolver type.
pub struct ExtrinsicInfo<'atblock, TypeId, Resolver> {
pub info: frame_decode::extrinsics::Extrinsic<'atblock, TypeId>,
pub resolver: &'atblock Resolver,
}
macro_rules! with_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicInfo::Legacy($info) => $fn,
AnyExtrinsicInfo::Current($info) => $fn,
}
};
}
pub(crate) use with_info;
@@ -0,0 +1,213 @@
use super::extrinsic_info::AnyExtrinsicInfo;
use crate::error::ExtrinsicTransactionExtensionError;
use crate::utils::Either;
use frame_decode::helpers::scale_decode;
use scale_info_legacy::{LookupName, TypeRegistrySet};
// Extrinsic extensions information for modern or legacy extrinsics.
enum AnyExtrinsicExtensionsInfo<'extrinsics, 'atblock> {
Legacy(ExtrinsicExtensionsInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicExtensionsInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>),
}
struct ExtrinsicExtensionsInfo<'extrinsics, 'atblock, TypeId, Resolver> {
info: &'extrinsics frame_decode::extrinsics::ExtrinsicExtensions<'atblock, TypeId>,
resolver: &'atblock Resolver,
}
/// This represents the transaction extensions of an extrinsic.
pub struct ExtrinsicTransactionParams<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: AnyExtrinsicExtensionsInfo<'extrinsics, 'atblock>,
}
macro_rules! with_extensions_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicExtensionsInfo::Legacy($info) => $fn,
AnyExtrinsicExtensionsInfo::Current($info) => $fn,
}
};
}
impl<'extrinsics, 'atblock> ExtrinsicTransactionParams<'extrinsics, 'atblock> {
pub(crate) fn new(
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
) -> Option<Self> {
match info {
AnyExtrinsicInfo::Current(info) => {
let extension_info = info.info.transaction_extension_payload()?;
Some(Self {
all_bytes,
info: AnyExtrinsicExtensionsInfo::Current(ExtrinsicExtensionsInfo {
info: extension_info,
resolver: info.resolver,
}),
})
}
AnyExtrinsicInfo::Legacy(info) => {
let extension_info = info.info.transaction_extension_payload()?;
Some(Self {
all_bytes,
info: AnyExtrinsicExtensionsInfo::Legacy(ExtrinsicExtensionsInfo {
info: extension_info,
resolver: info.resolver,
}),
})
}
}
}
/// Get the raw bytes for all of the transaction extensions.
pub fn bytes(&self) -> &'extrinsics [u8] {
with_extensions_info!(&self.info => &self.all_bytes[info.info.range()])
}
/// iterate over each of the transaction extensions in this extrinsic.
pub fn iter(
&self,
) -> impl Iterator<Item = ExtrinsicTransactionExtension<'extrinsics, 'atblock>> {
match &self.info {
AnyExtrinsicExtensionsInfo::Legacy(extension_info) => {
let iter = extension_info
.info
.iter()
.map(|s| ExtrinsicTransactionExtension {
bytes: &self.all_bytes[s.range()],
info: ExtrinsicExtensionInfo {
name: s.name(),
type_id: s.ty(),
resolver: extension_info.resolver,
}
.into(),
});
Either::A(iter)
}
AnyExtrinsicExtensionsInfo::Current(extension_info) => {
let iter = extension_info
.info
.iter()
.map(|s| ExtrinsicTransactionExtension {
bytes: &self.all_bytes[s.range()],
info: ExtrinsicExtensionInfo {
name: s.name(),
type_id: s.ty(),
resolver: extension_info.resolver,
}
.into(),
});
Either::B(iter)
}
}
}
/// Attempt to decode the transaction extensions into a type where each field name is the name of the transaction
/// extension and the field value is the decoded extension.
pub fn decode_as<T: scale_decode::DecodeAsFields>(
&self,
) -> Result<T, ExtrinsicTransactionExtensionError> {
with_extensions_info!(&self.info => {
let cursor = &mut self.bytes();
let mut fields = &mut info.info.iter().map(|named_arg| {
scale_decode::Field::new(named_arg.ty().clone(), Some(named_arg.name()))
});
let decoded = T::decode_as_fields(cursor, &mut fields, info.resolver)
.map_err(|e| ExtrinsicTransactionExtensionError::AllDecodeError { reason: e })?;
if !cursor.is_empty() {
return Err(ExtrinsicTransactionExtensionError::AllLeftoverBytes {
leftover_bytes: cursor.to_vec(),
})
}
Ok(decoded)
})
}
}
// Extrinsic single extension information for modern or legacy extrinsics.
enum AnyExtrinsicExtensionInfo<'extrinsics, 'atblock> {
Legacy(ExtrinsicExtensionInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicExtensionInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>),
}
impl<'extrinsics, 'atblock>
From<ExtrinsicExtensionInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyExtrinsicExtensionInfo<'extrinsics, 'atblock>
{
fn from(
info: ExtrinsicExtensionInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>,
) -> Self {
AnyExtrinsicExtensionInfo::Legacy(info)
}
}
impl<'extrinsics, 'atblock>
From<ExtrinsicExtensionInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>>
for AnyExtrinsicExtensionInfo<'extrinsics, 'atblock>
{
fn from(
info: ExtrinsicExtensionInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>,
) -> Self {
AnyExtrinsicExtensionInfo::Current(info)
}
}
struct ExtrinsicExtensionInfo<'extrinsics, 'atblock, TypeId, Resolver> {
name: &'extrinsics str,
type_id: &'extrinsics TypeId,
resolver: &'atblock Resolver,
}
macro_rules! with_extension_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicExtensionInfo::Legacy($info) => $fn,
AnyExtrinsicExtensionInfo::Current($info) => $fn,
}
};
}
/// This represents a single transaction extension in an extrinsic.
pub struct ExtrinsicTransactionExtension<'extrinsics, 'atblock> {
bytes: &'extrinsics [u8],
info: AnyExtrinsicExtensionInfo<'extrinsics, 'atblock>,
}
impl<'extrinsics, 'atblock> ExtrinsicTransactionExtension<'extrinsics, 'atblock> {
/// The bytes for this transaction extension.
pub fn bytes(&self) -> &'extrinsics [u8] {
self.bytes
}
/// The name/identifier for this transaction extension.
pub fn name(&self) -> &'extrinsics str {
with_extension_info!(&self.info => info.name)
}
/// Decode the bytes for this transaction extension into a type that implements `scale_decode::DecodeAsType`.
pub fn decode_as<T: scale_decode::DecodeAsType>(
&self,
) -> Result<T, ExtrinsicTransactionExtensionError> {
with_extension_info!(&self.info => {
let cursor = &mut &*self.bytes;
let decoded = T::decode_as_type(cursor, info.type_id.clone(), info.resolver)
.map_err(|reason| ExtrinsicTransactionExtensionError::DecodeError {
name: info.name.to_string(),
reason
})?;
if !cursor.is_empty() {
return Err(ExtrinsicTransactionExtensionError::LeftoverBytes {
name: info.name.to_string(),
leftover_bytes: cursor.to_vec(),
});
}
Ok(decoded)
})
}
}
@@ -0,0 +1,112 @@
use super::extrinsic_call::ExtrinsicCall;
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
use super::extrinsic_transaction_extensions::ExtrinsicTransactionParams;
use crate::client::OfflineClientAtBlockT;
use crate::config::Config;
use crate::error::ExtrinsicsError;
/// This represents some extrinsics in a block, and carries everything that we need to decode information out of them.
pub struct Extrinsics<'atblock> {
bytes: Vec<Vec<u8>>,
// Each index in this vec should line up with one index in the above vec.
infos: Vec<AnyExtrinsicInfo<'atblock>>,
}
impl<'atblock> Extrinsics<'atblock> {
// In here we hide the messy logic needed to decode extrinsics into a consistent output given either current or legacy metadata.
pub(crate) fn new<'client: 'atblock, T, Client>(
bytes: Vec<Vec<u8>>,
client: &'atblock Client,
) -> Result<Self, ExtrinsicsError>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
{
let infos = AnyExtrinsicInfo::new(&bytes, client.metadata(), client.legacy_types())?;
Ok(Extrinsics { bytes, infos })
}
pub(crate) fn empty() -> Self {
Self {
bytes: vec![],
infos: vec![],
}
}
/// How many extrinsics are in this block?
pub fn len(&self) -> usize {
self.bytes.len()
}
/// Are there any extrinsics in this block?
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
/// Iterate over the extrinsics.
pub fn iter(&self) -> impl Iterator<Item = Extrinsic<'_, 'atblock>> {
self.bytes
.iter()
.zip(self.infos.iter())
.enumerate()
.map(|(idx, (bytes, info))| Extrinsic { idx, bytes, info })
}
}
/// This represents an extrinsic, and carries everything that we need to decode information out of it.
pub struct Extrinsic<'extrinsics, 'atblock> {
idx: usize,
bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
}
impl<'extrinsics, 'atblock> Extrinsic<'extrinsics, 'atblock> {
/// Get the index of this extrinsic in the block.
pub fn index(&self) -> usize {
self.idx
}
/// Get the raw bytes of this extrinsic.
pub fn bytes(&self) -> &'extrinsics [u8] {
self.bytes
}
/// Is this extrinsic signed?
pub fn is_signed(&self) -> bool {
with_info!(&self.info => info.info.is_signed())
}
/// Return information about the call that this extrinsic is making.
pub fn call(&self) -> ExtrinsicCall<'extrinsics, 'atblock> {
ExtrinsicCall::new(self.bytes, self.info)
}
/// Return only the bytes of the address that signed this extrinsic.
///
/// # Note
///
/// Returns `None` if the extrinsic is not signed.
pub fn address_bytes(&self) -> Option<&'extrinsics [u8]> {
with_info!(&self.info => {
info.info
.signature_payload()
.map(|s| &self.bytes[s.address_range()])
})
}
/// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
pub fn signature_bytes(&self) -> Option<&'extrinsics [u8]> {
with_info!(&self.info => {
info.info
.signature_payload()
.map(|s| &self.bytes[s.signature_range()])
})
}
/// Get information about the transaction extensions of this extrinsic.
pub fn transaction_extensions(
&self,
) -> Option<ExtrinsicTransactionParams<'extrinsics, 'atblock>> {
ExtrinsicTransactionParams::new(self.bytes, self.info)
}
}
+27
View File
@@ -0,0 +1,27 @@
//! `subxt-historic` is a library for working with non head-of-chain data on Substrate-based blockchains.
// TODO: Remove this when we're ready to release, and document everything!
#![allow(missing_docs)]
mod utils;
pub mod client;
pub mod config;
pub mod error;
pub mod extrinsics;
pub mod storage;
pub use client::{OfflineClient, OnlineClient};
pub use config::polkadot::PolkadotConfig;
pub use config::substrate::SubstrateConfig;
pub use error::Error;
/// External types and crates that may be useful.
pub mod ext {
pub use futures::stream::{Stream, StreamExt};
}
/// Helper types that could be useful.
pub mod helpers {
pub use crate::utils::{AnyResolver, AnyResolverError, AnyTypeId};
}
+346
View File
@@ -0,0 +1,346 @@
mod list_storage_entries_any;
mod storage_entry;
mod storage_info;
mod storage_key;
mod storage_value;
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
use crate::config::Config;
use crate::error::StorageError;
use crate::storage::storage_info::with_info;
use std::borrow::Cow;
use std::sync::Arc;
use storage_info::AnyStorageInfo;
pub use storage_entry::StorageEntry;
pub use storage_key::{StorageHasher, StorageKey, StorageKeyPart};
pub use storage_value::StorageValue;
// We take how storage keys can be passed in from `frame-decode`, so re-export here.
pub use frame_decode::storage::{EncodableValues, IntoEncodableValues};
/// Work with storage.
pub struct StorageClient<'atblock, Client, T> {
client: &'atblock Client,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageClient<'atblock, Client, T> {
/// Work with storage.
pub(crate) fn new(client: &'atblock Client) -> Self {
Self {
client,
marker: std::marker::PhantomData,
}
}
}
// Things that we can do offline with storage.
impl<'atblock, Client, T> StorageClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// Select the storage entry you'd like to work with.
pub fn entry(
&self,
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
let pallet_name = pallet_name.into();
let entry_name = entry_name.into();
let storage_info = AnyStorageInfo::new(
&pallet_name,
&entry_name,
self.client.metadata(),
self.client.legacy_types(),
)?;
Ok(StorageEntryClient {
client: self.client,
pallet_name,
entry_name,
info: Arc::new(storage_info),
marker: std::marker::PhantomData,
})
}
/// Iterate over all of the storage entries listed in the metadata for the current block. This does **not** include well known
/// storage entries like `:code` which are not listed in the metadata.
pub fn entries(&self) -> impl Iterator<Item = StorageEntriesItem<'atblock, Client, T>> {
let client = self.client;
let metadata = client.metadata();
let mut pallet_name = Cow::Borrowed("");
list_storage_entries_any::list_storage_entries_any(metadata).filter_map(move |entry| {
match entry {
frame_decode::storage::StorageEntry::In(name) => {
// Set the pallet name for upcoming entries:
pallet_name = name;
None
}
frame_decode::storage::StorageEntry::Name(entry_name) => {
// Output each entry with the last seen pallet name:
Some(StorageEntriesItem {
pallet_name: pallet_name.clone(),
entry_name,
client: self.client,
marker: std::marker::PhantomData,
})
}
}
})
}
}
/// Working with a specific storage entry.
pub struct StorageEntriesItem<'atblock, Client, T> {
pallet_name: Cow<'atblock, str>,
entry_name: Cow<'atblock, str>,
client: &'atblock Client,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageEntriesItem<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// The pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// The storage entry name.
pub fn entry_name(&self) -> &str {
&self.entry_name
}
/// Extract the relevant storage information so that we can work with this entry.
pub fn entry(&self) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
StorageClient {
client: self.client,
marker: std::marker::PhantomData,
}
.entry(&*self.pallet_name, &*self.entry_name)
}
}
/// A client for working with a specific storage entry.
pub struct StorageEntryClient<'atblock, Client, T> {
client: &'atblock Client,
pallet_name: String,
entry_name: String,
info: Arc<AnyStorageInfo<'atblock>>,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// Get the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// Get the storage entry name.
pub fn entry_name(&self) -> &str {
&self.entry_name
}
/// The key which points to this storage entry (but not necessarily any values within it).
pub fn key_prefix(&self) -> [u8; 32] {
let pallet_name = &*self.pallet_name;
let entry_name = &*self.entry_name;
frame_decode::storage::encode_storage_key_prefix(pallet_name, entry_name)
}
/// Return the default value for this storage entry, if there is one. Returns `None` if there
/// is no default value.
pub fn default_value(&self) -> Option<StorageValue<'atblock>> {
with_info!(info = &*self.info => {
info.info.default_value.as_ref().map(|default_value| {
StorageValue::new(self.info.clone(), default_value.clone())
})
})
}
}
impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OnlineClientAtBlockT<'atblock, T>,
{
/// Fetch a specific key in this map. If the number of keys provided is not equal
/// to the number of keys required to fetch a single value from the map, then an error
/// will be emitted. If no value exists but there is a default value for this storage
/// entry, then the default value will be returned. Else, `None` will be returned.
pub async fn fetch<Keys: IntoEncodableValues>(
&self,
keys: Keys,
) -> Result<Option<StorageValue<'atblock>>, StorageError> {
let expected_num_keys = with_info!(info = &*self.info => {
info.info.keys.len()
});
// For fetching, we need exactly as many keys as exist for a storage entry.
if expected_num_keys != keys.num_encodable_values() {
return Err(StorageError::WrongNumberOfKeysProvidedForFetch {
num_keys_provided: keys.num_encodable_values(),
num_keys_expected: expected_num_keys,
});
}
let key_bytes = self.key(keys)?;
let info = self.info.clone();
let value = fetch(self.client, &key_bytes)
.await?
.map(|bytes| StorageValue::new(info, Cow::Owned(bytes)))
.or_else(|| self.default_value());
Ok(value)
}
/// Iterate over the values underneath the provided keys.
pub async fn iter<Keys: IntoEncodableValues>(
&self,
keys: Keys,
) -> Result<
impl futures::Stream<Item = Result<StorageEntry<'atblock>, StorageError>>
+ Unpin
+ use<'atblock, Client, T, Keys>,
StorageError,
> {
use futures::stream::StreamExt;
use pezkuwi_subxt_rpcs::methods::chain_head::{
ArchiveStorageEvent, StorageQuery, StorageQueryType,
};
let expected_num_keys = with_info!(info = &*self.info => {
info.info.keys.len()
});
// For iterating, we need at most one less key than the number that exists for a storage entry.
// TODO: The error message will be confusing if == keys are provided!
if keys.num_encodable_values() >= expected_num_keys {
return Err(StorageError::TooManyKeysProvidedForIter {
num_keys_provided: keys.num_encodable_values(),
max_keys_expected: expected_num_keys - 1,
});
}
let block_hash = self.client.block_hash();
let key_bytes = self.key(keys)?;
let items = std::iter::once(StorageQuery {
key: &*key_bytes,
query_type: StorageQueryType::DescendantsValues,
});
let sub = self
.client
.rpc_methods()
.archive_v1_storage(block_hash.into(), items, None)
.await
.map_err(|e| StorageError::RpcError { reason: e })?;
let info = self.info.clone();
let sub = sub.filter_map(move |item| {
let info = info.clone();
async move {
let item = match item {
Ok(ArchiveStorageEvent::Item(item)) => item,
Ok(ArchiveStorageEvent::Error(err)) => {
return Some(Err(StorageError::StorageEventError { reason: err.error }));
}
Ok(ArchiveStorageEvent::Done) => return None,
Err(e) => return Some(Err(StorageError::RpcError { reason: e })),
};
item.value
.map(|value| Ok(StorageEntry::new(info, item.key.0, Cow::Owned(value.0))))
}
});
Ok(Box::pin(sub))
}
// Encode a storage key for this storage entry to bytes. The key can be a partial key
// (i.e there are still multiple values below it) or a complete key that points to a specific value.
//
// Dev note: We don't have any functions that can take an already-encoded key and fetch an entry from
// it yet, so we don't expose this. If we did expose it, we might want to return some struct that wraps
// the key bytes and some metadata about them. Or maybe just fetch_raw and iter_raw.
fn key<Keys: IntoEncodableValues>(&self, keys: Keys) -> Result<Vec<u8>, StorageError> {
with_info!(info = &*self.info => {
let key_bytes = frame_decode::storage::encode_storage_key_with_info(
&self.pallet_name,
&self.entry_name,
keys,
&info.info,
info.resolver,
).map_err(|e| StorageError::KeyEncodeError { reason: e })?;
Ok(key_bytes)
})
}
}
// Fetch a single storage value by its key.
async fn fetch<'atblock, Client, T>(
client: &Client,
key_bytes: &[u8],
) -> Result<Option<Vec<u8>>, StorageError>
where
T: Config + 'atblock,
Client: OnlineClientAtBlockT<'atblock, T>,
{
use pezkuwi_subxt_rpcs::methods::chain_head::{ArchiveStorageEvent, StorageQuery, StorageQueryType};
let query = StorageQuery {
key: key_bytes,
query_type: StorageQueryType::Value,
};
let mut response_stream = client
.rpc_methods()
.archive_v1_storage(client.block_hash().into(), std::iter::once(query), None)
.await
.map_err(|e| StorageError::RpcError { reason: e })?;
let value = response_stream
.next()
.await
.transpose()
.map_err(|e| StorageError::RpcError { reason: e })?;
// No value found.
let Some(value) = value else {
return Ok(None);
};
let item = match value {
ArchiveStorageEvent::Item(item) => item,
// if it errors, return the error:
ArchiveStorageEvent::Error(err) => {
return Err(StorageError::StorageEventError { reason: err.error });
}
// if it's done, it means no value was returned:
ArchiveStorageEvent::Done => return Ok(None),
};
// This shouldn't happen, but if it does, the value we wanted wasn't found.
if item.key.0 != key_bytes {
return Ok(None);
}
// The bytes for the storage value. If this is None, then the API is misbehaving,
// ot no matching value was found.
let Some(value_bytes) = item.value else {
return Ok(None);
};
Ok(Some(value_bytes.0))
}
@@ -0,0 +1,35 @@
use frame_decode::storage::StorageEntryInfo;
use frame_metadata::RuntimeMetadata;
pub use frame_decode::storage::StorageEntry;
/// Returns an iterator listing the available storage entries in some metadata.
///
/// This basically calls [`StorageEntryInfo::storage_entries()`] for each metadata version,
/// returning an empty iterator where applicable (ie when passing legacy metadata and the
/// `legacy` features flag is not enabled).
pub fn list_storage_entries_any(
metadata: &RuntimeMetadata,
) -> impl Iterator<Item = StorageEntry<'_>> {
match metadata {
RuntimeMetadata::V0(_deprecated_metadata)
| RuntimeMetadata::V1(_deprecated_metadata)
| RuntimeMetadata::V2(_deprecated_metadata)
| RuntimeMetadata::V3(_deprecated_metadata)
| RuntimeMetadata::V4(_deprecated_metadata)
| RuntimeMetadata::V5(_deprecated_metadata)
| RuntimeMetadata::V6(_deprecated_metadata)
| RuntimeMetadata::V7(_deprecated_metadata) => {
Box::new(core::iter::empty()) as Box<dyn Iterator<Item = StorageEntry<'_>>>
}
RuntimeMetadata::V8(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V9(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V10(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V11(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V12(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V13(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V14(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V15(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V16(m) => Box::new(m.storage_entries()),
}
}
@@ -0,0 +1,48 @@
use super::storage_info::AnyStorageInfo;
use super::storage_key::StorageKey;
use super::storage_value::StorageValue;
use crate::error::StorageKeyError;
use std::borrow::Cow;
use std::sync::Arc;
/// This represents a storage entry, which is a key-value pair in the storage.
pub struct StorageEntry<'atblock> {
key: Vec<u8>,
// This contains the storage information already:
value: StorageValue<'atblock>,
}
impl<'atblock> StorageEntry<'atblock> {
/// Create a new storage entry.
pub fn new(
info: Arc<AnyStorageInfo<'atblock>>,
key: Vec<u8>,
value: Cow<'atblock, [u8]>,
) -> Self {
Self {
key,
value: StorageValue::new(info, value),
}
}
/// Get the raw bytes for this storage entry's key.
pub fn key_bytes(&self) -> &[u8] {
&self.key
}
/// Consume this storage entry and return the raw bytes for the key and value.
pub fn into_key_and_value_bytes(self) -> (Vec<u8>, Vec<u8>) {
(self.key, self.value.into_bytes())
}
/// Decode the key for this storage entry. This gives back a type from which we can
/// decode specific parts of the key hash (where applicable).
pub fn key(&'_ self) -> Result<StorageKey<'_, 'atblock>, StorageKeyError> {
StorageKey::new(&self.value.info, &self.key)
}
/// Return the storage value.
pub fn value(&self) -> &StorageValue<'atblock> {
&self.value
}
}
@@ -0,0 +1,102 @@
use crate::error::StorageError;
use frame_decode::storage::StorageTypeInfo;
use frame_metadata::RuntimeMetadata;
use scale_info_legacy::{LookupName, TypeRegistrySet};
pub enum AnyStorageInfo<'atblock> {
Legacy(StorageInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(StorageInfo<'atblock, u32, scale_info::PortableRegistry>),
}
impl<'atblock> AnyStorageInfo<'atblock> {
/// For a slice of storage entries, return a vec of information about each one.
pub fn new(
pallet_name: &str,
entry_name: &str,
metadata: &'atblock RuntimeMetadata,
legacy_types: &'atblock TypeRegistrySet<'atblock>,
) -> Result<Self, StorageError> {
let info = match metadata {
RuntimeMetadata::V8(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V9(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V10(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V11(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V12(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V13(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V14(m) => storage_info_inner(pallet_name, entry_name, m, &m.types),
RuntimeMetadata::V15(m) => storage_info_inner(pallet_name, entry_name, m, &m.types),
RuntimeMetadata::V16(m) => storage_info_inner(pallet_name, entry_name, m, &m.types),
unknown => {
return Err(StorageError::UnsupportedMetadataVersion {
version: unknown.version(),
});
}
}?;
fn storage_info_inner<'atblock, Info, Resolver>(
pallet_name: &str,
entry_name: &str,
m: &'atblock Info,
type_resolver: &'atblock Resolver,
) -> Result<AnyStorageInfo<'atblock>, StorageError>
where
Info: StorageTypeInfo,
Resolver: scale_type_resolver::TypeResolver<TypeId = Info::TypeId>,
AnyStorageInfo<'atblock>: From<StorageInfo<'atblock, Info::TypeId, Resolver>>,
{
m.storage_info(pallet_name, entry_name)
.map(|frame_storage_info| {
let info = StorageInfo {
info: frame_storage_info,
resolver: type_resolver,
};
AnyStorageInfo::from(info)
})
.map_err(|e| StorageError::ExtractStorageInfoError {
reason: e.into_owned(),
})
}
Ok(info)
}
/// Is the storage entry a map (ie something we'd provide extra keys to access a value, or otherwise iterate over)?
pub fn is_map(&self) -> bool {
match self {
AnyStorageInfo::Legacy(info) => !info.info.keys.is_empty(),
AnyStorageInfo::Current(info) => !info.info.keys.is_empty(),
}
}
}
impl<'atblock> From<StorageInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyStorageInfo<'atblock>
{
fn from(info: StorageInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>) -> Self {
AnyStorageInfo::Legacy(info)
}
}
impl<'atblock> From<StorageInfo<'atblock, u32, scale_info::PortableRegistry>>
for AnyStorageInfo<'atblock>
{
fn from(info: StorageInfo<'atblock, u32, scale_info::PortableRegistry>) -> Self {
AnyStorageInfo::Current(info)
}
}
pub struct StorageInfo<'atblock, TypeId: Clone, Resolver> {
pub info: frame_decode::storage::StorageInfo<'atblock, TypeId>,
pub resolver: &'atblock Resolver,
}
macro_rules! with_info {
($info:ident = $original_info:expr => $fn:expr) => {{
#[allow(clippy::clone_on_copy)]
let info = match $original_info {
AnyStorageInfo::Legacy($info) => $fn,
AnyStorageInfo::Current($info) => $fn,
};
info
}};
}
pub(crate) use with_info;
+176
View File
@@ -0,0 +1,176 @@
use super::AnyStorageInfo;
use crate::{error::StorageKeyError, storage::storage_info::with_info};
use scale_info_legacy::{LookupName, TypeRegistrySet};
// This is part of our public interface.
pub use frame_decode::storage::{IntoDecodableValues, StorageHasher};
enum AnyStorageKeyInfo<'atblock> {
Legacy(StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(StorageKeyInfo<'atblock, u32, scale_info::PortableRegistry>),
}
impl<'atblock> From<StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyStorageKeyInfo<'atblock>
{
fn from(info: StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>) -> Self {
AnyStorageKeyInfo::Legacy(info)
}
}
impl<'atblock> From<StorageKeyInfo<'atblock, u32, scale_info::PortableRegistry>>
for AnyStorageKeyInfo<'atblock>
{
fn from(info: StorageKeyInfo<'atblock, u32, scale_info::PortableRegistry>) -> Self {
AnyStorageKeyInfo::Current(info)
}
}
struct StorageKeyInfo<'atblock, TypeId, Resolver> {
info: frame_decode::storage::StorageKey<TypeId>,
resolver: &'atblock Resolver,
}
macro_rules! with_key_info {
($info:ident = $original_info:expr => $fn:expr) => {{
#[allow(clippy::clone_on_copy)]
let info = match $original_info {
AnyStorageKeyInfo::Legacy($info) => $fn,
AnyStorageKeyInfo::Current($info) => $fn,
};
info
}};
}
/// This represents the different parts of a storage key.
pub struct StorageKey<'entry, 'atblock> {
info: AnyStorageKeyInfo<'atblock>,
bytes: &'entry [u8],
}
impl<'entry, 'atblock> StorageKey<'entry, 'atblock> {
pub(crate) fn new(
info: &AnyStorageInfo<'atblock>,
bytes: &'entry [u8],
) -> Result<Self, StorageKeyError> {
with_info!(info = info => {
let cursor = &mut &*bytes;
let storage_key_info = frame_decode::storage::decode_storage_key_with_info(
cursor,
&info.info,
info.resolver,
).map_err(|e| {
StorageKeyError::DecodeError { reason: e.map_type_id(|id| id.to_string()) }
})?;
if !cursor.is_empty() {
return Err(StorageKeyError::LeftoverBytes {
leftover_bytes: cursor.to_vec(),
});
}
Ok(StorageKey {
info: StorageKeyInfo {
info: storage_key_info,
resolver: info.resolver,
}.into(),
bytes,
})
})
}
/// Attempt to decode the values contained within this storage key to the `Target` type
/// provided. This type is typically a tuple of types which each implement [`scale_decode::DecodeAsType`]
/// and correspond to each of the key types present, in order.
pub fn decode_as<Target: IntoDecodableValues>(&self) -> Result<Target, StorageKeyError> {
with_key_info!(info = &self.info => {
let values = frame_decode::storage::decode_storage_key_values(
self.bytes,
&info.info,
info.resolver
).map_err(|e| {
StorageKeyError::DecodeKeyValueError { reason: e }
})?;
Ok(values)
})
}
/// Iterate over the parts of this storage key. Each part of a storage key corresponds to a
/// single value that has been hashed.
pub fn parts(&'_ self) -> impl ExactSizeIterator<Item = StorageKeyPart<'_, 'entry, 'atblock>> {
let parts_len = with_key_info!(info = &self.info => info.info.parts().len());
(0..parts_len).map(move |index| StorageKeyPart {
index,
info: &self.info,
bytes: self.bytes,
})
}
/// Return the part of the storage key at the provided index, or `None` if the index is out of bounds.
pub fn part(&self, index: usize) -> Option<StorageKeyPart<'_, 'entry, 'atblock>> {
if index < self.parts().len() {
Some(StorageKeyPart {
index,
info: &self.info,
bytes: self.bytes,
})
} else {
None
}
}
}
/// This represents a part of a storage key.
pub struct StorageKeyPart<'key, 'entry, 'atblock> {
index: usize,
info: &'key AnyStorageKeyInfo<'atblock>,
bytes: &'entry [u8],
}
impl<'key, 'entry, 'atblock> StorageKeyPart<'key, 'entry, 'atblock> {
/// Get the raw bytes for this part of the storage key.
pub fn bytes(&self) -> &'entry [u8] {
with_key_info!(info = &self.info => {
let part = &info.info[self.index];
let hash_range = part.hash_range();
let value_range = part
.value()
.map(|v| v.range())
.unwrap_or(std::ops::Range { start: hash_range.end, end: hash_range.end });
let combined_range = std::ops::Range {
start: hash_range.start,
end: value_range.end,
};
&self.bytes[combined_range]
})
}
/// Get the hasher that was used to construct this part of the storage key.
pub fn hasher(&self) -> StorageHasher {
with_key_info!(info = &self.info => info.info[self.index].hasher())
}
/// For keys that were produced using "concat" or "identity" hashers, the value
/// is available as a part of the key hash, allowing us to decode it into anything
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
/// different hasher, this will return `None`.
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
with_key_info!(info = &self.info => {
let part_info = &info.info[self.index];
let Some(value_info) = part_info.value() else {
return Ok(None);
};
let value_bytes = &self.bytes[value_info.range()];
let value_ty = value_info.ty().clone();
let decoded_key_part = T::decode_as_type(
&mut &*value_bytes,
value_ty,
info.resolver,
).map_err(|e| StorageKeyError::DecodePartError { index: self.index, reason: e })?;
Ok(Some(decoded_key_part))
})
}
}
@@ -0,0 +1,79 @@
use super::storage_info::AnyStorageInfo;
use super::storage_info::with_info;
use crate::error::StorageValueError;
use crate::utils::{AnyResolver, AnyTypeId};
use scale_decode::DecodeAsType;
use std::borrow::Cow;
use std::sync::Arc;
/// This represents a storage value.
pub struct StorageValue<'atblock> {
pub(crate) info: Arc<AnyStorageInfo<'atblock>>,
bytes: Cow<'atblock, [u8]>,
resolver: AnyResolver<'atblock, 'atblock>,
}
impl<'atblock> StorageValue<'atblock> {
/// Create a new storage value.
pub(crate) fn new(info: Arc<AnyStorageInfo<'atblock>>, bytes: Cow<'atblock, [u8]>) -> Self {
let resolver = match &*info {
AnyStorageInfo::Current(info) => AnyResolver::A(info.resolver),
AnyStorageInfo::Legacy(info) => AnyResolver::B(info.resolver),
};
Self {
info,
bytes,
resolver,
}
}
/// Get the raw bytes for this storage value.
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Consume this storage value and return the raw bytes.
pub fn into_bytes(self) -> Vec<u8> {
self.bytes.to_vec()
}
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
/// version of [`StorageValue::decode_as`], as the visitor is able to preserve lifetimes
/// and has access to more type information than is available via [`StorageValue::decode_as`].
pub fn visit<
V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock, 'atblock>>,
>(
&self,
visitor: V,
) -> Result<V::Value<'_, '_>, V::Error> {
let type_id = match &*self.info {
AnyStorageInfo::Current(info) => AnyTypeId::A(info.info.value_id),
AnyStorageInfo::Legacy(info) => AnyTypeId::B(info.info.value_id.clone()),
};
let cursor = &mut self.bytes();
scale_decode::visitor::decode_with_visitor(cursor, type_id, &self.resolver, visitor)
}
/// Decode this storage value.
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
with_info!(info = &*self.info => {
let cursor = &mut &*self.bytes;
let value = T::decode_as_type(
cursor,
info.info.value_id.clone(),
info.resolver,
).map_err(|e| StorageValueError::DecodeError { reason: e })?;
if !cursor.is_empty() {
return Err(StorageValueError::LeftoverBytes {
leftover_bytes: cursor.to_vec(),
});
}
Ok(value)
})
}
}
+7
View File
@@ -0,0 +1,7 @@
mod any_resolver;
mod either;
mod range_map;
pub use any_resolver::{AnyResolver, AnyResolverError, AnyTypeId};
pub use either::Either;
pub use range_map::RangeMap;
+186
View File
@@ -0,0 +1,186 @@
use super::Either;
use scale_info_legacy::LookupName;
use scale_type_resolver::ResolvedTypeVisitor;
/// A type resolver which could either be for modern or historic resolving.
pub type AnyResolver<'a, 'b> =
Either<&'a scale_info::PortableRegistry, &'a scale_info_legacy::TypeRegistrySet<'b>>;
/// A type ID which is either a modern or historic ID.
pub type AnyTypeId = Either<u32, scale_info_legacy::LookupName>;
impl Default for AnyTypeId {
fn default() -> Self {
// Not a sensible default, but we don't need / can't provide a sensible one.
AnyTypeId::A(u32::MAX)
}
}
impl From<u32> for AnyTypeId {
fn from(value: u32) -> Self {
AnyTypeId::A(value)
}
}
impl From<LookupName> for AnyTypeId {
fn from(value: LookupName) -> Self {
AnyTypeId::B(value)
}
}
impl TryFrom<AnyTypeId> for u32 {
type Error = ();
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
match value {
AnyTypeId::A(v) => Ok(v),
AnyTypeId::B(_) => Err(()),
}
}
}
impl TryFrom<AnyTypeId> for LookupName {
type Error = ();
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
match value {
AnyTypeId::A(_) => Err(()),
AnyTypeId::B(v) => Ok(v),
}
}
}
/// A resolve error that comes from using [`AnyResolver`] to resolve some [`AnyTypeId`] into a type.
#[derive(Debug, thiserror::Error)]
pub enum AnyResolverError {
#[error("got a {got} type ID but expected a {expected} type ID")]
TypeIdMismatch {
got: &'static str,
expected: &'static str,
},
#[error("{0}")]
ScaleInfo(scale_type_resolver::portable_registry::Error),
#[error("{0}")]
ScaleInfoLegacy(scale_info_legacy::type_registry::TypeRegistryResolveError),
}
impl<'a, 'b> scale_type_resolver::TypeResolver for AnyResolver<'a, 'b> {
type TypeId = AnyTypeId;
type Error = AnyResolverError;
fn resolve_type<'this, V: ResolvedTypeVisitor<'this, TypeId = Self::TypeId>>(
&'this self,
type_id: Self::TypeId,
visitor: V,
) -> Result<V::Value, Self::Error> {
match (self, type_id) {
(Either::A(resolver), Either::A(id)) => resolver
.resolve_type(id, ModernVisitor(visitor))
.map_err(AnyResolverError::ScaleInfo),
(Either::B(resolver), Either::B(id)) => resolver
.resolve_type(id, LegacyVisitor(visitor))
.map_err(AnyResolverError::ScaleInfoLegacy),
(Either::A(_), Either::B(_)) => Err(AnyResolverError::TypeIdMismatch {
got: "LookupName",
expected: "u32",
}),
(Either::B(_), Either::A(_)) => Err(AnyResolverError::TypeIdMismatch {
got: "u32",
expected: "LookupName",
}),
}
}
}
// We need to have a visitor which understands only modern or legacy types, and can wrap the more generic visitor
// that must be provided to AnyResolver::resolve_type. This then allows us to visit historic _or_ modern types
// using the single visitor provided by the user.
struct LegacyVisitor<V>(V);
struct ModernVisitor<V>(V);
mod impls {
use super::{AnyTypeId, LegacyVisitor, LookupName, ModernVisitor};
use scale_type_resolver::*;
// An ugly implementation which maps from modern or legacy types into our AnyTypeId,
// to make LegacyVisitor and ModernVisitor valid visitors when wrapping a generic "any" visitor.
macro_rules! impl_visitor_mapper {
($struc:ident, $type_id_ty:ident, $variant:ident) => {
impl<'this, V> ResolvedTypeVisitor<'this> for $struc<V>
where
V: ResolvedTypeVisitor<'this, TypeId = AnyTypeId>,
{
type TypeId = $type_id_ty;
type Value = V::Value;
fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value {
self.0.visit_unhandled(kind)
}
fn visit_array(self, type_id: Self::TypeId, len: usize) -> Self::Value {
self.0.visit_array(AnyTypeId::$variant(type_id), len)
}
fn visit_not_found(self) -> Self::Value {
self.0.visit_not_found()
}
fn visit_composite<Path, Fields>(self, path: Path, fields: Fields) -> Self::Value
where
Path: PathIter<'this>,
Fields: FieldIter<'this, Self::TypeId>,
{
self.0.visit_composite(
path,
fields.map(|field| Field {
name: field.name,
id: AnyTypeId::$variant(field.id),
}),
)
}
fn visit_variant<Path, Fields, Var>(self, path: Path, variants: Var) -> Self::Value
where
Path: PathIter<'this>,
Fields: FieldIter<'this, Self::TypeId>,
Var: VariantIter<'this, Fields>,
{
self.0.visit_variant(
path,
variants.map(|variant| Variant {
index: variant.index,
name: variant.name,
fields: variant.fields.map(|field| Field {
name: field.name,
id: AnyTypeId::$variant(field.id),
}),
}),
)
}
fn visit_sequence<Path>(self, path: Path, type_id: Self::TypeId) -> Self::Value
where
Path: PathIter<'this>,
{
self.0.visit_sequence(path, AnyTypeId::$variant(type_id))
}
fn visit_tuple<TypeIds>(self, type_ids: TypeIds) -> Self::Value
where
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
{
self.0
.visit_tuple(type_ids.map(|id| AnyTypeId::$variant(id)))
}
fn visit_primitive(self, primitive: Primitive) -> Self::Value {
self.0.visit_primitive(primitive)
}
fn visit_compact(self, type_id: Self::TypeId) -> Self::Value {
self.0.visit_compact(AnyTypeId::$variant(type_id))
}
fn visit_bit_sequence(
self,
store_format: BitsStoreFormat,
order_format: BitsOrderFormat,
) -> Self::Value {
self.0.visit_bit_sequence(store_format, order_format)
}
}
};
}
impl_visitor_mapper!(ModernVisitor, u32, A);
impl_visitor_mapper!(LegacyVisitor, LookupName, B);
}
+49
View File
@@ -0,0 +1,49 @@
macro_rules! either {
($name:ident( $fst:ident, $($variant:ident),* )) => {
#[derive(Clone, Copy, Debug)]
pub enum $name<$fst, $($variant),*> {
$fst($fst),
$($variant($variant),)*
}
impl<$fst, $($variant),*> Iterator for $name<$fst, $($variant),*>
where
$fst: Iterator,
$($variant: Iterator<Item = $fst::Item>,)*
{
type Item = $fst::Item;
fn next(&mut self) -> Option<Self::Item> {
match self {
$name::$fst(inner) => inner.next(),
$( $name::$variant(inner) => inner.next(), )*
}
}
}
impl <$fst, $($variant),*> futures::stream::Stream for $name<$fst, $($variant),*>
where
$fst: futures::stream::Stream,
$($variant: futures::stream::Stream<Item = $fst::Item>,)*
{
type Item = $fst::Item;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
use std::pin::Pin;
// SAFETY: This is safe because we never move the inner value out of the Pin.
unsafe {
match self.get_unchecked_mut() {
$name::$fst(inner) => Pin::new_unchecked(inner).poll_next(cx),
$( $name::$variant(inner) => Pin::new_unchecked(inner).poll_next(cx), )*
}
}
}
}
}
}
either!(Either(A, B));
+154
View File
@@ -0,0 +1,154 @@
use std::fmt::Display;
/// A map that associates ranges of keys with values.
#[derive(Debug, Clone)]
pub struct RangeMap<K, V> {
// (range_start, range_ended, value). This is
// guaranteed to be sorted and have non-overlapping ranges.
mapping: Vec<(K, K, V)>,
}
impl<K: Clone + Copy + Display + PartialOrd + Ord, V> RangeMap<K, V> {
/// Build an empty [`RangeMap`] as a placeholder.
pub fn empty() -> Self {
RangeMap {
mapping: Vec::new(),
}
}
/// Build a [`RangeMap`].
pub fn builder() -> RangeMapBuilder<K, V> {
RangeMapBuilder {
mapping: Vec::new(),
}
}
/// Return the value whose key is within the range, or None if not found.
pub fn get(&self, key: K) -> Option<&V> {
let idx = self
.mapping
.binary_search_by_key(&key, |&(start, end, _)| {
if key >= start && key < end {
key
} else {
start
}
})
.ok()?;
self.mapping.get(idx).map(|(_, _, val)| val)
}
}
/// A builder for constructing a [`RangeMap`]. Use [``RangeMap::builder()`] to create one.
#[derive(Debug, Clone)]
pub struct RangeMapBuilder<K, V> {
mapping: Vec<(K, K, V)>,
}
impl<K: Clone + Copy + Display + PartialOrd + Ord, V> RangeMapBuilder<K, V> {
/// Try to add a range, mapping block numbers to a spec version.
///
/// Returns an error if the range is empty or overlaps with an existing range.
pub fn try_add_range(
&mut self,
start: K,
end: K,
val: V,
) -> Result<&mut Self, RangeMapError<K>> {
let (start, end) = if start < end {
(start, end)
} else {
(end, start)
};
if start == end {
return Err(RangeMapError::EmptyRange(start));
}
if let Some(&(s, e, _)) = self.mapping.iter().find(|&&(s, e, _)| start < e && end > s) {
return Err(RangeMapError::OverlappingRanges {
proposed: (start, end),
existing: (s, e),
});
}
self.mapping.push((start, end, val));
Ok(self)
}
/// Add a range of blocks with the given spec version.
///
/// # Panics
///
/// This method will panic if the range is empty or overlaps with an existing range.
pub fn add_range(mut self, start: K, end: K, val: V) -> Self {
if let Err(e) = self.try_add_range(start, end, val) {
panic!("{e}")
}
self
}
/// Finish adding ranges and build the [`RangeMap`].
pub fn build(mut self) -> RangeMap<K, V> {
self.mapping.sort_by_key(|&(start, _, _)| start);
RangeMap {
mapping: self.mapping,
}
}
}
/// An error that can occur when calling [`RangeMapBuilder::try_add_range()`].
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RangeMapError<K: Display> {
/// An error indicating that the proposed block range is empty.
#[error("Block range cannot be empty: start and end values must be different, but got {} for both", .0)]
EmptyRange(K),
/// An error indicating that the proposed block range overlaps with an existing one.
#[error("Overlapping block ranges are not allowed: proposed range is {}..{}, but we already have {}..{}", proposed.0, proposed.1, existing.0, existing.1)]
OverlappingRanges { proposed: (K, K), existing: (K, K) },
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_rangemap_get() {
let spec_version = RangeMap::builder()
.add_range(0, 100, 1)
.add_range(100, 200, 2)
.add_range(200, 300, 3)
.build();
assert_eq!(spec_version.get(0), Some(&1));
assert_eq!(spec_version.get(50), Some(&1));
assert_eq!(spec_version.get(100), Some(&2));
assert_eq!(spec_version.get(150), Some(&2));
assert_eq!(spec_version.get(200), Some(&3));
assert_eq!(spec_version.get(250), Some(&3));
assert_eq!(spec_version.get(300), None);
}
#[test]
fn test_rangemap_set() {
let mut spec_version = RangeMap::builder()
.add_range(0, 100, 1)
.add_range(200, 300, 3);
assert_eq!(
spec_version.try_add_range(99, 130, 2).unwrap_err(),
RangeMapError::OverlappingRanges {
proposed: (99, 130),
existing: (0, 100),
}
);
assert_eq!(
spec_version.try_add_range(170, 201, 2).unwrap_err(),
RangeMapError::OverlappingRanges {
proposed: (170, 201),
existing: (200, 300),
}
);
}
}