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

pallet-xcm: add support to authorize aliases #6336

Open
wants to merge 24 commits into
base: master
Choose a base branch
from

Conversation

acatangiu
Copy link
Contributor

@acatangiu acatangiu commented Nov 1, 2024

Add calls to pallet-xcm for adding and removing authorization for a certain aliaser location to alias into the caller origin.

pallet-xcm also exposes an AuthorizedAliases filter implementation usable with xcm_executor::Config::Aliasers filter to easily allow runtimes to plug in the explicitly authorized aliases using the calls above.

Usually useful to allow your local account to be aliased into from a remote location also under your control (like your account on another chain).

One cool example is Alice on Para42 doing something on Asset Hub without having to transfer fees from Para42, but instead use her local Asset Hub account:

// called by Alice on Para42
pallet_xcm::send(
	Location::new(1, Parachain(1000)),
	Xcm(vec![
		AliasOrigin(AliceOnAH),
		WithdrawAsset(fees),
		PayFees(fees),
		DoWhatever
	])
);

Part of Empowered cross-chain origins.

Fixes XCM: Arbitrary Origin Aliases #722

@acatangiu acatangiu added the T6-XCM This PR/Issue is related to XCM. label Nov 1, 2024
@acatangiu acatangiu self-assigned this Nov 1, 2024
@acatangiu acatangiu marked this pull request as ready for review November 6, 2024 17:32
@acatangiu acatangiu requested a review from a team as a code owner November 6, 2024 17:32
Copy link
Contributor

@bkontur bkontur left a comment

Choose a reason for hiding this comment

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

lgtm, left some small nits/questions

polkadot/xcm/pallet-xcm/src/lib.rs Show resolved Hide resolved
polkadot/xcm/pallet-xcm/src/benchmarking.rs Outdated Show resolved Hide resolved
polkadot/xcm/pallet-xcm/src/lib.rs Show resolved Hide resolved
polkadot/xcm/pallet-xcm/src/lib.rs Show resolved Hide resolved
@acatangiu
Copy link
Contributor Author

bot bench polkadot-pallet --subcommand=xcm --runtime=westend --pallet=pallet_xcm
bot bench polkadot-pallet --subcommand=xcm --runtime=rococo --pallet=pallet_xcm

bot bench cumulus-assets --subcommand=xcm --runtime=asset-hub-westend --pallet=pallet_xcm
bot bench cumulus-assets --subcommand=xcm --runtime=asset-hub-rococo --pallet=pallet_xcm

command-bot and others added 5 commits November 7, 2024 12:44
…stend --target_dir=polkadot --pallet=pallet_xcm
…coco --target_dir=polkadot --pallet=pallet_xcm
…set-hub-westend --runtime_dir=assets --target_dir=cumulus --pallet=pallet_xcm
…set-hub-rococo --runtime_dir=assets --target_dir=cumulus --pallet=pallet_xcm
@acatangiu
Copy link
Contributor Author

bot clean

polkadot/xcm/pallet-xcm/src/benchmarking.rs Outdated Show resolved Hide resolved
/// their/your name. Once authorized using this call, the `aliaser` can freely impersonate
/// `origin` in XCM programs executed on the local chain.
#[pallet::call_index(14)]
pub fn add_authorized_alias(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we allow this indefinitely or always require an expiry? Thinking of the case where you do trust your account on another chain but maybe that chain gets compromised and you forgot to remove your authorization. Then the parachain can impersonate your account on their chain and in turn impersonate your account on this chain.

An expiry would be annoying but would make it safer. Apps could batch this with the actual action you want to take if they know the expiry is past.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I’m personally not a fan of expiry. It’s a big UX burden and doesn’t bring any value IMO. If the aliaser is compromised then the aliasee is compromised, one cannot rely on an expiry as protection. One should immediately remove the authorisation in such an unlikely case.

The idea is worth evaluating from a UX perspective depending on the following:
A) will users authorise these relationships for repeated actions (effectively needing them alive for longer periods), or
B) will they be used for very occasional one-time actions (so for added security they should either be explicitly removed or automatically expire)

If the usage pattern is B, then an expiry actually helps UX. If it is A, it hinders UX (and adds more complexity - would need to maintain an ordered list based on expirations and manage it on each block - complex and expensive).

I say we keep it simple for now and see where it goes, we can pivot to the more expensive option if needed.

Copy link
Contributor

@franciscoaguirre franciscoaguirre Nov 7, 2024

Choose a reason for hiding this comment

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

I see expiry as a way to not allow users to forget that they allowed some location to impersonate them. I think it's a UX improvement to allow users to forget but still clean up after themselves.

The reason why I'm proposing this is because it's not only the location they explicitly allow that could be the problem, they open the surface area of bugs to bugs on the other chain. The main value in my opinion is that if a user on chain A allows an account from chain B to impersonate them and then forgets, a future vulnerability in chain B can't affect them anymore given that it expires.

I'd expect the usage pattern to be B, but if it is A we can still make the expiry refresh every time they make an action with it.

On the implementation side I don't see how it's so complex and expensive. You'd just keep the block number timeout and disallow aliases that are past that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added expiry here a606607, let me know what you think

polkadot/xcm/pallet-xcm/src/lib.rs Outdated Show resolved Hide resolved
polkadot/xcm/pallet-xcm/src/lib.rs Outdated Show resolved Hide resolved
polkadot/xcm/pallet-xcm/src/migration.rs Show resolved Hide resolved
prdoc/pr_6336.prdoc Show resolved Hide resolved
/// Entry of an authorized aliaser for a local origin. The aliaser `location` is only authorized
/// until its inner `expiry` block number.
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
pub(crate) struct OriginAliaser {
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not really see the reason to make stored types and storage items private. At the end when you need to use it for some reason you just have to copy past them and create an alias storage item.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not sure what you mean..

should define this other place? or just make it pub instead of pub(crate)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I would make them public

#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
pub(crate) struct OriginAliaser {
pub(crate) location: VersionedLocation,
pub(crate) expiry: Option<u64>,
Copy link
Contributor

Choose a reason for hiding this comment

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

why not to reference the actual block number type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

had some trouble storing BlockNumberFor<T>, then realized the interface is better to be generic - once we move to relay-block-number-provider that type might change again, but u64 will always be compatible

Copy link
Contributor

Choose a reason for hiding this comment

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

two times bigger than what we really use. when we change to configurable provider, the block number type wont change

authorized_aliases
.try_push(aliaser)
.map_err(|_| Error::<T>::TooManyAuthorizedAliases)?;
// todo: hold storage deposit
Copy link
Contributor

Choose a reason for hiding this comment

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

do you plan to address this todo in the current PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just pushed something, want feedback before I clean it up all the way

// todo: hold storage deposit
}
AuthorizedAliases::<T>::insert(&versioned_origin, authorized_aliases);
Ok(())
Copy link
Contributor

Choose a reason for hiding this comment

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

some event?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, still needs events

let block_num = frame_system::Pallet::<T>::block_number().saturated_into::<u64>();
let mut writes = 0;
// need to iterate keys and modify map in separate steps to avoid undefined behavior
let keys: Vec<VersionedLocation> = AuthorizedAliases::<T>::iter_keys().collect();
Copy link
Contributor

Choose a reason for hiding this comment

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

this might be too many reads, we should check the weight_cutoff on every iteration

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do we need to in practice? iterating over even a million keys should fit in practice, no?

I'm not even sure how we could do partial iterations, how to resume from where we left off...

Copy link
Contributor

Choose a reason for hiding this comment

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

in the context of this function (may be I missing a broader picture), it looks wrong. a client passes weight_cutoff, for this stage we ignore it. we do not know how small or big the weight_cutoff.

I think we can iterate one by one and if there was no enough weight to complete, we can return RemoveExpiredAliasAuthorizations(Some(cursor)).

Comment on lines 3586 to 3595
AuthorizedAliases::<T>::get(&target)
.iter()
.find(|&aliaser| {
let current_block =
frame_system::Pallet::<T>::block_number().saturated_into::<u64>();
let not_expired =
aliaser.expiry.map(|expiry| expiry < current_block).unwrap_or(true);
aliaser.location == origin && not_expired
})
.is_some()
Copy link
Contributor

Choose a reason for hiding this comment

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

it will be useful as the Pallet's function

@@ -170,6 +170,47 @@ pub mod data {
}
}

/// Implementation of `NeedsMigration` for `AuthorizedAliases` data.
impl<M: Get<u32>, T: frame_system::Config> NeedsMigration
Copy link
Contributor

Choose a reason for hiding this comment

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

nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T6-XCM This PR/Issue is related to XCM.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

XCM: Arbitrary Origin Aliases
4 participants