@ -88,12 +88,22 @@ CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
CTxMemPool mempool ( : : minRelayTxFee ) ;
CTxMemPool mempool ( : : minRelayTxFee ) ;
FeeFilterRounder filterRounder ( : : minRelayTxFee ) ;
FeeFilterRounder filterRounder ( : : minRelayTxFee ) ;
struct IteratorComparator
{
template < typename I >
bool operator ( ) ( const I & a , const I & b )
{
return & ( * a ) < & ( * b ) ;
}
} ;
struct COrphanTx {
struct COrphanTx {
CTransaction tx ;
CTransaction tx ;
NodeId fromPeer ;
NodeId fromPeer ;
int64_t nTimeExpire ;
} ;
} ;
map < uint256 , COrphanTx > mapOrphanTransactions GUARDED_BY ( cs_main ) ;
map < uint256 , COrphanTx > mapOrphanTransactions GUARDED_BY ( cs_main ) ;
map < uint256 , set < uint256 > > mapOrphanTransactionsByPrev GUARDED_BY ( cs_main ) ;
map < CO utPo int, set < map < uint256 , COrphanTx > : : iterator , IteratorComparator > > mapOrphanTransactionsByPrev GUARDED_BY ( cs_main ) ;
void EraseOrphansFor ( NodeId peer ) EXCLUSIVE_LOCKS_REQUIRED ( cs_main ) ;
void EraseOrphansFor ( NodeId peer ) EXCLUSIVE_LOCKS_REQUIRED ( cs_main ) ;
/**
/**
@ -623,40 +633,42 @@ bool AddOrphanTx(const CTransaction& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(c
// large transaction with a missing parent then we assume
// large transaction with a missing parent then we assume
// it will rebroadcast it later, after the parent transaction(s)
// it will rebroadcast it later, after the parent transaction(s)
// have been mined or received.
// have been mined or received.
// 10,000 orphans, each of which is at most 5,000 bytes big is
// 100 orphans, each of which is at most 99,999 bytes big is
// at most 500 megabytes of orphans :
// at most 10 megabytes of orphans and somewhat more byprev index (in the worst case) :
unsigned int sz = tx . GetSerializeSize ( SER_NETWORK , CTransaction : : CURRENT_VERSION ) ;
unsigned int sz = tx . GetSerializeSize ( SER_NETWORK , CTransaction : : CURRENT_VERSION ) ;
if ( sz > 5000 )
if ( sz > = MAX_STANDARD_TX_SIZE )
{
{
LogPrint ( " mempool " , " ignoring large orphan tx (size: %u, hash: %s) \n " , sz , hash . ToString ( ) ) ;
LogPrint ( " mempool " , " ignoring large orphan tx (size: %u, hash: %s) \n " , sz , hash . ToString ( ) ) ;
return false ;
return false ;
}
}
mapOrphanTransactions [ hash ] . tx = tx ;
auto ret = mapOrphanTransactions . emplace ( hash , COrphanTx { tx , peer , GetTime ( ) + ORPHAN_TX_EXPIRE_TIME } ) ;
mapOrphanTransactions [ hash ] . fromPeer = peer ;
assert ( ret . second ) ;
BOOST_FOREACH ( const CTxIn & txin , tx . vin )
BOOST_FOREACH ( const CTxIn & txin , tx . vin ) {
mapOrphanTransactionsByPrev [ txin . prevout . hash ] . insert ( hash ) ;
mapOrphanTransactionsByPrev [ txin . prevout ] . insert ( ret . first ) ;
}
LogPrint ( " mempool " , " stored orphan tx %s (mapsz %u prev sz %u) \n " , hash . ToString ( ) ,
LogPrint ( " mempool " , " stored orphan tx %s (mapsz %u out sz %u) \n " , hash . ToString ( ) ,
mapOrphanTransactions . size ( ) , mapOrphanTransactionsByPrev . size ( ) ) ;
mapOrphanTransactions . size ( ) , mapOrphanTransactionsByPrev . size ( ) ) ;
return true ;
return true ;
}
}
void static EraseOrphanTx ( uint256 hash ) EXCLUSIVE_LOCKS_REQUIRED ( cs_main )
int static EraseOrphanTx ( uint256 hash ) EXCLUSIVE_LOCKS_REQUIRED ( cs_main )
{
{
map < uint256 , COrphanTx > : : iterator it = mapOrphanTransactions . find ( hash ) ;
map < uint256 , COrphanTx > : : iterator it = mapOrphanTransactions . find ( hash ) ;
if ( it = = mapOrphanTransactions . end ( ) )
if ( it = = mapOrphanTransactions . end ( ) )
return ;
return 0 ;
BOOST_FOREACH ( const CTxIn & txin , it - > second . tx . vin )
BOOST_FOREACH ( const CTxIn & txin , it - > second . tx . vin )
{
{
map < uint256 , set < uint256 > > : : iterator itPrev = mapOrphanTransactionsByPrev . find ( txin . prevout . hash ) ;
auto itPrev = mapOrphanTransactionsByPrev . find ( txin . prevout ) ;
if ( itPrev = = mapOrphanTransactionsByPrev . end ( ) )
if ( itPrev = = mapOrphanTransactionsByPrev . end ( ) )
continue ;
continue ;
itPrev - > second . erase ( hash ) ;
itPrev - > second . erase ( it ) ;
if ( itPrev - > second . empty ( ) )
if ( itPrev - > second . empty ( ) )
mapOrphanTransactionsByPrev . erase ( itPrev ) ;
mapOrphanTransactionsByPrev . erase ( itPrev ) ;
}
}
mapOrphanTransactions . erase ( it ) ;
mapOrphanTransactions . erase ( it ) ;
return 1 ;
}
}
void EraseOrphansFor ( NodeId peer )
void EraseOrphansFor ( NodeId peer )
@ -668,8 +680,7 @@ void EraseOrphansFor(NodeId peer)
map < uint256 , COrphanTx > : : iterator maybeErase = iter + + ; // increment to avoid iterator becoming invalid
map < uint256 , COrphanTx > : : iterator maybeErase = iter + + ; // increment to avoid iterator becoming invalid
if ( maybeErase - > second . fromPeer = = peer )
if ( maybeErase - > second . fromPeer = = peer )
{
{
EraseOrphanTx ( maybeErase - > second . tx . GetHash ( ) ) ;
nErased + = EraseOrphanTx ( maybeErase - > second . tx . GetHash ( ) ) ;
+ + nErased ;
}
}
}
}
if ( nErased > 0 ) LogPrint ( " mempool " , " Erased %d orphan tx from peer %d \n " , nErased , peer ) ;
if ( nErased > 0 ) LogPrint ( " mempool " , " Erased %d orphan tx from peer %d \n " , nErased , peer ) ;
@ -679,6 +690,26 @@ void EraseOrphansFor(NodeId peer)
unsigned int LimitOrphanTxSize ( unsigned int nMaxOrphans ) EXCLUSIVE_LOCKS_REQUIRED ( cs_main )
unsigned int LimitOrphanTxSize ( unsigned int nMaxOrphans ) EXCLUSIVE_LOCKS_REQUIRED ( cs_main )
{
{
unsigned int nEvicted = 0 ;
unsigned int nEvicted = 0 ;
static int64_t nNextSweep ;
int64_t nNow = GetTime ( ) ;
if ( nNextSweep < = nNow ) {
// Sweep out expired orphan pool entries:
int nErased = 0 ;
int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL ;
map < uint256 , COrphanTx > : : iterator iter = mapOrphanTransactions . begin ( ) ;
while ( iter ! = mapOrphanTransactions . end ( ) )
{
map < uint256 , COrphanTx > : : iterator maybeErase = iter + + ;
if ( maybeErase - > second . nTimeExpire < = nNow ) {
nErased + = EraseOrphanTx ( maybeErase - > second . tx . GetHash ( ) ) ;
} else {
nMinExpTime = std : : min ( maybeErase - > second . nTimeExpire , nMinExpTime ) ;
}
}
// Sweep again 5 minutes after the next entry that expires in order to batch the linear scan.
nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL ;
if ( nErased > 0 ) LogPrint ( " mempool " , " Erased %d orphan tx due to expiration \n " , nErased ) ;
}
while ( mapOrphanTransactions . size ( ) > nMaxOrphans )
while ( mapOrphanTransactions . size ( ) > nMaxOrphans )
{
{
// Evict a random orphan:
// Evict a random orphan:
@ -2335,6 +2366,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
CCheckQueueControl < CScriptCheck > control ( fScriptChecks & & nScriptCheckThreads ? & scriptcheckqueue : NULL ) ;
CCheckQueueControl < CScriptCheck > control ( fScriptChecks & & nScriptCheckThreads ? & scriptcheckqueue : NULL ) ;
std : : vector < uint256 > vOrphanErase ;
std : : vector < int > prevheights ;
std : : vector < int > prevheights ;
CAmount nFees = 0 ;
CAmount nFees = 0 ;
int nInputs = 0 ;
int nInputs = 0 ;
@ -2367,6 +2399,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
prevheights [ j ] = view . AccessCoins ( tx . vin [ j ] . prevout . hash ) - > nHeight ;
prevheights [ j ] = view . AccessCoins ( tx . vin [ j ] . prevout . hash ) - > nHeight ;
}
}
// Which orphan pool entries must we evict?
for ( size_t j = 0 ; j < tx . vin . size ( ) ; j + + ) {
auto itByPrev = mapOrphanTransactionsByPrev . find ( tx . vin [ j ] . prevout ) ;
if ( itByPrev = = mapOrphanTransactionsByPrev . end ( ) ) continue ;
for ( auto mi = itByPrev - > second . begin ( ) ; mi ! = itByPrev - > second . end ( ) ; + + mi ) {
const CTransaction & orphanTx = ( * mi ) - > second . tx ;
const uint256 & orphanHash = orphanTx . GetHash ( ) ;
vOrphanErase . push_back ( orphanHash ) ;
}
}
if ( ! SequenceLocks ( tx , nLockTimeFlags , & prevheights , * pindex ) ) {
if ( ! SequenceLocks ( tx , nLockTimeFlags , & prevheights , * pindex ) ) {
return state . DoS ( 100 , error ( " %s: contains a non-BIP68-final transaction " , __func__ ) ,
return state . DoS ( 100 , error ( " %s: contains a non-BIP68-final transaction " , __func__ ) ,
REJECT_INVALID , " bad-txns-nonfinal " ) ;
REJECT_INVALID , " bad-txns-nonfinal " ) ;
@ -2454,6 +2497,15 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
GetMainSignals ( ) . UpdatedTransaction ( hashPrevBestCoinBase ) ;
GetMainSignals ( ) . UpdatedTransaction ( hashPrevBestCoinBase ) ;
hashPrevBestCoinBase = block . vtx [ 0 ] . GetHash ( ) ;
hashPrevBestCoinBase = block . vtx [ 0 ] . GetHash ( ) ;
// Erase orphan transactions include or precluded by this block
if ( vOrphanErase . size ( ) ) {
int nErased = 0 ;
BOOST_FOREACH ( uint256 & orphanHash , vOrphanErase ) {
nErased + = EraseOrphanTx ( orphanHash ) ;
}
LogPrint ( " mempool " , " Erased %d orphan tx included or conflicted by block \n " , nErased ) ;
}
int64_t nTime6 = GetTimeMicros ( ) ; nTimeCallbacks + = nTime6 - nTime5 ;
int64_t nTime6 = GetTimeMicros ( ) ; nTimeCallbacks + = nTime6 - nTime5 ;
LogPrint ( " bench " , " - Callbacks: %.2fms [%.2fs] \n " , 0.001 * ( nTime6 - nTime5 ) , nTimeCallbacks * 0.000001 ) ;
LogPrint ( " bench " , " - Callbacks: %.2fms [%.2fs] \n " , 0.001 * ( nTime6 - nTime5 ) , nTimeCallbacks * 0.000001 ) ;
@ -5041,7 +5093,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
return true ;
return true ;
}
}
vector < uint256 > vWorkQueue ;
deque < CO utPo int> vWorkQueue ;
vector < uint256 > vEraseQueue ;
vector < uint256 > vEraseQueue ;
CTransaction tx ;
CTransaction tx ;
vRecv > > tx ;
vRecv > > tx ;
@ -5060,7 +5112,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if ( ! AlreadyHave ( inv ) & & AcceptToMemoryPool ( mempool , state , tx , true , & fMissingInputs ) ) {
if ( ! AlreadyHave ( inv ) & & AcceptToMemoryPool ( mempool , state , tx , true , & fMissingInputs ) ) {
mempool . check ( pcoinsTip ) ;
mempool . check ( pcoinsTip ) ;
RelayTransaction ( tx ) ;
RelayTransaction ( tx ) ;
vWorkQueue . push_back ( inv . hash ) ;
for ( unsigned int i = 0 ; i < tx . vout . size ( ) ; i + + ) {
vWorkQueue . emplace_back ( inv . hash , i ) ;
}
pfrom - > nLastTXTime = GetTime ( ) ;
pfrom - > nLastTXTime = GetTime ( ) ;
@ -5071,18 +5125,18 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
// Recursively process any orphan transactions that depended on this one
// Recursively process any orphan transactions that depended on this one
set < NodeId > setMisbehaving ;
set < NodeId > setMisbehaving ;
for ( unsigned int i = 0 ; i < vWorkQueue . siz e( ) ; i + + )
while ( ! vWorkQueue . empty ( ) ) {
{
auto itByPrev = mapOrphanTransactionsByPrev . find ( vWorkQueue . front ( ) ) ;
map < uint256 , set < uint256 > > : : iterator itByPrev = mapOrphanTransactionsByPrev . find ( vWorkQueue [ i ] ) ;
vWorkQueue . pop_front ( ) ;
if ( itByPrev = = mapOrphanTransactionsByPrev . end ( ) )
if ( itByPrev = = mapOrphanTransactionsByPrev . end ( ) )
continue ;
continue ;
for ( set < uint256 > : : iterator mi = itByPrev - > second . begin ( ) ;
for ( auto mi = itByPrev - > second . begin ( ) ;
mi ! = itByPrev - > second . end ( ) ;
mi ! = itByPrev - > second . end ( ) ;
+ + mi )
+ + mi )
{
{
const uint256 & orphanHash = * mi ;
const CTransaction & orphanTx = ( * mi ) - > second . tx ;
const CTransaction & orphanTx = mapOrphanTransactions [ orphanHash ] . tx ;
const uint256 & orphanHash = orphanTx . GetHash ( ) ;
NodeId fromPeer = mapOrphanTransactions [ orphanHash ] . fromPeer ;
NodeId fromPeer = ( * mi ) - > second . fromPeer ;
bool fMissingInputs2 = false ;
bool fMissingInputs2 = false ;
// Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan
// Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan
// resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get
// resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get
@ -5095,7 +5149,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if ( AcceptToMemoryPool ( mempool , stateDummy , orphanTx , true , & fMissingInputs2 ) ) {
if ( AcceptToMemoryPool ( mempool , stateDummy , orphanTx , true , & fMissingInputs2 ) ) {
LogPrint ( " mempool " , " accepted orphan tx %s \n " , orphanHash . ToString ( ) ) ;
LogPrint ( " mempool " , " accepted orphan tx %s \n " , orphanHash . ToString ( ) ) ;
RelayTransaction ( orphanTx ) ;
RelayTransaction ( orphanTx ) ;
vWorkQueue . push_back ( orphanHash ) ;
for ( unsigned int i = 0 ; i < orphanTx . vout . size ( ) ; i + + ) {
vWorkQueue . emplace_back ( orphanHash , i ) ;
}
vEraseQueue . push_back ( orphanHash ) ;
vEraseQueue . push_back ( orphanHash ) ;
}
}
else if ( ! fMissingInputs2 )
else if ( ! fMissingInputs2 )
@ -5124,6 +5180,19 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
}
}
else if ( fMissingInputs )
else if ( fMissingInputs )
{
{
bool fRejectedParents = false ; // It may be the case that the orphans parents have all been rejected
BOOST_FOREACH ( const CTxIn & txin , tx . vin ) {
if ( recentRejects - > contains ( txin . prevout . hash ) ) {
fRejectedParents = true ;
break ;
}
}
if ( ! fRejectedParents ) {
BOOST_FOREACH ( const CTxIn & txin , tx . vin ) {
CInv inv ( MSG_TX , txin . prevout . hash ) ;
pfrom - > AddInventoryKnown ( inv ) ;
if ( ! AlreadyHave ( inv ) ) pfrom - > AskFor ( inv ) ;
}
AddOrphanTx ( tx , pfrom - > GetId ( ) ) ;
AddOrphanTx ( tx , pfrom - > GetId ( ) ) ;
// DoS prevention: do not allow mapOrphanTransactions to grow unbounded
// DoS prevention: do not allow mapOrphanTransactions to grow unbounded
@ -5131,6 +5200,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
unsigned int nEvicted = LimitOrphanTxSize ( nMaxOrphanTx ) ;
unsigned int nEvicted = LimitOrphanTxSize ( nMaxOrphanTx ) ;
if ( nEvicted > 0 )
if ( nEvicted > 0 )
LogPrint ( " mempool " , " mapOrphan overflow, removed %u tx \n " , nEvicted ) ;
LogPrint ( " mempool " , " mapOrphan overflow, removed %u tx \n " , nEvicted ) ;
} else {
LogPrint ( " mempool " , " not keeping orphan with rejected parents %s \n " , tx . GetHash ( ) . ToString ( ) ) ;
}
} else {
} else {
assert ( recentRejects ) ;
assert ( recentRejects ) ;
recentRejects - > insert ( tx . GetHash ( ) ) ;
recentRejects - > insert ( tx . GetHash ( ) ) ;