change UNLReport to also run consensus on import_vl_keys, change gensisMint to also allow Governance flags and marks to be set on account root, compiling, not tested

This commit is contained in:
Richard Holland
2023-06-19 13:09:14 +00:00
parent e397107ce9
commit d70ec5d2e4
15 changed files with 268 additions and 85 deletions

View File

@@ -92,7 +92,7 @@ RCLConsensus::Adaptor::Adaptor(
rand_int(
crypto_prng(),
std::numeric_limits<std::uint64_t>::max() - 1))
, nUnlVote_(validatorKeys_.nodeID, j_)
, nUnlVote_(validatorKeys_.nodeID, j_, app)
{
assert(valCookie_ != 0);

View File

@@ -1327,8 +1327,11 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
if (config_->IMPORT_VL_KEYS.empty())
{
JLOG(m_journal.fatal()) << "IMPORT_VL_KEYS section must be specified in validators file.";
return false;
JLOG(m_journal.warn()) << "[import_vl_keys] section not specified validators file. "
<< "Will attempt to use keys from ledger.";
}
else
{
}
//----------------------------------------------------------------------

View File

@@ -23,8 +23,8 @@
namespace ripple {
NegativeUNLVote::NegativeUNLVote(NodeID const& myId, beast::Journal j)
: myId_(myId), j_(j)
NegativeUNLVote::NegativeUNLVote(NodeID const& myId, beast::Journal j, Application& app)
: myId_(myId), j_(j), app_(app)
{
}
@@ -122,11 +122,16 @@ NegativeUNLVote::addReportingTx(
ordered.emplace(nidToKeyMap.at(n));
}
for (auto const& k : ordered)
for (auto const& pk : ordered)
{
STTx repUnlTx(ttUNL_REPORT, [&](auto& obj)
{
obj.setFieldVL(sfPublicKey, k);
obj.set(([&]()
{
auto inner = std::make_unique<STObject>(sfActiveValidator);
inner->setFieldVL(sfPublicKey, pk);
return inner;
})());
obj.setFieldU32(sfLedgerSequence, seq);
});
@@ -143,11 +148,47 @@ NegativeUNLVote::addReportingTx(
else
{
JLOG(j_.debug()) << "R-UNL: ledger seq=" << seq
<< ", add a ttUNL_REPORT Tx with txID: " << txID
<< ", add a ttUNL_REPORT (active_val) Tx with txID: " << txID
<< ", size=" << s.size()
<< ", " << repUnlTx.getJson(JsonOptions::none);
}
}
// do import VL key voting
auto const& keyMap = app_.config().IMPORT_VL_KEYS;
for (auto const& [_, pk] : keyMap)
{
STTx repUnlTx(ttUNL_REPORT, [&](auto& obj)
{
obj.set(([&]()
{
auto inner = std::make_unique<STObject>(sfImportVLKey);
inner->setFieldVL(sfPublicKey, pk);
return inner;
})());
obj.setFieldU32(sfLedgerSequence, seq);
});
uint256 txID = repUnlTx.getTransactionID();
Serializer s;
repUnlTx.add(s);
if (!initalSet->addGiveItem(
SHAMapNodeType::tnTRANSACTION_NM,
std::make_shared<SHAMapItem>(txID, s.slice())))
{
JLOG(j_.warn()) << "R-UNL: ledger seq=" << seq
<< ", add ttUNL_REPORT tx failed (import_vl_key)";
}
else
{
JLOG(j_.debug()) << "R-UNL: ledger seq=" << seq
<< ", add a ttUNL_REPORT (import_vl) Tx with txID: " << txID
<< ", size=" << s.size()
<< ", " << repUnlTx.getJson(JsonOptions::none);
}
}
}
void

View File

@@ -91,7 +91,7 @@ public:
* @param myId the NodeID of the local node
* @param j log
*/
NegativeUNLVote(NodeID const& myId, beast::Journal j);
NegativeUNLVote(NodeID const& myId, beast::Journal j, Application& app);
~NegativeUNLVote() = default;
/**
@@ -128,6 +128,7 @@ private:
beast::Journal j_;
mutable std::mutex mutex_;
hash_map<NodeID, LedgerIndex> newValidators_;
Application& app_;
/**
* UNLModify Tx candidates

View File

@@ -77,11 +77,19 @@ Change::preflight(PreflightContext const& ctx)
return temDISABLED;
}
if (ctx.tx.getTxnType() == ttUNL_REPORT &&
!ctx.rules.enabled(featureXahauGenesis))
if (ctx.tx.getTxnType() == ttUNL_REPORT)
{
JLOG(ctx.j.warn()) << "Change: UNLReport is not enabled.";
return temDISABLED;
if (!ctx.rules.enabled(featureXahauGenesis))
{
JLOG(ctx.j.warn()) << "Change: UNLReport is not enabled.";
return temDISABLED;
}
if (!ctx.tx.isFieldPresent(sfActiveValidator) && !ctx.tx.isFieldPresent(sfImportVLKey))
{
JLOG(ctx.j.warn()) << "Change: UNLReport must specify at least one of sfImportVLKey, sfActiveValidator";
return temMALFORMED;
}
}
return tesSUCCESS;
@@ -182,37 +190,63 @@ Change::applyUNLReport()
if (created)
sle = std::make_shared<SLE>(keylet::UNLReport());
auto const avExisting =
(sle->isFieldPresent(sfPreviousTxnLgrSeq) &&
sle->getFieldU32(sfPreviousTxnLgrSeq) < seq)
? STArray(sfActiveValidators)
: sle->getFieldArray(sfActiveValidators);
bool const reset =
sle->isFieldPresent(sfPreviousTxnLgrSeq) &&
sle->getFieldU32(sfPreviousTxnLgrSeq) < seq;
// canonically order using std::set
std::set<PublicKey> ordered;
for (auto const& obj: avExisting)
auto canonicalize = [&](SField const& arrayType, SField const& objType) -> std::vector<STObject>
{
auto pk = obj.getFieldVL(sfPublicKey);
if (!publicKeyType(makeSlice(pk)))
continue;
auto const existing =
reset
? STArray(arrayType)
: sle->getFieldArray(arrayType);
ordered.emplace(PublicKey(makeSlice(pk)));
// canonically order using std::set
std::set<PublicKey> ordered;
for (auto const& obj: existing)
{
auto pk = obj.getFieldVL(sfPublicKey);
if (!publicKeyType(makeSlice(pk)))
continue;
ordered.emplace(PublicKey(makeSlice(pk)));
};
if (ctx_.tx.isFieldPresent(objType))
{
auto pk =
const_cast<ripple::STTx&>(ctx_.tx)
.getField(arrayType)
.downcast<STObject>()
.getFieldVL(sfPublicKey);
if (publicKeyType(makeSlice(pk)))
ordered.emplace(PublicKey(makeSlice(pk)));
}
std::vector<STObject> out;
out.reserve(ordered.size());
for (auto const& k: ordered)
{
out.emplace_back(sfActiveValidator);
out.back().setFieldVL(sfPublicKey, k);
}
return out;
};
auto pk = ctx_.tx.getFieldVL(sfPublicKey);
if (publicKeyType(makeSlice(pk)))
ordered.emplace(PublicKey(makeSlice(pk)));
std::vector<STObject> av;
av.reserve(ordered.size());
for (auto const& k: ordered)
{
av.emplace_back(sfActiveValidator);
av.back().setFieldVL(sfPublicKey, k);
}
bool const hasAV = ctx_.tx.isFieldPresent(sfActiveValidator);
bool const hasVL = ctx_.tx.isFieldPresent(sfImportVLKey);
// update
sle->setFieldArray(sfActiveValidators, STArray(av, sfActiveValidators));
if (hasAV)
sle->setFieldArray(sfActiveValidators,
STArray(canonicalize(sfActiveValidators, sfActiveValidator),sfActiveValidators));
if (hasVL)
sle->setFieldArray(sfImportVLKeys,
STArray(canonicalize(sfImportVLKeys, sfImportVLKey),sfImportVLKeys));
if (created)
view().insert(sle);

View File

@@ -78,21 +78,38 @@ GenesisMint::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
auto const amt = dest.getFieldAmount(sfAmount);
if (!isXRP(amt))
bool const hasAmt = dest.isFieldPresent(sfAmount);
bool const hasMarks = dest.isFieldPresent(sfGovernanceMarks);
bool const hasFlags = dest.isFieldPresent(sfGovernanceFlags);
if (!hasAmt && !hasMarks && !hasFlags)
{
JLOG(ctx.j.warn())
<< "GenesisMint: only native amounts can be minted.";
<< "GenesisMint: each destination must have at least one of: "
<< "sfAmount, sfGovernanceFlags, sfGovernance marks.";
return temMALFORMED;
}
if (amt <= beast::zero)
if (hasAmt)
{
JLOG(ctx.j.warn())
<< "GenesisMint: only positive amounts can be minted.";
return temMALFORMED;
auto const amt = dest.getFieldAmount(sfAmount);
if (!isXRP(amt))
{
JLOG(ctx.j.warn())
<< "GenesisMint: only native amounts can be minted.";
return temMALFORMED;
}
if (amt <= beast::zero)
{
JLOG(ctx.j.warn())
<< "GenesisMint: only positive amounts can be minted.";
return temMALFORMED;
}
}
auto const accid = dest.getAccountID(sfDestination);
if (accid == noAccount() || accid == xrpAccount())
@@ -139,11 +156,17 @@ GenesisMint::doApply()
for (auto const& dest: dests)
{
auto const amt = dest.getFieldAmount(sfAmount);
auto const amt = dest[~sfAmount];
auto const flags = dest[~sfGovernanceFlags];
auto const marks = dest[~sfGovernanceMarks];
auto const id = dest.getAccountID(sfDestination);
auto const k = keylet::account(id);
auto sle = view().peek(k);
if (!sle)
bool const created = !sle;
if (created)
{
// Create the account.
std::uint32_t const seqno{
@@ -153,26 +176,37 @@ GenesisMint::doApply()
sle->setAccountID(sfAccount, id);
sle->setFieldU32(sfSequence, seqno);
sle->setFieldAmount(sfBalance, amt);
view().insert(sle);
if (amt)
sle->setFieldAmount(sfBalance, *amt);
else // give them 2 XRP if the account didn't exist, same as ttIMPORT
sle->setFieldAmount(sfBalance, XRPAmount {2 * DROPS_PER_XRP});
}
else
else if (amt)
{
// Credit the account
STAmount startBal = sle->getFieldAmount(sfBalance);
STAmount finalBal = startBal + amt;
STAmount finalBal = startBal + *amt;
if (finalBal > startBal)
{
sle->setFieldAmount(sfBalance, finalBal);
view().update(sle);
}
else
{
JLOG(ctx_.journal.warn())
<< "GenesisMint: cannot credit " << dest << " due to balance overflow";
}
}
// set flags and marks as applicable
if (flags)
sle->setFieldH256(sfGovernanceFlags, *flags);
if (marks)
sle->setFieldH256(sfGovernanceMarks, *marks);
if (created)
view().insert(sle);
else
view().update(sle);
}
return tesSUCCESS;
@@ -181,7 +215,7 @@ GenesisMint::doApply()
XRPAmount
GenesisMint::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return Transactor::calculateBaseFee(view, tx);
return XRPAmount { 0 } ;
}
} // namespace ripple

View File

@@ -560,11 +560,28 @@ Import::preflight(PreflightContext const& ctx)
if (!xpop)
return temMALFORMED;
// check if we recognise the vl key
auto const& vlKeys = ctx.app.config().IMPORT_VL_KEYS;
auto const found = std::ranges::find(vlKeys, (*xpop)[jss::validation][jss::unl][jss::public_key].asString());
if (found == vlKeys.end())
return telIMPORT_VL_KEY_NOT_RECOGNISED;
// we will check if we recognise the vl key in preclaim because it may be from on-ledger object
std::optional<PublicKey> masterVLKey;
{
std::string strPk = (*xpop)[jss::validation][jss::unl][jss::public_key].asString();
auto pkHex = strUnHex(strPk);
if (!pkHex)
{
JLOG(ctx.j.warn())
<< "Import: validation.unl.public_key was not valid hex.";
return temMALFORMED;
}
auto const pkType = publicKeyType(makeSlice(*pkHex));
if (!pkType)
{
JLOG(ctx.j.warn())
<< "Import: validation.unl.public_key was not a recognised public key type.";
return temMALFORMED;
}
masterVLKey = PublicKey(makeSlice(*pkHex));
}
auto const [stpTrans, meta] = getInnerTxn(tx, ctx.j, &(*xpop));
@@ -734,7 +751,9 @@ Import::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (strHex(m->masterKey) != *found)
// we will check the master key matches a known one in preclaim, because the import vl key might be
// from the on-ledger object
if (m->masterKey != masterVLKey)
{
JLOG(ctx.j.warn())
<< "Import: manifest master key did not match top level master key in unl section of xpop "
@@ -749,6 +768,7 @@ Import::preflight(PreflightContext const& ctx)
<< tx.getTransactionID();
return temMALFORMED;
}
// manifest signing (ephemeral) key
auto const signingKey = m->signingKey;
@@ -1248,7 +1268,34 @@ Import::preclaim(PreclaimContext const& ctx)
if (sleVL && sleVL->getFieldU32(sfImportSequence) > vl->first)
return tefPAST_IMPORT_VL_SEQ;
return tesSUCCESS;
// check master VL key
std::string strPk = (*xpop)[jss::validation][jss::unl][jss::public_key].asString();
if (auto const& found = ctx.app.config().IMPORT_VL_KEYS.find(strPk);
found != ctx.app.config().IMPORT_VL_KEYS.end())
return tesSUCCESS;
// not found in our local VL keys
auto pkHex = strUnHex(strPk);
if (!pkHex)
return tefINTERNAL;
auto const pkType = publicKeyType(makeSlice(*pkHex));
if (!pkType)
return tefINTERNAL;
PublicKey const pk (makeSlice(*pkHex));
// check on ledger
if (auto const unlRep = ctx.view.read(keylet::UNLReport()); unlRep)
{
auto const& vlKeys = unlRep->getFieldArray(sfImportVLKeys);
for (auto const& k: vlKeys)
if (PublicKey(k[sfPublicKey]) == pk)
return tesSUCCESS;
}
return telIMPORT_VL_KEY_NOT_RECOGNISED;
}
TER

View File

@@ -25,6 +25,7 @@
#include <ripple/basics/base_uint.h>
#include <ripple/beast/net/IPEndpoint.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SystemParameters.h> // VFALCO Breaks levelization
#include <boost/beast/core/string.hpp>
#include <boost/filesystem.hpp> // VFALCO FIX: This include should not be here
@@ -150,7 +151,7 @@ public:
std::vector<std::string> IPS_FIXED; // Fixed Peer IPs from rippled.cfg.
std::vector<std::string> SNTP_SERVERS; // SNTP servers from rippled.cfg.
std::vector<std::string> IMPORT_VL_KEYS;
std::map<std::string, PublicKey> IMPORT_VL_KEYS; // hex string -> class PublicKey (for caching purposes)
enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK };
StartUpType START_UP = NORMAL;

View File

@@ -904,15 +904,22 @@ Config::loadFromString(std::string const& fileContents)
auto iniFile = parseIniFile(data, true);
if (auto importKeys =
getIniFileSection(iniFile, SECTION_IMPORT_VL_KEYS))
IMPORT_VL_KEYS = *importKeys;
else
Throw<std::runtime_error>(
"The file specified in [" SECTION_VALIDATORS_FILE
"] "
"does not contain a [" SECTION_IMPORT_VL_KEYS
"] section: " +
validatorsFile.string());
getIniFileSection(iniFile, SECTION_IMPORT_VL_KEYS); importKeys)
{
for (std::string const& strPk : *importKeys)
{
auto pkHex = strUnHex(strPk);
if (!pkHex)
Throw<std::runtime_error>(
"Import VL Key '" + strPk + "' was not valid hex.");
auto const pkType = publicKeyType(makeSlice(*pkHex));
if (!pkType)
Throw<std::runtime_error>(
"Import VL Key '" + strPk + "' was not a valid key type.");
IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex));
}
}
if (RUN_STANDALONE)
break;

View File

@@ -477,6 +477,8 @@ extern SF_UINT256 const sfHookSetTxnID;
extern SF_UINT256 const sfOfferID;
extern SF_UINT256 const sfEscrowID;
extern SF_UINT256 const sfURITokenID;
extern SF_UINT256 const sfGovernanceFlags;
extern SF_UINT256 const sfGovernanceMarks;
// currency amount (common)
extern SF_AMOUNT const sfAmount;
@@ -582,6 +584,7 @@ extern SField const sfHookDefinition;
extern SField const sfHookParameter;
extern SField const sfHookGrant;
extern SField const sfActiveValidator;
extern SField const sfImportVLKey;
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -607,6 +610,7 @@ extern SField const sfHooks;
extern SField const sfHookGrants;
extern SField const sfGenesisMints;
extern SField const sfActiveValidators;
extern SField const sfImportVLKeys;
//------------------------------------------------------------------------------

View File

@@ -130,7 +130,9 @@ InnerObjectFormats::InnerObjectFormats()
sfGenesisMint.getCode(),
{
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfGovernanceFlags, soeOPTIONAL},
{sfGovernanceMarks, soeOPTIONAL},
});
}

View File

@@ -63,6 +63,8 @@ LedgerFormats::LedgerFormats()
{sfRewardAccumulator, soeOPTIONAL},
{sfFirstNFTokenSequence, soeOPTIONAL},
{sfImportSequence, soeOPTIONAL},
{sfGovernanceFlags, soeOPTIONAL},
{sfGovernanceMarks, soeOPTIONAL},
},
commonFields);
@@ -301,6 +303,7 @@ LedgerFormats::LedgerFormats()
add(jss::UNLReport,
ltUNL_REPORT,
{
{sfImportVLKeys, soeREQUIRED},
{sfActiveValidators, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},

View File

@@ -230,6 +230,8 @@ CONSTRUCT_TYPED_SFIELD(sfHookSetTxnID, "HookSetTxnID", UINT256,
CONSTRUCT_TYPED_SFIELD(sfOfferID, "OfferID", UINT256, 34);
CONSTRUCT_TYPED_SFIELD(sfEscrowID, "EscrowID", UINT256, 35);
CONSTRUCT_TYPED_SFIELD(sfURITokenID, "URITokenID", UINT256, 36);
CONSTRUCT_TYPED_SFIELD(sfGovernanceFlags, "GovernanceFlags", UINT256, 99);
CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, 98);
// currency amount (common)
CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
@@ -338,6 +340,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT,
CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24);
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 96);
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, 94);
// array of objects
// ARRAY/1 is reserved for end of array
@@ -360,6 +363,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY,
CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20);
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 96);
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94);
// clang-format on

View File

@@ -195,8 +195,10 @@ TxFormats::TxFormats()
add(jss::UNLReport,
ttUNL_REPORT,
{
{sfLedgerSequence, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},
{sfActiveValidator, soeOPTIONAL},
{sfImportVLKey, soeOPTIONAL},
},
commonFields);

View File

@@ -752,7 +752,7 @@ voteAndCheck(
std::size_t expect,
PreVote const& pre = defaultPreVote)
{
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
pre(vote);
auto txSet = std::make_shared<SHAMap>(
SHAMapType::TRANSACTION, history.env.app().getNodeFamily());
@@ -773,7 +773,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
// one add, one remove
auto txSet = std::make_shared<SHAMap>(
@@ -797,7 +797,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
uint256 pad_0(0);
uint256 pad_f = ~pad_0;
@@ -834,7 +834,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
if (history.goodHistory)
{
NegativeUNLVote vote(
history.UNLNodeIDs[3], history.env.journal);
history.UNLNodeIDs[3], history.env.journal, history.env.app());
BEAST_EXPECT(!vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -849,7 +849,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
if (history.goodHistory)
{
NegativeUNLVote vote(
history.UNLNodeIDs[3], history.env.journal);
history.UNLNodeIDs[3], history.env.journal, history.env.app());
BEAST_EXPECT(!vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -872,7 +872,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
history.UNLNodeIDs[idx] == myId &&
l->seq() % 2 == 0);
});
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
BEAST_EXPECT(!vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -915,7 +915,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
history.validations.add(badNode, v2);
}
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
// local node still on wrong chain, can build a scoreTable,
// but all other nodes' scores are zero
@@ -954,7 +954,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
[&](std::shared_ptr<Ledger const> const& l,
std::size_t idx) -> bool { return true; });
NegativeUNLVote vote(
history.UNLNodeIDs[3], history.env.journal);
history.UNLNodeIDs[3], history.env.journal, history.env.app());
auto scoreTable = vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,
@@ -1031,7 +1031,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
for (auto const& n : history.UNLNodeIDs)
goodScoreTable[n] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal);
NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal, history.env.app());
{
// all good scores
@@ -1165,7 +1165,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
std::array<std::uint32_t, 3> unlSizes = {34, 35, 80};
std::array<std::uint32_t, 3> nUnlPercent = {0, 50, 100};
@@ -1338,7 +1338,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
jtx::Env env(*this);
NodeID myId(0xA0);
NegativeUNLVote vote(myId, env.journal);
NegativeUNLVote vote(myId, env.journal, env.app());
// test cases:
// newValidators_ of the NegativeUNLVote empty, add one
@@ -1451,7 +1451,7 @@ class NegativeUNLVoteScoreTable_test : public beast::unit_test::suite
return add_50 || add_100 || add_me;
});
NegativeUNLVote vote(myId, history.env.journal);
NegativeUNLVote vote(myId, history.env.journal, history.env.app());
auto scoreTable = vote.buildScoreTable(
history.lastLedger(),
history.UNLNodeIDSet,