diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 55a1d1bf0..23089896d 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -41,6 +41,7 @@ using namespace dev::eth; using namespace p2p; unsigned const EthereumHost::c_oldProtocolVersion = 60; //TODO: remove this once v61+ is common +static unsigned const c_maxSendTransactions = 256; char const* const EthereumHost::s_stateNames[static_cast(SyncState::Size)] = {"Idle", "Waiting", "Hashes", "Blocks", "NewBlocks" }; @@ -67,8 +68,7 @@ bool EthereumHost::ensureInitialised() m_latestBlockSent = m_chain.currentHash(); clog(NetNote) << "Initialising: latest=" << m_latestBlockSent; - for (auto const& i: m_tq.transactions()) - m_transactionsSent.insert(i.first); + m_transactionsSent = m_tq.knownTransactions(); return true; } return false; @@ -114,25 +114,26 @@ void EthereumHost::doWork() void EthereumHost::maintainTransactions() { // Send any new transactions. - unordered_map, h256s> peerTransactions; - auto ts = m_tq.transactions(); - for (auto const& i: ts) + unordered_map, std::vector> peerTransactions; + auto ts = m_tq.topTransactions(c_maxSendTransactions); + for (size_t i = 0; i < ts.size(); ++i) { - bool unsent = !m_transactionsSent.count(i.first); - auto peers = get<1>(randomSelection(0, [&](EthereumPeer* p) { return p->m_requireTransactions || (unsent && !p->m_knownTransactions.count(i.first)); })); + auto const& t = ts[i]; + bool unsent = !m_transactionsSent.count(t.sha3()); + auto peers = get<1>(randomSelection(0, [&](EthereumPeer* p) { return p->m_requireTransactions || (unsent && !p->m_knownTransactions.count(t.sha3())); })); for (auto const& p: peers) - peerTransactions[p].push_back(i.first); + peerTransactions[p].push_back(i); } for (auto const& t: ts) - m_transactionsSent.insert(t.first); + m_transactionsSent.insert(t.sha3()); foreachPeer([&](shared_ptr _p) { bytes b; unsigned n = 0; - for (auto const& h: peerTransactions[_p]) + for (auto const& i: peerTransactions[_p]) { - _p->m_knownTransactions.insert(h); - b += ts[h].rlp(); + _p->m_knownTransactions.insert(ts[i].sha3()); + b += ts[i].rlp(); ++n; } diff --git a/libethereum/State.cpp b/libethereum/State.cpp index a000d628a..5362f6f68 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -47,6 +47,7 @@ namespace fs = boost::filesystem; #define ETH_TIMED_ENACTMENTS 0 static const u256 c_blockReward = c_network == Network::Olympic ? (1500 * finney) : (5 * ether); +static const unsigned c_maxSyncTransactions = 256; const char* StateSafeExceptions::name() { return EthViolet "⚙" EthBlue " ℹ"; } const char* StateDetail::name() { return EthViolet "⚙" EthWhite " ◌"; } @@ -495,7 +496,7 @@ pair State::sync(BlockChain const& _bc, TransactionQu pair ret; ret.second = false; - auto ts = _tq.transactions(); + auto ts = _tq.topTransactions(c_maxSyncTransactions); LastHashes lh; @@ -504,27 +505,26 @@ pair State::sync(BlockChain const& _bc, TransactionQu for (int goodTxs = 1; goodTxs; ) { goodTxs = 0; - for (auto const& i: ts) - if (!m_transactionSet.count(i.first)) + for (auto const& t: ts) + if (!m_transactionSet.count(t.sha3())) { try { - if (i.second.gasPrice() >= _gp.ask(*this)) + if (t.gasPrice() >= _gp.ask(*this)) { // Timer t; if (lh.empty()) lh = _bc.lastHashes(); - execute(lh, i.second); + execute(lh, t); ret.first.push_back(m_receipts.back()); - _tq.noteGood(i); ++goodTxs; // cnote << "TX took:" << t.elapsed() * 1000; } - else if (i.second.gasPrice() < _gp.ask(*this) * 9 / 10) + else if (t.gasPrice() < _gp.ask(*this) * 9 / 10) { // less than 90% of our ask price for gas. drop. - cnote << i.first << "Dropping El Cheapo transaction (<90% of ask price)"; - _tq.drop(i.first); + cnote << t.sha3() << "Dropping El Cheapo transaction (<90% of ask price)"; + _tq.drop(t.sha3()); } } catch (InvalidNonce const& in) @@ -535,57 +535,54 @@ pair State::sync(BlockChain const& _bc, TransactionQu if (req > got) { // too old - for (Transaction const& t: m_transactions) - if (t.from() == i.second.from()) + for (Transaction const& mt: m_transactions) + { + if (mt.from() == t.from()) { - if (t.nonce() < i.second.nonce()) - { - cnote << i.first << "Dropping old transaction (nonce too low)"; - _tq.drop(i.first); - } - else if (t.nonce() == i.second.nonce() && t.gasPrice() <= i.second.gasPrice()) - { - cnote << i.first << "Dropping old transaction (gas price lower)"; - _tq.drop(i.first); - } + if (mt.nonce() < t.nonce()) + cnote << t.sha3() << "Dropping old transaction (nonce too low)"; + else if (mt.nonce() == t.nonce() && mt.gasPrice() <= t.gasPrice()) + cnote << t.sha3() << "Dropping old transaction (gas price lower)"; } + } + _tq.drop(t.sha3()); } - else if (got > req + _tq.waiting(i.second.sender())) + else if (got > req + _tq.waiting(t.sender())) { // too new - cnote << i.first << "Dropping new transaction (too many nonces ahead)"; - _tq.drop(i.first); + cnote << t.sha3() << "Dropping new transaction (too many nonces ahead)"; + _tq.drop(t.sha3()); } else - _tq.setFuture(i); + _tq.setFuture(t.sha3()); } catch (BlockGasLimitReached const& e) { bigint const& got = *boost::get_error_info(e); if (got > m_currentBlock.gasLimit) { - cnote << i.first << "Dropping over-gassy transaction (gas > block's gas limit)"; - _tq.drop(i.first); + cnote << t.sha3() << "Dropping over-gassy transaction (gas > block's gas limit)"; + _tq.drop(t.sha3()); } else { // Temporarily no gas left in current block. // OPTIMISE: could note this and then we don't evaluate until a block that does have the gas left. // for now, just leave alone. -// _tq.setFuture(i); +// _tq.setFuture(t.sha3()); } } catch (Exception const& _e) { // Something else went wrong - drop it. - cnote << i.first << "Dropping invalid transaction:" << diagnostic_information(_e); - _tq.drop(i.first); + cnote << t.sha3() << "Dropping invalid transaction:" << diagnostic_information(_e); + _tq.drop(t.sha3()); } catch (std::exception const&) { // Something else went wrong - drop it. - _tq.drop(i.first); - cnote << i.first << "Transaction caused low-level exception :("; + _tq.drop(t.sha3()); + cnote << t.sha3() << "Transaction caused low-level exception :("; } } if (chrono::steady_clock::now() > deadline) diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index d2ced467e..478b4ff3c 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -95,10 +95,20 @@ ImportResult TransactionQueue::import(Transaction const& _transaction, ImportCal return ret; } -std::unordered_map TransactionQueue::transactions() const +Transactions TransactionQueue::topTransactions(unsigned _limit) const { ReadGuard l(m_lock); - return m_current; + Transactions res; + unsigned n = _limit; + for (auto t = m_current.begin(); n != 0 && t != m_current.end(); ++t, --n) + res.push_back(t->transaction); + return res; +} + +h256Hash TransactionQueue::knownTransactions() const +{ + ReadGuard l(m_lock); + return m_known; } ImportResult TransactionQueue::manageImport_WITH_LOCK(h256 const& _h, Transaction const& _transaction, ImportCallback const& _cb) @@ -108,35 +118,50 @@ ImportResult TransactionQueue::manageImport_WITH_LOCK(h256 const& _h, Transactio // Check validity of _transactionRLP as a transaction. To do this we just deserialise and attempt to determine the sender. // If it doesn't work, the signature is bad. // The transaction's nonce may yet be invalid (or, it could be "valid" but we may be missing a marginally older transaction). + assert(_h == _transaction.sha3()); // Remove any prior transaction with the same nonce but a lower gas price. // Bomb out if there's a prior transaction with higher gas price. - auto r = m_senders.equal_range(_transaction.from()); - for (auto it = r.first; it != r.second; ++it) - if (m_current.count(it->second) && m_current[it->second].nonce() == _transaction.nonce()) - if (_transaction.gasPrice() < m_current[it->second].gasPrice()) + auto cs = m_currentByAddressAndNonce.find(_transaction.from()); + if (cs != m_currentByAddressAndNonce.end()) + { + auto t = cs->second.find(_transaction.nonce()); + if (t != cs->second.end()) + { + if (_transaction.gasPrice() < (*t->second).transaction.gasPrice()) return ImportResult::OverbidGasPrice; else - { - remove_WITH_LOCK(it->second); - break; - } - else if (m_future.count(it->second) && m_future[it->second].nonce() == _transaction.nonce()) - if (_transaction.gasPrice() < m_future[it->second].gasPrice()) + remove_WITH_LOCK((*t->second).transaction.sha3()); + } + } + auto fs = m_future.find(_transaction.from()); + if (fs != m_future.end()) + { + auto t = fs->second.find(_transaction.nonce()); + if (t != fs->second.end()) + { + if (_transaction.gasPrice() < t->second.transaction.gasPrice()) return ImportResult::OverbidGasPrice; else { - remove_WITH_LOCK(it->second); - break; + fs->second.erase(t); + --m_futureSize; } - else {} - + } + } // If valid, append to blocks. insertCurrent_WITH_LOCK(make_pair(_h, _transaction)); m_known.insert(_h); if (_cb) m_callbacks[_h] = _cb; clog(TransactionQueueTraceChannel) << "Queued vaguely legit-looking transaction" << _h; + + while (m_current.size() > m_limit) + { + clog(TransactionQueueTraceChannel) << "Dropping out of bounds transaction" << _h; + remove_WITH_LOCK(m_current.rbegin()->transaction.sha3()); + } + m_onReady(); } catch (Exception const& _e) @@ -163,93 +188,111 @@ u256 TransactionQueue::maxNonce(Address const& _a) const u256 TransactionQueue::maxNonce_WITH_LOCK(Address const& _a) const { u256 ret = 0; - auto r = m_senders.equal_range(_a); - for (auto it = r.first; it != r.second; ++it) - if (m_current.count(it->second)) - { -// cdebug << it->first << "1+" << m_current.at(it->second).nonce(); - ret = max(ret, m_current.at(it->second).nonce() + 1); - } - else if (m_future.count(it->second)) - { -// cdebug << it->first << "1+" << m_future.at(it->second).nonce(); - ret = max(ret, m_future.at(it->second).nonce() + 1); - } - else - { - cwarn << "ERRROR!!!!! m_senders references non-current transaction"; - cwarn << "Sender" << it->first << "has transaction" << it->second; - cwarn << "Count of m_current for" << it->second << "is" << m_current.count(it->second); - } - return ret; + auto cs = m_currentByAddressAndNonce.find(_a); + if (cs != m_currentByAddressAndNonce.end() && !cs->second.empty()) + ret = cs->second.rbegin()->first; + auto fs = m_future.find(_a); + if (fs != m_future.end() && !fs->second.empty()) + ret = std::max(ret, fs->second.rbegin()->first); + return ret + 1; } void TransactionQueue::insertCurrent_WITH_LOCK(std::pair const& _p) { -// cdebug << "txQ::insertCurrent" << _p.first << _p.second.sender() << _p.second.nonce(); - m_senders.insert(make_pair(_p.second.sender(), _p.first)); - if (m_current.count(_p.first)) + if (m_currentByHash.count(_p.first)) + { cwarn << "Transaction hash" << _p.first << "already in current?!"; - m_current.insert(_p); -} + return; + } -bool TransactionQueue::remove_WITH_LOCK(h256 const& _txHash) -{ -// cdebug << "txQ::remove" << _txHash; - for (std::unordered_map* pool: { &m_current, &m_future }) + Transaction const& t = _p.second; + // Insert into current + auto inserted = m_currentByAddressAndNonce[t.from()].insert(std::make_pair(t.nonce(), PriorityQueue::iterator())); + PriorityQueue::iterator handle = m_current.emplace(VerifiedTransaction(t)); + inserted.first->second = handle; + m_currentByHash[_p.first] = handle; + + // Move following transactions from future to current + auto fs = m_future.find(t.from()); + if (fs != m_future.end()) { - auto pit = pool->find(_txHash); - if (pit != pool->end()) + u256 nonce = t.nonce() + 1; + auto fb = fs->second.find(nonce); + if (fb != fs->second.end()) { - auto r = m_senders.equal_range(pit->second.sender()); - for (auto i = r.first; i != r.second; ++i) - if (i->second == _txHash) - { - m_senders.erase(i); - break; - } -// cdebug << "=> nonce" << pit->second.nonce(); - pool->erase(pit); - return true; + auto ft = fb; + while (ft != fs->second.end() && ft->second.transaction.nonce() == nonce) + { + inserted = m_currentByAddressAndNonce[t.from()].insert(std::make_pair(ft->second.transaction.nonce(), PriorityQueue::iterator())); + PriorityQueue::iterator handle = m_current.emplace(move(ft->second)); + inserted.first->second = handle; + m_currentByHash[(*handle).transaction.sha3()] = handle; + --m_futureSize; + ++ft; + ++nonce; + } + fs->second.erase(fb, ft); + if (fs->second.empty()) + m_future.erase(t.from()); } } - return false; +} + +bool TransactionQueue::remove_WITH_LOCK(h256 const& _txHash) +{ + auto t = m_currentByHash.find(_txHash); + if (t == m_currentByHash.end()) + return false; + + Address from = (*t->second).transaction.from(); + auto it = m_currentByAddressAndNonce.find(from); + assert (it != m_currentByAddressAndNonce.end()); + it->second.erase((*t->second).transaction.nonce()); + m_current.erase(t->second); + m_currentByHash.erase(t); + if (it->second.empty()) + m_currentByAddressAndNonce.erase(it); + return true; } unsigned TransactionQueue::waiting(Address const& _a) const { ReadGuard l(m_lock); - auto it = m_senders.equal_range(_a); unsigned ret = 0; - for (auto i = it.first; i != it.second; ++i, ++ret) {} + auto cs = m_currentByAddressAndNonce.find(_a); + if (cs != m_currentByAddressAndNonce.end()) + ret = cs->second.size(); + auto fs = m_future.find(_a); + if (fs != m_future.end()) + ret += fs->second.size(); return ret; } -void TransactionQueue::setFuture(std::pair const& _t) +void TransactionQueue::setFuture(h256 const& _txHash) { // cdebug << "txQ::setFuture" << _t.first; WriteGuard l(m_lock); - if (m_current.count(_t.first)) - { - m_future.insert(_t); - m_current.erase(_t.first); - } -} + auto it = m_currentByHash.find(_txHash); + if (it == m_currentByHash.end()) + return; -void TransactionQueue::noteGood(std::pair const& _t) -{ -// cdebug << "txQ::noteGood" << _t.first; - WriteGuard l(m_lock); - auto r = m_senders.equal_range(_t.second.sender()); - for (auto it = r.first; it != r.second; ++it) + VerifiedTransaction const& st = *(it->second); + + Address from = st.transaction.from(); + auto& queue = m_currentByAddressAndNonce[from]; + auto& target = m_future[from]; + auto cutoff = queue.lower_bound(st.transaction.nonce()); + for (auto m = cutoff; m != queue.end(); ++m) { - auto fit = m_future.find(it->second); - if (fit != m_future.end()) - { - m_current.insert(*fit); - m_future.erase(fit); - } + VerifiedTransaction& t = const_cast(*(m->second)); // set has only const iterators. Since we are moving out of container that's fine + m_currentByHash.erase(t.transaction.sha3()); + target.emplace(t.transaction.nonce(), move(t)); + m_current.erase(m->second); + ++m_futureSize; } + queue.erase(cutoff, queue.end()); + if (queue.empty()) + m_currentByAddressAndNonce.erase(from); } void TransactionQueue::drop(h256 const& _txHash) @@ -265,3 +308,14 @@ void TransactionQueue::drop(h256 const& _txHash) remove_WITH_LOCK(_txHash); } + +void TransactionQueue::clear() +{ + WriteGuard l(m_lock); + m_known.clear(); + m_current.clear(); + m_currentByAddressAndNonce.clear(); + m_currentByHash.clear(); + m_future.clear(); + m_futureSize = 0; +} diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index d9bfef847..d92a73dab 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -43,6 +43,7 @@ enum class IfDropped { Ignore, Retry }; /** * @brief A queue of Transactions, each stored as RLP. + * Maintains a transaction queue sorted by nonce diff and gas price * @threadsafe */ class TransactionQueue @@ -50,6 +51,10 @@ class TransactionQueue public: using ImportCallback = std::function; + /// @brief TransactionQueue + /// @param _limit Maximum number of pending transactions in the queue + /// @param _futureLimit Maximum number of future nonce transactions + TransactionQueue(unsigned _limit = 1024, unsigned _futureLimit = 1024): m_current(PriorityCompare { *this }), m_limit(_limit), m_futureLimit(_futureLimit) {} ImportResult import(Transaction const& _tx, ImportCallback const& _cb = ImportCallback(), IfDropped _ik = IfDropped::Ignore); ImportResult import(bytes const& _tx, ImportCallback const& _cb = ImportCallback(), IfDropped _ik = IfDropped::Ignore) { return import(&_tx, _cb, _ik); } ImportResult import(bytesConstRef _tx, ImportCallback const& _cb = ImportCallback(), IfDropped _ik = IfDropped::Ignore); @@ -57,17 +62,40 @@ public: void drop(h256 const& _txHash); unsigned waiting(Address const& _a) const; - std::unordered_map transactions() const; - std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_current.size(), m_future.size()); } + Transactions topTransactions(unsigned _limit) const; + h256Hash knownTransactions() const; u256 maxNonce(Address const& _a) const; + void setFuture(h256 const& _t); - void setFuture(std::pair const& _t); - void noteGood(std::pair const& _t); - - void clear() { WriteGuard l(m_lock); m_senders.clear(); m_known.clear(); m_current.clear(); m_future.clear(); } + void clear(); template Handler onReady(T const& _t) { return m_onReady.add(_t); } private: + struct VerifiedTransaction + { + VerifiedTransaction(Transaction const& _t): transaction(_t) {} + VerifiedTransaction(VerifiedTransaction&& _t): transaction(std::move(_t.transaction)) {} + + VerifiedTransaction(VerifiedTransaction const&) = delete; + VerifiedTransaction operator=(VerifiedTransaction const&) = delete; + + Transaction transaction; + }; + + struct PriorityCompare + { + TransactionQueue& queue; + bool operator()(VerifiedTransaction const& _first, VerifiedTransaction const& _second) const + { + u256 const& height1 = _first.transaction.nonce() - queue.m_currentByAddressAndNonce[_first.transaction.sender()].begin()->first; + u256 const& height2 = _second.transaction.nonce() - queue.m_currentByAddressAndNonce[_second.transaction.sender()].begin()->first; + return height1 < height2 || (height1 == height2 && _first.transaction.gasPrice() > _second.transaction.gasPrice()); + } + }; + + // Use a set with dynamic comparator for minmax priority queue. The comparator takes into account min account nonce. Updating it does not affect the order. + using PriorityQueue = std::multiset; + ImportResult check_WITH_LOCK(h256 const& _h, IfDropped _ik); ImportResult manageImport_WITH_LOCK(h256 const& _h, Transaction const& _transaction, ImportCallback const& _cb); @@ -77,12 +105,19 @@ private: mutable SharedMutex m_lock; ///< General lock. h256Hash m_known; ///< Hashes of transactions in both sets. - std::unordered_multimap m_senders; ///< Mapping from the sender address to the transaction hash; useful for determining the nonce of a given sender. - std::unordered_map m_current; ///< Map of SHA3(tx) to tx. - std::unordered_map m_future; ///< For transactions that have a future nonce; we re-insert into current once the sender has a valid TX. + std::unordered_map> m_callbacks; ///< Called once. - h256Hash m_dropped; ///< Transactions that have previously been dropped. + h256Hash m_dropped; ///< Transactions that have previously been dropped + + PriorityQueue m_current; + std::unordered_map m_currentByHash; ///< Transaction hash to set ref + std::unordered_map> m_currentByAddressAndNonce; ///< Transactions grouped by account and nonce + std::unordered_map> m_future; /// Future transactions + Signal m_onReady; ///< Called when a subsequent call to import transactions will return a non-empty container. Be nice and exit fast. + unsigned m_limit; ///< Max number of pending transactions + unsigned m_futureLimit; ///< Max number of future transactions + unsigned m_futureSize = 0; ///< Current number of future transactions }; } diff --git a/libethereum/VerifiedBlock.h b/libethereum/VerifiedBlock.h index 6cafe4b2f..5d1efb7d0 100644 --- a/libethereum/VerifiedBlock.h +++ b/libethereum/VerifiedBlock.h @@ -43,7 +43,7 @@ struct VerifiedBlockRef /// @brief Verified block info, combines block data and verified info/transactions struct VerifiedBlock { - VerifiedBlock() {}; + VerifiedBlock() {} VerifiedBlock(BlockInfo&& _bi) { diff --git a/libp2p/Common.h b/libp2p/Common.h index 445ba0cca..c124115f9 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -236,7 +236,7 @@ template <> struct hash return boost::hash_range(range.begin(), range.end()); } if (_a.is_unspecified()) - return static_cast(0x3487194039229152ul); // Chosen by fair dice roll, guaranteed to be random + return static_cast(0x3487194039229152ull); // Chosen by fair dice roll, guaranteed to be random return std::hash()(_a.to_string()); } }; diff --git a/test/libethereum/blockchain.cpp b/test/libethereum/blockchain.cpp index 0314de78b..6455eee17 100644 --- a/test/libethereum/blockchain.cpp +++ b/test/libethereum/blockchain.cpp @@ -200,8 +200,8 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin) //get valid transactions Transactions txList; - for (auto const& txi: txs.transactions()) - txList.push_back(txi.second); + for (auto const& txi: txs.topTransactions(std::numeric_limits::max())) + txList.push_back(txi); blObj["transactions"] = writeTransactionsToJson(txList); BlockInfo current_BlockHeader = state.info(); diff --git a/test/libethereum/transactionqueue.cpp b/test/libethereum/transactionqueue.cpp index ab85350a5..aa76681c5 100644 --- a/test/libethereum/transactionqueue.cpp +++ b/test/libethereum/transactionqueue.cpp @@ -61,4 +61,112 @@ BOOST_AUTO_TEST_CASE(maxNonce) } +BOOST_AUTO_TEST_CASE(priority) +{ + dev::eth::TransactionQueue txq; + + const u256 gasCostCheap = 10 * szabo; + const u256 gasCostMed = 20 * szabo; + const u256 gasCostHigh = 30 * szabo; + const u256 gas = 25000; + Address dest = Address("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"); + Secret sender1 = Secret("0x3333333333333333333333333333333333333333333333333333333333333333"); + Secret sender2 = Secret("0x4444444444444444444444444444444444444444444444444444444444444444"); + Transaction tx0(0, gasCostCheap, gas, dest, bytes(), 0, sender1 ); + Transaction tx0_1(1, gasCostMed, gas, dest, bytes(), 0, sender1 ); + Transaction tx1(0, gasCostCheap, gas, dest, bytes(), 1, sender1 ); + Transaction tx2(0, gasCostHigh, gas, dest, bytes(), 0, sender2 ); + Transaction tx3(0, gasCostCheap + 1, gas, dest, bytes(), 1, sender2 ); + Transaction tx4(0, gasCostHigh, gas, dest, bytes(), 2, sender1 ); + Transaction tx5(0, gasCostMed, gas, dest, bytes(), 2, sender2 ); + + txq.import(tx0); + BOOST_CHECK(Transactions { tx0 } == txq.topTransactions(256)); + txq.import(tx0); + BOOST_CHECK(Transactions { tx0 } == txq.topTransactions(256)); + txq.import(tx0_1); + BOOST_CHECK(Transactions { tx0_1 } == txq.topTransactions(256)); + txq.import(tx1); + BOOST_CHECK((Transactions { tx0_1, tx1 }) == txq.topTransactions(256)); + txq.import(tx2); + BOOST_CHECK((Transactions { tx2, tx0_1, tx1 }) == txq.topTransactions(256)); + txq.import(tx3); + BOOST_CHECK((Transactions { tx2, tx0_1, tx1, tx3 }) == txq.topTransactions(256)); + txq.import(tx4); + BOOST_CHECK((Transactions { tx2, tx0_1, tx1, tx3, tx4 }) == txq.topTransactions(256)); + txq.import(tx5); + BOOST_CHECK((Transactions { tx2, tx0_1, tx1, tx3, tx5, tx4 }) == txq.topTransactions(256)); + + txq.drop(tx0_1.sha3()); + BOOST_CHECK((Transactions { tx2, tx1, tx3, tx5, tx4 }) == txq.topTransactions(256)); + txq.drop(tx1.sha3()); + BOOST_CHECK((Transactions { tx2, tx3, tx5, tx4 }) == txq.topTransactions(256)); + txq.drop(tx5.sha3()); + BOOST_CHECK((Transactions { tx2, tx3, tx4 }) == txq.topTransactions(256)); + + Transaction tx6(0, gasCostMed, gas, dest, bytes(), 20, sender1 ); + txq.import(tx6); + BOOST_CHECK((Transactions { tx2, tx3, tx4, tx6 }) == txq.topTransactions(256)); + + Transaction tx7(0, gasCostMed, gas, dest, bytes(), 2, sender2 ); + txq.import(tx7); + BOOST_CHECK((Transactions { tx2, tx3, tx4, tx6, tx7 }) == txq.topTransactions(256)); +} + +BOOST_AUTO_TEST_CASE(future) +{ + dev::eth::TransactionQueue txq; + + // from a94f5374fce5edbc8e2a8697c15331677e6ebf0b + const u256 gasCostMed = 20 * szabo; + const u256 gas = 25000; + Address dest = Address("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"); + Secret sender = Secret("0x3333333333333333333333333333333333333333333333333333333333333333"); + Transaction tx0(0, gasCostMed, gas, dest, bytes(), 0, sender ); + Transaction tx1(0, gasCostMed, gas, dest, bytes(), 1, sender ); + Transaction tx2(0, gasCostMed, gas, dest, bytes(), 2, sender ); + Transaction tx3(0, gasCostMed, gas, dest, bytes(), 3, sender ); + Transaction tx4(0, gasCostMed, gas, dest, bytes(), 4, sender ); + + txq.import(tx0); + txq.import(tx1); + txq.import(tx2); + txq.import(tx3); + txq.import(tx4); + BOOST_CHECK((Transactions { tx0, tx1, tx2, tx3, tx4 }) == txq.topTransactions(256)); + + txq.setFuture(tx2.sha3()); + BOOST_CHECK((Transactions { tx0, tx1 }) == txq.topTransactions(256)); + + Transaction tx2_2(1, gasCostMed, gas, dest, bytes(), 2, sender ); + txq.import(tx2_2); + BOOST_CHECK((Transactions { tx0, tx1, tx2_2, tx3, tx4 }) == txq.topTransactions(256)); +} + + +BOOST_AUTO_TEST_CASE(lmits) +{ + dev::eth::TransactionQueue txq(3, 3); + const u256 gasCostMed = 20 * szabo; + const u256 gas = 25000; + Address dest = Address("0x095e7baea6a6c7c4c2dfeb977efac326af552d87"); + Secret sender = Secret("0x3333333333333333333333333333333333333333333333333333333333333333"); + Secret sender2 = Secret("0x4444444444444444444444444444444444444444444444444444444444444444"); + Transaction tx0(0, gasCostMed, gas, dest, bytes(), 0, sender ); + Transaction tx1(0, gasCostMed, gas, dest, bytes(), 1, sender ); + Transaction tx2(0, gasCostMed, gas, dest, bytes(), 2, sender ); + Transaction tx3(0, gasCostMed, gas, dest, bytes(), 3, sender ); + Transaction tx4(0, gasCostMed, gas, dest, bytes(), 4, sender ); + Transaction tx5(0, gasCostMed + 1, gas, dest, bytes(), 0, sender2 ); + + txq.import(tx0); + txq.import(tx1); + txq.import(tx2); + txq.import(tx3); + txq.import(tx4); + txq.import(tx5); + BOOST_CHECK((Transactions { tx5, tx0, tx1 }) == txq.topTransactions(256)); +} + + BOOST_AUTO_TEST_SUITE_END()