Skip to content

fix: Bidirection Object Storage for delegation#6681

Open
Kassaking7 wants to merge 2 commits intoXRPLF:developfrom
Kassaking7:DEFI-568
Open

fix: Bidirection Object Storage for delegation#6681
Kassaking7 wants to merge 2 commits intoXRPLF:developfrom
Kassaking7:DEFI-568

Conversation

@Kassaking7
Copy link
Copy Markdown
Collaborator

@Kassaking7 Kassaking7 commented Mar 27, 2026

High Level Overview of Change

When account A (the delegatee) is deleted, any Delegate ledger objects in other accounts' owner directories that reference A via sfAuthorize were not cleaned up, leaving dangling objects on-ledger.

This PR fixes the issue by storing the Delegate object in both the delegating account's (B) and the authorized account's (A) owner directories, mirroring the pattern used by EscrowCreate. When either party deletes their account, AccountDelete walks their owner directory, finds the ltDELEGATE entry, and deleteDelegate cleans up both sides.

Context of Change

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)

I conducted a local test using standalone mode. The core commands I had:

% curl -s -X POST http://127.0.0.1:5005 \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "account_objects",
    "params": [{"account": "rGkPsFGDkKprjswvPjtqHVt4VwvRRbz7nM"}]
  }' | python3 -m json.tool
{
    "result": {
        "account": "rGkPsFGDkKprjswvPjtqHVt4VwvRRbz7nM",
        "account_objects": [
            {
                "Account": "rGkPsFGDkKprjswvPjtqHVt4VwvRRbz7nM",
                "Authorize": "rK27ECzvntUQPQRnpQfT4ByMKpYdPYktua",
                "DestinationNode": "0",
                "Flags": 0,
                "LedgerEntryType": "Delegate",
                "OwnerNode": "0",
                "Permissions": [
                    {
                        "Permission": {
                            "PermissionValue": "Payment"
                        }
                    }
                ],
                "PreviousTxnID": "ED79B04396CA6D34C74483B8FC5FD14BAFB3F9A8321609A14E2352EB9362FDA6",
                "PreviousTxnLgrSeq": 6,
                "index": "07349519B9ED9599FF6C84C206735741C5C24F4C456AB30EFAAC69C42FC45D77"
            }
        ],
        "ledger_current_index": 7,
        "status": "success",
        "validated": false
    }
}


%curl -s -X POST http://127.0.0.1:5005 \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "account_objects",
    "params": [{"account": "rK27ECzvntUQPQRnpQfT4ByMKpYdPYktua"}]
  }' | python3 -m json.tool
{
    "result": {
        "account": "rK27ECzvntUQPQRnpQfT4ByMKpYdPYktua",
        "account_objects": [
            {
                "Account": "rGkPsFGDkKprjswvPjtqHVt4VwvRRbz7nM",
                "Authorize": "rK27ECzvntUQPQRnpQfT4ByMKpYdPYktua",
                "DestinationNode": "0",
                "Flags": 0,
                "LedgerEntryType": "Delegate",
                "OwnerNode": "0",
                "Permissions": [
                    {
                        "Permission": {
                            "PermissionValue": "Payment"
                        }
                    }
                ],
                "PreviousTxnID": "ED79B04396CA6D34C74483B8FC5FD14BAFB3F9A8321609A14E2352EB9362FDA6",
                "PreviousTxnLgrSeq": 6,
                "index": "07349519B9ED9599FF6C84C206735741C5C24F4C456AB30EFAAC69C42FC45D77"
            }
        ],
        "ledger_current_index": 7,
        "status": "success",
        "validated": false
    }
}

%for i in $(seq 1 256); do
  curl -s -X POST http://127.0.0.1:5005 \
    -H 'Content-Type: application/json' \
    -d '{"method":"ledger_accept","params":[{}]}' > /dev/null
done
echo "done"
done

%curl -s -X POST http://127.0.0.1:5005 \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "submit",
    "params": [{
      "secret": "ssT5qnHJRAFZ6aRK1By3AfkD64Jqn",
      "tx_json": {
        "TransactionType": "AccountDelete",
        "Account": "rK27ECzvntUQPQRnpQfT4ByMKpYdPYktua",
        "Destination": "rGkPsFGDkKprjswvPjtqHVt4VwvRRbz7nM",
        "Fee": "2000000",
        "Sequence": 5
      }
    }]
  }' | python3 -m json.tool | grep engine_result
        "engine_result": "tesSUCCESS",
        "engine_result_code": 0,
        "engine_result_message": "The transaction was applied. Only final in a validated ledger.",

% curl -s -X POST http://127.0.0.1:5005 \
  -H 'Content-Type: application/json' \
  -d '{"method":"ledger_accept","params":[{}]}' | python3 -m json.tool | grep ledger_current
        "ledger_current_index": 264,

% curl -s -X POST http://127.0.0.1:5005 \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "account_objects",
    "params": [{"account": "rGkPsFGDkKprjswvPjtqHVt4VwvRRbz7nM"}]
  }' | python3 -m json.tool
{
    "result": {
        "account": "rGkPsFGDkKprjswvPjtqHVt4VwvRRbz7nM",
        "account_objects": [],
        "ledger_current_index": 264,
        "status": "success",
        "validated": false
    }
}
% curl -s -X POST http://127.0.0.1:5005 \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "account_info",
    "params": [{"account": "rK27ECzvntUQPQRnpQfT4ByMKpYdPYktua"}]
  }' | python3 -m json.tool | grep -E "error|Sequence"
        "error": "actNotFound",
        "error_code": 19,
        "error_message": "Account not found.",
        "status": "error",

We can see that now we don't have delegate object for delegator after delegatee's account got deleted.


// Add to authorized account's owner directory so the object can be found
// and cleaned up when the authorized account is deleted.
if (ctx_.view().rules().enabled(featurePermissionDelegationV1_1))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In DelegateSet.cpp, we don't need this guard, because it is explicitly defined in transactions.macro, that this tx is dependant on featurePermissionDelegationV1_1. Without enabling it will return temDISABLED in preflight.

return tecINTERNAL; // LCOV_EXCL_LINE

if (!view.dirRemove(keylet::ownerDir(account), (*sle)[sfOwnerNode], sle->key(), false))
AccountID const delegator = (*sle)[sfAccount];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: use auto const for modern c++ best practice

@github-actions
Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

@github-actions
Copy link
Copy Markdown

⚠️ This PR contains unsigned commits. To get your PR merged, please sign them. ⚠️

If only the most recent commit is unsigned, you can run:

  1. Amend the commit: git commit --amend --no-edit -n -S
  2. Overwrite the commit: git push --force-with-lease

If multiple commits are unsigned, you can run:

  1. Go into interactive rebase mode: git rebase --interactive HEAD~<NUM_OF_COMMITS>,
    where NUM_OF_COMMITS is the number of most recent commits that will be available
    to edit.
  2. Change "pick" to "edit" for the commits you need to sign, and then save and exit.
  3. For each commit, run: git commit --amend --no-edit -n -S
  4. Continue the rebase: git rebase --continue
  5. Overwrite the commit(s): git push --force-with-lease

If you're new to commit signing, there are different ways to set it up:

Sign commits with gpg

Follow the steps below to set up commit signing with gpg:

  1. Generate a GPG key
  2. Add the GPG key to your GitHub account
  3. Configure git to use your GPG key for commit signing
Sign commits with ssh-agent

Follow the steps below to set up commit signing with ssh-agent:

  1. Generate an SSH key and add it to ssh-agent
  2. Add the SSH key to your GitHub account
  3. Configure git to use your SSH key for commit signing
Sign commits with 1Password

You can also sign commits using 1Password, which lets you sign commits with biometrics without the signing key leaving the local 1Password process.
See use 1Password to sign your commits.

@github-actions
Copy link
Copy Markdown

All conflicts have been resolved. Assigned reviewers can now start or resume their review.

}

void
testAuthorizedAccountDeleteLocksReserve()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this test can be combined into the previous case:

" // Delegatee (bob) deletes account: Delegate object is cleaned up from
// both alice's and bob's owner directories."

@Kassaking7 Kassaking7 force-pushed the DEFI-568 branch 2 times, most recently from 98f7c62 to ac6b4dd Compare April 6, 2026 16:55
Copy link
Copy Markdown
Collaborator

@yinyiqian1 yinyiqian1 left a comment

Choose a reason for hiding this comment

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

LGTM

@yinyiqian1 yinyiqian1 added the Needs additional review PR requires at least one more code review approval before it can be merged label Apr 6, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.6%. Comparing base (56c9d1d) to head (d8d9892).
⚠️ Report is 2 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           develop   #6681   +/-   ##
=======================================
  Coverage     81.6%   81.6%           
=======================================
  Files         1010    1010           
  Lines        75982   75999   +17     
  Branches      7637    7631    -6     
=======================================
+ Hits         61984   62003   +19     
+ Misses       13998   13996    -2     
Files with missing lines Coverage Δ
include/xrpl/protocol/detail/ledger_entries.macro 100.0% <ø> (ø)
...de/xrpl/protocol_autogen/ledger_entries/Delegate.h 100.0% <100.0%> (ø)
include/xrpl/tx/transactors/delegate/DelegateSet.h 100.0% <ø> (ø)
...c/libxrpl/tx/transactors/account/AccountDelete.cpp 96.3% <100.0%> (ø)
...rc/libxrpl/tx/transactors/delegate/DelegateSet.cpp 100.0% <100.0%> (ø)

... and 4 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

This PR has conflicts, please resolve them in order for the PR to be reviewed.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

All conflicts have been resolved. Assigned reviewers can now start or resume their review.

@Kassaking7 Kassaking7 requested a review from shawnxie999 April 10, 2026 15:14
Copy link
Copy Markdown
Collaborator

@PeterChen13579 PeterChen13579 left a comment

Choose a reason for hiding this comment

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

LGTM, feel free to implement the suggestion I have, if you want. I'm okay either way

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs additional review PR requires at least one more code review approval before it can be merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants