Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add omni account origin #3117

Merged
merged 8 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ jobs:
- litentry
- rococo
- paseo
name: ${{ matrix.chain }}
name: ${{ matrix.chain }} || 'parachain-ts-test'
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -718,7 +718,7 @@ jobs:
- test_name: lit-test-failed-parentchain-extrinsic
- test_name: lit-twitter-identity-test
- test_name: lit-discord-identity-test
name: ${{ matrix.test_name }}
name: ${{ matrix.test_name }} || 'identity-single-worker-test'
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -791,7 +791,7 @@ jobs:
- test_name: lit-di-substrate-identity-multiworker-test
- test_name: lit-dr-vc-multiworker-test
- test_name: lit-resume-worker
name: ${{ matrix.test_name || 'identity-multi-worker-test' }}
name: ${{ matrix.test_name }} || 'identity-multi-worker-test'
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -857,7 +857,7 @@ jobs:
matrix:
include:
- test_name: lit-sign-bitcoin
name: ${{ matrix.test_name }}
name: ${{ matrix.test_name }} || 'bitacross-worker-test'
steps:
- uses: actions/checkout@v4

Expand Down
1 change: 1 addition & 0 deletions parachain/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

174 changes: 145 additions & 29 deletions parachain/pallets/omni-account/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
// Copyright 2020-2024 Trust Computing GmbH.
// This file is part of Litentry.
//
// Litentry is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Litentry is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Litentry. If not, see <https://www.gnu.org/licenses/>.

#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
Expand All @@ -10,9 +26,15 @@ pub use frame_system::pallet_prelude::BlockNumberFor;
pub use pallet::*;

use frame_support::pallet_prelude::*;
use frame_support::{
dispatch::{GetDispatchInfo, PostDispatchInfo},
traits::{IsSubType, UnfilteredDispatchable},
};
use frame_system::pallet_prelude::*;
use sp_core::H256;
use sp_core_hashing::blake2_256;
use sp_runtime::traits::Dispatchable;
use sp_std::boxed::Box;
use sp_std::vec::Vec;

#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)]
Expand All @@ -36,6 +58,21 @@ impl<T> IDGraphHash for BoundedVec<IDGraphMember, T> {
}
}

pub type MemberCount = u32;

// Customized origin for this pallet, to:
// 1. to decouple `TEECallOrigin` and extrinsic that should be sent from `OmniAccount` origin only
// 2. allow other pallets to specify ensure_origin using this origin
// 3. leave room for more delicate control over OmniAccount in the future (e.g. multisig-like control)
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[codec(mel_bound(AccountId: MaxEncodedLen))]
pub enum RawOrigin<AccountId> {
// dispatched from OmniAccount T::AccountId
OmniAccount(AccountId),
// dispatched by a given number of members of the OmniAccount IDGraph from a given total
OmniAccountMembers(AccountId, MemberCount, MemberCount),
}

#[frame_support::pallet]
pub mod pallet {
use super::*;
Expand All @@ -50,21 +87,45 @@ pub mod pallet {

#[pallet::config]
pub trait Config: frame_system::Config {
/// The event type of this pallet.
/// The runtime origin type
type RuntimeOrigin: From<RawOrigin<Self::AccountId>>
+ From<frame_system::RawOrigin<Self::AccountId>>;

/// The overarching call type
type RuntimeCall: Parameter
+ Dispatchable<
RuntimeOrigin = <Self as Config>::RuntimeOrigin,
PostInfo = PostDispatchInfo,
> + GetDispatchInfo
+ From<frame_system::Call<Self>>
+ UnfilteredDispatchable<RuntimeOrigin = <Self as Config>::RuntimeOrigin>
+ IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;

/// The event type of this pallet
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The origin which can manage the pallet.
type TEECallOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The origin that represents the off-chain worker
type TEECallOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
/// The maximum number of identities an id graph can have.
#[pallet::constant]
type MaxIDGraphLength: Get<u32>;
/// AccountId converter
type AccountIdConverter: AccountIdConverter<Self>;
/// The origin that represents the customised OmniAccount type
type OmniAccountOrigin: EnsureOrigin<
<Self as frame_system::Config>::RuntimeOrigin,
Success = Self::AccountId,
>;
}

pub type IDGraph<T> = BoundedVec<IDGraphMember, <T as Config>::MaxIDGraphLength>;

#[pallet::origin]
pub type Origin<T> = RawOrigin<<T as frame_system::Config>::AccountId>;

#[pallet::storage]
pub type LinkedIdentityHashes<T: Config> =
StorageMap<Hasher = Blake2_128Concat, Key = H256, Value = ()>;
StorageMap<Hasher = Blake2_128Concat, Key = H256, Value = T::AccountId>;

#[pallet::storage]
#[pallet::getter(fn id_graphs)]
Expand All @@ -85,6 +146,10 @@ pub mod pallet {
IdentityRemoved { who: T::AccountId, identity_hashes: Vec<H256> },
/// Identity made public
IdentityMadePublic { who: T::AccountId, identity_hash: H256 },
/// Some call is dispatched as omni-account origin
DispatchedAsOmniAccount { who: T::AccountId, result: DispatchResult },
/// Some call is dispatched as signed origin
DispatchedAsSigned { who: T::AccountId, result: DispatchResult },
}

#[pallet::error]
Expand All @@ -111,14 +176,56 @@ pub mod pallet {

#[pallet::call]
impl<T: Config> Pallet<T> {
// dispatch the `call` as RawOrigin::OmniAccount
#[pallet::call_index(0)]
#[pallet::weight((195_000_000, DispatchClass::Normal))]
pub fn dispatch_as_omni_account(
origin: OriginFor<T>,
account_hash: H256,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResult {
let _ = T::TEECallOrigin::ensure_origin(origin)?;
let omni_account =
LinkedIdentityHashes::<T>::get(account_hash).ok_or(Error::<T>::IdentityNotFound)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that who sent to link_identity is the OmniAccount? So, the OmniAccount should exist before linking any account, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no - nothing changed for link_identity

LinkedIdentityHashes contains now the mapping member_account_hash <> omni-account

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean who should be the omni-account even in the current impl

let result = call.dispatch(RawOrigin::OmniAccount(omni_account.clone()).into());
Self::deposit_event(Event::DispatchedAsOmniAccount {
who: omni_account,
result: result.map(|_| ()).map_err(|e| e.error),
});
Ok(())
}

// dispatch the `call` as the standard (frame_system) signed origin
// TODO: what about other customised origin like collective?
#[pallet::call_index(1)]
#[pallet::weight((195_000_000, DispatchClass::Normal))]
pub fn dispatch_as_signed(
origin: OriginFor<T>,
account_hash: H256,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResult {
let _ = T::TEECallOrigin::ensure_origin(origin)?;
let omni_account =
LinkedIdentityHashes::<T>::get(account_hash).ok_or(Error::<T>::IdentityNotFound)?;
let result =
call.dispatch(frame_system::RawOrigin::Signed(omni_account.clone()).into());
Self::deposit_event(Event::DispatchedAsSigned {
who: omni_account,
result: result.map(|_| ()).map_err(|e| e.error),
});
Ok(())
}

#[pallet::call_index(2)]
#[pallet::weight((195_000_000, DispatchClass::Normal))]
pub fn link_identity(
origin: OriginFor<T>,
who: Identity,
member_account: IDGraphMember,
maybe_id_graph_hash: Option<H256>,
) -> DispatchResult {
// We can't use `T::OmniAccountOrigin` here as the ownership of member account needs to
// be firstly validated by the TEE-worker before dispatching the extrinsic
let _ = T::TEECallOrigin::ensure_origin(origin)?;
ensure!(
!LinkedIdentityHashes::<T>::contains_key(member_account.hash),
Expand All @@ -138,7 +245,7 @@ pub mod pallet {
.try_push(member_account)
.map_err(|_| Error::<T>::IDGraphLenLimitReached)?;

LinkedIdentityHashes::<T>::insert(identity_hash, ());
LinkedIdentityHashes::<T>::insert(identity_hash, who_account_id.clone());
IDGraphHashes::<T>::insert(who_account_id.clone(), id_graph.graph_hash());
IDGraphs::<T>::insert(who_account_id.clone(), id_graph);

Expand All @@ -150,23 +257,17 @@ pub mod pallet {
Ok(())
}

#[pallet::call_index(4)]
#[pallet::call_index(3)]
#[pallet::weight((195_000_000, DispatchClass::Normal))]
pub fn remove_identities(
origin: OriginFor<T>,
who: Identity,
identity_hashes: Vec<H256>,
) -> DispatchResult {
let _ = T::TEECallOrigin::ensure_origin(origin)?;
let who = T::OmniAccountOrigin::ensure_origin(origin)?;
ensure!(!identity_hashes.is_empty(), Error::<T>::IdentitiesEmpty);

let who_account_id = match T::AccountIdConverter::convert(&who) {
Some(account_id) => account_id,
None => return Err(Error::<T>::InvalidIdentity.into()),
};

let mut id_graph_members =
IDGraphs::<T>::get(&who_account_id).ok_or(Error::<T>::UnknownIDGraph)?;
IDGraphs::<T>::get(&who).ok_or(Error::<T>::UnknownIDGraph)?;

id_graph_members.retain(|member| {
if identity_hashes.contains(&member.hash) {
Expand All @@ -178,43 +279,37 @@ pub mod pallet {
});

if id_graph_members.is_empty() {
IDGraphs::<T>::remove(&who_account_id);
IDGraphs::<T>::remove(&who);
} else {
IDGraphs::<T>::insert(who_account_id.clone(), id_graph_members);
IDGraphs::<T>::insert(who.clone(), id_graph_members);
}

Self::deposit_event(Event::IdentityRemoved { who: who_account_id, identity_hashes });
Self::deposit_event(Event::IdentityRemoved { who, identity_hashes });

Ok(())
}

#[pallet::call_index(5)]
#[pallet::call_index(4)]
#[pallet::weight((195_000_000, DispatchClass::Normal))]
pub fn make_identity_public(
origin: OriginFor<T>,
who: Identity,
identity_hash: H256,
public_identity: MemberIdentity,
) -> DispatchResult {
let _ = T::TEECallOrigin::ensure_origin(origin)?;
let who = T::OmniAccountOrigin::ensure_origin(origin)?;
ensure!(public_identity.is_public(), Error::<T>::IdentityIsPrivate);

let who_account_id = match T::AccountIdConverter::convert(&who) {
Some(account_id) => account_id,
None => return Err(Error::<T>::InvalidIdentity.into()),
};

let mut id_graph_members =
IDGraphs::<T>::get(&who_account_id).ok_or(Error::<T>::UnknownIDGraph)?;
IDGraphs::<T>::get(&who).ok_or(Error::<T>::UnknownIDGraph)?;
let id_graph_link = id_graph_members
.iter_mut()
.find(|member| member.hash == identity_hash)
.ok_or(Error::<T>::IdentityNotFound)?;
id_graph_link.id = public_identity;

IDGraphs::<T>::insert(who_account_id.clone(), id_graph_members);
IDGraphs::<T>::insert(who.clone(), id_graph_members);

Self::deposit_event(Event::IdentityMadePublic { who: who_account_id, identity_hash });
Self::deposit_event(Event::IdentityMadePublic { who, identity_hash });

Ok(())
}
Expand Down Expand Up @@ -270,10 +365,31 @@ pub mod pallet {
hash: owner_identity_hash,
})
.map_err(|_| Error::<T>::IDGraphLenLimitReached)?;
LinkedIdentityHashes::<T>::insert(owner_identity_hash, ());
LinkedIdentityHashes::<T>::insert(owner_identity_hash, owner_account_id.clone());
IDGraphs::<T>::insert(owner_account_id.clone(), id_graph_members.clone());

Ok(id_graph_members)
}
}
}

pub struct EnsureOmniAccount<AccountId>(PhantomData<AccountId>);
impl<O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, AccountId: Decode>
EnsureOrigin<O> for EnsureOmniAccount<AccountId>
{
type Success = AccountId;
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().and_then(|o| match o {
RawOrigin::OmniAccount(id) => Ok(id),
r => Err(O::from(r)),
})
}

#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
let zero_account_id =
AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed");
Ok(O::from(RawOrigin::OmniAccount(zero_account_id)))
}
}
32 changes: 26 additions & 6 deletions parachain/pallets/omni-account/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
use crate::{self as pallet_omni_account};
// Copyright 2020-2024 Trust Computing GmbH.
// This file is part of Litentry.
//
// Litentry is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Litentry is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Litentry. If not, see <https://www.gnu.org/licenses/>.

use crate::{self as pallet_omni_account, EnsureOmniAccount};
use core_primitives::Identity;
use frame_support::{
assert_ok,
Expand Down Expand Up @@ -152,22 +168,26 @@ impl pallet_omni_account::AccountIdConverter<TestRuntime> for IdentityToAccountI
}

impl pallet_omni_account::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type TEECallOrigin = EnsureEnclaveSigner<Self>;
type MaxIDGraphLength = ConstU32<3>;
type AccountIdConverter = IdentityToAccountIdConverter;
type OmniAccountOrigin = EnsureOmniAccount<Self::AccountId>;
}

pub fn get_tee_signer() -> SystemAccountId {
get_signer(TEST8_SIGNER_PUB)
}

pub fn new_test_ext() -> sp_io::TestExternalities {
let system = frame_system::GenesisConfig::<TestRuntime>::default();
let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system, ..Default::default() }
.build_storage()
.unwrap()
.into();
let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<TestRuntime> { balances: vec![(alice(), 10)] }
.assimilate_storage(&mut t)
.unwrap();

let mut ext: sp_io::TestExternalities = t.into();
ext.execute_with(|| {
System::set_block_number(1);
let signer = get_tee_signer();
Expand Down
Loading
Loading