@@ -10,7 +10,7 @@ import io.iohk.ethereum.{crypto, domain, rlp}
10
10
import io .iohk .ethereum .domain .Block ._
11
11
import io .iohk .ethereum .domain .{Account , Address , Block , BlockchainImpl , UInt256 }
12
12
import io .iohk .ethereum .ledger ._
13
- import io .iohk .ethereum .testmode .{TestModeComponentsProvider , TestmodeConsensus }
13
+ import io .iohk .ethereum .testmode .{SealEngineType , TestModeComponentsProvider }
14
14
import io .iohk .ethereum .transactions .PendingTransactionsManager
15
15
import io .iohk .ethereum .transactions .PendingTransactionsManager .PendingTransactionsResponse
16
16
import io .iohk .ethereum .utils .{BlockchainConfig , ByteStringUtils , ForkBlockNumbers , Logger }
@@ -50,15 +50,15 @@ object TestService {
50
50
case class ChainParams (
51
51
genesis : GenesisParams ,
52
52
blockchainParams : BlockchainParams ,
53
- sealEngine : String ,
53
+ sealEngine : SealEngineType ,
54
54
accounts : Map [ByteString , GenesisAccount ]
55
55
)
56
56
57
57
case class AccountsInRangeRequestParams (
58
58
blockHashOrNumber : Either [BigInt , ByteString ],
59
59
txIndex : BigInt ,
60
60
addressHash : ByteString ,
61
- maxResults : BigInt
61
+ maxResults : Int
62
62
)
63
63
64
64
case class AccountsInRange (
@@ -71,7 +71,7 @@ object TestService {
71
71
txIndex : BigInt ,
72
72
address : ByteString ,
73
73
begin : BigInt ,
74
- maxResults : BigInt
74
+ maxResults : Int
75
75
)
76
76
77
77
case class StorageEntry (key : String , value : String )
@@ -98,7 +98,11 @@ object TestService {
98
98
case class AccountsInRangeResponse (addressMap : Map [ByteString , ByteString ], nextKey : ByteString )
99
99
100
100
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
+ )
102
106
103
107
case class GetLogHashRequest (transactionHash : ByteString )
104
108
case class GetLogHashResponse (logHash : ByteString )
@@ -109,7 +113,8 @@ class TestService(
109
113
pendingTransactionsManager : ActorRef ,
110
114
consensusConfig : ConsensusConfig ,
111
115
testModeComponentsProvider : TestModeComponentsProvider ,
112
- initialConfig : BlockchainConfig
116
+ initialConfig : BlockchainConfig ,
117
+ preimageCache : collection.concurrent.Map [ByteString , UInt256 ]
113
118
)(implicit
114
119
scheduler : Scheduler
115
120
) extends Logger {
@@ -118,10 +123,10 @@ class TestService(
118
123
import io .iohk .ethereum .jsonrpc .AkkaTaskOps ._
119
124
120
125
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 ()
123
127
private var currentConfig : BlockchainConfig = initialConfig
124
128
private var blockTimestamp : Long = 0
129
+ private var sealEngine : SealEngineType = SealEngineType .NoReward
125
130
126
131
def setChainParams (request : SetChainParamsRequest ): ServiceResponse [SetChainParamsResponse ] = {
127
132
currentConfig = buildNewConfig(request.chainParams.blockchainParams)
@@ -142,6 +147,10 @@ class TestService(
142
147
// set coinbase for blocks that will be tried to mine
143
148
etherbase = Address (genesisData.coinbase)
144
149
150
+ sealEngine = request.chainParams.sealEngine
151
+
152
+ resetPreimages(genesisData)
153
+
145
154
// remove current genesis (Try because it may not exist)
146
155
Try (blockchain.removeBlock(blockchain.genesisHeader.hash, withState = false ))
147
156
@@ -153,8 +162,12 @@ class TestService(
153
162
storeGenesisAccountCodes(genesisData.alloc)
154
163
storeGenesisAccountStorageData(genesisData.alloc)
155
164
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))
158
171
159
172
SetChainParamsResponse ().rightNow
160
173
}
@@ -214,7 +227,9 @@ class TestService(
214
227
def mineBlocks (request : MineBlocksRequest ): ServiceResponse [MineBlocksResponse ] = {
215
228
def mineBlock (): Task [Unit ] = {
216
229
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
+ )
218
233
.map { res =>
219
234
log.info(" Block mining result: " + res)
220
235
pendingTransactionsManager ! PendingTransactionsManager .ClearPendingTransactions
@@ -245,10 +260,11 @@ class TestService(
245
260
246
261
def importRawBlock (request : ImportRawBlockRequest ): ServiceResponse [ImportRawBlockResponse ] = {
247
262
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 )))
249
265
case Success (value) =>
250
266
testModeComponentsProvider
251
- .ledger(currentConfig)
267
+ .ledger(currentConfig, sealEngine )
252
268
.importBlock(value)
253
269
.flatMap(handleResult)
254
270
}
@@ -259,7 +275,9 @@ class TestService(
259
275
case BlockImportedToTop (blockImportData) =>
260
276
val blockHash = s " 0x ${ByteStringUtils .hash2string(blockImportData.head.block.header.hash)}"
261
277
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 )))
263
281
}
264
282
}
265
283
@@ -268,6 +286,17 @@ class TestService(
268
286
SetEtherbaseResponse ().rightNow
269
287
}
270
288
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
+
271
300
private def getBlockForMining (parentBlock : Block ): Task [PendingBlock ] = {
272
301
implicit val timeout : Timeout = Timeout (20 .seconds)
273
302
pendingTransactionsManager
@@ -276,7 +305,7 @@ class TestService(
276
305
.onErrorRecover { case _ => PendingTransactionsResponse (Nil ) }
277
306
.map { pendingTxs =>
278
307
testModeComponentsProvider
279
- .consensus(currentConfig, blockTimestamp)
308
+ .consensus(currentConfig, sealEngine, blockTimestamp)
280
309
.blockGenerator
281
310
.generateBlock(
282
311
parentBlock,
@@ -290,57 +319,87 @@ class TestService(
290
319
.timeout(timeout.duration)
291
320
}
292
321
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
+ */
293
327
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
+
294
331
val blockOpt = request.parameters.blockHashOrNumber
295
332
.fold(number => blockchain.getBlockByNumber(number), blockHash => blockchain.getBlockByHash(blockHash))
296
333
297
334
if (blockOpt.isEmpty) {
298
335
AccountsInRangeResponse (Map (), ByteString (0 )).rightNow
299
336
} 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 )
302
342
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 )
306
347
307
348
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,
313
350
nextKey =
314
351
if (accountBatch.size > request.parameters.maxResults)
315
- ByteStringUtils .string2hash(addressesForExistingAccounts. last._1)
352
+ accountBatch. last._1
316
353
else UInt256 (0 ).bytes
317
354
).rightNow
318
355
}
319
356
}
320
357
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
321
366
def storageRangeAt (request : StorageRangeRequest ): ServiceResponse [StorageRangeResponse ] = {
322
367
323
368
val blockOpt = request.parameters.blockHashOrNumber
324
369
.fold(number => blockchain.getBlockByNumber(number), hash => blockchain.getBlockByHash(hash))
325
370
326
371
(for {
327
- block <- blockOpt.toRight(StorageRangeResponse (complete = false , Map .empty))
372
+ block <- blockOpt.toRight(StorageRangeResponse (complete = false , Map .empty, None ))
328
373
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 }
342
401
)
343
- ) ).fold(identity, identity).rightNow
402
+ } ).fold(identity, identity).rightNow
344
403
}
345
404
346
405
def getLogHash (request : GetLogHashRequest ): ServiceResponse [GetLogHashResponse ] = {
0 commit comments