mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-13 07:08:12 +00:00
Compare commits
2 Commits
bthomee/sh
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2478f509c | ||
|
|
c908ed7839 |
@@ -95,20 +95,40 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx)
|
||||
auto& router = ctx.registry.get().getHashRouter();
|
||||
|
||||
auto const id = ctx.tx.getTransactionID();
|
||||
auto const flags = router.getFlags(id);
|
||||
auto flags = router.getFlags(id);
|
||||
|
||||
// If we haven't checked the condition, check it
|
||||
// now. Whether it passes or not isn't important
|
||||
// in preflight.
|
||||
// now. Other than a malformed condition, whether
|
||||
// it passes or not isn't important in preflight.
|
||||
if (!any(flags & (kSfCfInvalid | kSfCfValid)))
|
||||
{
|
||||
if (checkCondition(*fb, *cb))
|
||||
{
|
||||
router.setFlags(id, kSfCfValid);
|
||||
flags = kSfCfValid;
|
||||
}
|
||||
else
|
||||
{
|
||||
router.setFlags(id, kSfCfInvalid);
|
||||
flags = kSfCfInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
// The invalid flag also covers a bad fulfillment, so
|
||||
// check the condition itself. This runs whether or not
|
||||
// the flag was cached, so the result is the same on
|
||||
// every node.
|
||||
if (any(flags & kSfCfInvalid) && ctx.rules.enabled(fixCleanup3_5_0))
|
||||
{
|
||||
using namespace xrpl::cryptoconditions;
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
auto const condition = Condition::deserialize(*cb, ec);
|
||||
if (!condition)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Malformed condition during escrow finish: " << ec.message();
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -941,41 +941,43 @@ struct Escrow_test : public beast::unit_test::Suite
|
||||
|
||||
// Now, try to fulfill using the same sequence of
|
||||
// malformed conditions.
|
||||
TER const malformedCondTer =
|
||||
features[fixCleanup3_5_0] ? TER{temMALFORMED} : TER{tecCRYPTOCONDITION_ERROR};
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{cp, cs}),
|
||||
escrow::kFulfillment(Slice{fp, fs}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(malformedCondTer));
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{cp, cs - 1}),
|
||||
escrow::kFulfillment(Slice{fp, fs}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(malformedCondTer));
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{cp, cs - 2}),
|
||||
escrow::kFulfillment(Slice{fp, fs}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(malformedCondTer));
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{cp + 1, cs - 1}),
|
||||
escrow::kFulfillment(Slice{fp, fs}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(malformedCondTer));
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{cp + 1, cs - 3}),
|
||||
escrow::kFulfillment(Slice{fp, fs}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(malformedCondTer));
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{cp + 2, cs - 2}),
|
||||
escrow::kFulfillment(Slice{fp, fs}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(malformedCondTer));
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{cp + 2, cs - 3}),
|
||||
escrow::kFulfillment(Slice{fp, fs}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(malformedCondTer));
|
||||
|
||||
// Now, using the correct kCondition, try malformed fulfillments:
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
@@ -1043,11 +1045,13 @@ struct Escrow_test : public beast::unit_test::Suite
|
||||
escrow::kCondition(escrow::kCb3),
|
||||
escrow::kCancelTime(env.now() + 1s));
|
||||
|
||||
TER const emptyCondTer =
|
||||
features[fixCleanup3_5_0] ? TER{temMALFORMED} : TER{tecCRYPTOCONDITION_ERROR};
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(Slice{}),
|
||||
escrow::kFulfillment(Slice{}),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(emptyCondTer));
|
||||
env(escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(escrow::kCb3),
|
||||
escrow::kFulfillment(Slice{}),
|
||||
@@ -1057,7 +1061,7 @@ struct Escrow_test : public beast::unit_test::Suite
|
||||
escrow::kCondition(Slice{}),
|
||||
escrow::kFulfillment(escrow::kFb3),
|
||||
Fee(150 * baseFee),
|
||||
Ter(tecCRYPTOCONDITION_ERROR));
|
||||
Ter(emptyCondTer));
|
||||
|
||||
// Assemble finish that is missing the Condition or the Fulfillment
|
||||
// since either both must be present, or neither can:
|
||||
@@ -1097,6 +1101,41 @@ struct Escrow_test : public beast::unit_test::Suite
|
||||
escrow::kCancelTime(env.now() + 1s),
|
||||
Ter(temMALFORMED));
|
||||
}
|
||||
{ // Test finish with a malformed condition
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq("alice");
|
||||
env(escrow::create("alice", "carol", XRP(1000)),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
escrow::kCancelTime(env.now() + 100s));
|
||||
env.close();
|
||||
|
||||
// Does not deserialize as a condition.
|
||||
std::array<std::uint8_t, 3> const malformed = {{0x78, 0x78, 0x78}};
|
||||
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const bobBalance = env.balance("bob");
|
||||
bool const fixEnabled = features[fixCleanup3_5_0];
|
||||
auto const jt = env.jt(
|
||||
escrow::finish("bob", "alice", seq),
|
||||
escrow::kCondition(malformed),
|
||||
escrow::kFulfillment(escrow::kFb1),
|
||||
Fee(150 * baseFee));
|
||||
env(jt, Ter(fixEnabled ? TER{temMALFORMED} : TER{tecCRYPTOCONDITION_ERROR}));
|
||||
// Run preflight on the same transaction again, now with the
|
||||
// HashRouter flag cached. The result must not change.
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jt.stx, TapNone, env.journal);
|
||||
BEAST_EXPECT(pf.ter == (fixEnabled ? TER{temMALFORMED} : TER{tesSUCCESS}));
|
||||
env.close();
|
||||
|
||||
// Before the fix, the transaction is included and charges a fee.
|
||||
env.require(
|
||||
Balance("bob", fixEnabled ? bobBalance : bobBalance - drops(150 * baseFee)));
|
||||
BEAST_EXPECT(env.le(keylet::escrow(Account("alice").id(), SeqProxy::rawSequence(seq))));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1642,6 +1681,7 @@ public:
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testWithFeats(all);
|
||||
testWithFeats(all - featureTokenEscrow);
|
||||
testWithFeats(all - fixCleanup3_5_0);
|
||||
testTags(all - fixIncludeKeyletFields);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ ConsensusTransSetSF::gotNode(
|
||||
|
||||
nodeCache_.insert(nodeHash, nodeData);
|
||||
|
||||
if ((type == SHAMapNodeType::TnTransactionNm) && (nodeData.size() > 16))
|
||||
if ((type == SHAMapNodeType::TnTransactionNm) && (nodeData.size() >= kMinTxNodeBytesToParse))
|
||||
{
|
||||
// this is a transaction, and we didn't have it
|
||||
JLOG(j_.debug()) << "Node on our acquiring TX set is TXN we may not have";
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/shamap/SHAMapSyncFilter.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
@@ -24,6 +25,15 @@ class ConsensusTransSetSF : public SHAMapSyncFilter
|
||||
public:
|
||||
using NodeCache = TaggedCache<SHAMapHash, Blob>;
|
||||
|
||||
/**
|
||||
* The size a node's hash-prefixed wire data must reach before gotNode()
|
||||
* tries to parse and resubmit it as a transaction. One byte past the
|
||||
* smallest a hash-prefixed SHAMap leaf can be, which a signed transaction
|
||||
* clears.
|
||||
*/
|
||||
static constexpr std::size_t kMinTxNodeBytesToParse =
|
||||
sizeof(std::uint32_t) + kMinShaMapItemBytes + 1;
|
||||
|
||||
ConsensusTransSetSF(Application& app, NodeCache& nodeCache);
|
||||
|
||||
// Note that the nodeData is overwritten by this call
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
namespace xrpl {
|
||||
|
||||
// A ledger we are trying to acquire
|
||||
class InboundLedger final : public TimeoutCounter,
|
||||
public std::enable_shared_from_this<InboundLedger>,
|
||||
public CountedObject<InboundLedger>
|
||||
class InboundLedger : public TimeoutCounter,
|
||||
public std::enable_shared_from_this<InboundLedger>,
|
||||
public CountedObject<InboundLedger>
|
||||
{
|
||||
public:
|
||||
using ClockType = beast::AbstractClock<std::chrono::steady_clock>;
|
||||
@@ -44,13 +44,29 @@ public:
|
||||
CONSENSUS, // We believe the consensus round requires this ledger
|
||||
};
|
||||
|
||||
/**
|
||||
* How long to wait between retries, and so how long one timeout takes.
|
||||
*/
|
||||
static constexpr std::chrono::milliseconds kRetryInterval{3000};
|
||||
|
||||
/**
|
||||
* @param app The application to run in.
|
||||
* @param hash The ledger to acquire.
|
||||
* @param seq Its sequence, or zero if not known yet.
|
||||
* @param reason Why it is being acquired.
|
||||
* @param clock The clock touch() records against.
|
||||
* @param peerSet Which peers to ask, and how to reach them.
|
||||
* @param retryInterval How long to wait between retries. TimeoutCounter
|
||||
* requires more than 10ms and less than 30s.
|
||||
*/
|
||||
InboundLedger(
|
||||
Application& app,
|
||||
UInt256 const& hash,
|
||||
std::uint32_t seq,
|
||||
Reason reason,
|
||||
ClockType&,
|
||||
std::unique_ptr<PeerSet> peerSet);
|
||||
ClockType& clock,
|
||||
std::unique_ptr<PeerSet> peerSet,
|
||||
std::chrono::milliseconds retryInterval = kRetryInterval);
|
||||
|
||||
~InboundLedger() override;
|
||||
|
||||
@@ -119,15 +135,33 @@ public:
|
||||
return lastAction_;
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
// Protected so a test subclass can drive an acquisition through trigger() and done().
|
||||
|
||||
// Why trigger() is being run, which decides how deep a request goes and whether an
|
||||
// aggressive retry applies.
|
||||
enum class TriggerReason { Added, Reply, Timeout };
|
||||
|
||||
/**
|
||||
* Ask for more nodes, or judge what has been collected.
|
||||
*
|
||||
* @param peer The peer to ask, or nullptr to ask everyone being tracked.
|
||||
* @param reason Why the acquisition is being triggered.
|
||||
*/
|
||||
void
|
||||
trigger(std::shared_ptr<Peer> const& peer, TriggerReason reason);
|
||||
|
||||
/**
|
||||
* Settle the acquisition and signal whatever is waiting on it. Runs at most
|
||||
* once. Callers hold mtx_, except trigger(), which releases it first.
|
||||
*/
|
||||
void
|
||||
done();
|
||||
|
||||
private:
|
||||
void
|
||||
filterNodes(std::vector<std::pair<SHAMapNodeID, UInt256>>& nodes, TriggerReason reason);
|
||||
|
||||
void
|
||||
trigger(std::shared_ptr<Peer> const&, TriggerReason);
|
||||
|
||||
std::vector<NeededHashT>
|
||||
getNeededHashes();
|
||||
|
||||
@@ -138,10 +172,7 @@ private:
|
||||
tryDB(node_store::Database& srcDB);
|
||||
|
||||
void
|
||||
done();
|
||||
|
||||
void
|
||||
onTimer(bool progress, ScopedLockType& peerSetLock) override;
|
||||
onTimer(bool progress, ScopedLockType& sl) override;
|
||||
|
||||
std::size_t
|
||||
getPeerCount() const;
|
||||
|
||||
@@ -55,8 +55,6 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static constexpr auto kPeerCountStart = 5; // Number of peers to start with
|
||||
static constexpr auto kPeerCountAdd = 3; // Number of peers to add on a timeout
|
||||
static constexpr auto kLedgerTimeoutRetriesMax = 6; // how many timeouts before we give up
|
||||
@@ -66,20 +64,18 @@ static constexpr auto kMissingNodesFind = 256; // Number of nodes to find initi
|
||||
static constexpr auto kReqNodesReply = 128; // Number of nodes to request for a reply
|
||||
static constexpr auto kReqNodes = 12; // Number of nodes to request blindly
|
||||
|
||||
// millisecond for each ledger timeout
|
||||
constexpr auto kLedgerAcquireTimeout = 3000ms;
|
||||
|
||||
InboundLedger::InboundLedger(
|
||||
Application& app,
|
||||
UInt256 const& hash,
|
||||
std::uint32_t seq,
|
||||
Reason reason,
|
||||
ClockType& clock,
|
||||
std::unique_ptr<PeerSet> peerSet)
|
||||
std::unique_ptr<PeerSet> peerSet,
|
||||
std::chrono::milliseconds retryInterval)
|
||||
: TimeoutCounter(
|
||||
app,
|
||||
hash,
|
||||
kLedgerAcquireTimeout,
|
||||
retryInterval,
|
||||
{.jobType = JtLedgerData, .jobName = "InboundLedger", .jobLimit = 5},
|
||||
app.getJournal("InboundLedger"))
|
||||
, clock_(clock)
|
||||
|
||||
@@ -98,9 +98,14 @@ protected:
|
||||
|
||||
/**
|
||||
* Hook called from invokeOnTimer().
|
||||
*
|
||||
* @param progress Whether the subtype recorded progress since the
|
||||
* last call.
|
||||
* @param sl Proof mtx_ is held. mtx_ is recursive, so a nested lock taken
|
||||
* inside the call does not release it.
|
||||
*/
|
||||
virtual void
|
||||
onTimer(bool progress, ScopedLockType&) = 0;
|
||||
onTimer(bool progress, ScopedLockType& sl) = 0;
|
||||
|
||||
/**
|
||||
* Return a weak pointer to this.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
@@ -26,22 +27,18 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Timeout interval in milliseconds
|
||||
constexpr auto kTxAcquireTimeout = 250ms;
|
||||
|
||||
static constexpr auto kNormTimeouts = 4;
|
||||
static constexpr auto kMaxTimeouts = 20;
|
||||
|
||||
TransactionAcquire::TransactionAcquire(
|
||||
Application& app,
|
||||
UInt256 const& hash,
|
||||
std::unique_ptr<PeerSet> peerSet)
|
||||
std::unique_ptr<PeerSet> peerSet,
|
||||
std::chrono::milliseconds retryInterval)
|
||||
: TimeoutCounter(
|
||||
app,
|
||||
hash,
|
||||
kTxAcquireTimeout,
|
||||
retryInterval,
|
||||
{.jobType = JtTxnData, .jobName = "TxAcq", .jobLimit = {}},
|
||||
app.getJournal("TransactionAcquire"))
|
||||
, peerSet_(std::move(peerSet))
|
||||
@@ -79,7 +76,7 @@ TransactionAcquire::done()
|
||||
}
|
||||
|
||||
void
|
||||
TransactionAcquire::onTimer(bool progress, ScopedLockType& psl)
|
||||
TransactionAcquire::onTimer(bool progress, ScopedLockType&)
|
||||
{
|
||||
if (timeouts_ > kMaxTimeouts)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
@@ -21,14 +22,30 @@ namespace xrpl {
|
||||
|
||||
// VFALCO TODO rename to PeerTxRequest
|
||||
// A transaction set we are trying to acquire
|
||||
class TransactionAcquire final : public TimeoutCounter,
|
||||
public std::enable_shared_from_this<TransactionAcquire>,
|
||||
public CountedObject<TransactionAcquire>
|
||||
class TransactionAcquire : public TimeoutCounter,
|
||||
public std::enable_shared_from_this<TransactionAcquire>,
|
||||
public CountedObject<TransactionAcquire>
|
||||
{
|
||||
public:
|
||||
using pointer = std::shared_ptr<TransactionAcquire>;
|
||||
|
||||
TransactionAcquire(Application& app, UInt256 const& hash, std::unique_ptr<PeerSet> peerSet);
|
||||
/**
|
||||
* How long to wait between retries, and so how long one timeout takes.
|
||||
*/
|
||||
static constexpr std::chrono::milliseconds kRetryInterval{250};
|
||||
|
||||
/**
|
||||
* @param app The application to run in.
|
||||
* @param hash The set to acquire.
|
||||
* @param peerSet Which peers to ask, and how to reach them.
|
||||
* @param retryInterval How long to wait between retries. TimeoutCounter
|
||||
* requires more than 10ms and less than 30s.
|
||||
*/
|
||||
TransactionAcquire(
|
||||
Application& app,
|
||||
UInt256 const& hash,
|
||||
std::unique_ptr<PeerSet> peerSet,
|
||||
std::chrono::milliseconds retryInterval = kRetryInterval);
|
||||
~TransactionAcquire() override = default;
|
||||
|
||||
SHAMapAddNode
|
||||
@@ -42,14 +59,23 @@ public:
|
||||
void
|
||||
stillNeed();
|
||||
|
||||
private:
|
||||
protected:
|
||||
// Kept protected so a test subclass can read the map's state.
|
||||
// Production callers reach a set through InboundTransactions.
|
||||
std::shared_ptr<SHAMap> map_;
|
||||
|
||||
private:
|
||||
bool haveRoot_{false};
|
||||
std::unique_ptr<PeerSet> peerSet_;
|
||||
|
||||
void
|
||||
onTimer(bool progress, ScopedLockType& peerSetLock) override;
|
||||
onTimer(bool progress, ScopedLockType& sl) override;
|
||||
|
||||
/**
|
||||
* Settle the acquired set and hand it on, or report the failure. Call under
|
||||
* mtx_. Runs once per outcome, and a stillNeed() revival gives one object a
|
||||
* second outcome.
|
||||
*/
|
||||
void
|
||||
done();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user