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

[Work in Progress] add AccountPermission #5164

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

yinyiqian1
Copy link
Collaborator

@yinyiqian1 yinyiqian1 commented Oct 25, 2024

spec:
XRPLF/XRPL-Standards#217
XRPLF/XRPL-Standards#218

This PR includes the following changes:

  1. Added AccountPermissionSet transaction.
    to enable an account(delegating account) delegate some permissions to another account(delegated account), so that the delegated account can send transactions on behalf of the delegating account.
  2. Added Account_Permission ledger object
    The AccountPermissionSet transaction will create AccountPermission ledger object, with keylet(delegating account, delegated account)
  3. Added Onbehalf of common field.
  4. Permission has two types:
    a. transaction level permission
    b. granular permission which will be part of a transaction

more details:

  1. introduced account and isDelegated in PreflightContext, PreclaimContext and ApplyContext. The account is the account which is the transaction being operated on.
  2. transactor's member account_ is updated to the account which is the transaction being operated on.
  3. a set of Granular Permissions gpSet is added to ApplyContext. It contains the granular permissions enabled for that transaction. But if the transaction is fully delegated, then even there are granular permissions related to that transaction, the gpSet will be cleared up. To be more specific:
    a. isDelegated=true && gpSet not empty: only granular permissions delegated to the transaction
    b. isDelegated=true && gpSet is empty: the transaction is fully delegated
  4. only the granular permissions listed in Permissions.cpp are supported, so the unauthorized granular operations and the other parts under these transactions are prohibited under granular permission mode.

High Level Overview of Change

Context of Change

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Performance (increase or change in throughput and/or latency)
  • Tests (you added tests for code that already exists, or your new feature included in this PR)
  • Documentation update
  • Chore (no impact to binary, e.g. .gitignore, formatting, dropping support for older tooling)
  • Release

API Impact

  • Public API: New feature (new methods and/or new fields)
  • Public API: Breaking change (in general, breaking changes should only impact the next api_version)
  • libxrpl change (any change that may affect libxrpl or dependents of libxrpl)
  • Peer protocol change (must be backward compatible or bump the peer protocol version)

@yinyiqian1 yinyiqian1 marked this pull request as draft October 25, 2024 19:01
Copy link

codecov bot commented Oct 25, 2024

Codecov Report

Attention: Patch coverage is 47.22892% with 219 lines in your changes missing coverage. Please review.

Project coverage is 77.7%. Comparing base (838978b) to head (7a7fa0c).
Report is 4 commits behind head on develop.

Files with missing lines Patch % Lines
src/xrpld/app/tx/detail/AccountPermissionSet.cpp 0.0% 56 Missing ⚠️
src/libxrpl/protocol/Permissions.cpp 0.0% 34 Missing ⚠️
src/xrpld/app/tx/detail/Transactor.cpp 13.9% 31 Missing ⚠️
src/xrpld/rpc/handlers/LedgerEntry.cpp 5.0% 19 Missing ⚠️
src/xrpld/app/tx/detail/SetTrust.cpp 26.3% 14 Missing ⚠️
src/libxrpl/protocol/STParsedJSON.cpp 23.5% 13 Missing ⚠️
src/xrpld/app/tx/detail/SetAccount.cpp 50.0% 13 Missing ⚠️
src/xrpld/app/tx/detail/Payment.cpp 33.3% 12 Missing ⚠️
src/libxrpl/protocol/Indexes.cpp 0.0% 5 Missing ⚠️
src/xrpld/app/tx/detail/DeleteAccount.cpp 28.6% 5 Missing ⚠️
... and 8 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           develop   #5164     +/-   ##
=========================================
- Coverage     77.9%   77.7%   -0.2%     
=========================================
  Files          782     787      +5     
  Lines        66621   66927    +306     
  Branches      8161    8270    +109     
=========================================
+ Hits         51902   51975     +73     
- Misses       14719   14952    +233     
Files with missing lines Coverage Δ
include/xrpl/protocol/Feature.h 100.0% <ø> (ø)
include/xrpl/protocol/Indexes.h 100.0% <ø> (ø)
include/xrpl/protocol/detail/ledger_entries.macro 100.0% <100.0%> (ø)
include/xrpl/protocol/detail/transactions.macro 100.0% <100.0%> (ø)
src/libxrpl/protocol/InnerObjectFormats.cpp 100.0% <100.0%> (ø)
src/libxrpl/protocol/TxFormats.cpp 100.0% <ø> (ø)
src/xrpld/app/tx/applySteps.h 100.0% <100.0%> (ø)
src/xrpld/app/tx/detail/AMMBid.cpp 90.4% <100.0%> (ø)
src/xrpld/app/tx/detail/AMMClawback.cpp 100.0% <100.0%> (ø)
src/xrpld/app/tx/detail/AMMCreate.cpp 90.5% <100.0%> (ø)
... and 50 more

... and 14 files with indirect coverage changes

Impacted file tree graph

gpMPTokenIssuanceUnlock = 65548,
};

class Permission
Copy link
Collaborator

Choose a reason for hiding this comment

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

should probably delete copy and copy assignment constructors

Copy link
Collaborator

Choose a reason for hiding this comment

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

does this still needs to be done?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

just added

@yinyiqian1 yinyiqian1 force-pushed the account_permissions branch 3 times, most recently from 6101542 to bd7150e Compare November 5, 2024 21:50
@yinyiqian1 yinyiqian1 force-pushed the account_permissions branch 2 times, most recently from 04cf7ad to 5c1d6a3 Compare November 18, 2024 16:24
@gregtatcam
Copy link
Collaborator

gregtatcam commented Nov 19, 2024

PR description should be added with a link to technical specifications if applicable.

@yinyiqian1
Copy link
Collaborator Author

PR description should be added with a link to technical specifications if applicable.

just added

@yinyiqian1
Copy link
Collaborator Author

FYI: unit test failures are transient, which have passed locally. "ERR:Env Env::close() failed: no response from server"

Copy link
Collaborator

@gregtatcam gregtatcam left a comment

Choose a reason for hiding this comment

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

I took a pass and left a few comments.
My big concern is how sfAccount is obfuscated by sfOnBehalfOf. sfAccount pretty much should never be used in the transactors with some exceptions. It's easy to miss these kind of errors where sfAccount is used instead of the account included in the context.

LEDGER_ENTRY(ltACCOUNT_PERMISSION, 0x0082, AccountPermission, ({
{sfAccount, soeREQUIRED},
{sfAuthorize, soeREQUIRED},
{sfPermissions, soeREQUIRED},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to make this optional since it could be empty?

Copy link
Collaborator Author

@yinyiqian1 yinyiqian1 Dec 2, 2024

Choose a reason for hiding this comment

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

Since being empty means deleting all the permissions, can we keep it as required? So that the user won't delete the permissions by accident when they mistakenly leave out the Permissions field. Giving an empty field is more clear that they want to delete all the permissions.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is an on-ledger object, not the transaction. It's empty if there is no permissions so it could be optional. Can save a bit of space this way, 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.

yes. make sense.

/** This transaction type delegates authorized account specified permissions */
TRANSACTION(ttACCOUNT_PERMISSION_SET, 61, AccountPermissionSet, ({
{sfAuthorize, soeREQUIRED},
{sfPermissions, soeREQUIRED},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to make it optional since it could be empty?

*/

enum GranularPermissionType : std::uint32_t {
gpTrustlineAuthorize = 65537,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is gp prefix necessary? Can we just have TrustlineAuthorize, etc?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

gp is to indicate it is a granular permission set's member. I'm ok with either way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

just deleted gp

@@ -77,6 +77,7 @@ enum class LedgerNameSpace : std::uint16_t {
MPTOKEN_ISSUANCE = '~',
MPTOKEN = 't',
CREDENTIAL = 'D',
ACCOUNT_PERMISSION = 'P',
Copy link
Collaborator

Choose a reason for hiding this comment

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

This value is already used by DEPOSIT_PREAUTH_CREDENTIALS

{
auto permissionObj = dynamic_cast<STObject const*>(&permission);

if (!permissionObj || (permissionObj->getFName() != sfPermission))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this needed? If the array is invalid it should fail in the array parsing. And if the name is not Permission then it should fail also before it gets to the transactor. Should have a unit-test fo this.

@@ -197,7 +197,7 @@ preclaimHelper<MPTIssue>(
TER
Clawback::preclaim(PreclaimContext const& ctx)
{
AccountID const issuer = ctx.tx[sfAccount];
AccountID const issuer = ctx.account;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be const&. Please check everywhere where an alias variable is used for ctx.account. It should be AccountID const&.

if (ctx_.isDelegated && !ctx_.gpSet.empty())
{
// if gpSet is not empty, granular delegation is happening.
if (bLock && ctx_.gpSet.find(gpMPTokenIssuanceLock) == ctx_.gpSet.end())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can simplify here and below.

if (bLock && !ctx.gpSet.contains(gpMPTokenIssuanceLock))

auto const amountIssue = dstAmount.issue();
if (isXRP(amountIssue))
return tecNO_AUTH;
if (amountIssue.account == account_ &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can simplify here and below:

if (amountIssue.account == account_ &&
        ctx_.gpSet.contains(gpPaymentMint))

if (bSetNoRipple || bClearNoRipple || bQualityIn || bQualityOut)
return terNO_AUTH;
if (bSetAuth &&
ctx_.gpSet.find(gpTrustlineAuthorize) == ctx_.gpSet.end())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can simplify here and below:

if (bSetAuth &&
        !ctx_.gpSet.contains(gpTrustlineAuthorize))

@@ -458,6 +458,7 @@ LedgerEntryTypesMatch::visitEntry(
switch (after->getType())
{
case ltACCOUNT_ROOT:
case ltACCOUNT_PERMISSION:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I can't leave a comment on unchanged lines so commenting here for ValidClawback::finalize:924, which is this line:

 AccountID const issuer = tx.getAccountID(sfAccount);

The issuer in this case could be either sfAccount or sfOnBehalfOf, 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.

This is missed out. I'll also check all the other similar places which is missed out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@gregtatcam
I also need to make change to AcceptedLedgerTx.cpp, LedgerToJson.cpp, and NetworkOPs.cpp, because they also check the account id when the transaction type is ttOFFER_CREATE. I'm working on this as well

{
}

STTx
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be STTx const&

}

template <class T>
typename std::enable_if<
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should use require instead here and below.

std::unordered_map<GranularPermissionType, TxType> granularTxTypeMap;

public:
static Permission const&
Copy link
Collaborator

Choose a reason for hiding this comment

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

They should be also static. There should be only one instance of those maps.

{
return tx_;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it also makes sense to add implicit conversion operator to STTx const&. Then don't need to call tx.getTx() in many cases. Can just pass tx.

Copy link
Collaborator

Choose a reason for hiding this comment

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

And perhaps change getTx() to something else, maybe getSTTx? tx.getTx() doesn't look right.

Copy link
Collaborator

@gregtatcam gregtatcam left a comment

Choose a reason for hiding this comment

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

The unit-tests are missing for AccountPermissionSet, all delegated transactions, and all granular permissions.

// delegated by another account by checking if the sfOnBehalfOf field is
// present. If it is present, we need to return the sfOnBehalfOf field as the
// account when calling getAccountID(sfAccount) and tx[sfAccount].
class STTxWr
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about renaming to STTxDelegated?

//==============================================================================

#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/SField.h>
Copy link
Collaborator

Choose a reason for hiding this comment

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

This and next include are not used.

@@ -206,7 +206,9 @@ fillJsonTx(
if ((fill.options & LedgerFill::ownerFunds) &&
txn->getTxnType() == ttOFFER_CREATE)
{
auto const account = txn->getAccountID(sfAccount);
auto const& account = txn->isFieldPresent(sfOnBehalfOf)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you about adding getDelegatingAccountID() to STTx?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

can we give it a more precise name like getDelegatingOrOriginalAccountID() or getEffectiveAccountID()?

Copy link
Collaborator

Choose a reason for hiding this comment

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

getEffectiveAccountID() sounds good.

auto pfresult = preflight(app, view.rules(), tx, flags, j);
bool const isDelegated = view.rules().enabled(featureAccountPermission) &&
tx.isFieldPresent(sfOnBehalfOf);
STTxWr txWr(tx, isDelegated);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you think if STTxWr is hidden even further? I.e. it can be created on a fly in *Context and *Result types and those type will have STTxWr const data member instead of STTWr const&. STTxWr is a lightweight class and it's not a problem instantiating a few instances. preflight, preclaim, *Context, and *Result signatures don't change then.

AccountPermissionSet::preclaim(PreclaimContext const& ctx)
{
auto const account = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
if (!account)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should use exists instead and simplify:

if (!ctx.view.exists(keylet::account(ctx.tx[sfAccount]))

@@ -126,6 +130,10 @@ preflight1(PreflightContext const& ctx)
ctx.tx.isFieldPresent(sfAccountTxnID))
return temINVALID;

if (!ctx.rules.enabled(featureAccountPermission) &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is already checked at the beginning of the function.

STTx const& tx,
std::unordered_set<GranularPermissionType>& permissions)
{
if (!view.read(keylet::account(tx[sfOnBehalfOf])))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be exists instead.

Should we check if sfOnBehalfOf is included in tx? checkPermissions is so far only called in delegated but still might make sense to check.

keylet::accountPermission(tx[sfOnBehalfOf], tx[sfAccount]);
auto const sle = view.read(accountPermissionKey);
if (!sle)
return temMALFORMED; // todo: change error code
Copy link
Collaborator

Choose a reason for hiding this comment

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

Change the error code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

how about tecNO_PERMISSION because the permission does not exist?

@@ -247,6 +300,21 @@ TER
Transactor::payFee()
{
auto const feePaid = ctx_.tx[sfFee].xrp();
if (ctx_.tx.isDelegated())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this needed? We always need the account sender to update the balance. I.e. it's always sfAccount. And if it's not delegated then account_ is sfAccount.

Copy link
Collaborator Author

@yinyiqian1 yinyiqian1 Jan 6, 2025

Choose a reason for hiding this comment

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

I removed the check here. But I still need to add
if (!ctx_.tx.isDelegated()) mSourceBalance -= feePaid;
to keep mSourceBalance value precise.

@@ -834,6 +869,7 @@ doLedgerEntry(RPC::JsonContext& context)
{jss::index, parseIndex, ltANY},
{jss::account_root, parseAccountRoot, ltACCOUNT_ROOT},
// TODO: add amendments
{jss::amm, parseAccountPermission, ltACCOUNT_PERMISSION},
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not amm, is it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

forgot to change when copying :(

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.

3 participants