@@ -10,7 +10,7 @@ import io.iohk.ethereum.{crypto, domain, rlp}
1010import io .iohk .ethereum .domain .Block ._
1111import io .iohk .ethereum .domain .{Account , Address , Block , BlockchainImpl , UInt256 }
1212import io .iohk .ethereum .ledger ._
13- import io .iohk .ethereum .testmode .{TestModeComponentsProvider , TestmodeConsensus }
13+ import io .iohk .ethereum .testmode .{SealEngineType , TestModeComponentsProvider }
1414import io .iohk .ethereum .transactions .PendingTransactionsManager
1515import io .iohk .ethereum .transactions .PendingTransactionsManager .PendingTransactionsResponse
1616import io .iohk .ethereum .utils .{BlockchainConfig , ByteStringUtils , ForkBlockNumbers , Logger }
@@ -50,15 +50,15 @@ object TestService {
5050 case class ChainParams (
5151 genesis : GenesisParams ,
5252 blockchainParams : BlockchainParams ,
53- sealEngine : String ,
53+ sealEngine : SealEngineType ,
5454 accounts : Map [ByteString , GenesisAccount ]
5555 )
5656
5757 case class AccountsInRangeRequestParams (
5858 blockHashOrNumber : Either [BigInt , ByteString ],
5959 txIndex : BigInt ,
6060 addressHash : ByteString ,
61- maxResults : BigInt
61+ maxResults : Int
6262 )
6363
6464 case class AccountsInRange (
@@ -71,7 +71,7 @@ object TestService {
7171 txIndex : BigInt ,
7272 address : ByteString ,
7373 begin : BigInt ,
74- maxResults : BigInt
74+ maxResults : Int
7575 )
7676
7777 case class StorageEntry (key : String , value : String )
@@ -98,7 +98,11 @@ object TestService {
9898 case class AccountsInRangeResponse (addressMap : Map [ByteString , ByteString ], nextKey : ByteString )
9999
100100 case class StorageRangeRequest (parameters : StorageRangeParams )
101- case class StorageRangeResponse (complete : Boolean , storage : Map [String , StorageEntry ])
101+ case class StorageRangeResponse (
102+ complete : Boolean ,
103+ storage : Map [String , StorageEntry ],
104+ nextKey : Option [String ]
105+ )
102106
103107 case class GetLogHashRequest (transactionHash : ByteString )
104108 case class GetLogHashResponse (logHash : ByteString )
@@ -109,7 +113,8 @@ class TestService(
109113 pendingTransactionsManager : ActorRef ,
110114 consensusConfig : ConsensusConfig ,
111115 testModeComponentsProvider : TestModeComponentsProvider ,
112- initialConfig : BlockchainConfig
116+ initialConfig : BlockchainConfig ,
117+ preimageCache : collection.concurrent.Map [ByteString , UInt256 ]
113118)(implicit
114119 scheduler : Scheduler
115120) extends Logger {
@@ -118,10 +123,10 @@ class TestService(
118123 import io .iohk .ethereum .jsonrpc .AkkaTaskOps ._
119124
120125 private var etherbase : Address = consensusConfig.coinbase
121- private var accountAddresses : List [String ] = List ()
122- private var accountRangeOffset = 0
126+ private var accountHashWithAdresses : List [(ByteString , Address )] = List ()
123127 private var currentConfig : BlockchainConfig = initialConfig
124128 private var blockTimestamp : Long = 0
129+ private var sealEngine : SealEngineType = SealEngineType .NoReward
125130
126131 def setChainParams (request : SetChainParamsRequest ): ServiceResponse [SetChainParamsResponse ] = {
127132 currentConfig = buildNewConfig(request.chainParams.blockchainParams)
@@ -142,6 +147,10 @@ class TestService(
142147 // set coinbase for blocks that will be tried to mine
143148 etherbase = Address (genesisData.coinbase)
144149
150+ sealEngine = request.chainParams.sealEngine
151+
152+ resetPreimages(genesisData)
153+
145154 // remove current genesis (Try because it may not exist)
146155 Try (blockchain.removeBlock(blockchain.genesisHeader.hash, withState = false ))
147156
@@ -153,8 +162,12 @@ class TestService(
153162 storeGenesisAccountCodes(genesisData.alloc)
154163 storeGenesisAccountStorageData(genesisData.alloc)
155164
156- accountAddresses = genesisData.alloc.keys.toList
157- accountRangeOffset = 0
165+ accountHashWithAdresses = (etherbase.toUnprefixedString :: genesisData.alloc.keys.toList)
166+ .map(hexAddress => {
167+ val address = Address (hexAddress)
168+ crypto.kec256(address.bytes) -> address
169+ })
170+ .sortBy(v => UInt256 (v._1))
158171
159172 SetChainParamsResponse ().rightNow
160173 }
@@ -214,7 +227,9 @@ class TestService(
214227 def mineBlocks (request : MineBlocksRequest ): ServiceResponse [MineBlocksResponse ] = {
215228 def mineBlock (): Task [Unit ] = {
216229 getBlockForMining(blockchain.getBestBlock().get)
217- .flatMap(blockForMining => testModeComponentsProvider.ledger(currentConfig).importBlock(blockForMining.block))
230+ .flatMap(blockForMining =>
231+ testModeComponentsProvider.ledger(currentConfig, sealEngine).importBlock(blockForMining.block)
232+ )
218233 .map { res =>
219234 log.info(" Block mining result: " + res)
220235 pendingTransactionsManager ! PendingTransactionsManager .ClearPendingTransactions
@@ -245,10 +260,11 @@ class TestService(
245260
246261 def importRawBlock (request : ImportRawBlockRequest ): ServiceResponse [ImportRawBlockResponse ] = {
247262 Try (decode(request.blockRlp).toBlock) match {
248- case Failure (_) => Task .now(Left (JsonRpcError (- 1 , " block validation failed!" , None )))
263+ case Failure (_) =>
264+ Task .now(Left (JsonRpcError (- 1 , " block validation failed!" , None )))
249265 case Success (value) =>
250266 testModeComponentsProvider
251- .ledger(currentConfig)
267+ .ledger(currentConfig, sealEngine )
252268 .importBlock(value)
253269 .flatMap(handleResult)
254270 }
@@ -259,7 +275,9 @@ class TestService(
259275 case BlockImportedToTop (blockImportData) =>
260276 val blockHash = s " 0x ${ByteStringUtils .hash2string(blockImportData.head.block.header.hash)}"
261277 ImportRawBlockResponse (blockHash).rightNow
262- case _ => Task .now(Left (JsonRpcError (- 1 , " block validation failed!" , None )))
278+ case e =>
279+ log.warn(" Block import failed with {}" , e)
280+ Task .now(Left (JsonRpcError (- 1 , " block validation failed!" , None )))
263281 }
264282 }
265283
@@ -268,6 +286,17 @@ class TestService(
268286 SetEtherbaseResponse ().rightNow
269287 }
270288
289+ private def resetPreimages (genesisData : GenesisData ): Unit = {
290+ preimageCache.clear()
291+ for {
292+ (_, account) <- genesisData.alloc
293+ storage <- account.storage
294+ storageKey <- storage.keys
295+ } {
296+ preimageCache.put(crypto.kec256(storageKey.bytes), storageKey)
297+ }
298+ }
299+
271300 private def getBlockForMining (parentBlock : Block ): Task [PendingBlock ] = {
272301 implicit val timeout : Timeout = Timeout (20 .seconds)
273302 pendingTransactionsManager
@@ -276,7 +305,7 @@ class TestService(
276305 .onErrorRecover { case _ => PendingTransactionsResponse (Nil ) }
277306 .map { pendingTxs =>
278307 testModeComponentsProvider
279- .consensus(currentConfig, blockTimestamp)
308+ .consensus(currentConfig, sealEngine, blockTimestamp)
280309 .blockGenerator
281310 .generateBlock(
282311 parentBlock,
@@ -290,57 +319,87 @@ class TestService(
290319 .timeout(timeout.duration)
291320 }
292321
322+ /** Get the list of accounts of size _maxResults in the given _blockHashOrNumber after given _txIndex.
323+ * In response AddressMap contains addressHash - > address starting from given _addressHash.
324+ * nexKey field is the next addressHash (if any addresses left in the state).
325+ * @see https://github.com/ethereum/retesteth/wiki/RPC-Methods#debug_accountrange
326+ */
293327 def getAccountsInRange (request : AccountsInRangeRequest ): ServiceResponse [AccountsInRangeResponse ] = {
328+ // This implementation works by keeping a list of know account from the genesis state
329+ // It might not cover all the cases as an account created inside a transaction won't be there.
330+
294331 val blockOpt = request.parameters.blockHashOrNumber
295332 .fold(number => blockchain.getBlockByNumber(number), blockHash => blockchain.getBlockByHash(blockHash))
296333
297334 if (blockOpt.isEmpty) {
298335 AccountsInRangeResponse (Map (), ByteString (0 )).rightNow
299336 } else {
300- val accountBatch = accountAddresses
301- .slice(accountRangeOffset, accountRangeOffset + request.parameters.maxResults.toInt + 1 )
337+ val accountBatch : Seq [(ByteString , Address )] = accountHashWithAdresses.view
338+ .dropWhile { case (hash, _) => UInt256 (hash) < UInt256 (request.parameters.addressHash) }
339+ .filter { case (_, address) => blockchain.getAccount(address, blockOpt.get.header.number).isDefined }
340+ .take(request.parameters.maxResults + 1 )
341+ .to(Seq )
302342
303- val addressesForExistingAccounts = accountBatch
304- .filter(key => blockchain.getAccount(Address (key), blockOpt.get.header.number).isDefined)
305- .map(key => (key, Address (crypto.kec256(Hex .decode(key)))))
343+ val addressMap : Map [ByteString , ByteString ] = accountBatch
344+ .take(request.parameters.maxResults)
345+ .map { case (hash, address) => hash -> address.bytes }
346+ .to(Map )
306347
307348 AccountsInRangeResponse (
308- addressMap = addressesForExistingAccounts
309- .take(request.parameters.maxResults.toInt)
310- .foldLeft(Map [ByteString , ByteString ]())((el, addressPair) =>
311- el + (addressPair._2.bytes -> ByteStringUtils .string2hash(addressPair._1))
312- ),
349+ addressMap = addressMap,
313350 nextKey =
314351 if (accountBatch.size > request.parameters.maxResults)
315- ByteStringUtils .string2hash(addressesForExistingAccounts. last._1)
352+ accountBatch. last._1
316353 else UInt256 (0 ).bytes
317354 ).rightNow
318355 }
319356 }
320357
358+ /** Get the list of storage values starting from _begin and up to _begin + _maxResults at given block.
359+ * nexKey field is the next key hash if any key left in the state, or 0x00 otherwise.
360+ *
361+ * Normally, this RPC method is supposed to also be able to look up the state after after transaction
362+ * _txIndex is executed. This is currently not supported in mantis.
363+ * @see https://github.com/ethereum/retesteth/wiki/RPC-Methods#debug_storagerangeat
364+ */
365+ // TODO ETCM-784, ETCM-758: see how we can get a state after an arbitrary transation
321366 def storageRangeAt (request : StorageRangeRequest ): ServiceResponse [StorageRangeResponse ] = {
322367
323368 val blockOpt = request.parameters.blockHashOrNumber
324369 .fold(number => blockchain.getBlockByNumber(number), hash => blockchain.getBlockByHash(hash))
325370
326371 (for {
327- block <- blockOpt.toRight(StorageRangeResponse (complete = false , Map .empty))
372+ block <- blockOpt.toRight(StorageRangeResponse (complete = false , Map .empty, None ))
328373 accountOpt = blockchain.getAccount(Address (request.parameters.address), block.header.number)
329- account <- accountOpt.toRight(StorageRangeResponse (complete = false , Map .empty))
330- storage = blockchain.getAccountStorageAt(
331- account.storageRoot,
332- request.parameters.begin,
333- ethCompatibleStorage = true
334- )
335- } yield StorageRangeResponse (
336- complete = true ,
337- storage = Map (
338- encodeAsHex(request.parameters.address).values -> StorageEntry (
339- encodeAsHex(request.parameters.begin).values,
340- encodeAsHex(storage).values
341- )
374+ account <- accountOpt.toRight(StorageRangeResponse (complete = false , Map .empty, None ))
375+
376+ } yield {
377+ // This implementation might be improved. It is working for most tests in ETS but might be
378+ // not really efficient and would not work outside of a test context. We simply iterate over
379+ // every key known by the preimage cache.
380+ val (valueBatch, next) = preimageCache.toSeq
381+ .sortBy(v => UInt256 (v._1))
382+ .view
383+ .dropWhile { case (hash, _) => UInt256 (hash) < request.parameters.begin }
384+ .map { case (keyHash, keyValue) =>
385+ (keyHash.toArray, keyValue, blockchain.getAccountStorageAt(account.storageRoot, keyValue, true ))
386+ }
387+ .filterNot { case (_, _, storageValue) => storageValue == ByteString (0 ) }
388+ .take(request.parameters.maxResults + 1 )
389+ .splitAt(request.parameters.maxResults)
390+
391+ val storage = valueBatch
392+ .map { case (keyHash, keyValue, value) =>
393+ UInt256 (keyHash).toHexString -> StorageEntry (keyValue.toHexString, UInt256 (value).toHexString)
394+ }
395+ .to(Map )
396+
397+ StorageRangeResponse (
398+ complete = next.isEmpty,
399+ storage = storage,
400+ nextKey = next.headOption.map { case (hash, _, _) => UInt256 (hash).toHexString }
342401 )
343- ) ).fold(identity, identity).rightNow
402+ } ).fold(identity, identity).rightNow
344403 }
345404
346405 def getLogHash (request : GetLogHashRequest ): ServiceResponse [GetLogHashResponse ] = {
0 commit comments