mirror of https://github.com/hyperledger/besu
Add txpool_besuPendingTransactions API (#1196)
Add txpool_besuPendingTransactions API which add options to filter pending transactions. Signed-off-by: Karim TAAM <karim.t2am@gmail.com>pull/976/head
parent
039b500ed5
commit
67cae63214
@ -0,0 +1,68 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.PendingTransactionsParams; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionPendingResult; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.Filter; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
public class TxPoolBesuPendingTransactions implements JsonRpcMethod { |
||||||
|
|
||||||
|
final PendingTransactionFilter pendingTransactionFilter; |
||||||
|
|
||||||
|
private final PendingTransactions pendingTransactions; |
||||||
|
|
||||||
|
public TxPoolBesuPendingTransactions(final PendingTransactions pendingTransactions) { |
||||||
|
this.pendingTransactions = pendingTransactions; |
||||||
|
this.pendingTransactionFilter = new PendingTransactionFilter(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return RpcMethod.TX_POOL_BESU_PENDING_TRANSACTIONS.getMethodName(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { |
||||||
|
|
||||||
|
final Integer limit = requestContext.getRequiredParameter(0, Integer.class); |
||||||
|
final List<Filter> filters = |
||||||
|
requestContext |
||||||
|
.getOptionalParameter(1, PendingTransactionsParams.class) |
||||||
|
.map(PendingTransactionsParams::filters) |
||||||
|
.orElse(Collections.emptyList()); |
||||||
|
|
||||||
|
final Set<Transaction> pendingTransactionsFiltered = |
||||||
|
pendingTransactionFilter.reduce(pendingTransactions.getTransactionInfo(), filters, limit); |
||||||
|
|
||||||
|
return new JsonRpcSuccessResponse( |
||||||
|
requestContext.getRequest().getId(), |
||||||
|
pendingTransactionsFiltered.stream() |
||||||
|
.map(TransactionPendingResult::new) |
||||||
|
.collect(Collectors.toSet())); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; |
||||||
|
|
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.FROM_FIELD; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.GAS_FIELD; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.GAS_PRICE_FIELD; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.NONCE_FIELD; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.TO_FIELD; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.VALUE_FIELD; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.ACTION; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.EQ; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.Filter; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator; |
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true) |
||||||
|
public class PendingTransactionsParams { |
||||||
|
|
||||||
|
private final Map<String, String> from, to, gas, gasPrice, value, nonce; |
||||||
|
|
||||||
|
@JsonCreator() |
||||||
|
public PendingTransactionsParams( |
||||||
|
@JsonProperty(FROM_FIELD) final Map<String, String> from, |
||||||
|
@JsonProperty(TO_FIELD) final Map<String, String> to, |
||||||
|
@JsonProperty(GAS_FIELD) final Map<String, String> gas, |
||||||
|
@JsonProperty(GAS_PRICE_FIELD) final Map<String, String> gasPrice, |
||||||
|
@JsonProperty(VALUE_FIELD) final Map<String, String> value, |
||||||
|
@JsonProperty(NONCE_FIELD) final Map<String, String> nonce) { |
||||||
|
this.from = from; |
||||||
|
this.to = to; |
||||||
|
this.gas = gas; |
||||||
|
this.gasPrice = gasPrice; |
||||||
|
this.value = value; |
||||||
|
this.nonce = nonce; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Filter> filters() throws IllegalArgumentException { |
||||||
|
final List<Filter> createdFilters = new ArrayList<>(); |
||||||
|
getFilter(FROM_FIELD, from).ifPresent(createdFilters::add); |
||||||
|
getFilter(TO_FIELD, to).ifPresent(createdFilters::add); |
||||||
|
getFilter(GAS_FIELD, gas).ifPresent(createdFilters::add); |
||||||
|
getFilter(GAS_PRICE_FIELD, gasPrice).ifPresent(createdFilters::add); |
||||||
|
getFilter(VALUE_FIELD, value).ifPresent(createdFilters::add); |
||||||
|
getFilter(NONCE_FIELD, nonce).ifPresent(createdFilters::add); |
||||||
|
return createdFilters; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method allows to retrieve a list of filters related to a key |
||||||
|
* |
||||||
|
* @param key the key that will be linked to the filters |
||||||
|
* @param map the list of filters to parse |
||||||
|
* @return the list of filters |
||||||
|
*/ |
||||||
|
private Optional<Filter> getFilter(final String key, final Map<String, String> map) { |
||||||
|
if (map != null) { |
||||||
|
if (map.size() > 1) { |
||||||
|
throw new InvalidJsonRpcParameters("Only one operator per filter type allowed"); |
||||||
|
} else if (!map.isEmpty()) { |
||||||
|
final Map.Entry<String, String> foundEntry = map.entrySet().stream().findFirst().get(); |
||||||
|
final Predicate predicate = |
||||||
|
Predicate.fromValue(foundEntry.getKey().toUpperCase()) |
||||||
|
.orElseThrow( |
||||||
|
() -> |
||||||
|
new InvalidJsonRpcParameters( |
||||||
|
"Unknown field expected one of `eq`, `gt`, `lt`, `action`")); |
||||||
|
|
||||||
|
final Filter filter = new Filter(key, foundEntry.getValue(), predicate); |
||||||
|
if (key.equals(FROM_FIELD) && !predicate.equals(EQ)) { |
||||||
|
throw new InvalidJsonRpcParameters("The `from` filter only supports the `eq` operator"); |
||||||
|
} else if (key.equals(TO_FIELD) && !predicate.equals(EQ) && !predicate.equals(ACTION)) { |
||||||
|
throw new InvalidJsonRpcParameters( |
||||||
|
"The `to` filter only supports the `eq` or `action` operator"); |
||||||
|
} else if (!key.equals(TO_FIELD) && predicate.equals(ACTION)) { |
||||||
|
throw new InvalidJsonRpcParameters( |
||||||
|
"The operator `action` is only supported by the `to` filter"); |
||||||
|
} |
||||||
|
return Optional.of(filter); |
||||||
|
} |
||||||
|
} |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool; |
||||||
|
|
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.ACTION; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.EQ; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; |
||||||
|
import org.hyperledger.besu.ethereum.core.Address; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.core.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions.TransactionInfo; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class allows to filter a list of pending transactions |
||||||
|
* |
||||||
|
* <p>Here is the list of fields that can be used to filter a transaction : from, to, gas, gasPrice, |
||||||
|
* value, nonce |
||||||
|
*/ |
||||||
|
public class PendingTransactionFilter { |
||||||
|
|
||||||
|
public static final String FROM_FIELD = "from"; |
||||||
|
public static final String TO_FIELD = "to"; |
||||||
|
public static final String GAS_FIELD = "gas"; |
||||||
|
public static final String GAS_PRICE_FIELD = "gasPrice"; |
||||||
|
public static final String VALUE_FIELD = "value"; |
||||||
|
public static final String NONCE_FIELD = "nonce"; |
||||||
|
|
||||||
|
public Set<Transaction> reduce( |
||||||
|
final Set<TransactionInfo> pendingTransactions, final List<Filter> filters, final int limit) |
||||||
|
throws InvalidJsonRpcParameters { |
||||||
|
return pendingTransactions.stream() |
||||||
|
.filter(transactionInfo -> applyFilters(transactionInfo, filters)) |
||||||
|
.limit(limit) |
||||||
|
.map(TransactionInfo::getTransaction) |
||||||
|
.collect(Collectors.toSet()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean applyFilters(final TransactionInfo transactionInfo, final List<Filter> filters) |
||||||
|
throws InvalidJsonRpcParameters { |
||||||
|
boolean isValid = true; |
||||||
|
for (Filter filter : filters) { |
||||||
|
final Predicate predicate = filter.getPredicate(); |
||||||
|
final String value = filter.getFieldValue(); |
||||||
|
switch (filter.getFieldName()) { |
||||||
|
case FROM_FIELD: |
||||||
|
isValid = validateFrom(transactionInfo, predicate, value); |
||||||
|
break; |
||||||
|
case TO_FIELD: |
||||||
|
isValid = validateTo(transactionInfo, predicate, value); |
||||||
|
break; |
||||||
|
case GAS_PRICE_FIELD: |
||||||
|
isValid = validateWei(transactionInfo.getTransaction().getGasPrice(), predicate, value); |
||||||
|
break; |
||||||
|
case GAS_FIELD: |
||||||
|
isValid = |
||||||
|
validateWei(Wei.of(transactionInfo.getTransaction().getGasLimit()), predicate, value); |
||||||
|
break; |
||||||
|
case VALUE_FIELD: |
||||||
|
isValid = validateWei(transactionInfo.getTransaction().getValue(), predicate, value); |
||||||
|
break; |
||||||
|
case NONCE_FIELD: |
||||||
|
isValid = validateNonce(transactionInfo, predicate, value); |
||||||
|
break; |
||||||
|
} |
||||||
|
if (!isValid) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean validateFrom( |
||||||
|
final TransactionInfo transactionInfo, final Predicate predicate, final String value) |
||||||
|
throws InvalidJsonRpcParameters { |
||||||
|
return predicate |
||||||
|
.getOperator() |
||||||
|
.apply(transactionInfo.getTransaction().getSender(), Address.fromHexString(value)); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean validateTo( |
||||||
|
final TransactionInfo transactionInfo, final Predicate predicate, final String value) |
||||||
|
throws InvalidJsonRpcParameters { |
||||||
|
final Optional<Address> maybeTo = transactionInfo.getTransaction().getTo(); |
||||||
|
if (maybeTo.isPresent() && predicate.equals(EQ)) { |
||||||
|
return predicate.getOperator().apply(maybeTo.get(), Address.fromHexString(value)); |
||||||
|
} else if (predicate.equals(ACTION)) { |
||||||
|
return transactionInfo.getTransaction().isContractCreation(); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean validateNonce( |
||||||
|
final TransactionInfo transactionInfo, final Predicate predicate, final String value) |
||||||
|
throws InvalidJsonRpcParameters { |
||||||
|
return predicate |
||||||
|
.getOperator() |
||||||
|
.apply(transactionInfo.getTransaction().getNonce(), Long.decode(value)); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean validateWei( |
||||||
|
final Wei transactionWei, final Predicate predicate, final String value) |
||||||
|
throws InvalidJsonRpcParameters { |
||||||
|
return predicate.getOperator().apply(transactionWei, Wei.fromHexString(value)); |
||||||
|
} |
||||||
|
|
||||||
|
public static class Filter { |
||||||
|
|
||||||
|
private final String fieldName; |
||||||
|
private final String fieldValue; |
||||||
|
private final Predicate predicate; |
||||||
|
|
||||||
|
public Filter(final String fieldName, final String fieldValue, final Predicate predicate) { |
||||||
|
this.fieldName = fieldName; |
||||||
|
this.fieldValue = fieldValue; |
||||||
|
this.predicate = predicate; |
||||||
|
} |
||||||
|
|
||||||
|
public String getFieldName() { |
||||||
|
return fieldName; |
||||||
|
} |
||||||
|
|
||||||
|
public String getFieldValue() { |
||||||
|
return fieldValue; |
||||||
|
} |
||||||
|
|
||||||
|
public Predicate getPredicate() { |
||||||
|
return predicate; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool; |
||||||
|
|
||||||
|
import java.util.Optional; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class describes the behavior of predicates that can be used to filter pending transactions |
||||||
|
*/ |
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"}) |
||||||
|
public enum Predicate { |
||||||
|
EQ((left, right) -> left.compareTo(right) == 0), |
||||||
|
LT((left, right) -> left.compareTo(right) < 0), |
||||||
|
GT((left, right) -> left.compareTo(right) > 0), |
||||||
|
ACTION((left, right) -> false); |
||||||
|
|
||||||
|
private final Operator operator; |
||||||
|
|
||||||
|
Predicate(final Operator predicate) { |
||||||
|
this.operator = predicate; |
||||||
|
} |
||||||
|
|
||||||
|
public Operator getOperator() { |
||||||
|
return operator; |
||||||
|
} |
||||||
|
|
||||||
|
@FunctionalInterface |
||||||
|
public interface Operator { |
||||||
|
boolean apply(final Comparable left, final Comparable right); |
||||||
|
} |
||||||
|
|
||||||
|
public static Optional<Predicate> fromValue(final String value) { |
||||||
|
return Stream.of(values()).filter(predicate -> predicate.name().equals(value)).findFirst(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,259 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.PendingTransactionsParams; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionPendingResult; |
||||||
|
import org.hyperledger.besu.ethereum.core.Address; |
||||||
|
import org.hyperledger.besu.ethereum.core.Hash; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.core.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; |
||||||
|
|
||||||
|
import java.math.BigInteger; |
||||||
|
import java.time.Instant; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class TxPoolBesuPendingTransactionsTest { |
||||||
|
|
||||||
|
@Mock private PendingTransactions pendingTransactions; |
||||||
|
private TxPoolBesuPendingTransactions method; |
||||||
|
private final String JSON_RPC_VERSION = "2.0"; |
||||||
|
private final String TXPOOL_PENDING_TRANSACTIONS_METHOD = "txpool_besuPendingTransactions"; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
final Set<PendingTransactions.TransactionInfo> listTrx = getPendingTransactions(); |
||||||
|
method = new TxPoolBesuPendingTransactions(pendingTransactions); |
||||||
|
when(this.pendingTransactions.getTransactionInfo()).thenReturn(listTrx); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void returnsCorrectMethodName() { |
||||||
|
assertThat(method.getName()).isEqualTo(TXPOOL_PENDING_TRANSACTIONS_METHOD); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnPendingTransactions() { |
||||||
|
final JsonRpcRequestContext request = |
||||||
|
new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
JSON_RPC_VERSION, TXPOOL_PENDING_TRANSACTIONS_METHOD, new Object[] {100})); |
||||||
|
|
||||||
|
final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request); |
||||||
|
final Set<TransactionPendingResult> result = |
||||||
|
(Set<TransactionPendingResult>) actualResponse.getResult(); |
||||||
|
assertThat(result.size()).isEqualTo(4); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnPendingTransactionsWithLimit() { |
||||||
|
final JsonRpcRequestContext request = |
||||||
|
new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
JSON_RPC_VERSION, TXPOOL_PENDING_TRANSACTIONS_METHOD, new Object[] {1})); |
||||||
|
|
||||||
|
final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request); |
||||||
|
|
||||||
|
final Set<TransactionPendingResult> result = |
||||||
|
(Set<TransactionPendingResult>) actualResponse.getResult(); |
||||||
|
assertThat(result.size()).isEqualTo(1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnPendingTransactionsWithFilter() { |
||||||
|
|
||||||
|
final Map<String, String> fromFilter = new HashMap<>(); |
||||||
|
fromFilter.put("eq", "0x0000000000000000000000000000000000000001"); |
||||||
|
|
||||||
|
final JsonRpcRequestContext request = |
||||||
|
new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
JSON_RPC_VERSION, |
||||||
|
TXPOOL_PENDING_TRANSACTIONS_METHOD, |
||||||
|
new Object[] { |
||||||
|
100, |
||||||
|
new PendingTransactionsParams( |
||||||
|
fromFilter, |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>()) |
||||||
|
})); |
||||||
|
|
||||||
|
final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request); |
||||||
|
|
||||||
|
final Set<TransactionPendingResult> result = |
||||||
|
(Set<TransactionPendingResult>) actualResponse.getResult(); |
||||||
|
assertThat(result.size()).isEqualTo(1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnsErrorIfInvalidPredicate() { |
||||||
|
|
||||||
|
final Map<String, String> fromFilter = new HashMap<>(); |
||||||
|
fromFilter.put("invalid", "0x0000000000000000000000000000000000000001"); |
||||||
|
|
||||||
|
final JsonRpcRequestContext request = |
||||||
|
new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
JSON_RPC_VERSION, |
||||||
|
TXPOOL_PENDING_TRANSACTIONS_METHOD, |
||||||
|
new Object[] { |
||||||
|
100, |
||||||
|
new PendingTransactionsParams( |
||||||
|
fromFilter, |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>()) |
||||||
|
})); |
||||||
|
|
||||||
|
assertThatThrownBy(() -> method.response(request)) |
||||||
|
.isInstanceOf(InvalidJsonRpcParameters.class) |
||||||
|
.hasMessageContaining("Unknown field expected one of `eq`, `gt`, `lt`, `action`"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnsErrorIfInvalidNumberOfPredicate() { |
||||||
|
|
||||||
|
final Map<String, String> fromFilter = new HashMap<>(); |
||||||
|
fromFilter.put("eq", "0x0000000000000000000000000000000000000001"); |
||||||
|
fromFilter.put("lt", "0x0000000000000000000000000000000000000001"); |
||||||
|
|
||||||
|
final JsonRpcRequestContext request = |
||||||
|
new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
JSON_RPC_VERSION, |
||||||
|
TXPOOL_PENDING_TRANSACTIONS_METHOD, |
||||||
|
new Object[] { |
||||||
|
100, |
||||||
|
new PendingTransactionsParams( |
||||||
|
fromFilter, |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>()) |
||||||
|
})); |
||||||
|
|
||||||
|
assertThatThrownBy(() -> method.response(request)) |
||||||
|
.isInstanceOf(InvalidJsonRpcParameters.class) |
||||||
|
.hasMessageContaining("Only one operator per filter type allowed"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnsErrorIfInvalidPredicateUsedForFromField() { |
||||||
|
|
||||||
|
final Map<String, String> fromFilter = new HashMap<>(); |
||||||
|
fromFilter.put("lt", "0x0000000000000000000000000000000000000001"); |
||||||
|
|
||||||
|
final JsonRpcRequestContext request = |
||||||
|
new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
JSON_RPC_VERSION, |
||||||
|
TXPOOL_PENDING_TRANSACTIONS_METHOD, |
||||||
|
new Object[] { |
||||||
|
100, |
||||||
|
new PendingTransactionsParams( |
||||||
|
fromFilter, |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>()) |
||||||
|
})); |
||||||
|
|
||||||
|
assertThatThrownBy(() -> method.response(request)) |
||||||
|
.isInstanceOf(InvalidJsonRpcParameters.class) |
||||||
|
.hasMessageContaining("The `from` filter only supports the `eq` operator"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldReturnsErrorIfInvalidPredicateUsedForToField() { |
||||||
|
|
||||||
|
final Map<String, String> toFilter = new HashMap<>(); |
||||||
|
toFilter.put("lt", "0x0000000000000000000000000000000000000001"); |
||||||
|
|
||||||
|
final JsonRpcRequestContext request = |
||||||
|
new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
JSON_RPC_VERSION, |
||||||
|
TXPOOL_PENDING_TRANSACTIONS_METHOD, |
||||||
|
new Object[] { |
||||||
|
100, |
||||||
|
new PendingTransactionsParams( |
||||||
|
new HashMap<>(), |
||||||
|
toFilter, |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>(), |
||||||
|
new HashMap<>()) |
||||||
|
})); |
||||||
|
|
||||||
|
assertThatThrownBy(() -> method.response(request)) |
||||||
|
.isInstanceOf(InvalidJsonRpcParameters.class) |
||||||
|
.hasMessageContaining("The `to` filter only supports the `eq` or `action` operator"); |
||||||
|
} |
||||||
|
|
||||||
|
private Set<PendingTransactions.TransactionInfo> getPendingTransactions() { |
||||||
|
final List<PendingTransactions.TransactionInfo> transactionInfoList = new ArrayList<>(); |
||||||
|
for (int i = 1; i < 5; i++) { |
||||||
|
Transaction transaction = mock(Transaction.class); |
||||||
|
when(transaction.getGasPrice()).thenReturn(Wei.of(i)); |
||||||
|
when(transaction.getValue()).thenReturn(Wei.of(i)); |
||||||
|
when(transaction.getGasLimit()).thenReturn((long) i); |
||||||
|
when(transaction.getNonce()).thenReturn((long) i); |
||||||
|
when(transaction.getPayload()).thenReturn(Bytes.EMPTY); |
||||||
|
when(transaction.getV()).thenReturn(BigInteger.ONE); |
||||||
|
when(transaction.getR()).thenReturn(BigInteger.ONE); |
||||||
|
when(transaction.getS()).thenReturn(BigInteger.ONE); |
||||||
|
when(transaction.getSender()).thenReturn(Address.fromHexString(String.valueOf(i))); |
||||||
|
when(transaction.getTo()) |
||||||
|
.thenReturn(Optional.of(Address.fromHexString(String.valueOf(i + 1)))); |
||||||
|
when(transaction.getHash()).thenReturn(Hash.fromHexStringLenient(String.valueOf(i))); |
||||||
|
transactionInfoList.add( |
||||||
|
new PendingTransactions.TransactionInfo( |
||||||
|
transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE))); |
||||||
|
} |
||||||
|
return new LinkedHashSet<>(transactionInfoList); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool; |
||||||
|
|
||||||
|
import static java.util.Arrays.asList; |
||||||
|
import static java.util.Collections.EMPTY_LIST; |
||||||
|
import static java.util.Collections.singletonList; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.ACTION; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.EQ; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.GT; |
||||||
|
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.LT; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.Filter; |
||||||
|
import org.hyperledger.besu.ethereum.core.Address; |
||||||
|
import org.hyperledger.besu.ethereum.core.Hash; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.core.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
|
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class PendingTransactionFilterTest { |
||||||
|
|
||||||
|
@Parameterized.Parameters |
||||||
|
public static Collection<Object[]> data() { |
||||||
|
return asList( |
||||||
|
new Object[][] { |
||||||
|
{ |
||||||
|
singletonList(new Filter("from", "0x0000000000000000000000000000000000000001", EQ)), |
||||||
|
100, |
||||||
|
singletonList("1") |
||||||
|
}, |
||||||
|
{ |
||||||
|
singletonList(new Filter("to", "0x0000000000000000000000000000000000000002", EQ)), |
||||||
|
100, |
||||||
|
singletonList("1") |
||||||
|
}, |
||||||
|
{singletonList(new Filter("gas", "0x01", EQ)), 100, singletonList("1")}, |
||||||
|
{singletonList(new Filter("gas", "0x01", LT)), 100, EMPTY_LIST}, |
||||||
|
{singletonList(new Filter("gas", "0x01", GT)), 100, asList("2", "3", "4")}, |
||||||
|
{singletonList(new Filter("gas", "0x01", GT)), 1, singletonList("2")}, |
||||||
|
{singletonList(new Filter("gasPrice", "0x01", EQ)), 100, singletonList("1")}, |
||||||
|
{singletonList(new Filter("gasPrice", "0x01", LT)), 100, EMPTY_LIST}, |
||||||
|
{singletonList(new Filter("gasPrice", "0x01", GT)), 100, asList("2", "3", "4")}, |
||||||
|
{singletonList(new Filter("gasPrice", "0x01", GT)), 1, singletonList("2")}, |
||||||
|
{singletonList(new Filter("value", "0x01", EQ)), 100, singletonList("1")}, |
||||||
|
{singletonList(new Filter("value", "0x01", LT)), 100, EMPTY_LIST}, |
||||||
|
{singletonList(new Filter("value", "0x01", GT)), 100, asList("2", "3", "4")}, |
||||||
|
{singletonList(new Filter("value", "0x01", GT)), 1, singletonList("2")}, |
||||||
|
{singletonList(new Filter("nonce", "0x01", EQ)), 100, singletonList("1")}, |
||||||
|
{singletonList(new Filter("nonce", "0x01", LT)), 100, EMPTY_LIST}, |
||||||
|
{singletonList(new Filter("nonce", "0x01", GT)), 100, asList("2", "3", "4")}, |
||||||
|
{singletonList(new Filter("nonce", "0x01", GT)), 1, singletonList("2")}, |
||||||
|
{ |
||||||
|
asList(new Filter("gas", "0x03", GT), new Filter("gasPrice", "0x02", GT)), |
||||||
|
100, |
||||||
|
singletonList("4") |
||||||
|
}, |
||||||
|
{ |
||||||
|
asList(new Filter("from", "0x01", EQ), new Filter("gasPrice", "0x02", GT)), |
||||||
|
100, |
||||||
|
EMPTY_LIST |
||||||
|
}, |
||||||
|
{singletonList(new Filter("to", "contract_creation", ACTION)), 1, singletonList("4")}, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private final PendingTransactionFilter pendingTransactionFilter = new PendingTransactionFilter(); |
||||||
|
|
||||||
|
private final List<Filter> filters; |
||||||
|
private final int limit; |
||||||
|
private final List<String> expectedListOfTransactionHash; |
||||||
|
|
||||||
|
public PendingTransactionFilterTest( |
||||||
|
final List<Filter> filters, |
||||||
|
final int limit, |
||||||
|
final List<String> expectedListOfTransactionHash) { |
||||||
|
this.filters = filters; |
||||||
|
this.limit = limit; |
||||||
|
this.expectedListOfTransactionHash = |
||||||
|
expectedListOfTransactionHash.stream() |
||||||
|
.map(Hash::fromHexStringLenient) |
||||||
|
.map(Hash::toHexString) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void localAndRemoteAddressShouldNotStartWithForwardSlash() { |
||||||
|
|
||||||
|
final Set<Transaction> filteredList = |
||||||
|
pendingTransactionFilter.reduce(getPendingTransactions(), filters, limit); |
||||||
|
|
||||||
|
assertThat(filteredList.size()).isEqualTo(expectedListOfTransactionHash.size()); |
||||||
|
for (Transaction trx : filteredList) { |
||||||
|
assertThat(expectedListOfTransactionHash).contains(trx.getHash().toHexString()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Set<PendingTransactions.TransactionInfo> getPendingTransactions() { |
||||||
|
final List<PendingTransactions.TransactionInfo> transactionInfoList = new ArrayList<>(); |
||||||
|
final int numberTrx = 5; |
||||||
|
for (int i = 1; i < numberTrx; i++) { |
||||||
|
Transaction transaction = mock(Transaction.class); |
||||||
|
when(transaction.getGasPrice()).thenReturn(Wei.of(i)); |
||||||
|
when(transaction.getValue()).thenReturn(Wei.of(i)); |
||||||
|
when(transaction.getGasLimit()).thenReturn((long) i); |
||||||
|
when(transaction.getNonce()).thenReturn((long) i); |
||||||
|
when(transaction.getSender()).thenReturn(Address.fromHexString(String.valueOf(i))); |
||||||
|
when(transaction.getTo()) |
||||||
|
.thenReturn(Optional.of(Address.fromHexString(String.valueOf(i + 1)))); |
||||||
|
when(transaction.getHash()).thenReturn(Hash.fromHexStringLenient(String.valueOf(i))); |
||||||
|
if (i == numberTrx - 1) { |
||||||
|
when(transaction.isContractCreation()).thenReturn(true); |
||||||
|
} |
||||||
|
transactionInfoList.add( |
||||||
|
new PendingTransactions.TransactionInfo( |
||||||
|
transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE))); |
||||||
|
} |
||||||
|
return new LinkedHashSet<>(transactionInfoList); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue