New Repositroy management:

+ Repository encapsulattion of all the local peer data
+ NewContractDetails - will be renamed for ContractDetails
+ NewContractDetails - saves now the storage table and the program code
+ RepositoryTest testing for the abilities of Repository design
This commit is contained in:
romanman 2014-06-24 17:10:30 +01:00
parent d2a3259fdf
commit 9d650350f0
5 changed files with 793 additions and 1 deletions

View File

@ -63,6 +63,15 @@ public class AccountState {
return nonce;
}
public byte[] getStateRoot() {
return stateRoot;
}
public void setStateRoot(byte[] stateRoot) {
rlpEncoded = null;
this.stateRoot = stateRoot;
}
public void incrementNonce(){
rlpEncoded = null;
this.nonce = nonce.add(BigInteger.ONE);
@ -81,9 +90,11 @@ public class AccountState {
return balance;
}
public void addToBalance(BigInteger value){
public BigInteger addToBalance(BigInteger value){
if (value.signum() != 0) rlpEncoded = null;
this.balance = balance.add(value);
return this.balance;
}
public void subFromBalance(BigInteger value) {

View File

@ -0,0 +1,160 @@
package org.ethereum.db;
import org.ethereum.trie.Trie;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPElement;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.ethereum.vm.DataWord;
import javax.enterprise.inject.New;
import java.util.*;
/**
* www.ethereumJ.com
*
* @author: Roman Mandeleil
* Created on: 24/06/2014 00:12
*/
public class NewContractDetails {
private byte[] rlpEncoded;
List<DataWord> storageKeys = new ArrayList<DataWord>();
List<DataWord> storageValues = new ArrayList<DataWord>();
byte[] code;
Trie storageTrie = new Trie(null);
public NewContractDetails(){}
public NewContractDetails(byte[] rlpCode) {
decode(rlpCode);
}
public NewContractDetails(Map<DataWord, DataWord> storage, byte[] code) {}
public void put(DataWord key, DataWord value){
storageTrie.update(key.getData(), value.getData());
storageKeys.add(key);
storageValues.add(value);
this.rlpEncoded = null;
}
public DataWord get(DataWord key){
if (storageKeys.size() == 0)
return null;
int foundIndex = -1;
for (int i = 0; i < storageKeys.size(); ++i){
if (storageKeys.get(i).equals(key) ){
foundIndex = i;
break;
}
}
if (foundIndex != -1)
return storageValues.get(foundIndex);
else
return null;
}
public byte[] getCode() {
return code;
}
public void setCode(byte[] code) {
this.code = code;
this.rlpEncoded = null;
}
public byte[] getStorageHash(){
return storageTrie.getRootHash();
}
public void decode(byte[] rlpCode){
RLPList data = RLP.decode2(rlpCode);
RLPList rlpList = (RLPList)data.get(0);
RLPList keys = (RLPList)rlpList.get(0);
RLPList values = (RLPList)rlpList.get(1);
RLPElement code = (RLPElement)rlpList.get(2);
if (keys.size() > 0){
storageKeys = new ArrayList<>();
storageValues = new ArrayList<>();
}
for (int i = 0; i < keys.size(); ++i){
RLPItem rlpItem = (RLPItem)keys.get(i);
storageKeys.add(new DataWord(rlpItem.getRLPData()));
}
for (int i = 0; i < values.size(); ++i){
RLPItem rlpItem = (RLPItem)values.get(i);
storageValues.add(new DataWord(rlpItem.getRLPData()));
}
for (int i = 0; i < keys.size(); ++i){
DataWord key = storageKeys.get(i);
DataWord value = storageValues.get(i);
storageTrie.update(key.getData(), value.getData());
}
this.code = code.getRLPData();
this.rlpEncoded = rlpCode;
}
public byte[] getEncoded(){
if(rlpEncoded == null) {
int size = storageKeys == null ? 0 : storageKeys.size();
byte[][] keys = new byte[size][];
byte[][] values = new byte[size][];
for (int i = 0; i < size; ++i){
DataWord key = storageKeys.get(i);
keys[i] = RLP.encodeElement(key.getData());
}
for (int i = 0; i < size; ++i){
DataWord value = storageValues.get(i);
values[i] = RLP.encodeElement( value.getData() );
}
byte[] rlpKeysList = RLP.encodeList(keys);
byte[] rlpValuesList = RLP.encodeList(values);
byte[] rlpCode = RLP.encodeElement(code);
this.rlpEncoded = RLP.encodeList(rlpKeysList, rlpValuesList, rlpCode);
}
return rlpEncoded;
}
public Map<DataWord, DataWord> getStorage(){
Map<DataWord, DataWord> storage = Collections.unmodifiableMap(new HashMap<DataWord, DataWord>());
for (int i = 0;
storageKeys != null &&
i < storageKeys.size(); ++i){
storage.put(storageKeys.get(i), storageValues.get(i));
}
return storage;
}
}

View File

@ -0,0 +1,260 @@
package org.ethereum.db;
import org.ethereum.core.AccountState;
import org.ethereum.core.ContractDetails;
import org.ethereum.crypto.HashUtil;
import org.ethereum.trie.TrackTrie;
import org.ethereum.trie.Trie;
import org.ethereum.vm.DataWord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
/**
*
*
***********************************************************************************
MainRepository
|
--> AccountState ---> Trie ---> leveldb (state) /key=address
--> nonce
--> balance
--> stateRoot
--> codeHash
|
--> ContractDetails ---> leveldb(details) /key=address
--> code ---> sha3(code) // saved into AccountInfo.codeHash
--> storage ---> Trie // to calculate the AccountInfo.stateRoot
***********************************************************************************
*
* www.ethereumJ.com
*
* @author: Roman Mandeleil
* Created on: 23/06/2014 23:01
*/
public class Repository {
private Logger logger = LoggerFactory.getLogger("repository");
TrackTrie accountStateDB;
TrackDatabase contractDetailsDB;
// todo: Listeners listeners
// todo: cash impl
DatabaseImpl detailsDB = null;
DatabaseImpl stateDB = null;
public Repository() {
detailsDB = new DatabaseImpl("details");
contractDetailsDB = new TrackDatabase(detailsDB);
stateDB = new DatabaseImpl("state");
Trie worldState = new Trie(stateDB.getDb());
accountStateDB = new TrackTrie(worldState);
}
private Repository(TrackTrie accountStateDB, TrackDatabase contractDetailsDB){
this.accountStateDB = accountStateDB;
this.contractDetailsDB = contractDetailsDB;
}
public Repository getTrack(){
TrackTrie trackState = new TrackTrie(accountStateDB);
TrackDatabase trackDetails = new TrackDatabase(contractDetailsDB);
return new Repository (trackState, trackDetails);
}
public void startTracking(){
logger.info("start tracking");
accountStateDB.startTrack();
contractDetailsDB.startTrack();
}
public void commit(){
logger.info("commit changes");
accountStateDB.commitTrack();
contractDetailsDB.commitTrack();
}
public void rollback(){
logger.info("rollback changes");
accountStateDB.rollbackTrack();
contractDetailsDB.rollbackTrack();
}
public AccountState createAccount(byte[] addr){
// 1. Save AccountState
AccountState state = new AccountState();
accountStateDB.update(addr, state.getEncoded());
// 2. Save ContractDetails
NewContractDetails details = new NewContractDetails();
contractDetailsDB.put(addr, details.getEncoded());
return state;
}
public AccountState getAccountState(byte[] addr){
byte[] accountStateRLP = accountStateDB.get(addr);
if (accountStateRLP.length == 0){
if (logger.isInfoEnabled())
logger.info("No account: [ {} ]", Hex.toHexString(addr));
return null;
}
AccountState state = new AccountState(accountStateRLP);
return state;
}
public NewContractDetails getContractDetails(byte[] addr){
byte[] accountDetailsRLP = contractDetailsDB.get(addr);
if (accountDetailsRLP == null){
if (logger.isInfoEnabled())
logger.info("No account: [ {} ]", Hex.toHexString(addr));
return null;
}
NewContractDetails details = new NewContractDetails(accountDetailsRLP);
return details;
}
public BigInteger addBalance(byte[] address, BigInteger value){
AccountState state = getAccountState(address);
if (state == null) return BigInteger.ZERO;
BigInteger newBalance = state.addToBalance(value);
if (logger.isInfoEnabled())
logger.info("Changing balance: account: [ {} ] new balance: [ {} ]",
Hex.toHexString(address), newBalance.longValue());
accountStateDB.update(address, state.getEncoded());
return newBalance;
}
public BigInteger getBalance(byte[] address){
AccountState state = getAccountState(address);
if (state == null) return BigInteger.ZERO;
return state.getBalance();
}
public BigInteger getNonce(byte[] address){
AccountState state = getAccountState(address);
if (state == null) return BigInteger.ZERO;
return state.getNonce();
}
public BigInteger increaseNonce(byte[] address){
AccountState state = getAccountState(address);
if (state == null) return BigInteger.ZERO;
state.incrementNonce();
if (logger.isInfoEnabled())
logger.info("Incerement nonce: account: [ {} ] new nonce: [ {} ]",
Hex.toHexString(address), state.getNonce().longValue());
accountStateDB.update(address, state.getEncoded());
return state.getNonce();
}
public void addStorageRow(byte[] address, DataWord key, DataWord value){
if (address == null || key == null) return;
AccountState state = getAccountState(address);
NewContractDetails details = getContractDetails(address);
if (state == null || details == null) return;
details.put(key, value);
byte[] storageHash = details.getStorageHash();
state.setStateRoot(storageHash);
if (logger.isInfoEnabled())
logger.info("Storage key/value saved: account: [ {} ]\n key: [ {} ] value: [ {} ]\n new storageHash: [ {} ]",
Hex.toHexString(address),
Hex.toHexString(key.getNoLeadZeroesData()),
Hex.toHexString(value.getNoLeadZeroesData()),
Hex.toHexString(storageHash));
accountStateDB.update(address, state.getEncoded());
contractDetailsDB.put(address, details.getEncoded());
}
public DataWord getStorageValue(byte[] address, DataWord key){
if (key == null) return null;
AccountState state = getAccountState(address);
if (state == null) return null;
NewContractDetails details = getContractDetails(address);
DataWord value = details.get(key);
return value;
}
public byte[] getCode(byte[] address){
NewContractDetails details = getContractDetails(address);
if (details == null) return null;
return details.getCode();
}
public void saveCode(byte[] address, byte[] code){
if (code == null) return;
AccountState state = getAccountState(address);
if (state == null) return;
NewContractDetails details = getContractDetails(address);
details.setCode(code);
byte[] codeHash = HashUtil.sha3(code);
state.setCodeHash(codeHash);
if (logger.isInfoEnabled())
logger.info("Program code saved: account: [ {} ] codeHash: [ {} ] \n code: [ {} ]",
Hex.toHexString(address),
Hex.toHexString(codeHash),
Hex.toHexString(code));
accountStateDB.update(address, state.getEncoded());
contractDetailsDB.put(address, details.getEncoded());
}
public void close(){
if (this.stateDB != null)
stateDB.close();
if (this.detailsDB != null)
detailsDB.close();
}
}

View File

@ -49,6 +49,9 @@ public class Cache {
if (this.nodes.get(keyObj) != null) {
return this.nodes.get(keyObj).getValue();
}
if (db != null) return new Value(null);
// Get the key of the database instead and cache it
byte[] data = this.db.get(key);
Value value = new Value(data);
@ -61,10 +64,15 @@ public class Cache {
public void delete(byte[] key) {
ByteArrayWrapper keyObj = new ByteArrayWrapper(key);
this.nodes.remove(keyObj);
if (db == null) return;
this.db.delete(key);
}
public void commit() {
if (db == null) return;
// Don't try to commit if it isn't dirty
if (!this.isDirty) {
return;

View File

@ -0,0 +1,353 @@
package org.ethereum.db;
import org.ethereum.core.AccountState;
import org.ethereum.core.ContractDetails;
import org.ethereum.vm.DataWord;
import org.junit.*;
import org.junit.runners.MethodSorters;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import static org.junit.Assert.*;
/**
* www.ethereumJ.com
*
* @author: Roman Mandeleil
* Created on: 23/06/2014 23:52
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class RepositoryTest {
@Test // create account,
// get account
public void test1(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
Repository repository = new Repository();
AccountState createdState = repository.createAccount(Hex.decode(addr));
AccountState fetchedState =
repository.getAccountState(Hex.decode(addr));
assertEquals(createdState.getEncoded(), fetchedState.getEncoded());
repository.close();
}
@Test // increase nonce
public void test2(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
Repository repository = new Repository();
BigInteger nonce0 = repository.getNonce(Hex.decode(addr));
repository.createAccount(Hex.decode(addr));
BigInteger nonce1 = repository.getNonce(Hex.decode(addr));
repository.increaseNonce(Hex.decode(addr));
BigInteger nonce2 = repository.getNonce(Hex.decode(addr));
assertEquals(0, nonce0.intValue());
assertEquals(0, nonce1.intValue());
assertEquals(1, nonce2.intValue());
repository.close();
}
@Test // increase nonce
public void test3(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
Repository repository = new Repository();
BigInteger nonce0 = repository.getNonce(Hex.decode(addr));
repository.createAccount(Hex.decode(addr));
BigInteger nonce1 = repository.getNonce(Hex.decode(addr));
repository.increaseNonce(Hex.decode(addr));
repository.increaseNonce(Hex.decode(addr));
repository.increaseNonce(Hex.decode(addr));
BigInteger nonce2 = repository.getNonce(Hex.decode(addr));
assertEquals(0, nonce0.intValue());
assertEquals(0, nonce1.intValue());
assertEquals(3, nonce2.intValue());
repository.close();
}
@Test // change balance
public void test4(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
Repository repository = new Repository();
BigInteger balance0 = repository.getBalance(Hex.decode(addr));
repository.createAccount(Hex.decode(addr));
BigInteger balance1 = repository.getBalance(Hex.decode(addr));
repository.addBalance(Hex.decode(addr), BigInteger.valueOf(300));
BigInteger balance2 = repository.getBalance(Hex.decode(addr));
assertEquals(0, balance0.intValue());
assertEquals(0, balance1.intValue());
assertEquals(300, balance2.intValue());
repository.close();
}
@Test // change balance
public void test5(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
Repository repository = new Repository();
BigInteger balance0 = repository.getBalance(Hex.decode(addr));
repository.createAccount(Hex.decode(addr));
BigInteger balance1 = repository.getBalance(Hex.decode(addr));
repository.addBalance(Hex.decode(addr), BigInteger.valueOf(300));
BigInteger balance2 = repository.getBalance(Hex.decode(addr));
repository.addBalance(Hex.decode(addr), BigInteger.valueOf(-150));
BigInteger balance3 = repository.getBalance(Hex.decode(addr));
assertEquals(0, balance0.intValue());
assertEquals(0, balance1.intValue());
assertEquals(300, balance2.intValue());
assertEquals(150, balance3.intValue());
repository.close();
}
@Test // get/set code
public void test6() {
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
Repository repository = new Repository();
byte[] code = repository.getCode(Hex.decode(addr));
assertTrue(code == null);
repository.close();
}
@Test // get/set code
public void test7() {
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
String codeString = "7f60c860005461012c602054000000000000000000000000000000000000000000600060206000f200";
String codeHash = "8f0d7fc8cc6fdd688fa58ae9256310069f5659ed2a8a3af994d80350fbf1e798";
Repository repository = new Repository();
byte[] code0 = repository.getCode(Hex.decode(addr));
repository.createAccount(Hex.decode(addr));
repository.saveCode(Hex.decode(addr), Hex.decode(codeString));
byte[] code1 = repository.getCode(Hex.decode(addr));
AccountState accountState = repository.getAccountState(Hex.decode(addr));
assertTrue(code0 == null);
assertEquals(codeString, Hex.toHexString(code1));
assertEquals(codeHash, Hex.toHexString(accountState.getCodeHash()));
repository.close();
}
@Test // get/set code
public void test8() {
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
String codeHash = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";
Repository repository = new Repository();
byte[] code0 = repository.getCode(Hex.decode(addr));
repository.createAccount(Hex.decode(addr));
repository.saveCode(Hex.decode(addr), null);
byte[] code1 = repository.getCode(Hex.decode(addr));
AccountState accountState = repository.getAccountState(Hex.decode(addr));
assertTrue(code0 == null);
assertNull(code1);
assertEquals(codeHash, Hex.toHexString(accountState.getCodeHash()));
repository.close();
}
@Test // storage set/get
public void test9(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
byte[] keyBytes = Hex.decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
DataWord key = new DataWord(keyBytes);
Repository repository = new Repository();
DataWord value = repository.getStorageValue(Hex.decode(addr), key);
assertNull(value);
repository.close();
}
@Test // storage set/get
public void test10(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
Repository repository = new Repository();
repository.createAccount(Hex.decode(addr));
byte[] keyBytes = Hex.decode("cd2a3d9f938e13cd947ec05abc7fe734df8dd826");
DataWord key = new DataWord(keyBytes);
byte[] valueBytes = Hex.decode("0F4240");
DataWord value = new DataWord(valueBytes);
repository.addStorageRow(Hex.decode(addr), key, value);
DataWord fetchedValue = repository.getStorageValue(Hex.decode(addr), key);
assertEquals(value, fetchedValue);
repository.close();
}
@Test // storage set/get
public void test11(){
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
String expectedStorageHash = "365ed874ad42c2b4af335212465291e03dcd1f0c5b600f40f048ed238ad61fd3";
Repository repository = new Repository();
repository.createAccount(Hex.decode(addr));
byte[] keyBytes = Hex.decode("03E8");
DataWord key1 = new DataWord(keyBytes);
keyBytes = Hex.decode("03E9");
DataWord key2 = new DataWord(keyBytes);
keyBytes = Hex.decode("03F0");
DataWord key3 = new DataWord(keyBytes);
byte[] valueBytes = Hex.decode("0F4240");
DataWord value1 = new DataWord(valueBytes);
valueBytes = Hex.decode("0F4241");
DataWord value2 = new DataWord(valueBytes);
valueBytes = Hex.decode("0F4242");
DataWord value3 = new DataWord(valueBytes);
repository.addStorageRow(Hex.decode(addr), key1, value1);
repository.addStorageRow(Hex.decode(addr), key2, value2);
repository.addStorageRow(Hex.decode(addr), key3, value3);
DataWord fetchedValue1 = repository.getStorageValue(Hex.decode(addr), key1);
DataWord fetchedValue2 = repository.getStorageValue(Hex.decode(addr), key2);
DataWord fetchedValue3 = repository.getStorageValue(Hex.decode(addr), key3);
AccountState accountState = repository.getAccountState(Hex.decode(addr));
String stateRoot = Hex.toHexString(accountState.getStateRoot());
assertEquals(value1, fetchedValue1);
assertEquals(value2, fetchedValue2);
assertEquals(value3, fetchedValue3);
assertEquals(expectedStorageHash, stateRoot);
repository.close();
}
@Test // commit/rollback
public void test12() {
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
String expectedStorageHash = "365ed874ad42c2b4af335212465291e03dcd1f0c5b600f40f048ed238ad61fd3";
long expectedBalance = 333;
Repository origRepository = new Repository();
Repository repository = origRepository.getTrack();
repository.startTracking();
repository.createAccount(Hex.decode(addr));
repository.addBalance(Hex.decode(addr), BigInteger.valueOf(expectedBalance));
repository.commit();
BigInteger balance = repository.getBalance(Hex.decode(addr));
assertEquals(expectedBalance, balance.longValue());
origRepository.close();
}
@Test // commit/rollback
public void test13() {
String addr = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
String expectedStorageHash = "365ed874ad42c2b4af335212465291e03dcd1f0c5b600f40f048ed238ad61fd3";
long expectedBalance_1 = 55500;
long expectedBalance_2 = 0;
Repository origRepository = new Repository();
Repository repository = origRepository.getTrack();
repository.startTracking();
repository.createAccount(Hex.decode(addr));
repository.addBalance(Hex.decode(addr), BigInteger.valueOf(55500));
BigInteger balance = repository.getBalance(Hex.decode(addr));
assertEquals(expectedBalance_1, balance.longValue());
repository.rollback();
balance = repository.getBalance(Hex.decode(addr));
assertEquals(expectedBalance_2, balance.longValue());
origRepository.close();
}
@Test // commit/rollback
public void test14() {
String addr_1 = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
String addr_2 = "77045e71a7a2c50903d88e564cd72fab11e82051";
String codeString = "7f60c860005461012c602054000000000000000000000000000000000000000000600060206000f200";
long expectedBalance = 55500;
Repository origRepository = new Repository();
Repository repository = origRepository.getTrack();
repository.createAccount(Hex.decode(addr_1));
repository.addBalance(Hex.decode(addr_1), BigInteger.valueOf(expectedBalance));
repository.startTracking();
repository.createAccount(Hex.decode(addr_2));
repository.saveCode(Hex.decode(addr_2), Hex.decode(codeString));
repository.addStorageRow(Hex.decode(addr_2), new DataWord(101), new DataWord(1000001));
repository.addStorageRow(Hex.decode(addr_2), new DataWord(102), new DataWord(1000002));
repository.addStorageRow(Hex.decode(addr_2), new DataWord(103), new DataWord(1000003));
repository.rollback();
BigInteger balance = repository.getBalance(Hex.decode(addr_1));
assertEquals(expectedBalance, balance.longValue());
DataWord value = repository.getStorageValue(Hex.decode(addr_2), new DataWord(101));
assertNull(value);
origRepository.close();
}
}