diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index a770bfbdc05..e56805a0dc9 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -523,6 +523,7 @@ target_sources (rippled PRIVATE src/ripple/app/tx/impl/DepositPreauth.cpp src/ripple/app/tx/impl/DID.cpp src/ripple/app/tx/impl/FSPin.cpp + src/ripple/app/tx/impl/FSNSPin.cpp src/ripple/app/tx/impl/Escrow.cpp src/ripple/app/tx/impl/InvariantCheck.cpp src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp @@ -796,6 +797,7 @@ if (tests) src/test/app/Flow_test.cpp src/test/app/Freeze_test.cpp src/test/app/FS_test.cpp + src/test/app/FSNS_test.cpp src/test/app/HashRouter_test.cpp src/test/app/LedgerHistory_test.cpp src/test/app/LedgerLoad_test.cpp diff --git a/src/ripple/app/tx/impl/FSNSPin.cpp b/src/ripple/app/tx/impl/FSNSPin.cpp new file mode 100644 index 00000000000..481488bb57f --- /dev/null +++ b/src/ripple/app/tx/impl/FSNSPin.cpp @@ -0,0 +1,132 @@ +//------------------------------------------------------------------------------ +/* + 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 + +namespace ripple { + +XRPAmount +FSNSPin::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + return Transactor::calculateBaseFee(view, tx); +} + +NotTEC +FSNSPin::preflight(PreflightContext const& ctx) +{ + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + std::uint32_t const txFlags = ctx.tx.getFlags(); + + if (ctx.tx.isFieldPresent(sfData)) + { + auto const data = ctx.tx.getFieldVL(sfData); + + if (data.size() < 1 || data.size() > 512) + { + JLOG(ctx.j.warn()) + << "Malformed transaction. Data must be at least 1 " + "character and no more than 512 characters."; + return temMALFORMED; + } + } + + return preflight2(ctx); +} + +TER +FSNSPin::preclaim(PreclaimContext const& ctx) +{ + if (ctx.tx.isFieldPresent(sfData)) + { + auto const data = ctx.tx.getFieldVL(sfData); + ripple::uint256 dataHash = ripple::sha512Half_s( + ripple::Slice(data.data(), data.size()) + ); + + if (ctx.view.exists(keylet::fs(dataHash))) + return tecDUPLICATE; + } + + return tesSUCCESS; +} + +TER +FSNSPin::doApply() +{ + auto const sle = view().peek(keylet::account(account_)); + if (!sle) + return tefINTERNAL; + + auto const nickname = ctx_.tx.getFieldH256(sfNickname); + ripple::uint256 rootIndex; + if (ctx_.tx.isFieldPresent(sfRootIndex)) + { + rootIndex = ctx_.tx.getFieldH256(sfRootIndex); + } + else if (ctx_.tx.isFieldPresent(sfData)) + { + auto const data = ctx_.tx.getFieldVL(sfData); + auto const dataHash = ripple::sha512Half_s( + ripple::Slice(data.data(), data.size()) + ); + + auto const fsKeylet = keylet::fs(dataHash); + rootIndex = fsKeylet.key; + auto const sleFS = std::make_shared(fsKeylet); + + sleFS->setFieldVL(sfData, data); + (*sleFS)[sfOwner] = account_; + + view().insert(sleFS); + } + else + { + // pass + } + + auto const fsnsKeylet = keylet::fsns(nickname); + auto const nsSle = ctx_.view().peek(fsnsKeylet); + if (!nsSle) + { + auto const sleFSNS = std::make_shared(fsnsKeylet); + sleFSNS->setFieldH256(sfRootIndex, rootIndex); + (*sleFSNS)[sfOwner] = account_; + view().insert(sleFSNS); + } + else + { + // if (account_ != *nsSle[sfOwner]) + // { + // return tefINTERNAL; + // } + (*nsSle)[sfRootIndex] = rootIndex; + view().update(nsSle); + } + + return tesSUCCESS; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/FSNSPin.h b/src/ripple/app/tx/impl/FSNSPin.h new file mode 100644 index 00000000000..0c9e8a3ead7 --- /dev/null +++ b/src/ripple/app/tx/impl/FSNSPin.h @@ -0,0 +1,54 @@ +//------------------------------------------------------------------------------ +/* + 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_FSNS_PIN_H_INCLUDED +#define RIPPLE_TX_FSNS_PIN_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +class FSNSPin : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker}; + + explicit FSNSPin(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 31439788adb..d651894d6ac 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -393,6 +393,7 @@ LedgerEntryTypesMatch::visitEntry( case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: case ltDID: case ltFS: + case ltFSNS: break; default: invalidTypeAdded_ = true; diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index a7b4c271e2e..d2ad2bc1ece 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -162,6 +163,8 @@ with_txn_type(TxType txnType, F&& f) return f.template operator()(); case ttFS_PIN: return f.template operator()(); + case ttFSNS_PIN: + return f.template operator()(); default: throw UnknownTxnType(txnType); } diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index a76a967c228..3bdfcb15c59 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -302,9 +302,9 @@ foreachFeature(FeatureBitset bs, F&& f) f(bitsetIndexToFeature(i)); } -// extern uint256 const featureOwnerPaysFee; -// extern uint256 const featureFlow; -// extern uint256 const featureFlowCross; +extern uint256 const featureOwnerPaysFee; +extern uint256 const featureFlow; +extern uint256 const featureFlowCross; extern uint256 const featureCryptoConditionsSuite; extern uint256 const fix1513; extern uint256 const featureDepositAuth; diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 3ab8138e1e1..65beae17977 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -286,6 +286,9 @@ did(AccountID const& account) noexcept; Keylet fs(uint256 const& id) noexcept; +Keylet +fsns(uint256 const& ns) 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 44e46fe5641..24c3c707c4c 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -198,6 +198,12 @@ enum LedgerEntryType : std::uint16_t */ ltFS = 0x0046, + /** The ledger object which tracks the NS. + + \sa keylet::ns + */ + ltFSNS = 0x006E, + //--------------------------------------------------------------------------- /** A special type, matching any ledger entry type. diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index 8d69c96eaf6..61496cf201e 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -193,6 +193,9 @@ enum TxType : std::uint16_t /** This transaction type creates an FS */ ttFS_PIN = 66, + /** This transaction type creates an FSNS */ + ttFSNS_PIN = 67, + /** This system-generated transaction type is used to update the status of the various amendments. diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index ca4bdb0142b..21261ce25ac 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -73,12 +73,13 @@ enum class LedgerNameSpace : std::uint16_t { XCHAIN_CREATE_ACCOUNT_CLAIM_ID = 'K', DID = 'I', FS = 'F', + FSNS = 'n', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. CONTRACT [[deprecated]] = 'c', GENERATOR [[deprecated]] = 'g', - NICKNAME [[deprecated]] = 'n', + // NICKNAME [[deprecated]] = 'n', }; template @@ -451,6 +452,12 @@ fs(uint256 const& id) noexcept return {ltFS, indexHash(LedgerNameSpace::FS, id)}; } +Keylet +fsns(uint256 const& ns) noexcept +{ + return {ltFSNS, indexHash(LedgerNameSpace::FSNS, ns)}; +} + } // namespace keylet } // namespace ripple diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index d72d073312c..81c8076bdc4 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -350,6 +350,17 @@ LedgerFormats::LedgerFormats() // {sfPreviousTxnLgrSeq, soeREQUIRED} }, commonFields); + + add(jss::FSNS, + ltFSNS, + { + {sfOwner, soeREQUIRED}, + {sfRootIndex, soeREQUIRED}, + // {sfOwnerNode, soeREQUIRED}, + // {sfPreviousTxnID, soeREQUIRED}, + // {sfPreviousTxnLgrSeq, soeREQUIRED} + }, + commonFields); // clang-format on } diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index cc1142cf469..668d1578886 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -490,6 +490,15 @@ TxFormats::TxFormats() {sfData, soeREQUIRED}, }, commonFields); + + add(jss::FSNSPin, + ttFSNS_PIN, + { + {sfNickname, soeREQUIRED}, + {sfRootIndex, soeOPTIONAL}, + {sfData, soeOPTIONAL}, + }, + commonFields); } TxFormats const& diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index b78f2a6d7f2..cf5dc900355 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -87,7 +87,9 @@ JSS(Fee); // in/out: TransactionSign; field. JSS(FeeSettings); // ledger type. JSS(Flags); // in/out: TransactionSign; field. JSS(FS); // ledger type. +JSS(FSNS); // ledger type. JSS(FSPin); // transaction type. +JSS(FSNSPin); // transaction type. JSS(incomplete_shards); // out: OverlayImpl, PeerImp JSS(Invalid); // JSS(LastLedgerSequence); // in: TransactionSign; field @@ -343,6 +345,7 @@ JSS(full); // in: LedgerClearer, handlers/Ledger JSS(full_reply); // out: PathFind JSS(fullbelow_size); // out: GetCounts JSS(fs); // in: LedgerEntry +JSS(fsns); // in: LedgerEntry JSS(good); // out: RPCVersion JSS(hash); // out: NetworkOPs, InboundLedger, // LedgerToJson, STTx; field diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index b5a5c78ff6a..0f439d48615 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -610,6 +610,18 @@ doLedgerEntry(RPC::JsonContext& context) } } } + else if (context.params.isMember(jss::fsns)) + { + expectedType = ltFSNS; + if (!context.params[jss::fsns].isObject()) + { + if (!uNodeIndex.parseHex(context.params[jss::fsns].asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + } else { if (context.params.isMember("params") && diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index 6ef30cdb310..10c1b873bc6 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -934,7 +934,7 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr std::array, 21> + static constexpr std::array, 22> types{ {{jss::account, ltACCOUNT_ROOT}, {jss::amendments, ltAMENDMENTS}, @@ -957,7 +957,8 @@ chooseLedgerEntryType(Json::Value const& params) {jss::xchain_owned_create_account_claim_id, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, {jss::did, ltDID}, - {jss::fs, ltFS}}}; + {jss::fs, ltFS}, + {jss::fs, ltFSNS}}}; auto const& p = params[jss::type]; if (!p.isString()) diff --git a/src/test/app/FSNS_test.cpp b/src/test/app/FSNS_test.cpp new file mode 100644 index 00000000000..60d5b470460 --- /dev/null +++ b/src/test/app/FSNS_test.cpp @@ -0,0 +1,246 @@ +//------------------------------------------------------------------------------ +/* + 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; +// } + +// bool +// checkVL(Slice const& result, std::string expected) +// { +// Serializer s; +// s.addRaw(result); +// return s.getString() == expected; +// } + +struct FSNS_test : public beast::unit_test::suite +{ + // void + // testEnabled(FeatureBitset features) + // { + // testcase("featureFS 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("DID 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 + // testPinInvalid(FeatureBitset features) + // { + // testcase("Invalid FSPin"); + + // 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); + + // // all empty fields + // env(did::set(alice), + // did::uri(""), + // did::document(""), + // did::data(""), + // ter(temEMPTY_DID)); + // 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); + + // // 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::data(longString), + // ter(temMALFORMED)); + // env.close(); + // BEAST_EXPECT(ownerCount(env, alice) == 0); + + // // Modifying a DID to become empty is checked in testSetModify + // } + + static Json::Value + fsns_pin(jtx::Account const& account) + { + Json::Value jv; + jv[jss::TransactionType] = jss::FSNSPin; + jv[sfNickname.jsonName] = to_string(uint256{beast::zero}); + jv[jss::Account] = to_string(account.id()); + jv[jss::Flags] = tfUniversal; + return jv; + } + + void + testPin(FeatureBitset features) + { + testcase("FSNSPin"); + + using namespace jtx; + using namespace std::chrono; + + Env env(*this); + + // Env env{*this, envconfig(), supported_amendments(), nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + Account const alice{"alice"}; + env.fund(XRP(5000), alice); + env.close(); + + auto tx = fsns_pin(alice); + tx[sfData.jsonName] = "DEADBEEF"; + env(tx, ter(tesSUCCESS)); + env.close(); + + Json::Value params; + params[jss::transaction] = env.tx()->getJson(JsonOptions::none)[jss::hash]; + auto jrr = env.rpc("json", "tx", to_string(params))[jss::result]; + std::cout << "RESULT: " << jrr << "\n"; + + // Json::Value jv; + // jv[jss::ledger_index] = "current"; + // jv[jss::queue] = true; + // jv[jss::expand] = true; + + // auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result]; + } + + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{supported_amendments()}; + testPin(all); + // testEnabled(all); + // testAccountReserve(all); + // testSetInvalid(all); + } +}; + +BEAST_DEFINE_TESTSUITE(FSNS, app, ripple); + +} // namespace test +} // namespace ripple