Skip to content

Commit

Permalink
Mesh 2098/store tracker address (#1559)
Browse files Browse the repository at this point in the history
* Add SupportedApiUpgrades storage; Add upgrade_api extrinsic

* Add get_latest_api_upgrade chain extension

* Add BoundedBTreeMap for managing supported apis

* Add unit tests and benchmark for upgrade_api

* Add draft for chain_extension_get_latest_api_upgrade benchmark

* Fix test import

* Change encoded_input to functional

* Remove map for AccountId; Derive MaxEncodedLen

* Remove unused constant; Add benhcmark weights

* Cargo fmt

* Split api hash storage into two: current and next upgrade

* Fix transaction_version comparison

* Remove hardcoded value; Remove next upgrade after updating current api hash

* Fix hardcoded value; Add CodeHash to event

* Fix test storage

---------

Co-authored-by: Robert Gabriel Jakabosky <[email protected]>
  • Loading branch information
HenriqueNogara and Neopallium authored Nov 22, 2023
1 parent f2a70e1 commit c4327b8
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 15 deletions.
45 changes: 45 additions & 0 deletions pallets/contracts/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,49 @@ benchmarks! {
verify {
assert_eq!(free_balance::<T>(&addr), ENDOWMENT + 1 as Balance);
}

upgrade_api {
let current_spec_version = T::Version::get().spec_version;
let current_tx_version = T::Version::get().transaction_version;
let api_code_hash: ApiCodeHash<T> = ApiCodeHash { hash: CodeHash::<T>::default() };
let chain_version = ChainVersion::new(current_spec_version, current_tx_version);
let api = Api::new(*b"POLY", current_spec_version);
let next_upgrade = NextUpgrade::new(chain_version, api_code_hash);
}: _(RawOrigin::Root, api.clone(), next_upgrade.clone())
verify {
assert_eq!(ApiNextUpgrade::<T>::get(&api).unwrap(), next_upgrade);
assert_eq!(CurrentApiHash::<T>::get(&api),None);
}

chain_extension_get_latest_api_upgrade {
let r in 0 .. CHAIN_EXTENSION_BATCHES;

let current_spec_version = T::Version::get().spec_version;
let current_tx_version = T::Version::get().transaction_version;

let api_code_hash: ApiCodeHash<T> = ApiCodeHash { hash: CodeHash::<T>::default() };
let next_upgrade = NextUpgrade::new(ChainVersion::new(current_spec_version, current_tx_version), api_code_hash.clone());
let output_len: u32 = api_code_hash.hash.as_ref().len() as u32;
let api = Api::new(*b"POLY", current_spec_version);

Module::<T>::upgrade_api(
RawOrigin::Root.into(),
api.clone(),
next_upgrade.clone(),
).unwrap();

let encoded_input = (0..r * CHAIN_EXTENSION_BATCH_SIZE)
.map(|_| {
api.encode()
})
.collect::<Vec<_>>();
let input_bytes = encoded_input.iter().flat_map(|a| a.clone()).collect::<Vec<_>>();

let contract = Contract::<T>::chain_extension(
r * CHAIN_EXTENSION_BATCH_SIZE,
FuncId::GetLatestApiUpgrade,
input_bytes,
output_len
);
}: { contract.call(); }
}
57 changes: 57 additions & 0 deletions pallets/contracts/src/chain_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub enum FuncId {
GetTransactionVersion,
GetKeyDid,
KeyHasher(KeyHasher, HashSize),
GetLatestApiUpgrade,

/// Deprecated Polymesh (<=5.0) chain extensions.
OldCallRuntime(ExtrinsicId),
Expand All @@ -99,6 +100,7 @@ impl FuncId {
0x10 => Some(Self::KeyHasher(KeyHasher::Twox, HashSize::B64)),
0x11 => Some(Self::KeyHasher(KeyHasher::Twox, HashSize::B128)),
0x12 => Some(Self::KeyHasher(KeyHasher::Twox, HashSize::B256)),
0x13 => Some(Self::GetLatestApiUpgrade),
_ => None,
},
0x1A => match func_id {
Expand Down Expand Up @@ -129,6 +131,7 @@ impl Into<u32> for FuncId {
Self::KeyHasher(KeyHasher::Twox, HashSize::B64) => (0x0000, 0x10),
Self::KeyHasher(KeyHasher::Twox, HashSize::B128) => (0x0000, 0x11),
Self::KeyHasher(KeyHasher::Twox, HashSize::B256) => (0x0000, 0x12),
Self::GetLatestApiUpgrade => (0x0000, 0x13),
Self::OldCallRuntime(ExtrinsicId(ext_id, func_id)) => {
(ext_id as u32, (func_id as u32) << 8)
}
Expand Down Expand Up @@ -401,6 +404,58 @@ where
Ok(ce::RetVal::Converging(0))
}

fn get_latest_api_upgrade<T, E>(env: ce::Environment<E, ce::InitState>) -> ce::Result<ce::RetVal>
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
E: ce::Ext<T = T>,
{
let mut env = env.buf_in_buf_out();
env.charge_weight(<T as Config>::WeightInfo::get_latest_api_upgrade())?;
let api: Api = env.read_as()?;

let spec_version = T::Version::get().spec_version;
let tx_version = T::Version::get().transaction_version;
let current_chain_version = ChainVersion::new(spec_version, tx_version);

let current_api_hash: Option<ApiCodeHash<T>> = CurrentApiHash::<T>::get(&api);
let next_upgrade: Option<NextUpgrade<T>> = ApiNextUpgrade::<T>::get(&api);
let latest_api_hash = {
match next_upgrade {
Some(next_upgrade) => {
if next_upgrade.chain_version <= current_chain_version {
CurrentApiHash::<T>::insert(&api, &next_upgrade.api_hash);
ApiNextUpgrade::<T>::remove(&api);
Some(next_upgrade.api_hash)
} else {
current_api_hash
}
}
None => current_api_hash,
}
};

// If there are no upgrades found, return an error
if latest_api_hash.is_none() {
return Err(Error::<T>::NoUpgradesSupported.into());
}

trace!(
target: "runtime",
"PolymeshExtension contract GetLatestApiUpgrade: {latest_api_hash:?}",
);
let encoded_api_hash = latest_api_hash.unwrap_or_default().encode();
env.write(&encoded_api_hash, false, None).map_err(|err| {
trace!(
target: "runtime",
"PolymeshExtension failed to write api code hash value into contract memory:{err:?}",
);
Error::<T>::ReadStorageFailed
})?;

Ok(ce::RetVal::Converging(0))
}

fn call_runtime<T, E>(
env: ce::Environment<E, ce::InitState>,
old_call: Option<ExtrinsicId>,
Expand Down Expand Up @@ -527,6 +582,7 @@ where
Some(FuncId::GetTransactionVersion) => get_version(env, false),
Some(FuncId::GetKeyDid) => get_key_did(env),
Some(FuncId::KeyHasher(hasher, size)) => key_hasher(env, hasher, size),
Some(FuncId::GetLatestApiUpgrade) => get_latest_api_upgrade(env),
Some(FuncId::OldCallRuntime(p)) => call_runtime(env, Some(p)),
None => {
trace!(
Expand Down Expand Up @@ -560,6 +616,7 @@ mod tests {
test_func_id(FuncId::KeyHasher(KeyHasher::Twox, HashSize::B64));
test_func_id(FuncId::KeyHasher(KeyHasher::Twox, HashSize::B128));
test_func_id(FuncId::KeyHasher(KeyHasher::Twox, HashSize::B256));
test_func_id(FuncId::GetLatestApiUpgrade);
test_func_id(FuncId::OldCallRuntime(ExtrinsicId(0x1A, 0x00)));
test_func_id(FuncId::OldCallRuntime(ExtrinsicId(0x1A, 0x01)));
test_func_id(FuncId::OldCallRuntime(ExtrinsicId(0x1A, 0x02)));
Expand Down
146 changes: 137 additions & 9 deletions pallets/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,24 @@

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
use codec::{Decode, Encode};

pub mod chain_extension;
pub use chain_extension::{ExtrinsicId, PolymeshExtension};

use codec::{Decode, Encode};
use frame_support::dispatch::{
DispatchError, DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo,
};
use frame_support::pallet_prelude::MaxEncodedLen;
use frame_support::traits::Get;
use frame_support::weights::Weight;
use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure};
use frame_system::ensure_root;
use scale_info::TypeInfo;
use sp_core::crypto::UncheckedFrom;
use sp_runtime::traits::Hash;
use sp_std::borrow::Cow;
use sp_std::{vec, vec::Vec};

pub use chain_extension::{ExtrinsicId, PolymeshExtension};
use pallet_contracts::Config as BConfig;
use pallet_contracts_primitives::{Code, ContractResult};
use pallet_identity::ParentDid;
Expand All @@ -85,6 +86,79 @@ type CodeHash<T> = <T as frame_system::Config>::Hash;

pub struct ContractPolymeshHooks;

#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)]
pub struct Api {
desc: [u8; 4],
major: u32,
}

impl Api {
pub fn new(desc: [u8; 4], major: u32) -> Self {
Self { desc, major }
}
}

#[derive(Clone, Decode, Encode, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ApiCodeHash<T: Config> {
pub hash: CodeHash<T>,
}

impl<T: Config> Default for ApiCodeHash<T> {
fn default() -> Self {
Self {
hash: CodeHash::<T>::default(),
}
}
}

impl<T: Config> sp_std::fmt::Debug for ApiCodeHash<T> {
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "hash: {:?}", self.hash)
}
}

#[derive(Clone, Debug, Decode, Encode, Eq, Ord, PartialOrd, PartialEq, TypeInfo)]
pub struct ChainVersion {
spec_version: u32,
tx_version: u32,
}

impl ChainVersion {
pub fn new(spec_version: u32, tx_version: u32) -> Self {
ChainVersion {
spec_version,
tx_version,
}
}
}

#[derive(Clone, Decode, Encode, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct NextUpgrade<T: Config> {
pub chain_version: ChainVersion,
pub api_hash: ApiCodeHash<T>,
}

impl<T: Config> NextUpgrade<T> {
pub fn new(chain_version: ChainVersion, api_hash: ApiCodeHash<T>) -> Self {
Self {
chain_version,
api_hash,
}
}
}

impl<T: Config> sp_std::fmt::Debug for NextUpgrade<T> {
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(
f,
"chain_version: {:?} api_hash: {:?}",
self.chain_version, self.api_hash
)
}
}

impl<T: Config> pallet_contracts::PolymeshHooks<T> for ContractPolymeshHooks
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
Expand Down Expand Up @@ -158,6 +232,8 @@ pub trait WeightInfo {
fn basic_runtime_call(n: u32) -> Weight;
fn instantiate_with_code_as_primary_key(code_len: u32, salt_len: u32) -> Weight;
fn instantiate_with_hash_as_primary_key(salt_len: u32) -> Weight;
fn chain_extension_get_latest_api_upgrade(r: u32) -> Weight;
fn upgrade_api() -> Weight;

/// Computes the cost of instantiating where `code_len`
/// and `salt_len` are specified in kilobytes.
Expand Down Expand Up @@ -218,14 +294,18 @@ pub trait WeightInfo {
.saturating_sub(Self::dummy_contract())
.saturating_sub(Self::basic_runtime_call(in_len))
}

fn get_latest_api_upgrade() -> Weight {
cost_batched!(chain_extension_get_latest_api_upgrade)
}
}

/// The `Config` trait for the smart contracts pallet.
pub trait Config:
IdentityConfig + BConfig<Currency = Self::Balances> + frame_system::Config
{
/// The overarching event type.
type RuntimeEvent: From<Event> + Into<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeEvent: From<Event<Self>> + Into<<Self as frame_system::Config>::RuntimeEvent>;

/// Max value that `in_len` can take, that is,
/// the length of the data sent from a contract when using the ChainExtension.
Expand All @@ -239,10 +319,13 @@ pub trait Config:
}

decl_event! {
pub enum Event {
// This pallet does not directly define custom events.
// See `pallet_contracts` and `pallet_identity`
// for events currently emitted by extrinsics of this pallet.
pub enum Event<T>
where
Hash = CodeHash<T>,
{
/// Emitted when a contract starts supporting a new API upgrade
/// Contains the [`Api`], [`ChainVersion`], and the bytes for the code hash.
ApiHashUpdated(Api, ChainVersion, Hash)
}
}

Expand All @@ -268,7 +351,11 @@ decl_error! {
/// The caller is not a primary key.
CallerNotAPrimaryKey,
/// Secondary key permissions are missing.
MissingKeyPermissions
MissingKeyPermissions,
/// Only future chain versions are allowed.
InvalidChainVersion,
/// There are no api upgrades supported for the contract.
NoUpgradesSupported
}
}

Expand All @@ -281,6 +368,12 @@ decl_storage! {
map hasher(identity) ExtrinsicId => bool;
/// Storage version.
StorageVersion get(fn storage_version) build(|_| Version::new(1)): Version;
/// Stores the chain version and code hash for the next chain upgrade.
pub ApiNextUpgrade get(fn api_next_upgrade):
map hasher(twox_64_concat) Api => Option<NextUpgrade<T>>;
/// Stores the code hash for the current api.
pub CurrentApiHash get (fn api_tracker):
map hasher(twox_64_concat) Api => Option<ApiCodeHash<T>>;
}
add_extra_genesis {
config(call_whitelist): Vec<ExtrinsicId>;
Expand Down Expand Up @@ -494,6 +587,15 @@ decl_module! {
true
)
}

#[weight = <T as Config>::WeightInfo::upgrade_api()]
pub fn upgrade_api(
origin,
api: Api,
next_upgrade: NextUpgrade<T>
) -> DispatchResult {
Self::base_upgrade_api(origin, api, next_upgrade)
}
}
}

Expand Down Expand Up @@ -694,4 +796,30 @@ where
Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),
}
}

fn base_upgrade_api(
origin: T::RuntimeOrigin,
api: Api,
next_upgrade: NextUpgrade<T>,
) -> DispatchResult {
ensure_root(origin)?;

let current_chain_version = ChainVersion::new(
T::Version::get().spec_version,
T::Version::get().transaction_version,
);

if next_upgrade.chain_version < current_chain_version {
return Err(Error::<T>::InvalidChainVersion.into());
}

ApiNextUpgrade::<T>::insert(&api, &next_upgrade);

Self::deposit_event(Event::<T>::ApiHashUpdated(
api,
next_upgrade.chain_version,
next_upgrade.api_hash.hash,
));
Ok(())
}
}
2 changes: 1 addition & 1 deletion pallets/runtime/develop/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ construct_runtime!(

// Contracts
Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 46,
PolymeshContracts: polymesh_contracts::{Pallet, Call, Storage, Event, Config},
PolymeshContracts: polymesh_contracts::{Pallet, Call, Storage, Event<T>, Config},

// Preimage register. Used by `pallet_scheduler`.
Preimage: pallet_preimage::{Pallet, Call, Storage, Event<T>},
Expand Down
2 changes: 1 addition & 1 deletion pallets/runtime/mainnet/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ construct_runtime!(

// Contracts
Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 46,
PolymeshContracts: polymesh_contracts::{Pallet, Call, Storage, Event, Config},
PolymeshContracts: polymesh_contracts::{Pallet, Call, Storage, Event<T>, Config},

// Preimage register. Used by `pallet_scheduler`.
Preimage: pallet_preimage::{Pallet, Call, Storage, Event<T>},
Expand Down
Loading

0 comments on commit c4327b8

Please sign in to comment.