From cef1f64e245b71a4a4cf070268600783b4d8bcc6 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 18 Jul 2023 11:17:26 -0700 Subject: [PATCH 01/18] initial DID implementation --- Builds/CMake/RippledCore.cmake | 2 + src/ripple/app/tx/impl/DID.cpp | 215 +++++++++++ src/ripple/app/tx/impl/DID.h | 73 ++++ src/ripple/app/tx/impl/DeleteAccount.cpp | 15 + src/ripple/app/tx/impl/InvariantCheck.cpp | 1 + src/ripple/app/tx/impl/applySteps.cpp | 21 ++ src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/Indexes.h | 3 + src/ripple/protocol/LedgerFormats.h | 6 + src/ripple/protocol/SField.h | 1 + src/ripple/protocol/TER.h | 4 +- src/ripple/protocol/TxFormats.h | 6 + src/ripple/protocol/impl/Feature.cpp | 1 + src/ripple/protocol/impl/Indexes.cpp | 7 + src/ripple/protocol/impl/LedgerFormats.cpp | 26 +- src/ripple/protocol/impl/SField.cpp | 1 + src/ripple/protocol/impl/TER.cpp | 2 + src/ripple/protocol/impl/TxFormats.cpp | 16 + src/ripple/protocol/jss.h | 4 + src/ripple/rpc/handlers/LedgerEntry.cpp | 10 + src/ripple/rpc/impl/RPCHelpers.cpp | 5 +- src/test/app/DID_test.cpp | 392 +++++++++++++++++++++ src/test/app/NFToken_test.cpp | 2 +- 23 files changed, 804 insertions(+), 12 deletions(-) create mode 100644 src/ripple/app/tx/impl/DID.cpp create mode 100644 src/ripple/app/tx/impl/DID.h create mode 100644 src/test/app/DID_test.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index b676c5ff5e9..486d16d89da 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -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 @@ -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 diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp new file mode 100644 index 00000000000..0d5e9279979 --- /dev/null +++ b/src/ripple/app/tx/impl/DID.cpp @@ -0,0 +1,215 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +/* + DID + ====== + + TODO: add docs here +*/ + +//------------------------------------------------------------------------------ + +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; + + return preflight2(ctx); +} + +TER +addSLE( + ApplyContext& ctx, + std::shared_ptr 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)) + { + if (auto const uri = ctx_.tx[~sfURI]) + { + if (uri->empty()) + { + sleDID->makeFieldAbsent(sfURI); + } + else + { + (*sleDID)[sfURI] = *uri; + } + } + if (auto const data = ctx_.tx[~sfData]) + { + if (data->empty()) + { + sleDID->makeFieldAbsent(sfData); + } + else + { + (*sleDID)[sfData] = *data; + } + } + if (!sleDID->isFieldPresent(sfURI) && !sleDID->isFieldPresent(sfData)) + { + return tecEMPTY_DID; + } + ctx_.view().update(sleDID); + return tesSUCCESS; + } + + // Create new ledger object otherwise + auto const sleDID = std::make_shared(didKeylet); + (*sleDID)[sfAccount] = account_; + if (auto const uri = ctx_.tx[~sfURI]; uri.has_value() && !uri->empty()) + (*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; +} + +NotTEC +DIDDelete::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; + + return preflight2(ctx); +} + +TER +DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner) +{ + 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, + 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)) + { + JLOG(j.fatal()) << "Unable to delete DID Token from owner."; + return tefBAD_LEDGER; + } + } + + auto const sleOwner = view.peek(keylet::account(owner)); + adjustOwnerCount(view, sleOwner, -1, j); + view.update(sleOwner); + + // Remove object from ledger + view.erase(sle); + return tesSUCCESS; +} + +TER +DIDDelete::doApply() +{ + 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; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/DID.h b/src/ripple/app/tx/impl/DID.h new file mode 100644 index 00000000000..13d5a261542 --- /dev/null +++ b/src/ripple/app/tx/impl/DID.h @@ -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 + +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, + AccountID const owner, + beast::Journal j); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/DeleteAccount.cpp b/src/ripple/app/tx/impl/DeleteAccount.cpp index 62cc9e1fbbf..574fdc051ea 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.cpp +++ b/src/ripple/app/tx/impl/DeleteAccount.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include #include @@ -133,6 +134,18 @@ removeNFTokenOfferFromLedger( return tesSUCCESS; } +TER +removeDIDFromLedger( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr 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 @@ -151,6 +164,8 @@ nonObligationDeleter(LedgerEntryType t) return removeDepositPreauthFromLedger; case ltNFTOKEN_OFFER: return removeNFTokenOfferFromLedger; + case ltDID: + return removeDIDFromLedger; default: return nullptr; } diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 23fa7b17115..aec4f8c3a11 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -380,6 +380,7 @@ LedgerEntryTypesMatch::visitEntry( case ltNFTOKEN_PAGE: case ltNFTOKEN_OFFER: case ltAMM: + case ltDID: break; default: invalidTypeAdded_ = true; diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index f0d092d793d..89af13105ae 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -165,6 +166,10 @@ invoke_preflight(PreflightContext const& ctx) return invoke_preflight_helper(ctx); case ttAMM_BID: return invoke_preflight_helper(ctx); + case ttDID_SET: + return invoke_preflight_helper(ctx); + case ttDID_DELETE: + return invoke_preflight_helper(ctx); default: assert(false); return {temUNKNOWN, TxConsequences{temUNKNOWN}}; @@ -278,6 +283,10 @@ invoke_preclaim(PreclaimContext const& ctx) return invoke_preclaim(ctx); case ttAMM_BID: return invoke_preclaim(ctx); + case ttDID_SET: + return invoke_preclaim(ctx); + case ttDID_DELETE: + return invoke_preclaim(ctx); default: assert(false); return temUNKNOWN; @@ -353,6 +362,10 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) return AMMVote::calculateBaseFee(view, tx); case ttAMM_BID: return AMMBid::calculateBaseFee(view, tx); + case ttDID_SET: + return DIDSet::calculateBaseFee(view, tx); + case ttDID_DELETE: + return DIDDelete::calculateBaseFee(view, tx); default: assert(false); return XRPAmount{0}; @@ -529,6 +542,14 @@ invoke_apply(ApplyContext& ctx) AMMBid p(ctx); return p(); } + case ttDID_SET: { + DIDSet p(ctx); + return p(); + } + case ttDID_DELETE: { + DIDDelete p(ctx); + return p(); + } default: assert(false); return {temUNKNOWN, false}; diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 626b99b8cdb..f5ba1ca65d2 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 61; +static constexpr std::size_t numFeatures = 62; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -348,6 +348,7 @@ extern uint256 const fixNonFungibleTokensV1_2; extern uint256 const fixNFTokenRemint; extern uint256 const fixReducedOffersV1; extern uint256 const featureClawback; +extern uint256 const featureDID; } // namespace ripple diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 014ff82ef1b..b0208a7306d 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -270,6 +270,9 @@ amm(Issue const& issue1, Issue const& issue2) noexcept; Keylet amm(uint256 const& amm) noexcept; +Keylet +did(AccountID const& account) noexcept; + } // namespace keylet // Everything below is deprecated and should be removed in favor of keylets: diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index a613c3a470d..53bb7815f7b 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -167,6 +167,12 @@ enum LedgerEntryType : std::uint16_t */ ltAMM = 0x0079, + /** The ledger object which tracks the DID. + + \sa keylet::did + */ + ltDID = 0x0049, + //--------------------------------------------------------------------------- /** A special type, matching any ledger entry type. diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index e1180bc1c93..4723409363e 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -515,6 +515,7 @@ extern SF_VL const sfCreateCode; extern SF_VL const sfMemoType; extern SF_VL const sfMemoData; extern SF_VL const sfMemoFormat; +extern SF_VL const sfData; // variable length (uncommon) extern SF_VL const sfFulfillment; diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 955181de7e2..6affc2d06f0 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -125,6 +125,7 @@ enum TEMcodes : TERUnderlyingType { temBAD_NFTOKEN_TRANSFER_FEE, temBAD_AMM_TOKENS, + temEMPTY_DID, }; //------------------------------------------------------------------------------ @@ -298,7 +299,8 @@ enum TECcodes : TERUnderlyingType { tecUNFUNDED_AMM = 162, tecAMM_BALANCE = 163, tecAMM_FAILED = 164, - tecAMM_INVALID_TOKENS = 165 + tecAMM_INVALID_TOKENS = 165, + tecEMPTY_DID = 166 }; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index a79f7dd79cb..5683482be88 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -157,6 +157,12 @@ enum TxType : std::uint16_t /** This transaction type bids for the auction slot */ ttAMM_BID = 39, + /** This transaction type creates or updates a DID */ + ttDID_SET = 40, + + /** This transaction type deletes a DID */ + ttDID_DELETE = 41, + /** This system-generated transaction type is used to update the status of the various amendments. For details, see: https://xrpl.org/amendments.html diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index b9710ebbc69..ad3e8fde97e 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -455,6 +455,7 @@ REGISTER_FIX (fixNFTokenRemint, Supported::yes, VoteBehavior::De REGISTER_FIX (fixReducedOffersV1, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo); +REGISTER_FEATURE(DID, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index 1140c42d3ef..63d3e903c16 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -64,6 +64,7 @@ enum class LedgerNameSpace : std::uint16_t { NFTOKEN_BUY_OFFERS = 'h', NFTOKEN_SELL_OFFERS = 'i', AMM = 'A', + DID = 'I', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. @@ -389,6 +390,12 @@ amm(uint256 const& id) noexcept return {ltAMM, id}; } +Keylet +did(AccountID const& account) noexcept +{ + return {ltDID, indexHash(LedgerNameSpace::DID, account)}; +} + } // namespace keylet } // namespace ripple diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 5228b625bb3..a172d680d3c 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -271,13 +271,25 @@ LedgerFormats::LedgerFormats() add(jss::AMM, ltAMM, { - {sfAccount, soeREQUIRED}, - {sfTradingFee, soeDEFAULT}, - {sfVoteSlots, soeOPTIONAL}, - {sfAuctionSlot, soeOPTIONAL}, - {sfLPTokenBalance, soeREQUIRED}, - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED} + {sfAccount, soeREQUIRED}, + {sfTradingFee, soeDEFAULT}, + {sfVoteSlots, soeOPTIONAL}, + {sfAuctionSlot, soeOPTIONAL}, + {sfLPTokenBalance, soeREQUIRED}, + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED} + }, + commonFields); + + add(jss::DID, + ltDID, + { + {sfAccount, soeREQUIRED}, + {sfURI, soeOPTIONAL}, + {sfData, soeOPTIONAL}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED} }, commonFields); diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 95b6d123941..2a468578d91 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -281,6 +281,7 @@ CONSTRUCT_TYPED_SFIELD(sfCreateCode, "CreateCode", VL, CONSTRUCT_TYPED_SFIELD(sfMemoType, "MemoType", VL, 12); CONSTRUCT_TYPED_SFIELD(sfMemoData, "MemoData", VL, 13); CONSTRUCT_TYPED_SFIELD(sfMemoFormat, "MemoFormat", VL, 14); +CONSTRUCT_TYPED_SFIELD(sfData, "Data", VL, 15); // variable length (uncommon) CONSTRUCT_TYPED_SFIELD(sfFulfillment, "Fulfillment", VL, 16); diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index c16e9541fbf..343fc9e9c3c 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -92,6 +92,7 @@ transResults() MAKE_ERROR(tecINSUFFICIENT_FUNDS, "Not enough funds available to complete requested transaction."), MAKE_ERROR(tecOBJECT_NOT_FOUND, "A requested object could not be located."), MAKE_ERROR(tecINSUFFICIENT_PAYMENT, "The payment is not sufficient."), + MAKE_ERROR(tecEMPTY_DID, "The DID object cannot be made to be empty."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -158,6 +159,7 @@ transResults() MAKE_ERROR(temBAD_WEIGHT, "Malformed: Weight must be a positive value."), MAKE_ERROR(temDST_IS_SRC, "Destination may not be source."), MAKE_ERROR(temDST_NEEDED, "Destination not specified."), + MAKE_ERROR(temEMPTY_DID, "Malformed: No DID data provided."), MAKE_ERROR(temINVALID, "The transaction is ill-formed."), MAKE_ERROR(temINVALID_FLAG, "The transaction has an invalid flag."), MAKE_ERROR(temREDUNDANT, "The transaction is redundant."), diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 062174815c9..a295f4956fc 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -394,6 +394,22 @@ TxFormats::TxFormats() {sfTicketSequence, soeOPTIONAL}, }, commonFields); + + add(jss::DIDSet, + ttDID_SET, + { + {sfURI, soeOPTIONAL}, + {sfData, soeOPTIONAL}, + {sfTicketSequence, soeOPTIONAL}, + }, + commonFields); + + add(jss::DIDDelete, + ttDID_DELETE, + { + {sfTicketSequence, soeOPTIONAL}, + }, + commonFields); } TxFormats const& diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index c7faa4ff98b..62a81d7b74b 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -67,6 +67,9 @@ JSS(CheckCash); // transaction type. JSS(CheckCreate); // transaction type. JSS(Clawback); // transaction type. JSS(ClearFlag); // field. +JSS(DID); // ledger type. +JSS(DIDDelete); // transaction type. +JSS(DIDSet); // transaction type. JSS(DeliverMin); // in: TransactionSign JSS(DepositPreauth); // transaction and ledger type. JSS(Destination); // in: TransactionSign; field. @@ -258,6 +261,7 @@ JSS(destination_currencies); // in: PathRequest, RipplePathFind JSS(destination_tag); // in: PathRequest // out: AccountChannels JSS(details); // out: Manifest, server_info +JSS(did); // in: LedgerEntry JSS(dir_entry); // out: DirectoryEntryIterator JSS(dir_index); // out: DirectoryEntryIterator JSS(dir_root); // out: DirectoryEntryIterator diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index 8d0dd236e6d..0dd8b04ee70 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -377,6 +377,16 @@ doLedgerEntry(RPC::JsonContext& context) } } } + else if (context.params.isMember(jss::did)) + { + expectedType = ltDID; + auto const account = + parseBase58(context.params[jss::did].asString()); + if (!account || account->isZero()) + jvResult[jss::error] = "malformedAddress"; + else + uNodeIndex = keylet::did(*account).key; + } else { if (context.params.isMember("params") && diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index bc38df62fc9..985c050c43b 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -981,7 +981,7 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr std::array, 16> + static constexpr std::array, 17> types{ {{jss::account, ltACCOUNT_ROOT}, {jss::amendments, ltAMENDMENTS}, @@ -998,7 +998,8 @@ chooseLedgerEntryType(Json::Value const& params) {jss::ticket, ltTICKET}, {jss::nft_offer, ltNFTOKEN_OFFER}, {jss::nft_page, ltNFTOKEN_PAGE}, - {jss::amm, ltAMM}}}; + {jss::amm, ltAMM}, + {jss::did, ltDID}}}; auto const& p = params[jss::type]; if (!p.isString()) diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp new file mode 100644 index 00000000000..4f23fafed79 --- /dev/null +++ b/src/test/app/DID_test.cpp @@ -0,0 +1,392 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ripple { +namespace test { + +// Helper function that returns the owner count of an account root. +std::uint32_t +ownerCount(test::jtx::Env const& env, test::jtx::Account const& acct) +{ + std::uint32_t ret{0}; + if (auto const sleAcct = env.le(acct)) + ret = sleAcct->at(sfOwnerCount); + return ret; +} + +namespace did { + +Json::Value +set(jtx::Account const& account) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::DIDSet; + jv[jss::Account] = to_string(account.id()); + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +setValid(jtx::Account const& account) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::DIDSet; + jv[jss::Account] = to_string(account.id()); + jv[jss::Flags] = tfUniversal; + jv[sfURI.jsonName] = strHex(std::string{"uri"}); + return jv; +} + +/** Sets the optional URI on a DIDSet. */ +class uri +{ +private: + std::string uri_; + +public: + explicit uri(std::string const& u) : uri_(strHex(u)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const + { + jtx.jv[sfURI.jsonName] = uri_; + } +}; + +/** Sets the optional URI on a DIDSet. */ +class data +{ +private: + std::string data_; + +public: + explicit data(std::string const& u) : data_(strHex(u)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const + { + jtx.jv[sfData.jsonName] = data_; + } +}; + +Json::Value +del(jtx::Account const& account) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::DIDDelete; + jv[jss::Account] = to_string(account.id()); + jv[jss::Flags] = tfUniversal; + return jv; +} + +} // namespace did + +bool +checkVL(Slice const& result, std::string expected) +{ + Serializer s; + s.addRaw(result); + return s.getString() == expected; +} + +struct DID_test : public beast::unit_test::suite +{ + void + testEnabled(FeatureBitset features) + { + testcase("Enabled"); + + using namespace jtx; + // If the DID amendment is not enabled, you should not be able + // to set or delete DIDs. + Env env{*this, features - featureDID}; + Account const alice{"alice"}; + env.fund(XRP(5000), alice); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + env(did::setValid(alice), ter(temDISABLED)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + env(did::del(alice), ter(temDISABLED)); + env.close(); + } + + void + testAccountReserve(FeatureBitset features) + { + // Verify that the reserve behaves as expected for minting. + testcase("Account reserve"); + + using namespace test::jtx; + + Env env{*this, features}; + Account const alice{"alice"}; + + // Fund alice enough to exist, but not enough to meet + // the reserve for creating a DID. + auto const acctReserve = env.current()->fees().accountReserve(0); + auto const incReserve = env.current()->fees().increment; + env.fund(acctReserve, alice); + env.close(); + BEAST_EXPECT(env.balance(alice) == acctReserve); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // alice does not have enough XRP to cover the reserve for a DID + env(did::setValid(alice), ter(tecINSUFFICIENT_RESERVE)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // Pay alice almost enough to make the reserve for a DID. + env(pay(env.master, alice, incReserve + drops(19))); + BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve + drops(9)); + env.close(); + + // alice still does not have enough XRP for the reserve of a DID. + env(did::setValid(alice), ter(tecINSUFFICIENT_RESERVE)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // Pay alice enough to make the reserve for a DID. + env(pay(env.master, alice, drops(11))); + env.close(); + + // Now alice can create a DID. + env(did::setValid(alice)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // alice deletes her DID. + env(did::del(alice)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + env.close(); + } + + void + testSetInvalid(FeatureBitset features) + { + testcase("Invalid Set"); + + using namespace jtx; + using namespace std::chrono; + + Env env(*this); + Account const alice{"alice"}; + env.fund(XRP(5000), alice); + env.close(); + + //---------------------------------------------------------------------- + // preflight + + // invalid flags + BEAST_EXPECT(ownerCount(env, alice) == 0); + env(did::setValid(alice), txflags(0x00010000), ter(temINVALID_FLAG)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // no fields + env(did::set(alice), ter(temEMPTY_DID)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // both empty fields + env(did::set(alice), did::uri(""), did::data(""), ter(temEMPTY_DID)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // Modifying a DID to become empty is checked in testSetModify + } + + void + testDeleteInvalid(FeatureBitset features) + { + testcase("Invalid Delete"); + + using namespace jtx; + using namespace std::chrono; + + Env env(*this); + Account const alice{"alice"}; + env.fund(XRP(5000), alice); + env.close(); + + //---------------------------------------------------------------------- + // preflight + + // invalid flags + BEAST_EXPECT(ownerCount(env, alice) == 0); + env(did::del(alice), txflags(0x00010000), ter(temINVALID_FLAG)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + //---------------------------------------------------------------------- + // doApply + + // DID doesn't exist + env(did::del(alice), ter(tecNO_ENTRY)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + } + + void + testSetValidInitial(FeatureBitset features) + { + testcase("Valid Initial Set"); + + using namespace jtx; + using namespace std::chrono; + + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const charlie{"charlie"}; + env.fund(XRP(5000), alice, bob, charlie); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(ownerCount(env, charlie) == 0); + + // only URI + env(did::set(alice), did::uri("uri")); + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // only Data + env(did::set(bob), did::data("data")); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + // both URI and Data + env(did::set(charlie), did::uri("uri"), did::data("data")); + BEAST_EXPECT(ownerCount(env, charlie) == 1); + } + + void + testSetModify(FeatureBitset features) + { + testcase("Modify DID with Set"); + + using namespace jtx; + using namespace std::chrono; + + Env env(*this); + Account const alice{"alice"}; + env.fund(XRP(5000), alice); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + auto const ar = env.le(alice); + + // Create DID + std::string const initialURI = "uri"; + { + env(did::set(alice), did::uri(initialURI)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(sleDID); + BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); + } + + // Try to delete URI, fails because no elements are set + { + env(did::set(alice), did::uri(""), ter(tecEMPTY_DID)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); + } + + // Set Data + std::string const initialData = "data"; + { + env(did::set(alice), did::data("data")); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], initialData)); + } + + // Remove URI + { + env(did::set(alice), did::uri("")); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], initialData)); + } + + // Remove Data + set URI + std::string const secondURI = "uri2"; + { + env(did::set(alice), did::uri(secondURI), did::data("")); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(checkVL((*sleDID)[sfURI], secondURI)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); + } + + // Remove URI + set Data + std::string const secondData = "data2"; + { + env(did::set(alice), did::uri(""), did::data(secondData)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], secondData)); + } + + // Delete DID + { + env(did::del(alice)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(!sleDID); + } + } + + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{supported_amendments()}; + testEnabled(all); + testAccountReserve(all); + testSetInvalid(all); + testDeleteInvalid(all); + testSetModify(all); + } +}; + +BEAST_DEFINE_TESTSUITE(DID, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 1f3636e4e39..d8866ec752d 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -581,7 +581,7 @@ class NFToken_test : public beast::unit_test::suite ter(temMALFORMED)); //---------------------------------------------------------------------- - // preflight + // preclaim // Non-existent issuer. env(token::mint(alice, 0u), From 2f4c2851206fa645e1ad5dfa1c94663f5d258935 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 17 Aug 2023 17:57:10 +0200 Subject: [PATCH 02/18] add RPC tests --- src/test/rpc/AccountObjects_test.cpp | 23 ++++++++++++- src/test/rpc/LedgerRPC_test.cpp | 51 ++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 7de5b73671e..58830d6260f 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -585,6 +585,7 @@ class AccountObjects_test : public beast::unit_test::suite BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::signer_list), 0)); BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::state), 0)); BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::ticket), 0)); + BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::did), 0)); // gw mints an NFT so we can find it. uint256 const nftID{token::getNextID(env, gw, 0u, tfTransferable)}; @@ -703,6 +704,26 @@ class AccountObjects_test : public beast::unit_test::suite BEAST_EXPECT( payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60); } + + { + // gw creates a DID that we can look for in the ledger. + Json::Value jvDID; + jvDID[jss::TransactionType] = jss::DIDSet; + jvDID[jss::Flags] = tfUniversal; + jvDID[jss::Account] = gw.human(); + jvDID[sfURI.jsonName] = strHex(std::string{"uri"}); + env(jvDID); + env.close(); + } + { + // Find the DID. + Json::Value const resp = acct_objs(gw, jss::did); + BEAST_EXPECT(acct_objs_is_size(resp, 1)); + + auto const& did = resp[jss::result][jss::account_objects][0u]; + BEAST_EXPECT(did[sfAccount.jsonName] == gw.human()); + BEAST_EXPECT(did[sfURI.jsonName] == strHex(std::string{"uri"})); + } // Make gw multisigning by adding a signerList. env(signers(gw, 6, {{alice, 7}})); env.close(); @@ -730,7 +751,7 @@ class AccountObjects_test : public beast::unit_test::suite auto const& ticket = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human()); BEAST_EXPECT(ticket[sfLedgerEntryType.jsonName] == jss::Ticket); - BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == 13); + BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == 14); } { // See how "deletion_blockers_only" handles gw's directory. diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 9c9a63005a7..4cd10de218f 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1212,6 +1212,56 @@ class LedgerRPC_test : public beast::unit_test::suite } } + void + testLedgerEntryDID() + { + testcase("ledger_entry Request DID"); + using namespace test::jtx; + using namespace std::literals::chrono_literals; + Env env{*this}; + Account const alice{"alice"}; + + env.fund(XRP(10000), alice); + env.close(); + + // Lambda to create a DID. + auto didCreate = [](test::jtx::Account const& account) { + Json::Value jv; + jv[jss::TransactionType] = jss::DIDSet; + jv[jss::Account] = account.human(); + jv[sfData.jsonName] = strHex(std::string{"data"}); + jv[sfURI.jsonName] = strHex(std::string{"uri"}); + return jv; + }; + + env(didCreate(alice)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + { + // Request the payment channel using its index. + Json::Value jvParams; + jvParams[jss::did] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfData.jsonName] == strHex(std::string{"data"})); + BEAST_EXPECT( + jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"})); + } + { + // Request an index that is not a payment channel. + Json::Value jvParams; + jvParams[jss::did] = env.master.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + void testLedgerEntryInvalidParams(unsigned int apiVersion) { @@ -1736,6 +1786,7 @@ class LedgerRPC_test : public beast::unit_test::suite testNoQueue(); testQueue(); testLedgerAccountsOption(); + testLedgerEntryDID(); // version specific tests for (auto testVersion = RPC::apiMinimumSupportedVersion; From 99b53beb144c66ed619563b94819f6a6deca8ca0 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 17 Aug 2023 18:10:19 +0200 Subject: [PATCH 03/18] respond to comments, handle max blob length --- src/ripple/app/tx/impl/DID.cpp | 33 ++++++++++++++++++--------------- src/ripple/protocol/Protocol.h | 6 ++++++ src/test/app/DID_test.cpp | 11 +++++++++++ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index 0d5e9279979..a70b6585290 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -57,6 +57,18 @@ DIDSet::preflight(PreflightContext const& ctx) ctx.tx.isFieldPresent(sfData) && ctx.tx[sfData].empty()) return temEMPTY_DID; + if (auto uri = ctx.tx[~sfURI]) + { + if (uri->length() > maxDIDURILength) + return temMALFORMED; + } + + if (auto data = ctx.tx[~sfData]) + { + if (data->length() > maxDIDDataLength) + return temMALFORMED; + } + return preflight2(ctx); } @@ -142,10 +154,7 @@ DIDSet::doApply() 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; + return addSLE(ctx_, sleDID, account_); } NotTEC @@ -181,13 +190,11 @@ DIDDelete::deleteSLE( beast::Journal j) { // Remove object from owner directory + if (!view.dirRemove( + keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true)) { - auto const page = (*sle)[sfOwnerNode]; - if (!view.dirRemove(keylet::ownerDir(owner), page, sle->key(), true)) - { - JLOG(j.fatal()) << "Unable to delete DID Token from owner."; - return tefBAD_LEDGER; - } + JLOG(j.fatal()) << "Unable to delete DID Token from owner."; + return tefBAD_LEDGER; } auto const sleOwner = view.peek(keylet::account(owner)); @@ -205,11 +212,7 @@ DIDDelete::doApply() 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; + return deleteSLE(ctx_, didKeylet, account_); } } // namespace ripple diff --git a/src/ripple/protocol/Protocol.h b/src/ripple/protocol/Protocol.h index 5df24271f68..dd4fb63708f 100644 --- a/src/ripple/protocol/Protocol.h +++ b/src/ripple/protocol/Protocol.h @@ -83,6 +83,12 @@ std::uint16_t constexpr maxTransferFee = 50000; /** The maximum length of a URI inside an NFT */ std::size_t constexpr maxTokenURILength = 256; +/** The maximum length of a Data element inside a DID */ +std::size_t constexpr maxDIDDataLength = 256; + +/** The maximum length of a URI inside a DID */ +std::size_t constexpr maxDIDURILength = 256; + /** The maximum length of a domain */ std::size_t constexpr maxDomainLength = 256; diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index 4f23fafed79..fafa4c665f7 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -225,6 +225,17 @@ struct DID_test : public beast::unit_test::suite env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); + // uri is too long + const std::string longString(257, 'a'); + env(did::set(alice), did::uri(longString), ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // data is too long + env(did::set(alice), did::data(longString), ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + // Modifying a DID to become empty is checked in testSetModify } From 5916f5f01a5e88dca7b122eff8024334239bddc1 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 17 Aug 2023 18:27:18 +0200 Subject: [PATCH 04/18] add AccountDelete test --- Builds/CMake/RippledCore.cmake | 1 + src/test/app/AccountDelete_test.cpp | 4 +- src/test/app/DID_test.cpp | 71 ------------------------ src/test/jtx.h | 1 + src/test/jtx/DID.h | 86 +++++++++++++++++++++++++++++ src/test/jtx/impl/DID.cpp | 67 ++++++++++++++++++++++ 6 files changed, 158 insertions(+), 72 deletions(-) create mode 100644 src/test/jtx/DID.h create mode 100644 src/test/jtx/impl/DID.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 486d16d89da..3db50358a5f 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -912,6 +912,7 @@ if (tests) src/test/jtx/impl/check.cpp src/test/jtx/impl/delivermin.cpp src/test/jtx/impl/deposit.cpp + src/test/jtx/impl/DID.cpp src/test/jtx/impl/envconfig.cpp src/test/jtx/impl/fee.cpp src/test/jtx/impl/flags.cpp diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index 2ec0b876a64..c52ca0cfbf7 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -148,13 +148,15 @@ class AccountDelete_test : public beast::unit_test::suite env.close(); // Give carol a deposit preauthorization, an offer, a ticket, - // and a signer list. Even with all that she's still deletable. + // a signer list, and a DID. Even with all that she's still deletable. env(deposit::auth(carol, becky)); std::uint32_t const carolOfferSeq{env.seq(carol)}; env(offer(carol, gw["USD"](51), XRP(51))); std::uint32_t const carolTicketSeq{env.seq(carol) + 1}; env(ticket::create(carol, 1)); env(signers(carol, 1, {{alice, 1}, {becky, 1}})); + env(signers(carol, 1, {{alice, 1}, {becky, 1}})); + env(did::setValid(carol)); // Deleting should fail with TOO_SOON, which is a relatively // cheap check compared to validating the contents of her directory. diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index fafa4c665f7..187b6e08c78 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -40,77 +40,6 @@ ownerCount(test::jtx::Env const& env, test::jtx::Account const& acct) return ret; } -namespace did { - -Json::Value -set(jtx::Account const& account) -{ - Json::Value jv; - jv[jss::TransactionType] = jss::DIDSet; - jv[jss::Account] = to_string(account.id()); - jv[jss::Flags] = tfUniversal; - return jv; -} - -Json::Value -setValid(jtx::Account const& account) -{ - Json::Value jv; - jv[jss::TransactionType] = jss::DIDSet; - jv[jss::Account] = to_string(account.id()); - jv[jss::Flags] = tfUniversal; - jv[sfURI.jsonName] = strHex(std::string{"uri"}); - return jv; -} - -/** Sets the optional URI on a DIDSet. */ -class uri -{ -private: - std::string uri_; - -public: - explicit uri(std::string const& u) : uri_(strHex(u)) - { - } - - void - operator()(jtx::Env&, jtx::JTx& jtx) const - { - jtx.jv[sfURI.jsonName] = uri_; - } -}; - -/** Sets the optional URI on a DIDSet. */ -class data -{ -private: - std::string data_; - -public: - explicit data(std::string const& u) : data_(strHex(u)) - { - } - - void - operator()(jtx::Env&, jtx::JTx& jtx) const - { - jtx.jv[sfData.jsonName] = data_; - } -}; - -Json::Value -del(jtx::Account const& account) -{ - Json::Value jv; - jv[jss::TransactionType] = jss::DIDDelete; - jv[jss::Account] = to_string(account.id()); - jv[jss::Flags] = tfUniversal; - return jv; -} - -} // namespace did - bool checkVL(Slice const& result, std::string expected) { diff --git a/src/test/jtx.h b/src/test/jtx.h index bcf51398d5c..45037e6434b 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include diff --git a/src/test/jtx/DID.h b/src/test/jtx/DID.h new file mode 100644 index 00000000000..75306335532 --- /dev/null +++ b/src/test/jtx/DID.h @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 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_TEST_JTX_DID_H_INCLUDED +#define RIPPLE_TEST_JTX_DID_H_INCLUDED + +#include +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +/** DID operations. */ +namespace did { + +Json::Value +set(jtx::Account const& account); + +Json::Value +setValid(jtx::Account const& account); + +/** Sets the optional URI on a DIDSet. */ +class uri +{ +private: + std::string uri_; + +public: + explicit uri(std::string const& u) : uri_(strHex(u)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const + { + jtx.jv[sfURI.jsonName] = uri_; + } +}; + +/** Sets the optional URI on a DIDSet. */ +class data +{ +private: + std::string data_; + +public: + explicit data(std::string const& u) : data_(strHex(u)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const + { + jtx.jv[sfData.jsonName] = data_; + } +}; + +Json::Value +del(jtx::Account const& account); + +} // namespace did + +} // namespace jtx + +} // namespace test +} // namespace ripple + +#endif diff --git a/src/test/jtx/impl/DID.cpp b/src/test/jtx/impl/DID.cpp new file mode 100644 index 00000000000..1c04ad74a1a --- /dev/null +++ b/src/test/jtx/impl/DID.cpp @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 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. +*/ +//============================================================================== + +#include +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +/** DID operations. */ +namespace did { + +Json::Value +set(jtx::Account const& account) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::DIDSet; + jv[jss::Account] = to_string(account.id()); + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +setValid(jtx::Account const& account) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::DIDSet; + jv[jss::Account] = to_string(account.id()); + jv[jss::Flags] = tfUniversal; + jv[sfURI.jsonName] = strHex(std::string{"uri"}); + return jv; +} + +Json::Value +del(jtx::Account const& account) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::DIDDelete; + jv[jss::Account] = to_string(account.id()); + jv[jss::Flags] = tfUniversal; + return jv; +} + +} // namespace did + +} // namespace jtx + +} // namespace test +} // namespace ripple From 7ee38f983e34c23449453884949cb43e869a03af Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 15 Sep 2023 16:38:08 -0400 Subject: [PATCH 05/18] update to latest version --- src/ripple/app/tx/impl/DID.cpp | 65 +++++++++-- src/ripple/app/tx/impl/DID.h | 3 + src/ripple/protocol/Protocol.h | 5 +- src/ripple/protocol/SField.h | 3 +- src/ripple/protocol/impl/LedgerFormats.cpp | 3 +- src/ripple/protocol/impl/SField.cpp | 3 +- src/ripple/protocol/impl/TxFormats.cpp | 3 +- src/test/app/DID_test.cpp | 124 +++++++++++++++++---- src/test/jtx/DID.h | 28 ++++- src/test/rpc/LedgerRPC_test.cpp | 5 +- 10 files changed, 197 insertions(+), 45 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index a70b6585290..c764b4d5af4 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. + Copyright (c) 2023 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 @@ -50,11 +50,13 @@ DIDSet::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - if (!ctx.tx.isFieldPresent(sfURI) && !ctx.tx.isFieldPresent(sfData)) + if (!ctx.tx.isFieldPresent(sfURI) && + !ctx.tx.isFieldPresent(sfDIDDocument) && + !ctx.tx.isFieldPresent(sfAttestation)) return temEMPTY_DID; if (ctx.tx.isFieldPresent(sfURI) && ctx.tx[sfURI].empty() && - ctx.tx.isFieldPresent(sfData) && ctx.tx[sfData].empty()) + ctx.tx.isFieldPresent(sfDIDDocument) && ctx.tx[sfDIDDocument].empty()) return temEMPTY_DID; if (auto uri = ctx.tx[~sfURI]) @@ -63,15 +65,37 @@ DIDSet::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (auto data = ctx.tx[~sfData]) + if (auto document = ctx.tx[~sfDIDDocument]) { - if (data->length() > maxDIDDataLength) + if (document->length() > maxDIDDocumentLength) + return temMALFORMED; + } + + if (auto attestation = ctx.tx[~sfAttestation]) + { + if (attestation->length() > maxDIDAttestationLength) return temMALFORMED; } return preflight2(ctx); } +TER +DIDSet::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx[sfAccount]; + Keylet const didKeylet = keylet::did(account); + + if (!ctx.view.read(didKeylet)) + { + if (!ctx.tx.isFieldPresent(sfURI) && + !ctx.tx.isFieldPresent(sfDIDDocument)) + return tecEMPTY_DID; + } + + return tesSUCCESS; +} + TER addSLE( ApplyContext& ctx, @@ -127,18 +151,31 @@ DIDSet::doApply() (*sleDID)[sfURI] = *uri; } } - if (auto const data = ctx_.tx[~sfData]) + if (auto const document = ctx_.tx[~sfDIDDocument]) + { + if (document->empty()) + { + sleDID->makeFieldAbsent(sfDIDDocument); + } + else + { + (*sleDID)[sfDIDDocument] = *document; + } + } + + if (auto const attestation = ctx_.tx[~sfAttestation]) { - if (data->empty()) + if (attestation->empty()) { - sleDID->makeFieldAbsent(sfData); + sleDID->makeFieldAbsent(sfAttestation); } else { - (*sleDID)[sfData] = *data; + (*sleDID)[sfAttestation] = *attestation; } } - if (!sleDID->isFieldPresent(sfURI) && !sleDID->isFieldPresent(sfData)) + if (!sleDID->isFieldPresent(sfURI) && + !sleDID->isFieldPresent(sfDIDDocument)) { return tecEMPTY_DID; } @@ -151,8 +188,12 @@ DIDSet::doApply() (*sleDID)[sfAccount] = account_; if (auto const uri = ctx_.tx[~sfURI]; uri.has_value() && !uri->empty()) (*sleDID)[sfURI] = uri.value(); - if (auto const data = ctx_.tx[~sfData]; data.has_value() && !data->empty()) - (*sleDID)[sfData] = data.value(); + if (auto const document = ctx_.tx[~sfDIDDocument]; + document.has_value() && !document->empty()) + (*sleDID)[sfDIDDocument] = document.value(); + if (auto const attestation = ctx_.tx[~sfAttestation]; + attestation.has_value() && !attestation->empty()) + (*sleDID)[sfAttestation] = attestation.value(); return addSLE(ctx_, sleDID, account_); } diff --git a/src/ripple/app/tx/impl/DID.h b/src/ripple/app/tx/impl/DID.h index 13d5a261542..9792bc29942 100644 --- a/src/ripple/app/tx/impl/DID.h +++ b/src/ripple/app/tx/impl/DID.h @@ -36,6 +36,9 @@ class DIDSet : public Transactor static NotTEC preflight(PreflightContext const& ctx); + static TER + preclaim(PreclaimContext const& ctx); + TER doApply() override; }; diff --git a/src/ripple/protocol/Protocol.h b/src/ripple/protocol/Protocol.h index dd4fb63708f..327b144e115 100644 --- a/src/ripple/protocol/Protocol.h +++ b/src/ripple/protocol/Protocol.h @@ -84,11 +84,14 @@ std::uint16_t constexpr maxTransferFee = 50000; std::size_t constexpr maxTokenURILength = 256; /** The maximum length of a Data element inside a DID */ -std::size_t constexpr maxDIDDataLength = 256; +std::size_t constexpr maxDIDDocumentLength = 256; /** The maximum length of a URI inside a DID */ std::size_t constexpr maxDIDURILength = 256; +/** The maximum length of an Attestation inside a DID */ +std::size_t constexpr maxDIDAttestationLength = 256; + /** The maximum length of a domain */ std::size_t constexpr maxDomainLength = 256; diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 4723409363e..8ca1466474d 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -515,7 +515,8 @@ extern SF_VL const sfCreateCode; extern SF_VL const sfMemoType; extern SF_VL const sfMemoData; extern SF_VL const sfMemoFormat; -extern SF_VL const sfData; +extern SF_VL const sfDIDDocument; +extern SF_VL const sfAttestation; // variable length (uncommon) extern SF_VL const sfFulfillment; diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index a172d680d3c..2a092d28005 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -285,8 +285,9 @@ LedgerFormats::LedgerFormats() ltDID, { {sfAccount, soeREQUIRED}, + {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, - {sfData, soeOPTIONAL}, + {sfAttestation, soeOPTIONAL}, {sfOwnerNode, soeREQUIRED}, {sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED} diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 2a468578d91..880d6e29c3f 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -281,7 +281,6 @@ CONSTRUCT_TYPED_SFIELD(sfCreateCode, "CreateCode", VL, CONSTRUCT_TYPED_SFIELD(sfMemoType, "MemoType", VL, 12); CONSTRUCT_TYPED_SFIELD(sfMemoData, "MemoData", VL, 13); CONSTRUCT_TYPED_SFIELD(sfMemoFormat, "MemoFormat", VL, 14); -CONSTRUCT_TYPED_SFIELD(sfData, "Data", VL, 15); // variable length (uncommon) CONSTRUCT_TYPED_SFIELD(sfFulfillment, "Fulfillment", VL, 16); @@ -294,6 +293,8 @@ CONSTRUCT_TYPED_SFIELD(sfHookStateData, "HookStateData", VL, CONSTRUCT_TYPED_SFIELD(sfHookReturnString, "HookReturnString", VL, 23); CONSTRUCT_TYPED_SFIELD(sfHookParameterName, "HookParameterName", VL, 24); CONSTRUCT_TYPED_SFIELD(sfHookParameterValue, "HookParameterValue", VL, 25); +CONSTRUCT_TYPED_SFIELD(sfDIDDocument, "DIDDocument", VL, 26); +CONSTRUCT_TYPED_SFIELD(sfAttestation, "Attestation", VL, 27); // account CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1); diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index a295f4956fc..66f505b256f 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -398,8 +398,9 @@ TxFormats::TxFormats() add(jss::DIDSet, ttDID_SET, { + {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, - {sfData, soeOPTIONAL}, + {sfAttestation, soeOPTIONAL}, {sfTicketSequence, soeOPTIONAL}, }, commonFields); diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index 187b6e08c78..d54ce7b75ec 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -149,8 +149,25 @@ struct DID_test : public beast::unit_test::suite env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); + // only attestation + env(did::set(alice), did::attestation("attest"), ter(tecEMPTY_DID)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + // both empty fields - env(did::set(alice), did::uri(""), did::data(""), ter(temEMPTY_DID)); + env(did::set(alice), + did::uri(""), + did::document(""), + ter(temEMPTY_DID)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // both empty fields with attestation + env(did::set(alice), + did::uri(""), + did::document(""), + did::attestation("attest"), + ter(temEMPTY_DID)); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -160,8 +177,16 @@ struct DID_test : public beast::unit_test::suite env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); - // data is too long - env(did::set(alice), did::data(longString), ter(temMALFORMED)); + // document is too long + env(did::set(alice), did::document(longString), ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // attestation is too long + env(did::set(alice), + did::document("data"), + did::attestation(longString), + ter(temMALFORMED)); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -211,7 +236,10 @@ struct DID_test : public beast::unit_test::suite Account const alice{"alice"}; Account const bob{"bob"}; Account const charlie{"charlie"}; - env.fund(XRP(5000), alice, bob, charlie); + Account const dave{"dave"}; + Account const edna{"edna"}; + Account const francis{"francis"}; + env.fund(XRP(5000), alice, bob, charlie, dave, edna, francis); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); BEAST_EXPECT(ownerCount(env, bob) == 0); @@ -221,13 +249,28 @@ struct DID_test : public beast::unit_test::suite env(did::set(alice), did::uri("uri")); BEAST_EXPECT(ownerCount(env, alice) == 1); - // only Data - env(did::set(bob), did::data("data")); + // only URI, plus attestation + env(did::set(bob), did::uri("uri"), did::attestation("attest")); BEAST_EXPECT(ownerCount(env, bob) == 1); - // both URI and Data - env(did::set(charlie), did::uri("uri"), did::data("data")); + // only DIDDocument + env(did::set(charlie), did::document("data")); BEAST_EXPECT(ownerCount(env, charlie) == 1); + + // only DIDDocument, plus attestation + env(did::set(dave), did::document("data"), did::attestation("attest")); + BEAST_EXPECT(ownerCount(env, dave) == 1); + + // both URI and DIDDocument + env(did::set(edna), did::uri("uri"), did::document("data")); + BEAST_EXPECT(ownerCount(env, edna) == 1); + + // both URI and DIDDocument, plus Attestation + env(did::set(francis), + did::uri("uri"), + did::document("data"), + did::attestation("attest")); + BEAST_EXPECT(ownerCount(env, francis) == 1); } void @@ -253,7 +296,8 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(sleDID); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); - BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfDIDDocument)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); } // Try to delete URI, fails because no elements are set @@ -262,17 +306,44 @@ struct DID_test : public beast::unit_test::suite BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); - BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfDIDDocument)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); + } + + // Set DIDDocument + std::string const initialDocument = "data"; + { + env(did::set(alice), did::document(initialDocument)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); + BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); } - // Set Data - std::string const initialData = "data"; + // Set Attestation + std::string const initialAttestation = "attest"; { - env(did::set(alice), did::data("data")); + env(did::set(alice), did::attestation(initialAttestation)); BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); - BEAST_EXPECT(checkVL((*sleDID)[sfData], initialData)); + BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); + BEAST_EXPECT(checkVL((*sleDID)[sfAttestation], initialAttestation)); + } + + // Try to delete URI/DIDDocument, fails because no elements are set + // (other than attestation) + { + env(did::set(alice), + did::document(""), + did::uri(""), + ter(temEMPTY_DID)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); + BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); + BEAST_EXPECT(checkVL((*sleDID)[sfAttestation], initialAttestation)); } // Remove URI @@ -281,27 +352,38 @@ struct DID_test : public beast::unit_test::suite BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); - BEAST_EXPECT(checkVL((*sleDID)[sfData], initialData)); + BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); + BEAST_EXPECT(checkVL((*sleDID)[sfAttestation], initialAttestation)); + } + + // Remove Attestation + { + env(did::set(alice), did::attestation("")); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); + BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); } // Remove Data + set URI std::string const secondURI = "uri2"; { - env(did::set(alice), did::uri(secondURI), did::data("")); + env(did::set(alice), did::uri(secondURI), did::document("")); BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], secondURI)); - BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfDIDDocument)); } - // Remove URI + set Data - std::string const secondData = "data2"; + // Remove URI + set Document + std::string const secondDocument = "data2"; { - env(did::set(alice), did::uri(""), did::data(secondData)); + env(did::set(alice), did::uri(""), did::document(secondDocument)); BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); - BEAST_EXPECT(checkVL((*sleDID)[sfData], secondData)); + BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], secondDocument)); } // Delete DID diff --git a/src/test/jtx/DID.h b/src/test/jtx/DID.h index 75306335532..873637999ec 100644 --- a/src/test/jtx/DID.h +++ b/src/test/jtx/DID.h @@ -37,6 +37,24 @@ set(jtx::Account const& account); Json::Value setValid(jtx::Account const& account); +/** Sets the optional DIDDocument on a DIDSet. */ +class document +{ +private: + std::string document_; + +public: + explicit document(std::string const& u) : document_(strHex(u)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const + { + jtx.jv[sfDIDDocument.jsonName] = document_; + } +}; + /** Sets the optional URI on a DIDSet. */ class uri { @@ -55,21 +73,21 @@ class uri } }; -/** Sets the optional URI on a DIDSet. */ -class data +/** Sets the optional Attestation on a DIDSet. */ +class attestation { private: - std::string data_; + std::string attestation_; public: - explicit data(std::string const& u) : data_(strHex(u)) + explicit attestation(std::string const& u) : attestation_(strHex(u)) { } void operator()(jtx::Env&, jtx::JTx& jtx) const { - jtx.jv[sfData.jsonName] = data_; + jtx.jv[sfAttestation.jsonName] = attestation_; } }; diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 4cd10de218f..6cf7a755491 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1229,7 +1229,7 @@ class LedgerRPC_test : public beast::unit_test::suite Json::Value jv; jv[jss::TransactionType] = jss::DIDSet; jv[jss::Account] = account.human(); - jv[sfData.jsonName] = strHex(std::string{"data"}); + jv[sfDIDDocument.jsonName] = strHex(std::string{"data"}); jv[sfURI.jsonName] = strHex(std::string{"uri"}); return jv; }; @@ -1247,7 +1247,8 @@ class LedgerRPC_test : public beast::unit_test::suite Json::Value const jrr = env.rpc( "json", "ledger_entry", to_string(jvParams))[jss::result]; BEAST_EXPECT( - jrr[jss::node][sfData.jsonName] == strHex(std::string{"data"})); + jrr[jss::node][sfDIDDocument.jsonName] == + strHex(std::string{"data"})); BEAST_EXPECT( jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"})); } From 298077f443ad9e0a0a8ee3045f2dafd0bd2a590b Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 26 Sep 2023 13:29:45 -0400 Subject: [PATCH 06/18] respond to comments --- src/ripple/app/tx/impl/DID.cpp | 90 ++++++++++------------------- src/test/app/AccountDelete_test.cpp | 1 - src/test/rpc/LedgerRPC_test.cpp | 4 +- 3 files changed, 34 insertions(+), 61 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index c764b4d5af4..304afda032f 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -59,23 +59,16 @@ DIDSet::preflight(PreflightContext const& ctx) ctx.tx.isFieldPresent(sfDIDDocument) && ctx.tx[sfDIDDocument].empty()) return temEMPTY_DID; - if (auto uri = ctx.tx[~sfURI]) - { - if (uri->length() > maxDIDURILength) - return temMALFORMED; - } + auto isTooLong = [&](auto const& sField, std::size_t length) -> bool { + if (auto field = ctx.tx[~sField]) + return field->length() > length; + return false; + }; - if (auto document = ctx.tx[~sfDIDDocument]) - { - if (document->length() > maxDIDDocumentLength) - return temMALFORMED; - } - - if (auto attestation = ctx.tx[~sfAttestation]) - { - if (attestation->length() > maxDIDAttestationLength) - return temMALFORMED; - } + if (isTooLong(sfURI, maxDIDURILength) || + isTooLong(sfDIDDocument, maxDIDDocumentLength) || + isTooLong(sfAttestation, maxDIDAttestationLength)) + return temMALFORMED; return preflight2(ctx); } @@ -83,14 +76,12 @@ DIDSet::preflight(PreflightContext const& ctx) TER DIDSet::preclaim(PreclaimContext const& ctx) { - AccountID const account = ctx.tx[sfAccount]; - Keylet const didKeylet = keylet::did(account); - - if (!ctx.view.read(didKeylet)) + if (!ctx.view.read(keylet::did(ctx.tx[sfAccount])) && + !ctx.tx.isFieldPresent(sfURI) && !ctx.tx.isFieldPresent(sfDIDDocument)) { - if (!ctx.tx.isFieldPresent(sfURI) && - !ctx.tx.isFieldPresent(sfDIDDocument)) - return tecEMPTY_DID; + // Need either the URI or document if the account doesn't already have a + // DID + return tecEMPTY_DID; } return tesSUCCESS; @@ -140,40 +131,23 @@ DIDSet::doApply() Keylet const didKeylet = keylet::did(account_); if (auto const sleDID = ctx_.view().peek(didKeylet)) { - if (auto const uri = ctx_.tx[~sfURI]) - { - if (uri->empty()) + auto update = [&](auto const& sField) { + if (auto const field = ctx_.tx[~sField]) { - sleDID->makeFieldAbsent(sfURI); + if (field->empty()) + { + sleDID->makeFieldAbsent(sField); + } + else + { + (*sleDID)[sField] = *field; + } } - else - { - (*sleDID)[sfURI] = *uri; - } - } - if (auto const document = ctx_.tx[~sfDIDDocument]) - { - if (document->empty()) - { - sleDID->makeFieldAbsent(sfDIDDocument); - } - else - { - (*sleDID)[sfDIDDocument] = *document; - } - } + }; + update(sfURI); + update(sfDIDDocument); + update(sfAttestation); - if (auto const attestation = ctx_.tx[~sfAttestation]) - { - if (attestation->empty()) - { - sleDID->makeFieldAbsent(sfAttestation); - } - else - { - (*sleDID)[sfAttestation] = *attestation; - } - } if (!sleDID->isFieldPresent(sfURI) && !sleDID->isFieldPresent(sfDIDDocument)) { @@ -239,6 +213,9 @@ DIDDelete::deleteSLE( } auto const sleOwner = view.peek(keylet::account(owner)); + if (!sleOwner) + return tecINTERNAL; + adjustOwnerCount(view, sleOwner, -1, j); view.update(sleOwner); @@ -250,10 +227,7 @@ DIDDelete::deleteSLE( TER DIDDelete::doApply() { - AccountID const account = ctx_.tx[sfAccount]; - auto const didKeylet = keylet::did(account); - - return deleteSLE(ctx_, didKeylet, account_); + return deleteSLE(ctx_, keylet::did(account_), account_); } } // namespace ripple diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index c52ca0cfbf7..fbd631f444a 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -155,7 +155,6 @@ class AccountDelete_test : public beast::unit_test::suite std::uint32_t const carolTicketSeq{env.seq(carol) + 1}; env(ticket::create(carol, 1)); env(signers(carol, 1, {{alice, 1}, {becky, 1}})); - env(signers(carol, 1, {{alice, 1}, {becky, 1}})); env(did::setValid(carol)); // Deleting should fail with TOO_SOON, which is a relatively diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index a003ec8446d..2b4d8527a64 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1558,7 +1558,7 @@ class LedgerRPC_test : public beast::unit_test::suite std::string const ledgerHash{to_string(env.closed()->info().hash)}; { - // Request the payment channel using its index. + // Request the DID using its index. Json::Value jvParams; jvParams[jss::did] = alice.human(); jvParams[jss::ledger_hash] = ledgerHash; @@ -1571,7 +1571,7 @@ class LedgerRPC_test : public beast::unit_test::suite jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"})); } { - // Request an index that is not a payment channel. + // Request an index that is not a DID. Json::Value jvParams; jvParams[jss::did] = env.master.human(); jvParams[jss::ledger_hash] = ledgerHash; From 04066ae42e9c8d7de59742d984b4202e4d705d54 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 26 Sep 2023 13:33:34 -0400 Subject: [PATCH 07/18] add another simplification lambda --- src/ripple/app/tx/impl/DID.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index 304afda032f..a6a5be753a8 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -160,14 +160,16 @@ DIDSet::doApply() // Create new ledger object otherwise auto const sleDID = std::make_shared(didKeylet); (*sleDID)[sfAccount] = account_; - if (auto const uri = ctx_.tx[~sfURI]; uri.has_value() && !uri->empty()) - (*sleDID)[sfURI] = uri.value(); - if (auto const document = ctx_.tx[~sfDIDDocument]; - document.has_value() && !document->empty()) - (*sleDID)[sfDIDDocument] = document.value(); - if (auto const attestation = ctx_.tx[~sfAttestation]; - attestation.has_value() && !attestation->empty()) - (*sleDID)[sfAttestation] = attestation.value(); + + auto set = [&](auto const& sField) { + if (auto const field = ctx_.tx[~sField]; + field.has_value() && !field->empty()) + (*sleDID)[sField] = field.value(); + }; + + set(sfURI); + set(sfDIDDocument); + set(sfAttestation); return addSLE(ctx_, sleDID, account_); } From 7609a66f0f4247238133ee8d81bdde0116e75e1c Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 3 Oct 2023 17:37:40 -0400 Subject: [PATCH 08/18] rename sfAttestation -> sfData --- src/ripple/app/tx/impl/DID.cpp | 9 +++---- src/ripple/protocol/SField.h | 2 +- src/ripple/protocol/impl/LedgerFormats.cpp | 2 +- src/ripple/protocol/impl/SField.cpp | 2 +- src/ripple/protocol/impl/TxFormats.cpp | 2 +- src/test/app/DID_test.cpp | 30 +++++++++++----------- src/test/jtx/DID.h | 8 +++--- 7 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index a6a5be753a8..c2f469c8c78 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -51,8 +51,7 @@ DIDSet::preflight(PreflightContext const& ctx) return ret; if (!ctx.tx.isFieldPresent(sfURI) && - !ctx.tx.isFieldPresent(sfDIDDocument) && - !ctx.tx.isFieldPresent(sfAttestation)) + !ctx.tx.isFieldPresent(sfDIDDocument) && !ctx.tx.isFieldPresent(sfData)) return temEMPTY_DID; if (ctx.tx.isFieldPresent(sfURI) && ctx.tx[sfURI].empty() && @@ -67,7 +66,7 @@ DIDSet::preflight(PreflightContext const& ctx) if (isTooLong(sfURI, maxDIDURILength) || isTooLong(sfDIDDocument, maxDIDDocumentLength) || - isTooLong(sfAttestation, maxDIDAttestationLength)) + isTooLong(sfData, maxDIDAttestationLength)) return temMALFORMED; return preflight2(ctx); @@ -146,7 +145,7 @@ DIDSet::doApply() }; update(sfURI); update(sfDIDDocument); - update(sfAttestation); + update(sfData); if (!sleDID->isFieldPresent(sfURI) && !sleDID->isFieldPresent(sfDIDDocument)) @@ -169,7 +168,7 @@ DIDSet::doApply() set(sfURI); set(sfDIDDocument); - set(sfAttestation); + set(sfData); return addSLE(ctx_, sleDID, account_); } diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 3863af33cc0..fe045d001d5 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -525,7 +525,7 @@ extern SF_VL const sfMemoType; extern SF_VL const sfMemoData; extern SF_VL const sfMemoFormat; extern SF_VL const sfDIDDocument; -extern SF_VL const sfAttestation; +extern SF_VL const sfData; // variable length (uncommon) extern SF_VL const sfFulfillment; diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 86c385ffe56..729ddc1c7bc 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -333,7 +333,7 @@ LedgerFormats::LedgerFormats() {sfAccount, soeREQUIRED}, {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, - {sfAttestation, soeOPTIONAL}, + {sfData, soeOPTIONAL}, {sfOwnerNode, soeREQUIRED}, {sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED} diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 35da3145dab..027c8ffb9c5 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -299,7 +299,7 @@ CONSTRUCT_TYPED_SFIELD(sfHookReturnString, "HookReturnString", VL, CONSTRUCT_TYPED_SFIELD(sfHookParameterName, "HookParameterName", VL, 24); CONSTRUCT_TYPED_SFIELD(sfHookParameterValue, "HookParameterValue", VL, 25); CONSTRUCT_TYPED_SFIELD(sfDIDDocument, "DIDDocument", VL, 26); -CONSTRUCT_TYPED_SFIELD(sfAttestation, "Attestation", VL, 27); +CONSTRUCT_TYPED_SFIELD(sfData, "Data", VL, 27); // account CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1); diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 5f9f62c0060..eb872300271 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -494,7 +494,7 @@ TxFormats::TxFormats() { {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, - {sfAttestation, soeOPTIONAL}, + {sfData, soeOPTIONAL}, }, commonFields); diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index e0db56deb6e..ed5bd0f81c6 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -150,7 +150,7 @@ struct DID_test : public beast::unit_test::suite BEAST_EXPECT(ownerCount(env, alice) == 0); // only attestation - env(did::set(alice), did::attestation("attest"), ter(tecEMPTY_DID)); + env(did::set(alice), did::data("attest"), ter(tecEMPTY_DID)); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -166,7 +166,7 @@ struct DID_test : public beast::unit_test::suite env(did::set(alice), did::uri(""), did::document(""), - did::attestation("attest"), + did::data("attest"), ter(temEMPTY_DID)); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -185,7 +185,7 @@ struct DID_test : public beast::unit_test::suite // attestation is too long env(did::set(alice), did::document("data"), - did::attestation(longString), + did::data(longString), ter(temMALFORMED)); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -250,7 +250,7 @@ struct DID_test : public beast::unit_test::suite BEAST_EXPECT(ownerCount(env, alice) == 1); // only URI, plus attestation - env(did::set(bob), did::uri("uri"), did::attestation("attest")); + env(did::set(bob), did::uri("uri"), did::data("attest")); BEAST_EXPECT(ownerCount(env, bob) == 1); // only DIDDocument @@ -258,7 +258,7 @@ struct DID_test : public beast::unit_test::suite BEAST_EXPECT(ownerCount(env, charlie) == 1); // only DIDDocument, plus attestation - env(did::set(dave), did::document("data"), did::attestation("attest")); + env(did::set(dave), did::document("data"), did::data("attest")); BEAST_EXPECT(ownerCount(env, dave) == 1); // both URI and DIDDocument @@ -269,7 +269,7 @@ struct DID_test : public beast::unit_test::suite env(did::set(francis), did::uri("uri"), did::document("data"), - did::attestation("attest")); + did::data("attest")); BEAST_EXPECT(ownerCount(env, francis) == 1); } @@ -297,7 +297,7 @@ struct DID_test : public beast::unit_test::suite BEAST_EXPECT(sleDID); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); BEAST_EXPECT(!sleDID->isFieldPresent(sfDIDDocument)); - BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); } // Try to delete URI, fails because no elements are set @@ -307,7 +307,7 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); BEAST_EXPECT(!sleDID->isFieldPresent(sfDIDDocument)); - BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); } // Set DIDDocument @@ -318,18 +318,18 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); } // Set Attestation std::string const initialAttestation = "attest"; { - env(did::set(alice), did::attestation(initialAttestation)); + env(did::set(alice), did::data(initialAttestation)); BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(checkVL((*sleDID)[sfAttestation], initialAttestation)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], initialAttestation)); } // Try to delete URI/DIDDocument, fails because no elements are set @@ -343,7 +343,7 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(checkVL((*sleDID)[sfAttestation], initialAttestation)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], initialAttestation)); } // Remove URI @@ -353,17 +353,17 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(checkVL((*sleDID)[sfAttestation], initialAttestation)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], initialAttestation)); } // Remove Attestation { - env(did::set(alice), did::attestation("")); + env(did::set(alice), did::data("")); BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(!sleDID->isFieldPresent(sfAttestation)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); } // Remove Data + set URI diff --git a/src/test/jtx/DID.h b/src/test/jtx/DID.h index 873637999ec..0cffb60e527 100644 --- a/src/test/jtx/DID.h +++ b/src/test/jtx/DID.h @@ -74,20 +74,20 @@ class uri }; /** Sets the optional Attestation on a DIDSet. */ -class attestation +class data { private: - std::string attestation_; + std::string data_; public: - explicit attestation(std::string const& u) : attestation_(strHex(u)) + explicit data(std::string const& u) : data_(strHex(u)) { } void operator()(jtx::Env&, jtx::JTx& jtx) const { - jtx.jv[sfAttestation.jsonName] = attestation_; + jtx.jv[sfData.jsonName] = data_; } }; From 47059087a2308a0ce1d9032c82419ed2f0992072 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 12:41:44 -0400 Subject: [PATCH 09/18] rename DID -> did in test --- src/test/jtx.h | 2 +- src/test/jtx/{DID.h => did.h} | 0 src/test/jtx/impl/{DID.cpp => did.cpp} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/test/jtx/{DID.h => did.h} (100%) rename src/test/jtx/impl/{DID.cpp => did.cpp} (98%) diff --git a/src/test/jtx.h b/src/test/jtx.h index 01b7426b876..03bbf154e63 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -36,6 +35,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/DID.h b/src/test/jtx/did.h similarity index 100% rename from src/test/jtx/DID.h rename to src/test/jtx/did.h diff --git a/src/test/jtx/impl/DID.cpp b/src/test/jtx/impl/did.cpp similarity index 98% rename from src/test/jtx/impl/DID.cpp rename to src/test/jtx/impl/did.cpp index 1c04ad74a1a..94dfcc32754 100644 --- a/src/test/jtx/impl/DID.cpp +++ b/src/test/jtx/impl/did.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include namespace ripple { namespace test { From f347bae57f9aac9417e082dc83df6f70c5c87af5 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 13:01:41 -0400 Subject: [PATCH 10/18] make test case names more descriptive --- src/test/app/DID_test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index ed5bd0f81c6..34ed06b5463 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -53,7 +53,7 @@ struct DID_test : public beast::unit_test::suite void testEnabled(FeatureBitset features) { - testcase("Enabled"); + testcase("featureDID Enabled"); using namespace jtx; // If the DID amendment is not enabled, you should not be able @@ -76,7 +76,7 @@ struct DID_test : public beast::unit_test::suite testAccountReserve(FeatureBitset features) { // Verify that the reserve behaves as expected for minting. - testcase("Account reserve"); + testcase("DID Account Reserve"); using namespace test::jtx; @@ -125,7 +125,7 @@ struct DID_test : public beast::unit_test::suite void testSetInvalid(FeatureBitset features) { - testcase("Invalid Set"); + testcase("Invalid DIDSet"); using namespace jtx; using namespace std::chrono; @@ -196,7 +196,7 @@ struct DID_test : public beast::unit_test::suite void testDeleteInvalid(FeatureBitset features) { - testcase("Invalid Delete"); + testcase("Invalid DIDDelete"); using namespace jtx; using namespace std::chrono; @@ -227,7 +227,7 @@ struct DID_test : public beast::unit_test::suite void testSetValidInitial(FeatureBitset features) { - testcase("Valid Initial Set"); + testcase("Valid Initial DIDSet"); using namespace jtx; using namespace std::chrono; @@ -276,7 +276,7 @@ struct DID_test : public beast::unit_test::suite void testSetModify(FeatureBitset features) { - testcase("Modify DID with Set"); + testcase("Modify DID with DIDSet"); using namespace jtx; using namespace std::chrono; From 6bda30b852d448b4b3df2d534b6441ed0af92663 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 12:05:17 -0400 Subject: [PATCH 11/18] fix minor issues --- src/ripple/app/tx/impl/DID.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index c2f469c8c78..46655b69d6b 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -75,7 +75,7 @@ DIDSet::preflight(PreflightContext const& ctx) TER DIDSet::preclaim(PreclaimContext const& ctx) { - if (!ctx.view.read(keylet::did(ctx.tx[sfAccount])) && + if (!ctx.view.exists(keylet::did(ctx.tx[sfAccount])) && !ctx.tx.isFieldPresent(sfURI) && !ctx.tx.isFieldPresent(sfDIDDocument)) { // Need either the URI or document if the account doesn't already have a @@ -161,9 +161,8 @@ DIDSet::doApply() (*sleDID)[sfAccount] = account_; auto set = [&](auto const& sField) { - if (auto const field = ctx_.tx[~sField]; - field.has_value() && !field->empty()) - (*sleDID)[sField] = field.value(); + if (auto const field = ctx_.tx[~sField]; field && !field->empty()) + (*sleDID)[sField] = *field; }; set(sfURI); From 31117f7fccebe37a4faf47d1a840448b8b86e660 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 15:29:35 -0400 Subject: [PATCH 12/18] move helper functions to View.h --- src/ripple/app/tx/impl/DID.cpp | 79 +--------------------- src/ripple/app/tx/impl/DID.h | 10 --- src/ripple/app/tx/impl/DeleteAccount.cpp | 2 +- src/ripple/ledger/View.h | 28 ++++++++ src/ripple/ledger/impl/View.cpp | 84 ++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 87 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index 46655b69d6b..c533746cc3e 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -86,43 +86,6 @@ DIDSet::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -TER -addSLE( - ApplyContext& ctx, - std::shared_ptr 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() { @@ -169,7 +132,7 @@ DIDSet::doApply() set(sfDIDDocument); set(sfData); - return addSLE(ctx_, sleDID, account_); + return addSLE(ctx_.view(), sleDID, account_, ctx_.journal); } NotTEC @@ -187,47 +150,11 @@ DIDDelete::preflight(PreflightContext const& ctx) return preflight2(ctx); } -TER -DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner) -{ - 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, - AccountID const owner, - beast::Journal j) -{ - // Remove object from owner directory - if (!view.dirRemove( - keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true)) - { - JLOG(j.fatal()) << "Unable to delete DID Token from owner."; - return tefBAD_LEDGER; - } - - auto const sleOwner = view.peek(keylet::account(owner)); - if (!sleOwner) - return tecINTERNAL; - - adjustOwnerCount(view, sleOwner, -1, j); - view.update(sleOwner); - - // Remove object from ledger - view.erase(sle); - return tesSUCCESS; -} - TER DIDDelete::doApply() { - return deleteSLE(ctx_, keylet::did(account_), account_); + return deleteSLE( + ctx_.view(), keylet::did(account_), account_, ctx_.journal); } } // namespace ripple diff --git a/src/ripple/app/tx/impl/DID.h b/src/ripple/app/tx/impl/DID.h index 9792bc29942..807b6863368 100644 --- a/src/ripple/app/tx/impl/DID.h +++ b/src/ripple/app/tx/impl/DID.h @@ -57,16 +57,6 @@ class DIDDelete : public Transactor 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, - AccountID const owner, - beast::Journal j); - TER doApply() override; }; diff --git a/src/ripple/app/tx/impl/DeleteAccount.cpp b/src/ripple/app/tx/impl/DeleteAccount.cpp index 49b645e31d9..c8191e1a42d 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.cpp +++ b/src/ripple/app/tx/impl/DeleteAccount.cpp @@ -143,7 +143,7 @@ removeDIDFromLedger( std::shared_ptr const& sleDel, beast::Journal j) { - return DIDDelete::deleteSLE(view, sleDel, account, j); + return deleteSLE(view, sleDel, account, j); } // Return nullptr if the LedgerEntryType represents an obligation that can't diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 5680114a79c..b5978805a75 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_LEDGER_VIEW_H_INCLUDED #define RIPPLE_LEDGER_VIEW_H_INCLUDED +#include #include #include #include @@ -399,6 +400,33 @@ trustDelete( TER offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j); +/** + * This is a helper function that adds a SLE to an owner's directory. + */ +TER +addSLE( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const& owner, + beast::Journal j); + +/** + * This is a helper function that deletes a SLE from an owner's directory. + */ +TER +deleteSLE( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const owner, + beast::Journal j); + +TER +deleteSLE( + ApplyView& view, + Keylet keylet, + AccountID const owner, + beast::Journal j); + //------------------------------------------------------------------------------ // diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 5050e8764e9..8144b40df33 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -942,6 +942,90 @@ offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j) return tesSUCCESS; } +/** + * This is a helper function that adds a SLE to an owner's directory. + */ +TER +addSLE( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const& owner, + beast::Journal j) +{ + auto const sleAccount = 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 = + view.fees().accountReserve((*sleAccount)[sfOwnerCount] + 1); + + if (balance < reserve) + return tecINSUFFICIENT_RESERVE; + } + + // Add ledger object to ledger + view.insert(sle); + + // Add ledger object to owner's page + { + auto page = view.dirInsert( + keylet::ownerDir(owner), sle->key(), describeOwnerDir(owner)); + if (!page) + return tecDIR_FULL; + (*sle)[sfOwnerNode] = *page; + } + adjustOwnerCount(view, sleAccount, 1, j); + view.update(sleAccount); + + return tesSUCCESS; +} + +/** + * This is a helper function that deletes a SLE from an owner's directory. + */ +TER +deleteSLE( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const owner, + beast::Journal j) +{ + if (!sle) + return tecNO_ENTRY; + + // Remove object from owner directory + if (!view.dirRemove( + keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true)) + { + JLOG(j.fatal()) << "Unable to delete DID Token from owner."; + return tefBAD_LEDGER; + } + + auto const sleOwner = view.peek(keylet::account(owner)); + if (!sleOwner) + return tecINTERNAL; + + adjustOwnerCount(view, sleOwner, -1, j); + view.update(sleOwner); + + // Remove object from ledger + view.erase(sle); + return tesSUCCESS; +} + +TER +deleteSLE( + ApplyView& view, + Keylet keylet, + AccountID const owner, + beast::Journal j) +{ + return deleteSLE(view, view.peek(keylet), owner, j); +} + // Direct send w/o fees: // - Redeeming IOUs and/or sending sender's own IOUs. // - Create trust line if needed. From 4acc54b39e136699c871423871eca4ac405a9052 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 15:40:10 -0400 Subject: [PATCH 13/18] add docs --- src/ripple/app/tx/impl/DID.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index c533746cc3e..20eb8c8b4d7 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -33,7 +33,12 @@ namespace ripple { DID ====== - TODO: add docs here + Decentralized Identifiers (DIDs) are a new type of identifier that enable + verifiable, self-sovereign digital identity and are designed to be + compatible with any distributed ledger or network. This implementation + conforms to the requirements specified in the DID v1.0 specification + currently recommended by the W3C Credentials Community Group + (https://www.w3.org/TR/did-core/). */ //------------------------------------------------------------------------------ From b0c99a4efffb997de49a6b5e0708c92751b3a869 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 15:47:48 -0400 Subject: [PATCH 14/18] update cmake file --- Builds/CMake/RippledCore.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index bca04effc18..584c01c7002 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -519,7 +519,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/did.cpp src/ripple/app/tx/impl/Escrow.cpp src/ripple/app/tx/impl/InvariantCheck.cpp src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp From fa63fc34dc986dc3186299811479113c00192472 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 15:50:39 -0400 Subject: [PATCH 15/18] actual cmake fix --- Builds/CMake/RippledCore.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 584c01c7002..ef6f925ec6d 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -519,7 +519,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/DID.cpp src/ripple/app/tx/impl/Escrow.cpp src/ripple/app/tx/impl/InvariantCheck.cpp src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp @@ -939,7 +939,7 @@ if (tests) src/test/jtx/impl/check.cpp src/test/jtx/impl/delivermin.cpp src/test/jtx/impl/deposit.cpp - src/test/jtx/impl/DID.cpp + src/test/jtx/impl/did.cpp src/test/jtx/impl/envconfig.cpp src/test/jtx/impl/fee.cpp src/test/jtx/impl/flags.cpp From 36716d6417d4e646cf692b6d9fe6fbf2f24e180b Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 16:58:07 -0400 Subject: [PATCH 16/18] Revert "move helper functions to View.h" This reverts commit 31117f7fccebe37a4faf47d1a840448b8b86e660. --- src/ripple/app/tx/impl/DID.cpp | 79 +++++++++++++++++++++- src/ripple/app/tx/impl/DID.h | 10 +++ src/ripple/app/tx/impl/DeleteAccount.cpp | 2 +- src/ripple/ledger/View.h | 28 -------- src/ripple/ledger/impl/View.cpp | 84 ------------------------ 5 files changed, 87 insertions(+), 116 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index 20eb8c8b4d7..bfae80032b2 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -91,6 +91,43 @@ DIDSet::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } +TER +addSLE( + ApplyContext& ctx, + std::shared_ptr 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() { @@ -137,7 +174,7 @@ DIDSet::doApply() set(sfDIDDocument); set(sfData); - return addSLE(ctx_.view(), sleDID, account_, ctx_.journal); + return addSLE(ctx_, sleDID, account_); } NotTEC @@ -155,11 +192,47 @@ DIDDelete::preflight(PreflightContext const& ctx) return preflight2(ctx); } +TER +DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner) +{ + 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, + AccountID const owner, + beast::Journal j) +{ + // Remove object from owner directory + if (!view.dirRemove( + keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true)) + { + JLOG(j.fatal()) << "Unable to delete DID Token from owner."; + return tefBAD_LEDGER; + } + + auto const sleOwner = view.peek(keylet::account(owner)); + if (!sleOwner) + return tecINTERNAL; + + adjustOwnerCount(view, sleOwner, -1, j); + view.update(sleOwner); + + // Remove object from ledger + view.erase(sle); + return tesSUCCESS; +} + TER DIDDelete::doApply() { - return deleteSLE( - ctx_.view(), keylet::did(account_), account_, ctx_.journal); + return deleteSLE(ctx_, keylet::did(account_), account_); } } // namespace ripple diff --git a/src/ripple/app/tx/impl/DID.h b/src/ripple/app/tx/impl/DID.h index 807b6863368..9792bc29942 100644 --- a/src/ripple/app/tx/impl/DID.h +++ b/src/ripple/app/tx/impl/DID.h @@ -57,6 +57,16 @@ class DIDDelete : public Transactor 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, + AccountID const owner, + beast::Journal j); + TER doApply() override; }; diff --git a/src/ripple/app/tx/impl/DeleteAccount.cpp b/src/ripple/app/tx/impl/DeleteAccount.cpp index c8191e1a42d..49b645e31d9 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.cpp +++ b/src/ripple/app/tx/impl/DeleteAccount.cpp @@ -143,7 +143,7 @@ removeDIDFromLedger( std::shared_ptr const& sleDel, beast::Journal j) { - return deleteSLE(view, sleDel, account, j); + return DIDDelete::deleteSLE(view, sleDel, account, j); } // Return nullptr if the LedgerEntryType represents an obligation that can't diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index b5978805a75..5680114a79c 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -20,7 +20,6 @@ #ifndef RIPPLE_LEDGER_VIEW_H_INCLUDED #define RIPPLE_LEDGER_VIEW_H_INCLUDED -#include #include #include #include @@ -400,33 +399,6 @@ trustDelete( TER offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j); -/** - * This is a helper function that adds a SLE to an owner's directory. - */ -TER -addSLE( - ApplyView& view, - std::shared_ptr const& sle, - AccountID const& owner, - beast::Journal j); - -/** - * This is a helper function that deletes a SLE from an owner's directory. - */ -TER -deleteSLE( - ApplyView& view, - std::shared_ptr const& sle, - AccountID const owner, - beast::Journal j); - -TER -deleteSLE( - ApplyView& view, - Keylet keylet, - AccountID const owner, - beast::Journal j); - //------------------------------------------------------------------------------ // diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 8144b40df33..5050e8764e9 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -942,90 +942,6 @@ offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j) return tesSUCCESS; } -/** - * This is a helper function that adds a SLE to an owner's directory. - */ -TER -addSLE( - ApplyView& view, - std::shared_ptr const& sle, - AccountID const& owner, - beast::Journal j) -{ - auto const sleAccount = 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 = - view.fees().accountReserve((*sleAccount)[sfOwnerCount] + 1); - - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - } - - // Add ledger object to ledger - view.insert(sle); - - // Add ledger object to owner's page - { - auto page = view.dirInsert( - keylet::ownerDir(owner), sle->key(), describeOwnerDir(owner)); - if (!page) - return tecDIR_FULL; - (*sle)[sfOwnerNode] = *page; - } - adjustOwnerCount(view, sleAccount, 1, j); - view.update(sleAccount); - - return tesSUCCESS; -} - -/** - * This is a helper function that deletes a SLE from an owner's directory. - */ -TER -deleteSLE( - ApplyView& view, - std::shared_ptr const& sle, - AccountID const owner, - beast::Journal j) -{ - if (!sle) - return tecNO_ENTRY; - - // Remove object from owner directory - if (!view.dirRemove( - keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true)) - { - JLOG(j.fatal()) << "Unable to delete DID Token from owner."; - return tefBAD_LEDGER; - } - - auto const sleOwner = view.peek(keylet::account(owner)); - if (!sleOwner) - return tecINTERNAL; - - adjustOwnerCount(view, sleOwner, -1, j); - view.update(sleOwner); - - // Remove object from ledger - view.erase(sle); - return tesSUCCESS; -} - -TER -deleteSLE( - ApplyView& view, - Keylet keylet, - AccountID const owner, - beast::Journal j) -{ - return deleteSLE(view, view.peek(keylet), owner, j); -} - // Direct send w/o fees: // - Redeeming IOUs and/or sending sender's own IOUs. // - Create trust line if needed. From c6e6b261001abbc80aaffa8545660893d1242eb9 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Fri, 6 Oct 2023 17:06:03 -0400 Subject: [PATCH 17/18] fix formatting --- src/ripple/app/tx/impl/DID.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index bfae80032b2..1fbfb3f20d9 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -35,10 +35,10 @@ namespace ripple { Decentralized Identifiers (DIDs) are a new type of identifier that enable verifiable, self-sovereign digital identity and are designed to be - compatible with any distributed ledger or network. This implementation - conforms to the requirements specified in the DID v1.0 specification - currently recommended by the W3C Credentials Community Group - (https://www.w3.org/TR/did-core/). + compatible with any distributed ledger or network. This implementation + conforms to the requirements specified in the DID v1.0 specification + currently recommended by the W3C Credentials Community Group + (https://www.w3.org/TR/did-core/). */ //------------------------------------------------------------------------------ From ea560af5db050c78b1bed9395a4e4c518ab0c1ce Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 17 Oct 2023 17:06:59 -0400 Subject: [PATCH 18/18] Update to latest spec --- src/ripple/app/tx/impl/DID.cpp | 20 ++------ src/ripple/app/tx/impl/DID.h | 3 -- src/test/app/DID_test.cpp | 83 +++++++++++++++------------------- 3 files changed, 41 insertions(+), 65 deletions(-) diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/ripple/app/tx/impl/DID.cpp index 1fbfb3f20d9..c92162c8306 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/ripple/app/tx/impl/DID.cpp @@ -60,7 +60,8 @@ DIDSet::preflight(PreflightContext const& ctx) return temEMPTY_DID; if (ctx.tx.isFieldPresent(sfURI) && ctx.tx[sfURI].empty() && - ctx.tx.isFieldPresent(sfDIDDocument) && ctx.tx[sfDIDDocument].empty()) + ctx.tx.isFieldPresent(sfDIDDocument) && ctx.tx[sfDIDDocument].empty() && + ctx.tx.isFieldPresent(sfData) && ctx.tx[sfData].empty()) return temEMPTY_DID; auto isTooLong = [&](auto const& sField, std::size_t length) -> bool { @@ -77,20 +78,6 @@ DIDSet::preflight(PreflightContext const& ctx) return preflight2(ctx); } -TER -DIDSet::preclaim(PreclaimContext const& ctx) -{ - if (!ctx.view.exists(keylet::did(ctx.tx[sfAccount])) && - !ctx.tx.isFieldPresent(sfURI) && !ctx.tx.isFieldPresent(sfDIDDocument)) - { - // Need either the URI or document if the account doesn't already have a - // DID - return tecEMPTY_DID; - } - - return tesSUCCESS; -} - TER addSLE( ApplyContext& ctx, @@ -153,7 +140,8 @@ DIDSet::doApply() update(sfData); if (!sleDID->isFieldPresent(sfURI) && - !sleDID->isFieldPresent(sfDIDDocument)) + !sleDID->isFieldPresent(sfDIDDocument) && + !sleDID->isFieldPresent(sfData)) { return tecEMPTY_DID; } diff --git a/src/ripple/app/tx/impl/DID.h b/src/ripple/app/tx/impl/DID.h index 9792bc29942..13d5a261542 100644 --- a/src/ripple/app/tx/impl/DID.h +++ b/src/ripple/app/tx/impl/DID.h @@ -36,9 +36,6 @@ class DIDSet : public Transactor static NotTEC preflight(PreflightContext const& ctx); - static TER - preclaim(PreclaimContext const& ctx); - TER doApply() override; }; diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index 34ed06b5463..3aa27978bfe 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -149,24 +149,11 @@ struct DID_test : public beast::unit_test::suite env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); - // only attestation - env(did::set(alice), did::data("attest"), ter(tecEMPTY_DID)); - env.close(); - BEAST_EXPECT(ownerCount(env, alice) == 0); - - // both empty fields - env(did::set(alice), - did::uri(""), - did::document(""), - ter(temEMPTY_DID)); - env.close(); - BEAST_EXPECT(ownerCount(env, alice) == 0); - - // both empty fields with attestation + // all empty fields env(did::set(alice), did::uri(""), did::document(""), - did::data("attest"), + did::data(""), ter(temEMPTY_DID)); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -239,6 +226,7 @@ struct DID_test : public beast::unit_test::suite Account const dave{"dave"}; Account const edna{"edna"}; Account const francis{"francis"}; + Account const george{"george"}; env.fund(XRP(5000), alice, bob, charlie, dave, edna, francis); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -249,28 +237,32 @@ struct DID_test : public beast::unit_test::suite env(did::set(alice), did::uri("uri")); BEAST_EXPECT(ownerCount(env, alice) == 1); - // only URI, plus attestation - env(did::set(bob), did::uri("uri"), did::data("attest")); + // only DIDDocument + env(did::set(bob), did::document("data")); BEAST_EXPECT(ownerCount(env, bob) == 1); - // only DIDDocument - env(did::set(charlie), did::document("data")); + // only Data + env(did::set(charlie), did::data("data")); BEAST_EXPECT(ownerCount(env, charlie) == 1); - // only DIDDocument, plus attestation - env(did::set(dave), did::document("data"), did::data("attest")); + // URI + Data + env(did::set(dave), did::uri("uri"), did::data("attest")); BEAST_EXPECT(ownerCount(env, dave) == 1); - // both URI and DIDDocument + // URI + DIDDocument env(did::set(edna), did::uri("uri"), did::document("data")); BEAST_EXPECT(ownerCount(env, edna) == 1); - // both URI and DIDDocument, plus Attestation - env(did::set(francis), + // DIDDocument + Data + env(did::set(francis), did::document("data"), did::data("attest")); + BEAST_EXPECT(ownerCount(env, francis) == 1); + + // URI + DIDDocument + Data + env(did::set(george), did::uri("uri"), did::document("data"), did::data("attest")); - BEAST_EXPECT(ownerCount(env, francis) == 1); + BEAST_EXPECT(ownerCount(env, george) == 1); } void @@ -321,29 +313,15 @@ struct DID_test : public beast::unit_test::suite BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); } - // Set Attestation - std::string const initialAttestation = "attest"; + // Set Data + std::string const initialData = "attest"; { - env(did::set(alice), did::data(initialAttestation)); + env(did::set(alice), did::data(initialData)); BEAST_EXPECT(ownerCount(env, alice) == 1); auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(checkVL((*sleDID)[sfData], initialAttestation)); - } - - // Try to delete URI/DIDDocument, fails because no elements are set - // (other than attestation) - { - env(did::set(alice), - did::document(""), - did::uri(""), - ter(temEMPTY_DID)); - BEAST_EXPECT(ownerCount(env, alice) == 1); - auto const sleDID = env.le(keylet::did(alice.id())); - BEAST_EXPECT(checkVL((*sleDID)[sfURI], initialURI)); - BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(checkVL((*sleDID)[sfData], initialAttestation)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], initialData)); } // Remove URI @@ -353,10 +331,10 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], initialDocument)); - BEAST_EXPECT(checkVL((*sleDID)[sfData], initialAttestation)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], initialData)); } - // Remove Attestation + // Remove Data { env(did::set(alice), did::data("")); BEAST_EXPECT(ownerCount(env, alice) == 1); @@ -374,9 +352,10 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(checkVL((*sleDID)[sfURI], secondURI)); BEAST_EXPECT(!sleDID->isFieldPresent(sfDIDDocument)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); } - // Remove URI + set Document + // Remove URI + set DIDDocument std::string const secondDocument = "data2"; { env(did::set(alice), did::uri(""), did::document(secondDocument)); @@ -384,6 +363,18 @@ struct DID_test : public beast::unit_test::suite auto const sleDID = env.le(keylet::did(alice.id())); BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); BEAST_EXPECT(checkVL((*sleDID)[sfDIDDocument], secondDocument)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfData)); + } + + // Remove DIDDocument + set Data + std::string const secondData = "randomData"; + { + env(did::set(alice), did::document(""), did::data(secondData)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + auto const sleDID = env.le(keylet::did(alice.id())); + BEAST_EXPECT(!sleDID->isFieldPresent(sfURI)); + BEAST_EXPECT(!sleDID->isFieldPresent(sfDIDDocument)); + BEAST_EXPECT(checkVL((*sleDID)[sfData], secondData)); } // Delete DID