Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add eth_pendingTransactions() support #2227

Merged
merged 10 commits into from
Mar 8, 2024
2 changes: 1 addition & 1 deletion rskj-core/src/main/java/co/rsk/RskContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -1869,7 +1869,7 @@ private EthModuleWallet getEthModuleWallet() {
if (wallet == null) {
ethModuleWallet = new EthModuleWalletDisabled();
} else {
ethModuleWallet = new EthModuleWalletEnabled(wallet);
ethModuleWallet = new EthModuleWalletEnabled(wallet, getTransactionPool(), getReceivedTxSignatureCache());
}
}

Expand Down
2 changes: 2 additions & 0 deletions rskj-core/src/main/java/co/rsk/rpc/Web3EthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ default String eth_sendTransaction(CallArgumentsParam args) {
TransactionResultDTO eth_getTransactionByBlockNumberAndIndex(BlockIdentifierParam bnOrId, HexIndexParam index) throws Exception;

TransactionReceiptDTO eth_getTransactionReceipt(TxHashParam transactionHash) throws Exception;
TransactionResultDTO[] eth_pendingTransactions() throws Exception;


BlockResultDTO eth_getUncleByBlockHashAndIndex(BlockHashParam blockHash, HexIndexParam uncleIdx) throws Exception;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class Web3InformationRetriever {

private static final String EARLIEST = "earliest";
private static final String LATEST = "latest";
private static final String PENDING = "pending";
public static final String PENDING = "pending";

private final TransactionPool transactionPool;
private final Blockchain blockchain;
Expand Down
8 changes: 5 additions & 3 deletions rskj-core/src/main/java/co/rsk/rpc/modules/eth/EthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.*;

import static java.util.Arrays.copyOfRange;
import static org.ethereum.rpc.exception.RskJsonRpcRequestException.invalidParamError;
Expand Down Expand Up @@ -189,6 +187,10 @@
}
}

public List<Transaction> ethPendingTransactions() {

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
EthModuleWallet.ethPendingTransactions
; it is advisable to add an Override annotation.
return ethModuleWallet.ethPendingTransactions();
}

protected String internalEstimateGas(ProgramResult reversibleExecutionResult) {
long estimatedGas = reversibleExecutionResult.getMovedRemainingGasToChild() ?
reversibleExecutionResult.getGasUsed() + reversibleExecutionResult.getDeductedRefund() :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@

package co.rsk.rpc.modules.eth;

import org.ethereum.core.Transaction;

import java.util.List;

public interface EthModuleWallet {

String[] accounts();

String sign(String addr, String data);

List<Transaction> ethPendingTransactions();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@

package co.rsk.rpc.modules.eth;

import org.ethereum.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.ethereum.rpc.exception.RskJsonRpcRequestException.invalidParamError;

Expand All @@ -36,6 +39,13 @@ public String[] accounts() {
return accounts;
}

@Override
public List<Transaction> ethPendingTransactions() {
List<Transaction> transactions = Collections.emptyList();
LOGGER.debug("eth_pendingTransactions(): {}", transactions);
return transactions;
}

@Override
public String sign(String addr, String data) {
LOGGER.debug("eth_sign({}, {}): {}", addr, data, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import co.rsk.core.RskAddress;
import org.bouncycastle.util.BigIntegers;
import org.ethereum.core.Account;
import org.ethereum.core.*;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.crypto.signature.ECDSASignature;
Expand All @@ -39,11 +41,14 @@
public class EthModuleWalletEnabled implements EthModuleWallet {

private static final Logger LOGGER = LoggerFactory.getLogger("web3");

private final Wallet wallet;
private final TransactionPool transactionPool;
private final SignatureCache signatureCache;

public EthModuleWalletEnabled(Wallet wallet) {
public EthModuleWalletEnabled(Wallet wallet, TransactionPool transactionPool, SignatureCache signatureCache) {
this.wallet = wallet;
this.transactionPool = transactionPool;
this.signatureCache = signatureCache;
}

@Override
Expand Down Expand Up @@ -90,4 +95,12 @@ private String sign(String data, ECKey ecKey) {
new byte[]{signature.getV()}
));
}
@Override
public List<Transaction> ethPendingTransactions() {
List<Transaction> pendingTxs = transactionPool.getPendingTransactions();
List<String> managedAccounts = Arrays.asList(accounts());
return pendingTxs.stream()
.filter(tx -> managedAccounts.contains(tx.getSender(signatureCache).toJsonString()))
.collect(Collectors.toList());
}
}
7 changes: 7 additions & 0 deletions rskj-core/src/main/java/org/ethereum/rpc/Web3Impl.java
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,13 @@ public TransactionReceiptDTO eth_getTransactionReceipt(TxHashParam transactionHa
return new TransactionReceiptDTO(block, txInfo, signatureCache);
}

@Override
public TransactionResultDTO[] eth_pendingTransactions() {
return ethModule.ethPendingTransactions().stream()
.map(tx -> new TransactionResultDTO(null, null, tx, config.rpcZeroSignatureIfRemasc(), signatureCache))
.toArray(TransactionResultDTO[]::new);
}

@Override
public BlockResultDTO eth_getUncleByBlockHashAndIndex(BlockHashParam blockHash, HexIndexParam uncleIdx) {
BlockResultDTO s = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ private Web3Impl internalCreateEnvironment(Blockchain blockchain,
EthModule ethModule = new EthModule(
config.getNetworkConstants().getBridgeConstants(), config.getNetworkConstants().getChainId(), blockchain, transactionPool,
reversibleTransactionExecutor1, new ExecutionBlockRetriever(blockchain, null, null),
repositoryLocator, new EthModuleWalletEnabled(wallet), transactionModule,
repositoryLocator, new EthModuleWalletEnabled(wallet, transactionPool, signatureCache), transactionModule,
new BridgeSupportFactory(
btcBlockStoreFactory, config.getNetworkConstants().getBridgeConstants(),
config.getActivationConfig(), signatureCache),
Expand Down
164 changes: 164 additions & 0 deletions rskj-core/src/test/java/co/rsk/rpc/modules/eth/EthModuleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@

import co.rsk.config.BridgeConstants;
import co.rsk.config.TestSystemProperties;
import co.rsk.core.Coin;
import co.rsk.core.ReversibleTransactionExecutor;
import co.rsk.core.RskAddress;
import co.rsk.core.Wallet;
import co.rsk.core.bc.PendingState;
import co.rsk.crypto.Keccak256;
import co.rsk.db.RepositoryLocator;
import co.rsk.net.TransactionGateway;
import co.rsk.peg.BridgeSupportFactory;
Expand All @@ -33,6 +35,7 @@
import org.ethereum.config.Constants;
import org.ethereum.core.*;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.signature.ECDSASignature;
import org.ethereum.datasource.HashMapDB;
import org.ethereum.rpc.CallArguments;
import org.ethereum.rpc.exception.RskJsonRpcRequestException;
Expand All @@ -49,6 +52,11 @@
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;
Expand Down Expand Up @@ -731,4 +739,160 @@ void whenExecuteSendTransactionWithInputAndDataParameters_callExecutorWithInput(
.receiveTransaction(transactionCaptor.capture());
assertArrayEquals(Hex.decode(expectedDataValue), transactionCaptor.getValue().getData());
}

@Test
void testEthPendingTransactionsWithNoTransactions() {
EthModuleWallet ethModuleWalletMock = mock(EthModuleWallet.class);
TransactionPool transactionPoolMock = mock(TransactionPool.class);
when(transactionPoolMock.getPendingTransactions()).thenReturn(Collections.emptyList());

Block block = mock(Block.class);
ExecutionBlockRetriever.Result blockResult = mock(ExecutionBlockRetriever.Result.class);
when(blockResult.getBlock()).thenReturn(block);
ExecutionBlockRetriever retriever = mock(ExecutionBlockRetriever.class);
Blockchain blockchain = mock(Blockchain.class);

ReversibleTransactionExecutor reversibleTransactionExecutor = mock(ReversibleTransactionExecutor.class);

EthModule ethModule = new EthModule(
null,
Constants.REGTEST_CHAIN_ID,
blockchain,
transactionPoolMock,
reversibleTransactionExecutor,
retriever,
mock(RepositoryLocator.class),
ethModuleWalletMock,
null,
new BridgeSupportFactory(
null, null, null, signatureCache),
config.getGasEstimationCap(),
config.getCallGasCap());

List<Transaction> result = ethModule.ethPendingTransactions();

assertTrue(result.isEmpty(), "Expected no transactions");
}

@Test
void pendingTransactionsWithMultipleManagedAccounts() {
Wallet wallet = mock(Wallet.class);
TransactionPool transactionPoolMock = mock(TransactionPool.class);
EthModuleWalletEnabled ethModuleWallet = new EthModuleWalletEnabled(wallet, transactionPoolMock, signatureCache);
ExecutionBlockRetriever retriever = mock(ExecutionBlockRetriever.class);
Blockchain blockchain = mock(Blockchain.class);
ReversibleTransactionExecutor reversibleTransactionExecutor = mock(ReversibleTransactionExecutor.class);

EthModule ethModule = new EthModule(
null,
Constants.REGTEST_CHAIN_ID,
blockchain,
transactionPoolMock,
reversibleTransactionExecutor,
retriever,
mock(RepositoryLocator.class),
ethModuleWallet,
null,
new BridgeSupportFactory(null, null, null, signatureCache),
config.getGasEstimationCap(),
config.getCallGasCap());

Transaction mockTransaction1 = createMockTransaction("0x63a15ed8c3b83efc744f2e0a7824a00846c21860");
Transaction mockTransaction2 = createMockTransaction( "0xa3a15ed8c3b83efc744f2e0a7824a00846c21860");
Transaction mockTransaction3 = createMockTransaction( "0xb3a15ed8c3b83efc744f2e0a7824a00846c21860");
List<Transaction> allTransactions = Arrays.asList(mockTransaction1, mockTransaction2, mockTransaction3);

when(transactionPoolMock.getPendingTransactions()).thenReturn(allTransactions);
when(ethModuleWallet.accounts()).thenReturn(new String[]{"0x63a15ed8c3b83efc744f2e0a7824a00846c21860", "0xa3a15ed8c3b83efc744f2e0a7824a00846c21860"});

List<Transaction> result = ethModule.ethPendingTransactions();

assertEquals(2, result.size(), "Expected only transactions from managed accounts");
}

@Test
void pendingTransactionsWithNoManagedAccounts() {
Wallet wallet = mock(Wallet.class);
TransactionPool transactionPoolMock = mock(TransactionPool.class);
EthModuleWalletEnabled ethModuleWallet = new EthModuleWalletEnabled(wallet, transactionPoolMock, signatureCache);
ExecutionBlockRetriever retriever = mock(ExecutionBlockRetriever.class);
Blockchain blockchain = mock(Blockchain.class);
ReversibleTransactionExecutor reversibleTransactionExecutor = mock(ReversibleTransactionExecutor.class);

EthModule ethModule = new EthModule(
null,
Constants.REGTEST_CHAIN_ID,
blockchain,
transactionPoolMock,
reversibleTransactionExecutor,
retriever,
mock(RepositoryLocator.class),
ethModuleWallet,
null,
new BridgeSupportFactory(null, null, null, signatureCache),
config.getGasEstimationCap(),
config.getCallGasCap());

Transaction mockTransaction1 = createMockTransaction("0x63a15ed8c3b83efc744f2e0a7824a00846c21860");
Transaction mockTransaction2 = createMockTransaction("0x13a15ed8c3b83efc744f2e0a7824a00846c21860");
List<Transaction> allTransactions = Arrays.asList(mockTransaction1, mockTransaction2);

when(transactionPoolMock.getPendingTransactions()).thenReturn(allTransactions);
when(ethModuleWallet.accounts()).thenReturn(new String[]{});

List<Transaction> result = ethModule.ethPendingTransactions();

assertTrue(result.isEmpty(), "Expected no transactions as there are no managed accounts");
}

@Test
void pendingTransactionsWithWalletDisabled() {
TransactionPool transactionPoolMock = mock(TransactionPool.class);
EthModuleWalletDisabled ethModuleWallet = new EthModuleWalletDisabled();
ExecutionBlockRetriever retriever = mock(ExecutionBlockRetriever.class);
Blockchain blockchain = mock(Blockchain.class);
ReversibleTransactionExecutor reversibleTransactionExecutor = mock(ReversibleTransactionExecutor.class);

EthModule ethModule = new EthModule(
null,
Constants.REGTEST_CHAIN_ID,
blockchain,
transactionPoolMock,
reversibleTransactionExecutor,
retriever,
mock(RepositoryLocator.class),
ethModuleWallet,
null,
new BridgeSupportFactory(null, null, null, signatureCache),
config.getGasEstimationCap(),
config.getCallGasCap());

List<Transaction> result = ethModule.ethPendingTransactions();

assertTrue(result.isEmpty(), "Expected no transactions as wallet is disabled");
}


private Transaction createMockTransaction(String fromAddress) {
Transaction transaction = mock(Transaction.class);
RskAddress address = new RskAddress(fromAddress);
System.out.println("mock address: " + address);
when(transaction.getSender(any(SignatureCache.class))).thenReturn(address);

byte[] mockHashBytes = new byte[32];
Arrays.fill(mockHashBytes, (byte) 1);
Keccak256 mockHash = new Keccak256(mockHashBytes);
when(transaction.getHash()).thenReturn(mockHash);
when(transaction.getReceiveAddress()).thenReturn(address);
when(transaction.getNonce()).thenReturn(BigInteger.ZERO.toByteArray());
when(transaction.getGasLimit()).thenReturn(BigInteger.valueOf(21000).toByteArray());
when(transaction.getGasPrice()).thenReturn(Coin.valueOf(50_000_000_000L));
when(transaction.getValue()).thenReturn(Coin.ZERO);
when(transaction.getData()).thenReturn(new byte[0]);
ECDSASignature mockSignature = new ECDSASignature(BigInteger.ONE, BigInteger.ONE);
when(transaction.getSignature()).thenReturn(mockSignature);
when(transaction.getEncodedV()).thenReturn((byte) 1);

return transaction;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ private Web3Impl createWeb3() {
EthModule ethModule = new EthModule(
config.getNetworkConstants().getBridgeConstants(), config.getNetworkConstants().getChainId(), blockChain, transactionPool,
null, new ExecutionBlockRetriever(blockChain, null, null),
null, new EthModuleWalletEnabled(wallet), null,
null, new EthModuleWalletEnabled(wallet, transactionPool, signatureCache), null,
new BridgeSupportFactory(
null, config.getNetworkConstants().getBridgeConstants(), config.getActivationConfig(), new BlockTxSignatureCache(new ReceivedTxSignatureCache())),
config.getGasEstimationCap(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ private static Web3Impl createWeb3(PeerScoringManager peerScoringManager) {
EthModule em = new EthModule(
config.getNetworkConstants().getBridgeConstants(), config.getNetworkConstants().getChainId(), world.getBlockChain(), null,
null, new ExecutionBlockRetriever(world.getBlockChain(), null, null),
null, new EthModuleWalletEnabled(wallet), null,
null, new EthModuleWalletEnabled(wallet, world.getTransactionPool(), world.getBlockTxSignatureCache()), null,
new BridgeSupportFactory(
null, config.getNetworkConstants().getBridgeConstants(), config.getActivationConfig(), new BlockTxSignatureCache(new ReceivedTxSignatureCache())),
config.getGasEstimationCap(),
Expand Down
Loading
Loading