Skip to content

Commit

Permalink
Fixed dumpState (it didn't work with Mainnet) and added partial expor…
Browse files Browse the repository at this point in the history
…t capabilities
  • Loading branch information
SergioDemianLerner authored and Vovchyk committed Nov 29, 2021
1 parent 4233ffe commit 4b8dd89
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 49 deletions.
66 changes: 50 additions & 16 deletions rskj-core/src/main/java/co/rsk/core/NetworkStateExporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,52 +57,86 @@ public NetworkStateExporter(RepositoryLocator repositoryLocator, Blockchain bloc
}

public boolean exportStatus(String outputFile) {
return exportStatus(outputFile, "",true,true);
}

public boolean exportStatus(String outputFile,String accountKey, boolean exportStorageKeys,boolean exportCode) {
RepositorySnapshot frozenRepository = repositoryLocator.snapshotAt(blockchain.getBestBlock().getHeader());

File dumpFile = new File(outputFile);

try(FileWriter fw = new FileWriter(dumpFile.getAbsoluteFile()); BufferedWriter bw = new BufferedWriter(fw)) {
JsonNodeFactory jsonFactory = new JsonNodeFactory(false);
ObjectNode mainNode = jsonFactory.objectNode();
for (RskAddress addr : frozenRepository.getAccountsKeys()) {
if(!addr.equals(RskAddress.nullAddress())) {
mainNode.set(addr.toString(), createAccountNode(mainNode, addr, frozenRepository));
if (accountKey.length()==0) {
for (RskAddress addr : frozenRepository.getAccountsKeys()) {
if (!addr.equals(RskAddress.nullAddress())) {
mainNode.set(addr.toString(), createAccountNode(mainNode, addr, frozenRepository, exportStorageKeys,exportCode));
}
}
} else {
RskAddress addr = new RskAddress(accountKey);
if (!addr.equals(RskAddress.nullAddress())) {
mainNode.set(addr.toString(), createAccountNode(mainNode, addr, frozenRepository, exportStorageKeys,exportCode));
}

}
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
bw.write(writer.writeValueAsString(mainNode));
return true;
} catch (IOException e) {
} catch (Exception e) {
logger.error(e.getMessage(), e);
panicProcessor.panic("dumpstate", e.getMessage());
return false;
}
}

private ObjectNode createContractNode(ObjectNode accountNode, AccountInformationProvider accountInformation, RskAddress addr, Iterator<DataWord> contractKeys) {
private ObjectNode createContractNode(ObjectNode accountNode, AccountInformationProvider accountInformation, RskAddress addr, Iterator<DataWord> contractKeys,boolean exportCode) {
ObjectNode contractNode = accountNode.objectNode();
contractNode.put("code", ByteUtil.toHexString(accountInformation.getCode(addr)));
ObjectNode dataNode = contractNode.objectNode();
while (contractKeys.hasNext()) {
DataWord key = contractKeys.next();
byte[] value = accountInformation.getStorageBytes(addr, key);
dataNode.put(ByteUtil.toHexString(key.getData()), ByteUtil.toHexString(value));
if (exportCode) {
byte[] code = accountInformation.getCode(addr);
String codeStr = "";
if (code == null) {
codeStr = "<empty>";
} else {
codeStr = ByteUtil.toHexString(code);
}
contractNode.put("code", codeStr);
}
contractNode.put("codeHash", accountInformation.getCodeHashStandard(addr).toHexString());

if (contractKeys!=null) {
ObjectNode dataNode = contractNode.objectNode();
while (contractKeys.hasNext()) {
DataWord key = contractKeys.next();
byte[] value = accountInformation.getStorageBytes(addr, key);
dataNode.put(ByteUtil.toHexString(key.getData()), ByteUtil.toHexString(value));
}
contractNode.set("data", dataNode);
}
contractNode.set("data", dataNode);
return contractNode;
}

private ObjectNode createAccountNode(ObjectNode mainNode, RskAddress addr, AccountInformationProvider accountInformation) {
private ObjectNode createAccountNode(ObjectNode mainNode, RskAddress addr, AccountInformationProvider accountInformation,
boolean exportStorageKeys,
boolean exportCode) {
ObjectNode accountNode = mainNode.objectNode();
Coin balance = accountInformation.getBalance(addr);
accountNode.put("balance", balance.asBigInteger().toString());
BigInteger nonce = accountInformation.getNonce(addr);
accountNode.put("nonce", nonce.toString());
Iterator<DataWord> contractKeys = accountInformation.getStorageKeys(addr);
if (contractKeys.hasNext() && !PrecompiledContracts.REMASC_ADDR.equals(addr)) {
accountNode.set("contract", createContractNode(accountNode, accountInformation, addr, contractKeys));

if (accountInformation.isContract(addr)) {
Iterator<DataWord> contractKeys = null;
if (exportStorageKeys) {
contractKeys = accountInformation.getStorageKeys(addr);
}
if (((contractKeys == null) || contractKeys.hasNext()) && !PrecompiledContracts.REMASC_ADDR.equals(addr)) {
accountNode.set("contract", createContractNode(accountNode, accountInformation, addr, contractKeys,exportCode));
}
}

return accountNode;
}
}
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 org.ethereum.vm.DataWord;

import javax.annotation.Nullable;
Expand Down Expand Up @@ -95,4 +96,14 @@ public interface AccountInformationProvider {
* @return value of the nonce
*/
BigInteger getNonce(RskAddress addr);

/**
* This method can retrieve the hash code without actually retrieving the code
* in some cases.
* This is the PRE RSKIP169 implementation, which has a bug we need to preserve
* before the implementation
* @param addr of the account
* @return hash of the contract code
*/
Keccak256 getCodeHashStandard(RskAddress addr);
}
5 changes: 5 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 @@ -83,6 +84,10 @@ public byte[] getCode(RskAddress addr) {
return postExecutionReturn(executedRepository -> executedRepository.getCode(addr));
}

@Override
public Keccak256 getCodeHashStandard(RskAddress addr) {
return postExecutionReturn(executedRepository -> executedRepository.getCodeHashStandard(addr));
}
@Override
public boolean isContract(RskAddress addr) {
return postExecutionReturn(executedRepository -> executedRepository.isContract(addr));
Expand Down
9 changes: 0 additions & 9 deletions rskj-core/src/main/java/co/rsk/db/RepositorySnapshot.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,6 @@ public interface RepositorySnapshot extends AccountInformationProvider {
*/
Keccak256 getCodeHashNonStandard(RskAddress addr);

/**
* This method can retrieve the hash code without actually retrieving the code
* in some cases.
* This is the POST RSKIP169 implementation which fixes the bug
* @param addr of the account
* @return hash of the contract code
*/
Keccak256 getCodeHashStandard(RskAddress addr);

/**
* @param addr - account to check
* @return - true if account exist,
Expand Down
16 changes: 13 additions & 3 deletions rskj-core/src/main/java/co/rsk/rpc/Web3RskImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,20 @@ public Web3RskImpl(
this.blockStore = blockStore;
}

public void ext_dumpState() {
public void ext_dumpState(String account,boolean exportStorageKeys,boolean exportCode) {
Block bestBlcock = blockStore.getBestBlock();
logger.info("Dumping state for block hash {}, block number {}", bestBlcock.getHash(), bestBlcock.getNumber());
networkStateExporter.exportStatus(System.getProperty("user.dir") + "/" + "rskdump.json");
logger.info("Dumping state for block hash {}, block number {}, account {}",
bestBlcock.getHash(), bestBlcock.getNumber(),account);
String name = "rskdump";
if (account.length()!=0) {
if (!account.toUpperCase().matches("^[0-9A-F]+$"))
return;
name = name +"-"+account;
}
String filename = System.getProperty("user.dir") + "/" + name+".json";
logger.info("Dumping in file: "+filename);
networkStateExporter.exportStatus(filename,account,exportStorageKeys,exportCode);
logger.info("Dump finished");
}

/**
Expand Down
40 changes: 27 additions & 13 deletions rskj-core/src/main/java/co/rsk/trie/Trie.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package co.rsk.trie;

import co.rsk.bitcoinj.core.VarInt;
import co.rsk.core.RskAddress;
import co.rsk.core.types.ints.Uint16;
import co.rsk.core.types.ints.Uint24;
import co.rsk.core.types.ints.Uint8;
Expand All @@ -29,6 +30,7 @@
import co.rsk.util.NodeStopper;
import org.ethereum.crypto.Keccak256Helper;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.db.TrieKeyMapper;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.RLP;
import org.slf4j.Logger;
Expand Down Expand Up @@ -1040,7 +1042,11 @@ public Iterator<IterationElement> getInOrderIterator() {
}

public Iterator<IterationElement> getPreOrderIterator() {
return new PreOrderIterator(this);
return new PreOrderIterator(this,false);
}

public Iterator<IterationElement> getPreOrderIterator(boolean stopAtAccountDepth) {
return new PreOrderIterator(this,stopAtAccountDepth);
}

private static byte[] cloneArray(byte[] array) {
Expand Down Expand Up @@ -1198,9 +1204,11 @@ private void pushLeftmostNode(TrieKeySlice nodeKey, Trie node) {
private class PreOrderIterator implements Iterator<IterationElement> {

private final Deque<IterationElement> visiting;
private boolean stopAtAccountDepth;

public PreOrderIterator(Trie root) {
public PreOrderIterator(Trie root,boolean stopAtAccountDepth) {
Objects.requireNonNull(root);
this.stopAtAccountDepth = stopAtAccountDepth;
TrieKeySlice traversedPath = root.getSharedPath();
this.visiting = new LinkedList<>();
this.visiting.push(new IterationElement(traversedPath, root));
Expand All @@ -1212,17 +1220,23 @@ public IterationElement next() {
IterationElement visitingElement = visiting.pop();
Trie node = visitingElement.getNode();
TrieKeySlice nodeKey = visitingElement.getNodeKey();
// need to visit the left subtree first, then the right since a stack is a LIFO, push the right subtree first,
// then the left
Trie rightNode = node.retrieveNode((byte) 0x01);
if (rightNode != null) {
TrieKeySlice rightNodeKey = nodeKey.rebuildSharedPath((byte) 0x01, rightNode.getSharedPath());
visiting.push(new IterationElement(rightNodeKey, rightNode));
}
Trie leftNode = node.retrieveNode((byte) 0x00);
if (leftNode != null) {
TrieKeySlice leftNodeKey = nodeKey.rebuildSharedPath((byte) 0x00, leftNode.getSharedPath());
visiting.push(new IterationElement(leftNodeKey, leftNode));

int nodeKeyLength = nodeKey.length();
// If we're stoping at accounts, do not add children
if ((!stopAtAccountDepth) || (nodeKeyLength < (1 + TrieKeyMapper.SECURE_KEY_SIZE + RskAddress.LENGTH_IN_BYTES) * Byte.SIZE))
{
// need to visit the left subtree first, then the right since a stack is a LIFO, push the right subtree first,
// then the left
Trie rightNode = node.retrieveNode((byte) 0x01);
if (rightNode != null) {
TrieKeySlice rightNodeKey = nodeKey.rebuildSharedPath((byte) 0x01, rightNode.getSharedPath());
visiting.push(new IterationElement(rightNodeKey, rightNode));
}
Trie leftNode = node.retrieveNode((byte) 0x00);
if (leftNode != null) {
TrieKeySlice leftNodeKey = nodeKey.rebuildSharedPath((byte) 0x00, leftNode.getSharedPath());
visiting.push(new IterationElement(leftNodeKey, leftNode));
}
}
// may not have pushed anything. If so, we are at the end
return visitingElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public synchronized Set<RskAddress> getAccountsKeys() {
//TODO(diegoll): this is needed when trie is a MutableTrieCache, check if makes sense to commit here
mutableTrie.commit();
Trie trie = mutableTrie.getTrie();
Iterator<Trie.IterationElement> preOrderIterator = trie.getPreOrderIterator();
Iterator<Trie.IterationElement> preOrderIterator = trie.getPreOrderIterator(true);
while (preOrderIterator.hasNext()) {
TrieKeySlice nodeKey = preOrderIterator.next().getNodeKey();
int nodeKeyLength = nodeKey.length();
Expand Down
52 changes: 46 additions & 6 deletions rskj-core/src/test/java/co/rsk/core/NetworkStateExporterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public static void cleanup(){

@Test
public void testEmptyRepo() throws Exception {
Map result = writeAndReadJson();
Map result = writeAndReadJson("",true,true);

Assert.assertEquals(0, result.keySet().size());
}
Expand Down Expand Up @@ -126,7 +126,7 @@ public void testNoContracts() throws Exception {
repository.addBalance(PrecompiledContracts.REMASC_ADDR, Coin.valueOf(10L));
repository.increaseNonce(PrecompiledContracts.REMASC_ADDR);

Map result = writeAndReadJson();
Map result = writeAndReadJson("",true,true);
Assert.assertEquals(3, result.keySet().size());

Map address1Value = (Map) result.get(address1String);
Expand All @@ -144,6 +144,7 @@ public void testNoContracts() throws Exception {
Assert.assertEquals("10",remascValue.get("balance"));
Assert.assertEquals("1",remascValue.get("nonce"));
}

@Test
public void testContracts() throws Exception {
String address1String = "1000000000000000000000000000000000000000";
Expand All @@ -152,14 +153,15 @@ public void testContracts() throws Exception {
repository.addBalance(addr1, Coin.valueOf(1L));
repository.increaseNonce(addr1);

repository.setupContract(addr1); // necessary for isContract() to return true.
repository.saveCode(addr1, new byte[]{1, 2, 3, 4});
repository.addStorageRow(addr1, DataWord.ZERO, DataWord.ONE);
repository.addStorageBytes(addr1, DataWord.ONE, new byte[]{5, 6, 7, 8});

AccountState accountState = repository.getAccountState(addr1);
repository.updateAccountState(addr1, accountState);

Map result = writeAndReadJson();
Map result = writeAndReadJson("",true,true);

Assert.assertEquals(1, result.keySet().size());

Expand All @@ -169,7 +171,9 @@ public void testContracts() throws Exception {
Assert.assertEquals("1",address1Value.get("balance"));
Assert.assertEquals("1",address1Value.get("nonce"));
Map contract = (Map) address1Value.get("contract");
Assert.assertEquals(2, contract.keySet().size());
Assert.assertEquals(3, contract.keySet().size());
String codeHash =(String) contract.get("codeHash");
Assert.assertEquals("a6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b", codeHash);
Assert.assertEquals("01020304",contract.get("code"));
Map data = (Map) contract.get("data");
Assert.assertEquals(2, data.keySet().size());
Expand All @@ -181,8 +185,44 @@ public void testContracts() throws Exception {
Assert.assertEquals("05060708", data.get(ByteUtil.toHexString(DataWord.ONE.getData())));
}

private Map writeAndReadJson() throws Exception {
Assert.assertTrue(nse.exportStatus(jsonFileName));
@Test
public void testSingleAccount() throws Exception {
String address1String = "1000000000000000000000000000000000000000";
RskAddress addr1 = new RskAddress(address1String);
repository.createAccount(addr1);
repository.addBalance(addr1, Coin.valueOf(1L));
repository.increaseNonce(addr1);

repository.setupContract(addr1); // necessary for isContract() to return true.
repository.saveCode(addr1, new byte[]{1, 2, 3, 4});
repository.addStorageRow(addr1, DataWord.ZERO, DataWord.ONE);
repository.addStorageBytes(addr1, DataWord.ONE, new byte[]{5, 6, 7, 8});

AccountState accountState = repository.getAccountState(addr1);
repository.updateAccountState(addr1, accountState);

String address2String = "2000000000000000000000000000000000000000";
RskAddress addr2 = new RskAddress(address2String);
repository.createAccount(addr2);

Map result = writeAndReadJson(addr1.toHexString(),false,false);

Assert.assertEquals(1, result.keySet().size());

// Getting address1String only works if the Trie is not secure.
Map address1Value = (Map) result.get(address1String);
Assert.assertEquals(3, address1Value.keySet().size());
Assert.assertEquals("1",address1Value.get("balance"));
Assert.assertEquals("1",address1Value.get("nonce"));
Map contract = (Map) address1Value.get("contract");
// "data" section and "code" must not be present (only "codeHash")
Assert.assertEquals(1, contract.keySet().size());
String codeHash =(String) contract.get("codeHash");
Assert.assertEquals("a6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b", codeHash);
}

private Map writeAndReadJson(String singleAccount,boolean exportStorageKeys,boolean exportCode) throws Exception {
Assert.assertTrue(nse.exportStatus(jsonFileName,singleAccount,exportStorageKeys,exportCode));

InputStream inputStream = new FileInputStream(jsonFileName);
String json = new String(ByteStreams.toByteArray(inputStream));
Expand Down
2 changes: 1 addition & 1 deletion rskj-core/src/test/java/co/rsk/rpc/Web3RskImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ null, new EthModuleWalletEnabled(wallet), null,
null,
null,
null);
web3.ext_dumpState();
web3.ext_dumpState("",true,true);
}

@Test
Expand Down

0 comments on commit 4b8dd89

Please sign in to comment.