Skip to content

Commit adfa1f2

Browse files
authored
[ETCM-135] Tx history improvements for wallet (#808)
1 parent dbbd199 commit adfa1f2

29 files changed

+942
-419
lines changed

insomnia_workspace.json

Lines changed: 188 additions & 151 deletions
Large diffs are not rendered by default.

src/it/scala/io/iohk/ethereum/sync/util/CommonFakePeer.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ import io.iohk.ethereum.network.p2p.EthereumMessageDecoder
2525
import io.iohk.ethereum.network.p2p.messages.CommonMessages.NewBlock
2626
import io.iohk.ethereum.network.rlpx.AuthHandshaker
2727
import io.iohk.ethereum.network.rlpx.RLPxConnectionHandler.RLPxConfiguration
28-
import io.iohk.ethereum.network.{EtcPeerManagerActor, ForkResolver, KnownNodesManager, PeerEventBusActor, PeerManagerActor, ServerActor}
28+
import io.iohk.ethereum.network.{
29+
EtcPeerManagerActor,
30+
ForkResolver,
31+
KnownNodesManager,
32+
PeerEventBusActor,
33+
PeerManagerActor,
34+
ServerActor
35+
}
2936
import io.iohk.ethereum.nodebuilder.{PruningConfigBuilder, SecureRandomBuilder}
3037
import io.iohk.ethereum.sync.util.SyncCommonItSpec._
3138
import io.iohk.ethereum.sync.util.SyncCommonItSpecUtils._

src/main/resources/application.conf

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,11 @@ mantis {
207207
}
208208

209209
# Enabled JSON-RPC APIs over the JSON-RPC endpoint
210-
# Available choices are: web3, eth, net, personal, daedalus, test, iele, debug, qa, checkpointing
211-
apis = "eth,web3,net,personal,daedalus,debug,qa,checkpointing"
210+
# Available choices are: web3, eth, net, personal, mantis, test, iele, debug, qa, checkpointing
211+
apis = "eth,web3,net,personal,mantis,debug,qa,checkpointing"
212212

213-
# Maximum number of blocks for daedalus_getAccountTransactions
214-
account-transactions-max-blocks = 50000
213+
# Maximum number of blocks for mantis_getAccountTransactions
214+
account-transactions-max-blocks = 1000
215215

216216
net {
217217
peer-manager-timeout = 5.seconds

src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import akka.util.ByteString
44
import io.iohk.ethereum.jsonrpc.EthService._
55
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
66
import io.iohk.ethereum.jsonrpc.PersonalService.{SendTransactionRequest, SendTransactionResponse, SignRequest}
7+
import io.iohk.ethereum.jsonrpc.serialization.JsonEncoder.OptionToNull._
78
import io.iohk.ethereum.jsonrpc.serialization.JsonMethodDecoder.NoParamsMethodDecoder
89
import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec, JsonMethodDecoder}
910
import org.json4s.JsonAST.{JArray, JBool, JString, JValue, _}
@@ -12,6 +13,7 @@ import org.json4s.{Extraction, JsonAST}
1213

1314
// scalastyle:off number.of.methods
1415
object EthJsonMethodsImplicits extends JsonMethodsImplicits {
16+
implicit val transactionResponseJsonEncoder: JsonEncoder[TransactionResponse] = Extraction.decompose(_)
1517

1618
implicit val eth_protocolVersion = new NoParamsMethodDecoder(ProtocolVersionRequest())
1719
with JsonEncoder[ProtocolVersionResponse] {
@@ -148,7 +150,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits {
148150
}
149151

150152
override def encodeJson(t: GetTransactionByHashResponse): JValue =
151-
Extraction.decompose(t.txResponse)
153+
JsonEncoder.encode(t.txResponse)
152154
}
153155

154156
implicit val eth_getTransactionReceipt =
@@ -169,7 +171,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits {
169171
implicit val GetTransactionByBlockHashAndIndexResponseEncoder =
170172
new JsonEncoder[GetTransactionByBlockHashAndIndexResponse] {
171173
override def encodeJson(t: GetTransactionByBlockHashAndIndexResponse): JValue =
172-
t.transactionResponse.map(Extraction.decompose).getOrElse(JNull)
174+
JsonEncoder.encode(t.transactionResponse)
173175
}
174176

175177
implicit val GetTransactionByBlockHashAndIndexRequestDecoder =
@@ -188,7 +190,7 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits {
188190
implicit val GetTransactionByBlockNumberAndIndexResponseEncoder =
189191
new JsonEncoder[GetTransactionByBlockNumberAndIndexResponse] {
190192
override def encodeJson(t: GetTransactionByBlockNumberAndIndexResponse): JValue =
191-
t.transactionResponse.map(Extraction.decompose).getOrElse(JNull)
193+
JsonEncoder.encode(t.transactionResponse)
192194
}
193195

194196
implicit val GetTransactionByBlockNumberAndIndexRequestDecoder =
@@ -575,23 +577,6 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits {
575577
)
576578
}
577579

578-
implicit val daedalus_getAccountTransactions =
579-
new JsonMethodDecoder[GetAccountTransactionsRequest] with JsonEncoder[GetAccountTransactionsResponse] {
580-
def decodeJson(params: Option[JArray]): Either[JsonRpcError, GetAccountTransactionsRequest] =
581-
params match {
582-
case Some(JArray(JString(addrJson) :: fromBlockJson :: toBlockJson :: Nil)) =>
583-
for {
584-
addr <- extractAddress(addrJson)
585-
fromBlock <- extractQuantity(fromBlockJson)
586-
toBlock <- extractQuantity(toBlockJson)
587-
} yield GetAccountTransactionsRequest(addr, fromBlock, toBlock)
588-
case _ => Left(InvalidParams())
589-
}
590-
591-
override def encodeJson(t: GetAccountTransactionsResponse): JValue =
592-
JObject("transactions" -> JArray(t.transactions.map(Extraction.decompose).toList))
593-
}
594-
595580
implicit val eth_getStorageRoot = new JsonMethodDecoder[GetStorageRootRequest]
596581
with JsonEncoder[GetStorageRootResponse] {
597582
def decodeJson(params: Option[JArray]): Either[JsonRpcError, GetStorageRootRequest] =

src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala

Lines changed: 13 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package io.iohk.ethereum.jsonrpc
22

3-
import java.time.Duration
4-
import java.util.Date
5-
import java.util.concurrent.atomic.AtomicReference
63
import akka.actor.ActorRef
74
import akka.util.{ByteString, Timeout}
85
import cats.syntax.either._
@@ -15,8 +12,10 @@ import io.iohk.ethereum.consensus.ethash.EthashUtils
1512
import io.iohk.ethereum.crypto._
1613
import io.iohk.ethereum.db.storage.TransactionMappingStorage.TransactionLocation
1714
import io.iohk.ethereum.domain.{BlockHeader, SignedTransaction, UInt256, _}
15+
import io.iohk.ethereum.jsonrpc.AkkaTaskOps._
1816
import io.iohk.ethereum.jsonrpc.FilterManager.{FilterChanges, FilterLogs, LogFilterLogs, TxLog}
1917
import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig
18+
import io.iohk.ethereum.jsonrpc.{FilterManager => FM}
2019
import io.iohk.ethereum.keystore.KeyStore
2120
import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, Ledger, StxLedger}
2221
import io.iohk.ethereum.mpt.MerklePatriciaTrie.MissingNodeException
@@ -29,10 +28,12 @@ import io.iohk.ethereum.rlp.UInt256RLPImplicits._
2928
import io.iohk.ethereum.transactions.PendingTransactionsManager
3029
import io.iohk.ethereum.transactions.PendingTransactionsManager.{PendingTransaction, PendingTransactionsResponse}
3130
import io.iohk.ethereum.utils._
32-
import io.iohk.ethereum.jsonrpc.AkkaTaskOps._
33-
import io.iohk.ethereum.jsonrpc.{FilterManager => FM}
3431
import monix.eval.Task
3532
import org.bouncycastle.util.encoders.Hex
33+
34+
import java.time.Duration
35+
import java.util.Date
36+
import java.util.concurrent.atomic.AtomicReference
3637
import scala.collection.concurrent.{TrieMap, Map => ConcurrentMap}
3738
import scala.concurrent.duration.FiniteDuration
3839
import scala.language.existentials
@@ -78,9 +79,6 @@ object EthService {
7879
case class GetTransactionByHashRequest(txHash: ByteString)
7980
case class GetTransactionByHashResponse(txResponse: Option[TransactionResponse])
8081

81-
case class GetAccountTransactionsRequest(address: Address, fromBlock: BigInt, toBlock: BigInt)
82-
case class GetAccountTransactionsResponse(transactions: Seq[TransactionResponse])
83-
8482
case class GetTransactionReceiptRequest(txHash: ByteString)
8583
case class GetTransactionReceiptResponse(txResponse: Option[TransactionReceiptResponse])
8684

@@ -325,7 +323,7 @@ class EthService(
325323
}
326324

327325
def getTransactionDataByHash(txHash: ByteString): Task[Option[TransactionData]] = {
328-
val maybeTxPendingResponse: Task[Option[TransactionData]] = getTransactionsFromPool().map {
326+
val maybeTxPendingResponse: Task[Option[TransactionData]] = getTransactionsFromPool.map {
329327
_.pendingTransactions.map(_.stx.tx).find(_.hash == txHash).map(TransactionData(_))
330328
}
331329

@@ -541,7 +539,7 @@ class EthService(
541539
reportActive()
542540
val bestBlock = blockchain.getBestBlock()
543541
val response: ServiceResponse[GetWorkResponse] =
544-
Task.parZip2(getOmmersFromPool(bestBlock.hash), getTransactionsFromPool()).map { case (ommers, pendingTxs) =>
542+
Task.parZip2(getOmmersFromPool(bestBlock.hash), getTransactionsFromPool).map { case (ommers, pendingTxs) =>
545543
val blockGenerator = ethash.blockGenerator
546544
val PendingBlockAndState(pb, _) = blockGenerator.generateBlock(
547545
bestBlock,
@@ -575,13 +573,13 @@ class EthService(
575573
})(Task.now(OmmersPool.Ommers(Nil))) // NOTE If not Ethash consensus, ommers do not make sense, so => Nil
576574

577575
// TODO This seems to be re-implemented in TransactionPicker, probably move to a better place? Also generalize the error message.
578-
private[jsonrpc] def getTransactionsFromPool(): Task[PendingTransactionsResponse] = {
576+
private[jsonrpc] val getTransactionsFromPool: Task[PendingTransactionsResponse] = {
579577
implicit val timeout: Timeout = Timeout(getTransactionFromPoolTimeout)
580578

581579
pendingTransactionsManager
582580
.askFor[PendingTransactionsResponse](PendingTransactionsManager.GetPendingTransactions)
583581
.onErrorRecoverWith { case ex: Throwable =>
584-
log.error("failed to get transactions, mining block with empty transactions list", ex)
582+
log.error("Failed to get pending transactions, passing empty transactions list", ex)
585583
Task.now(PendingTransactionsResponse(Nil))
586584
}
587585
}
@@ -623,8 +621,8 @@ class EthService(
623621
startingBlock = startingBlockNumber,
624622
currentBlock = blocksProgress.current,
625623
highestBlock = blocksProgress.target,
626-
knownStates = stateNodesProgress.current,
627-
pulledStates = stateNodesProgress.target
624+
knownStates = stateNodesProgress.target,
625+
pulledStates = stateNodesProgress.current
628626
)
629627
)
630628
)
@@ -927,47 +925,6 @@ class EthService(
927925
}
928926
}
929927

930-
def getAccountTransactions(
931-
request: GetAccountTransactionsRequest
932-
): ServiceResponse[GetAccountTransactionsResponse] = {
933-
val numBlocksToSearch = request.toBlock - request.fromBlock
934-
if (numBlocksToSearch > jsonRpcConfig.accountTransactionsMaxBlocks) {
935-
Task.now(
936-
Left(
937-
JsonRpcError.InvalidParams(
938-
s"""Maximum number of blocks to search is ${jsonRpcConfig.accountTransactionsMaxBlocks}, requested: $numBlocksToSearch.
939-
|See: 'network.rpc.account-transactions-max-blocks' config.""".stripMargin
940-
)
941-
)
942-
)
943-
} else {
944-
945-
def collectTxs(
946-
blockHeader: Option[BlockHeader],
947-
pending: Boolean
948-
): PartialFunction[SignedTransaction, TransactionResponse] = {
949-
case stx if stx.safeSenderIsEqualTo(request.address) =>
950-
TransactionResponse(stx, blockHeader, pending = Some(pending), isOutgoing = Some(true))
951-
case stx if stx.tx.receivingAddress.contains(request.address) =>
952-
TransactionResponse(stx, blockHeader, pending = Some(pending), isOutgoing = Some(false))
953-
}
954-
955-
getTransactionsFromPool map { case PendingTransactionsResponse(pendingTransactions) =>
956-
val pendingTxs = pendingTransactions
957-
.map(_.stx.tx)
958-
.collect(collectTxs(None, pending = true))
959-
960-
val txsFromBlocks = (request.toBlock to request.fromBlock by -1).toStream
961-
.flatMap { n => blockchain.getBlockByNumber(n) }
962-
.flatMap { block =>
963-
block.body.transactionList.collect(collectTxs(Some(block.header), pending = false)).reverse
964-
}
965-
966-
Right(GetAccountTransactionsResponse(pendingTxs ++ txsFromBlocks))
967-
}
968-
}
969-
}
970-
971928
def getStorageRoot(req: GetStorageRootRequest): ServiceResponse[GetStorageRootResponse] =
972929
withAccount(req.address, req.block) { account =>
973930
GetStorageRootResponse(account.storageRoot)
@@ -980,7 +937,7 @@ class EthService(
980937
* @return pending transactions
981938
*/
982939
def ethPendingTransactions(req: EthPendingTransactionsRequest): ServiceResponse[EthPendingTransactionsResponse] =
983-
getTransactionsFromPool().map { resp =>
940+
getTransactionsFromPool.map { resp =>
984941
Right(EthPendingTransactionsResponse(resp.pendingTransactions))
985942
}
986943
}

src/main/scala/io/iohk/ethereum/jsonrpc/JsonMethodsImplicits.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ import scala.util.Try
2222
trait JsonMethodsImplicits {
2323
implicit val formats = JsonSerializers.formats
2424

25-
protected def encodeAsHex(input: ByteString): JString =
25+
def encodeAsHex(input: ByteString): JString =
2626
JString(s"0x${Hex.toHexString(input.toArray[Byte])}")
2727

28-
protected def encodeAsHex(input: Byte): JString =
28+
def encodeAsHex(input: Byte): JString =
2929
JString(s"0x${Hex.toHexString(Array(input))}")
3030

31-
protected def encodeAsHex(input: BigInt): JString =
31+
def encodeAsHex(input: BigInt): JString =
3232
JString(s"0x${input.toString(16)}")
3333

3434
protected def decode(s: String): Array[Byte] = {

src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcController.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.iohk.ethereum.jsonrpc
33
import io.iohk.ethereum.jsonrpc.CheckpointingService._
44
import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse}
55
import io.iohk.ethereum.jsonrpc.EthService._
6+
import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse}
67
import io.iohk.ethereum.jsonrpc.NetService._
78
import io.iohk.ethereum.jsonrpc.PersonalService._
89
import io.iohk.ethereum.jsonrpc.QAService.{
@@ -29,6 +30,7 @@ class JsonRpcController(
2930
debugService: DebugService,
3031
qaService: QAService,
3132
checkpointingService: CheckpointingService,
33+
mantisService: MantisService,
3234
override val config: JsonRpcConfig
3335
) extends ApisBuilder
3436
with Logger
@@ -41,13 +43,14 @@ class JsonRpcController(
4143
import JsonMethodsImplicits._
4244
import QAJsonMethodsImplicits._
4345
import TestJsonMethodsImplicits._
46+
import MantisJsonMethodImplicits._
4447

4548
override def apisHandleFns: Map[String, PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]]] = Map(
4649
Apis.Eth -> handleEthRequest,
4750
Apis.Web3 -> handleWeb3Request,
4851
Apis.Net -> handleNetRequest,
4952
Apis.Personal -> handlePersonalRequest,
50-
Apis.Daedalus -> handleDaedalusRequest,
53+
Apis.Mantis -> handleMantisRequest,
5154
Apis.Rpc -> handleRpcRequest,
5255
Apis.Debug -> handleDebugRequest,
5356
Apis.Test -> handleTestRequest,
@@ -259,9 +262,9 @@ class JsonRpcController(
259262
handle[EcRecoverRequest, EcRecoverResponse](personalService.ecRecover, req)
260263
}
261264

262-
private def handleDaedalusRequest: PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]] = {
263-
case req @ JsonRpcRequest(_, "daedalus_getAccountTransactions", _, _) =>
264-
handle[GetAccountTransactionsRequest, GetAccountTransactionsResponse](ethService.getAccountTransactions, req)
265+
private def handleMantisRequest: PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]] = {
266+
case req @ JsonRpcRequest(_, "mantis_getAccountTransactions", _, _) =>
267+
handle[GetAccountTransactionsRequest, GetAccountTransactionsResponse](mantisService.getAccountTransactions, req)
265268
}
266269

267270
private def handleQARequest: PartialFunction[JsonRpcRequest, Task[JsonRpcResponse]] = {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.iohk.ethereum.jsonrpc
2+
3+
import io.iohk.ethereum.jsonrpc.EthJsonMethodsImplicits.transactionResponseJsonEncoder
4+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
5+
import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse}
6+
import io.iohk.ethereum.jsonrpc.serialization.JsonEncoder.Ops._
7+
import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec, JsonMethodDecoder}
8+
import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData
9+
import org.json4s.JsonAST._
10+
import org.json4s.Merge
11+
import JsonEncoder.OptionToNull._
12+
13+
object MantisJsonMethodImplicits extends JsonMethodsImplicits {
14+
implicit val extendedTransactionDataJsonEncoder: JsonEncoder[ExtendedTransactionData] = extendedTxData => {
15+
val asTxResponse = TransactionResponse(
16+
extendedTxData.stx,
17+
extendedTxData.minedTransactionData.map(_.header),
18+
extendedTxData.minedTransactionData.map(_.transactionIndex)
19+
)
20+
21+
val encodedTxResponse = JsonEncoder.encode(asTxResponse)
22+
val encodedExtension = JObject(
23+
"isOutgoing" -> extendedTxData.isOutgoing.jsonEncoded,
24+
"isPending" -> extendedTxData.isPending.jsonEncoded,
25+
"gasUsed" -> extendedTxData.minedTransactionData.map(_.gasUsed).jsonEncoded,
26+
"timestamp" -> extendedTxData.minedTransactionData.map(_.timestamp).jsonEncoded
27+
)
28+
29+
Merge.merge(encodedTxResponse, encodedExtension)
30+
}
31+
32+
implicit val mantis_getAccountTransactions
33+
: JsonMethodCodec[GetAccountTransactionsRequest, GetAccountTransactionsResponse] =
34+
new JsonMethodDecoder[GetAccountTransactionsRequest] with JsonEncoder[GetAccountTransactionsResponse] {
35+
def decodeJson(params: Option[JArray]): Either[JsonRpcError, GetAccountTransactionsRequest] =
36+
params match {
37+
case Some(JArray(JString(addrJson) :: fromBlockJson :: toBlockJson :: Nil)) =>
38+
for {
39+
addr <- extractAddress(addrJson)
40+
fromBlock <- extractQuantity(fromBlockJson)
41+
toBlock <- extractQuantity(toBlockJson)
42+
} yield GetAccountTransactionsRequest(addr, fromBlock to toBlock)
43+
case _ => Left(InvalidParams())
44+
}
45+
46+
override def encodeJson(t: GetAccountTransactionsResponse): JValue =
47+
JObject("transactions" -> t.transactions.jsonEncoded)
48+
}
49+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.iohk.ethereum.jsonrpc
2+
import io.iohk.ethereum.domain.Address
3+
import io.iohk.ethereum.jsonrpc.MantisService.{GetAccountTransactionsRequest, GetAccountTransactionsResponse}
4+
import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig
5+
import io.iohk.ethereum.transactions.TransactionHistoryService
6+
import io.iohk.ethereum.transactions.TransactionHistoryService.ExtendedTransactionData
7+
import monix.eval.Task
8+
import cats.implicits._
9+
10+
import scala.collection.immutable.NumericRange
11+
12+
object MantisService {
13+
case class GetAccountTransactionsRequest(address: Address, blocksRange: NumericRange[BigInt])
14+
case class GetAccountTransactionsResponse(transactions: List[ExtendedTransactionData])
15+
}
16+
class MantisService(transactionHistoryService: TransactionHistoryService, jsonRpcConfig: JsonRpcConfig) {
17+
def getAccountTransactions(
18+
request: GetAccountTransactionsRequest
19+
): ServiceResponse[GetAccountTransactionsResponse] = {
20+
if (request.blocksRange.length > jsonRpcConfig.accountTransactionsMaxBlocks) {
21+
Task.now(
22+
Left(
23+
JsonRpcError.InvalidParams(
24+
s"""Maximum number of blocks to search is ${jsonRpcConfig.accountTransactionsMaxBlocks}, requested: ${request.blocksRange.length}.
25+
|See: 'mantis.network.rpc.account-transactions-max-blocks' config.""".stripMargin
26+
)
27+
)
28+
)
29+
} else {
30+
transactionHistoryService
31+
.getAccountTransactions(request.address, request.blocksRange)
32+
.map(GetAccountTransactionsResponse(_).asRight)
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)