diff --git a/libethcore/ChainOperationParams.h b/libethcore/ChainOperationParams.h index ddab8e679..ec57fe4ed 100644 --- a/libethcore/ChainOperationParams.h +++ b/libethcore/ChainOperationParams.h @@ -175,6 +175,7 @@ struct SChain { time_t verifyDaSigsPatchTimestamp = 0; time_t storageDestructionPatchTimestamp = 0; time_t powCheckPatchTimestamp = 0; + time_t skipInvalidTransactionsPatchTimestamp = 0; SChain() { name = "TestChain"; diff --git a/libethereum/Block.cpp b/libethereum/Block.cpp index 559cceee3..28e17ad0f 100644 --- a/libethereum/Block.cpp +++ b/libethereum/Block.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -483,31 +484,38 @@ tuple< TransactionReceipts, unsigned > Block::syncEveryone( LOG( m_logger ) << "Transaction " << tr.sha3() << " WouldNotBeInBlock: gasPrice " << tr.gasPrice() << " < " << _gasPrice; - // Add to the user-originated transactions that we've executed. - m_transactions.push_back( tr ); - m_transactionSet.insert( tr.sha3() ); + if ( SkipInvalidTransactionsPatch::isEnabled() ) { + // Add to the user-originated transactions that we've executed. + m_transactions.push_back( tr ); + m_transactionSet.insert( tr.sha3() ); - // TODO deduplicate - // "bad" transaction receipt for failed transactions - TransactionReceipt const null_receipt = - info().number() >= sealEngine()->chainParams().byzantiumForkBlock ? - TransactionReceipt( 0, info().gasUsed(), LogEntries() ) : - TransactionReceipt( EmptyTrie, info().gasUsed(), LogEntries() ); + // TODO deduplicate + // "bad" transaction receipt for failed transactions + TransactionReceipt const null_receipt = + info().number() >= sealEngine()->chainParams().byzantiumForkBlock ? + TransactionReceipt( 0, info().gasUsed(), LogEntries() ) : + TransactionReceipt( EmptyTrie, info().gasUsed(), LogEntries() ); - m_receipts.push_back( null_receipt ); - receipts.push_back( null_receipt ); + m_receipts.push_back( null_receipt ); + receipts.push_back( null_receipt ); - ++count_bad; + ++count_bad; + } continue; } ExecutionResult res = execute( _bc.lastBlockHashes(), tr, Permanence::Committed, OnOpFunc() ); - receipts.push_back( m_receipts.back() ); - if ( res.excepted == TransactionException::WouldNotBeInBlock ) - ++count_bad; + if ( !SkipInvalidTransactionsPatch::isEnabled() || + res.excepted != TransactionException::WouldNotBeInBlock ) { + receipts.push_back( m_receipts.back() ); + + // if added but bad + if ( res.excepted == TransactionException::WouldNotBeInBlock ) + ++count_bad; + } // // Debug only, related SKALE-2814 partial catchup testing @@ -862,9 +870,12 @@ ExecutionResult Block::execute( if ( _p == Permanence::Committed || _p == Permanence::CommittedWithoutState || _p == Permanence::Uncommitted ) { // Add to the user-originated transactions that we've executed. - m_transactions.push_back( _t ); - m_receipts.push_back( resultReceipt.second ); - m_transactionSet.insert( _t.sha3() ); + if ( !SkipInvalidTransactionsPatch::isEnabled() || + resultReceipt.first.excepted != TransactionException::WouldNotBeInBlock ) { + m_transactions.push_back( _t ); + m_receipts.push_back( resultReceipt.second ); + m_transactionSet.insert( _t.sha3() ); + } } if ( _p == Permanence::Committed || _p == Permanence::Uncommitted ) { m_state = stateSnapshot.createStateModifyCopyAndPassLock(); diff --git a/libethereum/ChainParams.cpp b/libethereum/ChainParams.cpp index 6b4693a9e..fb48f748d 100644 --- a/libethereum/ChainParams.cpp +++ b/libethereum/ChainParams.cpp @@ -266,6 +266,11 @@ ChainParams ChainParams::loadConfig( sChainObj.at( "powCheckPatchTimestamp" ).get_int64() : 0; + s.skipInvalidTransactionsPatchTimestamp = + sChainObj.count( "skipInvalidTransactionsPatchTimestamp" ) ? + sChainObj.at( "skipInvalidTransactionsPatchTimestamp" ).get_int64() : + 0; + if ( sChainObj.count( "nodeGroups" ) ) { std::vector< NodeGroup > nodeGroups; for ( const auto& nodeGroupConf : sChainObj["nodeGroups"].get_obj() ) { diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 36e651e0c..06cec9516 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -162,6 +163,8 @@ Client::Client( ChainParams const& _params, int _networkID, RevertableFSPatch::setTimestamp( chainParams().sChain.revertableFSPatchTimestamp ); StorageDestructionPatch::setTimestamp( chainParams().sChain.storageDestructionPatchTimestamp ); POWCheckPatch::setTimestamp( chainParams().sChain.powCheckPatchTimestamp ); + SkipInvalidTransactionsPatch::setTimestamp( + this->chainParams().sChain.skipInvalidTransactionsPatchTimestamp ); } @@ -654,7 +657,7 @@ size_t Client::syncTransactions( RevertableFSPatch::lastBlockTimestamp = blockChain().info().timestamp(); StorageDestructionPatch::lastBlockTimestamp = blockChain().info().timestamp(); POWCheckPatch::lastBlockTimestamp = blockChain().info().timestamp(); - + SkipInvalidTransactionsPatch::lastBlockTimestamp = blockChain().info().timestamp(); DEV_WRITE_GUARDED( x_working ) { assert( !m_working.isSealed() ); diff --git a/libethereum/SkaleHost.cpp b/libethereum/SkaleHost.cpp index 8a3b54109..1f15035fe 100644 --- a/libethereum/SkaleHost.cpp +++ b/libethereum/SkaleHost.cpp @@ -733,7 +733,7 @@ void SkaleHost::createBlock( const ConsensusExtFace::transactions_vector& _appro logState(); - clog( VerbosityDebug, "skale-host" ) + clog( VerbosityInfo, "skale-host" ) << "TQBYTES:CTQ:" << m_tq.status().currentBytes << ":FTQ:" << m_tq.status().futureBytes << ":TQSIZE:CTQ:" << m_tq.status().current << ":FTQ:" << m_tq.status().future; diff --git a/libethereum/ValidationSchemes.cpp b/libethereum/ValidationSchemes.cpp index cebb16c8e..38231c130 100644 --- a/libethereum/ValidationSchemes.cpp +++ b/libethereum/ValidationSchemes.cpp @@ -268,7 +268,9 @@ void validateConfigJson( js::mObject const& _obj ) { { "storageDestructionPatchTimestamp", { { js::int_type }, JsonFieldPresence::Optional } }, { "powCheckPatchTimestamp", { { js::int_type }, JsonFieldPresence::Optional } }, - { "nodeGroups", { { js::obj_type }, JsonFieldPresence::Optional } } } ); + { "nodeGroups", { { js::obj_type }, JsonFieldPresence::Optional } }, + { "skipInvalidTransactionsPatchTimestamp", + { { js::int_type }, JsonFieldPresence::Optional } } } ); js::mArray const& nodes = sChain.at( "nodes" ).get_array(); for ( auto const& obj : nodes ) { diff --git a/libskale/CMakeLists.txt b/libskale/CMakeLists.txt index cad33c885..73acfbbf5 100644 --- a/libskale/CMakeLists.txt +++ b/libskale/CMakeLists.txt @@ -20,6 +20,7 @@ set(sources OverlayFS.cpp StorageDestructionPatch.cpp POWCheckPatch.cpp + SkipInvalidTransactionsPatch.cpp ) set(headers @@ -39,6 +40,7 @@ set(headers RevertableFSPatch.h POWCheckPatch.h OverlayFS.h + SkipInvalidTransactionsPatch.h ) add_library(skale ${sources} ${headers}) diff --git a/libskale/SkipInvalidTransactionsPatch.cpp b/libskale/SkipInvalidTransactionsPatch.cpp new file mode 100644 index 000000000..5ab660337 --- /dev/null +++ b/libskale/SkipInvalidTransactionsPatch.cpp @@ -0,0 +1,12 @@ +#include "SkipInvalidTransactionsPatch.h" + +using namespace dev::eth; + +time_t SkipInvalidTransactionsPatch::activationTimestamp; +time_t SkipInvalidTransactionsPatch::lastBlockTimestamp; + +bool SkipInvalidTransactionsPatch::isEnabled() { + if ( activationTimestamp == 0 ) + return false; + return lastBlockTimestamp >= activationTimestamp; +} diff --git a/libskale/SkipInvalidTransactionsPatch.h b/libskale/SkipInvalidTransactionsPatch.h new file mode 100644 index 000000000..7c171b09e --- /dev/null +++ b/libskale/SkipInvalidTransactionsPatch.h @@ -0,0 +1,52 @@ +#ifndef SKIPINVALIDTRANSACTIONSPATCH_H +#define SKIPINVALIDTRANSACTIONSPATCH_H + +#include +#include +#include +#include +#include + +namespace dev { +namespace eth { +class Client; +} +} // namespace dev + +// What this patch does: +// 1. "Invalid" transactions that came with winning block proposal from consensus +// are skipped, and not included in block. +// Their "validity is determined in Block::syncEveryone: +// a) Transactions should have gasPrice >= current block min gas price +// b) State::execute should not throw (it causes WouldNotBeInBlock exception). +// Usually this exception is caused by Executive::verifyTransaction() failure. +// +// 2. Specifically for historic node - we ignore "invalid" transactions that +// are already in block as though they never came. +// This affects following JSON-RPC calls: +// 1) eth_getBlockByHash/Number +// 2) eth_getTransactionReceipt (affects "transactionIndex" field) +// 3) eth_getBlockTransactionCountByHash/Number +// 4) eth_getTransactionByHash (invalid transactions are treated as never present) +// 5) eth_getTransactionByBlockHash/NumberAndIndex +// Transactions are removed from Transaction Queue as usually. + +// TODO better start to apply patches from 1st block after timestamp, not second +class SkipInvalidTransactionsPatch : public SchainPatch { +public: + static bool isEnabled(); + + static void setTimestamp( time_t _activationTimestamp ) { + activationTimestamp = _activationTimestamp; + printInfo( __FILE__, _activationTimestamp ); + } + + static time_t getActivationTimestamp() { return activationTimestamp; } + +private: + friend class dev::eth::Client; + static time_t activationTimestamp; + static time_t lastBlockTimestamp; +}; + +#endif // SKIPINVALIDTRANSACTIONSPATCH_H diff --git a/libweb3jsonrpc/Eth.cpp b/libweb3jsonrpc/Eth.cpp index 42f89a002..4b68b667b 100644 --- a/libweb3jsonrpc/Eth.cpp +++ b/libweb3jsonrpc/Eth.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -48,13 +49,89 @@ using namespace dev::rpc; const uint64_t MAX_CALL_CACHE_ENTRIES = 1024; const uint64_t MAX_RECEIPT_CACHE_ENTRIES = 1024; +#ifdef HISTORIC_STATE + +using namespace dev::rpc::_detail; + +// TODO Check LatestBlock number - update! +// Needs external locks to exchange read one to write one +void GappedTransactionIndexCache::ensureCached( BlockNumber _bn, + std::shared_lock< std::shared_mutex >& _readLock, + std::unique_lock< std::shared_mutex >& _writeLock ) const { + if ( _bn != PendingBlock && _bn != LatestBlock && real2gappedCache.count( _bn ) ) + return; + + // change read lock for write lock + // they both will be destroyed externally + _readLock.unlock(); + _writeLock.lock(); + + assert( real2gappedCache.size() <= cacheSize ); + if ( real2gappedCache.size() >= cacheSize ) { + real2gappedCache.erase( real2gappedCache.begin() ); + gapped2realCache.erase( gapped2realCache.begin() ); + } + + // can be empty for absent blocks + h256s transactions = client.transactionHashes( _bn ); + + real2gappedCache[_bn] = vector< size_t >( transactions.size(), UNDEFINED ); + gapped2realCache[_bn] = vector< size_t >(); + + u256 gasBefore = 0; + for ( size_t realIndex = 0; realIndex < transactions.size(); ++realIndex ) { + // find transaction gas usage + const h256& th = transactions[realIndex]; + u256 gasAfter = client.transactionReceipt( th ).cumulativeGasUsed(); + u256 diff = gasAfter - gasBefore; + gasBefore = gasAfter; + + // ignore transactions with 0 gas usage + if ( diff == 0 ) + continue; + + // cache it + size_t gappedIndex = gapped2realCache[_bn].size(); + gapped2realCache[_bn].push_back( realIndex ); + real2gappedCache[_bn][realIndex] = gappedIndex; + + } // for +} + +// returns true if block N can contain invalid transactions +// returns false if this block was created with SkipInvalidTransactionsPatch and they were skipped +bool hasPotentialInvalidTransactionsInBlock( BlockNumber _bn, const Interface& _client ) { + if ( _bn == 0 ) + return false; + + if ( SkipInvalidTransactionsPatch::getActivationTimestamp() == 0 ) + return true; + + if ( _bn == PendingBlock ) + return !SkipInvalidTransactionsPatch::isEnabled(); + + if ( _bn == LatestBlock ) + _bn = _client.number(); + + time_t prev_ts = _client.blockInfo( _bn - 1 ).timestamp(); + + return prev_ts < SkipInvalidTransactionsPatch::getActivationTimestamp(); +} + +#endif Eth::Eth( const std::string& configPath, eth::Interface& _eth, eth::AccountHolder& _ethAccounts ) : skutils::json_config_file_accessor( configPath ), m_eth( _eth ), m_ethAccounts( _ethAccounts ), m_callCache( MAX_CALL_CACHE_ENTRIES ), - m_receiptsCache( MAX_RECEIPT_CACHE_ENTRIES ) {} + m_receiptsCache( MAX_RECEIPT_CACHE_ENTRIES ) +#ifdef HISTORIC_STATE + , + m_gapCache( std::make_unique< GappedTransactionIndexCache >( 16, *client() ) ) +#endif +{ +} bool Eth::isEnabledTransactionSending() const { bool isEnabled = true; @@ -221,7 +298,14 @@ Json::Value Eth::eth_getBlockTransactionCountByHash( string const& _blockHash ) if ( !client()->isKnown( blockHash ) ) return Json::Value( Json::nullValue ); - return toJS( client()->transactionCount( blockHash ) ); +#ifdef HISTORIC_STATE + BlockNumber bn = client()->numberFromHash( blockHash ); + if ( !hasPotentialInvalidTransactionsInBlock( bn, *client() ) ) +#endif + return toJS( client()->transactionCount( blockHash ) ); +#ifdef HISTORIC_STATE + return toJS( m_gapCache->gappedBlockTransactionCount( bn ) ); +#endif } catch ( ... ) { BOOST_THROW_EXCEPTION( JsonRpcException( Errors::ERROR_RPC_INVALID_PARAMS ) ); } @@ -233,7 +317,14 @@ Json::Value Eth::eth_getBlockTransactionCountByNumber( string const& _blockNumbe if ( !client()->isKnown( blockNumber ) ) return Json::Value( Json::nullValue ); - return toJS( client()->transactionCount( jsToBlockNumber( _blockNumber ) ) ); +#ifdef HISTORIC_STATE + BlockNumber bn = jsToBlockNumber( _blockNumber ); + if ( !hasPotentialInvalidTransactionsInBlock( bn, *client() ) ) +#endif + return toJS( client()->transactionCount( jsToBlockNumber( _blockNumber ) ) ); +#ifdef HISTORIC_STATE + return toJS( m_gapCache->gappedBlockTransactionCount( blockNumber ) ); +#endif } catch ( ... ) { BOOST_THROW_EXCEPTION( JsonRpcException( Errors::ERROR_RPC_INVALID_PARAMS ) ); } @@ -475,13 +566,41 @@ Json::Value Eth::eth_getBlockByHash( string const& _blockHash, bool _includeTran if ( !client()->isKnown( h ) ) return Json::Value( Json::nullValue ); - if ( _includeTransactions ) + if ( _includeTransactions ) { + Transactions transactions = client()->transactions( h ); + +#ifdef HISTORIC_STATE + BlockNumber bn = client()->numberFromHash( h ); + if ( hasPotentialInvalidTransactionsInBlock( bn, *client() ) ) { + // remove invalid transactions + size_t index = 0; + Transactions::iterator newEnd = std::remove_if( transactions.begin(), + transactions.end(), [this, &index, bn]( const Transaction& ) -> bool { + return !m_gapCache->transactionPresent( bn, index++ ); + } ); + transactions.erase( newEnd, transactions.end() ); + } +#endif return toJson( client()->blockInfo( h ), client()->blockDetails( h ), - client()->uncleHashes( h ), client()->transactions( h ), client()->sealEngine() ); - else + client()->uncleHashes( h ), transactions, client()->sealEngine() ); + } else { + h256s transactions = client()->transactionHashes( h ); + +#ifdef HISTORIC_STATE + BlockNumber bn = client()->numberFromHash( h ); + if ( hasPotentialInvalidTransactionsInBlock( bn, *client() ) ) { + // remove invalid transactions + size_t index = 0; + h256s::iterator newEnd = std::remove_if( transactions.begin(), transactions.end(), + [this, &index, bn]( const h256& ) -> bool { + return !m_gapCache->transactionPresent( bn, index++ ); + } ); + transactions.erase( newEnd, transactions.end() ); + } +#endif return toJson( client()->blockInfo( h ), client()->blockDetails( h ), - client()->uncleHashes( h ), client()->transactionHashes( h ), - client()->sealEngine() ); + client()->uncleHashes( h ), transactions, client()->sealEngine() ); + } } catch ( ... ) { BOOST_THROW_EXCEPTION( JsonRpcException( Errors::ERROR_RPC_INVALID_PARAMS ) ); } @@ -493,6 +612,13 @@ Json::Value Eth::eth_getBlockByNumber( string const& _blockNumber, bool _include if ( !client()->isKnown( h ) ) return Json::Value( Json::nullValue ); +#ifdef HISTORIC_STATE + h256 bh = client()->hashFromNumber( h ); + return eth_getBlockByHash( "0x" + bh.hex(), _includeTransactions ); + } catch ( const JsonRpcException& ) { + throw; +#else + if ( _includeTransactions ) return toJson( client()->blockInfo( h ), client()->blockDetails( h ), client()->uncleHashes( h ), client()->transactions( h ), client()->sealEngine() ); @@ -500,6 +626,7 @@ Json::Value Eth::eth_getBlockByNumber( string const& _blockNumber, bool _include return toJson( client()->blockInfo( h ), client()->blockDetails( h ), client()->uncleHashes( h ), client()->transactionHashes( h ), client()->sealEngine() ); +#endif } catch ( ... ) { BOOST_THROW_EXCEPTION( JsonRpcException( Errors::ERROR_RPC_INVALID_PARAMS ) ); } @@ -511,6 +638,13 @@ Json::Value Eth::eth_getTransactionByHash( string const& _transactionHash ) { if ( !client()->isKnownTransaction( h ) ) return Json::Value( Json::nullValue ); +#ifdef HISTORIC_STATE + // skip invalid + auto rcp = client()->localisedTransactionReceipt( h ); + if ( rcp.gasUsed() == 0 ) + return Json::Value( Json::nullValue ); +#endif + return toJson( client()->localisedTransaction( h ) ); } catch ( ... ) { BOOST_THROW_EXCEPTION( JsonRpcException( Errors::ERROR_RPC_INVALID_PARAMS ) ); @@ -522,6 +656,16 @@ Json::Value Eth::eth_getTransactionByBlockHashAndIndex( try { h256 bh = jsToFixed< 32 >( _blockHash ); unsigned int ti = static_cast< unsigned int >( jsToInt( _transactionIndex ) ); + +#ifdef HISTORIC_STATE + BlockNumber bn = client()->numberFromHash( bh ); + if ( hasPotentialInvalidTransactionsInBlock( bn, *client() ) ) + try { + ti = m_gapCache->realIndexFromGapped( bn, ti ); + } catch ( const out_of_range& ) { + return Json::Value( Json::nullValue ); + } +#endif if ( !client()->isKnownTransaction( bh, ti ) ) return Json::Value( Json::nullValue ); @@ -537,6 +681,16 @@ Json::Value Eth::eth_getTransactionByBlockNumberAndIndex( BlockNumber bn = jsToBlockNumber( _blockNumber ); h256 bh = client()->hashFromNumber( bn ); unsigned int ti = static_cast< unsigned int >( jsToInt( _transactionIndex ) ); + +#ifdef HISTORIC_STATE + if ( hasPotentialInvalidTransactionsInBlock( bn, *client() ) ) + try { + ti = m_gapCache->realIndexFromGapped( bn, ti ); + } catch ( const out_of_range& ) { + return Json::Value( Json::nullValue ); + } +#endif + if ( !client()->isKnownTransaction( bh, ti ) ) return Json::Value( Json::nullValue ); @@ -583,6 +737,22 @@ LocalisedTransactionReceipt Eth::eth_getTransactionReceipt( string const& _trans auto cli = client(); auto rcp = cli->localisedTransactionReceipt( h ); +#ifdef HISTORIC_STATE + if ( hasPotentialInvalidTransactionsInBlock( rcp.blockNumber(), *client() ) ) { + // skip invalid + if ( rcp.gasUsed() == 0 ) { + m_receiptsCache.put( cacheKey, nullptr ); + throw std::invalid_argument( "Not known transaction" ); + } + + // substitute position, skipping invalid transactions + size_t newIndex = + m_gapCache->gappedIndexFromReal( rcp.blockNumber(), rcp.transactionIndex() ); + rcp = LocalisedTransactionReceipt( rcp, rcp.hash(), rcp.blockHash(), rcp.blockNumber(), + newIndex, rcp.from(), rcp.to(), rcp.gasUsed(), rcp.contractAddress() ); + } +#endif + // got a receipt. Put it into the cache before returning // so that we have it if anyone asks again m_receiptsCache.put( cacheKey, make_shared< LocalisedTransactionReceipt >( rcp ) ); diff --git a/libweb3jsonrpc/Eth.h b/libweb3jsonrpc/Eth.h index ee661dbdd..a3ce404da 100644 --- a/libweb3jsonrpc/Eth.h +++ b/libweb3jsonrpc/Eth.h @@ -46,13 +46,87 @@ struct TransactionSkeleton; class Interface; class LocalisedTransactionReceipt; } // namespace eth - } // namespace dev namespace dev { namespace rpc { +#ifdef HISTORIC_STATE +namespace _detail { +// cache for transaction index mapping +class GappedTransactionIndexCache { +public: + GappedTransactionIndexCache( size_t _cacheSize, const dev::eth::Interface& _client ) + : client( _client ), cacheSize( _cacheSize ) { + assert( _cacheSize > 0 ); + } + + size_t realBlockTransactionCount( dev::eth::BlockNumber _bn ) const { + std::shared_lock< std::shared_mutex > readLock( mtx ); + std::unique_lock< std::shared_mutex > writeLock( mtx, std::defer_lock ); + ensureCached( _bn, readLock, writeLock ); + + return real2gappedCache[_bn].size(); + } + size_t gappedBlockTransactionCount( dev::eth::BlockNumber _bn ) const { + std::shared_lock< std::shared_mutex > readLock( mtx ); + std::unique_lock< std::shared_mutex > writeLock( mtx, std::defer_lock ); + ensureCached( _bn, readLock, writeLock ); + + return gapped2realCache[_bn].size(); + } + // can throw + size_t realIndexFromGapped( dev::eth::BlockNumber _bn, size_t _gappedIndex ) const { + std::shared_lock< std::shared_mutex > readLock( mtx ); + std::unique_lock< std::shared_mutex > writeLock( mtx, std::defer_lock ); + ensureCached( _bn, readLock, writeLock ); + + // throws out_of_range! + return gapped2realCache[_bn].at( _gappedIndex ); + } + // can throw + size_t gappedIndexFromReal( dev::eth::BlockNumber _bn, size_t _realIndex ) const { + std::shared_lock< std::shared_mutex > readLock( mtx ); + std::unique_lock< std::shared_mutex > writeLock( mtx, std::defer_lock ); + ensureCached( _bn, readLock, writeLock ); + + // throws out_of_range! + size_t res = real2gappedCache[_bn].at( _realIndex ); + if ( res == UNDEFINED ) + throw std::out_of_range( "Transaction at index " + std::to_string( _realIndex ) + + " in block " + to_string( _bn ) + + " is invalid and should have been ignored!" ); + return res; + } + // can throw + // TODO rename to valid + bool transactionPresent( dev::eth::BlockNumber _bn, size_t _realIndex ) const { + std::shared_lock< std::shared_mutex > readLock( mtx ); + std::unique_lock< std::shared_mutex > writeLock( mtx, std::defer_lock ); + ensureCached( _bn, readLock, writeLock ); + + return real2gappedCache[_bn].at( _realIndex ) != UNDEFINED; + } + +private: + void ensureCached( dev::eth::BlockNumber _bn, std::shared_lock< std::shared_mutex >& _readLock, + std::unique_lock< std::shared_mutex >& _writeLock ) const; + +private: + mutable std::shared_mutex mtx; + + const dev::eth::Interface& client; + const size_t cacheSize; + + enum { UNDEFINED = ( size_t ) -1 }; + + mutable std::map< dev::eth::BlockNumber, std::vector< size_t > > real2gappedCache; + mutable std::map< dev::eth::BlockNumber, std::vector< size_t > > gapped2realCache; +}; +} // namespace _detail +#endif + // Should only be called within a catch block std::string exceptionToErrorMessage(); @@ -159,6 +233,10 @@ class Eth : public dev::rpc::EthFace, public skutils::json_config_file_accessor // the transaction was not yet ready // for which the request has been executed cache::lru_cache< string, ptr< dev::eth::LocalisedTransactionReceipt > > m_receiptsCache; + +#ifdef HISTORIC_STATE + std::unique_ptr< _detail::GappedTransactionIndexCache > m_gapCache; +#endif }; } // namespace rpc diff --git a/test/tools/libtesteth/TestHelper.cpp b/test/tools/libtesteth/TestHelper.cpp index 97f548786..7384cff09 100644 --- a/test/tools/libtesteth/TestHelper.cpp +++ b/test/tools/libtesteth/TestHelper.cpp @@ -119,7 +119,7 @@ void simulateMining( Client& client, size_t numBlocks, const dev::Address &addre for ( size_t blockNumber = 0; blockNumber < numBlocks; ++blockNumber ) { reward += client.sealEngine()->blockReward( blockNumber ); } - state.addBalance( client.author(), reward ); + state.addBalance( address, reward ); state.commit(); const auto balanceAfter = client.balanceAt( address ); balanceAfter > balanceBefore; // make compiler happy diff --git a/test/unittests/libethereum/SkaleHost.cpp b/test/unittests/libethereum/SkaleHost.cpp index af8536ef9..07b744a50 100644 --- a/test/unittests/libethereum/SkaleHost.cpp +++ b/test/unittests/libethereum/SkaleHost.cpp @@ -14,12 +14,15 @@ #include #include #include +#include #include #include #include +#include +#include #include @@ -124,6 +127,9 @@ struct SkaleHostFixture : public TestOutputHelperFixture { chainParams.nodeInfo.port = chainParams.nodeInfo.port6 = rand_port; chainParams.sChain.nodes[0].port = chainParams.sChain.nodes[0].port6 = rand_port; + // not 0-timestamp genesis - to test patch + chainParams.timestamp = 1; + if( params.count("multiTransactionMode") && stoi( params.at( "multiTransactionMode" ) ) ) chainParams.sChain.multiTransactionMode = true; @@ -223,7 +229,15 @@ struct SkaleHostFixture : public TestOutputHelperFixture { BOOST_FIXTURE_TEST_SUITE( SkaleHostSuite, SkaleHostFixture ) //, *boost::unit_test::disabled() ) -BOOST_AUTO_TEST_CASE( validTransaction ) { +auto skipInvalidTransactionsVariants = boost::unit_test::data::make({false, true}); + +BOOST_DATA_TEST_CASE( validTransaction, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -260,14 +274,20 @@ BOOST_AUTO_TEST_CASE( validTransaction ) { REQUIRE_BALANCE_DECREASE( senderAddress, value + gasPrice * 21000 ); } -// Transaction should be IGNORED during execution +// Transaction should be IGNORED or EXCLUDED during execution (depending on skipInvalidTransactionsFlag) // Proposer should be penalized // 1 Small amount of random bytes // 2 110 random bytes // 3 110 bytes of semi-correct RLP -BOOST_AUTO_TEST_CASE( transactionRlpBad +BOOST_DATA_TEST_CASE( transactionRlpBad, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag // , *boost::unit_test::precondition( dev::test::run_not_express ) ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); bytes small_tx1 = bytes(); @@ -290,7 +310,13 @@ BOOST_AUTO_TEST_CASE( transactionRlpBad 1U ) ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 1, 3 ); + + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 1, 0 ); + } + else{ + REQUIRE_BLOCK_SIZE( 1, 3 ); + } REQUIRE_NONCE_INCREASE( senderAddress, 0 ); REQUIRE_BALANCE_DECREASE( senderAddress, 0 ); @@ -299,31 +325,33 @@ BOOST_AUTO_TEST_CASE( transactionRlpBad Transactions txns = client->transactions( 1 ); // cerr << toJson( txns ); - REQUIRE_BLOCK_TRANSACTION( 1, 0, txns[0].sha3() ); - REQUIRE_BLOCK_TRANSACTION( 1, 1, txns[1].sha3() ); - REQUIRE_BLOCK_TRANSACTION( 1, 2, txns[2].sha3() ); + if(!skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_TRANSACTION( 1, 0, txns[0].sha3() ); + REQUIRE_BLOCK_TRANSACTION( 1, 1, txns[1].sha3() ); + REQUIRE_BLOCK_TRANSACTION( 1, 2, txns[2].sha3() ); - // check also receipts and locations - size_t i = 0; - for ( const Transaction& tx : txns ) { - Transaction tx2 = client->transaction( tx.sha3() ); - LocalisedTransaction lt = client->localisedTransaction( tx.sha3() ); - LocalisedTransactionReceipt lr = client->localisedTransactionReceipt( tx.sha3() ); + // check also receipts and locations + size_t i = 0; + for ( const Transaction& tx : txns ) { + Transaction tx2 = client->transaction( tx.sha3() ); + LocalisedTransaction lt = client->localisedTransaction( tx.sha3() ); + LocalisedTransactionReceipt lr = client->localisedTransactionReceipt( tx.sha3() ); - BOOST_REQUIRE_EQUAL( tx2, tx ); + BOOST_REQUIRE_EQUAL( tx2, tx ); - BOOST_REQUIRE_EQUAL( lt, tx ); - BOOST_REQUIRE_EQUAL( lt.blockNumber(), 1 ); - BOOST_REQUIRE_EQUAL( lt.blockHash(), client->hashFromNumber( 1 ) ); - BOOST_REQUIRE_EQUAL( lt.transactionIndex(), i ); + BOOST_REQUIRE_EQUAL( lt, tx ); + BOOST_REQUIRE_EQUAL( lt.blockNumber(), 1 ); + BOOST_REQUIRE_EQUAL( lt.blockHash(), client->hashFromNumber( 1 ) ); + BOOST_REQUIRE_EQUAL( lt.transactionIndex(), i ); - BOOST_REQUIRE_EQUAL( lr.hash(), tx.sha3() ); - BOOST_REQUIRE_EQUAL( lr.blockNumber(), lt.blockNumber() ); - BOOST_REQUIRE_EQUAL( lr.blockHash(), lt.blockHash() ); - BOOST_REQUIRE_EQUAL( lr.transactionIndex(), i ); + BOOST_REQUIRE_EQUAL( lr.hash(), tx.sha3() ); + BOOST_REQUIRE_EQUAL( lr.blockNumber(), lt.blockNumber() ); + BOOST_REQUIRE_EQUAL( lr.blockHash(), lt.blockHash() ); + BOOST_REQUIRE_EQUAL( lr.transactionIndex(), i ); - ++i; - } // for + ++i; + } // for + } } class VrsHackedTransaction : public Transaction { @@ -334,12 +362,18 @@ class VrsHackedTransaction : public Transaction { } }; -// Transaction should be IGNORED during execution +// Transaction should be IGNORED during execution or absent if skipInvalidTransactionsFlag // Proposer should be penalized // zero signature -BOOST_AUTO_TEST_CASE( transactionSigZero +BOOST_DATA_TEST_CASE( transactionSigZero, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag // , *boost::unit_test::precondition( dev::test::run_not_express ) ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -369,21 +403,32 @@ BOOST_AUTO_TEST_CASE( transactionSigZero stub->createBlock( ConsensusExtFace::transactions_vector{stream.out()}, utcTime(), 1U ) ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 1, 1 ); - h256 txHash = sha3( stream.out() ); - REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 1, 0 ); + } + else { + REQUIRE_BLOCK_SIZE( 1, 1 ); + h256 txHash = sha3( stream.out() ); + REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + } REQUIRE_NONCE_INCREASE( senderAddress, 0 ); REQUIRE_BALANCE_DECREASE( senderAddress, 0 ); } -// Transaction should be IGNORED during execution +// Transaction should be IGNORED during execution or absent if skipInvalidTransactionsFlag // Proposer should be penalized // corrupted signature -BOOST_AUTO_TEST_CASE( transactionSigBad +BOOST_DATA_TEST_CASE( transactionSigBad, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag // , *boost::unit_test::precondition( dev::test::run_not_express ) ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -412,21 +457,33 @@ BOOST_AUTO_TEST_CASE( transactionSigBad stub->createBlock( ConsensusExtFace::transactions_vector{data}, utcTime(), 1U ) ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 1, 1 ); - h256 txHash = sha3( data ); - REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 1, 0 ); + } + else { + REQUIRE_BLOCK_SIZE( 1, 1 ); + h256 txHash = sha3( data ); + REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + } REQUIRE_NONCE_INCREASE( senderAddress, 0 ); REQUIRE_BALANCE_DECREASE( senderAddress, 0 ); } -// Transaction should be IGNORED during execution +// Transaction should be IGNORED during execution or absent if skipInvalidTransactionsFlag // Proposer should be penalized // gas < min_gas -BOOST_AUTO_TEST_CASE( transactionGasIncorrect +BOOST_DATA_TEST_CASE( transactionGasIncorrect, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag // , *boost::unit_test::precondition( dev::test::run_not_express ) ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -454,8 +511,14 @@ BOOST_AUTO_TEST_CASE( transactionGasIncorrect stub->createBlock( ConsensusExtFace::transactions_vector{stream.out()}, utcTime(), 1U ) ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 1, 1 ); - REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 1, 0 ); + } + else { + REQUIRE_BLOCK_SIZE( 1, 1 ); + REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + } REQUIRE_NONCE_INCREASE( senderAddress, 0 ); REQUIRE_BALANCE_DECREASE( senderAddress, 0 ); @@ -465,9 +528,15 @@ BOOST_AUTO_TEST_CASE( transactionGasIncorrect // Sender should be charged for gas consumed // Proposer should NOT be penalized // transaction exceedes it's gas limit -BOOST_AUTO_TEST_CASE( transactionGasNotEnough +BOOST_DATA_TEST_CASE( transactionGasNotEnough, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag // , *boost::unit_test::precondition( dev::test::run_not_express ) ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -519,11 +588,16 @@ BOOST_AUTO_TEST_CASE( transactionGasNotEnough } -// Transaction should be IGNORED during execution +// Transaction should be IGNORED during execution or absent if skipInvalidTransactionsFlag // Proposer should be penalized // nonce too big -BOOST_AUTO_TEST_CASE( transactionNonceBig, - *boost::unit_test::precondition( dev::test::run_not_express ) ) { +BOOST_DATA_TEST_CASE( transactionNonceBig, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -551,19 +625,31 @@ BOOST_AUTO_TEST_CASE( transactionNonceBig, stub->createBlock( ConsensusExtFace::transactions_vector{stream.out()}, utcTime(), 1U ) ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 1, 1 ); - REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 1, 0 ); + } + else { + REQUIRE_BLOCK_SIZE( 1, 1 ); + REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + } REQUIRE_NONCE_INCREASE( senderAddress, 0 ); REQUIRE_BALANCE_DECREASE( senderAddress, 0 ); } -// Transaction should be IGNORED during execution +// Transaction should be IGNORED during execution or absent if skipInvalidTransactionsFlag // Proposer should be penalized // nonce too small -BOOST_AUTO_TEST_CASE( transactionNonceSmall +BOOST_DATA_TEST_CASE( transactionNonceSmall, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag //, *boost::unit_test::precondition( dev::test::run_not_express ) ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -605,18 +691,29 @@ BOOST_AUTO_TEST_CASE( transactionNonceSmall stub->createBlock( ConsensusExtFace::transactions_vector{stream2.out()}, utcTime(), 2U ) ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 2, 1 ); - REQUIRE_BLOCK_TRANSACTION( 2, 0, txHash ); + + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 2, 0 ); + } + else { + REQUIRE_BLOCK_SIZE( 2, 1 ); + REQUIRE_BLOCK_TRANSACTION( 2, 0, txHash ); + } REQUIRE_NONCE_INCREASE( senderAddress, 0 ); REQUIRE_BALANCE_DECREASE( senderAddress, 0 ); } -// Transaction should be IGNORED during execution +// Transaction should be IGNORED during execution or absent if skipInvalidTransactionsFlag // Proposer should be penalized // not enough cash -BOOST_AUTO_TEST_CASE( transactionBalanceBad, - *boost::unit_test::precondition( dev::test::run_not_express ) ) { +BOOST_DATA_TEST_CASE( transactionBalanceBad, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -644,19 +741,31 @@ BOOST_AUTO_TEST_CASE( transactionBalanceBad, stub->createBlock( ConsensusExtFace::transactions_vector{stream.out()}, utcTime(), 1U ) ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 1, 1 ); - REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 1, 0 ); + } + else { + REQUIRE_BLOCK_SIZE( 1, 1 ); + REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash ); + } REQUIRE_NONCE_INCREASE( senderAddress, 0 ); REQUIRE_BALANCE_DECREASE( senderAddress, 0 ); } -// Transaction should be IGNORED during execution +// Transaction should be IGNORED during execution or absent if skipInvalidTransactionsFlag // Proposer should be penalized // transaction goes beyond block gas limit -BOOST_AUTO_TEST_CASE( transactionGasBlockLimitExceeded +BOOST_DATA_TEST_CASE( transactionGasBlockLimitExceeded, skipInvalidTransactionsVariants, skipInvalidTransactionsFlag // , *boost::unit_test::precondition( dev::test::run_not_express ) ) { + + if(skipInvalidTransactionsFlag){ + const_cast(client->chainParams()).sChain.skipInvalidTransactionsPatchTimestamp = 1; + } + SkipInvalidTransactionsPatch::setTimestamp(client->chainParams().sChain.skipInvalidTransactionsPatchTimestamp); + auto senderAddress = coinbase.address(); auto receiver = KeyPair::create(); @@ -696,9 +805,18 @@ BOOST_AUTO_TEST_CASE( transactionGasBlockLimitExceeded BOOST_REQUIRE_EQUAL( client->number(), 1 ); REQUIRE_BLOCK_INCREASE( 1 ); - REQUIRE_BLOCK_SIZE( 1, 2 ); - REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash1 ); - REQUIRE_BLOCK_TRANSACTION( 1, 1, txHash2 ); + + if(skipInvalidTransactionsFlag){ + REQUIRE_BLOCK_SIZE( 1, 1 ); + + REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash1 ); + } + else { + REQUIRE_BLOCK_SIZE( 1, 2 ); + + REQUIRE_BLOCK_TRANSACTION( 1, 0, txHash1 ); + REQUIRE_BLOCK_TRANSACTION( 1, 1, txHash2 ); + } REQUIRE_NONCE_INCREASE( senderAddress, 1 ); REQUIRE_BALANCE_DECREASE( senderAddress, 10000 * dev::eth::szabo ); // only 1st! diff --git a/test/unittests/libweb3jsonrpc/jsonrpc.cpp b/test/unittests/libweb3jsonrpc/jsonrpc.cpp index 69f521f70..5ef568135 100644 --- a/test/unittests/libweb3jsonrpc/jsonrpc.cpp +++ b/test/unittests/libweb3jsonrpc/jsonrpc.cpp @@ -2865,6 +2865,133 @@ BOOST_AUTO_TEST_CASE( mtm_import_future_txs ) { // BOOST_REQUIRE( !mtm ); // } +// historic node shall ignore invalid transactions in block +BOOST_AUTO_TEST_CASE( skip_invalid_transactions ) { + JsonRpcFixture fixture( c_genesisConfigString, true, true, false, true ); + dev::eth::simulateMining( *( fixture.client ), 1 ); // 2 Ether + + cout << "Balance: " << fixture.rpcClient->eth_getBalance(fixture.accountHolder->allAccounts()[0].hex(), "latest") << endl; + + // 1 import 1 transaction to increase block number + // also send 1 eth to account2 + // TODO repair mineMoney function! (it asserts) + Json::Value txJson; + txJson["from"] = fixture.coinbase.address().hex(); + txJson["gas"] = "200000"; + txJson["gasPrice"] = "5000000000000"; + txJson["to"] = fixture.account2.address().hex(); + txJson["value"] = "1000000000000000000"; + + txJson["nonce"] = "0"; + TransactionSkeleton ts1 = toTransactionSkeleton( txJson ); + ts1 = fixture.client->populateTransactionWithDefaults( ts1 ); + pair< bool, Secret > ar1 = fixture.accountHolder->authenticate( ts1 ); + Transaction tx1( ts1, ar1.second ); + fixture.client->importTransaction( tx1 ); + + // 1 eth left (returned to author) + dev::eth::mineTransaction(*(fixture.client), 1); + cout << "Balance2: " << fixture.rpcClient->eth_getBalance(fixture.accountHolder->allAccounts()[0].hex(), "latest") << endl; + + // 2 import 4 transactions with money for 1st, 2nd, and 3rd + + // require full 1 Ether for gas+value + txJson["gas"] = "100000"; + txJson["nonce"] = "1"; + txJson["value"] = "500000000000000000";// take 0.5 eth out + ts1 = toTransactionSkeleton( txJson ); + ts1 = fixture.client->populateTransactionWithDefaults( ts1 ); + ar1 = fixture.accountHolder->authenticate( ts1 ); + tx1 = Transaction( ts1, ar1.second ); + + txJson["nonce"] = "2"; + TransactionSkeleton ts2 = toTransactionSkeleton( txJson ); + ts2 = fixture.client->populateTransactionWithDefaults( ts2 ); + pair< bool, Secret > ar2 = fixture.accountHolder->authenticate( ts2 ); + Transaction tx2( ts2, ar2.second ); + + txJson["from"] = fixture.account2.address().hex(); + txJson["nonce"] = "0"; + txJson["value"] = "0"; + txJson["gasPrice"] = "20000000000"; + txJson["gas"] = "53000"; + TransactionSkeleton ts3 = toTransactionSkeleton( txJson ); + ts3 = fixture.client->populateTransactionWithDefaults( ts3 ); + pair< bool, Secret > ar3 = fixture.accountHolder->authenticate( ts3 ); + Transaction tx3( ts3, ar3.second ); + + txJson["nonce"] = "1"; + TransactionSkeleton ts4 = toTransactionSkeleton( txJson ); + ts3 = fixture.client->populateTransactionWithDefaults( ts4 ); + pair< bool, Secret > ar4 = fixture.accountHolder->authenticate( ts4 ); + Transaction tx4( ts3, ar3.second ); + + h256 h4 = fixture.client->importTransaction( tx4 ); // ok + h256 h2 = fixture.client->importTransaction( tx2 ); // invalid + h256 h3 = fixture.client->importTransaction( tx3 ); // ok + h256 h1 = fixture.client->importTransaction( tx1 ); // ok + + dev::eth::mineTransaction(*(fixture.client), 1); + cout << "Balance3: " << fixture.rpcClient->eth_getBalance(fixture.accountHolder->allAccounts()[0].hex(), "latest") << endl; + + (void)h1; + (void)h2; + (void)h3; + (void)h4; + +#ifdef HISTORIC_STATE + // 3 check that historic node sees only 3 txns + + // 1 Block + Json::Value block = fixture.rpcClient->eth_getBlockByNumber("latest", "false"); + + BOOST_REQUIRE_EQUAL(block["transactions"].size(), 3); + BOOST_REQUIRE_EQUAL(block["transactions"][0]["transactionIndex"], "0x0"); + BOOST_REQUIRE_EQUAL(block["transactions"][1]["transactionIndex"], "0x1"); + BOOST_REQUIRE_EQUAL(block["transactions"][2]["transactionIndex"], "0x2"); + + // 2 receipts + Json::Value r1,r3,r4; + BOOST_REQUIRE_NO_THROW(r1 = fixture.rpcClient->eth_getTransactionReceipt(toJS(h1))); + BOOST_REQUIRE_THROW (fixture.rpcClient->eth_getTransactionReceipt(toJS(h2)), jsonrpc::JsonRpcException); + BOOST_REQUIRE_NO_THROW(r3 = fixture.rpcClient->eth_getTransactionReceipt(toJS(h3))); + BOOST_REQUIRE_NO_THROW(r4 = fixture.rpcClient->eth_getTransactionReceipt(toJS(h4))); + + BOOST_REQUIRE_EQUAL(r1["transactionIndex"], "0x0"); + BOOST_REQUIRE_EQUAL(r3["transactionIndex"], "0x1"); + BOOST_REQUIRE_EQUAL(r4["transactionIndex"], "0x2"); + + // 3 transaction by index + Json::Value t0 = fixture.rpcClient->eth_getTransactionByBlockNumberAndIndex("latest", "0"); + Json::Value t1 = fixture.rpcClient->eth_getTransactionByBlockNumberAndIndex("latest", "1"); + Json::Value t2 = fixture.rpcClient->eth_getTransactionByBlockNumberAndIndex("latest", "2"); + + BOOST_REQUIRE_EQUAL(jsToFixed<32>(t0["hash"].asString()), h1); + BOOST_REQUIRE_EQUAL(jsToFixed<32>(t1["hash"].asString()), h3); + BOOST_REQUIRE_EQUAL(jsToFixed<32>(t2["hash"].asString()), h4); + + string bh = r1["blockHash"].asString(); + + t0 = fixture.rpcClient->eth_getTransactionByBlockHashAndIndex(bh, "0"); + t1 = fixture.rpcClient->eth_getTransactionByBlockHashAndIndex(bh, "1"); + t2 = fixture.rpcClient->eth_getTransactionByBlockHashAndIndex(bh, "2"); + + BOOST_REQUIRE_EQUAL(jsToFixed<32>(t0["hash"].asString()), h1); + BOOST_REQUIRE_EQUAL(jsToFixed<32>(t1["hash"].asString()), h3); + BOOST_REQUIRE_EQUAL(jsToFixed<32>(t2["hash"].asString()), h4); + + // 4 transaction by hash + BOOST_REQUIRE_THROW (fixture.rpcClient->eth_getTransactionByHash(toJS(h2)), jsonrpc::JsonRpcException); + + // 5 transaction count + Json::Value cnt = fixture.rpcClient->eth_getBlockTransactionCountByNumber("latest"); + BOOST_REQUIRE_EQUAL(cnt.asString(), "0x3"); + cnt = fixture.rpcClient->eth_getBlockTransactionCountByHash(bh); + BOOST_REQUIRE_EQUAL(cnt.asString(), "0x3"); +#endif +} + + BOOST_FIXTURE_TEST_SUITE( RestrictedAddressSuite, RestrictedAddressFixture ) BOOST_AUTO_TEST_CASE( direct_call ) { @@ -3067,4 +3194,73 @@ BOOST_AUTO_TEST_CASE( uncached_filestorage ) { BOOST_AUTO_TEST_SUITE_END() +BOOST_FIXTURE_TEST_SUITE( GappedCacheSuite, JsonRpcFixture ) + +#ifdef HISTORIC_STATE + +BOOST_AUTO_TEST_CASE( test_blocks ) { + dev::rpc::_detail::GappedTransactionIndexCache cache(10, *client); + BOOST_REQUIRE_EQUAL(cache.realBlockTransactionCount(LatestBlock), 0); + BOOST_REQUIRE_EQUAL(cache.realBlockTransactionCount(PendingBlock), 0); + BOOST_REQUIRE_EQUAL(cache.realBlockTransactionCount(999999999), 0); +} + +BOOST_AUTO_TEST_CASE( test_transactions ) { + + simulateMining(*client, 1, Address("0xf6c2a4ba2350e58a45916a03d0faa70dcc5dcfbf")); + + dev::rpc::_detail::GappedTransactionIndexCache cache(10, *client); + + Transaction invalid( + fromHex("0x0011223344556677889900112233445566778899001122334455667788990011223344556677889900112233" + "445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788" + "990011223344556677889900112233445566778899" ), + CheckTransaction::None, true ); + + Transaction valid( + fromHex( "0xf86c808504a817c80083015f90943d7112ee86223baf0a506b9d2a77595cbbba51d1872386f26fc10000801ca0655757fd0650a65a373c48a4dc0f3d6ac5c3831aa0cc2cb863a5909dc6c25f72a071882ee8633466a243c0ea64dadb3120c1ca7a5cc7433c6c0b1c861a85322265" ), + CheckTransaction::None ); + valid.checkOutExternalGas( 1 ); + + client->importTransactionsAsBlock(Transactions{invalid, valid}, 1); + + BOOST_REQUIRE_EQUAL(cache.realBlockTransactionCount(LatestBlock), 2); + BOOST_REQUIRE_EQUAL(cache.gappedBlockTransactionCount(LatestBlock), 1); + BOOST_REQUIRE_EQUAL(cache.realIndexFromGapped(LatestBlock, 0), 1); + BOOST_REQUIRE_EQUAL(cache.gappedIndexFromReal(LatestBlock, 1), 0); + BOOST_REQUIRE_THROW(cache.gappedIndexFromReal(LatestBlock, 0), std::out_of_range); + BOOST_REQUIRE_EQUAL(cache.transactionPresent(LatestBlock, 0), false); + BOOST_REQUIRE_EQUAL(cache.transactionPresent(LatestBlock, 1), true); +} + +BOOST_AUTO_TEST_CASE( test_exceptions ) { + + simulateMining(*client, 1, Address("0xf6c2a4ba2350e58a45916a03d0faa70dcc5dcfbf")); + + dev::rpc::_detail::GappedTransactionIndexCache cache(10, *client); + + Transaction invalid( + fromHex("0x0011223344556677889900112233445566778899001122334455667788990011223344556677889900112233" + "445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788" + "990011223344556677889900112233445566778899" ), + CheckTransaction::None, true ); + + Transaction valid( + fromHex( "0xf86c808504a817c80083015f90943d7112ee86223baf0a506b9d2a77595cbbba51d1872386f26fc10000801ca0655757fd0650a65a373c48a4dc0f3d6ac5c3831aa0cc2cb863a5909dc6c25f72a071882ee8633466a243c0ea64dadb3120c1ca7a5cc7433c6c0b1c861a85322265" ), + CheckTransaction::None ); + valid.checkOutExternalGas( 1 ); + + client->importTransactionsAsBlock(Transactions{invalid, valid}, 1); + + BOOST_REQUIRE_THROW(cache.realIndexFromGapped(LatestBlock, 1), std::out_of_range); + BOOST_REQUIRE_THROW(cache.realIndexFromGapped(LatestBlock, 2), std::out_of_range); + BOOST_REQUIRE_THROW(cache.gappedIndexFromReal(LatestBlock, 2), std::out_of_range); + BOOST_REQUIRE_THROW(cache.gappedIndexFromReal(LatestBlock, 0), std::out_of_range); + BOOST_REQUIRE_THROW(cache.transactionPresent(LatestBlock, 2), std::out_of_range); +} + +#endif + +BOOST_AUTO_TEST_SUITE_END() + BOOST_AUTO_TEST_SUITE_END()