Merge pull request #68 from Xahau/denis-import

[WIP] `Import` Quality Control (tests & minor fixes)
This commit is contained in:
RichardAH
2023-06-26 14:56:58 +02:00
committed by GitHub
14 changed files with 3492 additions and 684 deletions

13
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"C_Cpp.formatting": "clangFormat",
"C_Cpp.clang_format_path": "/Users/dustedfloor/projects/transia-rnd/rippled-icv2/.clang-format",
"C_Cpp.clang_format_fallbackStyle": "{ ColumnLimit: 0 }",
"[cpp]":{
"editor.wordBasedSuggestions": false,
"editor.suggest.insertMode": "replace",
"editor.semanticHighlighting.enabled": true,
"editor.tabSize": 4,
"editor.defaultFormatter": "xaver.clang-format",
"editor.formatOnSave": false
}
}

View File

@@ -703,6 +703,7 @@ if (tests)
src/test/app/Flow_test.cpp
src/test/app/Freeze_test.cpp
src/test/app/HashRouter_test.cpp
src/test/app/Import_test.cpp
src/test/app/LedgerHistory_test.cpp
src/test/app/LedgerLoad_test.cpp
src/test/app/LedgerMaster_test.cpp

View File

@@ -2,6 +2,8 @@
NIH dep: wasmedge: web assembly runtime for hooks.
#]===================================================================]
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
@@ -24,6 +26,7 @@ ExternalProject_Add (wasmedge_src
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DLLVM_DIR=${LLVM_DIR}
-DLLVM_LIBRARY_DIR=${LLVM_LIBRARY_DIR}
-DLLVM_ENABLE_TERMINFO=OFF
$<$<NOT:$<BOOL:${is_multiconfig}>>:-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}>
$<$<BOOL:${MSVC}>:
"-DCMAKE_C_FLAGS=-GR -Gd -fp:precise -FS -MP -march=native"
@@ -63,4 +66,6 @@ set_target_properties (wasmedge PROPERTIES
"${wasmedge_src_BINARY_DIR}/include/api/"
)
target_link_libraries (ripple_libs INTERFACE wasmedge)
target_link_libraries(ripple_libs INTERFACE ${CURSES_LIBRARY})
target_link_libraries(ripple_libs INTERFACE xar)
add_library (NIH::WasmEdge ALIAS wasmedge)

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include <ripple/protocol/Import.h>
#include <ripple/app/tx/impl/Import.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
@@ -61,412 +62,6 @@ Import::makeTxConsequences(PreflightContext const& ctx)
return TxConsequences{ctx.tx, calculate(ctx)};
}
inline bool
isHex(std::string const& str)
{
return str.find_first_not_of("0123456789abcdefABCDEF") == std::string::npos;
}
inline bool
isBase58(std::string const& str)
{
return str.find_first_not_of(
"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") ==
std::string::npos;
}
inline bool
isBase64(std::string const& str)
{
return str.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"
"/=") == std::string::npos;
}
inline std::optional<uint64_t>
parse_uint64(std::string const& str)
{
uint64_t result;
auto [ptr, ec] =
std::from_chars(str.data(), str.data() + str.size(), result);
if (ec == std::errc())
return result;
return {};
}
inline bool
syntaxCheckProof(Json::Value const& proof, beast::Journal const& j, int depth = 0)
{
if (depth > 64)
return false;
if (proof.isArray())
{
// List form
if (proof.size() != 16)
{
JLOG(j.warn()) << "XPOP.transaction.proof list should be exactly 16 entries";
return false;
}
for (const auto& entry : proof)
{
if (entry.isString())
{
if (!isHex(entry.asString()) || entry.asString().size() != 64)
{
JLOG(j.warn()) << "XPOP.transaction.proof list entry missing "
"or wrong format "
<< "(should be hex string with 64 characters)";
return false;
}
}
else if (entry.isArray())
{
if (!syntaxCheckProof(entry, j, depth + 1))
return false;
}
else
{
JLOG(j.warn())
<< "XPOP.transaction.proof list entry has wrong format";
return false;
}
}
}
else if (proof.isObject())
{
// Tree form
if (depth == 0) // root is special case
{
if (!proof["hash"].isString() ||
proof["hash"].asString().size() != 64 ||
!proof["key"].isString() ||
proof["key"].asString().size() != 64 ||
!proof["children"].isObject())
{
JLOG(j.warn())
<< "XPOP.transaction.proof tree node has wrong format (root)";
return false;
}
return syntaxCheckProof(proof["children"], j, depth + 1);
}
for (const auto& branch : proof.getMemberNames())
{
if (branch.size() != 1 || !isHex(branch))
{
JLOG(j.warn()) << "XPOP.transaction.proof child node was not 0-F "
"hex nibble";
return false;
}
const auto& node = proof[branch];
if (!node.isObject() || !node["hash"].isString() ||
node["hash"].asString().size() != 64 ||
!node["key"].isString() ||
node["key"].asString().size() != 64 ||
!node["children"].isObject())
{
JLOG(j.warn())
<< "XPOP.transaction.proof tree node has wrong format";
return false;
}
if (!syntaxCheckProof(node["children"], j, depth + 1))
return false;
}
}
else
{
JLOG(j.warn()) << "XPOP.transaction.proof has wrong format (should be "
"array or object)";
return false;
}
return true;
}
// does not check signature etc
inline
std::optional<Json::Value>
syntaxCheckXPOP(Blob const& blob, beast::Journal const& j)
{
if (blob.empty())
return {};
std::string strJson(blob.begin(), blob.end());
if (strJson.empty())
return {};
try
{
Json::Value xpop;
Json::Reader reader;
if (!reader.parse(strJson, xpop))
{
JLOG(j.warn()) << "XPOP isn't a string";
return {};
}
if (!xpop.isObject())
{
JLOG(j.warn()) << "XPOP is not a JSON object";
return {};
}
if (!xpop["ledger"].isObject())
{
JLOG(j.warn()) << "XPOP.ledger is not a JSON object";
return {};
}
if (!xpop["transaction"].isObject())
{
JLOG(j.warn()) << "XPOP.transaction is not a JSON object";
return {};
}
if (!xpop["validation"].isObject())
{
JLOG(j.warn()) << "XPOP.validation is not a JSON object";
return {};
}
if (!xpop["ledger"]["acroot"].isString() ||
xpop["ledger"]["acroot"].asString().size() != 64 ||
!isHex(xpop["ledger"]["acroot"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.acroot missing or wrong format (should "
"be hex string)";
return {};
}
if (!xpop["ledger"]["txroot"].isString() ||
xpop["ledger"]["txroot"].asString().size() != 64 ||
!isHex(xpop["ledger"]["txroot"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.txroot missing or wrong format "
"(should be hex string)";
return {};
}
if (!xpop["ledger"]["phash"].isString() ||
xpop["ledger"]["phash"].asString().size() != 64 ||
!isHex(xpop["ledger"]["phash"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.phash missing or wrong format "
"(should be hex string)";
return {};
}
if (!xpop["ledger"]["close"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.close missing or wrong format "
"(should be int)";
return {};
}
if (!xpop["ledger"]["coins"].isInt())
{
// pass
}
else if (xpop["ledger"]["coins"].isString())
{
if (!parse_uint64(xpop["ledger"]["coins"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.coins missing or wrong format "
"(should be int or string)";
return {};
}
}
else
{
JLOG(j.warn()) << "XPOP.ledger.coins missing or wrong format "
"(should be int or string)";
return {};
}
if (!xpop["ledger"]["cres"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.cres missing or wrong format "
"(should be int)";
return {};
}
if (!xpop["ledger"]["flags"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.flags missing or wrong format "
"(should be int)";
return {};
}
if (!xpop["ledger"]["pclose"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.pclose missing or wrong format "
"(should be int)";
return {};
}
if (!xpop["transaction"]["blob"].isString() ||
!isHex(xpop["transaction"]["blob"].asString()))
{
JLOG(j.warn()) << "XPOP.transaction.blob missing or wrong format "
"(should be hex string)";
return {};
}
if (!xpop["transaction"]["meta"].isString() ||
!isHex(xpop["transaction"]["meta"].asString()))
{
JLOG(j.warn()) << "XPOP.transaction.meta missing or wrong format "
"(should be hex string)";
return {};
}
if (!syntaxCheckProof(xpop["transaction"]["proof"], j))
{
JLOG(j.warn()) << "XPOP.transaction.proof failed syntax check "
"(tree/list form)";
return {};
}
if (!xpop["validation"]["data"].isObject())
{
JLOG(j.warn()) << "XPOP.validation.data missing or wrong format "
"(should be JSON object)";
return {};
}
if (!xpop["validation"]["unl"].isObject())
{
JLOG(j.warn()) << "XPOP.validation.unl missing or wrong format "
"(should be JSON object)";
return {};
}
for (const auto& key : xpop["validation"]["data"].getMemberNames())
{
const auto& value = xpop["validation"]["data"][key];
if (!isBase58(key) || !value.isString() || !isHex(value.asString()))
{
JLOG(j.warn()) << "XPOP.validation.data entry has wrong format "
<< "(key should be base58 string and value "
"should be hex string)";
return {};
}
}
for (const auto& key : xpop["validation"]["unl"].getMemberNames())
{
const auto& value = xpop["validation"]["unl"][key];
if (key == "public_key")
{
if (!value.isString() || !isHex(value.asString()))
{
JLOG(j.warn()) << "XPOP.validation.unl.public_key missing or "
"wrong format (should be hex string)";
return {};
}
auto pk = strUnHex(value.asString());
if (!publicKeyType(makeSlice(*pk)))
{
JLOG(j.warn()) << "XPOP.validation.unl.public_key invalid key type.";
return {};
}
}
else if (key == "manifest")
{
if (!value.isString())
{
JLOG(j.warn()) << "XPOP.validation.unl.manifest missing or "
"wrong format (should be string)";
return {};
}
}
else if (key == "blob")
{
if (!value.isString() || !isBase64(value.asString()))
{
JLOG(j.warn()) << "XPOP.validation.unl.blob missing or wrong "
"format (should be base64 string)";
return {};
}
}
else if (key == "signature")
{
if (!value.isString() || !isHex(value.asString()))
{
JLOG(j.warn())
<< "XPOP.validation.unl.signature missing or wrong "
"format (should be hex string)";
return {};
}
}
else if (key == "version")
{
if (!value.isInt())
{
JLOG(j.warn()) << "XPOP.validation.unl.version missing or "
"wrong format (should be int)";
return {};
}
}
else
{
if (!value.isObject() && !value.isString())
{
JLOG(j.warn())
<< "XPOP.validation.unl entry has wrong format";
return {};
}
}
}
return xpop;
}
catch (...)
{
JLOG(j.warn()) << "An exception occurred during XPOP validation";
}
return {};
}
inline
std::optional<
std::pair<
uint32_t, // sequence
PublicKey // master key
>>
getVLInfo(Json::Value const& xpop, beast::Journal const& j)
{
auto const data = base64_decode(xpop[jss::validation][jss::unl][jss::blob].asString());
Json::Reader r;
Json::Value list;
if (!r.parse(data, list))
{
JLOG(j.warn())
<< "Import: unl blob was not valid json (after base64 decoding)";
return {};
}
auto const sequence = list[jss::sequence].asUInt();
auto const m =
deserializeManifest(base64_decode(xpop[jss::validation][jss::unl][jss::manifest].asString()));
if (!m)
{
JLOG(j.warn())
<< "Import: failed to deserialize manifest";
return {};
}
return { {sequence, m->masterKey} };
}
std::pair<
std::unique_ptr<STTx const>, // txn
@@ -535,6 +130,14 @@ Import::preflight(PreflightContext const& ctx)
auto& tx = ctx.tx;
if (tx.getFieldAmount(sfFee) != beast::zero)
{
JLOG(ctx.j.warn())
<< "Import: sfFee must be 0 "
<< tx.getTransactionID();
return temMALFORMED;
}
if (tx.getFieldVL(sfBlob).size() > (512 * 1024))
{
JLOG(ctx.j.warn())
@@ -554,7 +157,7 @@ Import::preflight(PreflightContext const& ctx)
}
// parse blob as json
auto const xpop =
auto const xpop =
syntaxCheckXPOP(tx.getFieldVL(sfBlob), ctx.j);
if (!xpop)
@@ -778,13 +381,31 @@ Import::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (!(list.isMember(jss::sequence) && list[jss::sequence].isInt() &&
list.isMember(jss::expiration) && list[jss::expiration].isInt() &&
(!list.isMember(jss::effective) || list[jss::effective].isInt()) &&
list.isMember(jss::validators) && list[jss::validators].isArray()))
if (!list.isMember(jss::sequence) || !list[jss::sequence].isInt())
{
JLOG(ctx.j.warn())
<< "Import: unl blob json (after base64 decoding) lacked required fields and/or types "
<< "Import: unl blob json (after base64 decoding) lacked required field (sequence) and/or types "
<< tx.getTransactionID();
return temMALFORMED;
}
if (!list.isMember(jss::expiration) || !list[jss::expiration].isInt())
{
JLOG(ctx.j.warn())
<< "Import: unl blob json (after base64 decoding) lacked required field (expiration) and/or types "
<< tx.getTransactionID();
return temMALFORMED;
}
if (list.isMember(jss::effective) && !list[jss::effective].isInt())
{
JLOG(ctx.j.warn())
<< "Import: unl blob json (after base64 decoding) lacked required field (effective) and/or types "
<< tx.getTransactionID();
return temMALFORMED;
}
if (!list.isMember(jss::validators) || !list[jss::validators].isArray())
{
JLOG(ctx.j.warn())
<< "Import: unl blob json (after base64 decoding) lacked required field (validators) and/or types "
<< tx.getTransactionID();
return temMALFORMED;
}
@@ -838,7 +459,7 @@ Import::preflight(PreflightContext const& ctx)
if (!([](Json::Value const& proof, std::string hash) -> bool
{
auto const proofContains =
[](Json::Value const* proof, std::string hash, int depth = 0, auto proofContains) -> bool
[](Json::Value const* proof, std::string hash, int depth = 0, auto proofContains = nullptr) -> bool
{
if (depth > 32)
return false;
@@ -885,7 +506,7 @@ Import::preflight(PreflightContext const& ctx)
([](Json::Value const& proof) -> uint256
{
auto hashProof =
[](Json::Value const& proof, int depth = 0, auto const& hashProof) -> uint256
[](Json::Value const& proof, int depth = 0, auto const& hashProof = nullptr) -> uint256
{
const uint256 nullhash;
@@ -918,6 +539,7 @@ Import::preflight(PreflightContext const& ctx)
for (int x = 0; x < 16; ++x)
{
// Duplicate / Sanity
std::string const nibble (1, "0123456789ABCDEF"[x]);
if (!proof[jss::children].isMember(nibble))
hash_append(h, nullhash);
@@ -948,6 +570,7 @@ Import::preflight(PreflightContext const& ctx)
auto coins = parse_uint64(lgr[jss::coins].asString());
uint256 phash, acroot;
// Duplicate / Sanity - syntaxCheckXPOP
if (!coins ||
!phash.parseHex(lgr[jss::phash].asString()) ||
!acroot.parseHex(lgr[jss::acroot].asString()))
@@ -1044,11 +667,18 @@ Import::preflight(PreflightContext const& ctx)
validatorsMaster[nodemaster] = nodepub;
}
JLOG(ctx.j.trace())
<< "totalValidatorCount: " << totalValidatorCount;
uint64_t quorum = totalValidatorCount * 0.8;
if (quorum == 0)
{
JLOG(ctx.j.warn())
<< "Import: resetting quorum to 1. "
<< tx.getTransactionID();
quorum = 1;
}
// count how many validations this ledger hash has
@@ -1185,7 +815,7 @@ Import::preflight(PreflightContext const& ctx)
// if it is less than the Account Sequence in the xpop then mint and
// update sfImportSequence
// Duplicate / Sanity
if (!stpTrans->isFieldPresent(sfSequence) ||
!stpTrans->isFieldPresent(sfFee) ||
!isXRP(stpTrans->getFieldAmount(sfFee)))
@@ -1195,13 +825,11 @@ Import::preflight(PreflightContext const& ctx)
<< tx.getTransactionID();
return temMALFORMED;
}
return preflight2(ctx);
}
// RH TODO: manifest serials should be kept on chain
TER
Import::preclaim(PreclaimContext const& ctx)
{
@@ -1238,14 +866,14 @@ Import::preclaim(PreclaimContext const& ctx)
return tefPAST_IMPORT_SEQ;
}
auto const vl = getVLInfo(*xpop, ctx.j);
auto const vlInfo = getVLInfo(*xpop, ctx.j);
if (!vl)
if (!vlInfo)
return tefINTERNAL;
auto const& sleVL = ctx.view.read(keylet::import_vlseq(vl->second));
auto const& sleVL = ctx.view.read(keylet::import_vlseq(vlInfo->second));
if (sleVL && sleVL->getFieldU32(sfImportSequence) > vl->first)
if (sleVL && sleVL->getFieldU32(sfImportSequence) > vlInfo->first)
return tefPAST_IMPORT_VL_SEQ;
return tesSUCCESS;
@@ -1282,6 +910,7 @@ Import::doApply()
auto const id = ctx_.tx[sfAccount];
auto const k = keylet::account(id);
auto const ksl = keylet::signers(id);
auto sle = view().peek(k);
@@ -1292,17 +921,18 @@ Import::doApply()
auto const tt = stpTrans->getTxnType();
// rekeying is only allowed on a tesSUCCESS, but minting is allowed on any tes or tec code.
if (meta->getFieldU8(sfTransactionResult) == tesSUCCESS)
{
if (tt == ttSIGNER_LIST_SET)
{
JLOG(ctx_.journal.warn()) << "SingerListSet";
// key import: signer list
setSignerEntries = stpTrans->getFieldArray(sfSignerEntries);
}
else if (tt == ttREGULAR_KEY_SET)
{
JLOG(ctx_.journal.warn()) << "SetRegularKey";
// key import: regular key
setRegularKey = stpTrans->getAccountID(sfRegularKey);
}
@@ -1310,9 +940,10 @@ Import::doApply()
bool const create = !sle;
if (!sle)
if (create)
{
// Create the account.
JLOG(ctx_.journal.warn()) << "create - create account";
std::uint32_t const seqno{
view().rules().enabled(featureDeletableAccounts) ? view().seq()
: 1};
@@ -1332,6 +963,8 @@ Import::doApply()
return tefINTERNAL;
}
JLOG(ctx_.journal.warn()) << "Import: inital balance" << initBal;
sle->setFieldAmount(sfBalance, initBal);
}
@@ -1343,16 +976,25 @@ Import::doApply()
return tefINTERNAL;
}
if (setSignerEntries)
sle->setFieldArray(sfSignerEntries, *setSignerEntries);
if (setSignerEntries) {
JLOG(ctx_.journal.warn()) << "signer list set";
auto signerList = std::make_shared<SLE>(ksl);
signerList->setFieldArray(sfSignerEntries, *setSignerEntries);
view().insert(signerList);
}
else if (setRegularKey)
{
JLOG(ctx_.journal.warn()) << "set regular key";
sle->setAccountID(sfRegularKey, *setRegularKey);
}
if (create)
{
JLOG(ctx_.journal.warn()) << "create";
if (!signedWithMaster)
{
// disable master if the account is created using non-master key
JLOG(ctx_.journal.warn()) << "create - disable master";
sle->setAccountID(sfRegularKey, noAccount());
sle->setFieldU32(sfFlags, lsfDisableMaster);
}
@@ -1361,11 +1003,13 @@ Import::doApply()
else
{
// account already exists
JLOG(ctx_.journal.warn()) << "update - import sequence";
sle->setFieldU32(sfImportSequence, importSequence);
// credit the PoB
if (burn > beast::zero)
{
JLOG(ctx_.journal.warn()) << "update - credit burn";
STAmount startBal = sle->getFieldAmount(sfBalance);
STAmount finalBal = startBal + burn;
if (finalBal > startBal)
@@ -1388,6 +1032,7 @@ Import::doApply()
if (!sleVL)
{
// create VL import seq counter
JLOG(ctx_.journal.warn()) << "create vl seq - insert import sequence + public key";
sleVL = std::make_shared<SLE>(keyletVL);
sleVL->setFieldU32(sfImportSequence, infoVL->first);
sleVL->setFieldVL(sfPublicKey, infoVL->second.slice());
@@ -1395,6 +1040,7 @@ Import::doApply()
}
else
{
JLOG(ctx_.journal.warn()) << "update vl - insert import sequence";
uint32_t current = sleVL->getFieldU32(sfImportSequence);
if (current > infoVL->first)
@@ -1421,6 +1067,7 @@ XRPAmount
Import::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return XRPAmount { 0 } ;
// return Transactor::calculateBaseFee(view, tx);
}
} // namespace ripple

View File

@@ -167,10 +167,10 @@ XRPNotCreated::finalize(
dropsAdded += Import::INITIAL_IMPORT_XRP; // welcome amount for new imports
JLOG(j.trace())
<< "Invariant XRPNotCreated Import: "
<< "Invariant XRPNotCreated Import: "
<< "dropsAdded: " << dropsAdded
<< " fee.drops(): " << fee.drops()
<< " drops_: " << drops_
<< " fee.drops(): " << fee.drops()
<< " drops_: " << drops_
<< " dropsAdded - fee.drops(): " << dropsAdded - fee.drops();
return (drops_ == dropsAdded.drops() - fee.drops());

View File

@@ -79,7 +79,7 @@ Invoke::calculateBaseFee(ReadView const& view, STTx const& tx)
XRPAmount extraFee{0};
if (tx.isFieldPresent(sfBlob))
extraFee += XRPAmount{ tx.getFieldVL(sfBlob).size() };
extraFee += XRPAmount{ static_cast<XRPAmount>(tx.getFieldVL(sfBlob).size()) };
if (tx.isFieldPresent(sfHookParameters))
{
@@ -93,7 +93,7 @@ Invoke::calculateBaseFee(ReadView const& view, STTx const& tx)
(param.isFieldPresent(sfHookParameterValue) ?
param.getFieldVL(sfHookParameterValue).size() : 0);
}
extraFee += XRPAmount { paramBytes };
extraFee += XRPAmount { static_cast<XRPAmount>(paramBytes) };
}
return Transactor::calculateBaseFee(view, tx) + extraFee;

View File

@@ -547,7 +547,7 @@ SetHook::calculateBaseFee(ReadView const& view, STTx const& tx)
(param.isFieldPresent(sfHookParameterValue) ?
param.getFieldVL(sfHookParameterValue).size() : 0);
}
extraFee += XRPAmount { paramBytes };
extraFee += XRPAmount { static_cast<XRPAmount>(paramBytes) };
}
}

View File

@@ -0,0 +1,436 @@
//------------------------------------------------------------------------------
/*
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_PROTOCOL_IMPORT_H_INCLUDED
#define RIPPLE_PROTOCOL_IMPORT_H_INCLUDED
// #include <ripple/basics/Log.h>
#include <charconv>
#include <ripple/app/misc/Manifest.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/basics/base64.h>
#include <ripple/json/json_reader.h>
namespace ripple {
inline bool
isHex(std::string const& str)
{
return str.find_first_not_of("0123456789abcdefABCDEF") == std::string::npos;
}
inline bool
isBase58(std::string const& str)
{
return str.find_first_not_of(
"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") ==
std::string::npos;
}
inline bool
isBase64(std::string const& str)
{
return str.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"
"/=") == std::string::npos;
}
inline std::optional<uint64_t>
parse_uint64(std::string const& str)
{
uint64_t result;
auto [ptr, ec] =
std::from_chars(str.data(), str.data() + str.size(), result);
if (ec == std::errc())
return result;
return {};
}
inline bool
syntaxCheckProof(Json::Value const& proof, beast::Journal const& j, int depth = 0)
{
if (depth > 64) {
JLOG(j.warn()) << "XPOP.transaction.proof list should be less than 64 entries";
return false;
}
if (proof.isArray())
{
// List form
if (proof.size() != 16)
{
JLOG(j.warn()) << "XPOP.transaction.proof list should be exactly 16 entries";
return false;
}
for (const auto& entry : proof)
{
if (entry.isString())
{
if (!isHex(entry.asString()) || entry.asString().size() != 64)
{
JLOG(j.warn()) << "XPOP.transaction.proof list entry missing "
"or wrong format "
<< "(should be hex string with 64 characters)";
return false;
}
}
else if (entry.isArray())
{
if (!syntaxCheckProof(entry, j, depth + 1))
return false;
}
else
{
JLOG(j.warn())
<< "XPOP.transaction.proof list entry has wrong format";
return false;
}
}
}
else if (proof.isObject())
{
// Tree form
if (depth == 0) // root is special case
{
if (!proof["hash"].isString() ||
proof["hash"].asString().size() != 64 ||
!proof["key"].isString() ||
proof["key"].asString().size() != 64 ||
!proof["children"].isObject())
{
JLOG(j.warn())
<< "XPOP.transaction.proof tree node has wrong format (root)";
return false;
}
return syntaxCheckProof(proof["children"], j, depth + 1);
}
for (const auto& branch : proof.getMemberNames())
{
if (branch.size() != 1 || !isHex(branch))
{
JLOG(j.warn()) << "XPOP.transaction.proof child node was not 0-F "
"hex nibble";
return false;
}
const auto& node = proof[branch];
if (!node.isObject() || !node["hash"].isString() ||
node["hash"].asString().size() != 64 ||
!node["key"].isString() ||
node["key"].asString().size() != 64 ||
!node["children"].isObject())
{
JLOG(j.warn())
<< "XPOP.transaction.proof tree node has wrong format";
return false;
}
if (!syntaxCheckProof(node["children"], j, depth + 1)) {
JLOG(j.warn())
<< "XPOP.transaction.proof bad children format";
return false;
}
}
}
else
{
JLOG(j.warn()) << "XPOP.transaction.proof has wrong format (should be "
"array or object)";
return false;
}
return true;
}
// does not check signature etc
inline
std::optional<Json::Value>
syntaxCheckXPOP(Blob const& blob, beast::Journal const& j)
{
if (blob.empty())
return {};
std::string strJson(blob.begin(), blob.end());
if (strJson.empty())
return {};
try
{
Json::Value xpop;
Json::Reader reader;
if (!reader.parse(strJson, xpop))
{
JLOG(j.warn()) << "XPOP failed to parse string json";
return {};
}
if (!xpop.isObject())
{
JLOG(j.warn()) << "XPOP is not a JSON object";
return {};
}
if (!xpop["ledger"].isObject())
{
JLOG(j.warn()) << "XPOP.ledger is not a JSON object";
return {};
}
if (!xpop["transaction"].isObject())
{
JLOG(j.warn()) << "XPOP.transaction is not a JSON object";
return {};
}
if (!xpop["validation"].isObject())
{
JLOG(j.warn()) << "XPOP.validation is not a JSON object";
return {};
}
if (!xpop["ledger"]["acroot"].isString() ||
xpop["ledger"]["acroot"].asString().size() != 64 ||
!isHex(xpop["ledger"]["acroot"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.acroot missing or wrong format (should "
"be hex string)";
return {};
}
if (!xpop["ledger"]["txroot"].isString() ||
xpop["ledger"]["txroot"].asString().size() != 64 ||
!isHex(xpop["ledger"]["txroot"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.txroot missing or wrong format "
"(should be hex string)";
return {};
}
if (!xpop["ledger"]["phash"].isString() ||
xpop["ledger"]["phash"].asString().size() != 64 ||
!isHex(xpop["ledger"]["phash"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.phash missing or wrong format "
"(should be hex string)";
return {};
}
if (!xpop["ledger"]["close"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.close missing or wrong format "
"(should be int)";
return {};
}
if (xpop["ledger"]["coins"].isInt())
{
// pass
}
else if (xpop["ledger"]["coins"].isString())
{
if (!parse_uint64(xpop["ledger"]["coins"].asString()))
{
JLOG(j.warn()) << "XPOP.ledger.coins missing or wrong format "
"(should be int or string)";
return {};
}
}
else
{
JLOG(j.warn()) << "XPOP.ledger.coins missing or wrong format "
"(should be int or string)";
return {};
}
if (!xpop["ledger"]["cres"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.cres missing or wrong format "
"(should be int)";
return {};
}
if (!xpop["ledger"]["flags"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.flags missing or wrong format "
"(should be int)";
return {};
}
if (!xpop["ledger"]["pclose"].isInt())
{
JLOG(j.warn()) << "XPOP.ledger.pclose missing or wrong format "
"(should be int)";
return {};
}
if (!xpop["transaction"]["blob"].isString() ||
!isHex(xpop["transaction"]["blob"].asString()))
{
JLOG(j.warn()) << "XPOP.transaction.blob missing or wrong format "
"(should be hex string)";
return {};
}
if (!xpop["transaction"]["meta"].isString() ||
!isHex(xpop["transaction"]["meta"].asString()))
{
JLOG(j.warn()) << "XPOP.transaction.meta missing or wrong format "
"(should be hex string)";
return {};
}
if (!syntaxCheckProof(xpop["transaction"]["proof"], j))
{
JLOG(j.warn()) << "XPOP.transaction.proof failed syntax check "
"(tree/list form)";
return {};
}
if (!xpop["validation"]["data"].isObject())
{
JLOG(j.warn()) << "XPOP.validation.data missing or wrong format "
"(should be JSON object)";
return {};
}
if (!xpop["validation"]["unl"].isObject())
{
JLOG(j.warn()) << "XPOP.validation.unl missing or wrong format "
"(should be JSON object)";
return {};
}
for (const auto& key : xpop["validation"]["data"].getMemberNames())
{
const auto& value = xpop["validation"]["data"][key];
if (!isBase58(key) || !value.isString() || !isHex(value.asString()))
{
JLOG(j.warn()) << "XPOP.validation.data entry has wrong format "
<< "(key should be base58 string and value "
"should be hex string)";
return {};
}
}
for (const auto& key : xpop["validation"]["unl"].getMemberNames())
{
const auto& value = xpop["validation"]["unl"][key];
if (key == "public_key")
{
if (!value.isString() || !isHex(value.asString()))
{
JLOG(j.warn()) << "XPOP.validation.unl.public_key missing or "
"wrong format (should be hex string)";
return {};
}
auto pk = strUnHex(value.asString());
if (!publicKeyType(makeSlice(*pk)))
{
JLOG(j.warn()) << "XPOP.validation.unl.public_key invalid key type.";
return {};
}
}
else if (key == "manifest")
{
if (!value.isString())
{
JLOG(j.warn()) << "XPOP.validation.unl.manifest missing or "
"wrong format (should be string)";
return {};
}
}
else if (key == "blob")
{
if (!value.isString() || !isBase64(value.asString()))
{
JLOG(j.warn()) << "XPOP.validation.unl.blob missing or wrong "
"format (should be base64 string)";
return {};
}
}
else if (key == "signature")
{
if (!value.isString() || !isHex(value.asString()))
{
JLOG(j.warn())
<< "XPOP.validation.unl.signature missing or wrong "
"format (should be hex string)";
return {};
}
}
else if (key == "version")
{
if (!value.isInt())
{
JLOG(j.warn()) << "XPOP.validation.unl.version missing or "
"wrong format (should be int)";
return {};
}
}
else
{
if (!value.isObject() && !value.isString())
{
JLOG(j.warn())
<< "XPOP.validation.unl entry has wrong format";
return {};
}
}
}
return xpop;
}
catch (...)
{
JLOG(j.warn()) << "An exception occurred during XPOP validation";
}
return {};
}
// <sequence, master key>
inline
std::optional<std::pair< uint32_t, PublicKey>>
getVLInfo(Json::Value const& xpop, beast::Journal const& j)
{
auto const data = base64_decode(xpop[jss::validation][jss::unl][jss::blob].asString());
Json::Reader r;
Json::Value list;
if (!r.parse(data, list))
{
JLOG(j.warn()) << "Import: unl blob was not valid json (after base64 decoding)";
return {};
}
auto const sequence = list[jss::sequence].asUInt();
auto const m = deserializeManifest(base64_decode(xpop[jss::validation][jss::unl][jss::manifest].asString()));
if (!m)
{
JLOG(j.warn()) << "Import: failed to deserialize manifest";
return {};
}
return { {sequence, m->masterKey} };
}
} // namespace ripple
#endif

View File

@@ -131,6 +131,7 @@ transResults()
MAKE_ERROR(telREQUIRES_NETWORK_ID, "Transactions submitted to this node/network must include a correct NetworkID field."),
MAKE_ERROR(telNETWORK_ID_MAKES_TX_NON_CANONICAL, "Transactions submitted to this node/network must NOT include a NetworkID field."),
MAKE_ERROR(telNON_LOCAL_EMITTED_TXN, "Emitted transaction cannot be applied because it was not generated locally."),
MAKE_ERROR(telIMPORT_VL_KEY_NOT_RECOGNISED, "Import vl key was not recognized."),
MAKE_ERROR(telCAN_NOT_QUEUE_IMPORT, "Import transaction was not able to be directly applied and cannot be queued."),
MAKE_ERROR(temMALFORMED, "Malformed transaction."),

View File

@@ -69,281 +69,281 @@ private:
Json::Value ret{Json::objectValue};
ret[jss::TYPES] = Json::objectValue;
auto const translate = [](std::string inp) -> std::string {
auto replace = [&](const char* f, const char* r) -> std::string {
std::string out = inp;
boost::replace_all(out, f, r);
return out;
};
// auto const translate = [](std::string inp) -> std::string {
// auto replace = [&](const char* f, const char* r) -> std::string {
// std::string out = inp;
// boost::replace_all(out, f, r);
// return out;
// };
auto find = [&](const char* s) -> bool {
return inp.find(s) != std::string::npos;
};
// auto find = [&](const char* s) -> bool {
// return inp.find(s) != std::string::npos;
// };
if (find("UINT"))
{
if (find("256") || find("160") || find("128"))
return replace("UINT", "Hash");
else
return replace("UINT", "UInt");
}
// if (find("UINT"))
// {
// if (find("256") || find("160") || find("128"))
// return replace("UINT", "Hash");
// else
// return replace("UINT", "UInt");
// }
if (inp == "OBJECT")
return "STObject";
if (inp == "ARRAY")
return "STArray";
if (inp == "AMM")
return "AMM";
if (inp == "ACCOUNT")
return "AccountID";
if (inp == "LEDGERENTRY")
return "LedgerEntry";
if (inp == "NOTPRESENT")
return "NotPresent";
if (inp == "PATHSET")
return "PathSet";
if (inp == "VL")
return "Blob";
if (inp == "DIR_NODE")
return "DirectoryNode";
if (inp == "PAYCHAN")
return "PayChannel";
// if (inp == "OBJECT")
// return "STObject";
// if (inp == "ARRAY")
// return "STArray";
// if (inp == "AMM")
// return "AMM";
// if (inp == "ACCOUNT")
// return "AccountID";
// if (inp == "LEDGERENTRY")
// return "LedgerEntry";
// if (inp == "NOTPRESENT")
// return "NotPresent";
// if (inp == "PATHSET")
// return "PathSet";
// if (inp == "VL")
// return "Blob";
// if (inp == "DIR_NODE")
// return "DirectoryNode";
// if (inp == "PAYCHAN")
// return "PayChannel";
static const std::map<std::string, std::string>
capitalization_exceptions = {
{"NFTOKEN", "NFToken"},
{"UNL", "UNL"},
{"XCHAIN", "XChain"},
{"ID", "ID"},
{"AMM", "AMM"},
{"URITOKEN", "URIToken"},
{"URI", "URI"}};
// static const std::map<std::string, std::string>
// capitalization_exceptions = {
// {"NFTOKEN", "NFToken"},
// {"UNL", "UNL"},
// {"XCHAIN", "XChain"},
// {"ID", "ID"},
// {"AMM", "AMM"},
// {"URITOKEN", "URIToken"},
// {"URI", "URI"}};
std::string out;
size_t pos = 0;
for (;;)
{
pos = inp.find("_");
if (pos == std::string::npos)
pos = inp.size();
std::string token = inp.substr(0, pos);
if (auto const e = capitalization_exceptions.find(token);
e != capitalization_exceptions.end())
out += e->second;
else if (token.size() > 1)
{
boost::algorithm::to_lower(token);
token.data()[0] -= ('a' - 'A');
out += token;
}
else
out += token;
if (pos == inp.size())
break;
inp = inp.substr(pos + 1);
}
return out;
};
// std::string out;
// size_t pos = 0;
// for (;;)
// {
// pos = inp.find("_");
// if (pos == std::string::npos)
// pos = inp.size();
// std::string token = inp.substr(0, pos);
// if (auto const e = capitalization_exceptions.find(token);
// e != capitalization_exceptions.end())
// out += e->second;
// else if (token.size() > 1)
// {
// boost::algorithm::to_lower(token);
// token.data()[0] -= ('a' - 'A');
// out += token;
// }
// else
// out += token;
// if (pos == inp.size())
// break;
// inp = inp.substr(pos + 1);
// }
// return out;
// };
ret[jss::TYPES]["Done"] = -1;
std::map<int32_t, std::string> type_map{{-1, "Done"}};
for (auto [value, name] : magic_enum::enum_entries<SerializedTypeID>())
{
std::string type_name =
translate(STR(name).substr(4) /* remove STI_ */);
int32_t type_value = std::stoi(STR(value));
ret[jss::TYPES][type_name] = type_value;
type_map[type_value] = type_name;
}
// ret[jss::TYPES]["Done"] = -1;
// std::map<int32_t, std::string> type_map{{-1, "Done"}};
// for (auto [value, name] : magic_enum::enum_entries<SerializedTypeID>())
// {
// std::string type_name =
// translate(STR(name).substr(4) /* remove STI_ */);
// int32_t type_value = std::stoi(STR(value));
// ret[jss::TYPES][type_name] = type_value;
// type_map[type_value] = type_name;
// }
ret[jss::LEDGER_ENTRY_TYPES] = Json::objectValue;
ret[jss::LEDGER_ENTRY_TYPES][jss::Invalid] = -1;
// ret[jss::LEDGER_ENTRY_TYPES] = Json::objectValue;
// ret[jss::LEDGER_ENTRY_TYPES][jss::Invalid] = -1;
for (auto [value, name] : magic_enum::enum_entries<LedgerEntryType>())
ret[jss::LEDGER_ENTRY_TYPES]
[translate(STR(name).substr(2) /* remove lt_ */)] =
std::stoi(STR(value));
// for (auto [value, name] : magic_enum::enum_entries<LedgerEntryType>())
// ret[jss::LEDGER_ENTRY_TYPES]
// [translate(STR(name).substr(2) /* remove lt_ */)] =
// std::stoi(STR(value));
ret[jss::FIELDS] = Json::arrayValue;
// ret[jss::FIELDS] = Json::arrayValue;
uint32_t i = 0;
{
Json::Value a = Json::arrayValue;
a[0U] = "Generic";
Json::Value v = Json::objectValue;
v[jss::nth] = 0;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = false;
v[jss::type] = "Unknown";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// uint32_t i = 0;
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "Generic";
// Json::Value v = Json::objectValue;
// v[jss::nth] = 0;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = false;
// v[jss::type] = "Unknown";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
{
Json::Value a = Json::arrayValue;
a[0U] = "Invalid";
Json::Value v = Json::objectValue;
v[jss::nth] = -1;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = false;
v[jss::type] = "Unknown";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "Invalid";
// Json::Value v = Json::objectValue;
// v[jss::nth] = -1;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = false;
// v[jss::type] = "Unknown";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
{
Json::Value a = Json::arrayValue;
a[0U] = "ObjectEndMarker";
Json::Value v = Json::objectValue;
v[jss::nth] = 1;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = true;
v[jss::type] = "STObject";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "ObjectEndMarker";
// Json::Value v = Json::objectValue;
// v[jss::nth] = 1;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = true;
// v[jss::type] = "STObject";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
{
Json::Value a = Json::arrayValue;
a[0U] = "ArrayEndMarker";
Json::Value v = Json::objectValue;
v[jss::nth] = 1;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = true;
v[jss::type] = "STArray";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "ArrayEndMarker";
// Json::Value v = Json::objectValue;
// v[jss::nth] = 1;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = true;
// v[jss::type] = "STArray";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
{
Json::Value a = Json::arrayValue;
a[0U] = "hash";
Json::Value v = Json::objectValue;
v[jss::nth] = 257;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = false;
v[jss::type] = "Hash256";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "hash";
// Json::Value v = Json::objectValue;
// v[jss::nth] = 257;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = false;
// v[jss::type] = "Hash256";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
{
Json::Value a = Json::arrayValue;
a[0U] = "index";
Json::Value v = Json::objectValue;
v[jss::nth] = 258;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = false;
v[jss::type] = "Hash256";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "index";
// Json::Value v = Json::objectValue;
// v[jss::nth] = 258;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = false;
// v[jss::type] = "Hash256";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
{
Json::Value a = Json::arrayValue;
a[0U] = "taker_gets_funded";
Json::Value v = Json::objectValue;
v[jss::nth] = 258;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = false;
v[jss::type] = "Amount";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "taker_gets_funded";
// Json::Value v = Json::objectValue;
// v[jss::nth] = 258;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = false;
// v[jss::type] = "Amount";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
{
Json::Value a = Json::arrayValue;
a[0U] = "taker_pays_funded";
Json::Value v = Json::objectValue;
v[jss::nth] = 259;
v[jss::isVLEncoded] = false;
v[jss::isSerialized] = false;
v[jss::isSigningField] = false;
v[jss::type] = "Amount";
a[1U] = v;
ret[jss::FIELDS][i++] = a;
}
// {
// Json::Value a = Json::arrayValue;
// a[0U] = "taker_pays_funded";
// Json::Value v = Json::objectValue;
// v[jss::nth] = 259;
// v[jss::isVLEncoded] = false;
// v[jss::isSerialized] = false;
// v[jss::isSigningField] = false;
// v[jss::type] = "Amount";
// a[1U] = v;
// ret[jss::FIELDS][i++] = a;
// }
for (auto const& [code, f] : ripple::SField::knownCodeToField)
{
if (f->fieldName == "")
continue;
// for (auto const& [code, f] : ripple::SField::knownCodeToField)
// {
// if (f->fieldName == "")
// continue;
Json::Value innerObj = Json::objectValue;
// Json::Value innerObj = Json::objectValue;
uint32_t fc = code & 0xFFU;
uint32_t tc = code >> 16U;
// uint32_t fc = code & 0xFFU;
// uint32_t tc = code >> 16U;
innerObj[jss::nth] = fc;
// innerObj[jss::nth] = fc;
innerObj[jss::isVLEncoded] =
(tc == 7U /* Blob */ || tc == 8U /* AccountID */ ||
tc == 19U /* Vector256 */);
// innerObj[jss::isVLEncoded] =
// (tc == 7U /* Blob */ || tc == 8U /* AccountID */ ||
// tc == 19U /* Vector256 */);
innerObj[jss::isSerialized] =
(tc <
10000); /* TRANSACTION, LEDGER_ENTRY, VALIDATION, METADATA */
// innerObj[jss::isSerialized] =
// (tc <
// 10000); /* TRANSACTION, LEDGER_ENTRY, VALIDATION, METADATA */
innerObj[jss::isSigningField] = f->shouldInclude(false);
// innerObj[jss::isSigningField] = f->shouldInclude(false);
innerObj[jss::type] = type_map[tc];
// innerObj[jss::type] = type_map[tc];
Json::Value innerArray = Json::arrayValue;
innerArray[0U] = f->fieldName;
innerArray[1U] = innerObj;
// Json::Value innerArray = Json::arrayValue;
// innerArray[0U] = f->fieldName;
// innerArray[1U] = innerObj;
ret[jss::FIELDS][i++] = innerArray;
}
// ret[jss::FIELDS][i++] = innerArray;
// }
ret[jss::TRANSACTION_RESULTS] = Json::objectValue;
for (auto [value, name] : magic_enum::enum_entries<TELcodes>())
ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
for (auto [value, name] : magic_enum::enum_entries<TEMcodes>())
ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
for (auto [value, name] : magic_enum::enum_entries<TEFcodes>())
ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
for (auto [value, name] : magic_enum::enum_entries<TERcodes>())
ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
for (auto [value, name] : magic_enum::enum_entries<TEScodes>())
ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
for (auto [value, name] : magic_enum::enum_entries<TECcodes>())
ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
// ret[jss::TRANSACTION_RESULTS] = Json::objectValue;
// for (auto [value, name] : magic_enum::enum_entries<TELcodes>())
// ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
// for (auto [value, name] : magic_enum::enum_entries<TEMcodes>())
// ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
// for (auto [value, name] : magic_enum::enum_entries<TEFcodes>())
// ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
// for (auto [value, name] : magic_enum::enum_entries<TERcodes>())
// ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
// for (auto [value, name] : magic_enum::enum_entries<TEScodes>())
// ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
// for (auto [value, name] : magic_enum::enum_entries<TECcodes>())
// ret[jss::TRANSACTION_RESULTS][STR(name)] = std::stoi(STR(value));
auto const translate_tt = [](std::string inp) -> std::string {
if (inp == "PaychanClaim")
return "PaymentChannelClaim";
if (inp == "PaychanCreate")
return "PaymentChannelCreate";
if (inp == "PaychanFund")
return "PaymentChannelFund";
if (inp == "RegularKeySet")
return "SetRegularKey";
if (inp == "HookSet")
return "SetHook";
return inp;
};
// auto const translate_tt = [](std::string inp) -> std::string {
// if (inp == "PaychanClaim")
// return "PaymentChannelClaim";
// if (inp == "PaychanCreate")
// return "PaymentChannelCreate";
// if (inp == "PaychanFund")
// return "PaymentChannelFund";
// if (inp == "RegularKeySet")
// return "SetRegularKey";
// if (inp == "HookSet")
// return "SetHook";
// return inp;
// };
ret[jss::TRANSACTION_TYPES] = Json::objectValue;
ret[jss::TRANSACTION_TYPES][jss::Invalid] = -1;
for (auto [value, name] : magic_enum::enum_entries<TxType>())
ret[jss::TRANSACTION_TYPES][translate_tt(translate(STR(name).substr(2)))] =
std::stoi(STR(value));
// ret[jss::TRANSACTION_TYPES] = Json::objectValue;
// ret[jss::TRANSACTION_TYPES][jss::Invalid] = -1;
// for (auto [value, name] : magic_enum::enum_entries<TxType>())
// ret[jss::TRANSACTION_TYPES][translate_tt(translate(STR(name).substr(2)))] =
// std::stoi(STR(value));
// generate hash
{
const std::string out = Json::FastWriter().write(ret);
defsHash =
ripple::sha512Half(ripple::Slice{out.data(), out.size()});
ret[jss::hash] = to_string(*defsHash);
}
// // generate hash
// {
// const std::string out = Json::FastWriter().write(ret);
// defsHash =
// ripple::sha512Half(ripple::Slice{out.data(), out.size()});
// ret[jss::hash] = to_string(*defsHash);
// }
return ret;
}

2648
src/test/app/Import_test.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -129,7 +129,7 @@ public:
jvn[jss::Fee] = to_string(env.current()->fees().base);
jvn[jss::Sequence] = env.seq(alice);
jvn[jss::LastLedgerSequence] = env.current()->info().seq + 2;
auto jt = env.jtnofill(jvn);
auto jt = env.jtnofill(jvn, alice);
Serializer s;
jt.stx->add(s);
BEAST_EXPECT(env.rpc("submit", strHex(s.slice()))[jss::result][jss::engine_result] == "telREQUIRES_NETWORK_ID");

View File

@@ -448,11 +448,23 @@ public:
/** Create a JTx from parameters. */
template <class JsonValue, class... FN>
JTx
jtnofill(JsonValue&& jv, FN const&... fN)
jt(JsonValue&& jv, Account account, FN const&... fN)
{
JTx jt(std::forward<JsonValue>(jv));
invoke(jt, fN...);
autofill_sig(jt);
acct_autofill(jt, account);
jt.stx = st(jt);
return jt;
}
/** Create a JTx from parameters. */
template <class JsonValue, class Account, class... FN>
JTx
jtnofill(JsonValue&& jv, Account account, FN const&... fN)
{
JTx jt(std::forward<JsonValue>(jv));
invoke(jt, fN...);
autofill_sig(jt, account);
jt.stx = st(jt);
return jt;
}
@@ -518,6 +530,22 @@ public:
{
apply(std::forward<JsonValue>(jv), fN...);
}
/** Apply funclets and submit. */
/** @{ */
template <class JsonValue, class Account, class... FN>
void
apply(JsonValue&& jv, Account const& account = {}, FN const&... fN)
{
submit(jt(std::forward<JsonValue>(jv), account, fN...));
}
template <class JsonValue, class Account, class... FN>
void
operator()(JsonValue&& jv, Account const& account, FN const&... fN)
{
apply(std::forward<JsonValue>(jv), account, fN...);
}
/** @} */
/** Return the TER for the last JTx. */
@@ -662,7 +690,10 @@ protected:
std::unordered_map<std::string, std::string> const& headers = {});
void
autofill_sig(JTx& jt);
autofill_sig(JTx& jt, Account const& account);
virtual void
acct_autofill(JTx& jt, Account const& account);
virtual void
autofill(JTx& jt);

View File

@@ -402,14 +402,14 @@ Env::txid() const
}
void
Env::autofill_sig(JTx& jt)
Env::autofill_sig(JTx& jt, Account const& account)
{
auto& jv = jt.jv;
if (jt.signer)
return jt.signer(*this, jt);
if (!jt.fill_sig)
return;
auto const account = lookup(jv[jss::Account].asString());
if (!app().checkSigs())
{
jv[jss::SigningPubKey] = strHex(account.pk().slice());
@@ -424,6 +424,31 @@ Env::autofill_sig(JTx& jt)
jtx::sign(jv, account);
}
void
Env::acct_autofill(JTx& jt, Account const& account)
{
auto& jv = jt.jv;
if (jt.fill_fee)
jtx::fill_fee(jv, *current());
if (jt.fill_seq)
jtx::fill_seq(jv, *current());
uint32_t networkID = app().config().NETWORK_ID;
if (!jv.isMember(jss::NetworkID) && networkID > 1024)
jv[jss::NetworkID] = std::to_string(networkID);
// Must come last
try
{
autofill_sig(jt, account);
}
catch (parse_error const&)
{
test.log << "parse failed:\n" << pretty(jv) << std::endl;
Rethrow();
}
}
void
Env::autofill(JTx& jt)
{
@@ -432,15 +457,16 @@ Env::autofill(JTx& jt)
jtx::fill_fee(jv, *current());
if (jt.fill_seq)
jtx::fill_seq(jv, *current());
uint32_t networkID = app().config().NETWORK_ID;
if (!jv.isMember(jss::NetworkID) && networkID > 1024)
jv[jss::NetworkID] = std::to_string(networkID);
// Must come last
try
{
autofill_sig(jt);
auto const account = lookup(jv[jss::Account].asString());
autofill_sig(jt, account);
}
catch (parse_error const&)
{