Skip to content

Commit

Permalink
Add config to run without valid etl (XRPLF#946)
Browse files Browse the repository at this point in the history
  • Loading branch information
cindyyan317 authored Oct 20, 2023
1 parent 1aab2b9 commit e062121
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 51 deletions.
1 change: 1 addition & 0 deletions example-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
// ---
}
},
"allow_no_etl": false, // Allow Clio to run without valid ETL source, otherwise Clio will stop if ETL check fails
"etl_sources": [
{
"ip": "127.0.0.1",
Expand Down
3 changes: 2 additions & 1 deletion src/etl/ETLService.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ class ETLService {

/**
* @brief Get the etl nodes' state
* @return the etl nodes' state, nullopt if etl nodes are not connected
*/
etl::ETLState
std::optional<etl::ETLState>
getETLState() const noexcept
{
return loadBalancer_->getETLState();
Expand Down
15 changes: 0 additions & 15 deletions src/etl/ETLState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,11 @@
*/
//==============================================================================

#include <data/BackendInterface.h>
#include <etl/ETLState.h>
#include <etl/Source.h>
#include <rpc/JS.h>

namespace etl {

ETLState
ETLState::fetchETLStateFromSource(Source const& source) noexcept
{
auto const serverInfoRippled = data::synchronous([&source](auto yield) {
return source.forwardToRippled({{"command", "server_info"}}, std::nullopt, yield);
});

if (serverInfoRippled)
return boost::json::value_to<ETLState>(boost::json::value(*serverInfoRippled));

return ETLState{};
}

ETLState
tag_invoke(boost::json::value_to_tag<ETLState>, boost::json::value const& jv)
{
Expand Down
21 changes: 17 additions & 4 deletions src/etl/ETLState.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@

#pragma once

#include <data/BackendInterface.h>

#include <boost/json.hpp>

#include <cstdint>
#include <optional>

namespace etl {

class Source;

/**
* @brief This class is responsible for fetching and storing the state of the ETL information, such as the network id
*/
Expand All @@ -36,9 +36,22 @@ struct ETLState {

/**
* @brief Fetch the ETL state from the rippled server
* @param source The source to fetch the state from
* @return The ETL state, nullopt if source not available
*/
static ETLState
fetchETLStateFromSource(Source const& source) noexcept;
template <typename Forward>
static std::optional<ETLState>
fetchETLStateFromSource(Forward const& source) noexcept
{
auto const serverInfoRippled = data::synchronous([&source](auto yield) {
return source.forwardToRippled({{"command", "server_info"}}, std::nullopt, yield);
});

if (serverInfoRippled)
return boost::json::value_to<ETLState>(boost::json::value(*serverInfoRippled));

return std::nullopt;
}
};

ETLState
Expand Down
56 changes: 35 additions & 21 deletions src/etl/LoadBalancer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,33 +83,44 @@ LoadBalancer::LoadBalancer(
downloadRanges_ = 4;
}

auto const allowNoEtl = config.valueOr("allow_no_etl", false);

auto const checkOnETLFailure = [this, allowNoEtl](std::string const& log) {
LOG(log_.error()) << log;

if (!allowNoEtl) {
LOG(log_.error()) << "Set allow_no_etl as true in config to allow clio run without valid ETL sources.";
throw std::logic_error("ETL configuration error.");
}
};

for (auto const& entry : config.array("etl_sources")) {
std::unique_ptr<Source> source = make_Source(entry, ioc, backend, subscriptions, validatedLedgers, *this);

// checking etl node validity
auto const state = ETLState::fetchETLStateFromSource(*source);

if (!state.networkID) {
LOG(log_.error()) << "Failed to fetch ETL state from source = " << source->toString()
<< " Please check the configuration and network";
throw std::logic_error("ETL node not available");
auto const stateOpt = ETLState::fetchETLStateFromSource(*source);

if (!stateOpt) {
checkOnETLFailure(fmt::format(
"Failed to fetch ETL state from source = {} Please check the configuration and network",
source->toString()
));
} else if (etlState_ && etlState_->networkID && stateOpt->networkID && etlState_->networkID != stateOpt->networkID) {
checkOnETLFailure(fmt::format(
"ETL sources must be on the same network. Source network id = {} does not match others network id = {}",
*(stateOpt->networkID),
*(etlState_->networkID)
));
} else {
etlState_ = stateOpt;
}

if (etlState_ && etlState_->networkID != state.networkID) {
LOG(log_.error()) << "ETL sources must be on the same network. "
<< "Source network id = " << *(state.networkID)
<< " does not match others network id = " << *(etlState_->networkID);
throw std::logic_error("ETL nodes are not in the same network");
}
etlState_ = state;
sources_.push_back(std::move(source));
LOG(log_.info()) << "Added etl source - " << sources_.back()->toString();
}

if (sources_.empty()) {
LOG(log_.error()) << "No ETL sources configured. Please check the configuration";
throw std::logic_error("No ETL sources configured");
}
if (sources_.empty())
checkOnETLFailure("No ETL sources configured. Please check the configuration");
}

LoadBalancer::~LoadBalancer()
Expand Down Expand Up @@ -256,11 +267,14 @@ LoadBalancer::execute(Func f, uint32_t ledgerSequence)
return true;
}

ETLState
LoadBalancer::getETLState() const noexcept
std::optional<ETLState>
LoadBalancer::getETLState() noexcept
{
assert(etlState_); // etlState_ is set in the constructor
return *etlState_;
if (!etlState_) {
// retry ETLState fetch
etlState_ = ETLState::fetchETLStateFromSource(*this);
}
return etlState_;
}

} // namespace etl
5 changes: 3 additions & 2 deletions src/etl/LoadBalancer.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,10 @@ class LoadBalancer {

/**
* @brief Return state of ETL nodes.
* @return ETL state, nullopt if etl nodes not available
*/
ETLState
getETLState() const noexcept;
std::optional<ETLState>
getETLState() noexcept;

private:
/**
Expand Down
4 changes: 3 additions & 1 deletion src/rpc/handlers/Tx.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ class BaseTxHandler {
return Error{Status{RippledError::rpcEXCESSIVE_LGR_RANGE}};
}

auto const currentNetId = etl_->getETLState().networkID;
std::optional<uint32_t> currentNetId = std::nullopt;
if (auto const& etlState = etl_->getETLState(); etlState.has_value())
currentNetId = etlState->networkID;

std::optional<data::TransactionAndMetadata> dbResponse;

Expand Down
10 changes: 6 additions & 4 deletions unittests/etl/ETLStateTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ TEST_F(ETLStateTest, Error)
{
EXPECT_CALL(source, forwardToRippled).WillOnce(Return(std::nullopt));
auto const state = etl::ETLState::fetchETLStateFromSource(source);
EXPECT_FALSE(state.networkID.has_value());
EXPECT_FALSE(state);
}

TEST_F(ETLStateTest, NetworkIdValid)
Expand All @@ -52,8 +52,9 @@ TEST_F(ETLStateTest, NetworkIdValid)
);
EXPECT_CALL(source, forwardToRippled).WillOnce(Return(json.as_object()));
auto const state = etl::ETLState::fetchETLStateFromSource(source);
ASSERT_TRUE(state.networkID.has_value());
EXPECT_EQ(state.networkID.value(), 12);
ASSERT_TRUE(state.has_value());
ASSERT_TRUE(state->networkID.has_value());
EXPECT_EQ(state->networkID.value(), 12);
}

TEST_F(ETLStateTest, NetworkIdInvalid)
Expand All @@ -69,5 +70,6 @@ TEST_F(ETLStateTest, NetworkIdInvalid)
);
EXPECT_CALL(source, forwardToRippled).WillOnce(Return(json.as_object()));
auto const state = etl::ETLState::fetchETLStateFromSource(source);
EXPECT_FALSE(state.networkID.has_value());
ASSERT_TRUE(state.has_value());
EXPECT_FALSE(state->networkID.has_value());
}
77 changes: 75 additions & 2 deletions unittests/rpc/handlers/TxTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ TEST_F(RPCTxTest, ReturnBinaryWithCTID)
TEST_F(RPCTxTest, MintNFT)
{
// Note: `inLedger` is API v1 only. See DefaultOutput_*
auto const static OUT = fmt::format(
auto static const OUT = fmt::format(
R"({{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "50",
Expand Down Expand Up @@ -756,9 +756,82 @@ TEST_F(RPCTxTest, ReturnCTIDForTxInput)
});
}

TEST_F(RPCTxTest, NotReturnCTIDIfETLNotAvaiable)
{
auto constexpr static OUT = R"({
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee":"2",
"Sequence":100,
"SigningPubKey":"74657374",
"TakerGets":
{
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
"issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
"value":"200"
},
"TakerPays":"300",
"TransactionType":"OfferCreate",
"hash":"2E2FBAAFF767227FE4381C4BE9855986A6B9F96C62F6E443731AB36F7BBB8A08",
"meta":
{
"AffectedNodes":
[
{
"CreatedNode":
{
"LedgerEntryType":"Offer",
"NewFields":
{
"TakerGets":"200",
"TakerPays":
{
"currency":"0158415500000000C1F76FF6ECB0BAC600000000",
"issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"value":"300"
}
}
}
}
],
"TransactionIndex":100,
"TransactionResult":"tesSUCCESS"
},
"date":123456,
"ledger_index":100,
"inLedger":100,
"validated": true
})";
auto const rawBackendPtr = dynamic_cast<MockBackend*>(mockBackendPtr.get());
TransactionAndMetadata tx;
tx.metadata = CreateMetaDataForCreateOffer(CURRENCY, ACCOUNT, 100, 200, 300).getSerializer().peekData();
tx.transaction =
CreateCreateOfferTransactionObject(ACCOUNT, 2, 100, CURRENCY, ACCOUNT2, 200, 300).getSerializer().peekData();
tx.date = 123456;
tx.ledgerSequence = 100;
EXPECT_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillOnce(Return(tx));

auto const rawETLPtr = dynamic_cast<MockETLService*>(mockETLServicePtr.get());
ASSERT_NE(rawETLPtr, nullptr);
EXPECT_CALL(*rawETLPtr, getETLState).WillOnce(Return(std::nullopt));

runSpawn([this](auto yield) {
auto const handler = AnyHandler{TestTxHandler{mockBackendPtr, mockETLServicePtr}};
auto const req = json::parse(fmt::format(
R"({{
"command": "tx",
"transaction": "{}"
}})",
TXNID
));
auto const output = handler.process(req, Context{yield});
ASSERT_TRUE(output);
EXPECT_EQ(*output, json::parse(OUT));
});
}

TEST_F(RPCTxTest, ViaCTID)
{
auto const static OUT = fmt::format(
auto static const OUT = fmt::format(
R"({{
"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee":"2",
Expand Down
2 changes: 1 addition & 1 deletion unittests/util/MockETLService.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ struct MockETLService {
MOCK_METHOD(std::uint32_t, lastPublishAgeSeconds, (), (const));
MOCK_METHOD(std::uint32_t, lastCloseAgeSeconds, (), (const));
MOCK_METHOD(bool, isAmendmentBlocked, (), (const));
MOCK_METHOD(etl::ETLState, getETLState, (), (const));
MOCK_METHOD(std::optional<etl::ETLState>, getETLState, (), (const));
};

0 comments on commit e062121

Please sign in to comment.