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

DID: Decentralized identifiers (DIDs) (XLS-40): #4636

Merged
merged 28 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cef1f64
initial DID implementation
mvadari Jul 18, 2023
2f4c285
add RPC tests
mvadari Aug 17, 2023
99b53be
respond to comments, handle max blob length
mvadari Aug 17, 2023
5916f5f
add AccountDelete test
mvadari Aug 17, 2023
7ee38f9
update to latest version
mvadari Sep 15, 2023
27ad50c
merge develop
mvadari Sep 15, 2023
ed9e915
merge develop
mvadari Sep 15, 2023
10b6ff7
Merge branch 'develop' into did
mvadari Sep 18, 2023
7fc2015
Merge branch 'develop' into did
mvadari Sep 19, 2023
298077f
respond to comments
mvadari Sep 26, 2023
04066ae
add another simplification lambda
mvadari Sep 26, 2023
13dac8e
Merge branch 'develop' into did
mvadari Oct 3, 2023
7609a66
rename sfAttestation -> sfData
mvadari Oct 3, 2023
4705908
rename DID -> did in test
mvadari Oct 6, 2023
f347bae
make test case names more descriptive
mvadari Oct 6, 2023
6bda30b
fix minor issues
mvadari Oct 6, 2023
31117f7
move helper functions to View.h
mvadari Oct 6, 2023
4acc54b
add docs
mvadari Oct 6, 2023
b0c99a4
update cmake file
mvadari Oct 6, 2023
fa63fc3
actual cmake fix
mvadari Oct 6, 2023
36716d6
Revert "move helper functions to View.h"
mvadari Oct 6, 2023
c6e6b26
fix formatting
mvadari Oct 6, 2023
d02176b
Merge branch 'develop' into did
mvadari Oct 7, 2023
7eb733f
Merge branch 'develop' into did
mvadari Oct 11, 2023
d65b77f
Merge branch 'develop' into did
intelliot Oct 12, 2023
9885bf5
Merge branch 'develop' into did
mvadari Oct 17, 2023
ea560af
Update to latest spec
mvadari Oct 17, 2023
7c2768d
Merge branch 'develop' into did
intelliot Oct 18, 2023
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
2 changes: 2 additions & 0 deletions Builds/CMake/RippledCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/CreateTicket.cpp
src/ripple/app/tx/impl/DeleteAccount.cpp
src/ripple/app/tx/impl/DepositPreauth.cpp
src/ripple/app/tx/impl/DID.cpp
src/ripple/app/tx/impl/Escrow.cpp
src/ripple/app/tx/impl/InvariantCheck.cpp
src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp
Expand Down Expand Up @@ -760,6 +761,7 @@ if (tests)
src/test/app/DeliverMin_test.cpp
src/test/app/DepositAuth_test.cpp
src/test/app/Discrepancy_test.cpp
src/test/app/DID_test.cpp
src/test/app/DNS_test.cpp
src/test/app/Escrow_test.cpp
src/test/app/FeeVote_test.cpp
Expand Down
215 changes: 215 additions & 0 deletions src/ripple/app/tx/impl/DID.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
mvadari marked this conversation as resolved.
Show resolved Hide resolved

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#include <ripple/app/tx/impl/DID.h>

#include <ripple/basics/Log.h>
#include <ripple/ledger/ApplyView.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>

namespace ripple {

/*
DID
======

TODO: add docs here
seelabs marked this conversation as resolved.
Show resolved Hide resolved
*/

//------------------------------------------------------------------------------

NotTEC
DIDSet::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureDID))
return temDISABLED;

if (ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

if (!ctx.tx.isFieldPresent(sfURI) && !ctx.tx.isFieldPresent(sfData))
return temEMPTY_DID;

if (ctx.tx.isFieldPresent(sfURI) && ctx.tx[sfURI].empty() &&
ctx.tx.isFieldPresent(sfData) && ctx.tx[sfData].empty())
return temEMPTY_DID;
mvadari marked this conversation as resolved.
Show resolved Hide resolved

mvadari marked this conversation as resolved.
Show resolved Hide resolved
return preflight2(ctx);
}

TER
addSLE(
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's your call, but I don't find much value in making this a separate function. I'd probably just make a block in the one place it's called. But also fine as-is. (Although it should live in an anonymous namespace if you do keep it a separate function).

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 made it a separate function because I'd like to make it a helper function for the future, but I figured that the refactor wasn't worth doing in this PR and could wait for a separate PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That makes sense. I'm fine with it as-is. Thanks for the explanation.

ApplyContext& ctx,
std::shared_ptr<SLE> const& sle,
AccountID const& owner)
{
auto const sleAccount = ctx.view().peek(keylet::account(owner));
if (!sleAccount)
return tefINTERNAL;

// Check reserve availability for new object creation
{
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
auto const reserve =
ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);

if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}

// Add ledger object to ledger
ctx.view().insert(sle);

// Add ledger object to owner's page
{
auto page = ctx.view().dirInsert(
keylet::ownerDir(owner), sle->key(), describeOwnerDir(owner));
if (!page)
return tecDIR_FULL;
(*sle)[sfOwnerNode] = *page;
}
adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal);
ctx.view().update(sleAccount);

return tesSUCCESS;
}

TER
DIDSet::doApply()
{
// Edit ledger object if it already exists
Keylet const didKeylet = keylet::did(account_);
if (auto const sleDID = ctx_.view().peek(didKeylet))
{
mvadari marked this conversation as resolved.
Show resolved Hide resolved
if (auto const uri = ctx_.tx[~sfURI])
{
if (uri->empty())
{
sleDID->makeFieldAbsent(sfURI);
}
else
{
(*sleDID)[sfURI] = *uri;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You should be able to just reduce this if/else with:

(*sleDID)[~sfURI] = ctx_.tx[~sfURI];

Notice that you don't check if one or both the URI and DATA fields are being set to the same value that they already hold; so it's possible for someone to "update" the object while making no change. You may want to elide the call to update in that case.

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 don't think that reduction works.
What I want is: do nothing if the field doesn't exist in the transaction, delete the field if the field is an empty string, and replace it in any other case.
What my understanding of that line of code is: delete the field if it doesn't exist, and replace it in any other case.

Copy link
Contributor

Choose a reason for hiding this comment

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

You're right, given what you're trying to do my proposed construct won't work. It's fine as it, but I wouldn't have personally structured this as you have; I'd have included two flags that, if set, indicate the type of value you're setting/clearing. So, if sfFlags & flagURI was set, then the URI field is replaced with the URI field from the transaction.

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 was the method that @seelabs and @aanchal4 thought was most user-intuitive.

}
if (auto const data = ctx_.tx[~sfData])
{
if (data->empty())
{
sleDID->makeFieldAbsent(sfData);
}
else
{
(*sleDID)[sfData] = *data;
}
}
if (!sleDID->isFieldPresent(sfURI) && !sleDID->isFieldPresent(sfData))
mvadari marked this conversation as resolved.
Show resolved Hide resolved
{
return tecEMPTY_DID;
}
ctx_.view().update(sleDID);
return tesSUCCESS;
}

// Create new ledger object otherwise
auto const sleDID = std::make_shared<SLE>(didKeylet);
(*sleDID)[sfAccount] = account_;
if (auto const uri = ctx_.tx[~sfURI]; uri.has_value() && !uri->empty())
mvadari marked this conversation as resolved.
Show resolved Hide resolved
(*sleDID)[sfURI] = uri.value();
if (auto const data = ctx_.tx[~sfData]; data.has_value() && !data->empty())
(*sleDID)[sfData] = data.value();

if (auto const ret = addSLE(ctx_, sleDID, account_); !isTesSuccess(ret))
return ret;

return tesSUCCESS;
mvadari marked this conversation as resolved.
Show resolved Hide resolved
}

NotTEC
DIDDelete::preflight(PreflightContext const& ctx)
mvadari marked this conversation as resolved.
Show resolved Hide resolved
{
if (!ctx.rules.enabled(featureDID))
return temDISABLED;

if (ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

return preflight2(ctx);
}

TER
DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner)
seelabs marked this conversation as resolved.
Show resolved Hide resolved
{
auto const sle = ctx.view().peek(sleKeylet);
if (!sle)
return tecNO_ENTRY;

return DIDDelete::deleteSLE(ctx.view(), sle, owner, ctx.journal);
}

TER
DIDDelete::deleteSLE(
ApplyView& view,
std::shared_ptr<SLE> sle,
AccountID const owner,
beast::Journal j)
{
// Remove object from owner directory
{
auto const page = (*sle)[sfOwnerNode];
if (!view.dirRemove(keylet::ownerDir(owner), page, sle->key(), true))
mvadari marked this conversation as resolved.
Show resolved Hide resolved
{
JLOG(j.fatal()) << "Unable to delete DID Token from owner.";
return tefBAD_LEDGER;
}
}

auto const sleOwner = view.peek(keylet::account(owner));
mvadari marked this conversation as resolved.
Show resolved Hide resolved
adjustOwnerCount(view, sleOwner, -1, j);
view.update(sleOwner);

// Remove object from ledger
view.erase(sle);
return tesSUCCESS;
}

TER
DIDDelete::doApply()
{
mvadari marked this conversation as resolved.
Show resolved Hide resolved
AccountID const account = ctx_.tx[sfAccount];
auto const didKeylet = keylet::did(account);

if (auto const ret = deleteSLE(ctx_, didKeylet, account_);
!isTesSuccess(ret))
return ret;

return tesSUCCESS;
mvadari marked this conversation as resolved.
Show resolved Hide resolved
}

} // namespace ripple
73 changes: 73 additions & 0 deletions src/ripple/app/tx/impl/DID.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#ifndef RIPPLE_TX_DID_H_INCLUDED
#define RIPPLE_TX_DID_H_INCLUDED

#include <ripple/app/tx/impl/Transactor.h>

namespace ripple {

class DIDSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};

explicit DIDSet(ApplyContext& ctx) : Transactor(ctx)
{
}

static NotTEC
preflight(PreflightContext const& ctx);

TER
doApply() override;
};

//------------------------------------------------------------------------------

class DIDDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};

explicit DIDDelete(ApplyContext& ctx) : Transactor(ctx)
{
}

static NotTEC
preflight(PreflightContext const& ctx);

static TER
deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner);

static TER
deleteSLE(
ApplyView& view,
std::shared_ptr<SLE> sle,
AccountID const owner,
beast::Journal j);

TER
doApply() override;
};

} // namespace ripple

#endif
15 changes: 15 additions & 0 deletions src/ripple/app/tx/impl/DeleteAccount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
//==============================================================================

#include <ripple/app/tx/impl/DID.h>
#include <ripple/app/tx/impl/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/SetSignerList.h>
Expand Down Expand Up @@ -133,6 +134,18 @@ removeNFTokenOfferFromLedger(
return tesSUCCESS;
}

TER
removeDIDFromLedger(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return DIDDelete::deleteSLE(view, sleDel, account, j);
}

// Return nullptr if the LedgerEntryType represents an obligation that can't
// be deleted. Otherwise return the pointer to the function that can delete
// the non-obligation
Expand All @@ -151,6 +164,8 @@ nonObligationDeleter(LedgerEntryType t)
return removeDepositPreauthFromLedger;
case ltNFTOKEN_OFFER:
return removeNFTokenOfferFromLedger;
case ltDID:
return removeDIDFromLedger;
default:
return nullptr;
}
Expand Down
1 change: 1 addition & 0 deletions src/ripple/app/tx/impl/InvariantCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ LedgerEntryTypesMatch::visitEntry(
case ltNFTOKEN_PAGE:
case ltNFTOKEN_OFFER:
case ltAMM:
case ltDID:
break;
default:
invalidTypeAdded_ = true;
Expand Down
Loading