Skip to content

Make Chain a variant of AccountOwner#3560

Merged
deuszx merged 6 commits intomainfrom
cleanup-account-owner-chain
Mar 16, 2025
Merged

Make Chain a variant of AccountOwner#3560
deuszx merged 6 commits intomainfrom
cleanup-account-owner-chain

Conversation

@deuszx
Copy link
Contributor

@deuszx deuszx commented Mar 13, 2025

Motivation

We want to fix and expand our notion of Address (currently Owner, GenericApplicationId, etc.) to different type of addresses (32-byte Linera/Solana, 20-byte EVM). In order to do that we need to prepare the code for the introduction of new variants.

Proposal

Inline a Chain into AccountOwner enum to identify cases where transactions (mostly token transfers) are targeting or using chain's account balance. Previously that case was handled with the usage of Option<AccountOwner>. This made refactoring more difficult.

Test Plan

CI should catch regressions.

Release Plan

  • Nothing to do / These changes follow the usual release cycle.

Links

@deuszx deuszx force-pushed the cleanup-account-owner-chain branch 4 times, most recently from 795c626 to d66cb15 Compare March 13, 2025 12:55
@deuszx deuszx force-pushed the cleanup-account-owner-chain branch from d66cb15 to 85f269f Compare March 13, 2025 12:57
@deuszx deuszx requested review from afck and bart-linera March 14, 2025 14:40
@deuszx deuszx marked this pull request as ready for review March 14, 2025 14:40
Copy link
Contributor

@bart-linera bart-linera left a comment

Choose a reason for hiding this comment

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

I'm not sure I agree with the premise. It seems like a chain as a transfer target doesn't really belong as a variant of AccountOwner. If we want to be more explicit, I'd just make a type like enum TransferTarget { AccountOwner(AccountOwner), Chain } and replace Option<AccountOwner> with it. Then AccountOwner would play the role of identifying someone owning tokens that can authenticate transfers, and TransferTarget would represent, well, a target of a transfer.

I'm just not sure if that helps with solving the problem this was intended to solve, but I guess new potential variants of "addresses" could then be new variants of TransferTarget?

Comment on lines +329 to +331
AccountOwner::Chain => {
unreachable!("Chains cannot be used to authenticate")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Those unreachable!s suggest to me that we might be conflating two different things here: who can authenticate a transaction, and where tokens can be transferred to. Maybe those should be represented by separate types? (Not necessarily something for this PR.)

Copy link
Contributor

@ma2bd ma2bd Mar 14, 2025

Choose a reason for hiding this comment

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

AccountOwner::Chain is both a destination (the chain balance) and an ownership model (any block producer + temporarily message fees).

Copy link
Contributor

@ma2bd ma2bd Mar 14, 2025

Choose a reason for hiding this comment

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

Here the issue is that unreachable! is inaccurate and the error message is kinda misleading. It should be something like

panic!("Using chain balance is not authorized")

Copy link
Contributor Author

@deuszx deuszx Mar 15, 2025

Choose a reason for hiding this comment

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

@deuszx
Copy link
Contributor Author

deuszx commented Mar 14, 2025

I'm not sure I agree with the premise. It seems like a chain as a transfer target doesn't really belong as a variant of AccountOwner. If we want to be more explicit, I'd just make a type like enum TransferTarget { AccountOwner(AccountOwner), Chain } and replace Option<AccountOwner> with it. Then AccountOwner would play the role of identifying someone owning tokens that can authenticate transfers, and TransferTarget would represent, well, a target of a transfer.

I'm just not sure if that helps with solving the problem this was intended to solve, but I guess new potential variants of "addresses" could then be new variants of TransferTarget?

I think the ultimate solution would be to simply remove that variant entirely and either hardcode that every chain has an account with some hardcoded address ([0u8; 32]) where we store its balance or remove it - i.e. chains cannot have token balance at all.

Copy link
Contributor

@bart-linera bart-linera left a comment

Choose a reason for hiding this comment

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

After some discussion with Mateusz, it seems that a kind of separation between authenticating parties and owners already exists, and what drew my attention seems to have been implementation details of some example applications - so this change seems to be okay to me now 👍

Comment on lines -1179 to +1182
application_id: UserApplicationId<A>,
application_id: ApplicationId<A>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This (and below) seems to be a part of a different change?

authenticated_application_id == Some(owner),
ExecutionError::UnauthenticatedClaimOwner
),
AccountOwner::Chain => unreachable!(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it would make sense to handle this case? If a chain owner can transfer from an AccountOwner::Chain, then maybe it makes sense to let them claim from an AccountOwner::Chain, too? (if that's even possible to be implemented)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds reasonable to mirror logic of transfer here. I didn't decide to do it b/c I would change the logic - i.e. the source here wasn't wrapped with Option.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cc @afck

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not do it in this PR, but I agree it sounds reasonable.

@linera-io linera-io deleted a comment from deuszx Mar 14, 2025
@@ -144,6 +144,9 @@ impl NativeFungibleTokenContract {
);
}
AccountOwner::Application(_) => panic!("Applications not supported yet"),
Copy link
Contributor

@ma2bd ma2bd Mar 14, 2025

Choose a reason for hiding this comment

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

We should fix that (not here but soon)

match self {
AccountOwner::User(owner) => write!(f, "User:{}", owner)?,
AccountOwner::Application(app_id) => write!(f, "Application:{}", app_id)?,
AccountOwner::Chain => write!(f, "Chain")?,
Copy link
Contributor

@ma2bd ma2bd Mar 14, 2025

Choose a reason for hiding this comment

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

I look forward to removing these tags

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The ::Chain variant might not be so easy to remove b/c there are flow paths where the Chain is still a valid identifier. Right now I can see two ways to get rid of it:

  1. Do not let chain own a balance.
  2. Turn ::Chain variant into a special case of address ([u8; 32]).

The first one isn't just a refactor (internal change) as it requires making some high-level design decisions around opening new chains etc.

I think the second option is the easier one and we should try to do it. We could hash a string like "Linera Chain Address" (note it cannot simply be [0u8; 32] b/c in Ed25519 a private key for all zeros public key is well-known).

Copy link
Contributor

@ma2bd ma2bd Mar 15, 2025

Choose a reason for hiding this comment

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

note it cannot simply be [0u8; 32] b/c in Ed25519 a private key for all zeros public key is well-known)

Addresses are not public keys but hash values, so [0u8; 32] would work. I proposed 0x0 (Reserved(0)) in the internal doc.

Copy link
Contributor

@ma2bd ma2bd left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. Very nice improvement. No major issue except that refund_grand_to should be an option.

context.authenticated_signer,
None,
owner.map(AccountOwner::User),
owner.map(AccountOwner::User).unwrap_or(AccountOwner::Chain),
Copy link
Contributor

Choose a reason for hiding this comment

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

We should instead modify SystemOperation to use SystemOperation::Transfer { owner: Owner .. } and resolve the default value way earlier.

Copy link
Contributor

@ma2bd ma2bd Mar 14, 2025

Choose a reason for hiding this comment

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

unwrap_or(AccountOwner::Chain) is a little bit of a red flag. After this PR, I would imagine that only CLI options may default to a chain account.

Copy link
Contributor Author

@deuszx deuszx Mar 15, 2025

Choose a reason for hiding this comment

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

I agree it looks dangerous. So I tracked the places where I use AccountOwner::Chain as the default - there are two (actually three but there are two instances where I use ::Chain as source when constructing a SystemMessage::Credit so that's one for the purpose of this discussion):

  1. In send_refund we previously were using None as source and now we use AccountOwner::Chain.
  2. In system.rs#execute_operation we previously were using None as source for the transfer and now we default to ::Chain.

In both those cases None resolved to the "chain" already:

  1. main vs this PR
  2. main vs this PR.

There's no regression here.

Also, see #3573 where I replace Transfer::source: Option<Owner> with AccountOwner and get rid of some of these unwrap_or(AccountOwner::Chain).

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for checking. Indeed the source of a refund's Credit message is currently a best effort. (It only matters if the refund message itself is rejected.)

let mut outcome = RawExecutionOutcome {
authenticated_signer: context.authenticated_signer,
refund_grant_to: context.refund_grant_to(),
refund_grant_to: Some(context.refund_grant_to()),
Copy link
Contributor

Choose a reason for hiding this comment

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

No. We should not force refunds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, you're right. We actually had two Options previously here - the outer one (return type) and inner one (Some(Account { owner: None })) and previously Some(None) would not refund at all.

Copy link
Contributor Author

@deuszx deuszx Mar 15, 2025

Choose a reason for hiding this comment

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

@deuszx deuszx force-pushed the cleanup-account-owner-chain branch from 99f977e to 9a67aed Compare March 15, 2025 17:30
@deuszx deuszx force-pushed the cleanup-account-owner-chain branch from 9a67aed to b6110b9 Compare March 16, 2025 16:05
chain.execution_state.system.balances.get(&owner).await?;
}
AccountOwner::Chain => {
info.requested_owner_balance = Some(*chain.execution_state.system.balance.get());
Copy link
Contributor

Choose a reason for hiding this comment

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

Here or very soon we should remove SystemExecutionStateView::balance by the way

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 took a quick stab at removing the chains' balances entirely and it wasn't very difficult. I can propose something in a separate PR.

context.authenticated_signer,
None,
owner.map(AccountOwner::User),
owner.map(AccountOwner::User).unwrap_or(AccountOwner::Chain),
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for checking. Indeed the source of a refund's Credit message is currently a best effort. (It only matters if the refund message itself is rejected.)

@deuszx deuszx merged commit 5f582a7 into main Mar 16, 2025
23 checks passed
@deuszx deuszx deleted the cleanup-account-owner-chain branch March 16, 2025 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants