Move serai-client off serai-runtime, MIT licensing it

Uses a full-fledged serai-abi to do so.

Removes use of UncheckedExtrinsic as a pointlessly (for us) length-prefixed
block with a more complicated signing algorithm than advantageous.

In the future, we should considering consolidating the various primitives
crates. I'm not convinced we benefit from one primitives crate per pallet.
This commit is contained in:
Luke Parker
2023-12-07 02:30:09 -05:00
parent 6416e0079b
commit c511a54d18
38 changed files with 484 additions and 378 deletions

View File

@@ -6,19 +6,18 @@ mod serai;
#[cfg(feature = "serai")]
pub use serai::*;
// If we aren't exposing the Serai client (subxt), still expose all primitives
#[cfg(not(feature = "serai"))]
pub use serai_runtime::primitives;
pub use serai_abi::primitives;
#[cfg(not(feature = "serai"))]
mod other_primitives {
pub mod in_instructions {
pub use serai_runtime::in_instructions::primitives;
}
pub mod coins {
pub use serai_runtime::coins::primitives;
pub use serai_abi::coins::primitives;
}
pub mod validator_sets {
pub use serai_runtime::validator_sets::primitives;
pub use serai_abi::validator_sets::primitives;
}
pub mod in_instructions {
pub use serai_abi::in_instructions::primitives;
}
}
#[cfg(not(feature = "serai"))]

View File

@@ -1,17 +1,14 @@
use scale::Encode;
use serai_runtime::{
primitives::{SeraiAddress, Amount, Coin, Balance},
coins, Runtime,
};
pub use coins::primitives;
use serai_abi::primitives::{SeraiAddress, Amount, Coin, Balance};
pub use serai_abi::coins::primitives;
use primitives::OutInstructionWithBalance;
use crate::{TemporalSerai, SeraiError};
const PALLET: &str = "Coins";
pub type CoinsEvent = coins::Event<Runtime>;
pub type CoinsEvent = serai_abi::coins::Event;
#[derive(Clone, Copy)]
pub struct SeraiCoins<'a>(pub(crate) TemporalSerai<'a>);
@@ -24,7 +21,7 @@ impl<'a> SeraiCoins<'a> {
self
.0
.events(|event| {
if let serai_runtime::RuntimeEvent::Coins(event) = event {
if let serai_abi::Event::Coins(event) = event {
Some(event).filter(|event| matches!(event, CoinsEvent::Mint { .. }))
} else {
None
@@ -37,7 +34,7 @@ impl<'a> SeraiCoins<'a> {
self
.0
.events(|event| {
if let serai_runtime::RuntimeEvent::Coins(event) = event {
if let serai_abi::Event::Coins(event) = event {
Some(event).filter(|event| matches!(event, CoinsEvent::BurnWithInstruction { .. }))
} else {
None
@@ -68,22 +65,15 @@ impl<'a> SeraiCoins<'a> {
)
}
pub fn transfer(to: SeraiAddress, balance: Balance) -> serai_runtime::RuntimeCall {
serai_runtime::RuntimeCall::Coins(serai_runtime::coins::Call::<Runtime>::transfer {
to: to.into(),
balance,
})
pub fn transfer(to: SeraiAddress, balance: Balance) -> serai_abi::Call {
serai_abi::Call::Coins(serai_abi::coins::Call::transfer { to, balance })
}
pub fn burn(balance: Balance) -> serai_runtime::RuntimeCall {
serai_runtime::RuntimeCall::Coins(serai_runtime::coins::Call::<Runtime>::burn { balance })
pub fn burn(balance: Balance) -> serai_abi::Call {
serai_abi::Call::Coins(serai_abi::coins::Call::burn { balance })
}
pub fn burn_with_instruction(
instruction: OutInstructionWithBalance,
) -> serai_runtime::RuntimeCall {
serai_runtime::RuntimeCall::Coins(
serai_runtime::coins::Call::<Runtime>::burn_with_instruction { instruction },
)
pub fn burn_with_instruction(instruction: OutInstructionWithBalance) -> serai_abi::Call {
serai_abi::Call::Coins(serai_abi::coins::Call::burn_with_instruction { instruction })
}
}

View File

@@ -1,12 +1,9 @@
use sp_core::bounded_vec::BoundedVec;
use serai_runtime::{
primitives::{SeraiAddress, Amount, Coin},
dex, Runtime,
};
use serai_abi::primitives::{SeraiAddress, Amount, Coin};
use crate::{SeraiError, TemporalSerai};
pub type DexEvent = dex::Event<Runtime>;
pub type DexEvent = serai_abi::dex::Event;
#[derive(Clone, Copy)]
pub struct SeraiDex<'a>(pub(crate) TemporalSerai<'a>);
@@ -14,15 +11,7 @@ impl<'a> SeraiDex<'a> {
pub async fn events(&self) -> Result<Vec<DexEvent>, SeraiError> {
self
.0
.events(
|event| {
if let serai_runtime::RuntimeEvent::Dex(event) = event {
Some(event)
} else {
None
}
},
)
.events(|event| if let serai_abi::Event::Dex(event) = event { Some(event) } else { None })
.await
}
@@ -33,14 +22,14 @@ impl<'a> SeraiDex<'a> {
min_coin_amount: Amount,
min_sri_amount: Amount,
address: SeraiAddress,
) -> serai_runtime::RuntimeCall {
serai_runtime::RuntimeCall::Dex(dex::Call::<Runtime>::add_liquidity {
) -> serai_abi::Call {
serai_abi::Call::Dex(serai_abi::dex::Call::add_liquidity {
coin,
coin_desired: coin_amount.0,
sri_desired: sri_amount.0,
coin_min: min_coin_amount.0,
sri_min: min_sri_amount.0,
mint_to: address.into(),
mint_to: address,
})
}
@@ -50,7 +39,7 @@ impl<'a> SeraiDex<'a> {
amount_in: Amount,
amount_out_min: Amount,
address: SeraiAddress,
) -> serai_runtime::RuntimeCall {
) -> serai_abi::Call {
let path = if to_coin.is_native() {
BoundedVec::try_from(vec![from_coin, Coin::Serai]).unwrap()
} else if from_coin.is_native() {
@@ -59,11 +48,11 @@ impl<'a> SeraiDex<'a> {
BoundedVec::try_from(vec![from_coin, Coin::Serai, to_coin]).unwrap()
};
serai_runtime::RuntimeCall::Dex(dex::Call::<Runtime>::swap_exact_tokens_for_tokens {
serai_abi::Call::Dex(serai_abi::dex::Call::swap_exact_tokens_for_tokens {
path,
amount_in: amount_in.0,
amount_out_min: amount_out_min.0,
send_to: address.into(),
send_to: address,
})
}
}

View File

@@ -1,13 +1,12 @@
use serai_runtime::{in_instructions, Runtime};
pub use in_instructions::primitives;
pub use serai_abi::in_instructions::primitives;
use primitives::SignedBatch;
use crate::{
primitives::{BlockHash, NetworkId},
SeraiError, Serai, TemporalSerai,
Transaction, SeraiError, Serai, TemporalSerai,
};
pub type InInstructionsEvent = in_instructions::Event<Runtime>;
pub type InInstructionsEvent = serai_abi::in_instructions::Event;
const PALLET: &str = "InInstructions";
@@ -36,7 +35,7 @@ impl<'a> SeraiInInstructions<'a> {
self
.0
.events(|event| {
if let serai_runtime::RuntimeEvent::InInstructions(event) = event {
if let serai_abi::Event::InInstructions(event) = event {
Some(event).filter(|event| matches!(event, InInstructionsEvent::Batch { .. }))
} else {
None
@@ -45,9 +44,9 @@ impl<'a> SeraiInInstructions<'a> {
.await
}
pub fn execute_batch(batch: SignedBatch) -> Vec<u8> {
Serai::unsigned(&serai_runtime::RuntimeCall::InInstructions(
in_instructions::Call::<Runtime>::execute_batch { batch },
pub fn execute_batch(batch: SignedBatch) -> Transaction {
Serai::unsigned(serai_abi::Call::InInstructions(
serai_abi::in_instructions::Call::execute_batch { batch },
))
}
}

View File

@@ -10,11 +10,12 @@ pub use sp_core::{
sr25519::{Public, Pair},
};
pub use serai_runtime::primitives;
pub use primitives::{SeraiAddress, Signature, Amount};
pub use serai_abi as abi;
pub use abi::{primitives, Transaction};
use abi::*;
pub use serai_runtime as runtime;
use serai_runtime::{Header, Block as SeraiBlock};
pub use primitives::{SeraiAddress, Signature, Amount};
use primitives::Header;
pub mod coins;
pub use coins::SeraiCoins;
@@ -25,36 +26,28 @@ pub use in_instructions::SeraiInInstructions;
pub mod validator_sets;
pub use validator_sets::SeraiValidatorSets;
pub type Transaction = serai_runtime::UncheckedExtrinsic;
#[derive(Clone, Debug)]
pub struct Block(SeraiBlock);
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode)]
pub struct Block {
pub header: Header,
pub transactions: Vec<Transaction>,
}
impl Block {
pub fn hash(&self) -> [u8; 32] {
self.0.header.hash().into()
self.header.hash().into()
}
pub fn number(&self) -> u64 {
self.0.header.number
self.header.number
}
/// Returns the time of this block, set by its producer, in milliseconds since the epoch.
pub fn time(&self) -> Result<u64, SeraiError> {
for extrinsic in &self.0.extrinsics {
if let serai_runtime::RuntimeCall::Timestamp(serai_runtime::timestamp::Call::set { now }) =
&extrinsic.function
{
return Ok(*now);
for transaction in &self.transactions {
if let Call::Timestamp(timestamp::Call::set { now }) = &transaction.call {
return Ok(u64::from(*now));
}
}
Err(SeraiError::InvalidNode("no time was present in block".to_string()))
}
pub fn header(&self) -> &Header {
&self.0.header
}
pub fn transactions(&self) -> &[Transaction] {
&self.0.extrinsics
}
}
#[derive(Error, Debug)]
@@ -158,54 +151,37 @@ impl Serai {
Ok(res)
}
fn unsigned(call: &serai_runtime::RuntimeCall) -> Vec<u8> {
// TODO: Should Serai purge the old transaction code AND set this to 0/1?
const EXTRINSIC_FORMAT_VERSION: u8 = 4;
let mut tx = vec![EXTRINSIC_FORMAT_VERSION];
tx.extend(call.encode());
let mut length_prefixed = Compact(u32::try_from(tx.len()).unwrap()).encode();
length_prefixed.extend(tx);
length_prefixed
fn unsigned(call: Call) -> Transaction {
Transaction { call, signature: None }
}
pub fn sign(
&self,
signer: &Pair,
call: &serai_runtime::RuntimeCall,
nonce: u32,
tip: u64,
) -> Vec<u8> {
pub fn sign(&self, signer: &Pair, call: Call, nonce: u32, tip: u64) -> Transaction {
const SPEC_VERSION: u32 = 1;
const TX_VERSION: u32 = 1;
const EXTRINSIC_FORMAT_VERSION: u8 = 4;
let era = sp_runtime::generic::Era::Immortal;
let extra = (era, Compact(nonce), Compact(tip));
let genesis = self.genesis;
let mortality_checkpoint = genesis;
let mut signature_payload =
(call, extra, SPEC_VERSION, TX_VERSION, genesis, mortality_checkpoint).encode();
if signature_payload.len() > 256 {
signature_payload = sp_core::blake2_256(&signature_payload).to_vec();
}
let extra =
Extra { era: sp_runtime::generic::Era::Immortal, nonce: Compact(nonce), tip: Compact(tip) };
let signature_payload = (
&call,
&extra,
SignedPayloadExtra {
spec_version: SPEC_VERSION,
tx_version: TX_VERSION,
genesis: self.genesis,
mortality_checkpoint: self.genesis,
},
)
.encode();
let signature = signer.sign(&signature_payload);
let signed = 1 << 7;
let tx = (signed + EXTRINSIC_FORMAT_VERSION, signer.public(), signature, extra, call).encode();
let mut length_prefixed = Compact(u32::try_from(tx.len()).unwrap()).encode();
length_prefixed.extend(tx);
length_prefixed
Transaction { call, signature: Some((signer.public().into(), signature, extra)) }
}
// TODO: Move this to take in Transaction
pub async fn publish(&self, tx: &[u8]) -> Result<(), SeraiError> {
pub async fn publish(&self, tx: &Transaction) -> Result<(), SeraiError> {
// Drop the returned hash, which is the hash of the raw extrinsic, as extrinsics are allowed
// to share hashes and this hash is accordingly useless/unsafe
// If we are to return something, it should be block included in and position within block
let _: String = self.call("author_submitExtrinsic", [hex::encode(tx)]).await?;
let _: String = self.call("author_submitExtrinsic", [hex::encode(tx.encode())]).await?;
Ok(())
}
@@ -221,14 +197,15 @@ impl Serai {
}
pub async fn block(&self, hash: [u8; 32]) -> Result<Option<Block>, SeraiError> {
// TODO: Remove this wrapping from Serai?
#[derive(Deserialize)]
struct WrappedBlock {
block: SeraiBlock,
}
let block: Option<WrappedBlock> = self.call("chain_getBlock", [hex::encode(hash)]).await?;
let block: Option<String> = self.call("chain_getBlockBin", [hex::encode(hash)]).await?;
let Some(block) = block else { return Ok(None) };
Ok(Some(Block(block.block)))
let Ok(bytes) = Self::hex_decode(block) else {
Err(SeraiError::InvalidNode("didn't return a hex-encoded block".to_string()))?
};
let Ok(block) = Block::decode(&mut bytes.as_slice()) else {
Err(SeraiError::InvalidNode("didn't return a block".to_string()))?
};
Ok(Some(block))
}
pub async fn latest_finalized_block(&self) -> Result<Block, SeraiError> {
@@ -276,7 +253,7 @@ impl Serai {
let hash = self.block_hash(number).await?;
let Some(hash) = hash else { return Ok(None) };
let Some(block) = self.block(hash).await? else { return Ok(None) };
if !self.is_finalized(&block.0.header).await? {
if !self.is_finalized(&block.header).await? {
return Ok(None);
}
Ok(Some(block))
@@ -326,14 +303,10 @@ impl<'a> TemporalSerai<'a> {
self.0
}
async fn events<E>(
&self,
filter_map: impl Fn(serai_runtime::RuntimeEvent) -> Option<E>,
) -> Result<Vec<E>, SeraiError> {
async fn events<E>(&self, filter_map: impl Fn(Event) -> Option<E>) -> Result<Vec<E>, SeraiError> {
let mut res = vec![];
let all_events: Option<
Vec<serai_runtime::system::EventRecord<serai_runtime::RuntimeEvent, [u8; 32]>>,
> = self.storage("System", "Events", ()).await?;
let all_events: Option<Vec<frame_system::EventRecord<Event, [u8; 32]>>> =
self.storage("System", "Events", ()).await?;
#[allow(clippy::unwrap_or_default)]
for event in all_events.unwrap_or(vec![]) {
if let Some(event) = filter_map(event.event) {

View File

@@ -2,15 +2,18 @@ use scale::Encode;
use sp_core::sr25519::{Public, Signature};
use serai_runtime::{primitives::Amount, validator_sets, Runtime};
pub use validator_sets::primitives;
use serai_abi::primitives::Amount;
pub use serai_abi::validator_sets::primitives;
use primitives::{Session, ValidatorSet, KeyPair};
use crate::{primitives::NetworkId, Serai, TemporalSerai, SeraiError};
use crate::{
primitives::{NetworkId, SeraiAddress},
Transaction, Serai, TemporalSerai, SeraiError,
};
const PALLET: &str = "ValidatorSets";
pub type ValidatorSetsEvent = validator_sets::Event<Runtime>;
pub type ValidatorSetsEvent = serai_abi::validator_sets::Event;
#[derive(Clone, Copy)]
pub struct SeraiValidatorSets<'a>(pub(crate) TemporalSerai<'a>);
@@ -23,7 +26,7 @@ impl<'a> SeraiValidatorSets<'a> {
self
.0
.events(|event| {
if let serai_runtime::RuntimeEvent::ValidatorSets(event) = event {
if let serai_abi::Event::ValidatorSets(event) = event {
Some(event).filter(|event| matches!(event, ValidatorSetsEvent::NewSet { .. }))
} else {
None
@@ -36,7 +39,7 @@ impl<'a> SeraiValidatorSets<'a> {
self
.0
.events(|event| {
if let serai_runtime::RuntimeEvent::ValidatorSets(event) = event {
if let serai_abi::Event::ValidatorSets(event) = event {
Some(event).filter(|event| matches!(event, ValidatorSetsEvent::KeyGen { .. }))
} else {
None
@@ -49,7 +52,7 @@ impl<'a> SeraiValidatorSets<'a> {
self
.0
.events(|event| {
if let serai_runtime::RuntimeEvent::ValidatorSets(event) = event {
if let serai_abi::Event::ValidatorSets(event) = event {
Some(event).filter(|event| matches!(event, ValidatorSetsEvent::SetRetired { .. }))
} else {
None
@@ -107,20 +110,22 @@ impl<'a> SeraiValidatorSets<'a> {
self.0.storage(PALLET, "Keys", (sp_core::hashing::twox_64(&set.encode()), set)).await
}
pub fn set_keys(network: NetworkId, key_pair: KeyPair, signature: Signature) -> Vec<u8> {
Serai::unsigned(&serai_runtime::RuntimeCall::ValidatorSets(
validator_sets::Call::<Runtime>::set_keys { network, key_pair, signature },
))
pub fn set_keys(network: NetworkId, key_pair: KeyPair, signature: Signature) -> Transaction {
Serai::unsigned(serai_abi::Call::ValidatorSets(serai_abi::validator_sets::Call::set_keys {
network,
key_pair,
signature,
}))
}
pub fn remove_participant(
network: NetworkId,
to_remove: Public,
signers: Vec<Public>,
to_remove: SeraiAddress,
signers: Vec<SeraiAddress>,
signature: Signature,
) -> Vec<u8> {
Serai::unsigned(&serai_runtime::RuntimeCall::ValidatorSets(
validator_sets::Call::<Runtime>::remove_participant {
) -> Transaction {
Serai::unsigned(serai_abi::Call::ValidatorSets(
serai_abi::validator_sets::Call::remove_participant {
network,
to_remove,
signers,