diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index fa0dc0be2..0c35154e5 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -648,6 +648,43 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx) if (ctx.view.txExists(ctx.tx.getTransactionID())) return tefALREADY; + if (hook::isEmittedTxn(ctx.tx) && ctx.view.rules().enabled(featureHooks) && + ctx.view.rules().enabled(fixXahauV2)) + { + // check if the emitted txn exists on ledger and is in the emission + // directory if not that's a re-apply so discard + auto const kl = keylet::emittedTxn(ctx.tx.getTransactionID()); + auto const sleE = ctx.view.read(kl); + if (!sleE) + return tefNONDIR_EMIT; + + // lookup the page + uint64_t const page = sleE->getFieldU64(sfOwnerNode); + auto node = ctx.view.read(keylet::page(keylet::emittedDir(), page)); + + if (!node) + { + JLOG(ctx.j.warn()) + << "applyTransaction: orphaned emitted txn detected. keylet=" + << to_string(kl.key); + + // RH TODO: work out how to safely delete the object + return tefNONDIR_EMIT; + } + + auto entries = node->getFieldV256(sfIndexes); + auto it = std::find(entries.begin(), entries.end(), kl.key); + if (entries.end() == it) + { + JLOG(ctx.j.warn()) << "applyTransaction: orphaned emitted txn " + "detected (2). keylet=" + << to_string(kl.key); + + // RH TODO: work out how to safely delete the object + return tefNONDIR_EMIT; + } + } + return tesSUCCESS; } diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index e3ddeb632..21886c4d6 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 = 66; +static constexpr std::size_t numFeatures = 67; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -354,6 +354,7 @@ extern uint256 const featureImport; extern uint256 const featureXahauGenesis; extern uint256 const featureHooksUpdate1; extern uint256 const fixXahauV1; +extern uint256 const fixXahauV2; } // namespace ripple diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 2e2fb41f6..750c58133 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -183,6 +183,7 @@ enum TEFcodes : TERUnderlyingType { tefNFTOKEN_IS_NOT_TRANSFERABLE, tefPAST_IMPORT_SEQ, tefPAST_IMPORT_VL_SEQ, + tefNONDIR_EMIT, }; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 6603685fb..19aa0fca7 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -460,7 +460,7 @@ REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::De REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FIX (fixXahauV1, Supported::yes, VoteBehavior::DefaultNo); - +REGISTER_FIX (fixXahauV2, 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/TER.cpp b/src/ripple/protocol/impl/TER.cpp index 8e8f37847..b7bc39186 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -115,6 +115,7 @@ transResults() MAKE_ERROR(tefTOO_BIG, "Transaction affects too many items."), MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."), MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."), + MAKE_ERROR(tefNONDIR_EMIT, "An emitted txn was injected into the ledger without a corresponding directory entry."), MAKE_ERROR(telLOCAL_ERROR, "Local failure."), MAKE_ERROR(telBAD_DOMAIN, "Domain too long."), diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 44ea1d795..b33c2fc64 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -18,6 +18,9 @@ */ //============================================================================== +#include +#include +#include #include #include #include @@ -4331,7 +4334,220 @@ private: } void - testWithFeats(FeatureBitset features) + testEmittedTxnReliability(FeatureBitset features) + { + testcase("emitted txn reliability"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + auto setHook = [](test::jtx::Account const& account) { + std::string const createCodeHex = + "0061736D0100000001350860057F7F7F7F7F017E60017F017E60047F7F7F7F" + "017E60037F7F7E017E60027F7F017E60037F7F7F017E60027F7F017F600001" + "7E02CD010D03656E76057472616365000003656E760C6574786E5F72657365" + "727665000103656E760A7574696C5F6163636964000203656E760974726163" + "655F6E756D000303656E760C686F6F6B5F6163636F756E74000403656E760A" + "6F74786E5F6669656C64000503656E7608726F6C6C6261636B000303656E76" + "025F67000603656E7606616363657074000303656E760A6C65646765725F73" + "6571000703656E760C6574786E5F64657461696C73000403656E760D657478" + "6E5F6665655F62617365000403656E7604656D697400020303020101050301" + "0002062B077F0141C08C040B7F004180080B7F0041B40C0B7F004180080B7F" + "0041C08C040B7F0041000B7F0041010B070F02046362616B000D04686F6F6B" + "000E0AE1930002AC800001017F230041106B220124002001200036020C41F6" + "0B411A41B70A4119410010001A200141106A240042000BAE930002017F017C" + "230041B0056B22012400200120003602AC0541E40B411141840A4110410010" + "001A410110011A200120014190056A411441940A4123100237038805418C08" + "410320012903880510031A200141F0046A411410041A2001200141D0046A41" + "144181802010053E02CC0441E409411120013402CC0410031A20012802CC04" + "411448044041910C4123420110061A0B200141003602C804200141013602C8" + "04200141003602C4040340418180B48178411510071A4100210020012802C8" + "04047F20012802C4044114480520000B4101710440200120012802C4042001" + "41F0046A6A2D000020012802C404200141D0046A6A2D0000463602C8042001" + "20012802C40441016A3602C4040C010B0B20012802C804450440419B08411D" + "420210081A0B200120014190046A413041818018100537038804200142C084" + "3D370380040240200129038804420852044041D00A41CE0041D40841CD0041" + "0010001A0C010B419F0B41C40041A10941C300410010001A200120012D0090" + "04410776047E427E052001310097042001310090044291A2C480B001834238" + "862001310091044230867C2001310092044228867C2001310093044220867C" + "2001310094044218867C2001310095044210867C2001310096044208867C7C" + "0B3703F803419008410A20012903F80310031A20012903F80342A08D065504" + "402001027E20012903F803B94400000040E17A843FA2220299440000000000" + "00E0436304402002B00C010B428080808080808080807F0B370380040B0B41" + "F609410D20012903800410031A2001200141E0016A3602DC01200120012903" + "80043703B801200141003602B401200141003602B001200110093E02AC0120" + "0141C0016A411410041A200141003A00AB0120012802DC0141123A00002001" + "2802DC0120012D00AB014108763A000120012802DC0120012D00AB013A0002" + "200120012802DC0141036A3602DC0120014180808080783602A40120014102" + "3A00A30120012802DC0120012D00A301410F7141206A3A000020012802DC01" + "20012802A4014118763A000120012802DC0120012802A4014110763A000220" + "012802DC0120012802A4014108763A000320012802DC0120012802A4013A00" + "04200120012802DC0141056A3602DC01200120012802B00136029C01200141" + "033A009B0120012802DC0120012D009B01410F7141206A3A000020012802DC" + "01200128029C014118763A000120012802DC01200128029C014110763A0002" + "20012802DC01200128029C014108763A000320012802DC01200128029C013A" + "0004200120012802DC0141056A3602DC012001410036029401200141043A00" + "930120012802DC0120012D009301410F7141206A3A000020012802DC012001" + "280294014118763A000120012802DC012001280294014110763A0002200128" + "02DC012001280294014108763A000320012802DC012001280294013A000420" + "0120012802DC0141056A3602DC01200120012802B40136028C012001410E3A" + "008B0120012802DC0120012D008B01410F7141206A3A000020012802DC0120" + "0128028C014118763A000120012802DC01200128028C014110763A00022001" + "2802DC01200128028C014108763A000320012802DC01200128028C013A0004" + "200120012802DC0141056A3602DC01200120012802AC0141016A3602840120" + "01411A3A00830120012802DC0141203A000020012802DC0120012D0083013A" + "000120012802DC012001280284014118763A000220012802DC012001280284" + "014110763A000320012802DC012001280284014108763A000420012802DC01" + "2001280284013A0005200120012802DC0141066A3602DC01200120012802AC" + "0141056A36027C2001411B3A007B20012802DC0141203A000020012802DC01" + "20012D007B3A000120012802DC01200128027C4118763A000220012802DC01" + "200128027C4110763A000320012802DC01200128027C4108763A0004200128" + "02DC01200128027C3A0005200120012802DC0141066A3602DC01200141013A" + "007A200120012903B80137037020012802DC0120012D007A410F7141E0006A" + "3A000020012802DC012001290370423888423F8342407D3C000120012802DC" + "01200129037042308842FF01833C000220012802DC01200129037042288842" + "FF01833C000320012802DC01200129037042208842FF01833C000420012802" + "DC01200129037042188842FF01833C000520012802DC012001290370421088" + "42FF01833C000620012802DC01200129037042088842FF01833C0007200128" + "02DC01200129037042FF01833C0008200120012802DC0141096A3602DC0120" + "0120012802DC0136026C200141083A006B2001420037036020012802DC0120" + "012D006B410F7141E0006A3A000020012802DC012001290360423888423F83" + "42407D3C000120012802DC01200129036042308842FF01833C000220012802" + "DC01200129036042288842FF01833C000320012802DC012001290360422088" + "42FF01833C000420012802DC01200129036042188842FF01833C0005200128" + "02DC01200129036042108842FF01833C000620012802DC0120012903604208" + "8842FF01833C000720012802DC01200129036042FF01833C00082001200128" + "02DC0141096A3602DC0120012802DC0141F3003A000020012802DC0141213A" + "000120012802DC01420037030220012802DC01420037030A20012802DC0142" + "0037031220012802DC014200370319200120012802DC0141236A3602DC0120" + "0141013A005F20012802DC0120012D005F4180016A3A000020012802DC0141" + "143A000120012802DC0120012903C00137030220012802DC0120012903C801" + "37030A20012802DC0120012802D001360212200120012802DC0141166A3602" + "DC01200141033A005E20012802DC0120012D005E4180016A3A000020012802" + "DC0141143A000120012802DC0120012903900537030220012802DC01200129" + "03980537030A20012802DC0120012802A005360212200120012802DC014116" + "6A3602DC01200120012802DC01418E02100A3703502001200141E0016A418E" + "02100B370348200141083A004720012001290348370338200128026C20012D" + "0047410F7141E0006A3A0000200128026C2001290338423888423F8342407D" + "3C0001200128026C200129033842308842FF01833C0002200128026C200129" + "033842288842FF01833C0003200128026C200129033842208842FF01833C00" + "04200128026C200129033842188842FF01833C0005200128026C2001290338" + "42108842FF01833C0006200128026C200129033842088842FF01833C000720" + "0128026C200129033842FF01833C00082001200128026C41096A36026C2001" + "200141106A4120200141E0016A418E02100C370308418008410B2001290308" + "10031A41B808411C420010081A200141B0056A240042000B0BBB0401004180" + "080BB304656D69745F726573756C7400726574006F74786E5F64726F707300" + "436172626F6E3A20496E636F6D696E67207472616E73616374696F6E004361" + "72626F6E3A20456D6974746564207472616E73616374696F6E00436172626F" + "6E3A204E6F6E2D787270207472616E73616374696F6E206465746563746564" + "2C2073656E64696E672064656661756C7420313030302064726F707320746F" + "207266436172626F6E00436172626F6E3A20585250207472616E7361637469" + "6F6E2064657465637465642C20636F6D707574696E6720312520746F207365" + "6E6420746F207266436172626F6E006163636F756E745F6669656C645F6C65" + "6E0064726F70735F746F5F73656E6400436172626F6E3A2073746172746564" + "0072504D68375069396374363939695A5554576179744A556F48634A376367" + "797A694B00436172626F6E3A2063616C6C6261636B2063616C6C65642E0022" + "436172626F6E3A204E6F6E2D787270207472616E73616374696F6E20646574" + "65637465642C2073656E64696E672064656661756C7420313030302064726F" + "707320746F207266436172626F6E220022436172626F6E3A20585250207472" + "616E73616374696F6E2064657465637465642C20636F6D707574696E672031" + "2520746F2073656E6420746F207266436172626F6E220022436172626F6E3A" + "2073746172746564220022436172626F6E3A2063616C6C6261636B2063616C" + "6C65642E2200436172626F6E3A2073664163636F756E74206669656C64206D" + "697373696E67212121"; + Json::Value jhv = hso(createCodeHex); + jhv[jss::HookOn] = + "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffbfff" + "ff"; + Json::Value jv = ripple::test::jtx::hook(account, {{jhv}}, 0); + return jv; + }; + + env(setHook(account), HSFEE); + env.close(); + + // ttINVOKE + env(invoke::invoke(account), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + Blob txBlob; + auto meta = env.meta(); + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + for (auto const& node : meta->getFieldArray(sfAffectedNodes)) + { + SField const& metaType = node.getFName(); + uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); + if (metaType == sfCreatedNode && nodeType == ltEMITTED_TXN) + { + auto const& nf = const_cast(node) + .getField(sfNewFields) + .downcast(); + auto const& et = const_cast(nf) + .getField(sfEmittedTxn) + .downcast(); + + txBlob = et.getSerializer().getData(); + break; + } + } + env.close(); + + auto const preDest = env.balance(dest); + bool const withFix = env.current()->rules().enabled(fixXahauV2); + + bool didApply; + TER terRes; + + env.app().openLedger().modify([&](OpenView& view, beast::Journal j) { + auto const tx = + std::make_unique(Slice{txBlob.data(), txBlob.size()}); + std::tie(terRes, didApply) = + ripple::apply(env.app(), view, *tx, tapNONE, env.journal); + + bool const applyResult = withFix ? false : true; + if (withFix) + { + BEAST_EXPECT(terRes == tefNONDIR_EMIT); + } + else + { + BEAST_EXPECT(terRes == tesSUCCESS); + } + BEAST_EXPECT(didApply == applyResult); + return didApply; + }); + + env.close(); + + auto const postDest = env.balance(dest); + auto const postValue = withFix ? XRP(0) : XRP(1); + BEAST_EXPECT(postDest == preDest + postValue); + + for (size_t i = 0; i < 4; i++) + { + Json::Value params1; + params1[jss::tx_blob] = strHex(Slice{txBlob.data(), txBlob.size()}); + auto const jrr1 = env.rpc("json", "inject", to_string(params1)); + env.close(); + } + + auto const postDest1 = env.balance(dest); + auto const postValue1 = withFix ? XRP(0) : XRP(2); + BEAST_EXPECT(postDest1 == postDest + postValue1); + } + + void + testTSH(FeatureBitset features) { testAccountSetTSH(features); testAccountDeleteTSH(features); @@ -4366,14 +4582,22 @@ private: testURITokenCreateSellOfferTSH(features); } + void + testEmittedTxn(FeatureBitset features) + { + testEmittedTxnReliability(features); + } + public: void run() override { using namespace test::jtx; auto const sa = supported_amendments(); - testWithFeats(sa - fixXahauV1); - testWithFeats(sa); + testTSH(sa - fixXahauV1); + testTSH(sa); + testEmittedTxn(sa - fixXahauV2); + testEmittedTxn(sa); } };