mirror of
https://github.com/dashpay/dash.git
synced 2024-12-27 04:52:59 +01:00
refactor: Add spork helpers in corresponding modules (#3859)
* Introduce AreChainLocksEnabled() Also rename isSporkActive to isEnabled * Introduce AreSuperblocksEnabled() * Introduce RejectConflictingBlocks() * Introduce IsQuorumDKGEnabled()
This commit is contained in:
parent
3e4a2be018
commit
8e9c159e81
@ -1303,3 +1303,8 @@ void CGovernanceManager::RemoveInvalidVotes()
|
|||||||
// store current MN list for the next run so that we can determine which keys changed
|
// store current MN list for the next run so that we can determine which keys changed
|
||||||
lastMNListForVotingKeys = curMNList;
|
lastMNListForVotingKeys = curMNList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AreSuperblocksEnabled()
|
||||||
|
{
|
||||||
|
return sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED);
|
||||||
|
}
|
||||||
|
@ -432,4 +432,6 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool AreSuperblocksEnabled();
|
||||||
|
|
||||||
#endif // BITCOIN_GOVERNANCE_GOVERNANCE_H
|
#endif // BITCOIN_GOVERNANCE_GOVERNANCE_H
|
||||||
|
@ -90,7 +90,7 @@ CChainLockSig CChainLocksHandler::GetBestChainLock()
|
|||||||
|
|
||||||
void CChainLocksHandler::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
void CChainLocksHandler::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) {
|
if (!AreChainLocksEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,8 +230,8 @@ void CChainLocksHandler::CheckActiveState()
|
|||||||
|
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
bool oldIsEnforced = isEnforced;
|
bool oldIsEnforced = isEnforced;
|
||||||
isSporkActive = sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);
|
isEnabled = AreChainLocksEnabled();
|
||||||
isEnforced = (fDIP0008Active && isSporkActive);
|
isEnforced = (fDIP0008Active && isEnabled);
|
||||||
|
|
||||||
if (!oldIsEnforced && isEnforced) {
|
if (!oldIsEnforced && isEnforced) {
|
||||||
// ChainLocks got activated just recently, but it's possible that it was already running before, leaving
|
// ChainLocks got activated just recently, but it's possible that it was already running before, leaving
|
||||||
@ -273,7 +273,7 @@ void CChainLocksHandler::TrySignChainTip()
|
|||||||
{
|
{
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
|
|
||||||
if (!isSporkActive) {
|
if (!isEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +300,7 @@ void CChainLocksHandler::TrySignChainTip()
|
|||||||
// considered safe when it is islocked or at least known since 10 minutes (from mempool or block). These checks are
|
// considered safe when it is islocked or at least known since 10 minutes (from mempool or block). These checks are
|
||||||
// performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the
|
// performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the
|
||||||
// way down, we consider all TXs to be safe.
|
// way down, we consider all TXs to be safe.
|
||||||
if (IsInstantSendEnabled() && sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
|
if (IsInstantSendEnabled() && RejectConflictingBlocks()) {
|
||||||
auto pindexWalk = pindex;
|
auto pindexWalk = pindex;
|
||||||
while (pindexWalk) {
|
while (pindexWalk) {
|
||||||
if (pindex->nHeight - pindexWalk->nHeight > 5) {
|
if (pindex->nHeight - pindexWalk->nHeight > 5) {
|
||||||
@ -459,7 +459,7 @@ CChainLocksHandler::BlockTxs::mapped_type CChainLocksHandler::GetBlockTxs(const
|
|||||||
|
|
||||||
bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid)
|
bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid)
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
|
if (!RejectConflictingBlocks()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!IsInstantSendEnabled()) {
|
if (!IsInstantSendEnabled()) {
|
||||||
@ -469,7 +469,7 @@ bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid)
|
|||||||
int64_t txAge = 0;
|
int64_t txAge = 0;
|
||||||
{
|
{
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
if (!isSporkActive || !isEnforced) {
|
if (!isEnabled || !isEnforced) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
auto it = txFirstSeenTime.find(txid);
|
auto it = txFirstSeenTime.find(txid);
|
||||||
@ -568,7 +568,7 @@ void CChainLocksHandler::HandleNewRecoveredSig(const llmq::CRecoveredSig& recove
|
|||||||
{
|
{
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
|
|
||||||
if (!isSporkActive) {
|
if (!isEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -727,4 +727,9 @@ void CChainLocksHandler::Cleanup()
|
|||||||
lastCleanupTime = GetTimeMillis();
|
lastCleanupTime = GetTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AreChainLocksEnabled()
|
||||||
|
{
|
||||||
|
return sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
@ -57,7 +57,7 @@ private:
|
|||||||
boost::thread* scheduler_thread;
|
boost::thread* scheduler_thread;
|
||||||
CCriticalSection cs;
|
CCriticalSection cs;
|
||||||
bool tryLockChainTipScheduled{false};
|
bool tryLockChainTipScheduled{false};
|
||||||
bool isSporkActive{false};
|
bool isEnabled{false};
|
||||||
bool isEnforced{false};
|
bool isEnforced{false};
|
||||||
|
|
||||||
uint256 bestChainLockHash;
|
uint256 bestChainLockHash;
|
||||||
@ -122,6 +122,7 @@ private:
|
|||||||
|
|
||||||
extern CChainLocksHandler* chainLocksHandler;
|
extern CChainLocksHandler* chainLocksHandler;
|
||||||
|
|
||||||
|
bool AreChainLocksEnabled();
|
||||||
|
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ void CDKGSessionManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fIni
|
|||||||
return;
|
return;
|
||||||
if (!deterministicMNManager->IsDIP3Enforced(pindexNew->nHeight))
|
if (!deterministicMNManager->IsDIP3Enforced(pindexNew->nHeight))
|
||||||
return;
|
return;
|
||||||
if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED))
|
if (!IsQuorumDKGEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (auto& qt : dkgSessionHandlers) {
|
for (auto& qt : dkgSessionHandlers) {
|
||||||
@ -66,7 +66,7 @@ void CDKGSessionManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fIni
|
|||||||
|
|
||||||
void CDKGSessionManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
void CDKGSessionManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman)
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED))
|
if (!IsQuorumDKGEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (strCommand != NetMsgType::QCONTRIB
|
if (strCommand != NetMsgType::QCONTRIB
|
||||||
@ -101,7 +101,7 @@ void CDKGSessionManager::ProcessMessage(CNode* pfrom, const std::string& strComm
|
|||||||
|
|
||||||
bool CDKGSessionManager::AlreadyHave(const CInv& inv) const
|
bool CDKGSessionManager::AlreadyHave(const CInv& inv) const
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED))
|
if (!IsQuorumDKGEnabled())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (const auto& p : dkgSessionHandlers) {
|
for (const auto& p : dkgSessionHandlers) {
|
||||||
@ -118,7 +118,7 @@ bool CDKGSessionManager::AlreadyHave(const CInv& inv) const
|
|||||||
|
|
||||||
bool CDKGSessionManager::GetContribution(const uint256& hash, CDKGContribution& ret) const
|
bool CDKGSessionManager::GetContribution(const uint256& hash, CDKGContribution& ret) const
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED))
|
if (!IsQuorumDKGEnabled())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (const auto& p : dkgSessionHandlers) {
|
for (const auto& p : dkgSessionHandlers) {
|
||||||
@ -138,7 +138,7 @@ bool CDKGSessionManager::GetContribution(const uint256& hash, CDKGContribution&
|
|||||||
|
|
||||||
bool CDKGSessionManager::GetComplaint(const uint256& hash, CDKGComplaint& ret) const
|
bool CDKGSessionManager::GetComplaint(const uint256& hash, CDKGComplaint& ret) const
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED))
|
if (!IsQuorumDKGEnabled())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (const auto& p : dkgSessionHandlers) {
|
for (const auto& p : dkgSessionHandlers) {
|
||||||
@ -158,7 +158,7 @@ bool CDKGSessionManager::GetComplaint(const uint256& hash, CDKGComplaint& ret) c
|
|||||||
|
|
||||||
bool CDKGSessionManager::GetJustification(const uint256& hash, CDKGJustification& ret) const
|
bool CDKGSessionManager::GetJustification(const uint256& hash, CDKGJustification& ret) const
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED))
|
if (!IsQuorumDKGEnabled())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (const auto& p : dkgSessionHandlers) {
|
for (const auto& p : dkgSessionHandlers) {
|
||||||
@ -178,7 +178,7 @@ bool CDKGSessionManager::GetJustification(const uint256& hash, CDKGJustification
|
|||||||
|
|
||||||
bool CDKGSessionManager::GetPrematureCommitment(const uint256& hash, CDKGPrematureCommitment& ret) const
|
bool CDKGSessionManager::GetPrematureCommitment(const uint256& hash, CDKGPrematureCommitment& ret) const
|
||||||
{
|
{
|
||||||
if (!sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED))
|
if (!IsQuorumDKGEnabled())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (const auto& p : dkgSessionHandlers) {
|
for (const auto& p : dkgSessionHandlers) {
|
||||||
@ -272,4 +272,9 @@ void CDKGSessionManager::CleanupCache()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsQuorumDKGEnabled()
|
||||||
|
{
|
||||||
|
return sporkManager.IsSporkActive(SPORK_17_QUORUM_DKG_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
@ -71,6 +71,8 @@ private:
|
|||||||
void CleanupCache();
|
void CleanupCache();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool IsQuorumDKGEnabled();
|
||||||
|
|
||||||
extern CDKGSessionManager* quorumDKGSessionManager;
|
extern CDKGSessionManager* quorumDKGSessionManager;
|
||||||
|
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
@ -1154,7 +1154,7 @@ void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew)
|
|||||||
// TODO remove this after DIP8 has activated
|
// TODO remove this after DIP8 has activated
|
||||||
bool fDIP0008Active = VersionBitsState(pindexNew->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0008, versionbitscache) == ThresholdState::ACTIVE;
|
bool fDIP0008Active = VersionBitsState(pindexNew->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0008, versionbitscache) == ThresholdState::ACTIVE;
|
||||||
|
|
||||||
if (sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED) && fDIP0008Active) {
|
if (AreChainLocksEnabled() && fDIP0008Active) {
|
||||||
// Nothing to do here. We should keep all islocks and let chainlocks handle them.
|
// Nothing to do here. We should keep all islocks and let chainlocks handle them.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1534,4 +1534,9 @@ bool IsInstantSendEnabled()
|
|||||||
return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED);
|
return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RejectConflictingBlocks()
|
||||||
|
{
|
||||||
|
return sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
@ -173,6 +173,7 @@ public:
|
|||||||
extern CInstantSendManager* quorumInstantSendManager;
|
extern CInstantSendManager* quorumInstantSendManager;
|
||||||
|
|
||||||
bool IsInstantSendEnabled();
|
bool IsInstantSendEnabled();
|
||||||
|
bool RejectConflictingBlocks();
|
||||||
|
|
||||||
} // namespace llmq
|
} // namespace llmq
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#include <masternode/masternode-sync.h>
|
#include <masternode/masternode-sync.h>
|
||||||
#include <netfulfilledman.h>
|
#include <netfulfilledman.h>
|
||||||
#include <netmessagemaker.h>
|
#include <netmessagemaker.h>
|
||||||
#include <spork.h>
|
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -119,7 +118,7 @@ bool IsBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockRewar
|
|||||||
|
|
||||||
// we are synced and possibly on a superblock now
|
// we are synced and possibly on a superblock now
|
||||||
|
|
||||||
if (!sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) {
|
if (!AreSuperblocksEnabled()) {
|
||||||
// should NOT allow superblocks at all, when superblocks are disabled
|
// should NOT allow superblocks at all, when superblocks are disabled
|
||||||
// revert to block reward limits in this case
|
// revert to block reward limits in this case
|
||||||
LogPrint(BCLog::GOBJECT, "%s -- Superblocks are disabled, no superblocks allowed\n", __func__);
|
LogPrint(BCLog::GOBJECT, "%s -- Superblocks are disabled, no superblocks allowed\n", __func__);
|
||||||
@ -177,7 +176,7 @@ bool IsBlockPayeeValid(const CTransaction& txNew, int nBlockHeight, CAmount bloc
|
|||||||
// superblocks started
|
// superblocks started
|
||||||
// SEE IF THIS IS A VALID SUPERBLOCK
|
// SEE IF THIS IS A VALID SUPERBLOCK
|
||||||
|
|
||||||
if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) {
|
if(AreSuperblocksEnabled()) {
|
||||||
if(CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
|
if(CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
|
||||||
if(CSuperblockManager::IsValid(txNew, nBlockHeight, blockReward)) {
|
if(CSuperblockManager::IsValid(txNew, nBlockHeight, blockReward)) {
|
||||||
LogPrint(BCLog::GOBJECT, "%s -- Valid superblock at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
|
LogPrint(BCLog::GOBJECT, "%s -- Valid superblock at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */
|
||||||
@ -209,10 +208,9 @@ void FillBlockPayments(CMutableTransaction& txNew, int nBlockHeight, CAmount blo
|
|||||||
{
|
{
|
||||||
// only create superblocks if spork is enabled AND if superblock is actually triggered
|
// only create superblocks if spork is enabled AND if superblock is actually triggered
|
||||||
// (height should be validated inside)
|
// (height should be validated inside)
|
||||||
if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED) &&
|
if(AreSuperblocksEnabled() && CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
|
||||||
CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
|
LogPrint(BCLog::GOBJECT, "%s -- triggered superblock creation at height %d\n", __func__, nBlockHeight);
|
||||||
LogPrint(BCLog::GOBJECT, "%s -- triggered superblock creation at height %d\n", __func__, nBlockHeight);
|
CSuperblockManager::GetSuperblockPayments(nBlockHeight, voutSuperblockPaymentsRet);
|
||||||
CSuperblockManager::GetSuperblockPayments(nBlockHeight, voutSuperblockPaymentsRet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CMasternodePayments::GetMasternodeTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) {
|
if (!CMasternodePayments::GetMasternodeTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) {
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#include <rpc/blockchain.h>
|
#include <rpc/blockchain.h>
|
||||||
#include <rpc/mining.h>
|
#include <rpc/mining.h>
|
||||||
#include <rpc/server.h>
|
#include <rpc/server.h>
|
||||||
#include <spork.h>
|
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
#include <util.h>
|
#include <util.h>
|
||||||
#include <utilstrencodings.h>
|
#include <utilstrencodings.h>
|
||||||
@ -472,7 +471,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request)
|
|||||||
mnpayments.GetBlockTxOuts(chainActive.Height() + 1, 0, voutMasternodePayments);
|
mnpayments.GetBlockTxOuts(chainActive.Height() + 1, 0, voutMasternodePayments);
|
||||||
|
|
||||||
// next bock is a superblock and we need governance info to correctly construct it
|
// next bock is a superblock and we need governance info to correctly construct it
|
||||||
if (sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)
|
if (AreSuperblocksEnabled()
|
||||||
&& !masternodeSync.IsSynced()
|
&& !masternodeSync.IsSynced()
|
||||||
&& CSuperblock::IsValidBlockHeight(chainActive.Height() + 1))
|
&& CSuperblock::IsValidBlockHeight(chainActive.Height() + 1))
|
||||||
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Dash Core is syncing with network...");
|
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Dash Core is syncing with network...");
|
||||||
@ -704,7 +703,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request)
|
|||||||
}
|
}
|
||||||
result.pushKV("superblock", superblockObjArray);
|
result.pushKV("superblock", superblockObjArray);
|
||||||
result.pushKV("superblocks_started", pindexPrev->nHeight + 1 > consensusParams.nSuperblockStartBlock);
|
result.pushKV("superblocks_started", pindexPrev->nHeight + 1 > consensusParams.nSuperblockStartBlock);
|
||||||
result.pushKV("superblocks_enabled", sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED));
|
result.pushKV("superblocks_enabled", AreSuperblocksEnabled());
|
||||||
|
|
||||||
result.pushKV("coinbase_payload", HexStr(pblock->vtx[0]->vExtraPayload));
|
result.pushKV("coinbase_payload", HexStr(pblock->vtx[0]->vExtraPayload));
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@
|
|||||||
#include <ui_interface.h>
|
#include <ui_interface.h>
|
||||||
#include <undo.h>
|
#include <undo.h>
|
||||||
#include <util.h>
|
#include <util.h>
|
||||||
#include <spork.h>
|
|
||||||
#include <utilmoneystr.h>
|
#include <utilmoneystr.h>
|
||||||
#include <utilstrencodings.h>
|
#include <utilstrencodings.h>
|
||||||
#include <validationinterface.h>
|
#include <validationinterface.h>
|
||||||
@ -2313,7 +2312,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
|
|||||||
|
|
||||||
// DASH : CHECK TRANSACTIONS FOR INSTANTSEND
|
// DASH : CHECK TRANSACTIONS FOR INSTANTSEND
|
||||||
|
|
||||||
if (sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
|
if (llmq::RejectConflictingBlocks()) {
|
||||||
// Require other nodes to comply, send them some data in case they are missing it.
|
// Require other nodes to comply, send them some data in case they are missing it.
|
||||||
for (const auto& tx : block.vtx) {
|
for (const auto& tx : block.vtx) {
|
||||||
// skip txes that have no inputs
|
// skip txes that have no inputs
|
||||||
|
Loading…
Reference in New Issue
Block a user