Coverage Summary for Class: TxPoolModuleImpl (co.rsk.rpc.modules.txpool)
Class |
Class, %
|
Method, %
|
Line, %
|
TxPoolModuleImpl |
100%
(1/1)
|
12.5%
(1/8)
|
5.6%
(4/71)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2018 RSK Labs Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package co.rsk.rpc.modules.txpool;
20
21 import co.rsk.core.RskAddress;
22 import com.fasterxml.jackson.databind.JsonNode;
23 import com.fasterxml.jackson.databind.node.ArrayNode;
24 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
25 import com.fasterxml.jackson.databind.node.ObjectNode;
26 import org.ethereum.core.Transaction;
27 import org.ethereum.core.TransactionPool;
28 import org.ethereum.rpc.TypeConverter;
29
30 import java.math.BigInteger;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.function.Function;
36
37 public class TxPoolModuleImpl implements TxPoolModule {
38
39 public static final String PENDING = "pending";
40 public static final String QUEUED = "queued";
41 private final JsonNodeFactory jsonNodeFactory;
42 private final TransactionPool transactionPool;
43
44 public TxPoolModuleImpl(TransactionPool transactionPool) {
45 this.transactionPool = transactionPool;
46 jsonNodeFactory = JsonNodeFactory.instance;
47 }
48
49 /**
50 * This method should return 2 dictionaries containing pending and queued transactions
51 * Each entry is an origin-address to a batch of scheduled transactions
52 * These batches themselves are maps associating nonces with actual transactions.
53 * When there are no transactions the answer would be
54 * "{"pending": {}, "queued": {}}"
55 */
56 @Override
57 public JsonNode content() {
58 Map<String, JsonNode> contentProps = new HashMap<>();
59 Map<RskAddress, Map<BigInteger, List<Transaction>>> pendingGrouped = groupTransactions(transactionPool.getPendingTransactions());
60 Map<RskAddress, Map<BigInteger, List<Transaction>>> queuedGrouped = groupTransactions(transactionPool.getQueuedTransactions());
61 contentProps.put(PENDING, serializeTransactions(pendingGrouped, this::fullSerializer));
62 contentProps.put(QUEUED, serializeTransactions(queuedGrouped, this::fullSerializer));
63 JsonNode node = jsonNodeFactory.objectNode().setAll(contentProps);
64 return node;
65 }
66
67 private JsonNode serializeTransactions(
68 Map<RskAddress, Map<BigInteger, List<Transaction>>> groupedTransactions,
69 Function<Transaction, JsonNode> txSerializer) {
70 Map<String, JsonNode> senderProps = new HashMap<>();
71 for (Map.Entry<RskAddress, Map<BigInteger, List<Transaction>>> entrySender : groupedTransactions.entrySet()){
72 Map<String, JsonNode> nonceProps = new HashMap<>();
73 for (Map.Entry<BigInteger, List<Transaction>> entryNonce : entrySender.getValue().entrySet()){
74 ArrayNode txsNodes = jsonNodeFactory.arrayNode();
75 for (Transaction tx : entryNonce.getValue()) {
76 txsNodes.add(txSerializer.apply(tx));
77 }
78 nonceProps.put(entryNonce.getKey().toString(),txsNodes);
79 }
80 senderProps.put(entrySender.getKey().toString(), jsonNodeFactory.objectNode().setAll(nonceProps));
81 }
82 return jsonNodeFactory.objectNode().setAll(senderProps);
83 }
84
85 private JsonNode fullSerializer(Transaction tx) {
86 ObjectNode txNode = jsonNodeFactory.objectNode();
87
88 txNode.put("blockHash", "0x0000000000000000000000000000000000000000000000000000000000000000");
89 txNode.putNull("blockNumber");
90 txNode.putNull("transactionIndex");
91
92 txNode.put("from", TypeConverter.toJsonHex(tx.getSender().getBytes()));
93 txNode.put("gas", TypeConverter.toQuantityJsonHex(tx.getGasLimitAsInteger()));
94 txNode.put("gasPrice", TypeConverter.toJsonHex(tx.getGasPrice().getBytes()));
95 txNode.put("hash", TypeConverter.toJsonHex(tx.getHash().toHexString()));
96 txNode.put("input", TypeConverter.toUnformattedJsonHex(tx.getData()));
97 txNode.put("nonce", TypeConverter.toQuantityJsonHex(tx.getNonceAsInteger()));
98 txNode.put("to", TypeConverter.toJsonHex(tx.getReceiveAddress().getBytes()));
99 txNode.put("value", TypeConverter.toJsonHex(tx.getValue().getBytes()));
100
101 return txNode;
102 }
103
104 private JsonNode summarySerializer(Transaction tx) {
105 String summary = "{}: {} wei + {} x {} gas";
106 String summaryFormatted = String.format(summary,
107 tx.getReceiveAddress().toString(),
108 tx.getValue().toString(),
109 tx.getGasLimitAsInteger().toString(),
110 tx.getGasPrice().toString());
111 return jsonNodeFactory.textNode(summaryFormatted);
112 }
113
114 private Map<RskAddress, Map<BigInteger, List<Transaction>>> groupTransactions(List<Transaction> transactions) {
115 Map<RskAddress, Map<BigInteger, List<Transaction>>> groupedTransactions = new HashMap<>();
116 for (Transaction tx : transactions){
117 Map<BigInteger, List<Transaction>> txsBySender = groupedTransactions.get(tx.getSender());
118 if (txsBySender == null){
119 txsBySender = new HashMap<>();
120 groupedTransactions.put(tx.getSender(), txsBySender);
121 }
122 List<Transaction> txsByNonce = txsBySender.get(tx.getNonceAsInteger());
123 if (txsByNonce == null){
124 List<Transaction> txs = new ArrayList<>();
125 txs.add(tx);
126 txsBySender.put(tx.getNonceAsInteger(), txs);
127 } else {
128 txsByNonce.add(tx);
129 }
130 }
131 return groupedTransactions;
132 }
133
134 /**
135 * This method should return 2 dictionaries containing pending and queued transactions
136 * Each entry is an origin-address to a batch of scheduled transactions
137 * These batches themselves are maps associating nonces with transactions summary strings.
138 * When there are no transactions the answer would be
139 * "{"pending": {}, "queued": {}}"
140 */
141 @Override
142 public JsonNode inspect() {
143 Map<String, JsonNode> contentProps = new HashMap<>();
144 Map<RskAddress, Map<BigInteger, List<Transaction>>> pendingGrouped = groupTransactions(transactionPool.getPendingTransactions());
145 Map<RskAddress, Map<BigInteger, List<Transaction>>> queuedGrouped = groupTransactions(transactionPool.getQueuedTransactions());
146 contentProps.put(PENDING, serializeTransactions(pendingGrouped, this::summarySerializer));
147 contentProps.put(QUEUED, serializeTransactions(queuedGrouped, this::summarySerializer));
148 JsonNode node = jsonNodeFactory.objectNode().setAll(contentProps);
149 return node;
150 }
151
152 /**
153 * This method should return 2 integers for pending and queued transactions
154 * These value represents
155 * the number of transactions currently pending for inclusion in the next block(s),
156 * as well as the ones that are being scheduled for future execution only.
157 * "{"pending": 0, "queued": 0}"
158 */
159 @Override
160 public JsonNode status() {
161 Map<String, JsonNode> txProps = new HashMap<>();
162 txProps.put(PENDING, jsonNodeFactory.numberNode(transactionPool.getPendingTransactions().size()));
163 txProps.put(QUEUED, jsonNodeFactory.numberNode(transactionPool.getQueuedTransactions().size()));
164 JsonNode node = jsonNodeFactory.objectNode().setAll(txProps);
165 return node;
166 }
167 }