mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Add Rules to ReadView:
An instance of Rules provides information on the tx processing rules in a particular ledger. * OpenView allows rules to be set on construction. Conflicts: src/ripple/unity/ledger.cpp
This commit is contained in:
@@ -192,7 +192,7 @@ Ledger::Ledger (create_genesis_t, Config const& config)
|
||||
updateHash();
|
||||
setClosed();
|
||||
setAccepted();
|
||||
setFees(config);
|
||||
setup(config);
|
||||
}
|
||||
|
||||
Ledger::Ledger (uint256 const& parentHash,
|
||||
@@ -240,7 +240,7 @@ Ledger::Ledger (uint256 const& parentHash,
|
||||
|
||||
txMap_->setImmutable ();
|
||||
stateMap_->setImmutable ();
|
||||
setFees(config);
|
||||
setup(config);
|
||||
}
|
||||
|
||||
// Create a new ledger that's a snapshot of this one
|
||||
@@ -296,7 +296,7 @@ Ledger::Ledger (void const* data,
|
||||
{
|
||||
SerialIter sit (data, size);
|
||||
setRaw (sit, hasPrefix);
|
||||
// We can't set the fees until the stateMap is populated
|
||||
// Can't set up until the stateMap is filled in
|
||||
}
|
||||
|
||||
Ledger::Ledger (std::uint32_t ledgerSeq,
|
||||
@@ -312,7 +312,7 @@ Ledger::Ledger (std::uint32_t ledgerSeq,
|
||||
info_.seq = ledgerSeq;
|
||||
info_.closeTime = closeTime;
|
||||
info_.closeTimeResolution = ledgerDefaultTimeResolution;
|
||||
setFees(config);
|
||||
setup(config);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -332,7 +332,7 @@ void Ledger::setImmutable ()
|
||||
txMap_->setImmutable ();
|
||||
if (stateMap_)
|
||||
stateMap_->setImmutable ();
|
||||
setFees (getConfig ());
|
||||
setup(getConfig ());
|
||||
}
|
||||
|
||||
void Ledger::updateHash()
|
||||
@@ -1066,7 +1066,7 @@ Ledger::rawTxInsert (uint256 const& key,
|
||||
}
|
||||
|
||||
void
|
||||
Ledger::setFees (Config const& config)
|
||||
Ledger::setup (Config const& config)
|
||||
{
|
||||
fees_.base = config.FEE_DEFAULT;
|
||||
fees_.units = config.TRANSACTION_FEE_BASE;
|
||||
@@ -1089,6 +1089,7 @@ Ledger::setFees (Config const& config)
|
||||
if (sle->getFieldIndex (sfReserveIncrement) != -1)
|
||||
fees_.increment = sle->getFieldU32 (sfReserveIncrement);
|
||||
}
|
||||
rules_ = Rules(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
|
||||
@@ -142,6 +142,12 @@ public:
|
||||
return fees_;
|
||||
}
|
||||
|
||||
Rules const&
|
||||
rules() const override
|
||||
{
|
||||
return rules_;
|
||||
}
|
||||
|
||||
bool
|
||||
exists (Keylet const& k) const override;
|
||||
|
||||
@@ -379,7 +385,7 @@ private:
|
||||
bool saveValidatedLedger (bool current);
|
||||
|
||||
void
|
||||
setFees (Config const& config);
|
||||
setup (Config const& config);
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
peek (Keylet const& k) const;
|
||||
@@ -406,6 +412,7 @@ private:
|
||||
std::mutex mutable mutex_;
|
||||
|
||||
Fees fees_;
|
||||
Rules rules_;
|
||||
LedgerInfo info_;
|
||||
|
||||
// Ripple cost of the reference transaction
|
||||
|
||||
@@ -143,14 +143,16 @@ public:
|
||||
The current view is atomically set to the
|
||||
new open view.
|
||||
|
||||
@param rules The rules for the open ledger
|
||||
@param ledger A new closed ledger
|
||||
*/
|
||||
void
|
||||
accept(std::shared_ptr<Ledger const> const& ledger,
|
||||
OrderedTxs const& locals, bool retriesFirst,
|
||||
OrderedTxs& retries, ApplyFlags flags,
|
||||
IHashRouter& router,
|
||||
std::string const& suffix = "");
|
||||
accept (Rules const& rules,
|
||||
std::shared_ptr<Ledger const> const& ledger,
|
||||
OrderedTxs const& locals, bool retriesFirst,
|
||||
OrderedTxs& retries, ApplyFlags flags,
|
||||
IHashRouter& router,
|
||||
std::string const& suffix = "");
|
||||
|
||||
/** Algorithm for applying transactions.
|
||||
|
||||
@@ -174,8 +176,8 @@ private:
|
||||
};
|
||||
|
||||
std::shared_ptr<OpenView>
|
||||
create (std::shared_ptr<
|
||||
Ledger const> const& ledger);
|
||||
create (Rules const& rules,
|
||||
std::shared_ptr<Ledger const> const& ledger);
|
||||
|
||||
static
|
||||
Result
|
||||
|
||||
@@ -1157,8 +1157,15 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
|
||||
ledgerMaster_.pushLedger (newLCL, newOL);
|
||||
|
||||
#if RIPPLE_OPEN_LEDGER
|
||||
getApp().openLedger().accept(newLCL,
|
||||
localTx, anyDisputes, retries, tapNONE,
|
||||
auto const lastVal =
|
||||
getApp().getLedgerMaster().getValidatedLedger();
|
||||
boost::optional<Rules> rules;
|
||||
if (lastVal)
|
||||
rules.emplace(*lastVal);
|
||||
else
|
||||
rules.emplace();
|
||||
getApp().openLedger().accept(*rules,
|
||||
newLCL, localTx, anyDisputes, retries, tapNONE,
|
||||
getApp().getHashRouter(), "consensus");
|
||||
getApp().openLedger().verify(*newOL, "consensus after");
|
||||
#endif
|
||||
|
||||
@@ -32,7 +32,7 @@ OpenLedger::OpenLedger(std::shared_ptr<
|
||||
: j_ (journal)
|
||||
, cache_ (cache)
|
||||
, config_ (config)
|
||||
, current_ (create(ledger))
|
||||
, current_ (create(ledger->rules(), ledger))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -73,15 +73,15 @@ OpenLedger::modify (std::function<
|
||||
}
|
||||
|
||||
void
|
||||
OpenLedger::accept(std::shared_ptr<
|
||||
Ledger const> const& ledger,
|
||||
OpenLedger::accept(Rules const& rules,
|
||||
std::shared_ptr<Ledger const> const& ledger,
|
||||
OrderedTxs const& locals, bool retriesFirst,
|
||||
OrderedTxs& retries, ApplyFlags flags,
|
||||
IHashRouter& router, std::string const& suffix)
|
||||
{
|
||||
JLOG(j_.error) <<
|
||||
"accept ledger " << ledger->seq() << " " << suffix;
|
||||
auto next = create(ledger);
|
||||
auto next = create(rules, ledger);
|
||||
if (retriesFirst)
|
||||
{
|
||||
// Handle disputed tx, outside lock
|
||||
@@ -121,11 +121,11 @@ OpenLedger::accept(std::shared_ptr<
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::shared_ptr<OpenView>
|
||||
OpenLedger::create(std::shared_ptr<
|
||||
Ledger const> const& ledger)
|
||||
OpenLedger::create (Rules const& rules,
|
||||
std::shared_ptr<Ledger const> const& ledger)
|
||||
{
|
||||
return std::make_shared<OpenView>(
|
||||
open_ledger, std::make_shared<
|
||||
open_ledger, rules, std::make_shared<
|
||||
CachedLedger const>(ledger,
|
||||
cache_));
|
||||
}
|
||||
|
||||
@@ -1322,7 +1322,14 @@ void NetworkOPsImp::switchLastClosedLedger (
|
||||
|
||||
#if RIPPLE_OPEN_LEDGER
|
||||
auto retries = localTx;
|
||||
getApp().openLedger().accept(
|
||||
auto const lastVal =
|
||||
getApp().getLedgerMaster().getValidatedLedger();
|
||||
boost::optional<Rules> rules;
|
||||
if (lastVal)
|
||||
rules.emplace(*lastVal);
|
||||
else
|
||||
rules.emplace();
|
||||
getApp().openLedger().accept(*rules,
|
||||
newLCL, OrderedTxs({}), false, retries,
|
||||
tapNONE, getApp().getHashRouter(), "jump");
|
||||
getApp().openLedger().verify(
|
||||
|
||||
@@ -28,6 +28,7 @@ RippleLineCache::RippleLineCache(
|
||||
{
|
||||
// We want the caching that OpenView provides
|
||||
// And we need to own a shared_ptr to the input view
|
||||
// VFALCO TODO This should be a CachedLedger
|
||||
mLedger = std::make_shared<OpenView>(&*ledger, ledger);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ public:
|
||||
ApplyViewImpl (ApplyViewImpl&&) = default;
|
||||
#endif
|
||||
|
||||
ApplyViewImpl (ReadView const* base,
|
||||
ApplyFlags flags);
|
||||
ApplyViewImpl(
|
||||
ReadView const* base, ApplyFlags flags);
|
||||
|
||||
/** Apply the transaction.
|
||||
|
||||
|
||||
@@ -77,6 +77,12 @@ public:
|
||||
return base_.fees();
|
||||
}
|
||||
|
||||
Rules const&
|
||||
rules() const override
|
||||
{
|
||||
return base_.rules();
|
||||
}
|
||||
|
||||
boost::optional<key_type>
|
||||
succ (key_type const& key, boost::optional<
|
||||
key_type> last = boost::none) const override
|
||||
|
||||
@@ -54,6 +54,7 @@ private:
|
||||
Serializer const>, std::shared_ptr<
|
||||
Serializer const>>>;
|
||||
|
||||
Rules rules_;
|
||||
txs_map txs_;
|
||||
LedgerInfo info_;
|
||||
ReadView const* base_;
|
||||
@@ -69,6 +70,7 @@ public:
|
||||
OpenView (OpenView&& other)
|
||||
: ReadView (std::move(other))
|
||||
, TxsRawView (std::move(other))
|
||||
, rules_ (std::move(other.rules_))
|
||||
, txs_ (std::move(other.txs_))
|
||||
, info_ (std::move(other.info_))
|
||||
, base_ (std::move(other.base_))
|
||||
@@ -108,16 +110,20 @@ public:
|
||||
ownership of a copy of `hold` until
|
||||
the MetaView is destroyed.
|
||||
|
||||
Calls to rules() will return the
|
||||
rules provided on construction.
|
||||
|
||||
The tx list starts empty and will contain
|
||||
all newly inserted tx.
|
||||
*/
|
||||
/** @{ */
|
||||
OpenView (open_ledger_t, ReadView const* base,
|
||||
std::shared_ptr<void const> hold = nullptr);
|
||||
OpenView (open_ledger_t,
|
||||
ReadView const* base, Rules const& rules,
|
||||
std::shared_ptr<void const> hold = nullptr);
|
||||
|
||||
OpenView (open_ledger_t, std::shared_ptr<
|
||||
ReadView const> const& base)
|
||||
: OpenView (open_ledger, &*base, base)
|
||||
OpenView (open_ledger_t, Rules const& rules,
|
||||
std::shared_ptr<ReadView const> const& base)
|
||||
: OpenView (open_ledger, &*base, rules, base)
|
||||
{
|
||||
}
|
||||
/** @} */
|
||||
@@ -128,6 +134,8 @@ public:
|
||||
|
||||
The LedgerInfo is copied from the base.
|
||||
|
||||
The rules are inherited from the base.
|
||||
|
||||
The tx list starts empty and will contain
|
||||
all newly inserted tx.
|
||||
*/
|
||||
@@ -154,6 +162,9 @@ public:
|
||||
Fees const&
|
||||
fees() const override;
|
||||
|
||||
Rules const&
|
||||
rules() const override;
|
||||
|
||||
bool
|
||||
exists (Keylet const& k) const override;
|
||||
|
||||
|
||||
@@ -107,8 +107,7 @@ public:
|
||||
PaymentSandbox (PaymentSandbox&&) = default;
|
||||
#endif
|
||||
|
||||
PaymentSandbox (ReadView const* base,
|
||||
ApplyFlags flags)
|
||||
PaymentSandbox (ReadView const* base, ApplyFlags flags)
|
||||
: ApplyViewBase (base, flags)
|
||||
{
|
||||
}
|
||||
@@ -134,14 +133,14 @@ public:
|
||||
/** @{ */
|
||||
explicit
|
||||
PaymentSandbox (PaymentSandbox const* base)
|
||||
: ApplyViewBase (base, base->flags())
|
||||
: ApplyViewBase(base, base->flags())
|
||||
, ps_ (base)
|
||||
{
|
||||
}
|
||||
|
||||
explicit
|
||||
PaymentSandbox (PaymentSandbox* base)
|
||||
: ApplyViewBase (base, base->flags())
|
||||
: ApplyViewBase(base, base->flags())
|
||||
, ps_ (base)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -104,17 +104,59 @@ struct LedgerInfo
|
||||
std::uint32_t closeTime = 0;
|
||||
};
|
||||
|
||||
// ledger close flags
|
||||
static
|
||||
std::uint32_t const sLCF_NoConsensusTime = 1;
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline
|
||||
bool getCloseAgree (LedgerInfo const& info)
|
||||
class DigestAwareReadView;
|
||||
|
||||
/** Rules controlling protocol behavior. */
|
||||
class Rules
|
||||
{
|
||||
return (info.closeFlags & sLCF_NoConsensusTime) == 0;
|
||||
}
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
void addRaw (LedgerInfo const&, Serializer&);
|
||||
std::shared_ptr<Impl const> impl_;
|
||||
|
||||
public:
|
||||
Rules (Rules const&) = default;
|
||||
Rules& operator= (Rules const&) = default;
|
||||
|
||||
/** Construct an empty rule set.
|
||||
|
||||
These are the rules reflected by
|
||||
the genesis ledger.
|
||||
*/
|
||||
Rules() = default;
|
||||
|
||||
/** Construct rules from a ledger.
|
||||
|
||||
The ledger contents are analyzed for rules
|
||||
and amendments and extracted to the object.
|
||||
*/
|
||||
explicit
|
||||
Rules (DigestAwareReadView const& ledger);
|
||||
|
||||
/** Returns `true` if a feature is enabled. */
|
||||
bool
|
||||
enabled (uint256 const& feature) const;
|
||||
|
||||
/** Returns `true` if these rules don't match the ledger. */
|
||||
bool
|
||||
changed (DigestAwareReadView const& ledger) const;
|
||||
|
||||
/** Returns `true` if two rule sets are identical.
|
||||
|
||||
@note This is for diagnostics. To determine if new
|
||||
rules should be constructed, call changed() first instead.
|
||||
*/
|
||||
bool
|
||||
operator== (Rules const&) const;
|
||||
|
||||
bool
|
||||
operator!= (Rules const& other) const
|
||||
{
|
||||
return ! (*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -214,6 +256,11 @@ public:
|
||||
Fees const&
|
||||
fees() const = 0;
|
||||
|
||||
/** Returns the tx processing rules. */
|
||||
virtual
|
||||
Rules const&
|
||||
rules() const = 0;
|
||||
|
||||
/** Determine if a state item exists.
|
||||
|
||||
@note This can be more efficient than calling read.
|
||||
@@ -344,6 +391,20 @@ public:
|
||||
digest (key_type const& key) const = 0;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// ledger close flags
|
||||
static
|
||||
std::uint32_t const sLCF_NoConsensusTime = 1;
|
||||
|
||||
inline
|
||||
bool getCloseAgree (LedgerInfo const& info)
|
||||
{
|
||||
return (info.closeFlags & sLCF_NoConsensusTime) == 0;
|
||||
}
|
||||
|
||||
void addRaw (LedgerInfo const&, Serializer&);
|
||||
|
||||
} // ripple
|
||||
|
||||
#include <ripple/ledger/detail/ReadViewFwdRange.ipp>
|
||||
|
||||
@@ -49,14 +49,13 @@ public:
|
||||
Sandbox (Sandbox&&) = default;
|
||||
#endif
|
||||
|
||||
Sandbox (ReadView const* base,
|
||||
ApplyFlags flags)
|
||||
Sandbox (ReadView const* base, ApplyFlags flags)
|
||||
: ApplyViewBase (base, flags)
|
||||
{
|
||||
}
|
||||
|
||||
Sandbox (ApplyView const* base)
|
||||
: Sandbox (base, base->flags())
|
||||
: Sandbox(base, base->flags())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ public:
|
||||
ApplyViewBase (ApplyViewBase&&) = default;
|
||||
#endif
|
||||
|
||||
ApplyViewBase (ReadView const* base,
|
||||
ApplyFlags flags);
|
||||
ApplyViewBase(
|
||||
ReadView const* base, ApplyFlags flags);
|
||||
|
||||
// ReadView
|
||||
|
||||
@@ -63,6 +63,9 @@ public:
|
||||
Fees const&
|
||||
fees() const override;
|
||||
|
||||
Rules const&
|
||||
rules() const override;
|
||||
|
||||
bool
|
||||
exists (Keylet const& k) const override;
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@ namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
ApplyViewBase::ApplyViewBase(
|
||||
ReadView const* base,
|
||||
ApplyFlags flags)
|
||||
ReadView const* base, ApplyFlags flags)
|
||||
: flags_ (flags)
|
||||
, base_ (base)
|
||||
{
|
||||
@@ -46,6 +45,12 @@ ApplyViewBase::fees() const
|
||||
return base_->fees();
|
||||
}
|
||||
|
||||
Rules const&
|
||||
ApplyViewBase::rules() const
|
||||
{
|
||||
return base_->rules();
|
||||
}
|
||||
|
||||
bool
|
||||
ApplyViewBase::exists (Keylet const& k) const
|
||||
{
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/ledger/ApplyViewImpl.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
ApplyViewImpl::ApplyViewImpl(
|
||||
ReadView const* base,
|
||||
ApplyFlags flags)
|
||||
ReadView const* base, ApplyFlags flags)
|
||||
: ApplyViewBase (base, flags)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -89,9 +89,10 @@ public:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
OpenView::OpenView (open_ledger_t,
|
||||
ReadView const* base,
|
||||
ReadView const* base, Rules const& rules,
|
||||
std::shared_ptr<void const> hold)
|
||||
: info_ (base->info())
|
||||
: rules_ (rules)
|
||||
, info_ (base->info())
|
||||
, base_ (base)
|
||||
, hold_ (std::move(hold))
|
||||
{
|
||||
@@ -102,7 +103,8 @@ OpenView::OpenView (open_ledger_t,
|
||||
|
||||
OpenView::OpenView (ReadView const* base,
|
||||
std::shared_ptr<void const> hold)
|
||||
: info_ (base->info())
|
||||
: rules_ (base->rules())
|
||||
, info_ (base->info())
|
||||
, base_ (base)
|
||||
, hold_ (std::move(hold))
|
||||
{
|
||||
@@ -138,6 +140,12 @@ OpenView::fees() const
|
||||
return base_->fees();
|
||||
}
|
||||
|
||||
Rules const&
|
||||
OpenView::rules() const
|
||||
{
|
||||
return rules_;
|
||||
}
|
||||
|
||||
bool
|
||||
OpenView::exists (Keylet const& k) const
|
||||
{
|
||||
|
||||
@@ -19,9 +19,105 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class Rules::Impl
|
||||
{
|
||||
private:
|
||||
std::unordered_set<uint256,
|
||||
hardened_hash<>> set_;
|
||||
boost::optional<uint256> digest_;
|
||||
|
||||
public:
|
||||
Impl (DigestAwareReadView const& ledger)
|
||||
{
|
||||
auto const k = keylet::amendments();
|
||||
digest_ = ledger.digest(k.key);
|
||||
if (! digest_)
|
||||
return;
|
||||
auto const sle = ledger.read(k);
|
||||
if (! sle)
|
||||
{
|
||||
// LogicError() ?
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const& item :
|
||||
sle->getFieldV256(sfAmendments))
|
||||
set_.insert(item);
|
||||
}
|
||||
|
||||
bool
|
||||
enabled (uint256 const& feature) const
|
||||
{
|
||||
return set_.count(feature) > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
changed (DigestAwareReadView const& ledger) const
|
||||
{
|
||||
auto const digest =
|
||||
ledger.digest(keylet::amendments().key);
|
||||
if (! digest && ! digest_)
|
||||
return false;
|
||||
if (! digest || ! digest_)
|
||||
return true;
|
||||
return *digest != *digest_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator== (Impl const& other) const
|
||||
{
|
||||
if (! digest_ && ! other.digest_)
|
||||
return true;
|
||||
if (! digest_ || ! other.digest_)
|
||||
return false;
|
||||
return *digest_ == *other.digest_;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Rules::Rules (DigestAwareReadView const& ledger)
|
||||
: impl_(std::make_shared<Impl>(ledger))
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
Rules::enabled (uint256 const& feature) const
|
||||
{
|
||||
if (! impl_)
|
||||
return false;
|
||||
return impl_->enabled(feature);
|
||||
}
|
||||
|
||||
bool
|
||||
Rules::changed (DigestAwareReadView const& ledger) const
|
||||
{
|
||||
if (! impl_)
|
||||
return static_cast<bool>(
|
||||
ledger.digest(keylet::amendments().key));
|
||||
return impl_->changed(ledger);
|
||||
}
|
||||
|
||||
bool
|
||||
Rules::operator== (Rules const& other) const
|
||||
{
|
||||
#if 0
|
||||
if (! impl_ && ! other.impl_)
|
||||
return true;
|
||||
if (! impl_ || ! other.impl_)
|
||||
return false;
|
||||
return *impl_ == *other.impl_;
|
||||
#else
|
||||
return impl_.get() == other.impl_.get();
|
||||
#endif
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ReadView::sles_type::sles_type(
|
||||
ReadView const& view)
|
||||
: ReadViewFwdRange(view)
|
||||
|
||||
@@ -117,7 +117,7 @@ Env::close(NetClock::time_point const& closeTime)
|
||||
closeTime.time_since_epoch ()).count (),
|
||||
ledgerPossibleTimeResolutions[0], false);
|
||||
OrderedTxs locals({});
|
||||
openLedger.accept(next, locals,
|
||||
openLedger.accept(next->rules(), next, locals,
|
||||
false, retries, applyFlags(), *router);
|
||||
closed_ = next;
|
||||
cachedSLEs_.expire();
|
||||
|
||||
Reference in New Issue
Block a user