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

Support eth_getProof JSON RPC method #1519

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@

import co.rsk.core.Coin;
import co.rsk.core.RskAddress;
import co.rsk.crypto.Keccak256;
import org.ethereum.vm.DataWord;

import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;

public interface AccountInformationProvider {

Expand Down Expand Up @@ -95,4 +97,32 @@ public interface AccountInformationProvider {
* @return value of the nonce
*/
BigInteger getNonce(RskAddress addr);

/**
* Retrieve a Keccak256(of the current storage root) of a given account
*
* @param addr an address
* @return Keccak256(storageRoot)
* */
byte[] getStorageHash(RskAddress addr);

/**
* Retrieves an account proof for a given address
* An account proof represents all the nodes starting from the state root following the path by the given the address.
* Each node is serialized and RLP encoded.
*
* @param addr an address
* @return a list of account proofs for a given address
* */
List<byte[]> getAccountProof(RskAddress addr);

/**
* Retrieves an account proof for a given address
* An account proof represents all the nodes starting from the state root following the path by the given the address.
* Each node is serialized and RLP encoded.
*
* @param addr an address
* @return a list of storage proofs for a given address and storage key
* */
List<byte[]> getStorageProof(RskAddress addr, DataWord storageKey);
}
16 changes: 16 additions & 0 deletions rskj-core/src/main/java/co/rsk/core/bc/PendingState.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import co.rsk.core.Coin;
import co.rsk.core.RskAddress;
import co.rsk.crypto.Keccak256;
import co.rsk.db.RepositorySnapshot;
import org.ethereum.core.Repository;
import org.ethereum.core.Transaction;
Expand Down Expand Up @@ -100,6 +101,21 @@ public BigInteger getNonce(RskAddress addr) {
return maxNonce.orElse(nextNonce);
}

@Override
public byte[] getStorageHash(RskAddress addr) {
return postExecutionReturn(executedRepository -> executedRepository.getStorageHash(addr));
}

@Override
public List<byte[]> getAccountProof(RskAddress addr) {
return postExecutionReturn(executedRepository -> executedRepository.getAccountProof(addr));
}

@Override
public List<byte[]> getStorageProof(RskAddress addr, DataWord storageKey) {
return postExecutionReturn(executedRepository -> executedRepository.getStorageProof(addr, storageKey));
}

// sortByPriceTakingIntoAccountSenderAndNonce sorts the transactions by price, but
// first clustering by sender and then each cluster is order by nonce.
//
Expand Down
5 changes: 5 additions & 0 deletions rskj-core/src/main/java/co/rsk/db/MutableTrieCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ public Iterator<DataWord> getStorageKeys(RskAddress addr) {
return new StorageKeysIterator(storageKeys, accountItems, addr, trieKeyMapper);
}

@Override
public List<Trie> getNodes(byte[] key) {
return trie.getNodes(key);
}

// This method returns a wrapper with the same content and size expected for a account key
// when the key is from the same size than the original wrapper, it returns the same object
private ByteArrayWrapper getAccountWrapper(ByteArrayWrapper originalWrapper) {
Expand Down
5 changes: 5 additions & 0 deletions rskj-core/src/main/java/co/rsk/db/MutableTrieImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public Iterator<DataWord> getStorageKeys(RskAddress addr) {
return Collections.emptyIterator();
}

@Override
public List<Trie> getNodes(byte[] key) {
return trie.getNodes(key);
}

@Override
public void deleteRecursive(byte[] key) {
trie = trie.deleteRecursive(key);
Expand Down
40 changes: 26 additions & 14 deletions rskj-core/src/main/java/co/rsk/rpc/Web3EthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
package co.rsk.rpc;

import co.rsk.rpc.modules.eth.EthModule;
import co.rsk.rpc.modules.eth.getProof.ProofDTO;
import org.ethereum.rpc.Web3;
import org.ethereum.rpc.dto.BlockResultDTO;
import org.ethereum.rpc.dto.CompilationResultDTO;
import org.ethereum.rpc.dto.TransactionReceiptDTO;
import org.ethereum.rpc.dto.TransactionResultDTO;

import java.math.BigInteger;
import java.util.List;
import java.util.Map;

public interface Web3EthModule {
Expand All @@ -45,8 +47,6 @@ default String eth_estimateGas(Web3.CallArguments args) {
return getEthModule().estimateGas(args);
}



default Map<String, Object> eth_bridgeState() throws Exception {
return getEthModule().bridgeState();
}
Expand All @@ -55,6 +55,18 @@ default String eth_chainId() {
return getEthModule().chainId();
}

default String eth_getCode(String address, String blockId) {
return getEthModule().getCode(address, blockId);
}

default String eth_sendRawTransaction(String rawData) {
return getEthModule().sendRawTransaction(rawData);
}

default String eth_sendTransaction(Web3.CallArguments args) {
return getEthModule().sendTransaction(args);
}

EthModule getEthModule();

String eth_protocolVersion();
Expand Down Expand Up @@ -87,18 +99,6 @@ default String eth_chainId() {

String eth_getUncleCountByBlockNumber(String bnOrId)throws Exception;

default String eth_getCode(String address, String blockId) {
return getEthModule().getCode(address, blockId);
}

default String eth_sendRawTransaction(String rawData) {
return getEthModule().sendRawTransaction(rawData);
}

default String eth_sendTransaction(Web3.CallArguments args) {
return getEthModule().sendTransaction(args);
}

BlockResultDTO eth_getBlockByHash(String blockHash, Boolean fullTransactionObjects) throws Exception;

BlockResultDTO eth_getBlockByNumber(String bnOrId, Boolean fullTransactionObjects) throws Exception;
Expand Down Expand Up @@ -142,4 +142,16 @@ default String eth_sendTransaction(Web3.CallArguments args) {
boolean eth_submitWork(String nonce, String header, String mince);

boolean eth_submitHashrate(String hashrate, String id);

/**
* According to the EIP-1186 https://eips.ethereum.org/EIPS/eip-1186
* Returns account and storage proofs for a specific address and storage key
*
* @param address an address
* @param storageKeys storage keys to get storage proofs
* @param blockOrId a block number to query the blockchain state (it could also be "latest" or "pending")
*
* @return account and storage proofs
* */
ProofDTO eth_getProof(String address, List<String> storageKeys, String blockOrId);
}
88 changes: 81 additions & 7 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 @@ -29,27 +29,31 @@
import co.rsk.peg.BridgeSupport;
import co.rsk.peg.BridgeSupportFactory;
import co.rsk.rpc.ExecutionBlockRetriever;
import co.rsk.rpc.modules.eth.getProof.ProofDTO;
import co.rsk.rpc.modules.eth.getProof.StorageProofDTO;
import co.rsk.trie.TrieStoreImpl;
import com.google.common.annotations.VisibleForTesting;
import org.bouncycastle.util.encoders.DecoderException;
import org.ethereum.core.*;
import org.ethereum.crypto.HashUtil;
import org.ethereum.datasource.HashMapDB;
import org.ethereum.db.MutableRepository;
import org.ethereum.rpc.TypeConverter;
import org.ethereum.rpc.Web3;
import org.ethereum.rpc.converters.CallArgumentsToByteArray;
import org.ethereum.rpc.exception.RskJsonRpcRequestException;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.PrecompiledContracts;
import org.ethereum.vm.program.ProgramResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.Arrays.copyOfRange;
import static org.ethereum.rpc.TypeConverter.stringHexToBigInteger;
import static org.ethereum.rpc.TypeConverter.toUnformattedJsonHex;
import static org.ethereum.rpc.TypeConverter.*;
import static org.ethereum.rpc.exception.RskJsonRpcRequestException.invalidParamError;

// TODO add all RPC methods
Expand All @@ -72,6 +76,7 @@ public class EthModule
private final BridgeSupportFactory bridgeSupportFactory;
private final byte chainId;

public static final String NO_CONTRACT_CODE_HASH = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";

public EthModule(
BridgeConstants bridgeConstants,
Expand Down Expand Up @@ -147,7 +152,7 @@ public String estimateGas(Web3.CallArguments args) {
String s = null;
try {
ProgramResult res = callConstant(args, blockchain.getBestBlock());
return s = TypeConverter.toQuantityJsonHex(res.getGasUsed());
return s = toQuantityJsonHex(res.getGasUsed());
} finally {
LOGGER.debug("eth_estimateGas(): {}", s);
}
Expand Down Expand Up @@ -202,7 +207,8 @@ public String getCode(String address, String blockId) {
}
}

private AccountInformationProvider getAccountInformationProvider(String id) {
@VisibleForTesting
public AccountInformationProvider getAccountInformationProvider(String id) {
switch (id.toLowerCase()) {
case "pending":
return transactionPool.getPendingState();
Expand Down Expand Up @@ -275,4 +281,72 @@ private ProgramResult callConstant_workaround(Web3.CallArguments args, BlockResu
hexArgs.getFromAddress()
);
}

/**
* Fetch proof data from account provider and creates a Proof object
*
* @param address an RSK address
* @param storageKeys storage keys to prove (each storage key as UNFORMATED DATA, check https://eth.wiki/json-rpc/API)
* @param blockOrId a block id
*
* @return a proof object
* // todo In case an address or storage-value does not exist, the proof needs to provide enough data to verify this fact.
* // todo This means the client needs to follow the path from the root node and deliver until the last matching node.
* // todo If the last matching node is a branch, the proof value in the node must be an empty one.
* // todo In case of leaf-type, it must be pointing to a different relative-path in order to proof that the requested path does not exist.
* */
public ProofDTO getProof(String address, List<String> storageKeys, String blockOrId) {
RskAddress rskAddress = new RskAddress(address);
AccountInformationProvider accountInformationProvider = getAccountInformationProvider(blockOrId);

String balance = toQuantityJsonHex(accountInformationProvider.getBalance(rskAddress).asBigInteger());
String nonce = toQuantityJsonHex(accountInformationProvider.getNonce(rskAddress));
String storageHash = toUnformattedJsonHex(accountInformationProvider.getStorageHash(rskAddress));

// EIP-1186: For an externally owned account returns a SHA3(empty byte array)
// todo(fedejinich) this might be improved by using mutableRepositroy.getCodeHashStandard
String codeHash = accountInformationProvider.isContract(rskAddress) ?
toUnformattedJsonHex(HashUtil.keccak256(accountInformationProvider.getCode(rskAddress))) :
NO_CONTRACT_CODE_HASH;

List<String> accountProof = accountInformationProvider.getAccountProof(rskAddress)
.stream()
.map(proof -> toUnformattedJsonHex(proof))
.collect(Collectors.toList());

List<StorageProofDTO> storageProofs = storageKeys
.stream()
.map(storageKey -> storageProof(rskAddress, storageKey, accountInformationProvider))
.collect(Collectors.toList());

return new ProofDTO(balance, codeHash, nonce, storageHash, accountProof, storageProofs);
}

/**
* Retrieves a storage proof for a given (address,storageKey) and storage value, then adapts it to return a StorageProofDTO object.
*
* @param rskAddress an rsk address
* @param storageKey a storage key
* @param accountInformationProvider an account information provider to retrieve data from the expected block
*
* @return a storage proof object containing key, value and storage proofs
* */
private StorageProofDTO storageProof(RskAddress rskAddress, String storageKey, AccountInformationProvider accountInformationProvider) {
DataWord storageKeyDw;
try {
storageKeyDw = DataWord.valueFromHex(storageKey.substring(2)); // todo (fedejinich) strip correctly
} catch (DecoderException e) {
throw new IllegalArgumentException("invalid storage keys");
}

List<String> storageProof = Optional.ofNullable(accountInformationProvider.getStorageProof(rskAddress, storageKeyDw))
.orElse(Collections.emptyList())
.stream()
.map(proof -> toUnformattedJsonHex(proof))
.collect(Collectors.toList());
DataWord value = accountInformationProvider.getStorageValue(rskAddress, storageKeyDw);

// todo In case an address or storage-value does not exist, the proof needs to provide enough data to verify this fact.
return new StorageProofDTO(storageKey, value != null ? toUnformattedJsonHex(value.getData()) : null, storageProof);
}
}
Loading