From 7b20ebe895551a1815224362f7660d44cc0de4f9 Mon Sep 17 00:00:00 2001 From: Rob Dawson Date: Wed, 8 May 2019 15:06:53 +1000 Subject: [PATCH] Support including the transaction details in the pending transactions query. (#1410) Signed-off-by: Adrian Sutton --- ...loyPrivateSmartContractAcceptanceTest.java | 1 - .../websocket/subscription/Subscription.java | 9 +- .../subscription/SubscriptionBuilder.java | 2 +- .../NewBlockHeadersSubscription.java | 2 +- .../subscription/logs/LogsSubscription.java | 2 +- .../PendingTransactionDetailResult.java | 117 ++++++++++++++++++ ...PendingTransactionSubscriptionService.java | 16 ++- ...ptionParam.java => SubscriptionParam.java} | 5 +- .../request/SubscriptionRequestMapper.java | 19 +-- .../syncing/SyncingSubscription.java | 2 +- .../jsonrpc/SimpleTestTransactionBuilder.java | 98 +++++++++++++++ .../subscription/SubscriptionBuilderTest.java | 4 +- ...sactionDroppedSubscriptionServiceTest.java | 5 +- ...ingTransactionSubscriptionServiceTest.java | 51 ++++++-- .../SubscriptionRequestMapperTest.java | 16 +-- 15 files changed, 308 insertions(+), 41 deletions(-) create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java rename ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/{NewBlockHeadersSubscriptionParam.java => SubscriptionParam.java} (85%) create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/SimpleTestTransactionBuilder.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java index af7b8e8a7d..815f3e36b1 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java @@ -106,7 +106,6 @@ public class DeployPrivateSmartContractAcceptanceTest extends PrivateAcceptanceT transactionHash = minerNode.execute(privateTransactions.createPrivateRawTransaction(storeValue)); - privateTransactionVerifier.validEventReturned("1000").verify(minerNode, transactionHash); } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/Subscription.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/Subscription.java index bbc70d410b..ab6cb3b89a 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/Subscription.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/Subscription.java @@ -21,10 +21,13 @@ public class Subscription { private final Long id; private final SubscriptionType subscriptionType; + private final Boolean includeTransaction; - public Subscription(final Long id, final SubscriptionType subscriptionType) { + public Subscription( + final Long id, final SubscriptionType subscriptionType, final Boolean includeTransaction) { this.id = id; this.subscriptionType = subscriptionType; + this.includeTransaction = includeTransaction; } public SubscriptionType getSubscriptionType() { @@ -35,6 +38,10 @@ public class Subscription { return id; } + public Boolean getIncludeTransaction() { + return includeTransaction; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilder.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilder.java index d6f41e4b29..273810b50a 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilder.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilder.java @@ -43,7 +43,7 @@ public class SubscriptionBuilder { } case NEW_PENDING_TRANSACTIONS: default: - return new Subscription(id, subscriptionType); + return new Subscription(id, subscriptionType, request.getIncludeTransaction()); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscription.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscription.java index 2513b3fa33..be8e22e8b6 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscription.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscription.java @@ -20,7 +20,7 @@ public class NewBlockHeadersSubscription extends Subscription { private final boolean includeTransactions; public NewBlockHeadersSubscription(final Long subscriptionId, final boolean includeTransactions) { - super(subscriptionId, SubscriptionType.NEW_BLOCK_HEADERS); + super(subscriptionId, SubscriptionType.NEW_BLOCK_HEADERS, Boolean.FALSE); this.includeTransactions = includeTransactions; } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscription.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscription.java index e96c2d0fc9..04bb5b7985 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscription.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/logs/LogsSubscription.java @@ -22,7 +22,7 @@ public class LogsSubscription extends Subscription { private final FilterParameter filterParameter; public LogsSubscription(final Long subscriptionId, final FilterParameter filterParameter) { - super(subscriptionId, SubscriptionType.LOGS); + super(subscriptionId, SubscriptionType.LOGS, Boolean.FALSE); this.filterParameter = filterParameter; } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java new file mode 100644 index 0000000000..990b2f3d34 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java @@ -0,0 +1,117 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.pending; + +import tech.pegasys.pantheon.ethereum.core.Transaction; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.JsonRpcResult; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.Quantity; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({ + "from", + "gas", + "gasPrice", + "hash", + "input", + "nonce", + "to", + "value", + "v", + "r", + "s" +}) +public class PendingTransactionDetailResult implements JsonRpcResult { + private final String from; + private final String gas; + private final String gasPrice; + private final String hash; + private final String input; + private final String nonce; + private final String to; + private final String value; + private final String v; + private final String r; + private final String s; + + public PendingTransactionDetailResult(final Transaction tx) { + this.from = tx.getSender().toString(); + this.gas = Quantity.create(tx.getGasLimit()); + this.gasPrice = Quantity.create(tx.getGasPrice()); + this.hash = tx.hash().toString(); + this.input = tx.getPayload().toString(); + this.nonce = Quantity.create(tx.getNonce()); + this.to = tx.getTo().map(BytesValue::toString).orElse(null); + this.value = Quantity.create(tx.getValue()); + this.v = Quantity.format(tx.getV()); + this.r = Quantity.format(tx.getR()); + this.s = Quantity.format(tx.getS()); + } + + @JsonGetter(value = "from") + public String getFrom() { + return from; + } + + @JsonGetter(value = "gas") + public String getGas() { + return gas; + } + + @JsonGetter(value = "gasPrice") + public String getGasPrice() { + return gasPrice; + } + + @JsonGetter(value = "hash") + public String getHash() { + return hash; + } + + @JsonGetter(value = "input") + public String getInput() { + return input; + } + + @JsonGetter(value = "nonce") + public String getNonce() { + return nonce; + } + + @JsonGetter(value = "to") + public String getTo() { + return to; + } + + @JsonGetter(value = "value") + public String getValue() { + return value; + } + + @JsonGetter(value = "v") + public String getV() { + return v; + } + + @JsonGetter(value = "r") + public String getR() { + return r; + } + + @JsonGetter(value = "s") + public String getS() { + return s; + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionService.java index 440b9f9d6c..5842f70eec 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionService.java @@ -12,7 +12,6 @@ */ package tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.pending; -import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.eth.transactions.PendingTransactionListener; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.Subscription; @@ -31,15 +30,22 @@ public class PendingTransactionSubscriptionService implements PendingTransaction @Override public void onTransactionAdded(final Transaction pendingTransaction) { - notifySubscribers(pendingTransaction.hash()); + notifySubscribers(pendingTransaction); } - private void notifySubscribers(final Hash pendingTransaction) { + private void notifySubscribers(final Transaction pendingTransaction) { final List subscriptions = pendingTransactionSubscriptions(); - final PendingTransactionResult msg = new PendingTransactionResult(pendingTransaction); + final PendingTransactionResult hashResult = + new PendingTransactionResult(pendingTransaction.hash()); + final PendingTransactionDetailResult detailResult = + new PendingTransactionDetailResult(pendingTransaction); for (final Subscription subscription : subscriptions) { - subscriptionManager.sendMessage(subscription.getId(), msg); + if (Boolean.TRUE.equals(subscription.getIncludeTransaction())) { + subscriptionManager.sendMessage(subscription.getId(), detailResult); + } else { + subscriptionManager.sendMessage(subscription.getId(), hashResult); + } } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/NewBlockHeadersSubscriptionParam.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionParam.java similarity index 85% rename from ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/NewBlockHeadersSubscriptionParam.java rename to ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionParam.java index 4df2cc0f8f..5fee8fc369 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/NewBlockHeadersSubscriptionParam.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionParam.java @@ -15,13 +15,12 @@ package tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.request; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -class NewBlockHeadersSubscriptionParam { +class SubscriptionParam { private final boolean includeTransaction; @JsonCreator - NewBlockHeadersSubscriptionParam( - @JsonProperty("includeTransactions") final boolean includeTransaction) { + SubscriptionParam(@JsonProperty("includeTransactions") final boolean includeTransaction) { this.includeTransaction = includeTransaction; } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java index 1e78450aa3..1a1c2b7f63 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java @@ -39,11 +39,11 @@ public class SubscriptionRequestMapper { final SubscriptionType subscriptionType = parameter.required(webSocketRpcRequest.getParams(), 0, SubscriptionType.class); - switch (subscriptionType) { case NEW_BLOCK_HEADERS: { - return parseNewBlockHeadersRequest(webSocketRpcRequest); + final boolean includeTransactions = includeTransactions(webSocketRpcRequest); + return parseNewBlockHeadersRequest(webSocketRpcRequest, includeTransactions); } case LOGS: { @@ -52,18 +52,23 @@ public class SubscriptionRequestMapper { case NEW_PENDING_TRANSACTIONS: case SYNCING: default: + final boolean includeTransactions = includeTransactions(webSocketRpcRequest); return new SubscribeRequest( - subscriptionType, null, null, webSocketRpcRequest.getConnectionId()); + subscriptionType, null, includeTransactions, webSocketRpcRequest.getConnectionId()); } } catch (final Exception e) { throw new InvalidSubscriptionRequestException("Error parsing subscribe request", e); } } - private SubscribeRequest parseNewBlockHeadersRequest(final WebSocketRpcRequest request) { - final Optional params = - parameter.optional(request.getParams(), 1, NewBlockHeadersSubscriptionParam.class); - final boolean includeTransactions = params.isPresent() && params.get().includeTransaction(); + private boolean includeTransactions(final WebSocketRpcRequest webSocketRpcRequest) { + final Optional params = + parameter.optional(webSocketRpcRequest.getParams(), 1, SubscriptionParam.class); + return params.isPresent() && params.get().includeTransaction(); + } + + private SubscribeRequest parseNewBlockHeadersRequest( + final WebSocketRpcRequest request, final Boolean includeTransactions) { return new SubscribeRequest( SubscriptionType.NEW_BLOCK_HEADERS, null, includeTransactions, request.getConnectionId()); } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/syncing/SyncingSubscription.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/syncing/SyncingSubscription.java index 8f4e4db9c9..010e55131b 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/syncing/SyncingSubscription.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/syncing/SyncingSubscription.java @@ -19,7 +19,7 @@ public class SyncingSubscription extends Subscription { private boolean firstMessageHasBeenSent = false; public SyncingSubscription(final Long id, final SubscriptionType subscriptionType) { - super(id, subscriptionType); + super(id, subscriptionType, Boolean.FALSE); } public void setFirstMessageHasBeenSent(final boolean firstMessageHasBeenSent) { diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/SimpleTestTransactionBuilder.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/SimpleTestTransactionBuilder.java new file mode 100644 index 0000000000..42ade97d29 --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/SimpleTestTransactionBuilder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.Transaction; +import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.math.BigInteger; +import java.util.Optional; + +public class SimpleTestTransactionBuilder { + + private static final int HEX_RADIX = 16; + + public static Transaction transaction(final Hash hash) { + return transaction( + hash, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "0x2fefd8", + "0x1", + "0x5b5b610705806100106000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063102accc11461012c57806312a7b9141461013a5780631774e6461461014c5780631e26fd331461015d5780631f9030371461016e578063343a875d1461018057806338cc4831146101955780634e7ad367146101bd57806357cb2fc4146101cb57806365538c73146101e057806368895979146101ee57806376bc21d9146102005780639a19a9531461020e5780639dc2c8f51461021f578063a53b1c1e1461022d578063a67808571461023e578063b61c05031461024c578063c2b12a731461025a578063d2282dc51461026b578063e30081a01461027c578063e8beef5b1461028d578063f38b06001461029b578063f5b53e17146102a9578063fd408767146102bb57005b6101346104d6565b60006000f35b61014261039b565b8060005260206000f35b610157600435610326565b60006000f35b6101686004356102c9565b60006000f35b610176610442565b8060005260206000f35b6101886103d3565b8060ff1660005260206000f35b61019d610413565b8073ffffffffffffffffffffffffffffffffffffffff1660005260206000f35b6101c56104c5565b60006000f35b6101d36103b7565b8060000b60005260206000f35b6101e8610454565b60006000f35b6101f6610401565b8060005260206000f35b61020861051f565b60006000f35b6102196004356102e5565b60006000f35b610227610693565b60006000f35b610238600435610342565b60006000f35b610246610484565b60006000f35b610254610493565b60006000f35b61026560043561038d565b60006000f35b610276600435610350565b60006000f35b61028760043561035e565b60006000f35b6102956105b4565b60006000f35b6102a3610547565b60006000f35b6102b16103ef565b8060005260206000f35b6102c3610600565b60006000f35b80600060006101000a81548160ff021916908302179055505b50565b80600060016101000a81548160ff02191690837f01000000000000000000000000000000000000000000000000000000000000009081020402179055505b50565b80600060026101000a81548160ff021916908302179055505b50565b806001600050819055505b50565b806002600050819055505b50565b80600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b50565b806004600050819055505b50565b6000600060009054906101000a900460ff1690506103b4565b90565b6000600060019054906101000a900460000b90506103d0565b90565b6000600060029054906101000a900460ff1690506103ec565b90565b600060016000505490506103fe565b90565b60006002600050549050610410565b90565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905061043f565b90565b60006004600050549050610451565b90565b7f65c9ac8011e286e89d02a269890f41d67ca2cc597b2c76c7c69321ff492be5806000602a81526020016000a15b565b6000602a81526020016000a05b565b60017f81933b308056e7e85668661dcd102b1f22795b4431f9cf4625794f381c271c6b6000602a81526020016000a25b565b60016000602a81526020016000a15b565b3373ffffffffffffffffffffffffffffffffffffffff1660017f0e216b62efbb97e751a2ce09f607048751720397ecfb9eef1e48a6644948985b6000602a81526020016000a35b565b3373ffffffffffffffffffffffffffffffffffffffff1660016000602a81526020016000a25b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff1660017f317b31292193c2a4f561cc40a95ea0d97a2733f14af6d6d59522473e1f3ae65f6000602a81526020016000a45b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff1660016000602a81526020016000a35b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff1660017fd5f0a30e4be0c6be577a71eceb7464245a796a7e6a55c0d971837b250de05f4e60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe98152602001602a81526020016000a45b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff16600160007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe98152602001602a81526020016000a35b56", + "0x0", + null, + "0xa", + "0x1c", + "0xe439aa8812c1c0a751b0931ea20c5a30cd54fe15cae883c59fd8107e04557679", + "0x58d025af99b538b778a47da8115c43d5cee564c3cc8d58eb972aaf80ea2c406e"); + } + + public static Transaction transaction( + final Hash blockHash, + final String fromAddress, + final String gas, + final String gasPrice, + final String input, + final String nonce, + final String toAddress, + final String value, + final String v, + final String r, + final String s) { + + final Transaction transaction = mock(Transaction.class); + when(transaction.hash()).thenReturn(blockHash); + when(transaction.getGasPrice()).thenReturn(Wei.fromHexString(gasPrice)); + when(transaction.getNonce()).thenReturn(unsignedLong(nonce)); + when(transaction.getV()).thenReturn(bigInteger(v)); + when(transaction.getR()).thenReturn(bigInteger(r)); + when(transaction.getS()).thenReturn(bigInteger(s)); + when(transaction.getTo()).thenReturn(Optional.ofNullable(address(toAddress))); + when(transaction.getSender()).thenReturn(address(fromAddress)); + when(transaction.getPayload()).thenReturn(bytes(input)); + when(transaction.getValue()).thenReturn(wei(value)); + when(transaction.getGasLimit()).thenReturn(unsignedLong(gas)); + return transaction; + } + + private static long unsignedLong(final String value) { + final String hex = removeHexPrefix(value); + return new BigInteger(hex, HEX_RADIX).longValue(); + } + + private static String removeHexPrefix(final String prefixedHex) { + return prefixedHex.startsWith("0x") ? prefixedHex.substring(2) : prefixedHex; + } + + private static BigInteger bigInteger(final String hex) { + return new BigInteger(removeHexPrefix(hex), HEX_RADIX); + } + + private static Wei wei(final String hex) { + return Wei.fromHexString(hex); + } + + private static Address address(final String hex) { + return Address.fromHexString(hex); + } + + private static BytesValue bytes(final String hex) { + return BytesValue.fromHexString(hex); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilderTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilderTest.java index 264eee2ba8..3a5baa3731 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilderTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/SubscriptionBuilderTest.java @@ -65,7 +65,7 @@ public class SubscriptionBuilderTest { final SubscribeRequest subscribeRequest = new SubscribeRequest(SubscriptionType.NEW_PENDING_TRANSACTIONS, null, null, "connectionId"); final Subscription expectedSubscription = - new Subscription(1L, SubscriptionType.NEW_PENDING_TRANSACTIONS); + new Subscription(1L, SubscriptionType.NEW_PENDING_TRANSACTIONS, null); final Subscription builtSubscription = subscriptionBuilder.build(1L, subscribeRequest); @@ -107,7 +107,7 @@ public class SubscriptionBuilderTest { final Function function = subscriptionBuilder.mapToSubscriptionClass(Subscription.class); final Subscription logsSubscription = - new Subscription(1L, SubscriptionType.NEW_PENDING_TRANSACTIONS); + new Subscription(1L, SubscriptionType.NEW_PENDING_TRANSACTIONS, Boolean.FALSE); assertThat(function.apply(logsSubscription)).isInstanceOf(Subscription.class); } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDroppedSubscriptionServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDroppedSubscriptionServiceTest.java index 46bd8d9356..a509c1f4ec 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDroppedSubscriptionServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionDroppedSubscriptionServiceTest.java @@ -103,7 +103,10 @@ public class PendingTransactionDroppedSubscriptionServiceTest { when(subscriptionManager.subscriptionsOfType(any(), any())) .thenReturn( Arrays.stream(subscriptionsIds) - .mapToObj(id -> new Subscription(id, SubscriptionType.DROPPED_PENDING_TRANSACTIONS)) + .mapToObj( + id -> + new Subscription( + id, SubscriptionType.DROPPED_PENDING_TRANSACTIONS, Boolean.FALSE)) .collect(Collectors.toList())); } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionServiceTest.java index 751cbd553e..689071c937 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/pending/PendingTransactionSubscriptionServiceTest.java @@ -15,7 +15,6 @@ package tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.pending; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.refEq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; @@ -25,6 +24,7 @@ import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.Transaction; +import tech.pegasys.pantheon.ethereum.jsonrpc.SimpleTestTransactionBuilder; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.Subscription; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.SubscriptionManager; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.request.SubscriptionType; @@ -60,8 +60,8 @@ public class PendingTransactionSubscriptionServiceTest { @Test public void onTransactionAddedMustSendMessage() { final long[] subscriptionIds = new long[] {5, 56, 989}; - setUpSubscriptions(subscriptionIds); - final Transaction pending = transaction(TX_ONE); + setUpSubscriptions(Boolean.FALSE, subscriptionIds); + final Transaction pending = SimpleTestTransactionBuilder.transaction(TX_ONE); service.onTransactionAdded(pending); @@ -70,6 +70,19 @@ public class PendingTransactionSubscriptionServiceTest { verifySubscriptionMangerInteractions(messages(TX_ONE, subscriptionIds)); } + @Test + public void onTransactionAddedMustSendMessageWithDetails() { + final long[] subscriptionIds = new long[] {5, 56, 989}; + setUpSubscriptions(Boolean.TRUE, subscriptionIds); + final Transaction pending = SimpleTestTransactionBuilder.transaction(TX_ONE); + + service.onTransactionAdded(pending); + + verifyZeroInteractions(block); + verifyZeroInteractions(blockchain); + verifySubscriptionMangerDetailInteractions(messages(pending, subscriptionIds)); + } + private void verifySubscriptionMangerInteractions(final Map expected) { verify(subscriptionManager) .subscriptionsOfType(SubscriptionType.NEW_PENDING_TRANSACTIONS, Subscription.class); @@ -83,6 +96,18 @@ public class PendingTransactionSubscriptionServiceTest { verifyNoMoreInteractions(subscriptionManager); } + private void verifySubscriptionMangerDetailInteractions(final Map expected) { + verify(subscriptionManager) + .subscriptionsOfType(SubscriptionType.NEW_PENDING_TRANSACTIONS, Subscription.class); + + for (final Map.Entry message : expected.entrySet()) { + PendingTransactionDetailResult value = new PendingTransactionDetailResult(message.getValue()); + verify(subscriptionManager).sendMessage(eq(message.getKey()), refEq(value)); + } + + verifyNoMoreInteractions(subscriptionManager); + } + private Map messages(final Hash result, final long... subscriptionIds) { final Map messages = new HashMap<>(); @@ -93,17 +118,25 @@ public class PendingTransactionSubscriptionServiceTest { return messages; } - private Transaction transaction(final Hash hash) { - final Transaction tx = mock(Transaction.class); - when(tx.hash()).thenReturn(hash); - return tx; + private Map messages(final Transaction result, final long... subscriptionIds) { + final Map messages = new HashMap<>(); + + for (final long subscriptionId : subscriptionIds) { + messages.put(subscriptionId, result); + } + + return messages; } - private void setUpSubscriptions(final long... subscriptionsIds) { + private void setUpSubscriptions( + final Boolean includeTransactions, final long... subscriptionsIds) { when(subscriptionManager.subscriptionsOfType(any(), any())) .thenReturn( Arrays.stream(subscriptionsIds) - .mapToObj(id -> new Subscription(id, SubscriptionType.NEW_PENDING_TRANSACTIONS)) + .mapToObj( + id -> + new Subscription( + id, SubscriptionType.NEW_PENDING_TRANSACTIONS, includeTransactions)) .collect(Collectors.toList())); } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java index def01f07d0..67cffb9027 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java @@ -281,7 +281,7 @@ public class SubscriptionRequestMapperTest { parseWebSocketRpcRequest( "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newPendingTransactions\"]}"); final SubscribeRequest expectedSubscribeRequest = - new SubscribeRequest(SubscriptionType.NEW_PENDING_TRANSACTIONS, null, null, CONNECTION_ID); + new SubscribeRequest(SubscriptionType.NEW_PENDING_TRANSACTIONS, null, false, CONNECTION_ID); final SubscribeRequest subscribeRequest = mapper.mapSubscribeRequest(jsonRpcRequest); @@ -289,12 +289,12 @@ public class SubscriptionRequestMapperTest { } @Test - public void mapRequestToNewPendingTransactionsIgnoresSecondParam() { + public void mapRequestToNewPendingTransactionsParsesSecondParam() { final JsonRpcRequest jsonRpcRequest = parseWebSocketRpcRequest( - "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newPendingTransactions\", {\"foo\": \"bar\"}]}"); + "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newPendingTransactions\", {\"includeTransactions\": false}]}"); final SubscribeRequest expectedSubscribeRequest = - new SubscribeRequest(SubscriptionType.NEW_PENDING_TRANSACTIONS, null, null, CONNECTION_ID); + new SubscribeRequest(SubscriptionType.NEW_PENDING_TRANSACTIONS, null, false, CONNECTION_ID); final SubscribeRequest subscribeRequest = mapper.mapSubscribeRequest(jsonRpcRequest); @@ -307,7 +307,7 @@ public class SubscriptionRequestMapperTest { parseWebSocketRpcRequest( "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"syncing\"]}"); final SubscribeRequest expectedSubscribeRequest = - new SubscribeRequest(SubscriptionType.SYNCING, null, null, CONNECTION_ID); + new SubscribeRequest(SubscriptionType.SYNCING, null, false, CONNECTION_ID); final SubscribeRequest subscribeRequest = mapper.mapSubscribeRequest(jsonRpcRequest); @@ -315,12 +315,12 @@ public class SubscriptionRequestMapperTest { } @Test - public void mapRequestToSyncingSubscribeIgnoresSecondParam() { + public void mapRequestToSyncingSubscribeParsesSecondParam() { final JsonRpcRequest jsonRpcRequest = parseWebSocketRpcRequest( - "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"syncing\", {\"foo\": \"bar\"}]}"); + "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"syncing\", {\"includeTransactions\": true}]}"); final SubscribeRequest expectedSubscribeRequest = - new SubscribeRequest(SubscriptionType.SYNCING, null, null, CONNECTION_ID); + new SubscribeRequest(SubscriptionType.SYNCING, null, true, CONNECTION_ID); final SubscribeRequest subscribeRequest = mapper.mapSubscribeRequest(jsonRpcRequest);