Skip to content
This repository was archived by the owner on Oct 28, 2021. It is now read-only.

Commit d57e0d2

Browse files
authored
Merge pull request #5687 from ethereum/tq-maxdropped
Enforce 1024 max dropped transactions in the transaction queue
2 parents 22d02e9 + 2542256 commit d57e0d2

File tree

7 files changed

+234
-34
lines changed

7 files changed

+234
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- Fixed: [#5662](https://github.com/ethereum/aleth/pull/5662) Correct depth value when aleth-interpreter invokes `evmc_host_interface::call` callback.
4242
- Fixed: [#5666](https://github.com/ethereum/aleth/pull/5666) aleth-interpreter returns `EVMC_INVALID_INSTRUCTION` when `INVALID` opcode is encountered and `EVMC_UNKNOWN_INSTRUCTION` for undefined opcodes.
4343
- Fixed: [#5706](https://github.com/ethereum/aleth/pull/5706) Stop tracking sent transactions after they've been imported into the blockchain.
44+
- Fixed: [#5687](https://github.com/ethereum/aleth/pull/5687) Limit transaction queue's dropped transaction history to 1024 transactions.
4445

4546
## [1.6.0] - 2019-04-16
4647

libdevcore/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_library(
3333
Log.h
3434
LoggingProgramOptions.cpp
3535
LoggingProgramOptions.h
36+
LruCache.h
3637
MemoryDB.cpp
3738
MemoryDB.h
3839
OverlayDB.cpp

libdevcore/LruCache.h

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Aleth: Ethereum C++ client, tools and libraries.
2+
// Copyright 2019 Aleth Authors.
3+
// Licensed under the GNU General Public License, Version 3.
4+
5+
#pragma once
6+
7+
#include <list>
8+
#include <unordered_map>
9+
10+
namespace dev
11+
{
12+
template <class Key, class Value>
13+
class LruCache
14+
{
15+
using key_type = Key;
16+
using value_type = Value;
17+
using list_type = std::list<std::pair<key_type, value_type>>;
18+
using map_type = std::unordered_map<key_type, typename list_type::const_iterator>;
19+
20+
public:
21+
explicit LruCache(size_t _capacity) : m_capacity(_capacity) {}
22+
23+
size_t insert(key_type const& _key, value_type const& _val)
24+
{
25+
auto const cIter = m_index.find(_key);
26+
if (cIter == m_index.cend())
27+
{
28+
if (m_index.size() == m_capacity)
29+
{
30+
m_index.erase(m_data.back().first);
31+
m_data.pop_back();
32+
}
33+
m_data.push_front({_key, _val});
34+
m_index[_key] = m_data.begin();
35+
}
36+
else
37+
m_data.splice(m_data.begin(), m_data, cIter->second);
38+
39+
return m_index.size();
40+
}
41+
42+
size_t remove(key_type const& _key)
43+
{
44+
auto const cIter = m_index.find(_key);
45+
if (cIter != m_index.cend())
46+
{
47+
m_data.erase(cIter->second);
48+
m_index.erase(cIter);
49+
}
50+
51+
return m_index.size();
52+
}
53+
54+
bool touch(key_type const& _key)
55+
{
56+
auto const cIter = m_index.find(_key);
57+
if (cIter != m_index.cend())
58+
{
59+
m_data.splice(m_data.begin(), m_data, cIter->second);
60+
return true;
61+
}
62+
return false;
63+
}
64+
65+
bool contains(key_type const& _key) const { return m_index.find(_key) != m_index.cend(); }
66+
67+
bool contains(key_type const& _key, value_type const& _value) const
68+
{
69+
auto const cIter = m_index.find(_key);
70+
return cIter != m_index.cend() && (*(cIter->second)).second == _value;
71+
}
72+
73+
bool empty() const noexcept { return m_index.empty(); }
74+
75+
size_t size() const noexcept { return m_index.size(); }
76+
77+
size_t capacity() const noexcept { return m_capacity; }
78+
79+
void clear() noexcept
80+
{
81+
m_index.clear();
82+
m_data.clear();
83+
}
84+
85+
// Expose data iterator for testing purposes
86+
typename list_type::const_iterator cbegin() const noexcept { return m_data.cbegin(); }
87+
typename list_type::iterator begin() noexcept { return m_data.begin(); }
88+
typename list_type::const_iterator cend() const noexcept { return m_data.cend(); }
89+
typename list_type::iterator end() noexcept { return m_data.end(); }
90+
91+
private:
92+
list_type m_data;
93+
map_type m_index;
94+
size_t m_capacity;
95+
};
96+
} // namespace dev

libethereum/TransactionQueue.cpp

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,6 @@
1-
/*
2-
This file is part of cpp-ethereum.
3-
4-
cpp-ethereum is free software: you can redistribute it and/or modify
5-
it under the terms of the GNU General Public License as published by
6-
the Free Software Foundation, either version 3 of the License, or
7-
(at your option) any later version.
8-
9-
cpp-ethereum is distributed in the hope that it will be useful,
10-
but WITHOUT ANY WARRANTY; without even the implied warranty of
11-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12-
GNU General Public License for more details.
13-
14-
You should have received a copy of the GNU General Public License
15-
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
16-
*/
17-
/** @file TransactionQueue.cpp
18-
* @author Gav Wood <i@gavwood.com>
19-
* @date 2014
20-
*/
1+
// Aleth: Ethereum C++ client, tools and libraries.
2+
// Copyright 2019 Aleth Authors.
3+
// Licensed under the GNU General Public License, Version 3.
214

225
#include "TransactionQueue.h"
236

@@ -28,12 +11,17 @@ using namespace std;
2811
using namespace dev;
2912
using namespace dev::eth;
3013

31-
const size_t c_maxVerificationQueueSize = 8192;
32-
33-
TransactionQueue::TransactionQueue(unsigned _limit, unsigned _futureLimit):
34-
m_current(PriorityCompare { *this }),
35-
m_limit(_limit),
36-
m_futureLimit(_futureLimit)
14+
namespace
15+
{
16+
constexpr size_t c_maxVerificationQueueSize = 8192;
17+
constexpr size_t c_maxDroppedTransactionCount = 1024;
18+
} // namespace
19+
20+
TransactionQueue::TransactionQueue(unsigned _limit, unsigned _futureLimit)
21+
: m_dropped{c_maxDroppedTransactionCount},
22+
m_current{PriorityCompare{*this}},
23+
m_limit{_limit},
24+
m_futureLimit{_futureLimit}
3725
{
3826
unsigned verifierThreads = std::max(thread::hardware_concurrency(), 3U) - 2U;
3927
for (unsigned i = 0; i < verifierThreads; ++i)
@@ -70,7 +58,7 @@ ImportResult TransactionQueue::check_WITH_LOCK(h256 const& _h, IfDropped _ik)
7058
if (m_known.count(_h))
7159
return ImportResult::AlreadyKnown;
7260

73-
if (m_dropped.count(_h) && _ik == IfDropped::Ignore)
61+
if (m_dropped.touch(_h) && _ik == IfDropped::Ignore)
7462
return ImportResult::AlreadyInChain;
7563

7664
return ImportResult::Success;
@@ -328,7 +316,7 @@ void TransactionQueue::drop(h256 const& _txHash)
328316
return;
329317

330318
UpgradeGuard ul(l);
331-
m_dropped.insert(_txHash);
319+
m_dropped.insert(_txHash, true /* placeholder value */);
332320
remove_WITH_LOCK(_txHash);
333321
}
334322

libethereum/TransactionQueue.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121

2222
#pragma once
2323

24-
#include <functional>
25-
#include <condition_variable>
26-
#include <thread>
27-
#include <deque>
24+
#include "Transaction.h"
2825
#include <libdevcore/Common.h>
2926
#include <libdevcore/Guards.h>
3027
#include <libdevcore/Log.h>
28+
#include <libdevcore/LruCache.h>
3129
#include <libethcore/Common.h>
32-
#include "Transaction.h"
30+
#include <condition_variable>
31+
#include <deque>
32+
#include <functional>
33+
#include <thread>
3334

3435
namespace dev
3536
{
@@ -189,7 +190,11 @@ class TransactionQueue
189190
h256Hash m_known; ///< Headers of transactions in both sets.
190191

191192
std::unordered_map<h256, std::function<void(ImportResult)>> m_callbacks; ///< Called once.
192-
h256Hash m_dropped; ///< Transactions that have previously been dropped
193+
194+
///< Transactions that have previously been dropped. We technically only need to store the tx
195+
///< hash, but we also store bool as a placeholder value so that we can use an LRU cache to cap
196+
///< the number of transaction hashes stored.
197+
LruCache<h256, bool> m_dropped;
193198

194199
PriorityQueue m_current;
195200
std::unordered_map<h256, PriorityQueue::iterator> m_currentByHash; ///< Transaction hash to set ref

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ set(unittest_sources
1010
unittests/libdevcore/CommonJS.cpp
1111
unittests/libdevcore/core.cpp
1212
unittests/libdevcore/FixedHash.cpp
13+
unittests/libdevcore/LruCache.cpp
1314
unittests/libdevcore/RangeMask.cpp
1415
unittests/libdevcore/RLP.cpp
1516

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Aleth: Ethereum C++ client, tools and libraries.
2+
// Copyright 2019 Aleth Authors.
3+
// Licensed under the GNU General Public License, Version 3.
4+
5+
#include <libdevcore/LruCache.h>
6+
#include <test/tools/libtestutils/Common.h>
7+
#include <gtest/gtest.h>
8+
9+
using namespace std;
10+
using namespace dev;
11+
using namespace dev::test;
12+
13+
namespace
14+
{
15+
using LRU = LruCache<int, int>;
16+
using PAIR = pair<int, int>;
17+
using VEC = vector<PAIR>;
18+
19+
constexpr size_t c_capacity = 10;
20+
21+
mt19937_64 g_randomGenerator(random_device{}());
22+
23+
int randomNumber(int _min = INT_MIN, int _max = INT_MAX)
24+
{
25+
return std::uniform_int_distribution<int>{_min, _max}(g_randomGenerator);
26+
}
27+
28+
VEC Populate(LRU& _lruCache, size_t _count)
29+
{
30+
VEC ret;
31+
for (size_t i = 0; i < _count; i++)
32+
{
33+
auto const item = PAIR{randomNumber(), randomNumber()};
34+
ret.push_back(item);
35+
_lruCache.insert(item.first, item.second);
36+
}
37+
reverse(ret.begin(), ret.end());
38+
39+
return ret;
40+
}
41+
42+
void VerifyEquals(LRU& _lruCache, VEC& _data)
43+
{
44+
EXPECT_EQ(_lruCache.size(), _data.size());
45+
size_t i = 0;
46+
auto iter = _lruCache.begin();
47+
while (iter != _lruCache.cend() && i < _data.size())
48+
{
49+
EXPECT_EQ(*iter, _data[i]);
50+
iter++;
51+
i++;
52+
}
53+
}
54+
} // namespace
55+
56+
TEST(LruCache, BasicOperations)
57+
{
58+
LRU lruCache{c_capacity};
59+
EXPECT_EQ(lruCache.capacity(), c_capacity);
60+
EXPECT_TRUE(lruCache.empty());
61+
62+
// Populate and verify
63+
VEC testData = Populate(lruCache, lruCache.capacity());
64+
VerifyEquals(lruCache, testData);
65+
66+
// Reverse order and verify
67+
for (size_t i = 0; i < testData.size(); i++)
68+
lruCache.touch(testData[i].first);
69+
reverse(testData.begin(), testData.end());
70+
VerifyEquals(lruCache, testData);
71+
72+
// Remove elements and verify
73+
auto size = lruCache.size();
74+
for (PAIR item : testData)
75+
{
76+
lruCache.remove(item.first);
77+
EXPECT_FALSE(lruCache.contains(item.first));
78+
EXPECT_EQ(lruCache.size(), --size);
79+
}
80+
}
81+
82+
TEST(LruCache, AdvancedOperations)
83+
{
84+
LRU lruCache{c_capacity};
85+
VEC testData = Populate(lruCache, lruCache.capacity());
86+
VerifyEquals(lruCache, testData);
87+
testData = Populate(lruCache, lruCache.capacity());
88+
VerifyEquals(lruCache, testData);
89+
lruCache.clear();
90+
EXPECT_TRUE(lruCache.empty());
91+
EXPECT_EQ(lruCache.capacity(), c_capacity);
92+
}
93+
94+
TEST(LruCache, Constructors)
95+
{
96+
LRU lruCache{c_capacity};
97+
VEC testData = Populate(lruCache, lruCache.capacity());
98+
VerifyEquals(lruCache, testData);
99+
100+
LRU lruCacheCopy{lruCache};
101+
VerifyEquals(lruCacheCopy, testData);
102+
EXPECT_EQ(lruCache.capacity(), lruCacheCopy.capacity());
103+
104+
LRU lruCacheMove{move(lruCache)};
105+
VerifyEquals(lruCacheMove, testData);
106+
EXPECT_TRUE(lruCache.empty());
107+
EXPECT_EQ(lruCache.capacity(), c_capacity);
108+
}

0 commit comments

Comments
 (0)