setup import test

This commit is contained in:
Denis Angell
2023-06-01 00:09:58 +00:00
parent 41461bdddc
commit f42d9124df
5 changed files with 2840 additions and 10 deletions

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

@@ -838,7 +838,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 +885,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 +918,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 +949,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,10 +1046,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 +1195,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)))
@@ -1238,14 +1248,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;
@@ -1298,11 +1308,13 @@ Import::doApply()
{
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 +1322,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};
@@ -1343,16 +1356,23 @@ Import::doApply()
return tefINTERNAL;
}
if (setSignerEntries)
if (setSignerEntries) {
JLOG(ctx_.journal.warn()) << "signer list set";
sle->setFieldArray(sfSignerEntries, *setSignerEntries);
}
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 +1381,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 +1410,7 @@ Import::doApply()
if (!sleVL)
{
// create VL import seq counter
JLOG(ctx_.journal.warn()) << "create vl - insert import sequence + public key";
sleVL = std::make_shared<SLE>(keyletVL);
sleVL->setFieldU32(sfImportSequence, infoVL->first);
sleVL->setFieldVL(sfPublicKey, infoVL->second.slice());
@@ -1395,6 +1418,7 @@ Import::doApply()
}
else
{
JLOG(ctx_.journal.warn()) << "update vl - insert import sequence";
uint32_t current = sleVL->getFieldU32(sfImportSequence);
if (current > infoVL->first)

View File

@@ -0,0 +1,438 @@
//------------------------------------------------------------------------------
/*
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)";
std::cout << "Import: unl blob was not valid json (after base64 decoding)" << "\n";
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";
std::cout << "Import: failed to deserialize manifest" << "\n";
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."),

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

File diff suppressed because it is too large Load Diff