diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 947b2d95f..c62df2228 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -1105,8 +1105,11 @@ private: std::shared_ptr loadLedgerFromFile(std::string const& ledgerID); + std::shared_ptr + loadLedgerFromJson(std::string const& jsonValue); + bool - loadOldLedger(std::string const& ledgerID, bool replay, bool isFilename); + loadOldLedger(std::string const& ledgerID, bool replay, bool isFilename, bool isJson); void setMaxDisallowedLedger(); @@ -1234,14 +1237,15 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) } else if ( startUp == Config::LOAD || startUp == Config::LOAD_FILE || - startUp == Config::REPLAY) + startUp == Config::REPLAY || startUp == Config::LOAD_JSON) { JLOG(m_journal.info()) << "Loading specified Ledger"; if (!loadOldLedger( config_->START_LEDGER, startUp == Config::REPLAY, - startUp == Config::LOAD_FILE)) + startUp == Config::LOAD_FILE, + startUp == Config::LOAD_JSON)) { JLOG(m_journal.error()) << "The specified ledger could not be loaded."; @@ -1907,17 +1911,153 @@ ApplicationImp::loadLedgerFromFile(std::string const& name) } } +std::shared_ptr +ApplicationImp::loadLedgerFromJson(std::string const& jsonValue) +{ + try + { + Json::Reader reader; + Json::Value jLedger; + + if (!reader.parse(jsonValue, jLedger)) + { + JLOG(m_journal.fatal()) << "Unable to parse ledger JSON"; + return nullptr; + } + + std::reference_wrapper ledger(jLedger); + + // accept a wrapped ledger + if (ledger.get().isMember("result")) + ledger = ledger.get()["result"]; + + if (ledger.get().isMember("ledger")) + ledger = ledger.get()["ledger"]; + + std::uint32_t seq = 1; + auto closeTime = timeKeeper().closeTime(); + using namespace std::chrono_literals; + auto closeTimeResolution = 30s; + bool closeTimeEstimated = false; + std::uint64_t totalDrops = 0; + + if (ledger.get().isMember("accountState")) + { + if (ledger.get().isMember(jss::ledger_index)) + { + seq = ledger.get()[jss::ledger_index].asUInt(); + } + + if (ledger.get().isMember("close_time")) + { + using tp = NetClock::time_point; + using d = tp::duration; + closeTime = tp{d{ledger.get()["close_time"].asUInt()}}; + } + if (ledger.get().isMember("close_time_resolution")) + { + using namespace std::chrono; + closeTimeResolution = + seconds{ledger.get()["close_time_resolution"].asUInt()}; + } + if (ledger.get().isMember("close_time_estimated")) + { + closeTimeEstimated = + ledger.get()["close_time_estimated"].asBool(); + } + if (ledger.get().isMember("total_coins")) + { + totalDrops = beast::lexicalCastThrow( + ledger.get()["total_coins"].asString()); + } + + ledger = ledger.get()["accountState"]; + } + + if (!ledger.get().isArrayOrNull()) + { + JLOG(m_journal.fatal()) << "State nodes must be an array"; + return nullptr; + } + + auto loadLedger = + std::make_shared(seq, closeTime, *config_, nodeFamily_); + loadLedger->setTotalDrops(totalDrops); + + for (Json::UInt index = 0; index < ledger.get().size(); ++index) + { + Json::Value& entry = ledger.get()[index]; + + if (!entry.isObjectOrNull()) + { + JLOG(m_journal.fatal()) << "Invalid entry in ledger"; + return nullptr; + } + + uint256 uIndex; + + if (!uIndex.parseHex(entry[jss::index].asString())) + { + JLOG(m_journal.fatal()) << "Invalid entry in ledger"; + return nullptr; + } + + entry.removeMember(jss::index); + + STParsedJSONObject stp("sle", ledger.get()[index]); + + if (!stp.object || uIndex.isZero()) + { + JLOG(m_journal.fatal()) << "Invalid entry in ledger"; + return nullptr; + } + + // VFALCO TODO This is the only place that + // constructor is used, try to remove it + STLedgerEntry sle(*stp.object, uIndex); + + if (!loadLedger->addSLE(sle)) + { + JLOG(m_journal.fatal()) + << "Couldn't add serialized ledger: " << uIndex; + return nullptr; + } + } + + loadLedger->stateMap().flushDirty(hotACCOUNT_NODE); + + assert( + loadLedger->info().seq < XRP_LEDGER_EARLIEST_FEES || + loadLedger->read(keylet::fees())); + loadLedger->setAccepted( + closeTime, closeTimeResolution, !closeTimeEstimated); + + return loadLedger; + } + catch (std::exception const& x) + { + JLOG(m_journal.fatal()) << "Ledger contains invalid data: " << x.what(); + return nullptr; + } +} + bool ApplicationImp::loadOldLedger( std::string const& ledgerID, bool replay, - bool isFileName) + bool isFileName, + bool isJson) { try { std::shared_ptr loadLedger, replayLedger; - if (isFileName) + if (isJson) + { + if (!ledgerID.empty()) + loadLedger = loadLedgerFromJson(ledgerID); + } + else if (isFileName) { if (!ledgerID.empty()) loadLedger = loadLedgerFromFile(ledgerID); @@ -1999,7 +2139,7 @@ ApplicationImp::loadOldLedger( using namespace std::chrono_literals; using namespace date; static constexpr NetClock::time_point ledgerWarnTimePoint{ - sys_days{January / 1 / 2018} - sys_days{January / 1 / 2000}}; + sys_days{January / 1 / 2000} - sys_days{January / 1 / 2000}}; if (loadLedger->info().closeTime < ledgerWarnTimePoint) { JLOG(m_journal.fatal()) diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index 81aec79a8..b2928b5b2 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -153,7 +153,7 @@ public: std::map IMPORT_VL_KEYS; // hex string -> class PublicKey (for caching purposes) - enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK }; + enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK, LOAD_JSON }; StartUpType START_UP = NORMAL; bool START_VALID = false; diff --git a/src/test/app/Import_json.h b/src/test/app/Import_json.h index e22ae9d21..6f82c89c8 100644 --- a/src/test/app/Import_json.h +++ b/src/test/app/Import_json.h @@ -856,35 +856,35 @@ std::string ImportTCSignersListSet::w_regular_key = R"json({ })json"; std::string ImportTCSignersListSet::w_signers = R"json({ "ledger": { - "acroot": "7BEA5E50817FFB696F4D70912B2FB840231B8FC2A02CAD17CB73D1DFF5D94BE5", - "close": 743016462, - "coins": "99999999993999592", + "acroot": "BC35E65B52724CF258BDAC8B8E0D3B9CA0F012F5B243F6AAD1B671EDABD5188E", + "close": 745594953, + "coins": "99999999993999700", "cres": 10, "flags": 0, - "index": 2457, - "pclose": 743016461, - "phash": "CEF791942FB98486D2246AB6407FF1EE4A0AD64B71677914CB011772571E989D", - "txroot": "45754F58CA09CDAFD3EA59B34E0EA780444510D18F0427FBF4B65EEF64507E27" + "index": 24, + "pclose": 745594952, + "phash": "EDDEC4C98F1B45292D0AE92F8AA7A1EE7C17CCC01170186F8B7BFA7A4CEA6925", + "txroot": "26B8E1D319A3F49ED7D5D21AFED146F424A978415FA8C13D3166A3809875FEAC" }, "transaction": { - "blob": "12000C2200000000240000006F201B000009AC201D000053592023000000026840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F832B3DF3E0107321028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C095407BF7446304402206EF61C271A51D5165F51EE1C3A8E37A8808A24A28AD3CC09676AA5D2539F57D7022006A20F75C8D925D4ECBA4443DD60E07CC18C3A98A1984B22AD3BFF89CE38FFE98114B389FBCED0AF9DCDFF62900BFAEFA3EB872D8A96E1E010732102691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE74463044022030A85575020C6EE1E2AC891D03290BC37E48635464EFB1E44EE0F5F80CB6990902203D23389CA8070ACD14B599CDCD7310D7DA263F802BD068740751C1B959A4EB088114F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE1F1F4EB1300018114AA266540F7DACC27E264B75ED0A5ED7330BFB614E1EB1300018114A10BDE04D40DCA35CC59D912AB30ED4D6A61774EE1F1", - "meta": "201C00000000F8E511005356472CD116F1449F280243169C442271168E368750479CC7B20816170EDBDCA4E6E6F4EB1300018114B389FBCED0AF9DCDFF62900BFAEFA3EB872D8A96E1EB1300018114F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE1F1E1E72200010000202300000002202600000000340000000000000000F4EB1300018114A10BDE04D40DCA35CC59D912AB30ED4D6A61774EE1EB1300018114AA266540F7DACC27E264B75ED0A5ED7330BFB614E1F1E1E1E51100612500000998554E395F6DFD1D1AAB766529CD9924849F827149D03DD8933012234AEAB87B49F35692FA6A9FC8EA6018D5D16532D7795C91BFB0831355BDFDA177E86C8BF997985FE6240000006F624000000076F88A70E1E7220001000024000000702D00000002624000000076DA05C08114AE123A8556F3CF91154711376AFB0F894F832B3D8814F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE1E1F1031000", + "blob": "12000C22000000002400000014201B0000002B201D000053592023000000026840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F832B3DF3E0107321028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C095407BF74473045022100B6035EC54072A7899F6B37AD29225F7843F2A1EF1A2A42C9EA595B7C38C8470702200A5D42359A559420D9CD31FF7E4BE179DE2C79D1D6FF1E4A8E5E27C449C05ED68114B389FBCED0AF9DCDFF62900BFAEFA3EB872D8A96E1E010732102691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE7446304402202E8E08C0BD54232B0F704956364B8EA169073F53023EFFDAE3B61DAF4FE4873F02202C9F93866DAFA0C7FF30F4F12F45C47F5BE7716DE14DABFC95FB04EE08D2DA808114F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE1F1F4EB1300018114AA266540F7DACC27E264B75ED0A5ED7330BFB614E1EB1300018114D91B8EE5C7ABF632469D4C0907C5E40C8B8F79B3E1F1", + "meta": "201C00000000F8E511005356472CD116F1449F280243169C442271168E368750479CC7B20816170EDBDCA4E6E6F4EB1300018114B389FBCED0AF9DCDFF62900BFAEFA3EB872D8A96E1EB1300018114F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE1F1E1E72200010000202300000002202600000000340000000000000000F4EB1300018114AA266540F7DACC27E264B75ED0A5ED7330BFB614E1EB1300018114D91B8EE5C7ABF632469D4C0907C5E40C8B8F79B3E1F1E1E1E51100612500000017550139B458A3DEB2D65B5DEDEAB0D26A5DDC0BF338462CB0F78A9E25719887A88B5692FA6A9FC8EA6018D5D16532D7795C91BFB0831355BDFDA177E86C8BF997985FE62400000014624000000253CEDAB8E1E7220001000024000000152D00000002624000000253B056088114AE123A8556F3CF91154711376AFB0F894F832B3D8814F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE1E1F1031000", "proof": { "children": { - "0": { + "A": { "children": {}, - "hash": "F592039C7BB93AA3012A38B982E287F98DF7B9FCA67521657A9A6FD6D0FE7674", - "key": "07C3837B381A2B5DCF1F5DBC68A69EC3B7A0085B65632D565FA2792577E35DAD" + "hash": "8AC4EE647EC4B6810B6D50C4A368C5BFFC3F988CCBB890E4E2F115FD7AC5EEA2", + "key": "A44A27F06DBB1DF10AA9CBC537E31F0FEE47BD952C1FA38B6D525DFF3F848F1B" } }, - "hash": "45754F58CA09CDAFD3EA59B34E0EA780444510D18F0427FBF4B65EEF64507E27", + "hash": "26B8E1D319A3F49ED7D5D21AFED146F424A978415FA8C13D3166A3809875FEAC", "key": "0000000000000000000000000000000000000000000000000000000000000000" } }, "validation": { "data": { - "n94QWAYxKUHacmyFTnzK4bvqVcUfr6RwtaNxCM2cJRY59UHmz1Fr": "22800000012600000999292C49880F3ACC053D3EFDA612F05189456F953CBBB7C2442E6AFC1A834F0E7B53A5D0F700E2C2E530F7D99D38C5D05017379BE1750B822774ABBF87E20124148DAA5FAD35ACFFE9B7B2E5415A5729BC385019CEF791942FB98486D2246AB6407FF1EE4A0AD64B71677914CB011772571E989D732103FCA947A7F08B146457BEF95AF0CF7C3ABF0D09CD1DC02099F7185C37BB3280757646304402204622E0EFAE31C7A3CE3C73914A0562F30B333020A026B1E2E3CA8FE17DF6E50D0220239F540C079C1B63EA5932A68D3EB319C362F414C6A11705D87F0D9763B4B52F", - "n9KqAeJTJEJaMZNN35SNrPDbs324rwjDPy6BFHjZ4oM4en4snKjf": "22800000012600000999292C49880F3AB3E046630EFCB1DF5189456F953CBBB7C2442E6AFC1A834F0E7B53A5D0F700E2C2E530F7D99D38C5D05017379BE1750B822774ABBF87E20124148DAA5FAD35ACFFE9B7B2E5415A5729BC385019CEF791942FB98486D2246AB6407FF1EE4A0AD64B71677914CB011772571E989D732102AB4E3B7C53A4265C51952DD9D2CD2829219CDD4F55F63969E38C5C910F3C5F1C76473045022100BC8DB80B154E77E1A005E564FD2C71F823619868F51D2514BA45270F3F17CD67022069B4ABEE1CD8E00DABFAC5F33B85E840E8FC4C7A97AAB5A8433EC8F25FE86457" + "n94QWAYxKUHacmyFTnzK4bvqVcUfr6RwtaNxCM2cJRY59UHmz1Fr": "22800000012600000018292C70E04C3ABA681F6633694AA9515EB28913F8136DE8F40386E3D88F6566B9BB16337B08EEC943A21C49B597BB1C5017B3C185C3FCC69339DDB7D0FCB8AC25748E7940436499206D7F782FBA2EFFB90C5019EDDEC4C98F1B45292D0AE92F8AA7A1EE7C17CCC01170186F8B7BFA7A4CEA6925732103FCA947A7F08B146457BEF95AF0CF7C3ABF0D09CD1DC02099F7185C37BB3280757647304502210085E05CF1DFEC3704E0463FD96CC36A8ACA1AB156B70ABBA3C9EE304FF05C5A66022068EDB6C464090BB4FA013B427515A74B9FAAFF1934014AEC9C2B0E7F2415F6A9", + "n9KqAeJTJEJaMZNN35SNrPDbs324rwjDPy6BFHjZ4oM4en4snKjf": "22800000012600000018292C70E04C3A81E3252D02472AF8515EB28913F8136DE8F40386E3D88F6566B9BB16337B08EEC943A21C49B597BB1C5017B3C185C3FCC69339DDB7D0FCB8AC25748E7940436499206D7F782FBA2EFFB90C5019EDDEC4C98F1B45292D0AE92F8AA7A1EE7C17CCC01170186F8B7BFA7A4CEA6925732102AB4E3B7C53A4265C51952DD9D2CD2829219CDD4F55F63969E38C5C910F3C5F1C7646304402205BD0C7ED0A15D2814EA3427CEE5A91458E5FD587E9539D4E1F91E1EB74A74130022045C33B140951AC365C4F7E0A1D94DE9EAA795087CCCCC3E87193E66E7DE67EB5" }, "unl": { "blob": "eyJzZXF1ZW5jZSI6MSwiZXhwaXJhdGlvbiI6NzY3Nzg0NjQ1LCJ2YWxpZGF0b3JzIjpbeyJ2YWxpZGF0aW9uX3B1YmxpY19rZXkiOiJFRDExREMwN0E2REEzRDA3QzAxMkUxOUZGOUFDNjdBQ0U1MzlBMjk1MTQ1QzhEQTM5NjQzN0NBQ0FFQzM2NzA5RjYiLCJtYW5pZmVzdCI6IkpBQUFBQUZ4SWUwUjNBZW0yajBId0JMaG4vbXNaNnpsT2FLVkZGeU5vNVpEZkt5dXcyY0o5bk1oQXF0T08zeFRwQ1pjVVpVdDJkTE5LQ2tobk4xUFZmWTVhZU9NWEpFUFBGOGNka2N3UlFJaEFOYlBEZlZLUzdwSWFqejlOMlFNNEY4Q3hhd1dJcmF0c3QyQjd3ZWR5czFGQWlCR0s3L1YwRDRBMlp2VFJEQlVQb0JLUVhpQk83QUV3ckRiOFlvN2RUSTJTWEFTUU9lQTIrVGdGVlRwNzdlN3VFZGpYNU1BYk8yU0t2dk8zOW1POEFyQkNqemRPUElxVmIrWi9QMDJUTmYzNjUrYjVCOGc3V0xEY0x2VkNpZUxjemw4MHcwPSJ9LHsidmFsaWRhdGlvbl9wdWJsaWNfa2V5IjoiRURBMTY0RjRCMzZDMkQ3MzA0NjJENUY3NjJCRkEyODA4QUE1MDkyQUJDRUNFQkIyNzA4OTUyNUQxRDA1NEJFMzNCIiwibWFuaWZlc3QiOiJKQUFBQUFGeEllMmhaUFN6YkMxekJHTFY5Mksvb29DS3BRa3F2T3pyc25DSlVsMGRCVXZqTzNNaEEveXBSNmZ3aXhSa1Y3NzVXdkRQZkRxL0RRbk5IY0FnbWZjWVhEZTdNb0IxZGtjd1JRSWhBTjRldTFvSGV0bFVDUmZmZ2FaeTkvTWJrQ0prWlFZdlJoMFVJZWFCaVZodkFpQWhGWXZKTXBPeVFpNGxFdzdzM0pTVTBMRm5FckRWVlRnK3NYUVk0c3Zta25BU1FCSGswakIweG1yL1UwbnkyajNrRmVvdEJUVEExVzdXcHBKbXFvYmR4SXkyR0w0QXBRaEpPcm5aRy93bXZqeFJCNXV4Y05FNUdUR1NZekQ3azhhVEx3bz0ifV19", @@ -1155,6 +1155,66 @@ std::string ImportTCPayment::w_seed = R"json({ } })json"; +class ImportTCHalving +{ +public: + static std::string base_genesis; +}; + +std::string ImportTCHalving::base_genesis = R"json({ + "ledger": { + "accepted": true, + "accountState": [ + { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Balance": "0", + "Flags": 0, + "LedgerEntryType": "AccountRoot", + "OwnerCount": 0, + "PreviousTxnID": "A92EF82C3C68F771927E3892A2F708F12CBD492EF68A860F042E4053C8EC6C8D", + "PreviousTxnLgrSeq": 0, + "Sequence": 1, + "index": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8" + }, + { + "Amendments": [], + "Flags": 0, + "LedgerEntryType": "Amendments", + "index": "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4" + }, + { + "BaseFee": "A", + "Flags": 0, + "LedgerEntryType": "FeeSettings", + "ReferenceFeeUnits": 10, + "ReserveBase": 1000000, + "ReserveIncrement": 200000, + "XahauActivationLgrSeq": 0, + "index": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651" + } + ], + "account_hash": "5DF3A98772FB73E782B8740E87885C6BAD9BA486422E3626DEF968AD2CB2C514", + "close_flags": 0, + "close_time": 0, + "close_time_human": "2000-Jan-01 00:00:00.000000", + "close_time_resolution": 10, + "closed": true, + "hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", + "ledger_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", + "ledger_index": "0", + "parent_close_time": 0, + "parent_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", + "seqNum": "0", + "totalCoins": "0", + "total_coins": "0", + "transaction_hash": "9A77D1D1A4B36DA77B9C4DC63FDEB8F821741D157802F9C42A6ED86003D8B4A0", + "transactions": [] + }, + "ledger_current_index": 0, + "status": "success", + "validated": true +})json"; + } // namespace test } // namespace ripple #endif \ No newline at end of file diff --git a/src/test/app/Import_test.cpp b/src/test/app/Import_test.cpp index 1564c65ed..934e5098b 100644 --- a/src/test/app/Import_test.cpp +++ b/src/test/app/Import_test.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #define BEAST_REQUIRE(x) \ { \ @@ -51,10 +52,6 @@ class Import_test : public beast::unit_test::suite "ED74D4036C6591A4BDF9C54CEFA39B996A" "5DCE5F86D11FDA1874481CE9D5A1CDC1"}; return envconfig([&](std::unique_ptr cfg) { - // cfg->section(SECTION_RPC_STARTUP) - // .append( - // "{ \"command\": \"log_level\", \"severity\": \"warn\" " - // "}"); cfg->NETWORK_ID = networkID; Section config; config.append( @@ -91,10 +88,6 @@ class Import_test : public beast::unit_test::suite "ED74D4036C6591A4BDF9C54CEFA39B996A" "5DCE5F86D11FDA1874481CE9D5A1CDC1"}; return envconfig([&](std::unique_ptr cfg) { - cfg->section(SECTION_RPC_STARTUP) - .append( - "{ \"command\": \"log_level\", \"severity\": \"warn\" " - "}"); cfg->NETWORK_ID = networkID; Section config; config.append( @@ -180,7 +173,7 @@ class Import_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, makeNetworkConfig(11111)}; + test::jtx::Env env{*this, makeNetworkConfig(21337)}; // old fee XRPAmount const value = Import::computeStartingBonus(*env.current()); @@ -250,7 +243,7 @@ class Import_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, makeNetworkConfig(11111)}; + test::jtx::Env env{*this, makeNetworkConfig(21337)}; auto const alice = Account("alice"); env.fund(XRP(1000), alice); @@ -356,7 +349,7 @@ class Import_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, makeNetworkConfig(11111)}; + test::jtx::Env env{*this, makeNetworkConfig(21337)}; // blob empty { @@ -1459,7 +1452,7 @@ class Import_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, makeNetworkConfig(11111)}; + test::jtx::Env env{*this, makeNetworkConfig(21337)}; std::string strJson = R"json({ "ledger": { @@ -2326,58 +2319,58 @@ class Import_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - // // w/ seed -> dne (bad signer) - // { - // test::jtx::Env env{*this, makeNetworkConfig(21337)}; + // w/ seed -> dne (bad signer) + { + test::jtx::Env env{*this, makeNetworkConfig(21337)}; - // auto const feeDrops = env.current()->fees().base; + auto const feeDrops = env.current()->fees().base; - // // confirm total coins header - // auto initCoins = env.current()->info().drops; - // BEAST_EXPECT(initCoins == 100'000'000'000'000'000); + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 100'000'000'000'000'000); - // // burn 10'000 xrp - // auto const master = Account("masterpassphrase"); - // env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); - // env.close(); + // burn 10'000 xrp + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); - // // confirm total coins header - // auto burnCoins = env.current()->info().drops; - // BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); + // confirm total coins header + auto burnCoins = env.current()->info().drops; + BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); - // // init env - // auto const alice = Account("alice"); - // auto const bob = Account("bob"); - // env.memoize(alice); - // env.memoize(bob); + // init env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.memoize(alice); + env.memoize(bob); - // // confirm env - // auto const preCoins = env.current()->info().drops; - // BEAST_EXPECT(preCoins == burnCoins - (4 * feeDrops)); - // auto const preAlice = env.balance(alice); - // BEAST_EXPECT(preAlice == XRP(0)); + // confirm env + auto const preCoins = env.current()->info().drops; + BEAST_EXPECT(preCoins == burnCoins); + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); - // // import tx - // auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); - // Json::Value tx = import(bob, xpopJson); - // tx[jss::Sequence] = 0; - // tx[jss::Fee] = 0; - // env(tx, bob, ter(temMALFORMED)); - // env.close(); + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(bob, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, bob, ter(temMALFORMED)); + env.close(); - // // confirm fee was minted - // auto const postAlice = env.balance(alice); - // BEAST_EXPECT(postAlice == preAlice); + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice); - // // confirm total coins header - // auto const postCoins = env.current()->info().drops; - // BEAST_EXPECT(postCoins == preCoins); + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == preCoins); - // // confirm account does not exist - // auto const [acct, acctSle] = - // accountKeyAndSle(*env.current(), alice); - // BEAST_EXPECT(acctSle == nullptr); - // } + // confirm account does not exist + auto const [acct, acctSle] = + accountKeyAndSle(*env.current(), alice); + BEAST_EXPECT(acctSle == nullptr); + } // w/ seed -> dne { @@ -2442,11 +2435,19 @@ class Import_test : public beast::unit_test::suite auto const feeDrops = env.current()->fees().base; + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 100'000'000'000'000'000); + // burn 10'000 xrp auto const master = Account("masterpassphrase"); env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); env.close(); + // confirm total coins header + auto burnCoins = env.current()->info().drops; + BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); + // init env auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2487,11 +2488,19 @@ class Import_test : public beast::unit_test::suite auto const feeDrops = env.current()->fees().base; + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 100'000'000'000'000'000); + // burn 10'000 xrp auto const master = Account("masterpassphrase"); env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); env.close(); + // confirm total coins header + auto burnCoins = env.current()->info().drops; + BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); + // init env auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2537,61 +2546,75 @@ class Import_test : public beast::unit_test::suite } // w/ signers list -> dne - { -// test::jtx::Env env{*this, makeNetworkConfig(21337)}; - test::jtx::Env env{*this, makeNetworkConfig(21337), supported_amendments(), nullptr, - beast::severities::kTrace - }; + { + test::jtx::Env env{*this, makeNetworkConfig(21337)}; - auto const feeDrops = env.current()->fees().base; + auto const feeDrops = env.current()->fees().base; - // burn 1000 xrp - auto const master = Account("masterpassphrase"); - env(noop(master), fee(1'000'000'000), ter(tesSUCCESS)); - env.close(); + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 100'000'000'000'000'000); - // init env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - auto const carol = Account("carol"); - env.memoize(alice); - env.memoize(bob); - env.memoize(carol); + // burn 10'000 xrp + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); - // confirm env - auto const preCoins = env.current()->info().drops; - BEAST_EXPECT(preCoins == 99'999'999'999'900'000); - auto const preAlice = env.balance(alice); - BEAST_EXPECT(preAlice == XRP(0)); + // confirm total coins header + auto burnCoins = env.current()->info().drops; + BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); - // import tx - auto const xpopJson = loadXpop(ImportTCAccountSet::w_signers); - Json::Value tx = import(alice, xpopJson); - tx[jss::Sequence] = 0; - tx[jss::Fee] = 0; - env( - tx, - alice, - msig(bob, carol), - M("accountset with signers 2573"), - fee(3 * feeDrops), - ter(tesSUCCESS) - ); - env.close(); + // init env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.memoize(alice); + env.memoize(bob); + env.memoize(carol); - // confirm fee was minted - auto const postAlice = env.balance(alice); - BEAST_EXPECT(postAlice == preAlice + XRP(0.00001) + XRP(2)); - auto const postCoins = env.current()->info().drops; - BEAST_EXPECT(postCoins == 99'999'999'999'900'000); + // confirm env + auto const preCoins = env.current()->info().drops; + BEAST_EXPECT(preCoins == burnCoins); + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); - // confirm signers not set - auto const k = keylet::signers(bob); - BEAST_EXPECT(env.current()->read(k) == nullptr); - // alice cannnot sign - env(noop(alice), sig(alice), fee(feeDrops), - ter(tefMASTER_DISABLED)); - } + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_signers); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env( + tx, + alice, + msig(bob, carol), + ter(tesSUCCESS) + ); + env.close(); + + // total burn = burn drops - feeDrops + auto const totalBurn = drops(48) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + totalBurn); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == preCoins + totalBurn); + + // confirm signers not set + auto const k = keylet::signers(alice); + BEAST_EXPECT(env.current()->read(k) == nullptr); + env(noop(alice), + msig(bob, carol), + fee(3 * feeDrops), + ter(tefNOT_MULTI_SIGNING)); + env.close(); + + // alice cannnot sign + env(noop(alice), sig(alice), fee(feeDrops), + ter(tefMASTER_DISABLED)); + } // w/ seed -> funded { @@ -2758,6 +2781,14 @@ class Import_test : public beast::unit_test::suite // confirm signers not set auto const k = keylet::signers(alice); BEAST_EXPECT(env.current()->read(k) == nullptr); + env(noop(alice), + msig(bob, carol), + fee(3 * feeDrops), + ter(tefNOT_MULTI_SIGNING)); + env.close(); + + // alice cannnot sign + env(noop(alice), sig(alice), fee(feeDrops), ter(tesSUCCESS)); } } @@ -2944,76 +2975,73 @@ class Import_test : public beast::unit_test::suite env(noop(alice), sig(carol), fee(feeDrops), ter(tesSUCCESS)); } - // // w/ signers -> dne - // { - // test::jtx::Env env{*this, makeNetworkConfig(21337)}; + // w/ signers -> dne + { + test::jtx::Env env{*this, makeNetworkConfig(21337)}; - // auto const feeDrops = env.current()->fees().base; + auto const feeDrops = env.current()->fees().base; - // // confirm env - // auto initCoins = env.current()->info().drops; - // BEAST_EXPECT(initCoins == 100'000'000'000'000'000); + // confirm env + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 100'000'000'000'000'000); - // // burn 10'000 xrp - // auto const master = Account("masterpassphrase"); - // env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); - // env.close(); + // burn 10'000 xrp + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); - // auto burnCoins = env.current()->info().drops; - // BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); + auto burnCoins = env.current()->info().drops; + BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); - // // init env - // auto const alice = Account("alice"); - // auto const bob = Account("bob"); - // auto const carol = Account("carol"); - // auto const dave = Account("dave"); - // env.memoize(alice); - // env.memoize(bob); - // env.memoize(carol); - // env.memoize(dave); + // init env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const dave = Account("dave"); + env.memoize(alice); + env.memoize(bob); + env.memoize(carol); + env.memoize(dave); - // // confirm env - // auto const preCoins = env.current()->info().drops; - // BEAST_EXPECT(preCoins == burnCoins); - // auto const preAlice = env.balance(alice); - // BEAST_EXPECT(preAlice == XRP(0)); + // confirm env + auto const preCoins = env.current()->info().drops; + BEAST_EXPECT(preCoins == burnCoins); + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); - // // import tx - // auto const xpopJson = loadXpop(ImportTCSetRegularKey::w_signers); - // Json::Value tx = import(alice, xpopJson); - // tx[jss::Sequence] = 0; - // tx[jss::Fee] = 0; - // env( - // tx, - // alice, - // msig(bob, carol), - // fee(3 * feeDrops), - // ter(tesSUCCESS) - // ); - // env.close(); + // import tx + auto const xpopJson = loadXpop(ImportTCSetRegularKey::w_signers); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env( + tx, + alice, + msig(bob, carol), + ter(tesSUCCESS) + ); + env.close(); - // // total burn = burn drops - fee drops - // auto const totalBurn = drops(12) + XRP(2); + // total burn = burn drops - fee drops + auto const totalBurn = drops(48) + XRP(2); - // // confirm fee was minted - // auto const postAlice = env.balance(alice); - // BEAST_EXPECT(postAlice == preAlice + totalBurn); + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + totalBurn); - // // confirm total coins header - // auto const postCoins = env.current()->info().drops; - // BEAST_EXPECT(postCoins == preCoins + totalBurn; + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == preCoins + totalBurn); - // // confirm signers not set - // auto const k = keylet::signers(alice); - // BEAST_EXPECT(env.current()->read(k) == nullptr); + // confirm signers not set + auto const k = keylet::signers(alice); + BEAST_EXPECT(env.current()->read(k) == nullptr); - // // confirm regular key - // auto const [acct, acctSle] = - // accountKeyAndSle(*env.current(), alice); - // BEAST_EXPECT(acctSle && acctSle->isFieldPresent(sfRegularKey) && - // acctSle->getAccountID(sfRegularKey) == dave.id()); - // env(noop(alice), sig(dave), fee(feeDrops), ter(tesSUCCESS)); - // } + // confirm regular key + auto const [acct, acctSle] = accountKeyAndSle(*env.current(), alice); + BEAST_EXPECT(acctSle && acctSle->isFieldPresent(sfRegularKey) && acctSle->getAccountID(sfRegularKey) == dave.id()); + env(noop(alice), sig(dave), fee(feeDrops), ter(tesSUCCESS)); + } // w/ seed -> funded { @@ -3198,75 +3226,79 @@ class Import_test : public beast::unit_test::suite env(noop(alice), sig(carol), fee(feeDrops), ter(tesSUCCESS)); } - // // w/ signers list -> funded (update regular key) - // { - // test::jtx::Env env{*this, makeNetworkConfig(21337)}; + // w/ signers list -> funded (update regular key) + { + test::jtx::Env env{*this, makeNetworkConfig(21337)}; - // auto const feeDrops = env.current()->fees().base; + auto const feeDrops = env.current()->fees().base; - // // confirm total coins header - // auto initCoins = env.current()->info().drops; - // BEAST_EXPECT(initCoins == 100'000'000'000'000'000); + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 100'000'000'000'000'000); - // // burn 10'000 xrp - // auto const master = Account("masterpassphrase"); - // env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); - // env.close(); + // burn 10'000 xrp + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); - // // confirm total coins header - // auto burnCoins = env.current()->info().drops; - // BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); + // confirm total coins header + auto burnCoins = env.current()->info().drops; + BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); - // auto const alice = Account("alice"); - // auto const bob = Account("bob"); - // auto const carol = Account("carol"); - // auto const dave = Account("dave"); - // auto const elsa = Account("elsa"); - // env.fund(XRP(1000), alice, bob, carol, dave, elsa); - // env.close(); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const dave = Account("dave"); + auto const elsa = Account("elsa"); + env.fund(XRP(1000), alice, bob, carol, dave, elsa); + env.close(); - // // confirm env - // auto const envCoins = env.current()->info().drops; - // BEAST_EXPECT(envCoins == burnCoins - (10 * feeDrops)); - // auto const envAlice = env.balance(alice); - // BEAST_EXPECT(envAlice == XRP(1000)); + // confirm env + auto const envCoins = env.current()->info().drops; + BEAST_EXPECT(envCoins == burnCoins - (10 * feeDrops)); + auto const envAlice = env.balance(alice); + BEAST_EXPECT(envAlice == XRP(1000)); - // // set the regular key - // env(regkey(alice, elsa)); - // env(noop(alice), sig(elsa), fee(feeDrops), ter(tesSUCCESS)); - // env.close(); + // set the regular key + env(regkey(alice, elsa)); + env(noop(alice), sig(elsa), fee(feeDrops), ter(tesSUCCESS)); + env.close(); - // // confirm total coins header - // auto const preCoins = env.current()->info().drops; - // BEAST_EXPECT(preCoins == envCoins - (2 * feeDrops)); - // auto const preAlice = env.balance(alice); - // BEAST_EXPECT(preAlice == envAlice - (2 * feeDrops)); + // confirm total coins header + auto const preCoins = env.current()->info().drops; + BEAST_EXPECT(preCoins == envCoins - (2 * feeDrops)); + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == envAlice - (2 * feeDrops)); - // // import tx - // auto const xpopJson = loadXpop(ImportTCSetRegularKey::w_signers); - // env(import(alice, xpopJson), - // msig(bob, carol), - // fee(3 * feeDrops), - // ter(tesSUCCESS)); - // env.close(); + // import tx + auto const xpopJson = loadXpop(ImportTCSetRegularKey::w_signers); + env(import(alice, xpopJson), + msig(bob, carol), + fee(3 * feeDrops), + ter(tesSUCCESS)); + env.close(); - // // total burn = burn drops - fee drops - // auto const totalBurn = drops(48) - feeDrops; + // total burn = burn drops - fee drops + auto const totalBurn = drops(48) - (3 * feeDrops); - // // confirm fee was minted - // auto const postAlice = env.balance(alice); - // BEAST_EXPECT(postAlice == preAlice + totalBurn); + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + totalBurn); - // // confirm total coins header - // auto const postCoins = env.current()->info().drops; - // BEAST_EXPECT(postCoins == preCoins + totalBurn); + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == preCoins + totalBurn); - // // confirm regular key - // auto const [acct, acctSle] = - // accountKeyAndSle(*env.current(), alice); - // BEAST_EXPECT(acctSle->getAccountID(sfRegularKey) == dave.id()); - // env(noop(alice), sig(carol), fee(feeDrops), ter(tesSUCCESS)); - // } + // confirm regular key + auto const [acct, acctSle] = + accountKeyAndSle(*env.current(), alice); + BEAST_EXPECT(acctSle->getAccountID(sfRegularKey) == dave.id()); + env(noop(alice), sig(dave), fee(feeDrops), ter(tesSUCCESS)); + + // confirm signers list not set + auto const k = keylet::signers(alice); + BEAST_EXPECT(env.current()->read(k) == nullptr); + } // seed -> funded (empty regular key) { @@ -3553,7 +3585,7 @@ class Import_test : public beast::unit_test::suite // w/ seed -> dne w/ seed (Bad Fee) { - test::jtx::Env env{*this, makeMaxFeeConfig(21337)}; + test::jtx::Env env{*this, makeNetworkConfig(21337)}; auto const feeDrops = env.current()->fees().base; @@ -3592,23 +3624,8 @@ class Import_test : public beast::unit_test::suite Json::Value tx = import(alice, xpopJson); tx[jss::Sequence] = 0; // tx[jss::Fee] = 0; - env(tx, alice, ter(tefINTERNAL)); + env(tx, alice, ter(temBAD_FEE)); env.close(); - - // // total burn = burn drops - fee drops - // auto const totalBurn = drops(12) + XRP(2); - - // // confirm fee was minted - // auto const postAlice = env.balance(alice); - // BEAST_EXPECT(postAlice == preAlice + totalBurn); - - // // confirm fee was minted - // auto const postCoins = env.current()->info().drops; - // BEAST_EXPECT(postCoins == preCoins + totalBurn); - - // // confirm signers not set - // auto const k = keylet::signers(alice); - // BEAST_EXPECT(env.current()->read(k) == nullptr); } // w/ seed -> dne w/ seed @@ -3774,7 +3791,7 @@ class Import_test : public beast::unit_test::suite env(noop(alice), fee(feeDrops), ter(tefMASTER_DISABLED)); } - // w/ signers -> dne failure + // w/ signers -> dne { test::jtx::Env env{*this, makeNetworkConfig(21337)}; @@ -3797,7 +3814,11 @@ class Import_test : public beast::unit_test::suite auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); + auto const dave = Account("dave"); + auto const elsa = Account("elsa"); env.memoize(alice); + env.memoize(dave); + env.memoize(elsa); // confirm total coins header auto const preCoins = env.current()->info().drops; @@ -3815,100 +3836,35 @@ class Import_test : public beast::unit_test::suite env(tx, alice, msig(bob, carol), - fee(3 * feeDrops), - ter(tefINTERNAL)); + ter(tesSUCCESS)); env.close(); + + // confirm signers set + auto const k = keylet::signers(alice); + auto const [signers, signersSle] = signersKeyAndSle(*env.current(), alice); + auto const signerEntries = signersSle->getFieldArray(sfSignerEntries); + BEAST_EXPECT(signerEntries.size() == 2); + BEAST_EXPECT(signerEntries[0u].getFieldU16(sfSignerWeight) == 1); + BEAST_EXPECT(signerEntries[0u].getAccountID(sfAccount) == dave.id()); + BEAST_EXPECT(signerEntries[1u].getFieldU16(sfSignerWeight) == 1); + BEAST_EXPECT(signerEntries[1u].getAccountID(sfAccount) == elsa.id()); + + // confirm multisign tx + env.close(); + auto const aliceSeq = env.seq(alice); + env(noop(alice), + msig(dave, elsa), + fee(3 * feeDrops), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(env.seq(alice) == aliceSeq + 1); + + // confirm regular key bad auth + env(noop(alice), sig(bob), fee(feeDrops), ter(tefBAD_AUTH)); + + // confirm noop master disabled + env(noop(alice), fee(feeDrops), ter(tefMASTER_DISABLED)); } - - // // w/ signers -> dne - // { - // test::jtx::Env env{*this, makeNetworkConfig(21337)}; - - // auto const feeDrops = env.current()->fees().base; - - // // confirm total coins header - // auto initCoins = env.current()->info().drops; - // BEAST_EXPECT(initCoins == 100'000'000'000'000'000); - - // // burn 100,000 xrp - // auto const master = Account("masterpassphrase"); - // env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); - // env.close(); - - // // confirm total coins header - // auto burnCoins = env.current()->info().drops; - // BEAST_EXPECT(burnCoins == initCoins - 10'000'000'000); - - // // init env - // auto const alice = Account("alice"); - // auto const bob = Account("bob"); - // auto const carol = Account("carol"); - // auto const dave = Account("dave"); - // auto const elsa = Account("elsa"); - // env.memoize(alice); - // env.fund(XRP(1000), bob, carol, dave, elsa); - // env.close(); - - // // confirm total coins header - // auto const preCoins = env.current()->info().drops; - // BEAST_EXPECT(preCoins == burnCoins); - - // // confirm alice balance - // auto const preAlice = env.balance(alice); - // BEAST_EXPECT(preAlice == XRP(0)); - - // // import tx - // auto const xpopJson = loadXpop(ImportTCSignersListSet::w_signers); - // Json::Value tx = import(alice, xpopJson); - // tx[jss::Sequence] = 0; - // tx[jss::Fee] = 0; - // env(tx, - // alice, - // msig(bob, carol), - // fee(3 * feeDrops), - // ter(tesSUCCESS)); - // env.close(); - - // // total burn = (burn drops + burn fee drops) + initial value - // auto const totalBurn = XRP(2) + drops(48) + XRP(2); - - // // confirm fee was minted - // auto const postAlice = env.balance(alice); - // BEAST_EXPECT(postAlice == preAlice + totalBurn); - - // // confirm fee was minted - // auto const postCoins = env.current()->info().drops; - // BEAST_EXPECT(postCoins == preCoins + totalBurn); - - // // confirm signers set - // auto const k = keylet::signers(alice); - // auto const [signers, signersSle] = - // signersKeyAndSle(*env.current(), alice); - // auto const signerEntries = - // signersSle->getFieldArray(sfSignerEntries); - // BEAST_EXPECT(signerEntries.size() == 2); - // BEAST_EXPECT(signerEntries[0u].getFieldU16(sfSignerWeight) == 1); - // BEAST_EXPECT( - // signerEntries[0u].getAccountID(sfAccount) == dave.id()); - // BEAST_EXPECT(signerEntries[1u].getFieldU16(sfSignerWeight) == 1); - // BEAST_EXPECT(signerEntries[1u].getAccountID(sfAccount) == elsa.id()); - - // // confirm multisign tx - // env.close(); - // auto const aliceSeq = env.seq(alice); - // env(noop(alice), - // msig(dave, elsa), - // fee(3 * feeDrops), - // ter(tesSUCCESS)); - // env.close(); - // BEAST_EXPECT(env.seq(alice) == aliceSeq + 1); - - // // confirm regular key bad auth - // env(noop(alice), sig(bob), fee(feeDrops), ter(tefBAD_AUTH)); - - // // confirm noop master disabled - // env(noop(alice), fee(feeDrops), ter(tefMASTER_DISABLED)); - // } // w/ seed -> funded { @@ -4553,6 +4509,492 @@ class Import_test : public beast::unit_test::suite // } } + std::unique_ptr + makeGenesisConfig( + FeatureBitset features, + uint32_t networkID, + std::string fee, + std::string a_res, + std::string o_res, + uint32_t ledgerID) + { + using namespace jtx; + + // IMPORT VL KEY + std::vector const keys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A" + "5DCE5F86D11FDA1874481CE9D5A1CDC1"}; + + Json::Value jsonValue; + Json::Reader reader; + reader.parse(ImportTCHalving::base_genesis, jsonValue); + + foreachFeature(features, [&](uint256 const& feature) { + std::string featureName = featureToName(feature); + std::optional featureHash = getRegisteredFeature(featureName); + if (featureHash.has_value()) + { + std::string hashString = to_string(featureHash.value()); + jsonValue["ledger"]["accountState"][1]["Amendments"].append(hashString); + } + }); + + jsonValue["ledger_current_index"] = ledgerID; + jsonValue["ledger"]["ledger_index"] = to_string(ledgerID); + jsonValue["ledger"]["seqNum"] = to_string(ledgerID); + + return envconfig([&](std::unique_ptr cfg) { + cfg->NETWORK_ID = networkID; + cfg->START_LEDGER = jsonValue.toStyledString(); + cfg->START_UP = Config::LOAD_JSON; + Section config; + config.append( + {"reference_fee = " + fee, + "account_reserve = " + a_res, + "owner_reserve = " + o_res}); + auto setup = setup_FeeVote(config); + cfg->FEES = setup; + + for (auto const& strPk : keys) + { + auto pkHex = strUnHex(strPk); + if (!pkHex) + Throw( + "Import VL Key '" + strPk + "' was not valid hex."); + + auto const pkType = publicKeyType(makeSlice(*pkHex)); + if (!pkType) + Throw( + "Import VL Key '" + strPk + + "' was not a valid key type."); + + cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex)); + } + return cfg; + }); + } + + void + testHalving(FeatureBitset features) + { + testcase("halving"); + + using namespace test::jtx; + using namespace std::literals; + + // Halving @ ledger seq 1'999'999 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 1999998 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 1'999'999); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = XRP(1'000) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 2'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 1999998 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + env.close(); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 2'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = XRP(1'000) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 2'000'001 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 1999998 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + env.close(); + env.close(); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 2'000'001); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(999999964) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 5'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 4999999 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 5'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(892857142) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 20'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 19999999 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 20'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(357142857) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 29'999'998 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 29999998 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 29'999'999); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(35) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 30'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 29999998 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + env.close(); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 30'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(0) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 50'000'001 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 50000000 + ), + features + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 50'000'001); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(0) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + } + public: void run() override @@ -4585,6 +5027,7 @@ public: testImportSequence(features); testMaxSupply(features); testMinMax(features); + testHalving(features - featureOwnerPaysFee); } };