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/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/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 7abe35c10a..8538718167 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala @@ -4,6 +4,7 @@ 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, _} @@ -12,6 +13,7 @@ import org.json4s.{Extraction, JsonAST} // 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 +150,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 +171,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 +190,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,23 +577,6 @@ object EthJsonMethodsImplicits extends JsonMethodsImplicits { ) } - 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, toBlock) - case _ => Left(InvalidParams()) - } - - override def encodeJson(t: GetAccountTransactionsResponse): JValue = - JObject("transactions" -> JArray(t.transactions.map(Extraction.decompose).toList)) - } - 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 0fd7c7f692..a1520746e2 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._ @@ -15,8 +12,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 @@ -29,10 +28,12 @@ import io.iohk.ethereum.rlp.UInt256RLPImplicits._ import io.iohk.ethereum.transactions.PendingTransactionsManager import io.iohk.ethereum.transactions.PendingTransactionsManager.{PendingTransaction, PendingTransactionsResponse} 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 java.time.Duration +import java.util.Date +import java.util.concurrent.atomic.AtomicReference import scala.collection.concurrent.{TrieMap, Map => ConcurrentMap} import scala.concurrent.duration.FiniteDuration import scala.language.existentials @@ -78,9 +79,6 @@ object EthService { case class GetTransactionByHashRequest(txHash: ByteString) case class GetTransactionByHashResponse(txResponse: Option[TransactionResponse]) - case class GetAccountTransactionsRequest(address: Address, fromBlock: BigInt, toBlock: BigInt) - case class GetAccountTransactionsResponse(transactions: Seq[TransactionResponse]) - case class GetTransactionReceiptRequest(txHash: ByteString) case class GetTransactionReceiptResponse(txResponse: Option[TransactionReceiptResponse]) @@ -325,7 +323,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 +539,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 +573,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)) } } @@ -623,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 ) ) ) @@ -927,47 +925,6 @@ class EthService( } } - def getAccountTransactions( - request: GetAccountTransactionsRequest - ): ServiceResponse[GetAccountTransactionsResponse] = { - val numBlocksToSearch = request.toBlock - request.fromBlock - if (numBlocksToSearch > 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 - ) - ) - ) - } 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)) - } - } - } - def getStorageRoot(req: GetStorageRootRequest): ServiceResponse[GetStorageRootResponse] = withAccount(req.address, req.block) { account => GetStorageRootResponse(account.storageRoot) @@ -980,7 +937,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/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/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..de7743260f --- /dev/null +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/MantisJsonMethodImplicits.scala @@ -0,0 +1,49 @@ +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(_.header), + extendedTxData.minedTransactionData.map(_.transactionIndex) + ) + + val encodedTxResponse = JsonEncoder.encode(asTxResponse) + val encodedExtension = JObject( + "isOutgoing" -> extendedTxData.isOutgoing.jsonEncoded, + "isPending" -> extendedTxData.isPending.jsonEncoded, + "gasUsed" -> extendedTxData.minedTransactionData.map(_.gasUsed).jsonEncoded, + "timestamp" -> extendedTxData.minedTransactionData.map(_.timestamp).jsonEncoded + ) + + 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" -> t.transactions.jsonEncoded) + } +} 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/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..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,16 +11,27 @@ 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)) - 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/nodebuilder/NodeBuilder.scala b/src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala index d4c19b32e9..41dcff30e8 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 @@ -264,14 +264,30 @@ trait NetServiceBuilder { } trait PendingTransactionsManagerBuilder { - self: ActorSystemBuilder - with PeerManagerActorBuilder - with EtcPeerManagerActorBuilder - with PeerEventBusBuilder - with TxPoolConfigBuilder => + 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)) + } +} - lazy val pendingTransactionsManager: ActorRef = - system.actorOf(PendingTransactionsManager.props(txPoolConfig, peerManager, etcPeerManager, peerEventBus)) +trait TransactionHistoryServiceBuilder { + 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 { @@ -335,7 +351,7 @@ trait EthServiceBuilder { with JSONRpcConfigBuilder with AsyncConfigBuilder => - lazy val ethService = new EthService( + val ethService = new EthService( blockchain, ledger, stxLedger, @@ -393,6 +409,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) @@ -404,7 +426,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" @@ -414,7 +436,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 { @@ -432,7 +454,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) @@ -448,6 +471,7 @@ trait JSONRpcControllerBuilder { debugService, qaService, checkpointingService, + mantisService, jsonRpcConfig ) } @@ -630,6 +654,7 @@ trait Node with DebugServiceBuilder with QaServiceBuilder with CheckpointingServiceBuilder + with MantisServiceBuilder with KeyStoreBuilder with ApisBuilder with JSONRpcConfigBuilder @@ -643,7 +668,7 @@ trait Node with BlockchainConfigBuilder with VmConfigBuilder with PeerEventBusBuilder - with PendingTransactionsManagerBuilder + with PendingTransactionsManagerBuilder.Default with OmmersPoolBuilder with EtcPeerManagerActorBuilder with BlockchainHostBuilder @@ -665,3 +690,4 @@ trait Node with KeyStoreConfigBuilder with AsyncConfigBuilder with CheckpointBlockGeneratorBuilder + with TransactionHistoryServiceBuilder.Default 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..360b95ac58 --- /dev/null +++ b/src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala @@ -0,0 +1,143 @@ +package io.iohk.ethereum.transactions + +import akka.actor.ActorRef +import akka.util.Timeout +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, + 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 + +class TransactionHistoryService( + blockchain: Blockchain, + pendingTransactionsManager: ActorRef, + getTransactionFromPoolTimeout: FiniteDuration +) extends Logger { + 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 } + .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(PendingTxChecker.checkTx(_, account))) + } + + Task.parMap2(txnsFromBlocks, txnsFromMempool)(_ ++ _) + } + + private val getTransactionsFromPool: Task[List[PendingTransaction]] = { + implicit val timeout: Timeout = getTransactionFromPoolTimeout + pendingTransactionsManager + .askFor[PendingTransactionsManager.PendingTransactionsResponse](PendingTransactionsManager.GetPendingTransactions) + .map(_.pendingTransactions.toList) + .onErrorRecoverWith { case ex: Throwable => + log.error("Failed to get pending transactions, passing empty transactions list", ex) + Task.now(List.empty) + } + } +} +object TransactionHistoryService { + case class MinedTransactionData( + header: BlockHeader, + transactionIndex: Int, + gasUsed: BigInt + ) { + lazy val timestamp: Long = header.unixTimestamp + } + case class ExtendedTransactionData( + stx: SignedTransaction, + isOutgoing: Boolean, + minedTransactionData: Option[MinedTransactionData] + ) { + val isPending: Boolean = minedTransactionData.isEmpty + } + + 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 + } + } + } + + 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 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 + } + } + + 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 + } + + 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/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/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/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 8401c63a90..a6cbfc0acd 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala @@ -1,7 +1,5 @@ package io.iohk.ethereum.jsonrpc -import java.security.SecureRandom - import akka.actor.ActorSystem import akka.testkit.{TestKit, TestProbe} import akka.util.ByteString @@ -33,7 +31,7 @@ import io.iohk.ethereum.transactions.PendingTransactionsManager.{ PendingTransactionsResponse } import io.iohk.ethereum.utils._ -import io.iohk.ethereum.{Fixtures, NormalPatience, Timeouts, WithActorSystemShutDown, crypto} +import io.iohk.ethereum._ import monix.execution.Scheduler.Implicits.global import org.bouncycastle.util.encoders.Hex import org.scalactic.TypeCheckedTripleEquals @@ -511,8 +509,8 @@ class EthServiceSpec startingBlock = 999, currentBlock = 200, highestBlock = 10000, - knownStates = 100, - pulledStates = 144 + knownStates = 144, + pulledStates = 100 ) ) ) @@ -1099,81 +1097,8 @@ 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, 3125360, 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) - ), - TransactionResponse(tx1, blockHeader = Some(blockWithTx1.header), pending = Some(false), isOutgoing = Some(false)) - ) - - 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, 3125371, 3125381) - - 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))) - - response.futureValue shouldEqual Right(GetAccountTransactionsResponse(expectedSent)) - } - 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 +1125,7 @@ class EthServiceSpec }) .toList - val res = ethService.getTransactionsFromPool().runToFuture + val res = ethService.getTransactionsFromPool.runToFuture pendingTransactionsManager.expectMsg(GetPendingTransactions) pendingTransactionsManager.reply(PendingTransactionsResponse(transactions)) @@ -1209,7 +1134,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")) @@ -1263,7 +1188,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/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" ) } 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 761da5cc43..8973005dbc 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerFixture.scala @@ -21,6 +21,7 @@ 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 +75,7 @@ class JsonRpcControllerFixture(implicit system: ActorSystem) val debugService = mock[DebugService] val qaService = mock[QAService] val checkpointingService = mock[CheckpointingService] + val mantisService = mock[MantisService] val ethService = new EthService( blockchain, @@ -102,6 +104,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 b467d4dcab..eedef962be 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala @@ -4,14 +4,13 @@ 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.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 @@ -20,9 +19,7 @@ import io.iohk.ethereum.network.p2p.messages.Versions 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, Formats, JArray, JObject, JString} import org.scalatest.concurrent.{Eventually, ScalaFutures} import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers @@ -152,57 +149,16 @@ 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" - ) - ) - } - - 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( - Seq( - TransactionResponse(sentTx, Some(block.header), isOutgoing = Some(true)), - TransactionResponse(receivedTx, Some(block.header), isOutgoing = Some(false)) - ) - ) - ) - ) - ) - - val request: JsonRpcRequest = newJsonRpcRequest( - "daedalus_getAccountTransactions", - List( - JString(s"0x7B9Bc474667Db2fFE5b08d000F1Acc285B2Ae47D"), - JInt(100), - JInt(200) + "net" -> JString("1.0"), + "rpc" -> JString("1.0"), + "personal" -> JString("1.0"), + "eth" -> JString("1.0"), + "web3" -> JString("1.0"), + "mantis" -> JString("1.0"), + "debug" -> JString("1.0"), + "qa" -> JString("1.0"), + "checkpointing" -> JString("1.0") ) ) - - 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))) - ) - - 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..6009620b30 --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/MantisJRCSpec.scala @@ -0,0 +1,116 @@ +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, 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, JLong, 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(MinedTransactionData(block.header, 0, 42))), + ExtendedTransactionData( + receivedTx, + isOutgoing = false, + Some(MinedTransactionData(block.header, 1, 21)) + ) + ) + ) + ) + ) + ) + + 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), + "timestamp" -> JLong(block.header.unixTimestamp), + "gasUsed" -> JString(s"0x${BigInt(42).toString(16)}") + ) + ), + JObject( + Extraction + .decompose(TransactionResponse(receivedTx, Some(block.header), Some(1))) + .asInstanceOf[JObject] + .obj ++ List( + "isPending" -> JBool(false), + "isOutgoing" -> JBool(false), + "timestamp" -> JLong(block.header.unixTimestamp), + "gasUsed" -> JString(s"0x${BigInt(21).toString(16)}") + ) + ) + ) + + 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..4930619a01 --- /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, MinedTransactionData} +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.Default + 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(MinedTransactionData(block.header, 0, 42)) + ) + ) + + override 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/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala b/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala new file mode 100644 index 0000000000..30b1e2ec8f --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/transactions/TransactionHistoryServiceSpec.scala @@ -0,0 +1,102 @@ +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, HashOutcome, Receipt, SignedTransaction, Transaction} +import io.iohk.ethereum.transactions.TransactionHistoryService.{ExtendedTransactionData, MinedTransactionData} +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 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(MinedTransactionData(blockWithTxs2and3.header, 1, 44)) + ), + ExtendedTransactionData( + tx2, + isOutgoing = false, + Some(MinedTransactionData(blockWithTxs2and3.header, 0, 43)) + ), + 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)) + } 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))) + } +} 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 { 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" } }