From ba621fffb817dd3529b417320698381e4cf15b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Kope=C4=87?= Date: Wed, 18 Nov 2020 10:55:48 +0100 Subject: [PATCH 1/7] [ETCM-135] Extract tx history logic into separate service --- .../jsonrpc/EthJsonMethodsImplicits.scala | 2 +- .../io/iohk/ethereum/jsonrpc/EthService.scala | 56 ++++------ .../ethereum/nodebuilder/NodeBuilder.scala | 13 ++- .../TransactionHistoryService.scala | 104 ++++++++++++++++++ .../ethereum/jsonrpc/EthServiceSpec.scala | 22 +++- .../jsonrpc/JsonRpcControllerFixture.scala | 5 + 6 files changed, 156 insertions(+), 46 deletions(-) create mode 100644 src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala index 7abe35c10a..d4755e9136 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala @@ -584,7 +584,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { addr <- extractAddress(addrJson) fromBlock <- extractQuantity(fromBlockJson) toBlock <- extractQuantity(toBlockJson) - } yield GetAccountTransactionsRequest(addr, fromBlock, toBlock) + } yield GetAccountTransactionsRequest(addr, fromBlock to toBlock) case _ => Left(InvalidParams()) } diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala index 0fd7c7f692..65db0550ae 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala @@ -3,6 +3,7 @@ package io.iohk.ethereum.jsonrpc import java.time.Duration import java.util.Date import java.util.concurrent.atomic.AtomicReference + import akka.actor.ActorRef import akka.util.{ByteString, Timeout} import cats.syntax.either._ @@ -15,8 +16,10 @@ import io.iohk.ethereum.consensus.ethash.EthashUtils import io.iohk.ethereum.crypto._ import io.iohk.ethereum.db.storage.TransactionMappingStorage.TransactionLocation import io.iohk.ethereum.domain.{BlockHeader, SignedTransaction, UInt256, _} +import io.iohk.ethereum.jsonrpc.AkkaTaskOps._ import io.iohk.ethereum.jsonrpc.FilterManager.{FilterChanges, FilterLogs, LogFilterLogs, TxLog} import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig +import io.iohk.ethereum.jsonrpc.{FilterManager => FM} import io.iohk.ethereum.keystore.KeyStore import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, Ledger, StxLedger} import io.iohk.ethereum.mpt.MerklePatriciaTrie.MissingNodeException @@ -26,14 +29,14 @@ import io.iohk.ethereum.rlp.RLPImplicitConversions._ import io.iohk.ethereum.rlp.RLPImplicits._ import io.iohk.ethereum.rlp.RLPList import io.iohk.ethereum.rlp.UInt256RLPImplicits._ -import io.iohk.ethereum.transactions.PendingTransactionsManager import io.iohk.ethereum.transactions.PendingTransactionsManager.{PendingTransaction, PendingTransactionsResponse} +import io.iohk.ethereum.transactions.{PendingTransactionsManager, TransactionHistoryService} import io.iohk.ethereum.utils._ -import io.iohk.ethereum.jsonrpc.AkkaTaskOps._ -import io.iohk.ethereum.jsonrpc.{FilterManager => FM} import monix.eval.Task import org.bouncycastle.util.encoders.Hex + import scala.collection.concurrent.{TrieMap, Map => ConcurrentMap} +import scala.collection.immutable.NumericRange import scala.concurrent.duration.FiniteDuration import scala.language.existentials import scala.reflect.ClassTag @@ -78,7 +81,7 @@ object EthService { case class GetTransactionByHashRequest(txHash: ByteString) case class GetTransactionByHashResponse(txResponse: Option[TransactionResponse]) - case class GetAccountTransactionsRequest(address: Address, fromBlock: BigInt, toBlock: BigInt) + case class GetAccountTransactionsRequest(address: Address, blocksRange: NumericRange[BigInt]) case class GetAccountTransactionsResponse(transactions: Seq[TransactionResponse]) case class GetTransactionReceiptRequest(txHash: ByteString) @@ -216,6 +219,7 @@ class EthService( syncingController: ActorRef, ommersPool: ActorRef, filterManager: ActorRef, + transactionHistoryService: TransactionHistoryService, filterConfig: FilterConfig, blockchainConfig: BlockchainConfig, protocolVersion: Int, @@ -325,7 +329,7 @@ class EthService( } def getTransactionDataByHash(txHash: ByteString): Task[Option[TransactionData]] = { - val maybeTxPendingResponse: Task[Option[TransactionData]] = getTransactionsFromPool().map { + val maybeTxPendingResponse: Task[Option[TransactionData]] = getTransactionsFromPool.map { _.pendingTransactions.map(_.stx.tx).find(_.hash == txHash).map(TransactionData(_)) } @@ -541,7 +545,7 @@ class EthService( reportActive() val bestBlock = blockchain.getBestBlock() val response: ServiceResponse[GetWorkResponse] = - Task.parZip2(getOmmersFromPool(bestBlock.hash), getTransactionsFromPool()).map { case (ommers, pendingTxs) => + Task.parZip2(getOmmersFromPool(bestBlock.hash), getTransactionsFromPool).map { case (ommers, pendingTxs) => val blockGenerator = ethash.blockGenerator val PendingBlockAndState(pb, _) = blockGenerator.generateBlock( bestBlock, @@ -575,13 +579,13 @@ class EthService( })(Task.now(OmmersPool.Ommers(Nil))) // NOTE If not Ethash consensus, ommers do not make sense, so => Nil // TODO This seems to be re-implemented in TransactionPicker, probably move to a better place? Also generalize the error message. - private[jsonrpc] def getTransactionsFromPool(): Task[PendingTransactionsResponse] = { + private[jsonrpc] val getTransactionsFromPool: Task[PendingTransactionsResponse] = { implicit val timeout: Timeout = Timeout(getTransactionFromPoolTimeout) pendingTransactionsManager .askFor[PendingTransactionsResponse](PendingTransactionsManager.GetPendingTransactions) .onErrorRecoverWith { case ex: Throwable => - log.error("failed to get transactions, mining block with empty transactions list", ex) + log.error("Failed to get pending transactions, passing empty transactions list", ex) Task.now(PendingTransactionsResponse(Nil)) } } @@ -930,41 +934,19 @@ class EthService( def getAccountTransactions( request: GetAccountTransactionsRequest ): ServiceResponse[GetAccountTransactionsResponse] = { - val numBlocksToSearch = request.toBlock - request.fromBlock - if (numBlocksToSearch > jsonRpcConfig.accountTransactionsMaxBlocks) { + if (request.blocksRange.length > jsonRpcConfig.accountTransactionsMaxBlocks) { Task.now( Left( JsonRpcError.InvalidParams( - s"""Maximum number of blocks to search is ${jsonRpcConfig.accountTransactionsMaxBlocks}, requested: $numBlocksToSearch. - |See: 'network.rpc.account-transactions-max-blocks' config.""".stripMargin + s"""Maximum number of blocks to search is ${jsonRpcConfig.accountTransactionsMaxBlocks}, requested: ${request.blocksRange.length}. + |See: 'mantis.network.rpc.account-transactions-max-blocks' config.""".stripMargin ) ) ) } else { - - def collectTxs( - blockHeader: Option[BlockHeader], - pending: Boolean - ): PartialFunction[SignedTransaction, TransactionResponse] = { - case stx if stx.safeSenderIsEqualTo(request.address) => - TransactionResponse(stx, blockHeader, pending = Some(pending), isOutgoing = Some(true)) - case stx if stx.tx.receivingAddress.contains(request.address) => - TransactionResponse(stx, blockHeader, pending = Some(pending), isOutgoing = Some(false)) - } - - getTransactionsFromPool map { case PendingTransactionsResponse(pendingTransactions) => - val pendingTxs = pendingTransactions - .map(_.stx.tx) - .collect(collectTxs(None, pending = true)) - - val txsFromBlocks = (request.toBlock to request.fromBlock by -1).toStream - .flatMap { n => blockchain.getBlockByNumber(n) } - .flatMap { block => - block.body.transactionList.collect(collectTxs(Some(block.header), pending = false)).reverse - } - - Right(GetAccountTransactionsResponse(pendingTxs ++ txsFromBlocks)) - } + transactionHistoryService + .getAccountTransactions(request.address, request.blocksRange) + .map(GetAccountTransactionsResponse(_).asRight) } } @@ -980,7 +962,7 @@ class EthService( * @return pending transactions */ def ethPendingTransactions(req: EthPendingTransactionsRequest): ServiceResponse[EthPendingTransactionsResponse] = - getTransactionsFromPool().map { resp => + getTransactionsFromPool.map { resp => Right(EthPendingTransactionsResponse(resp.pendingTransactions)) } } diff --git a/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala b/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala index d4c19b32e9..0765e8c69f 100644 --- a/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala +++ b/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala @@ -31,7 +31,7 @@ import io.iohk.ethereum.network.rlpx.AuthHandshaker import io.iohk.ethereum.network.{PeerManagerActor, ServerActor, _} import io.iohk.ethereum.ommers.OmmersPool import io.iohk.ethereum.testmode.{TestLedgerBuilder, TestmodeConsensusBuilder} -import io.iohk.ethereum.transactions.PendingTransactionsManager +import io.iohk.ethereum.transactions.{PendingTransactionsManager, TransactionHistoryService} import io.iohk.ethereum.utils.Config.SyncConfig import io.iohk.ethereum.utils._ import java.security.SecureRandom @@ -274,6 +274,12 @@ trait PendingTransactionsManagerBuilder { system.actorOf(PendingTransactionsManager.props(txPoolConfig, peerManager, etcPeerManager, peerEventBus)) } +trait TransactionHistoryServiceBuilder { + self: BlockchainBuilder with PendingTransactionsManagerBuilder with TxPoolConfigBuilder => + lazy val transactionHistoryService = + new TransactionHistoryService(blockchain, pendingTransactionsManager, txPoolConfig.getTransactionFromPoolTimeout) +} + trait FilterManagerBuilder { self: ActorSystemBuilder with BlockchainBuilder @@ -333,7 +339,8 @@ trait EthServiceBuilder { with FilterConfigBuilder with TxPoolConfigBuilder with JSONRpcConfigBuilder - with AsyncConfigBuilder => + with AsyncConfigBuilder + with TransactionHistoryServiceBuilder => lazy val ethService = new EthService( blockchain, @@ -344,6 +351,7 @@ trait EthServiceBuilder { syncController, ommersPool, filterManager, + transactionHistoryService, filterConfig, blockchainConfig, Config.Network.protocolVersion, @@ -665,3 +673,4 @@ trait Node with KeyStoreConfigBuilder with AsyncConfigBuilder with CheckpointBlockGeneratorBuilder + with TransactionHistoryServiceBuilder diff --git a/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala new file mode 100644 index 0000000000..68ccee6542 --- /dev/null +++ b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala @@ -0,0 +1,104 @@ +package io.iohk.ethereum.transactions + +import akka.actor.ActorRef +import akka.util.Timeout +import io.iohk.ethereum.domain.{Address, BlockHeader, Blockchain, SignedTransaction} +import io.iohk.ethereum.jsonrpc.AkkaTaskOps.TaskActorOps +import io.iohk.ethereum.jsonrpc.TransactionResponse +import io.iohk.ethereum.transactions.PendingTransactionsManager.PendingTransaction +import io.iohk.ethereum.transactions.TransactionHistoryService.TxChecker +import io.iohk.ethereum.utils.Logger +import monix.eval.Task +import monix.reactive.{Observable, OverflowStrategy} + +import scala.collection.immutable.NumericRange +import scala.concurrent.duration.FiniteDuration + +class TransactionHistoryService( + blockchain: Blockchain, + pendingTransactionsManager: ActorRef, + getTransactionFromPoolTimeout: FiniteDuration +) extends Logger { + def getAccountTransactions(account: Address, fromBlocks: NumericRange[BigInt]): Task[Seq[TransactionResponse]] = { + val txnsFromBlocks = Observable + .from(fromBlocks.reverse) + .mapParallelOrdered(10)(blockNr => Task { blockchain.getBlockByNumber(blockNr) })(OverflowStrategy.Unbounded) + .collect { case Some(block) => block } + .concatMapIterable { block => + val checker = TxChecker.forSigned(block.header) + block.body.transactionList.toVector + .collect(Function.unlift(checker.checkTx(_, account))) + .reverse + } + .toListL + + val txnsFromMempool = getTransactionsFromPool map { pendingTransactions => + pendingTransactions + .collect(Function.unlift(TxChecker.forPending.checkTx(_, account))) + } + + Task.parMap2(txnsFromBlocks, txnsFromMempool)((pending, fromBlocks) => (pending ++ fromBlocks)) + } + + private val getTransactionsFromPool: Task[Vector[PendingTransaction]] = { + implicit val timeout: Timeout = getTransactionFromPoolTimeout + pendingTransactionsManager + .askFor[PendingTransactionsManager.PendingTransactionsResponse](PendingTransactionsManager.GetPendingTransactions) + .map(_.pendingTransactions.toVector) + .onErrorRecoverWith { case ex: Throwable => + log.error("Failed to get pending transactions, passing empty transactions list", ex) + Task.now(Vector.empty) + } + } +} +object TransactionHistoryService { + trait TxChecker[T] { + def isTxPending: Boolean + def maybeBlockHeader: Option[BlockHeader] + def isSender(tx: T, address: Address): Boolean + def isReceiver(tx: T, address: Address): Boolean + def asSigned(tx: T): SignedTransaction + + def checkTx(tx: T, address: Address): Option[TransactionResponse] = { + if (isSender(tx, address)) { + Some( + TransactionResponse( + asSigned(tx), + maybeBlockHeader, + pending = Some(isTxPending), + isOutgoing = Some(true) + ) + ) + } else if (isReceiver(tx, address)) { + Some( + TransactionResponse( + asSigned(tx), + maybeBlockHeader, + pending = Some(isTxPending), + isOutgoing = Some(false) + ) + ) + } else { + None + } + } + } + object TxChecker { + val forPending: TxChecker[PendingTransaction] = new TxChecker[PendingTransaction] { + val isTxPending = true + val maybeBlockHeader = None + def isSender(tx: PendingTransaction, maybeSender: Address) = tx.stx.senderAddress == maybeSender + def isReceiver(tx: PendingTransaction, maybeReceiver: Address) = + tx.stx.tx.tx.receivingAddress.contains(maybeReceiver) + def asSigned(tx: PendingTransaction) = tx.stx.tx + } + + def forSigned(blockHeader: BlockHeader): TxChecker[SignedTransaction] = new TxChecker[SignedTransaction] { + val isTxPending = false + val maybeBlockHeader = Some(blockHeader) + def isSender(tx: SignedTransaction, maybeSender: Address) = tx.safeSenderIsEqualTo(maybeSender) + def isReceiver(tx: SignedTransaction, maybeReceiver: Address) = tx.tx.receivingAddress.contains(maybeReceiver) + def asSigned(tx: SignedTransaction) = tx + } + } +} diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala index 8401c63a90..bc603f37c7 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala @@ -26,12 +26,13 @@ import io.iohk.ethereum.mpt.{ByteArrayEncoder, ByteArraySerializable, MerklePatr import io.iohk.ethereum.nodebuilder.ApisBuilder import io.iohk.ethereum.ommers.OmmersPool import io.iohk.ethereum.testing.ActorsTesting.simpleAutoPilot -import io.iohk.ethereum.transactions.PendingTransactionsManager +import io.iohk.ethereum.transactions.{PendingTransactionsManager, TransactionHistoryService} import io.iohk.ethereum.transactions.PendingTransactionsManager.{ GetPendingTransactions, PendingTransaction, PendingTransactionsResponse } +import io.iohk.ethereum.utils.ByteStringUtils.hash2string import io.iohk.ethereum.utils._ import io.iohk.ethereum.{Fixtures, NormalPatience, Timeouts, WithActorSystemShutDown, crypto} import monix.execution.Scheduler.Implicits.global @@ -1123,7 +1124,7 @@ class EthServiceSpec .and(blockchain.storeBlock(blockWithTxs2and3)) .commit() - val request = GetAccountTransactionsRequest(address, 3125360, 3125370) + val request = GetAccountTransactionsRequest(address, BigInt(3125360) to BigInt(3125370)) val response = ethService.getAccountTransactions(request).runSyncUnsafe() pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions) @@ -1145,6 +1146,11 @@ class EthServiceSpec TransactionResponse(tx1, blockHeader = Some(blockWithTx1.header), pending = Some(false), isOutgoing = Some(false)) ) + assert( + response.map(_.transactions.map(hash2string _ compose (_.hash))) === Right( + expectedTxs.map(hash2string _ compose (_.hash)) + ) + ) response shouldEqual Right(GetAccountTransactionsResponse(expectedTxs)) } @@ -1160,7 +1166,7 @@ class EthServiceSpec val signedTx = SignedTransaction.sign(tx, keyPair, None) val pendingTx = PendingTransaction(signedTx, System.currentTimeMillis) - val request = GetAccountTransactionsRequest(signedTx.senderAddress, 3125371, 3125381) + val request = GetAccountTransactionsRequest(signedTx.senderAddress, BigInt(3125371) to BigInt(3125381)) val response = ethService.getAccountTransactions(request).runToFuture pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions) @@ -1173,7 +1179,7 @@ class EthServiceSpec } it should "send message to pendingTransactionsManager and return an empty GetPendingTransactionsResponse" in new TestSetup { - val res = ethService.getTransactionsFromPool().runSyncUnsafe() + val res = ethService.getTransactionsFromPool.runSyncUnsafe() pendingTransactionsManager.expectMsg(GetPendingTransactions) pendingTransactionsManager.reply(PendingTransactionsResponse(Nil)) @@ -1200,7 +1206,7 @@ class EthServiceSpec }) .toList - val res = ethService.getTransactionsFromPool().runToFuture + val res = ethService.getTransactionsFromPool.runToFuture pendingTransactionsManager.expectMsg(GetPendingTransactions) pendingTransactionsManager.reply(PendingTransactionsResponse(transactions)) @@ -1209,7 +1215,7 @@ class EthServiceSpec } it should "send message to pendingTransactionsManager and return an empty GetPendingTransactionsResponse in case of error" in new TestSetup { - val res = ethService.getTransactionsFromPool().runSyncUnsafe() + val res = ethService.getTransactionsFromPool.runSyncUnsafe() pendingTransactionsManager.expectMsg(GetPendingTransactions) pendingTransactionsManager.reply(new ClassCastException("error")) @@ -1238,6 +1244,9 @@ class EthServiceSpec val minerActiveTimeout: FiniteDuration = 5.seconds val getTransactionFromPoolTimeout: FiniteDuration = 5.seconds + val transactionHistoryService = + new TransactionHistoryService(blockchain, pendingTransactionsManager.ref, getTransactionFromPoolTimeout) + val filterConfig = new FilterConfig { override val filterTimeout: FiniteDuration = Timeouts.normalTimeout override val filterManagerQueryTimeout: FiniteDuration = Timeouts.normalTimeout @@ -1272,6 +1281,7 @@ class EthServiceSpec syncingController.ref, ommersPool.ref, filterManager.ref, + transactionHistoryService, filterConfig, blockchainConfig, currentProtocolVersion, diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala index 761da5cc43..99f0bacdf7 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala @@ -16,11 +16,13 @@ import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpc import io.iohk.ethereum.keystore.KeyStore import io.iohk.ethereum.ledger.{BloomFilter, Ledger, StxLedger} import io.iohk.ethereum.nodebuilder.ApisBuilder +import io.iohk.ethereum.transactions.TransactionHistoryService import io.iohk.ethereum.utils.{Config, FilterConfig} import io.iohk.ethereum.{Fixtures, ObjectGenerators, Timeouts} import org.bouncycastle.util.encoders.Hex import org.json4s.JsonAST.{JArray, JInt, JString, JValue} import org.scalamock.scalatest.MockFactory + import scala.concurrent.duration._ class JsonRpcControllerFixture(implicit system: ActorSystem) @@ -74,6 +76,8 @@ class JsonRpcControllerFixture(implicit system: ActorSystem) val debugService = mock[DebugService] val qaService = mock[QAService] val checkpointingService = mock[CheckpointingService] + val transactionHistoryService = + new TransactionHistoryService(blockchain, pendingTransactionsManager.ref, getTransactionFromPoolTimeout) val ethService = new EthService( blockchain, @@ -84,6 +88,7 @@ class JsonRpcControllerFixture(implicit system: ActorSystem) syncingController.ref, ommersPool.ref, filterManager.ref, + transactionHistoryService, filterConfig, blockchainConfig, currentProtocolVersion, From 5f006321e9ff27f2fde11e8420b7d6c1e94f9767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Kope=C4=87?= Date: Fri, 20 Nov 2020 09:42:52 +0100 Subject: [PATCH 2/7] [ETCM-135] Move transaction history service tests --- .../jsonrpc/EthJsonMethodsImplicits.scala | 30 ++++- .../io/iohk/ethereum/jsonrpc/EthService.scala | 4 +- .../jsonrpc/TransactionResponse.scala | 18 +-- .../jsonrpc/serialization/JsonEncoder.scala | 9 +- .../TransactionHistoryService.scala | 96 ++++++++------- .../ethereum/jsonrpc/EthServiceSpec.scala | 114 +++++++----------- .../jsonrpc/JsonRpcControllerSpec.scala | 49 +++++--- .../TransactionHistoryServiceSpec.scala | 95 +++++++++++++++ .../PendingTransactionsManagerAutoPilot.scala | 55 +++++++++ 9 files changed, 314 insertions(+), 156 deletions(-) create mode 100644 src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala create mode 100644 src/test/scala/io/iohk/ethereum/transactions/testing/PendingTransactionsManagerAutoPilot.scala diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala index d4755e9136..ea783dd7e9 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala @@ -8,10 +8,14 @@ import io.iohk.ethereum.jsonrpc.serialization.JsonMethodDecoder.NoParamsMethodDe import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec, JsonMethodDecoder} import org.json4s.JsonAST.{JArray, JBool, JString, JValue, _} import org.json4s.JsonDSL._ -import org.json4s.{Extraction, JsonAST} +import org.json4s.{Extraction, JsonAST, Merge} +import JsonEncoder.OptionToNull._ +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import org.json4s // scalastyle:off number.of.methods object EthJsonMethodsImplicits extends JsonMethodsImplicits { + implicit val transactionResponseJsonEncoder: JsonEncoder[TransactionResponse] = Extraction.decompose(_) implicit val eth_protocolVersion = new NoParamsMethodDecoder(ProtocolVersionRequest()) with JsonEncoder[ProtocolVersionResponse] { @@ -148,7 +152,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { } override def encodeJson(t: GetTransactionByHashResponse): JValue = - Extraction.decompose(t.txResponse) + JsonEncoder.encode(t.txResponse) } implicit val eth_getTransactionReceipt = @@ -169,7 +173,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { implicit val GetTransactionByBlockHashAndIndexResponseEncoder = new JsonEncoder[GetTransactionByBlockHashAndIndexResponse] { override def encodeJson(t: GetTransactionByBlockHashAndIndexResponse): JValue = - t.transactionResponse.map(Extraction.decompose).getOrElse(JNull) + JsonEncoder.encode(t.transactionResponse) } implicit val GetTransactionByBlockHashAndIndexRequestDecoder = @@ -188,7 +192,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { implicit val GetTransactionByBlockNumberAndIndexResponseEncoder = new JsonEncoder[GetTransactionByBlockNumberAndIndexResponse] { override def encodeJson(t: GetTransactionByBlockNumberAndIndexResponse): JValue = - t.transactionResponse.map(Extraction.decompose).getOrElse(JNull) + JsonEncoder.encode(t.transactionResponse) } implicit val GetTransactionByBlockNumberAndIndexRequestDecoder = @@ -575,6 +579,22 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { ) } + implicit val extendedTransactionDataJsonEncoder: JsonEncoder[ExtendedTransactionData] = extendedTxData => { + val asTxResponse = TransactionResponse( + extendedTxData.stx, + extendedTxData.minedTransactionData.map(_._1), + extendedTxData.minedTransactionData.map(_._2) + ) + + val encodedTxResponse = JsonEncoder.encode(asTxResponse) + val encodedExtension = JObject( + "isOutgoing" -> JBool(extendedTxData.isOutgoing), + "isPending" -> JBool(extendedTxData.isPending) + ) + + Merge.merge(encodedTxResponse, encodedExtension) + } + implicit val daedalus_getAccountTransactions = new JsonMethodDecoder[GetAccountTransactionsRequest] with JsonEncoder[GetAccountTransactionsResponse] { def decodeJson(params: Option[JArray]): Either[JsonRpcError, GetAccountTransactionsRequest] = @@ -589,7 +609,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { } override def encodeJson(t: GetAccountTransactionsResponse): JValue = - JObject("transactions" -> JArray(t.transactions.map(Extraction.decompose).toList)) + JObject("transactions" -> JsonEncoder.encode(t.transactions)) } implicit val eth_getStorageRoot = new JsonMethodDecoder[GetStorageRootRequest] diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala index 65db0550ae..32a19c6920 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala @@ -3,7 +3,6 @@ package io.iohk.ethereum.jsonrpc import java.time.Duration import java.util.Date import java.util.concurrent.atomic.AtomicReference - import akka.actor.ActorRef import akka.util.{ByteString, Timeout} import cats.syntax.either._ @@ -30,6 +29,7 @@ import io.iohk.ethereum.rlp.RLPImplicits._ import io.iohk.ethereum.rlp.RLPList import io.iohk.ethereum.rlp.UInt256RLPImplicits._ import io.iohk.ethereum.transactions.PendingTransactionsManager.{PendingTransaction, PendingTransactionsResponse} +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData import io.iohk.ethereum.transactions.{PendingTransactionsManager, TransactionHistoryService} import io.iohk.ethereum.utils._ import monix.eval.Task @@ -82,7 +82,7 @@ object EthService { case class GetTransactionByHashResponse(txResponse: Option[TransactionResponse]) case class GetAccountTransactionsRequest(address: Address, blocksRange: NumericRange[BigInt]) - case class GetAccountTransactionsResponse(transactions: Seq[TransactionResponse]) + case class GetAccountTransactionsResponse(transactions: List[ExtendedTransactionData]) case class GetTransactionReceiptRequest(txHash: ByteString) case class GetTransactionReceiptResponse(txResponse: Option[TransactionReceiptResponse]) diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/TransactionResponse.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/TransactionResponse.scala index 1c6410ac05..4e1c277454 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/TransactionResponse.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/TransactionResponse.scala @@ -14,30 +14,24 @@ final case class TransactionResponse( value: BigInt, gasPrice: BigInt, gas: BigInt, - input: ByteString, - pending: Option[Boolean], - isOutgoing: Option[Boolean] + input: ByteString ) final case class TransactionData( stx: SignedTransaction, blockHeader: Option[BlockHeader] = None, - transactionIndex: Option[Int] = None, - pending: Option[Boolean] = None, - isOutgoing: Option[Boolean] = None + transactionIndex: Option[Int] = None ) object TransactionResponse { def apply(tx: TransactionData): TransactionResponse = - TransactionResponse(tx.stx, tx.blockHeader, tx.transactionIndex, tx.pending, tx.isOutgoing) + TransactionResponse(tx.stx, tx.blockHeader, tx.transactionIndex) def apply( stx: SignedTransaction, blockHeader: Option[BlockHeader] = None, - transactionIndex: Option[Int] = None, - pending: Option[Boolean] = None, - isOutgoing: Option[Boolean] = None + transactionIndex: Option[Int] = None ): TransactionResponse = TransactionResponse( hash = stx.hash, @@ -50,9 +44,7 @@ object TransactionResponse { value = stx.tx.value, gasPrice = stx.tx.gasPrice, gas = stx.tx.gasLimit, - input = stx.tx.payload, - pending = pending, - isOutgoing = isOutgoing + input = stx.tx.payload ) } diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala index 056a53f04c..427bd1853c 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala @@ -18,8 +18,11 @@ object JsonEncoder { implicit def listEncoder[T](implicit itemEncoder: JsonEncoder[T]): JsonEncoder[List[T]] = list => JArray(list.map(itemEncoder.encodeJson)) - def optionToNullEncoder[T](implicit valueEncoder: JsonEncoder[T]): JsonEncoder[Option[T]] = { - case Some(value) => valueEncoder.encodeJson(value) - case None => JNull + trait OptionToNull { + implicit def optionToNullEncoder[T](implicit valueEncoder: JsonEncoder[T]): JsonEncoder[Option[T]] = { + case Some(value) => valueEncoder.encodeJson(value) + case None => JNull + } } + object OptionToNull extends OptionToNull } diff --git a/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala index 68ccee6542..0fa46572c3 100644 --- a/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala +++ b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala @@ -1,12 +1,12 @@ package io.iohk.ethereum.transactions import akka.actor.ActorRef +import cats.implicits._ import akka.util.Timeout -import io.iohk.ethereum.domain.{Address, BlockHeader, Blockchain, SignedTransaction} +import io.iohk.ethereum.domain.{Address, Block, BlockHeader, Blockchain, SignedTransaction} import io.iohk.ethereum.jsonrpc.AkkaTaskOps.TaskActorOps -import io.iohk.ethereum.jsonrpc.TransactionResponse import io.iohk.ethereum.transactions.PendingTransactionsManager.PendingTransaction -import io.iohk.ethereum.transactions.TransactionHistoryService.TxChecker +import io.iohk.ethereum.transactions.TransactionHistoryService.{ExtendedTransactionData, TxChecker} import io.iohk.ethereum.utils.Logger import monix.eval.Task import monix.reactive.{Observable, OverflowStrategy} @@ -19,14 +19,17 @@ class TransactionHistoryService( pendingTransactionsManager: ActorRef, getTransactionFromPoolTimeout: FiniteDuration ) extends Logger { - def getAccountTransactions(account: Address, fromBlocks: NumericRange[BigInt]): Task[Seq[TransactionResponse]] = { + def getAccountTransactions( + account: Address, + fromBlocks: NumericRange[BigInt] + ): Task[List[ExtendedTransactionData]] = { val txnsFromBlocks = Observable .from(fromBlocks.reverse) .mapParallelOrdered(10)(blockNr => Task { blockchain.getBlockByNumber(blockNr) })(OverflowStrategy.Unbounded) .collect { case Some(block) => block } .concatMapIterable { block => - val checker = TxChecker.forSigned(block.header) - block.body.transactionList.toVector + val checker = TxChecker.forSigned(block) + block.body.transactionList.toList .collect(Function.unlift(checker.checkTx(_, account))) .reverse } @@ -37,68 +40,73 @@ class TransactionHistoryService( .collect(Function.unlift(TxChecker.forPending.checkTx(_, account))) } - Task.parMap2(txnsFromBlocks, txnsFromMempool)((pending, fromBlocks) => (pending ++ fromBlocks)) + Task.parMap2(txnsFromBlocks, txnsFromMempool)(_ ++ _) } - private val getTransactionsFromPool: Task[Vector[PendingTransaction]] = { + private val getTransactionsFromPool: Task[List[PendingTransaction]] = { implicit val timeout: Timeout = getTransactionFromPoolTimeout pendingTransactionsManager .askFor[PendingTransactionsManager.PendingTransactionsResponse](PendingTransactionsManager.GetPendingTransactions) - .map(_.pendingTransactions.toVector) + .map(_.pendingTransactions.toList) .onErrorRecoverWith { case ex: Throwable => log.error("Failed to get pending transactions, passing empty transactions list", ex) - Task.now(Vector.empty) + Task.now(List.empty) } } } object TransactionHistoryService { - trait TxChecker[T] { - def isTxPending: Boolean - def maybeBlockHeader: Option[BlockHeader] - def isSender(tx: T, address: Address): Boolean - def isReceiver(tx: T, address: Address): Boolean - def asSigned(tx: T): SignedTransaction + case class ExtendedTransactionData( + stx: SignedTransaction, + isOutgoing: Boolean, + //block header and transaction index + minedTransactionData: Option[(BlockHeader, Int)] + ) { + val isPending: Boolean = minedTransactionData.isEmpty + } - def checkTx(tx: T, address: Address): Option[TransactionResponse] = { - if (isSender(tx, address)) { - Some( - TransactionResponse( - asSigned(tx), - maybeBlockHeader, - pending = Some(isTxPending), - isOutgoing = Some(true) - ) - ) - } else if (isReceiver(tx, address)) { - Some( - TransactionResponse( - asSigned(tx), - maybeBlockHeader, - pending = Some(isTxPending), - isOutgoing = Some(false) - ) - ) - } else { - None - } - } + trait TxChecker[T] { + def checkTx(tx: T, address: Address): Option[ExtendedTransactionData] } object TxChecker { val forPending: TxChecker[PendingTransaction] = new TxChecker[PendingTransaction] { - val isTxPending = true - val maybeBlockHeader = None def isSender(tx: PendingTransaction, maybeSender: Address) = tx.stx.senderAddress == maybeSender def isReceiver(tx: PendingTransaction, maybeReceiver: Address) = tx.stx.tx.tx.receivingAddress.contains(maybeReceiver) def asSigned(tx: PendingTransaction) = tx.stx.tx + + def checkTx(tx: PendingTransaction, address: Address): Option[ExtendedTransactionData] = { + if (isSender(tx, address)) { + Some(ExtendedTransactionData(asSigned(tx), isOutgoing = true, None)) + } else if (isReceiver(tx, address)) { + Some(ExtendedTransactionData(asSigned(tx), isOutgoing = false, None)) + } else { + None + } + } } - def forSigned(blockHeader: BlockHeader): TxChecker[SignedTransaction] = new TxChecker[SignedTransaction] { - val isTxPending = false - val maybeBlockHeader = Some(blockHeader) + def forSigned(block: Block): TxChecker[SignedTransaction] = new TxChecker[SignedTransaction] { def isSender(tx: SignedTransaction, maybeSender: Address) = tx.safeSenderIsEqualTo(maybeSender) def isReceiver(tx: SignedTransaction, maybeReceiver: Address) = tx.tx.receivingAddress.contains(maybeReceiver) def asSigned(tx: SignedTransaction) = tx + + def getMinedTxData(tx: SignedTransaction): Option[(BlockHeader, Int)] = { + val maybeIndex = block.body.transactionList.zipWithIndex.collectFirst { + case (someTx, index) if someTx.hash == tx.hash => index + } + + (Some(block.header), maybeIndex).tupled + } + + def checkTx(tx: SignedTransaction, address: Address): Option[ExtendedTransactionData] = { + if (isSender(tx, address)) { + Some(ExtendedTransactionData(asSigned(tx), isOutgoing = true, getMinedTxData(tx))) + } else if (isReceiver(tx, address)) { + Some(ExtendedTransactionData(asSigned(tx), isOutgoing = false, getMinedTxData(tx))) + } else { + None + } + } } } } diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala index bc603f37c7..25f9bca998 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala @@ -1,7 +1,6 @@ package io.iohk.ethereum.jsonrpc import java.security.SecureRandom - import akka.actor.ActorSystem import akka.testkit.{TestKit, TestProbe} import akka.util.ByteString @@ -32,9 +31,11 @@ import io.iohk.ethereum.transactions.PendingTransactionsManager.{ PendingTransaction, PendingTransactionsResponse } +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData import io.iohk.ethereum.utils.ByteStringUtils.hash2string import io.iohk.ethereum.utils._ -import io.iohk.ethereum.{Fixtures, NormalPatience, Timeouts, WithActorSystemShutDown, crypto} +import io.iohk.ethereum.{BlockHelpers, Fixtures, NormalPatience, Timeouts, WithActorSystemShutDown, crypto} +import monix.eval.Task import monix.execution.Scheduler.Implicits.global import org.bouncycastle.util.encoders.Hex import org.scalactic.TypeCheckedTripleEquals @@ -44,6 +45,7 @@ import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers +import scala.collection.immutable.NumericRange import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration} // scalastyle:off file.size.limit @@ -1100,82 +1102,50 @@ class EthServiceSpec ) } - it should "return account recent transactions in newest -> oldest order" in new TestSetup { - (ledger.consensus _: (() => Consensus)).expects().returns(consensus) - - val address = Address("0xee4439beb5c71513b080bbf9393441697a29f478") - - val keyPair = crypto.generateKeyPair(new SecureRandom) - - val tx1 = SignedTransaction.sign(Transaction(0, 123, 456, Some(address), 1, ByteString()), keyPair, None).tx - val tx2 = SignedTransaction.sign(Transaction(0, 123, 456, Some(address), 2, ByteString()), keyPair, None).tx - val tx3 = SignedTransaction.sign(Transaction(0, 123, 456, Some(address), 3, ByteString()), keyPair, None).tx - - val blockWithTx1 = - Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx1))) - - val blockWithTxs2and3 = Block( - Fixtures.Blocks.Block3125369.header.copy(number = 3125370), - Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx2, tx3)) - ) - - blockchain - .storeBlock(blockWithTx1) - .and(blockchain.storeBlock(blockWithTxs2and3)) - .commit() - - val request = GetAccountTransactionsRequest(address, BigInt(3125360) to BigInt(3125370)) - - val response = ethService.getAccountTransactions(request).runSyncUnsafe() - pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions) - pendingTransactionsManager.reply(PendingTransactionsResponse(Nil)) - - val expectedTxs = Seq( - TransactionResponse( - tx3, - blockHeader = Some(blockWithTxs2and3.header), - pending = Some(false), - isOutgoing = Some(false) - ), - TransactionResponse( - tx2, - blockHeader = Some(blockWithTxs2and3.header), - pending = Some(false), - isOutgoing = Some(false) + it should "get account's transaction history" in new TestSetup { + val fakeTransaction = SignedTransactionWithSender( + Transaction( + nonce = 0, + gasPrice = 123, + gasLimit = 123, + receivingAddress = Address("0x1234"), + value = 0, + payload = ByteString() ), - TransactionResponse(tx1, blockHeader = Some(blockWithTx1.header), pending = Some(false), isOutgoing = Some(false)) + signature = ECDSASignature(0, 0, 0.toByte), + sender = Address("0x1234") ) - assert( - response.map(_.transactions.map(hash2string _ compose (_.hash))) === Right( - expectedTxs.map(hash2string _ compose (_.hash)) + override val block = + BlockHelpers.generateBlock(BlockHelpers.genesis).copy(body = BlockBody(List(fakeTransaction.tx), Nil)) + + val expectedResponse = List( + ExtendedTransactionData( + fakeTransaction.tx, + isOutgoing = true, + Some((block.header, 0)) ) ) - response shouldEqual Right(GetAccountTransactionsResponse(expectedTxs)) - } - - it should "not return account recent transactions from older blocks and return pending txs" in new TestSetup { - (ledger.consensus _: (() => Consensus)).expects().returns(consensus) - - val blockWithTx = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body) - blockchain.storeBlock(blockWithTx).commit() - - val keyPair = crypto.generateKeyPair(new SecureRandom) - - val tx = Transaction(0, 123, 456, None, 99, ByteString()) - val signedTx = SignedTransaction.sign(tx, keyPair, None) - val pendingTx = PendingTransaction(signedTx, System.currentTimeMillis) - val request = GetAccountTransactionsRequest(signedTx.senderAddress, BigInt(3125371) to BigInt(3125381)) + override lazy val transactionHistoryService = + new TransactionHistoryService(blockchain, pendingTransactionsManager.ref, getTransactionFromPoolTimeout) { + override def getAccountTransactions(account: Address, fromBlocks: NumericRange[BigInt]) = + Task.pure(expectedResponse) + } - val response = ethService.getAccountTransactions(request).runToFuture - pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions) - pendingTransactionsManager.reply(PendingTransactionsResponse(Seq(pendingTx))) - - val expectedSent = - Seq(TransactionResponse(signedTx.tx, blockHeader = None, pending = Some(true), isOutgoing = Some(true))) + ethService + .getAccountTransactions(GetAccountTransactionsRequest(fakeTransaction.senderAddress, BigInt(0) to BigInt(1))) + .map(result => assert(result === Right(GetAccountTransactionsResponse(expectedResponse)))) + .runSyncUnsafe() + } - response.futureValue shouldEqual Right(GetAccountTransactionsResponse(expectedSent)) + it should "validate range size against configuration" in new TestSetup { + ethService + .getAccountTransactions( + GetAccountTransactionsRequest(Address(1), BigInt(0) to BigInt(jsonRpcConfig.accountTransactionsMaxBlocks + 1)) + ) + .map(result => assert(result.isLeft)) + .runSyncUnsafe() } it should "send message to pendingTransactionsManager and return an empty GetPendingTransactionsResponse" in new TestSetup { @@ -1244,7 +1214,7 @@ class EthServiceSpec val minerActiveTimeout: FiniteDuration = 5.seconds val getTransactionFromPoolTimeout: FiniteDuration = 5.seconds - val transactionHistoryService = + lazy val transactionHistoryService = new TransactionHistoryService(blockchain, pendingTransactionsManager.ref, getTransactionFromPoolTimeout) val filterConfig = new FilterConfig { @@ -1272,7 +1242,7 @@ class EthServiceSpec val jsonRpcConfig = JsonRpcConfig(Config.config, available) - val ethService = new EthService( + lazy val ethService = new EthService( blockchain, ledger, stxLedger, diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala index b467d4dcab..5fccc47197 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala @@ -17,12 +17,11 @@ import io.iohk.ethereum.jsonrpc.server.ipc.JsonRpcIpcServer import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo import io.iohk.ethereum.network.p2p.messages.CommonMessages.Status import io.iohk.ethereum.network.p2p.messages.Versions +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData import io.iohk.ethereum.{Fixtures, LongPatience, WithActorSystemShutDown} import monix.eval.Task import monix.execution.Scheduler.Implicits.global -import org.json4s.JsonAST._ -import org.json4s.JsonDSL._ -import org.json4s.{DefaultFormats, Extraction, Formats} +import org.json4s.{DefaultFormats, Extraction, Formats, JArray, JBool, JInt, JObject, JString} import org.scalatest.concurrent.{Eventually, ScalaFutures} import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers @@ -152,15 +151,15 @@ class JsonRpcControllerSpec response should haveResult( JObject( - "net" -> "1.0", - "rpc" -> "1.0", - "personal" -> "1.0", - "eth" -> "1.0", - "web3" -> "1.0", - "daedalus" -> "1.0", - "debug" -> "1.0", - "qa" -> "1.0", - "checkpointing" -> "1.0" + "net" -> JString("1.0"), + "rpc" -> JString("1.0"), + "personal" -> JString("1.0"), + "eth" -> JString("1.0"), + "web3" -> JString("1.0"), + "daedalus" -> JString("1.0"), + "debug" -> JString("1.0"), + "qa" -> JString("1.0"), + "checkpointing" -> JString("1.0") ) ) } @@ -179,9 +178,9 @@ class JsonRpcControllerSpec Task.now( Right( GetAccountTransactionsResponse( - Seq( - TransactionResponse(sentTx, Some(block.header), isOutgoing = Some(true)), - TransactionResponse(receivedTx, Some(block.header), isOutgoing = Some(false)) + List( + ExtendedTransactionData(sentTx, isOutgoing = true, Some((block.header, 0))), + ExtendedTransactionData(receivedTx, isOutgoing = false, Some((block.header, 1))) ) ) ) @@ -199,8 +198,24 @@ class JsonRpcControllerSpec val response = jsonRpcController.handleRequest(request).runSyncUnsafe() val expectedTxs = Seq( - Extraction.decompose(TransactionResponse(sentTx, Some(block.header), isOutgoing = Some(true))), - Extraction.decompose(TransactionResponse(receivedTx, Some(block.header), isOutgoing = Some(false))) + JObject( + Extraction + .decompose(TransactionResponse(sentTx, Some(block.header), Some(0))) + .asInstanceOf[JObject] + .obj ++ List( + "isPending" -> JBool(false), + "isOutgoing" -> JBool(true) + ) + ), + JObject( + Extraction + .decompose(TransactionResponse(receivedTx, Some(block.header), Some(1))) + .asInstanceOf[JObject] + .obj ++ List( + "isPending" -> JBool(false), + "isOutgoing" -> JBool(false) + ) + ) ) response should haveObjectResult("transactions" -> JArray(expectedTxs.toList)) diff --git a/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala b/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala new file mode 100644 index 0000000000..9caa03c1d6 --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala @@ -0,0 +1,95 @@ +package io.iohk.ethereum.transactions + +import akka.actor.ActorSystem +import akka.testkit.{TestKit, TestProbe} +import akka.util.ByteString +import io.iohk.ethereum._ +import io.iohk.ethereum.blockchain.sync.EphemBlockchainTestSetup +import io.iohk.ethereum.domain.{Address, Block, SignedTransaction, Transaction} +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import io.iohk.ethereum.transactions.testing.PendingTransactionsManagerAutoPilot +import monix.eval.Task + +import java.security.SecureRandom + +class TransactionHistoryServiceSpec + extends TestKit(ActorSystem("TransactionHistoryServiceSpec-system")) + with FreeSpecBase + with SpecFixtures + with WithActorSystemShutDown { + class Fixture extends EphemBlockchainTestSetup { + val pendingTransactionManager = TestProbe() + pendingTransactionManager.setAutoPilot(PendingTransactionsManagerAutoPilot()) + val transactionHistoryService = + new TransactionHistoryService(blockchain, pendingTransactionManager.ref, Timeouts.normalTimeout) + } + + def createFixture() = new Fixture + + "returns account recent transactions in newest -> oldest order" in testCaseM { fixture => + import fixture._ + + val address = Address("0xee4439beb5c71513b080bbf9393441697a29f478") + + val keyPair = crypto.generateKeyPair(new SecureRandom) + + val tx1 = SignedTransaction.sign(Transaction(0, 123, 456, Some(address), 1, ByteString()), keyPair, None).tx + val tx2 = SignedTransaction.sign(Transaction(0, 123, 456, Some(address), 2, ByteString()), keyPair, None).tx + val tx3 = SignedTransaction.sign(Transaction(0, 123, 456, Some(address), 3, ByteString()), keyPair, None).tx + + val blockWithTx1 = + Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx1))) + + val blockWithTxs2and3 = Block( + Fixtures.Blocks.Block3125369.header.copy(number = 3125370), + Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx2, tx3)) + ) + + val expectedTxs = Seq( + ExtendedTransactionData( + tx3, + isOutgoing = false, + Some(blockWithTxs2and3.header -> 1) + ), + ExtendedTransactionData( + tx2, + isOutgoing = false, + Some(blockWithTxs2and3.header -> 0) + ), + ExtendedTransactionData(tx1, isOutgoing = false, Some(blockWithTx1.header -> 0)) + ) + + for { + _ <- Task { + blockchain + .storeBlock(blockWithTx1) + .and(blockchain.storeBlock(blockWithTxs2and3)) + .commit() + } + response <- transactionHistoryService.getAccountTransactions(address, BigInt(3125360) to BigInt(3125370)) + } yield assert(response === expectedTxs) + } + + "does not return account recent transactions from older blocks and return pending txs" in testCaseM { fixture => + import fixture._ + + val blockWithTx = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body) + + val keyPair = crypto.generateKeyPair(new SecureRandom) + + val tx = Transaction(0, 123, 456, None, 99, ByteString()) + val signedTx = SignedTransaction.sign(tx, keyPair, None) + + val expectedSent = + Seq(ExtendedTransactionData(signedTx.tx, isOutgoing = true, None)) + + for { + _ <- Task { blockchain.storeBlock(blockWithTx).commit() } + _ <- Task { pendingTransactionManager.ref ! PendingTransactionsManager.AddTransactions(signedTx) } + response <- transactionHistoryService.getAccountTransactions( + signedTx.senderAddress, + BigInt(3125371) to BigInt(3125381) + ) + } yield assert(response === expectedSent) + } +} diff --git a/src/test/scala/io/iohk/ethereum/transactions/testing/PendingTransactionsManagerAutoPilot.scala b/src/test/scala/io/iohk/ethereum/transactions/testing/PendingTransactionsManagerAutoPilot.scala new file mode 100644 index 0000000000..328862aa64 --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/transactions/testing/PendingTransactionsManagerAutoPilot.scala @@ -0,0 +1,55 @@ +package io.iohk.ethereum.transactions.testing +import akka.actor.ActorRef +import akka.testkit.TestActor.AutoPilot +import akka.util.ByteString +import io.iohk.ethereum.domain.{SignedTransaction, SignedTransactionWithSender} +import io.iohk.ethereum.transactions.PendingTransactionsManager._ +import io.iohk.ethereum.transactions.SignedTransactionsFilterActor.ProperSignedTransactions + +case class PendingTransactionsManagerAutoPilot(pendingTransactions: Set[PendingTransaction] = Set.empty) + extends AutoPilot { + def run(sender: ActorRef, msg: Any) = { + msg match { + case AddUncheckedTransactions(transactions) => + val validTxs = SignedTransactionWithSender.getSignedTransactions(transactions) + this.addTransactions(validTxs.toSet) + + case AddTransactions(signedTransactions) => + this.addTransactions(signedTransactions) + + case AddOrOverrideTransaction(newStx) => + // Only validated transactions are added this way, it is safe to call get + val newStxSender = SignedTransaction.getSender(newStx).get + val obsoleteTxs = pendingTransactions + .filter(ptx => ptx.stx.senderAddress == newStxSender && ptx.stx.tx.tx.nonce == newStx.tx.nonce) + .map(_.stx.tx.hash) + + removeTransactions(obsoleteTxs).addTransactions(Set(SignedTransactionWithSender(newStx, newStxSender))) + + case GetPendingTransactions => + sender ! PendingTransactionsResponse(pendingTransactions.toSeq) + this + + case RemoveTransactions(signedTransactions) => + this.removeTransactions(signedTransactions.map(_.hash).toSet) + + case ProperSignedTransactions(transactions, peerId) => + this.addTransactions(transactions) + + case ClearPendingTransactions => + copy(pendingTransactions = Set.empty) + } + } + + def addTransactions(signedTransactions: Set[SignedTransactionWithSender]) = { + val timestamp = System.currentTimeMillis() + val stxs = pendingTransactions.map(_.stx) + val transactionsToAdd = signedTransactions.diff(stxs).map(tx => PendingTransaction(tx, timestamp)) + + copy(pendingTransactions ++ transactionsToAdd) + } + + def removeTransactions(hashes: Set[ByteString]) = { + copy(pendingTransactions.filterNot(ptx => hashes.contains(ptx.stx.tx.hash))) + } +} From fca89cf4d44040ee710c25bf2543ccfdd34dfb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Kope=C4=87?= Date: Fri, 20 Nov 2020 11:21:24 +0100 Subject: [PATCH 3/7] [ETCM-135] Move getAccountTransactions into mantis namespace --- src/main/resources/application.conf | 8 +- .../jsonrpc/EthJsonMethodsImplicits.scala | 39 +------ .../io/iohk/ethereum/jsonrpc/EthService.scala | 33 +----- .../ethereum/jsonrpc/JsonRpcController.scala | 11 +- .../jsonrpc/MantisJsonMethodImplicits.scala | 45 ++++++++ .../iohk/ethereum/jsonrpc/MantisService.scala | 35 ++++++ .../ethereum/nodebuilder/NodeBuilder.scala | 42 ++++--- src/test/resources/application.conf | 2 +- .../sync/EphemBlockchainTestSetup.scala | 2 +- .../jsonrpc/CheckpointingJRCSpec.scala | 3 + .../ethereum/jsonrpc/EthServiceSpec.scala | 50 -------- .../JsonRpcControllerEthTransactionSpec.scala | 2 + .../jsonrpc/JsonRpcControllerFixture.scala | 5 +- .../jsonrpc/JsonRpcControllerSpec.scala | 60 +--------- .../iohk/ethereum/jsonrpc/MantisJRCSpec.scala | 108 ++++++++++++++++++ .../ethereum/jsonrpc/MantisServiceSpec.scala | 100 ++++++++++++++++ .../io/iohk/ethereum/jsonrpc/QaJRCSpec.scala | 4 +- src/universal/conf/testmode.conf | 2 +- 18 files changed, 347 insertions(+), 204 deletions(-) create mode 100644 src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala create mode 100644 src/main/scala/io/iohk/ethereum/jsonrpc/MantisService.scala create mode 100644 src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala create mode 100644 src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index c599b498fb..bd28bfcd33 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -207,11 +207,11 @@ mantis { } # Enabled JSON-RPC APIs over the JSON-RPC endpoint - # Available choices are: web3, eth, net, personal, daedalus, test, iele, debug, qa, checkpointing - apis = "eth,web3,net,personal,daedalus,debug,qa,checkpointing" + # Available choices are: web3, eth, net, personal, mantis, test, iele, debug, qa, checkpointing + apis = "eth,web3,net,personal,mantis,debug,qa,checkpointing" - # Maximum number of blocks for daedalus_getAccountTransactions - account-transactions-max-blocks = 50000 + # Maximum number of blocks for mantis_getAccountTransactions + account-transactions-max-blocks = 1000 net { peer-manager-timeout = 5.seconds diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala index ea783dd7e9..8538718167 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala @@ -4,14 +4,12 @@ import akka.util.ByteString import io.iohk.ethereum.jsonrpc.EthService._ import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams import io.iohk.ethereum.jsonrpc.PersonalService.{SendTransactionRequest, SendTransactionResponse, SignRequest} +import io.iohk.ethereum.jsonrpc.serialization.JsonEncoder.OptionToNull._ import io.iohk.ethereum.jsonrpc.serialization.JsonMethodDecoder.NoParamsMethodDecoder import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec, JsonMethodDecoder} import org.json4s.JsonAST.{JArray, JBool, JString, JValue, _} import org.json4s.JsonDSL._ -import org.json4s.{Extraction, JsonAST, Merge} -import JsonEncoder.OptionToNull._ -import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData -import org.json4s +import org.json4s.{Extraction, JsonAST} // scalastyle:off number.of.methods object EthJsonMethodsImplicits extends JsonMethodsImplicits { @@ -579,39 +577,6 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { ) } - implicit val extendedTransactionDataJsonEncoder: JsonEncoder[ExtendedTransactionData] = extendedTxData => { - val asTxResponse = TransactionResponse( - extendedTxData.stx, - extendedTxData.minedTransactionData.map(_._1), - extendedTxData.minedTransactionData.map(_._2) - ) - - val encodedTxResponse = JsonEncoder.encode(asTxResponse) - val encodedExtension = JObject( - "isOutgoing" -> JBool(extendedTxData.isOutgoing), - "isPending" -> JBool(extendedTxData.isPending) - ) - - Merge.merge(encodedTxResponse, encodedExtension) - } - - implicit val daedalus_getAccountTransactions = - new JsonMethodDecoder[GetAccountTransactionsRequest] with JsonEncoder[GetAccountTransactionsResponse] { - def decodeJson(params: Option[JArray]): Either[JsonRpcError, GetAccountTransactionsRequest] = - params match { - case Some(JArray(JString(addrJson) :: fromBlockJson :: toBlockJson :: Nil)) => - for { - addr <- extractAddress(addrJson) - fromBlock <- extractQuantity(fromBlockJson) - toBlock <- extractQuantity(toBlockJson) - } yield GetAccountTransactionsRequest(addr, fromBlock to toBlock) - case _ => Left(InvalidParams()) - } - - override def encodeJson(t: GetAccountTransactionsResponse): JValue = - JObject("transactions" -> JsonEncoder.encode(t.transactions)) - } - implicit val eth_getStorageRoot = new JsonMethodDecoder[GetStorageRootRequest] with JsonEncoder[GetStorageRootResponse] { def decodeJson(params: Option[JArray]): Either[JsonRpcError, GetStorageRootRequest] = diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala index 32a19c6920..3c8814dd0b 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala @@ -1,8 +1,5 @@ package io.iohk.ethereum.jsonrpc -import java.time.Duration -import java.util.Date -import java.util.concurrent.atomic.AtomicReference import akka.actor.ActorRef import akka.util.{ByteString, Timeout} import cats.syntax.either._ @@ -28,15 +25,16 @@ import io.iohk.ethereum.rlp.RLPImplicitConversions._ import io.iohk.ethereum.rlp.RLPImplicits._ import io.iohk.ethereum.rlp.RLPList import io.iohk.ethereum.rlp.UInt256RLPImplicits._ +import io.iohk.ethereum.transactions.PendingTransactionsManager import io.iohk.ethereum.transactions.PendingTransactionsManager.{PendingTransaction, PendingTransactionsResponse} -import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData -import io.iohk.ethereum.transactions.{PendingTransactionsManager, TransactionHistoryService} import io.iohk.ethereum.utils._ import monix.eval.Task import org.bouncycastle.util.encoders.Hex +import java.time.Duration +import java.util.Date +import java.util.concurrent.atomic.AtomicReference import scala.collection.concurrent.{TrieMap, Map => ConcurrentMap} -import scala.collection.immutable.NumericRange import scala.concurrent.duration.FiniteDuration import scala.language.existentials import scala.reflect.ClassTag @@ -81,9 +79,6 @@ object EthService { case class GetTransactionByHashRequest(txHash: ByteString) case class GetTransactionByHashResponse(txResponse: Option[TransactionResponse]) - case class GetAccountTransactionsRequest(address: Address, blocksRange: NumericRange[BigInt]) - case class GetAccountTransactionsResponse(transactions: List[ExtendedTransactionData]) - case class GetTransactionReceiptRequest(txHash: ByteString) case class GetTransactionReceiptResponse(txResponse: Option[TransactionReceiptResponse]) @@ -219,7 +214,6 @@ class EthService( syncingController: ActorRef, ommersPool: ActorRef, filterManager: ActorRef, - transactionHistoryService: TransactionHistoryService, filterConfig: FilterConfig, blockchainConfig: BlockchainConfig, protocolVersion: Int, @@ -931,25 +925,6 @@ class EthService( } } - def getAccountTransactions( - request: GetAccountTransactionsRequest - ): ServiceResponse[GetAccountTransactionsResponse] = { - if (request.blocksRange.length > jsonRpcConfig.accountTransactionsMaxBlocks) { - Task.now( - Left( - JsonRpcError.InvalidParams( - s"""Maximum number of blocks to search is ${jsonRpcConfig.accountTransactionsMaxBlocks}, requested: ${request.blocksRange.length}. - |See: 'mantis.network.rpc.account-transactions-max-blocks' config.""".stripMargin - ) - ) - ) - } else { - transactionHistoryService - .getAccountTransactions(request.address, request.blocksRange) - .map(GetAccountTransactionsResponse(_).asRight) - } - } - def getStorageRoot(req: GetStorageRootRequest): ServiceResponse[GetStorageRootResponse] = withAccount(req.address, req.block) { account => GetStorageRootResponse(account.storageRoot) diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcController.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcController.scala index a7ba9e4eaf..307ebe8aec 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcController.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcController.scala @@ -3,6 +3,7 @@ package io.iohk.ethereum.jsonrpc import io.iohk.ethereum.jsonrpc.CheckpointingService._ import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse} import io.iohk.ethereum.jsonrpc.EthService._ +import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse} import io.iohk.ethereum.jsonrpc.NetService._ import io.iohk.ethereum.jsonrpc.PersonalService._ import io.iohk.ethereum.jsonrpc.QAService.{ @@ -29,6 +30,7 @@ class JsonRpcController( debugService: DebugService, qaService: QAService, checkpointingService: CheckpointingService, + mantisService: MantisService, override val config: JsonRpcConfig ) extends ApisBuilder with Logger @@ -41,13 +43,14 @@ class JsonRpcController( import JsonMethodsImplicits._ import QAJsonMethodsImplicits._ import TestJsonMethodsImplicits._ + import MantisJsonMethodImplicits._ override def apisHandleFns: Map[String, PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]]] = Map( Apis.Eth -> handleEthRequest, Apis.Web3 -> handleWeb3Request, Apis.Net -> handleNetRequest, Apis.Personal -> handlePersonalRequest, - Apis.Daedalus -> handleDaedalusRequest, + Apis.Mantis -> handleMantisRequest, Apis.Rpc -> handleRpcRequest, Apis.Debug -> handleDebugRequest, Apis.Test -> handleTestRequest, @@ -259,9 +262,9 @@ class JsonRpcController( handle[EcRecoverRequest, EcRecoverResponse](personalService.ecRecover, req) } - private def handleDaedalusRequest: PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]] = { - case req @ JsonRpcRequest(_, "daedalus_getAccountTransactions", _, _) => - handle[GetAccountTransactionsRequest, GetAccountTransactionsResponse](ethService.getAccountTransactions, req) + private def handleMantisRequest: PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]] = { + case req @ JsonRpcRequest(_, "mantis_getAccountTransactions", _, _) => + handle[GetAccountTransactionsRequest, GetAccountTransactionsResponse](mantisService.getAccountTransactions, req) } private def handleQARequest: PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]] = { diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala new file mode 100644 index 0000000000..263553b15b --- /dev/null +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala @@ -0,0 +1,45 @@ +package io.iohk.ethereum.jsonrpc + +import io.iohk.ethereum.jsonrpc.EthJsonMethodsImplicits.transactionResponseJsonEncoder +import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams +import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse} +import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec, JsonMethodDecoder} +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import org.json4s.JsonAST._ +import org.json4s.Merge + +object MantisJsonMethodImplicits extends JsonMethodsImplicits { + implicit val extendedTransactionDataJsonEncoder: JsonEncoder[ExtendedTransactionData] = extendedTxData => { + val asTxResponse = TransactionResponse( + extendedTxData.stx, + extendedTxData.minedTransactionData.map(_._1), + extendedTxData.minedTransactionData.map(_._2) + ) + + val encodedTxResponse = JsonEncoder.encode(asTxResponse) + val encodedExtension = JObject( + "isOutgoing" -> JBool(extendedTxData.isOutgoing), + "isPending" -> JBool(extendedTxData.isPending) + ) + + Merge.merge(encodedTxResponse, encodedExtension) + } + + implicit val mantis_getAccountTransactions + : JsonMethodCodec[GetAccountTransactionsRequest, GetAccountTransactionsResponse] = + new JsonMethodDecoder[GetAccountTransactionsRequest] with JsonEncoder[GetAccountTransactionsResponse] { + def decodeJson(params: Option[JArray]): Either[JsonRpcError, GetAccountTransactionsRequest] = + params match { + case Some(JArray(JString(addrJson) :: fromBlockJson :: toBlockJson :: Nil)) => + for { + addr <- extractAddress(addrJson) + fromBlock <- extractQuantity(fromBlockJson) + toBlock <- extractQuantity(toBlockJson) + } yield GetAccountTransactionsRequest(addr, fromBlock to toBlock) + case _ => Left(InvalidParams()) + } + + override def encodeJson(t: GetAccountTransactionsResponse): JValue = + JObject("transactions" -> JsonEncoder.encode(t.transactions)) + } +} diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/MantisService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/MantisService.scala new file mode 100644 index 0000000000..fdafb33025 --- /dev/null +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/MantisService.scala @@ -0,0 +1,35 @@ +package io.iohk.ethereum.jsonrpc +import io.iohk.ethereum.domain.Address +import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse} +import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig +import io.iohk.ethereum.transactions.TransactionHistoryService +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import monix.eval.Task +import cats.implicits._ + +import scala.collection.immutable.NumericRange + +object MantisService { + case class GetAccountTransactionsRequest(address: Address, blocksRange: NumericRange[BigInt]) + case class GetAccountTransactionsResponse(transactions: List[ExtendedTransactionData]) +} +class MantisService(transactionHistoryService: TransactionHistoryService, jsonRpcConfig: JsonRpcConfig) { + def getAccountTransactions( + request: GetAccountTransactionsRequest + ): ServiceResponse[GetAccountTransactionsResponse] = { + if (request.blocksRange.length > jsonRpcConfig.accountTransactionsMaxBlocks) { + Task.now( + Left( + JsonRpcError.InvalidParams( + s"""Maximum number of blocks to search is ${jsonRpcConfig.accountTransactionsMaxBlocks}, requested: ${request.blocksRange.length}. + |See: 'mantis.network.rpc.account-transactions-max-blocks' config.""".stripMargin + ) + ) + ) + } else { + transactionHistoryService + .getAccountTransactions(request.address, request.blocksRange) + .map(GetAccountTransactionsResponse(_).asRight) + } + } +} diff --git a/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala b/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala index 0765e8c69f..0eb7576879 100644 --- a/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala +++ b/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala @@ -264,14 +264,19 @@ trait NetServiceBuilder { } trait PendingTransactionsManagerBuilder { - self: ActorSystemBuilder - with PeerManagerActorBuilder - with EtcPeerManagerActorBuilder - with PeerEventBusBuilder - with TxPoolConfigBuilder => - - lazy val pendingTransactionsManager: ActorRef = - system.actorOf(PendingTransactionsManager.props(txPoolConfig, peerManager, etcPeerManager, peerEventBus)) + def pendingTransactionsManager: ActorRef +} +object PendingTransactionsManagerBuilder { + trait Default extends PendingTransactionsManagerBuilder { + self: ActorSystemBuilder + with PeerManagerActorBuilder + with EtcPeerManagerActorBuilder + with PeerEventBusBuilder + with TxPoolConfigBuilder => + + lazy val pendingTransactionsManager: ActorRef = + system.actorOf(PendingTransactionsManager.props(txPoolConfig, peerManager, etcPeerManager, peerEventBus)) + } } trait TransactionHistoryServiceBuilder { @@ -339,8 +344,7 @@ trait EthServiceBuilder { with FilterConfigBuilder with TxPoolConfigBuilder with JSONRpcConfigBuilder - with AsyncConfigBuilder - with TransactionHistoryServiceBuilder => + with AsyncConfigBuilder => lazy val ethService = new EthService( blockchain, @@ -351,7 +355,6 @@ trait EthServiceBuilder { syncController, ommersPool, filterManager, - transactionHistoryService, filterConfig, blockchainConfig, Config.Network.protocolVersion, @@ -401,6 +404,12 @@ trait CheckpointingServiceBuilder { ) } +trait MantisServiceBuilder { + self: TransactionHistoryServiceBuilder with JSONRpcConfigBuilder => + + lazy val mantisService = new MantisService(transactionHistoryService, jsonRpcConfig) +} + trait KeyStoreBuilder { self: SecureRandomBuilder with KeyStoreConfigBuilder => lazy val keyStore: KeyStore = new KeyStoreImpl(keyStoreConfig, secureRandom) @@ -412,7 +421,7 @@ trait ApisBuilder extends ApisBase { val Web3 = "web3" val Net = "net" val Personal = "personal" - val Daedalus = "daedalus" + val Mantis = "mantis" val Debug = "debug" val Rpc = "rpc" val Test = "test" @@ -422,7 +431,7 @@ trait ApisBuilder extends ApisBase { } import Apis._ - override def available: List[String] = List(Eth, Web3, Net, Personal, Daedalus, Debug, Test, Iele, Qa, Checkpointing) + override def available: List[String] = List(Eth, Web3, Net, Personal, Mantis, Debug, Test, Iele, Qa, Checkpointing) } trait JSONRpcConfigBuilder { @@ -440,7 +449,8 @@ trait JSONRpcControllerBuilder { with DebugServiceBuilder with JSONRpcConfigBuilder with QaServiceBuilder - with CheckpointingServiceBuilder => + with CheckpointingServiceBuilder + with MantisServiceBuilder => private val testService = if (Config.testmode) Some(this.asInstanceOf[TestServiceBuilder].testService) @@ -456,6 +466,7 @@ trait JSONRpcControllerBuilder { debugService, qaService, checkpointingService, + mantisService, jsonRpcConfig ) } @@ -638,6 +649,7 @@ trait Node with DebugServiceBuilder with QaServiceBuilder with CheckpointingServiceBuilder + with MantisServiceBuilder with KeyStoreBuilder with ApisBuilder with JSONRpcConfigBuilder @@ -651,7 +663,7 @@ trait Node with BlockchainConfigBuilder with VmConfigBuilder with PeerEventBusBuilder - with PendingTransactionsManagerBuilder + with PendingTransactionsManagerBuilder.Default with OmmersPoolBuilder with EtcPeerManagerActorBuilder with BlockchainHostBuilder diff --git a/src/test/resources/application.conf b/src/test/resources/application.conf index e95ea3c57f..3456d282e3 100644 --- a/src/test/resources/application.conf +++ b/src/test/resources/application.conf @@ -14,7 +14,7 @@ mantis { network.peer.max-pending-peers = 1 - network.rpc.apis = "eth,web3,net,personal,daedalus,debug,qa,checkpointing" + network.rpc.apis = "eth,web3,net,personal,mantis,debug,qa,checkpointing" blockchains { network = "test" diff --git a/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala b/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala index 2227c78f33..747a9352b7 100644 --- a/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala +++ b/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala @@ -3,7 +3,7 @@ package io.iohk.ethereum.blockchain.sync import io.iohk.ethereum.db.components.{EphemDataSourceComponent, Storages} import io.iohk.ethereum.db.storage.pruning.{ArchivePruning, PruningMode} import io.iohk.ethereum.ledger.Ledger.VMImpl -import io.iohk.ethereum.nodebuilder.PruningConfigBuilder +import io.iohk.ethereum.nodebuilder.{BlockchainBuilder, PruningConfigBuilder} trait EphemBlockchainTestSetup extends ScenarioSetup { diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/CheckpointingJRCSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/CheckpointingJRCSpec.scala index edfbd73495..a0e3f53fdc 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/CheckpointingJRCSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/CheckpointingJRCSpec.scala @@ -209,6 +209,8 @@ class CheckpointingJRCSpec val ethService = mock[EthService] val qaService = mock[QAService] val checkpointingService = mock[CheckpointingService] + val mantisService = mock[MantisService] + val jsonRpcController = new JsonRpcController( web3Service, @@ -219,6 +221,7 @@ class CheckpointingJRCSpec debugService, qaService, checkpointingService, + mantisService, config ) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala index 25f9bca998..ed5c6a1df0 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala @@ -1102,52 +1102,6 @@ class EthServiceSpec ) } - it should "get account's transaction history" in new TestSetup { - val fakeTransaction = SignedTransactionWithSender( - Transaction( - nonce = 0, - gasPrice = 123, - gasLimit = 123, - receivingAddress = Address("0x1234"), - value = 0, - payload = ByteString() - ), - signature = ECDSASignature(0, 0, 0.toByte), - sender = Address("0x1234") - ) - - override val block = - BlockHelpers.generateBlock(BlockHelpers.genesis).copy(body = BlockBody(List(fakeTransaction.tx), Nil)) - - val expectedResponse = List( - ExtendedTransactionData( - fakeTransaction.tx, - isOutgoing = true, - Some((block.header, 0)) - ) - ) - - override lazy val transactionHistoryService = - new TransactionHistoryService(blockchain, pendingTransactionsManager.ref, getTransactionFromPoolTimeout) { - override def getAccountTransactions(account: Address, fromBlocks: NumericRange[BigInt]) = - Task.pure(expectedResponse) - } - - ethService - .getAccountTransactions(GetAccountTransactionsRequest(fakeTransaction.senderAddress, BigInt(0) to BigInt(1))) - .map(result => assert(result === Right(GetAccountTransactionsResponse(expectedResponse)))) - .runSyncUnsafe() - } - - it should "validate range size against configuration" in new TestSetup { - ethService - .getAccountTransactions( - GetAccountTransactionsRequest(Address(1), BigInt(0) to BigInt(jsonRpcConfig.accountTransactionsMaxBlocks + 1)) - ) - .map(result => assert(result.isLeft)) - .runSyncUnsafe() - } - it should "send message to pendingTransactionsManager and return an empty GetPendingTransactionsResponse" in new TestSetup { val res = ethService.getTransactionsFromPool.runSyncUnsafe() @@ -1214,9 +1168,6 @@ class EthServiceSpec val minerActiveTimeout: FiniteDuration = 5.seconds val getTransactionFromPoolTimeout: FiniteDuration = 5.seconds - lazy val transactionHistoryService = - new TransactionHistoryService(blockchain, pendingTransactionsManager.ref, getTransactionFromPoolTimeout) - val filterConfig = new FilterConfig { override val filterTimeout: FiniteDuration = Timeouts.normalTimeout override val filterManagerQueryTimeout: FiniteDuration = Timeouts.normalTimeout @@ -1251,7 +1202,6 @@ class EthServiceSpec syncingController.ref, ommersPool.ref, filterManager.ref, - transactionHistoryService, filterConfig, blockchainConfig, currentProtocolVersion, diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthTransactionSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthTransactionSpec.scala index 8349deb8e8..8c32dfb778 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthTransactionSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthTransactionSpec.scala @@ -430,6 +430,7 @@ class JsonRpcControllerEthTransactionSpec debugService, qaService, checkpointingService, + mantisService, config ) @@ -480,6 +481,7 @@ class JsonRpcControllerEthTransactionSpec debugService, qaService, checkpointingService, + mantisService, config ) val request = JsonRpcRequest( diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala index 99f0bacdf7..6da7e9482d 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala @@ -76,8 +76,7 @@ class JsonRpcControllerFixture(implicit system: ActorSystem) val debugService = mock[DebugService] val qaService = mock[QAService] val checkpointingService = mock[CheckpointingService] - val transactionHistoryService = - new TransactionHistoryService(blockchain, pendingTransactionsManager.ref, getTransactionFromPoolTimeout) + val mantisService = mock[MantisService] val ethService = new EthService( blockchain, @@ -88,7 +87,6 @@ class JsonRpcControllerFixture(implicit system: ActorSystem) syncingController.ref, ommersPool.ref, filterManager.ref, - transactionHistoryService, filterConfig, blockchainConfig, currentProtocolVersion, @@ -107,6 +105,7 @@ class JsonRpcControllerFixture(implicit system: ActorSystem) debugService, qaService, checkpointingService, + mantisService, config ) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala index 5fccc47197..e311faa2c1 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala @@ -5,6 +5,7 @@ import akka.testkit.TestKit import io.iohk.ethereum.domain.ChainWeight import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse} import io.iohk.ethereum.jsonrpc.EthService._ +import io.iohk.ethereum.jsonrpc.MantisService.GetAccountTransactionsResponse import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers.{ OptionNoneToJNullSerializer, @@ -156,68 +157,11 @@ class JsonRpcControllerSpec "personal" -> JString("1.0"), "eth" -> JString("1.0"), "web3" -> JString("1.0"), - "daedalus" -> JString("1.0"), + "mantis" -> JString("1.0"), "debug" -> JString("1.0"), "qa" -> JString("1.0"), "checkpointing" -> JString("1.0") ) ) } - - it should "daedalus_getAccountTransactions" in new JsonRpcControllerFixture { - val mockEthService: EthService = mock[EthService] - override val jsonRpcController = newJsonRpcController(mockEthService) - - val block = Fixtures.Blocks.Block3125369 - val sentTx = block.body.transactionList.head - val receivedTx = block.body.transactionList.last - - (mockEthService.getAccountTransactions _) - .expects(*) - .returning( - Task.now( - Right( - GetAccountTransactionsResponse( - List( - ExtendedTransactionData(sentTx, isOutgoing = true, Some((block.header, 0))), - ExtendedTransactionData(receivedTx, isOutgoing = false, Some((block.header, 1))) - ) - ) - ) - ) - ) - - val request: JsonRpcRequest = newJsonRpcRequest( - "daedalus_getAccountTransactions", - List( - JString(s"0x7B9Bc474667Db2fFE5b08d000F1Acc285B2Ae47D"), - JInt(100), - JInt(200) - ) - ) - - val response = jsonRpcController.handleRequest(request).runSyncUnsafe() - val expectedTxs = Seq( - JObject( - Extraction - .decompose(TransactionResponse(sentTx, Some(block.header), Some(0))) - .asInstanceOf[JObject] - .obj ++ List( - "isPending" -> JBool(false), - "isOutgoing" -> JBool(true) - ) - ), - JObject( - Extraction - .decompose(TransactionResponse(receivedTx, Some(block.header), Some(1))) - .asInstanceOf[JObject] - .obj ++ List( - "isPending" -> JBool(false), - "isOutgoing" -> JBool(false) - ) - ) - ) - - response should haveObjectResult("transactions" -> JArray(expectedTxs.toList)) - } } diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala new file mode 100644 index 0000000000..0f6cabad98 --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala @@ -0,0 +1,108 @@ +package io.iohk.ethereum.jsonrpc + +import io.iohk.ethereum.jsonrpc.MantisService.GetAccountTransactionsResponse +import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig +import io.iohk.ethereum.nodebuilder.ApisBuilder +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import io.iohk.ethereum.utils.Config +import io.iohk.ethereum.{Fixtures, FreeSpecBase, SpecFixtures} +import monix.eval.Task +import org.json4s.{Extraction, JArray, JBool, JInt, JObject, JString} +import org.scalamock.scalatest.AsyncMockFactory + +class MantisJRCSpec extends FreeSpecBase with SpecFixtures with AsyncMockFactory with JRCMatchers { + import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers.formats + + class Fixture extends ApisBuilder { + def config: JsonRpcConfig = JsonRpcConfig(Config.config, available) + + val web3Service = mock[Web3Service] + val netService = mock[NetService] + val personalService = mock[PersonalService] + val debugService = mock[DebugService] + val ethService = mock[EthService] + val qaService = mock[QAService] + val checkpointingService = mock[CheckpointingService] + val mantisService = mock[MantisService] + + val jsonRpcController = + new JsonRpcController( + web3Service, + netService, + ethService, + personalService, + None, + debugService, + qaService, + checkpointingService, + mantisService, + config + ) + + } + def createFixture() = new Fixture + + "Mantis JRC" - { + "should handle mantis_getAccountTransactions" in testCaseM { fixture => + import fixture._ + val block = Fixtures.Blocks.Block3125369 + val sentTx = block.body.transactionList.head + val receivedTx = block.body.transactionList.last + + (mantisService.getAccountTransactions _) + .expects(*) + .returning( + Task.now( + Right( + GetAccountTransactionsResponse( + List( + ExtendedTransactionData(sentTx, isOutgoing = true, Some((block.header, 0))), + ExtendedTransactionData(receivedTx, isOutgoing = false, Some((block.header, 1))) + ) + ) + ) + ) + ) + + val request: JsonRpcRequest = JsonRpcRequest( + "2.0", + "mantis_getAccountTransactions", + Some( + JArray( + List( + JString(s"0x7B9Bc474667Db2fFE5b08d000F1Acc285B2Ae47D"), + JInt(100), + JInt(200) + ) + ) + ), + Some(JInt(1)) + ) + + val expectedTxs = Seq( + JObject( + Extraction + .decompose(TransactionResponse(sentTx, Some(block.header), Some(0))) + .asInstanceOf[JObject] + .obj ++ List( + "isPending" -> JBool(false), + "isOutgoing" -> JBool(true) + ) + ), + JObject( + Extraction + .decompose(TransactionResponse(receivedTx, Some(block.header), Some(1))) + .asInstanceOf[JObject] + .obj ++ List( + "isPending" -> JBool(false), + "isOutgoing" -> JBool(false) + ) + ) + ) + + for { + response <- jsonRpcController.handleRequest(request) + } yield response should haveObjectResult("transactions" -> JArray(expectedTxs.toList)) + } + } +} diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala new file mode 100644 index 0000000000..f247d70057 --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala @@ -0,0 +1,100 @@ +package io.iohk.ethereum.jsonrpc + +import akka.actor.{ActorRef, ActorSystem} +import akka.testkit.{TestKit, TestProbe} +import akka.util.ByteString +import io.iohk.ethereum.blockchain.sync.EphemBlockchainTestSetup +import io.iohk.ethereum.crypto.ECDSASignature +import io.iohk.ethereum.domain.{Address, BlockBody, SignedTransactionWithSender, Transaction} +import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse} +import io.iohk.ethereum.nodebuilder.{ + ApisBuilder, + JSONRpcConfigBuilder, + MantisServiceBuilder, + PendingTransactionsManagerBuilder, + TransactionHistoryServiceBuilder, + TxPoolConfigBuilder +} +import io.iohk.ethereum.transactions.TransactionHistoryService +import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import io.iohk.ethereum.{BlockHelpers, FreeSpecBase, SpecFixtures, WithActorSystemShutDown} +import monix.eval.Task + +import scala.collection.immutable.NumericRange + +class MantisServiceSpec + extends TestKit(ActorSystem("MantisServiceSpec")) + with FreeSpecBase + with SpecFixtures + with WithActorSystemShutDown { + class Fixture + extends TransactionHistoryServiceBuilder + with EphemBlockchainTestSetup + with PendingTransactionsManagerBuilder + with TxPoolConfigBuilder + with MantisServiceBuilder + with JSONRpcConfigBuilder + with ApisBuilder { + lazy val pendingTransactionsManagerProbe = TestProbe() + override lazy val pendingTransactionsManager: ActorRef = pendingTransactionsManagerProbe.ref + } + def createFixture() = new Fixture + + "Mantis Service" - { + "should get account's transaction history" in { + class TxHistoryFixture extends Fixture { + val fakeTransaction = SignedTransactionWithSender( + Transaction( + nonce = 0, + gasPrice = 123, + gasLimit = 123, + receivingAddress = Address("0x1234"), + value = 0, + payload = ByteString() + ), + signature = ECDSASignature(0, 0, 0.toByte), + sender = Address("0x1234") + ) + + val block = + BlockHelpers.generateBlock(BlockHelpers.genesis).copy(body = BlockBody(List(fakeTransaction.tx), Nil)) + + val expectedResponse = List( + ExtendedTransactionData( + fakeTransaction.tx, + isOutgoing = true, + Some((block.header, 0)) + ) + ) + + override lazy val transactionHistoryService: TransactionHistoryService = + new TransactionHistoryService( + blockchain, + pendingTransactionsManager, + txPoolConfig.getTransactionFromPoolTimeout + ) { + override def getAccountTransactions(account: Address, fromBlocks: NumericRange[BigInt]) = + Task.pure(expectedResponse) + } + } + + customTestCaseM(new TxHistoryFixture) { fixture => + import fixture._ + + mantisService + .getAccountTransactions(GetAccountTransactionsRequest(fakeTransaction.senderAddress, BigInt(0) to BigInt(1))) + .map(result => assert(result === Right(GetAccountTransactionsResponse(expectedResponse)))) + } + } + + "should validate range size against configuration" in testCaseM { fixture => + import fixture._ + + mantisService + .getAccountTransactions( + GetAccountTransactionsRequest(Address(1), BigInt(0) to BigInt(jsonRpcConfig.accountTransactionsMaxBlocks + 1)) + ) + .map(result => assert(result.isLeft)) + } + } +} diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/QaJRCSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/QaJRCSpec.scala index 678d2db8df..2a809edba4 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/QaJRCSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/QaJRCSpec.scala @@ -245,8 +245,9 @@ class QaJRCSpec val debugService = mock[DebugService] val ethService = mock[EthService] val checkpointingService = mock[CheckpointingService] - + val mantisService = mock[MantisService] val qaService = mock[QAService] + val jsonRpcController = new JsonRpcController( web3Service, @@ -257,6 +258,7 @@ class QaJRCSpec debugService, qaService, checkpointingService, + mantisService, config ) diff --git a/src/universal/conf/testmode.conf b/src/universal/conf/testmode.conf index 9e17b48ec5..986c74379a 100644 --- a/src/universal/conf/testmode.conf +++ b/src/universal/conf/testmode.conf @@ -17,7 +17,7 @@ mantis { } network.rpc { - apis = "eth,web3,net,personal,daedalus,test,iele,debug,qa,checkpointing" + apis = "eth,web3,net,personal,mantis,test,iele,debug,qa,checkpointing" } } From e2f8a88f2ad49aaecae9b527859a3f216ebf024e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Kope=C4=87?= Date: Fri, 20 Nov 2020 22:22:17 +0100 Subject: [PATCH 4/7] [ETCM-135] Add timestamp and gasUsed to mantis_getAccountTransactions --- .../jsonrpc/JsonMethodsImplicits.scala | 6 +- .../jsonrpc/MantisJsonMethodImplicits.scala | 14 +- .../jsonrpc/serialization/JsonEncoder.scala | 11 +- .../TransactionHistoryService.scala | 122 +++++++++++------- .../sync/EphemBlockchainTestSetup.scala | 2 +- .../ethereum/jsonrpc/EthServiceSpec.scala | 9 +- .../jsonrpc/JsonRpcControllerFixture.scala | 1 - .../jsonrpc/JsonRpcControllerSpec.scala | 9 +- .../iohk/ethereum/jsonrpc/MantisJRCSpec.scala | 20 ++- .../ethereum/jsonrpc/MantisServiceSpec.scala | 4 +- .../TransactionHistoryServiceSpec.scala | 17 ++- 11 files changed, 133 insertions(+), 82 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/JsonMethodsImplicits.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/JsonMethodsImplicits.scala index bafb3ddd2b..34a2a76e60 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/JsonMethodsImplicits.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/JsonMethodsImplicits.scala @@ -22,13 +22,13 @@ import scala.util.Try trait JsonMethodsImplicits { implicit val formats = JsonSerializers.formats - protected def encodeAsHex(input: ByteString): JString = + def encodeAsHex(input: ByteString): JString = JString(s"0x${Hex.toHexString(input.toArray[Byte])}") - protected def encodeAsHex(input: Byte): JString = + def encodeAsHex(input: Byte): JString = JString(s"0x${Hex.toHexString(Array(input))}") - protected def encodeAsHex(input: BigInt): JString = + def encodeAsHex(input: BigInt): JString = JString(s"0x${input.toString(16)}") protected def decode(s: String): Array[Byte] = { diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala index 263553b15b..de7743260f 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala @@ -3,23 +3,27 @@ package io.iohk.ethereum.jsonrpc import io.iohk.ethereum.jsonrpc.EthJsonMethodsImplicits.transactionResponseJsonEncoder import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse} +import io.iohk.ethereum.jsonrpc.serialization.JsonEncoder.Ops._ import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec, JsonMethodDecoder} import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData import org.json4s.JsonAST._ import org.json4s.Merge +import JsonEncoder.OptionToNull._ object MantisJsonMethodImplicits extends JsonMethodsImplicits { implicit val extendedTransactionDataJsonEncoder: JsonEncoder[ExtendedTransactionData] = extendedTxData => { val asTxResponse = TransactionResponse( extendedTxData.stx, - extendedTxData.minedTransactionData.map(_._1), - extendedTxData.minedTransactionData.map(_._2) + extendedTxData.minedTransactionData.map(_.header), + extendedTxData.minedTransactionData.map(_.transactionIndex) ) val encodedTxResponse = JsonEncoder.encode(asTxResponse) val encodedExtension = JObject( - "isOutgoing" -> JBool(extendedTxData.isOutgoing), - "isPending" -> JBool(extendedTxData.isPending) + "isOutgoing" -> extendedTxData.isOutgoing.jsonEncoded, + "isPending" -> extendedTxData.isPending.jsonEncoded, + "gasUsed" -> extendedTxData.minedTransactionData.map(_.gasUsed).jsonEncoded, + "timestamp" -> extendedTxData.minedTransactionData.map(_.timestamp).jsonEncoded ) Merge.merge(encodedTxResponse, encodedExtension) @@ -40,6 +44,6 @@ object MantisJsonMethodImplicits extends JsonMethodsImplicits { } override def encodeJson(t: GetAccountTransactionsResponse): JValue = - JObject("transactions" -> JsonEncoder.encode(t.transactions)) + JObject("transactions" -> t.transactions.jsonEncoded) } } diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala index 427bd1853c..2843b42a9e 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/serialization/JsonEncoder.scala @@ -1,6 +1,7 @@ package io.iohk.ethereum.jsonrpc.serialization -import org.json4s.{JArray, JBool, JInt, JNull, JString, JValue} +import io.iohk.ethereum.jsonrpc.JsonMethodsImplicits +import org.json4s.{JArray, JBool, JInt, JLong, JNull, JString, JValue} trait JsonEncoder[T] { def encodeJson(t: T): JValue @@ -10,10 +11,18 @@ object JsonEncoder { def encode[T](value: T)(implicit encoder: JsonEncoder[T]): JValue = encoder.encodeJson(value) + object Ops { + implicit class JsonEncoderOps[T](val item: T) extends AnyVal { + def jsonEncoded(implicit encoder: JsonEncoder[T]): JValue = encoder.encodeJson(item) + } + } + implicit val stringEncoder: JsonEncoder[String] = JString(_) implicit val intEncoder: JsonEncoder[Int] = JInt(_) + implicit val longEncoder: JsonEncoder[Long] = JLong(_) implicit val booleanEncoder: JsonEncoder[Boolean] = JBool(_) implicit val jvalueEncoder: JsonEncoder[JValue] = identity + implicit val bigIntEncoder: JsonEncoder[BigInt] = JsonMethodsImplicits.encodeAsHex(_) implicit def listEncoder[T](implicit itemEncoder: JsonEncoder[T]): JsonEncoder[List[T]] = list => JArray(list.map(itemEncoder.encodeJson)) diff --git a/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala index 0fa46572c3..2417bb63b0 100644 --- a/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala +++ b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala @@ -1,18 +1,23 @@ package io.iohk.ethereum.transactions import akka.actor.ActorRef -import cats.implicits._ import akka.util.Timeout -import io.iohk.ethereum.domain.{Address, Block, BlockHeader, Blockchain, SignedTransaction} +import cats.implicits._ +import io.iohk.ethereum.domain._ import io.iohk.ethereum.jsonrpc.AkkaTaskOps.TaskActorOps import io.iohk.ethereum.transactions.PendingTransactionsManager.PendingTransaction -import io.iohk.ethereum.transactions.TransactionHistoryService.{ExtendedTransactionData, TxChecker} +import io.iohk.ethereum.transactions.TransactionHistoryService.{ + ExtendedTransactionData, + MinedTxChecker, + PendingTxChecker +} import io.iohk.ethereum.utils.Logger import monix.eval.Task import monix.reactive.{Observable, OverflowStrategy} import scala.collection.immutable.NumericRange import scala.concurrent.duration.FiniteDuration +import scala.language.higherKinds class TransactionHistoryService( blockchain: Blockchain, @@ -27,17 +32,26 @@ class TransactionHistoryService( .from(fromBlocks.reverse) .mapParallelOrdered(10)(blockNr => Task { blockchain.getBlockByNumber(blockNr) })(OverflowStrategy.Unbounded) .collect { case Some(block) => block } - .concatMapIterable { block => - val checker = TxChecker.forSigned(block) - block.body.transactionList.toList - .collect(Function.unlift(checker.checkTx(_, account))) - .reverse + .concatMap { block => + val getBlockReceipts = Task { + blockchain.getReceiptsByHash(block.hash).map(_.toVector).getOrElse(Vector.empty) + }.memoizeOnSuccess + + Observable + .from(block.body.transactionList.reverse) + .collect(Function.unlift(MinedTxChecker.checkTx(_, account))) + .mapEval { case (tx, mkExtendedData) => + getBlockReceipts.map(MinedTxChecker.getMinedTxData(tx, block, _).map(mkExtendedData(_))) + } + .collect { case Some(data) => + data + } } .toListL val txnsFromMempool = getTransactionsFromPool map { pendingTransactions => pendingTransactions - .collect(Function.unlift(TxChecker.forPending.checkTx(_, account))) + .collect(Function.unlift(PendingTxChecker.checkTx(_, account))) } Task.parMap2(txnsFromBlocks, txnsFromMempool)(_ ++ _) @@ -55,58 +69,76 @@ class TransactionHistoryService( } } object TransactionHistoryService { + case class MinedTransactionData( + header: BlockHeader, + transactionIndex: Int, + gasUsed: BigInt + ) { + lazy val timestamp: Long = header.unixTimestamp + } case class ExtendedTransactionData( stx: SignedTransaction, isOutgoing: Boolean, - //block header and transaction index - minedTransactionData: Option[(BlockHeader, Int)] + minedTransactionData: Option[MinedTransactionData] ) { val isPending: Boolean = minedTransactionData.isEmpty } - trait TxChecker[T] { - def checkTx(tx: T, address: Address): Option[ExtendedTransactionData] - } - object TxChecker { - val forPending: TxChecker[PendingTransaction] = new TxChecker[PendingTransaction] { - def isSender(tx: PendingTransaction, maybeSender: Address) = tx.stx.senderAddress == maybeSender - def isReceiver(tx: PendingTransaction, maybeReceiver: Address) = - tx.stx.tx.tx.receivingAddress.contains(maybeReceiver) - def asSigned(tx: PendingTransaction) = tx.stx.tx + object PendingTxChecker { + def isSender(tx: PendingTransaction, maybeSender: Address): Boolean = tx.stx.senderAddress == maybeSender + def isReceiver(tx: PendingTransaction, maybeReceiver: Address): Boolean = + tx.stx.tx.tx.receivingAddress.contains(maybeReceiver) + def asSigned(tx: PendingTransaction): SignedTransaction = tx.stx.tx - def checkTx(tx: PendingTransaction, address: Address): Option[ExtendedTransactionData] = { - if (isSender(tx, address)) { - Some(ExtendedTransactionData(asSigned(tx), isOutgoing = true, None)) - } else if (isReceiver(tx, address)) { - Some(ExtendedTransactionData(asSigned(tx), isOutgoing = false, None)) - } else { - None - } + def checkTx(tx: PendingTransaction, address: Address): Option[ExtendedTransactionData] = { + if (isSender(tx, address)) { + Some(ExtendedTransactionData(asSigned(tx), isOutgoing = true, None)) + } else if (isReceiver(tx, address)) { + Some(ExtendedTransactionData(asSigned(tx), isOutgoing = false, None)) + } else { + None } } + } - def forSigned(block: Block): TxChecker[SignedTransaction] = new TxChecker[SignedTransaction] { - def isSender(tx: SignedTransaction, maybeSender: Address) = tx.safeSenderIsEqualTo(maybeSender) - def isReceiver(tx: SignedTransaction, maybeReceiver: Address) = tx.tx.receivingAddress.contains(maybeReceiver) - def asSigned(tx: SignedTransaction) = tx + object MinedTxChecker { + def isSender(tx: SignedTransaction, maybeSender: Address): Boolean = tx.safeSenderIsEqualTo(maybeSender) + def isReceiver(tx: SignedTransaction, maybeReceiver: Address): Boolean = + tx.tx.receivingAddress.contains(maybeReceiver) - def getMinedTxData(tx: SignedTransaction): Option[(BlockHeader, Int)] = { - val maybeIndex = block.body.transactionList.zipWithIndex.collectFirst { - case (someTx, index) if someTx.hash == tx.hash => index - } + def checkTx( + tx: SignedTransaction, + address: Address + ): Option[(SignedTransaction, MinedTransactionData => ExtendedTransactionData)] = { + if (isSender(tx, address)) { + Some((tx, data => ExtendedTransactionData(tx, isOutgoing = true, Some(data)))) + } else if (isReceiver(tx, address)) { + Some((tx, data => ExtendedTransactionData(tx, isOutgoing = false, Some(data)))) + } else { + None + } + } - (Some(block.header), maybeIndex).tupled + def getMinedTxData( + tx: SignedTransaction, + block: Block, + blockReceipts: Vector[Receipt] + ): Option[MinedTransactionData] = { + val maybeIndex = block.body.transactionList.zipWithIndex.collectFirst { + case (someTx, index) if someTx.hash == tx.hash => index } - def checkTx(tx: SignedTransaction, address: Address): Option[ExtendedTransactionData] = { - if (isSender(tx, address)) { - Some(ExtendedTransactionData(asSigned(tx), isOutgoing = true, getMinedTxData(tx))) - } else if (isReceiver(tx, address)) { - Some(ExtendedTransactionData(asSigned(tx), isOutgoing = false, getMinedTxData(tx))) - } else { - None - } + val maybeGasUsed = for { + index <- maybeIndex + txReceipt <- blockReceipts.lift(index) + } yield { + val previousCumulativeGas: BigInt = + (if (index > 0) blockReceipts.lift(index - 1) else None).map(_.cumulativeGasUsed).getOrElse(0) + + txReceipt.cumulativeGasUsed - previousCumulativeGas } + + (Some(block.header), maybeIndex, maybeGasUsed).mapN(MinedTransactionData) } } } diff --git a/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala b/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala index 747a9352b7..2227c78f33 100644 --- a/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala +++ b/src/test/scala/io/iohk/ethereum/blockchain/sync/EphemBlockchainTestSetup.scala @@ -3,7 +3,7 @@ package io.iohk.ethereum.blockchain.sync import io.iohk.ethereum.db.components.{EphemDataSourceComponent, Storages} import io.iohk.ethereum.db.storage.pruning.{ArchivePruning, PruningMode} import io.iohk.ethereum.ledger.Ledger.VMImpl -import io.iohk.ethereum.nodebuilder.{BlockchainBuilder, PruningConfigBuilder} +import io.iohk.ethereum.nodebuilder.PruningConfigBuilder trait EphemBlockchainTestSetup extends ScenarioSetup { diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala index ed5c6a1df0..6368740c30 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala @@ -1,6 +1,5 @@ package io.iohk.ethereum.jsonrpc -import java.security.SecureRandom import akka.actor.ActorSystem import akka.testkit.{TestKit, TestProbe} import akka.util.ByteString @@ -25,17 +24,14 @@ import io.iohk.ethereum.mpt.{ByteArrayEncoder, ByteArraySerializable, MerklePatr import io.iohk.ethereum.nodebuilder.ApisBuilder import io.iohk.ethereum.ommers.OmmersPool import io.iohk.ethereum.testing.ActorsTesting.simpleAutoPilot -import io.iohk.ethereum.transactions.{PendingTransactionsManager, TransactionHistoryService} +import io.iohk.ethereum.transactions.PendingTransactionsManager import io.iohk.ethereum.transactions.PendingTransactionsManager.{ GetPendingTransactions, PendingTransaction, PendingTransactionsResponse } -import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData -import io.iohk.ethereum.utils.ByteStringUtils.hash2string import io.iohk.ethereum.utils._ -import io.iohk.ethereum.{BlockHelpers, Fixtures, NormalPatience, Timeouts, WithActorSystemShutDown, crypto} -import monix.eval.Task +import io.iohk.ethereum._ import monix.execution.Scheduler.Implicits.global import org.bouncycastle.util.encoders.Hex import org.scalactic.TypeCheckedTripleEquals @@ -45,7 +41,6 @@ import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers -import scala.collection.immutable.NumericRange import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration} // scalastyle:off file.size.limit diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala index 6da7e9482d..8973005dbc 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala @@ -16,7 +16,6 @@ import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpc import io.iohk.ethereum.keystore.KeyStore import io.iohk.ethereum.ledger.{BloomFilter, Ledger, StxLedger} import io.iohk.ethereum.nodebuilder.ApisBuilder -import io.iohk.ethereum.transactions.TransactionHistoryService import io.iohk.ethereum.utils.{Config, FilterConfig} import io.iohk.ethereum.{Fixtures, ObjectGenerators, Timeouts} import org.bouncycastle.util.encoders.Hex diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala index e311faa2c1..eedef962be 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala @@ -4,25 +4,22 @@ import akka.actor.ActorSystem import akka.testkit.TestKit import io.iohk.ethereum.domain.ChainWeight import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse} -import io.iohk.ethereum.jsonrpc.EthService._ -import io.iohk.ethereum.jsonrpc.MantisService.GetAccountTransactionsResponse -import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig +import io.iohk.ethereum.jsonrpc.NetService.{ListeningResponse, PeerCountResponse, VersionResponse} import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers.{ OptionNoneToJNullSerializer, QuantitiesSerializer, UnformattedDataJsonSerializer } -import io.iohk.ethereum.jsonrpc.NetService.{ListeningResponse, PeerCountResponse, VersionResponse} +import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpServer import io.iohk.ethereum.jsonrpc.server.ipc.JsonRpcIpcServer import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo import io.iohk.ethereum.network.p2p.messages.CommonMessages.Status import io.iohk.ethereum.network.p2p.messages.Versions -import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData import io.iohk.ethereum.{Fixtures, LongPatience, WithActorSystemShutDown} import monix.eval.Task import monix.execution.Scheduler.Implicits.global -import org.json4s.{DefaultFormats, Extraction, Formats, JArray, JBool, JInt, JObject, JString} +import org.json4s.{DefaultFormats, Formats, JArray, JObject, JString} import org.scalatest.concurrent.{Eventually, ScalaFutures} import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala index 0f6cabad98..6009620b30 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala @@ -3,11 +3,11 @@ package io.iohk.ethereum.jsonrpc import io.iohk.ethereum.jsonrpc.MantisService.GetAccountTransactionsResponse import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig import io.iohk.ethereum.nodebuilder.ApisBuilder -import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import io.iohk.ethereum.transactions.TransactionHistoryService.{ExtendedTransactionData, MinedTransactionData} import io.iohk.ethereum.utils.Config import io.iohk.ethereum.{Fixtures, FreeSpecBase, SpecFixtures} import monix.eval.Task -import org.json4s.{Extraction, JArray, JBool, JInt, JObject, JString} +import org.json4s.{Extraction, JArray, JBool, JInt, JLong, JObject, JString} import org.scalamock.scalatest.AsyncMockFactory class MantisJRCSpec extends FreeSpecBase with SpecFixtures with AsyncMockFactory with JRCMatchers { @@ -56,8 +56,12 @@ class MantisJRCSpec extends FreeSpecBase with SpecFixtures with AsyncMockFactory Right( GetAccountTransactionsResponse( List( - ExtendedTransactionData(sentTx, isOutgoing = true, Some((block.header, 0))), - ExtendedTransactionData(receivedTx, isOutgoing = false, Some((block.header, 1))) + ExtendedTransactionData(sentTx, isOutgoing = true, Some(MinedTransactionData(block.header, 0, 42))), + ExtendedTransactionData( + receivedTx, + isOutgoing = false, + Some(MinedTransactionData(block.header, 1, 21)) + ) ) ) ) @@ -86,7 +90,9 @@ class MantisJRCSpec extends FreeSpecBase with SpecFixtures with AsyncMockFactory .asInstanceOf[JObject] .obj ++ List( "isPending" -> JBool(false), - "isOutgoing" -> JBool(true) + "isOutgoing" -> JBool(true), + "timestamp" -> JLong(block.header.unixTimestamp), + "gasUsed" -> JString(s"0x${BigInt(42).toString(16)}") ) ), JObject( @@ -95,7 +101,9 @@ class MantisJRCSpec extends FreeSpecBase with SpecFixtures with AsyncMockFactory .asInstanceOf[JObject] .obj ++ List( "isPending" -> JBool(false), - "isOutgoing" -> JBool(false) + "isOutgoing" -> JBool(false), + "timestamp" -> JLong(block.header.unixTimestamp), + "gasUsed" -> JString(s"0x${BigInt(21).toString(16)}") ) ) ) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala index f247d70057..a3aa0f55e1 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala @@ -16,7 +16,7 @@ import io.iohk.ethereum.nodebuilder.{ TxPoolConfigBuilder } import io.iohk.ethereum.transactions.TransactionHistoryService -import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import io.iohk.ethereum.transactions.TransactionHistoryService.{ExtendedTransactionData, MinedTransactionData} import io.iohk.ethereum.{BlockHelpers, FreeSpecBase, SpecFixtures, WithActorSystemShutDown} import monix.eval.Task @@ -63,7 +63,7 @@ class MantisServiceSpec ExtendedTransactionData( fakeTransaction.tx, isOutgoing = true, - Some((block.header, 0)) + Some(MinedTransactionData(block.header, 0, 42)) ) ) diff --git a/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala b/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala index 9caa03c1d6..30b1e2ec8f 100644 --- a/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala @@ -5,8 +5,8 @@ import akka.testkit.{TestKit, TestProbe} import akka.util.ByteString import io.iohk.ethereum._ import io.iohk.ethereum.blockchain.sync.EphemBlockchainTestSetup -import io.iohk.ethereum.domain.{Address, Block, SignedTransaction, Transaction} -import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData +import io.iohk.ethereum.domain.{Address, Block, HashOutcome, Receipt, SignedTransaction, Transaction} +import io.iohk.ethereum.transactions.TransactionHistoryService.{ExtendedTransactionData, MinedTransactionData} import io.iohk.ethereum.transactions.testing.PendingTransactionsManagerAutoPilot import monix.eval.Task @@ -39,31 +39,38 @@ class TransactionHistoryServiceSpec val blockWithTx1 = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx1))) + val blockTx1Receipts = Seq(Receipt(HashOutcome(ByteString("foo")), 42, ByteString.empty, Nil)) val blockWithTxs2and3 = Block( Fixtures.Blocks.Block3125369.header.copy(number = 3125370), Fixtures.Blocks.Block3125369.body.copy(transactionList = Seq(tx2, tx3)) ) + val blockTx2And3Receipts = Seq( + Receipt(HashOutcome(ByteString("bar")), 43, ByteString.empty, Nil), + Receipt(HashOutcome(ByteString("baz")), 43 + 44, ByteString.empty, Nil) + ) val expectedTxs = Seq( ExtendedTransactionData( tx3, isOutgoing = false, - Some(blockWithTxs2and3.header -> 1) + Some(MinedTransactionData(blockWithTxs2and3.header, 1, 44)) ), ExtendedTransactionData( tx2, isOutgoing = false, - Some(blockWithTxs2and3.header -> 0) + Some(MinedTransactionData(blockWithTxs2and3.header, 0, 43)) ), - ExtendedTransactionData(tx1, isOutgoing = false, Some(blockWithTx1.header -> 0)) + ExtendedTransactionData(tx1, isOutgoing = false, Some(MinedTransactionData(blockWithTx1.header, 0, 42))) ) for { _ <- Task { blockchain .storeBlock(blockWithTx1) + .and(blockchain.storeReceipts(blockWithTx1.hash, blockTx1Receipts)) .and(blockchain.storeBlock(blockWithTxs2and3)) + .and(blockchain.storeReceipts(blockWithTxs2and3.hash, blockTx2And3Receipts)) .commit() } response <- transactionHistoryService.getAccountTransactions(address, BigInt(3125360) to BigInt(3125370)) From 47960a4f0214d21e81e189d427bbf5e9bd806a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Kope=C4=87?= Date: Fri, 20 Nov 2020 22:59:31 +0100 Subject: [PATCH 5/7] [ETCM-135] Update insomnia workspace --- insomnia_workspace.json | 339 ++++++++++-------- .../TransactionHistoryService.scala | 1 - 2 files changed, 188 insertions(+), 152 deletions(-) diff --git a/insomnia_workspace.json b/insomnia_workspace.json index 47f72a33b1..9abc3e2b22 100644 --- a/insomnia_workspace.json +++ b/insomnia_workspace.json @@ -1,32 +1,32 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2020-11-02T20:39:43.839Z", + "__export_date": "2020-11-20T21:57:27.426Z", "__export_source": "insomnia.desktop.app:v2020.4.2", "resources": [ { - "_id": "req_4222a4d54ba24fa7813429bdcdb732df", - "parentId": "fld_a7212a5b96194230a7e0abc76ee2bf26", - "modified": 1601637263922, - "created": 1601637164399, - "url": "{{ node_url }}", - "name": "checkpointing_getLatestBlock", + "_id": "req_3a0f1537c235444ea8f4b9dac488e14c", + "parentId": "fld_69840c51732b4213b880c2d1c48a212b", + "modified": 1605909416772, + "created": 1605907827178, + "url": "{{node_url}}", + "name": "mantis_getAccountTransactions", "description": "", "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"checkpointing_getLatestBlock\",\n\t\"params\": [5]\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"mantis_getAccountTransactions\",\n\t\"params\": [\"$address\", 1 , 999]\n}" }, "parameters": [], "headers": [ { "name": "Content-Type", "value": "application/json", - "id": "pair_a28efff3b44442d99b7f3fbc54d8c9b6" + "id": "pair_0a140acca58a4c679d227d29955f741e" } ], "authentication": {}, - "metaSortKey": -1601637164399, + "metaSortKey": -1605907827178, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -37,15 +37,15 @@ "_type": "request" }, { - "_id": "fld_a7212a5b96194230a7e0abc76ee2bf26", + "_id": "fld_69840c51732b4213b880c2d1c48a212b", "parentId": "wrk_097d43914a4d4aea8b6f73f647921182", - "modified": 1601637156313, - "created": 1601637156313, - "name": "Checkpointing", + "modified": 1605907810643, + "created": 1605907810643, + "name": "mantis", "description": "", "environment": {}, "environmentPropertyOrder": null, - "metaSortKey": -1601637156313, + "metaSortKey": -1605907810643, "_type": "request_group" }, { @@ -59,17 +59,17 @@ "_type": "workspace" }, { - "_id": "req_da1e409360394849b673ec4e27f542b6", + "_id": "req_4222a4d54ba24fa7813429bdcdb732df", "parentId": "fld_a7212a5b96194230a7e0abc76ee2bf26", - "modified": 1601637193229, - "created": 1601637186866, + "modified": 1601637263922, + "created": 1601637164399, "url": "{{ node_url }}", - "name": "checkpointing_pushCheckpoint", + "name": "checkpointing_getLatestBlock", "description": "", "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"checkpointing_pushCheckpoint\",\n\t\"params\": [\n\t\t\"127d6fde40d20208641c057a1ad4d12d44433881a660b15ac99f04f25762fb9b\",\n\t\t[\n\t\"2194b40851c648e7570e75ea2c507887d11c2270f7523469953fc5c3d5e0f50f48d73ea0b827eb81bb2fc0511f09d10b8f1d3f88e251ed231bb0f5cd03826d281b\",\n\t\t\t\"bbd4ae567202a6e7f40826c964a918760253596bb92052ea7ef4b30338b19fc12d56d497c88f0f13eff0ad542a8a4c1069559cb43e9741b849bf6577287450e31b\"\n\t\t]\n\t]\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"checkpointing_getLatestBlock\",\n\t\"params\": [5]\n}" }, "parameters": [], "headers": [ @@ -80,43 +80,7 @@ } ], "authentication": {}, - "metaSortKey": -1583661254601.5, - "isPrivate": false, - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_5c84aeff984d41e4aca173ac56a0b113", - "parentId": "fld_9f9137459d5c429d83901f5c682be0f9", - "modified": 1604346888565, - "created": 1603997152827, - "url": "{{ faucet_url }}", - "name": "send_funds", - "description": "", - "method": "POST", - "body": - { - "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"faucet_sendFunds\", \n \"params\": [\"$address\"],\n \"id\": 1\n}" - }, - "parameters": [], - "headers": - [ - { - "name": "Content-Type", - "value": "application/json", - "description": "", - "id": "pair_b080d7c5b5194ad09efdd6926ed108c5", - "disabled": false - } - ], - "authentication": {}, - "metaSortKey": -1603997152827, + "metaSortKey": -1601637164399, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -127,42 +91,40 @@ "_type": "request" }, { - "_id": "fld_9f9137459d5c429d83901f5c682be0f9", + "_id": "fld_a7212a5b96194230a7e0abc76ee2bf26", "parentId": "wrk_097d43914a4d4aea8b6f73f647921182", - "modified": 1604349428103, - "created": 1603918083216, - "name": "faucet", + "modified": 1601637156313, + "created": 1601637156313, + "name": "Checkpointing", "description": "", "environment": {}, "environmentPropertyOrder": null, - "metaSortKey": -1552939140242, + "metaSortKey": -1601637156313, "_type": "request_group" }, { - "_id": "req_48316ba9ba834bcc94fdeae41d966eb9", - "parentId": "fld_9f9137459d5c429d83901f5c682be0f9", - "modified": 1604346774105, - "created": 1604338553808, - "url": "{{ faucet_url }}", - "name": "status", + "_id": "req_da1e409360394849b673ec4e27f542b6", + "parentId": "fld_a7212a5b96194230a7e0abc76ee2bf26", + "modified": 1601637193229, + "created": 1601637186866, + "url": "{{ node_url }}", + "name": "checkpointing_pushCheckpoint", "description": "", "method": "POST", - "body": - { + "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"faucet_status\", \n \"params\": [],\n \"id\": 1\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"checkpointing_pushCheckpoint\",\n\t\"params\": [\n\t\t\"127d6fde40d20208641c057a1ad4d12d44433881a660b15ac99f04f25762fb9b\",\n\t\t[\n\t\"2194b40851c648e7570e75ea2c507887d11c2270f7523469953fc5c3d5e0f50f48d73ea0b827eb81bb2fc0511f09d10b8f1d3f88e251ed231bb0f5cd03826d281b\",\n\t\t\t\"bbd4ae567202a6e7f40826c964a918760253596bb92052ea7ef4b30338b19fc12d56d497c88f0f13eff0ad542a8a4c1069559cb43e9741b849bf6577287450e31b\"\n\t\t]\n\t]\n}" }, "parameters": [], - "headers": - [ + "headers": [ { "name": "Content-Type", "value": "application/json", - "id": "pair_bce95e5eaba54223bb10210f4563af71" + "id": "pair_a28efff3b44442d99b7f3fbc54d8c9b6" } ], "authentication": {}, - "metaSortKey": -1604338553809, + "metaSortKey": -1583661254601.5, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -173,33 +135,28 @@ "_type": "request" }, { - "_id": "req_cd0078ce4a034ebdbdf7dc9e20e78a29", + "_id": "req_f2986c964bf74360878144213ca342a7", "parentId": "fld_2b54cbb84e244284b3ef752c5f805376", - "modified": 1600249644998, - "created": 1600249397450, + "modified": 1573042783130, + "created": 1573042743181, "url": "{{ node_url }}", - "name": "qa_mineBlocks", + "name": "qa_getFederationMembersInfo", "description": "", "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"qa_mineBlocks\", \n \"params\": [1, true],\n \"id\": 1\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"qa_getFederationMembersInfo\",\n\t\"params\": []\n}" }, "parameters": [], "headers": [ { - "id": "pair_9f4d6a9dde554cd384487e04fa3b21aa", + "id": "pair_ba5a5914f02a4b27b86a330054582828", "name": "Content-Type", "value": "application/json" - }, - { - "id": "pair_088edc31f5e04f20a16b465a673871bb", - "name": "Cache-Control", - "value": "no-cache" } ], "authentication": {}, - "metaSortKey": -1552939150156.9297, + "metaSortKey": -1568046037126, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -223,13 +180,18 @@ }, { "_id": "req_3ae9151dec5b4046b06fdb3408e9ab1f", - "authentication": {}, + "parentId": "fld_2b54cbb84e244284b3ef752c5f805376", + "modified": 1572263390995, + "created": 1572007151716, + "url": "{{ node_url }}", + "name": "qa_generateCheckpoint", + "description": "", + "method": "POST", "body": { "mimeType": "application/json", "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"qa_generateCheckpoint\",\n\t\"params\": [\t\t[\"0x926db397ed35bedee660fe5bf6879679fa71f1fe8c27823f7f6a1e5d96a49b94\"], \"0xe160bdd7664e1b921612a4ed225321bdf3b8f70beb6b968c7a423d6022e39e16\"\n\t]\n}" }, - "created": 1572007151716, - "description": "", + "parameters": [], "headers": [ { "id": "pair_6f2ea2de85ee4eabbbd25bde888e9dc1", @@ -237,31 +199,31 @@ "value": "application/json" } ], - "isPrivate": false, + "authentication": {}, "metaSortKey": -1566535778023.25, - "method": "POST", - "modified": 1572263390995, - "name": "qa_generateCheckpoint", - "parameters": [], - "parentId": "fld_2b54cbb84e244284b3ef752c5f805376", + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, "settingDisableRenderRequestBody": false, "settingEncodeUrl": true, - "settingFollowRedirects": "global", "settingRebuildPath": true, - "settingSendCookies": true, - "settingStoreCookies": true, - "url": "{{ node_url }}", + "settingFollowRedirects": "global", "_type": "request" }, { "_id": "req_e1287e4fcba348eea9d326bb208092c3", - "authentication": {}, + "parentId": "fld_2b54cbb84e244284b3ef752c5f805376", + "modified": 1573216279799, + "created": 1573216065706, + "url": "{{ node_url }}", + "name": "qa_generateCheckpoint (for latest block)", + "description": "", + "method": "POST", "body": { "mimeType": "application/json", "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"qa_generateCheckpoint\",\n\t\"params\": [\t\t[\"0x926db397ed35bedee660fe5bf6879679fa71f1fe8c27823f7f6a1e5d96a49b94\"]\n\t]\n}" }, - "created": 1573216065706, - "description": "", + "parameters": [], "headers": [ { "id": "pair_6f2ea2de85ee4eabbbd25bde888e9dc1", @@ -269,52 +231,52 @@ "value": "application/json" } ], - "isPrivate": false, + "authentication": {}, "metaSortKey": -1566394039140.875, - "method": "POST", - "modified": 1573216279799, - "name": "qa_generateCheckpoint (for latest block)", - "parameters": [], - "parentId": "fld_2b54cbb84e244284b3ef752c5f805376", + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, "settingDisableRenderRequestBody": false, "settingEncodeUrl": true, - "settingFollowRedirects": "global", "settingRebuildPath": true, - "settingSendCookies": true, - "settingStoreCookies": true, - "url": "{{ node_url }}", + "settingFollowRedirects": "global", "_type": "request" }, { - "_id": "req_f2986c964bf74360878144213ca342a7", - "authentication": {}, + "_id": "req_cd0078ce4a034ebdbdf7dc9e20e78a29", + "parentId": "fld_2b54cbb84e244284b3ef752c5f805376", + "modified": 1600249644998, + "created": 1600249397450, + "url": "{{ node_url }}", + "name": "qa_mineBlocks", + "description": "", + "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"id\": 1,\n\t\"method\": \"qa_getFederationMembersInfo\",\n\t\"params\": []\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"qa_mineBlocks\", \n \"params\": [1, true],\n \"id\": 1\n}" }, - "created": 1573042743181, - "description": "", + "parameters": [], "headers": [ { - "id": "pair_ba5a5914f02a4b27b86a330054582828", + "id": "pair_9f4d6a9dde554cd384487e04fa3b21aa", "name": "Content-Type", "value": "application/json" + }, + { + "id": "pair_088edc31f5e04f20a16b465a673871bb", + "name": "Cache-Control", + "value": "no-cache" } ], + "authentication": {}, + "metaSortKey": -1552939150156.9297, "isPrivate": false, - "metaSortKey": -1568046037126, - "method": "POST", - "modified": 1573042783130, - "name": "qa_getFederationMembersInfo", - "parameters": [], - "parentId": "fld_2b54cbb84e244284b3ef752c5f805376", + "settingStoreCookies": true, + "settingSendCookies": true, "settingDisableRenderRequestBody": false, "settingEncodeUrl": true, - "settingFollowRedirects": "global", "settingRebuildPath": true, - "settingSendCookies": true, - "settingStoreCookies": true, - "url": "{{ node_url }}", + "settingFollowRedirects": "global", "_type": "request" }, { @@ -883,7 +845,7 @@ { "_id": "req_7770f112c5cb4fc4a0f2d7fea4f6166e", "parentId": "fld_a06eb77e183c4727800eb7dc43ceabe1", - "modified": 1599825954655, + "modified": 1605909042772, "created": 1554490202940, "url": "{{ node_url }}", "name": "eth_getTransactionByHash", @@ -891,7 +853,7 @@ "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"eth_getTransactionByHash\", \n\t\"params\": [\"0x926db397ed35bedee660fe5bf6879679fa71f1fe8c27823f7f6a1e5d96a49b94\"],\n \"id\": 1\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"eth_getTransactionByHash\", \n\t\"params\": [\"$tx_hash\"],\n \"id\": 1\n}" }, "parameters": [], "headers": [ @@ -1066,17 +1028,17 @@ "_type": "request" }, { - "_id": "req_71950018809a482da79fc927070de862", + "_id": "req_4c1135a4a69644fe9850292131197b47", "parentId": "fld_a06eb77e183c4727800eb7dc43ceabe1", - "modified": 1599825973333, - "created": 1576585718979, + "modified": 1602234543563, + "created": 1602234438767, "url": "{{ node_url }}", - "name": "eth_getBalance", - "description": "", + "name": "eth_pendingTransactions", + "description": "Returns the transactions that are pending in the transaction pool and have a from address that is one of the accounts this node manages", "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"eth_getBalance\", \n\t\"params\": [\n\t\t\"$address\", \"$hexBlockNumber\"\n\t],\n \"id\": 1\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"eth_pendingTransactions\", \n\t\"params\": [],\n \"id\": 1\n}" }, "parameters": [], "headers": [ @@ -1092,7 +1054,7 @@ } ], "authentication": {}, - "metaSortKey": -1552732410716.25, + "metaSortKey": -1552732410719.375, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -1103,17 +1065,17 @@ "_type": "request" }, { - "_id": "req_4c1135a4a69644fe9850292131197b47", + "_id": "req_71950018809a482da79fc927070de862", "parentId": "fld_a06eb77e183c4727800eb7dc43ceabe1", - "modified": 1602234543563, - "created": 1602234438767, + "modified": 1599825973333, + "created": 1576585718979, "url": "{{ node_url }}", - "name": "eth_pendingTransactions", - "description": "Returns the transactions that are pending in the transaction pool and have a from address that is one of the accounts this node manages", + "name": "eth_getBalance", + "description": "", "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"eth_pendingTransactions\", \n\t\"params\": [],\n \"id\": 1\n}" + "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"eth_getBalance\", \n\t\"params\": [\n\t\t\"$address\", \"$hexBlockNumber\"\n\t],\n \"id\": 1\n}" }, "parameters": [], "headers": [ @@ -1129,7 +1091,7 @@ } ], "authentication": {}, - "metaSortKey": -1552732410719.375, + "metaSortKey": -1552732410716.25, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -1250,6 +1212,84 @@ "settingFollowRedirects": "global", "_type": "request" }, + { + "_id": "req_48316ba9ba834bcc94fdeae41d966eb9", + "parentId": "fld_9f9137459d5c429d83901f5c682be0f9", + "modified": 1604346774105, + "created": 1604338553808, + "url": "{{ faucet_url }}", + "name": "status", + "description": "", + "method": "POST", + "body": { + "mimeType": "application/json", + "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"faucet_status\", \n \"params\": [],\n \"id\": 1\n}" + }, + "parameters": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "id": "pair_bce95e5eaba54223bb10210f4563af71" + } + ], + "authentication": {}, + "metaSortKey": -1604338553809, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "fld_9f9137459d5c429d83901f5c682be0f9", + "parentId": "wrk_097d43914a4d4aea8b6f73f647921182", + "modified": 1604349428103, + "created": 1603918083216, + "name": "faucet", + "description": "", + "environment": {}, + "environmentPropertyOrder": null, + "metaSortKey": -1552939140242, + "_type": "request_group" + }, + { + "_id": "req_5c84aeff984d41e4aca173ac56a0b113", + "parentId": "fld_9f9137459d5c429d83901f5c682be0f9", + "modified": 1604346888565, + "created": 1603997152827, + "url": "{{ faucet_url }}", + "name": "send_funds", + "description": "", + "method": "POST", + "body": { + "mimeType": "application/json", + "text": "{\n\t\"jsonrpc\": \"2.0\",\n \"method\": \"faucet_sendFunds\", \n \"params\": [\"$address\"],\n \"id\": 1\n}" + }, + "parameters": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "description": "", + "id": "pair_b080d7c5b5194ad09efdd6926ed108c5", + "disabled": false + } + ], + "authentication": {}, + "metaSortKey": -1603997152827, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, { "_id": "env_ee4c8118750744559d3b1020845fe5d4", "parentId": "wrk_097d43914a4d4aea8b6f73f647921182", @@ -1288,15 +1328,12 @@ "modified": 1604347516298, "created": 1552663140073, "name": "Develop", - "data": - { + "data": { "node_url": "http://127.0.0.1:8546", "faucet_url": "http://127.0.0.1:8099" }, - "dataPropertyOrder": - { - "&": - [ + "dataPropertyOrder": { + "&": [ "node_url", "faucet_url" ] diff --git a/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala index 2417bb63b0..360b95ac58 100644 --- a/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala +++ b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala @@ -17,7 +17,6 @@ import monix.reactive.{Observable, OverflowStrategy} import scala.collection.immutable.NumericRange import scala.concurrent.duration.FiniteDuration -import scala.language.higherKinds class TransactionHistoryService( blockchain: Blockchain, From 29ca2af093e5764ad88bf07cf7f50e98643ff441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Kope=C4=87?= Date: Mon, 23 Nov 2020 14:45:47 +0100 Subject: [PATCH 6/7] [ETCM-135] Revert lazy vals in favor of vals --- .../iohk/ethereum/sync/util/CommonFakePeer.scala | 9 ++++++++- .../iohk/ethereum/nodebuilder/NodeBuilder.scala | 15 ++++++++++----- .../ethereum/blockchain/sync/StateSyncSpec.scala | 8 +++++++- .../io/iohk/ethereum/domain/BlockchainSpec.scala | 16 +++++++++++----- .../ethereum/jsonrpc/MantisServiceSpec.scala | 4 ++-- .../io/iohk/ethereum/utils/VersionInfoSpec.scala | 3 ++- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/it/scala/io/iohk/ethereum/sync/util/CommonFakePeer.scala b/src/it/scala/io/iohk/ethereum/sync/util/CommonFakePeer.scala index e56e0ca31a..983796892e 100644 --- a/src/it/scala/io/iohk/ethereum/sync/util/CommonFakePeer.scala +++ b/src/it/scala/io/iohk/ethereum/sync/util/CommonFakePeer.scala @@ -25,7 +25,14 @@ import io.iohk.ethereum.network.p2p.EthereumMessageDecoder import io.iohk.ethereum.network.p2p.messages.CommonMessages.NewBlock import io.iohk.ethereum.network.rlpx.AuthHandshaker import io.iohk.ethereum.network.rlpx.RLPxConnectionHandler.RLPxConfiguration -import io.iohk.ethereum.network.{EtcPeerManagerActor, ForkResolver, KnownNodesManager, PeerEventBusActor, PeerManagerActor, ServerActor} +import io.iohk.ethereum.network.{ + EtcPeerManagerActor, + ForkResolver, + KnownNodesManager, + PeerEventBusActor, + PeerManagerActor, + ServerActor +} import io.iohk.ethereum.nodebuilder.{PruningConfigBuilder, SecureRandomBuilder} import io.iohk.ethereum.sync.util.SyncCommonItSpec._ import io.iohk.ethereum.sync.util.SyncCommonItSpecUtils._ diff --git a/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala b/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala index 0eb7576879..41dcff30e8 100644 --- a/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala +++ b/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala @@ -280,9 +280,14 @@ object PendingTransactionsManagerBuilder { } trait TransactionHistoryServiceBuilder { - self: BlockchainBuilder with PendingTransactionsManagerBuilder with TxPoolConfigBuilder => - lazy val transactionHistoryService = - new TransactionHistoryService(blockchain, pendingTransactionsManager, txPoolConfig.getTransactionFromPoolTimeout) + def transactionHistoryService: TransactionHistoryService +} +object TransactionHistoryServiceBuilder { + trait Default extends TransactionHistoryServiceBuilder { + self: BlockchainBuilder with PendingTransactionsManagerBuilder with TxPoolConfigBuilder => + val transactionHistoryService = + new TransactionHistoryService(blockchain, pendingTransactionsManager, txPoolConfig.getTransactionFromPoolTimeout) + } } trait FilterManagerBuilder { @@ -346,7 +351,7 @@ trait EthServiceBuilder { with JSONRpcConfigBuilder with AsyncConfigBuilder => - lazy val ethService = new EthService( + val ethService = new EthService( blockchain, ledger, stxLedger, @@ -685,4 +690,4 @@ trait Node with KeyStoreConfigBuilder with AsyncConfigBuilder with CheckpointBlockGeneratorBuilder - with TransactionHistoryServiceBuilder + with TransactionHistoryServiceBuilder.Default diff --git a/src/test/scala/io/iohk/ethereum/blockchain/sync/StateSyncSpec.scala b/src/test/scala/io/iohk/ethereum/blockchain/sync/StateSyncSpec.scala index e9789dc6e9..af6810ccdc 100644 --- a/src/test/scala/io/iohk/ethereum/blockchain/sync/StateSyncSpec.scala +++ b/src/test/scala/io/iohk/ethereum/blockchain/sync/StateSyncSpec.scala @@ -9,7 +9,13 @@ import akka.testkit.{TestKit, TestProbe} import akka.util.ByteString import io.iohk.ethereum.blockchain.sync.StateSyncUtils.{MptNodeData, TrieProvider} import io.iohk.ethereum.blockchain.sync.fast.{SyncStateScheduler, SyncStateSchedulerActor} -import io.iohk.ethereum.blockchain.sync.fast.SyncStateSchedulerActor.{RestartRequested, StartSyncingTo, StateSyncFinished, StateSyncStats, WaitingForNewTargetBlock} +import io.iohk.ethereum.blockchain.sync.fast.SyncStateSchedulerActor.{ + RestartRequested, + StartSyncingTo, + StateSyncFinished, + StateSyncStats, + WaitingForNewTargetBlock +} import io.iohk.ethereum.db.dataSource.RocksDbDataSource.IterationError import io.iohk.ethereum.domain.{Address, BlockchainImpl, ChainWeight} import io.iohk.ethereum.network.EtcPeerManagerActor.{GetHandshakedPeers, HandshakedPeers, PeerInfo, SendMessage} diff --git a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala index 37448a9b3f..6f0c32674a 100644 --- a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala +++ b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala @@ -154,15 +154,17 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh } blockchainWithStubPersisting.getBestBlockNumber() shouldBe blocksToImport.last.number - blockchainStoragesWithStubPersisting.appStateStorage.getBestBlockNumber() shouldBe blockImportToPersist.fold(0: BigInt)(_.number) - + blockchainStoragesWithStubPersisting.appStateStorage.getBestBlockNumber() shouldBe blockImportToPersist.fold( + 0: BigInt + )(_.number) // Rollback blocks val numberBlocksToRollback = intGen(0, numberBlocksToImport).sample.get val (blocksNotRollbacked, blocksToRollback) = blocksToImport.splitAt(numberBlocksToRollback) // Randomly select the block rollback to persist (empty means no persistance) - val blockRollbackToPersist = if (blocksToRollback.isEmpty) None else Gen.option(Gen.oneOf(blocksToRollback)).sample.get + val blockRollbackToPersist = + if (blocksToRollback.isEmpty) None else Gen.option(Gen.oneOf(blocksToRollback)).sample.get (stubStateStorage .onBlockRollback(_: BigInt, _: BigInt)(_: () => Unit)) .when(*, *, *) @@ -188,14 +190,18 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh trait TestSetup extends MockFactory { val maxNumberBlocksToImport: Int = 30 - def calculatePersistedBestBlock(blockImportPersisted: Option[BigInt], blockRollbackPersisted: Option[BigInt], blocksRollbacked: Seq[BigInt]): BigInt = { + def calculatePersistedBestBlock( + blockImportPersisted: Option[BigInt], + blockRollbackPersisted: Option[BigInt], + blocksRollbacked: Seq[BigInt] + ): BigInt = { (blocksRollbacked, blockImportPersisted) match { case (Nil, Some(bi)) => // No blocks rollbacked, last persist was the persist during import bi case (nonEmptyRollbackedBlocks, Some(bi)) => // Last forced persist during apply/rollback - val maxForcedPersist = blockRollbackPersisted.fold(bi){ br => (br - 1).max(bi)} + val maxForcedPersist = blockRollbackPersisted.fold(bi) { br => (br - 1).max(bi) } // The above number would have been decreased by any rollbacked blocks (nonEmptyRollbackedBlocks.head - 1).min(maxForcedPersist) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala index a3aa0f55e1..4930619a01 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisServiceSpec.scala @@ -28,7 +28,7 @@ class MantisServiceSpec with SpecFixtures with WithActorSystemShutDown { class Fixture - extends TransactionHistoryServiceBuilder + extends TransactionHistoryServiceBuilder.Default with EphemBlockchainTestSetup with PendingTransactionsManagerBuilder with TxPoolConfigBuilder @@ -67,7 +67,7 @@ class MantisServiceSpec ) ) - override lazy val transactionHistoryService: TransactionHistoryService = + override val transactionHistoryService: TransactionHistoryService = new TransactionHistoryService( blockchain, pendingTransactionsManager, diff --git a/src/test/scala/io/iohk/ethereum/utils/VersionInfoSpec.scala b/src/test/scala/io/iohk/ethereum/utils/VersionInfoSpec.scala index e864824e4b..3db07a08e0 100644 --- a/src/test/scala/io/iohk/ethereum/utils/VersionInfoSpec.scala +++ b/src/test/scala/io/iohk/ethereum/utils/VersionInfoSpec.scala @@ -7,7 +7,8 @@ class VersionInfoSpec extends AnyFlatSpec with Matchers { behavior of "nodeName" it should "match ethstats expected structure and preserve major and minor Java version" in { - VersionInfo.nodeName() should fullyMatch regex """mantis/v\d(\.\d+)*-[a-z0-9]{7}/[^/]+-[^/]+/[^/]+-.[^/]+-java-\d+\.\d+[._0-9]*""" + VersionInfo + .nodeName() should fullyMatch regex """mantis/v\d(\.\d+)*-[a-z0-9]{7}/[^/]+-[^/]+/[^/]+-.[^/]+-java-\d+\.\d+[._0-9]*""" } it should "augment the name with an identity" in { From 6c26ff1745b8f41b618ab2eefb4d06fc6e5a22df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Kope=C4=87?= Date: Wed, 25 Nov 2020 13:24:33 +0100 Subject: [PATCH 7/7] [ETCM-135] Fix serialization issue in eth_syncing --- src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala | 4 ++-- src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala | 4 ++-- .../io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala index 3c8814dd0b..a1520746e2 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala @@ -621,8 +621,8 @@ class EthService( startingBlock = startingBlockNumber, currentBlock = blocksProgress.current, highestBlock = blocksProgress.target, - knownStates = stateNodesProgress.current, - pulledStates = stateNodesProgress.target + knownStates = stateNodesProgress.target, + pulledStates = stateNodesProgress.current ) ) ) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala index 6368740c30..a6cbfc0acd 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala @@ -509,8 +509,8 @@ class EthServiceSpec startingBlock = 999, currentBlock = 200, highestBlock = 10000, - knownStates = 100, - pulledStates = 144 + knownStates = 144, + pulledStates = 100 ) ) ) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala index e5f79f8462..e865f9bec3 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala @@ -85,8 +85,8 @@ class JsonRpcControllerEthSpec "startingBlock" -> "0x3e7", "currentBlock" -> "0xc8", "highestBlock" -> "0x2710", - "knownStates" -> "0x64", - "pulledStates" -> "0x90" + "knownStates" -> "0x90", + "pulledStates" -> "0x64" ) }