Use Repository as abstraction layer for Blockchain

This commit is contained in:
nicksavers 2014-08-02 13:48:38 +02:00
parent d8063c5ff3
commit 2b42ebaac2
26 changed files with 421 additions and 408 deletions

View File

@ -110,7 +110,7 @@ public class Block {
}
public Block getParent() {
return WorldManager.getInstance().getBlockChain().getByNumber(this.getNumber() - 1);
return WorldManager.getInstance().getBlockchain().getByNumber(this.getNumber() - 1);
}
public byte[] getParentHash() {

View File

@ -1,17 +1,18 @@
package org.ethereum.core;
import org.ethereum.db.DatabaseImpl;
import org.ethereum.db.Repository;
import org.ethereum.listener.EthereumListener;
import org.ethereum.manager.WorldManager;
import org.ethereum.net.BlockQueue;
import org.ethereum.util.AdvancedDeviceUtils;
import org.ethereum.util.ByteUtil;
import org.iq80.leveldb.DBIterator;
import org.ethereum.vm.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -53,21 +54,39 @@ import static org.ethereum.core.Denomination.SZABO;
public class Blockchain {
private static Logger logger = LoggerFactory.getLogger("blockchain");
private static Logger stateLogger = LoggerFactory.getLogger("state");
// to avoid using minGasPrice=0 from Genesis for the wallet
private static long INITIAL_MIN_GAS_PRICE = 10 * SZABO.longValue();
private DatabaseImpl chainDb;
private Repository repository;
private Block lastBlock;
// keep the index of the chain for
// convenient usage, <block_number, block_hash>
private Map<Long, byte[]> index = new HashMap<>();
private Map<Long, byte[]> blockCache = new HashMap<>();
private Map<String, Transaction> pendingTransactions = Collections
.synchronizedMap(new HashMap<String, Transaction>());
private BlockQueue blockQueue = new BlockQueue();
public Blockchain() {
this.chainDb = new DatabaseImpl("blockchain");
public Blockchain(Repository repository) {
this.repository = repository;
}
public BlockQueue getBlockQueue() {
return blockQueue;
}
public Map<Long, byte[]> getBlockCache() {
return this.blockCache;
}
public long getGasPrice() {
// In case of the genesis block we don't want to rely on the min gas price
return lastBlock.isGenesis() ? lastBlock.getMinGasPrice() : INITIAL_MIN_GAS_PRICE;
}
public Block getLastBlock() {
return lastBlock;
@ -77,12 +96,19 @@ public class Blockchain {
this.lastBlock = block;
}
public byte[] getLatestBlockHash() {
if (blockCache.isEmpty())
return Genesis.getInstance().getHash();
else
return getLastBlock().getHash();
}
public int getSize() {
return index.size();
return blockCache.size();
}
public Block getByNumber(long blockNr) {
return new Block(chainDb.get(ByteUtil.longToBytes(blockNr)));
return repository.getBlock(blockNr);
}
public void add(Block block) {
@ -92,13 +118,13 @@ public class Blockchain {
// if it is the first block to add
// make sure the parent is genesis
if (index.isEmpty()
if (blockCache.isEmpty()
&& !Arrays.equals(Genesis.getInstance().getHash(),
block.getParentHash())) {
return;
}
// if there is some blocks already keep chain continuity
if (!index.isEmpty()) {
if (!blockCache.isEmpty()) {
String hashLast = Hex.toHexString(getLastBlock().getHash());
String blockParentHash = Hex.toHexString(block.getParentHash());
if (!hashLast.equals(blockParentHash)) return;
@ -120,16 +146,7 @@ public class Blockchain {
EthereumListener listener = WorldManager.getInstance().getListener();
if (listener != null)
listener.trace(String.format("Block chain size: [ %d ]", this.getSize()));
/*
if (lastBlock.getNumber() >= 30) {
System.out.println("** checkpoint **");
this.close();
WorldManager.getInstance().getRepository().close();
System.exit(1);
}
*/
}
public void processBlock(Block block) {
@ -138,7 +155,7 @@ public class Blockchain {
for (Transaction tx : block.getTransactionsList())
// TODO: refactor the wallet pending transactions to the world manager
WorldManager.getInstance().addWalletTransaction(tx);
WorldManager.getInstance().applyBlock(block);
this.applyBlock(block);
WorldManager.getInstance().getWallet().processBlock(block);
}
this.storeBlock(block);
@ -147,7 +164,7 @@ public class Blockchain {
}
}
private void storeBlock(Block block) {
public void storeBlock(Block block) {
/* Debug check to see if the state is still as expected */
if(logger.isWarnEnabled()) {
String blockStateRootHash = Hex.toHexString(block.getStateRoot());
@ -155,12 +172,13 @@ public class Blockchain {
if(!blockStateRootHash.equals(worldStateRootHash)){
logger.error("ERROR: STATE CONFLICT! block: {} worldstate {} mismatch", block.getNumber(), worldStateRootHash);
// Last conflict on block 1157 -> worldstate b1d9a978451ef04c1639011d9516473d51c608dbd25906c89be791707008d2de
repository.close();
System.exit(-1); // Don't add block
}
}
this.chainDb.put(ByteUtil.longToBytes(block.getNumber()), block.getEncoded());
this.index.put(block.getNumber(), block.getHash());
this.repository.saveBlock(block);
this.blockCache.put(block.getNumber(), block.getHash());
this.setLastBlock(block);
if (logger.isDebugEnabled())
@ -168,46 +186,233 @@ public class Blockchain {
logger.info("*** Block chain size: [ {} ]", this.getSize());
}
public long getGasPrice() {
// In case of the genesis block we don't want to rely on the min gas price
return lastBlock.isGenesis() ? lastBlock.getMinGasPrice() : INITIAL_MIN_GAS_PRICE;
}
public void applyBlock(Block block) {
int i = 0;
for (Transaction tx : block.getTransactionsList()) {
stateLogger.debug("apply block: [ {} ] tx: [ {} ] ", block.getNumber(), i);
applyTransaction(block, tx, block.getCoinbase());
repository.dumpState(block.getNumber(), i, tx.getHash());
++i;
public byte[] getLatestBlockHash() {
if (index.isEmpty())
return Genesis.getInstance().getHash();
else
return getLastBlock().getHash();
}
}
// miner reward
if (repository.getAccountState(block.getCoinbase()) == null)
repository.createAccount(block.getCoinbase());
repository.addBalance(block.getCoinbase(), Block.BLOCK_REWARD);
for (Block uncle : block.getUncleList()) {
repository.addBalance(uncle.getCoinbase(), Block.UNCLE_REWARD);
}
repository.dumpState(block.getNumber(), 0,
null);
}
public void load() {
DBIterator iterator = chainDb.iterator();
try {
if (!iterator.hasNext()) {
logger.info("DB is empty - adding Genesis");
this.lastBlock = Genesis.getInstance();
this.storeBlock(lastBlock);
logger.debug("Block #{} -> {}", Genesis.NUMBER, lastBlock.toFlatString());
} else {
logger.debug("Displaying blocks stored in DB sorted on blocknumber");
for (iterator.seekToFirst(); iterator.hasNext();) {
this.lastBlock = new Block(iterator.next().getValue());
this.index.put(lastBlock.getNumber(), lastBlock.getHash());
logger.debug("Block #{} -> {}", lastBlock.getNumber(), lastBlock.toFlatString());
}
}
} finally {
// Make sure you close the iterator to avoid resource leaks.
try {
iterator.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
public void applyTransaction(Block block, Transaction tx, byte[] coinbase) {
byte[] senderAddress = tx.getSender();
AccountState senderAccount = repository.getAccountState(senderAddress);
if (senderAccount == null) {
if (stateLogger.isWarnEnabled())
stateLogger.warn("No such address: {}",
Hex.toHexString(senderAddress));
return;
}
// 1. VALIDATE THE NONCE
BigInteger nonce = senderAccount.getNonce();
BigInteger txNonce = new BigInteger(1, tx.getNonce());
if (nonce.compareTo(txNonce) != 0) {
if (stateLogger.isWarnEnabled())
stateLogger.warn("Invalid nonce account.nonce={} tx.nonce={}",
nonce, txNonce);
return;
}
// 3. FIND OUT THE TRANSACTION TYPE
byte[] receiverAddress, code = null;
boolean isContractCreation = tx.isContractCreation();
if (isContractCreation) {
receiverAddress = tx.getContractAddress();
repository.createAccount(receiverAddress);
if(stateLogger.isDebugEnabled())
stateLogger.debug("new contract created address={}",
Hex.toHexString(receiverAddress));
code = tx.getData(); // init code
if (stateLogger.isDebugEnabled())
stateLogger.debug("running the init for contract: address={}",
Hex.toHexString(receiverAddress));
} else {
receiverAddress = tx.getReceiveAddress();
AccountState receiverState = repository.getAccountState(receiverAddress);
if (receiverState == null) {
repository.createAccount(receiverAddress);
if (stateLogger.isDebugEnabled())
stateLogger.debug("new receiver account created address={}",
Hex.toHexString(receiverAddress));
} else {
code = repository.getCode(receiverAddress);
if (code != null) {
if (stateLogger.isDebugEnabled())
stateLogger.debug("calling for existing contract: address={}",
Hex.toHexString(receiverAddress));
}
}
}
// 2.1 UPDATE THE NONCE
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
repository.increaseNonce(senderAddress);
// 2.2 PERFORM THE GAS VALUE TX
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
BigInteger gasDebit = tx.getTotalGasValueDebit();
// Debit the actual total gas value from the sender
// the purchased gas will be available for
// the contract in the execution state,
// it can be retrieved using GAS op
if (gasDebit.signum() == 1) {
BigInteger balance = senderAccount.getBalance();
if (balance.compareTo(gasDebit) == -1) {
logger.debug("No gas to start the execution: sender={}",
Hex.toHexString(senderAddress));
return;
}
repository.addBalance(senderAddress, gasDebit.negate());
// The coinbase get the gas cost
if (coinbase != null)
repository.addBalance(coinbase, gasDebit);
if (stateLogger.isDebugEnabled())
stateLogger.debug(
"Before contract execution debit the sender address with gas total cost, "
+ "\n sender={} \n gas_debit= {}",
Hex.toHexString(senderAddress), gasDebit);
}
// 3. START TRACKING FOR REVERT CHANGES OPTION !!!
Repository trackRepository = repository.getTrack();
trackRepository.startTracking();
try {
// 4. THE SIMPLE VALUE/BALANCE CHANGE
if (tx.getValue() != null) {
BigInteger senderBalance = senderAccount.getBalance();
if (senderBalance.compareTo(new BigInteger(1, tx.getValue())) >= 0) {
repository.addBalance(receiverAddress,
new BigInteger(1, tx.getValue()));
repository.addBalance(senderAddress,
new BigInteger(1, tx.getValue()).negate());
if (stateLogger.isDebugEnabled())
stateLogger.debug("Update value balance \n "
+ "sender={}, receiver={}, value={}",
Hex.toHexString(senderAddress),
Hex.toHexString(receiverAddress),
new BigInteger(tx.getValue()));
}
}
// 5. CREATE OR EXECUTE PROGRAM
if (isContractCreation || code != null) {
Block currBlock = (block == null) ? this.getLastBlock() : block;
ProgramInvoke programInvoke = ProgramInvokeFactory
.createProgramInvoke(tx, currBlock, trackRepository);
VM vm = new VM();
Program program = new Program(code, programInvoke);
if (CONFIG.playVM())
vm.play(program);
ProgramResult result = program.getResult();
applyProgramResult(result, gasDebit, trackRepository,
senderAddress, receiverAddress, coinbase, isContractCreation);
} else {
// refund everything except fee (500 + 5*txdata)
BigInteger gasPrice = new BigInteger(1, tx.getGasPrice());
long dataFee = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA;
long minTxFee = GasCost.TRANSACTION + dataFee;
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(
minTxFee).multiply(gasPrice));
if (refund.signum() > 0) {
// gas refund
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
}
} catch (RuntimeException e) {
trackRepository.rollback();
return;
}
trackRepository.commit();
pendingTransactions.put(Hex.toHexString(tx.getHash()), tx);
}
public void close() {
if (this.chainDb != null)
chainDb.close();
}
/**
* After any contract code finish the run the certain result should take
* place, according the given circumstances
*
* @param result
* @param gasDebit
* @param senderAddress
* @param contractAddress
*/
private void applyProgramResult(ProgramResult result, BigInteger gasDebit,
Repository repository, byte[] senderAddress,
byte[] contractAddress, byte[] coinbase, boolean initResults) {
if (result.getException() != null
&& result.getException() instanceof Program.OutOfGasException) {
stateLogger.debug("contract run halted by OutOfGas: contract={}",
Hex.toHexString(contractAddress));
throw result.getException();
}
BigInteger gasPrice = BigInteger.valueOf(this.getGasPrice());
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(
result.getGasUsed()).multiply(gasPrice));
if (refund.signum() > 0) {
if (stateLogger.isDebugEnabled())
stateLogger
.debug("After contract execution the sender address refunded with gas leftover, "
+ "\n sender={} \n contract={} \n gas_refund= {}",
Hex.toHexString(senderAddress),
Hex.toHexString(contractAddress), refund);
// gas refund
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
if (initResults) {
// Save the code created by init
byte[] bodyCode = null;
if (result.getHReturn() != null) {
bodyCode = result.getHReturn().array();
}
if (bodyCode != null) {
if (stateLogger.isDebugEnabled())
stateLogger
.debug("saving code of the contract to the db:\n contract={} code={}",
Hex.toHexString(contractAddress),
Hex.toHexString(bodyCode));
repository.saveCode(contractAddress, bodyCode);
}
}
// delete the marked to die accounts
if (result.getDeleteAccounts() == null) return;
for (DataWord address : result.getDeleteAccounts()){
repository.delete(address.getNoLeadZeroesData());
}
}
}

View File

@ -23,15 +23,15 @@ public class ContractDetails {
private byte[] rlpEncoded;
private List<DataWord> storageKeys = new ArrayList<DataWord>();
private List<DataWord> storageValues = new ArrayList<DataWord>();
private List<DataWord> storageKeys = new ArrayList<>();
private List<DataWord> storageValues = new ArrayList<>();
private byte[] code;
private Trie storageTrie = new Trie(null);
public ContractDetails() {
}
public ContractDetails() {
}
public ContractDetails(byte[] rlpCode) {
decode(rlpCode);
@ -165,7 +165,7 @@ public class ContractDetails {
}
public Map<DataWord, DataWord> getStorage() {
Map<DataWord, DataWord> storage = new HashMap<DataWord, DataWord>();
Map<DataWord, DataWord> storage = new HashMap<>();
for (int i = 0; storageKeys != null && i < storageKeys.size(); ++i) {
storage.put(storageKeys.get(i), storageValues.get(i));
}

View File

@ -2,6 +2,9 @@ package org.ethereum.db;
import org.codehaus.plexus.util.FileUtils;
import org.ethereum.core.AccountState;
import org.ethereum.core.Block;
import org.ethereum.core.Blockchain;
import org.ethereum.core.Genesis;
import org.ethereum.crypto.HashUtil;
import org.ethereum.json.JSONHelper;
import org.ethereum.manager.WorldManager;
@ -9,6 +12,7 @@ import org.ethereum.trie.TrackTrie;
import org.ethereum.trie.Trie;
import org.ethereum.util.ByteUtil;
import org.ethereum.vm.DataWord;
import org.iq80.leveldb.DBIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.BigIntegers;
@ -56,18 +60,25 @@ public class Repository {
// TODO: Listeners listeners
// TODO: cash impl
private DatabaseImpl detailsDB = null;
private DatabaseImpl stateDB = null;
private DatabaseImpl chainDB = null;
private DatabaseImpl detailsDB = null;
private DatabaseImpl stateDB = null;
/**
* Create a new Repository DAO
* assuming empty db and thus no stateRoot
*
* @See loadBlockchain() to update the stateRoot
*/
public Repository() {
detailsDB = new DatabaseImpl("details");
contractDetailsDB = new TrackDatabase(detailsDB);
stateDB = new DatabaseImpl("state");
worldState = new Trie(stateDB.getDb());
accountStateDB = new TrackTrie(worldState);
chainDB = new DatabaseImpl("blockchain");
detailsDB = new DatabaseImpl("details");
contractDetailsDB = new TrackDatabase(detailsDB);
stateDB = new DatabaseImpl("state");
worldState = new Trie(stateDB.getDb());
accountStateDB = new TrackTrie(worldState);
}
private Repository(TrackTrie accountStateDB, TrackDatabase contractDetailsDB) {
this.accountStateDB = accountStateDB;
this.contractDetailsDB = contractDetailsDB;
@ -80,27 +91,72 @@ public class Repository {
}
public void startTracking() {
logger.info("start tracking");
logger.debug("start tracking");
accountStateDB.startTrack();
contractDetailsDB.startTrack();
}
public void commit() {
logger.info("commit changes");
logger.debug("commit changes");
accountStateDB.commitTrack();
contractDetailsDB.commitTrack();
}
public void rollback() {
logger.info("rollback changes");
logger.debug("rollback changes");
accountStateDB.rollbackTrack();
contractDetailsDB.rollbackTrack();
}
public Block getBlock(long blockNr) {
return new Block(chainDB.get(ByteUtil.longToBytes(blockNr)));
}
public void saveBlock(Block block) {
this.chainDB.put(ByteUtil.longToBytes(block.getNumber()), block.getEncoded());
}
public Blockchain loadBlockchain() {
Blockchain blockchain = new Blockchain(this);
DBIterator iterator = chainDB.iterator();
try {
if (!iterator.hasNext()) {
logger.info("DB is empty - adding Genesis");
blockchain.storeBlock(Genesis.getInstance());
logger.debug("Block #{} -> {}", Genesis.NUMBER, blockchain.getLastBlock().toFlatString());
} else {
logger.debug("Displaying blocks stored in DB sorted on blocknumber");
for (iterator.seekToFirst(); iterator.hasNext();) {
Block block = new Block(iterator.next().getValue());
blockchain.getBlockCache().put(block.getNumber(), block.getHash());
blockchain.setLastBlock(block);
logger.debug("Block #{} -> {}", block.getNumber(), block.toFlatString());
}
logger.info(
"*** Loaded up to block [ {} ] with stateRoot [ {} ]",
blockchain.getLastBlock().getNumber(), Hex
.toHexString(blockchain.getLastBlock()
.getStateRoot()));
}
} finally {
// Make sure you close the iterator to avoid resource leaks.
try {
iterator.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
// Update world state to latest loaded block from db
this.worldState.setRoot(blockchain.getLastBlock().getStateRoot());
return blockchain;
}
public AccountState createAccount(byte[] addr) {
this.validateAddress(addr);
// 1. Save AccountState
AccountState state = new AccountState();
accountStateDB.update(addr, state.getEncoded());
@ -109,8 +165,8 @@ public class Repository {
ContractDetails details = new ContractDetails();
contractDetailsDB.put(addr, details.getEncoded());
if (logger.isInfoEnabled())
logger.info("New account created: [ {} ]", Hex.toHexString(addr));
if (logger.isDebugEnabled())
logger.debug("New account created: [ {} ]", Hex.toHexString(addr));
return state;
}
@ -122,12 +178,10 @@ public class Repository {
public AccountState getAccountState(byte[] addr) {
this.validateAddress(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);
@ -138,18 +192,17 @@ public class Repository {
this.validateAddress(addr);
if (logger.isInfoEnabled())
logger.info("Account: [ {} ]", Hex.toHexString(addr));
if (logger.isDebugEnabled())
logger.debug("Get contract details for: [ {} ]", Hex.toHexString(addr));
byte[] accountDetailsRLP = contractDetailsDB.get(addr);
if (accountDetailsRLP == null) {
return null;
}
if (logger.isInfoEnabled())
logger.info("Contract details RLP: [ {} ]", Hex.toHexString(accountDetailsRLP));
if (logger.isDebugEnabled())
logger.debug("Contract details RLP: [ {} ]", Hex.toHexString(accountDetailsRLP));
ContractDetails details = new ContractDetails(accountDetailsRLP);
return details;
@ -167,8 +220,8 @@ public class Repository {
BigInteger newBalance = state.addToBalance(value);
if (logger.isInfoEnabled())
logger.info("Changing balance: account: [ {} ] new balance: [ {} ] delta: [ {} ]",
if (logger.isDebugEnabled())
logger.debug("Changing balance: account: [ {} ] new balance: [ {} ] delta: [ {} ]",
Hex.toHexString(addr), newBalance.toString(), value);
accountStateDB.update(addr, state.getEncoded());
@ -197,8 +250,8 @@ public class Repository {
if (state == null) return BigInteger.ZERO;
state.incrementNonce();
if (logger.isInfoEnabled())
logger.info("Incerement nonce: account: [ {} ] new nonce: [ {} ]",
if (logger.isDebugEnabled())
logger.debug("Incerement nonce: account: [ {} ] new nonce: [ {} ]",
Hex.toHexString(addr), state.getNonce().longValue());
accountStateDB.update(addr, state.getEncoded());
@ -219,8 +272,8 @@ public class Repository {
byte[] storageHash = details.getStorageHash();
state.setStateRoot(storageHash);
if (logger.isInfoEnabled())
logger.info("Storage key/value saved: account: [ {} ]\n key: [ {} ] value: [ {} ]\n new storageHash: [ {} ]",
if (logger.isDebugEnabled())
logger.debug("Storage key/value saved: account: [ {} ]\n key: [ {} ] value: [ {} ]\n new storageHash: [ {} ]",
Hex.toHexString(addr),
Hex.toHexString(key.getNoLeadZeroesData()),
Hex.toHexString(value.getNoLeadZeroesData()),
@ -272,8 +325,8 @@ public class Repository {
byte[] codeHash = HashUtil.sha3(code);
state.setCodeHash(codeHash);
if (logger.isInfoEnabled())
logger.info("Program code saved:\n account: [ {} ]\n codeHash: [ {} ] \n code: [ {} ]",
if (logger.isDebugEnabled())
logger.debug("Program code saved:\n account: [ {} ]\n codeHash: [ {} ] \n code: [ {} ]",
Hex.toHexString(addr),
Hex.toHexString(codeHash),
Hex.toHexString(code));
@ -302,7 +355,7 @@ public class Repository {
return stateDB.dumpKeys();
}
public void dumpState(long blockNumber, int txNumber, String txHash) {
public void dumpState(long blockNumber, int txNumber, byte[] txHash) {
if (!CONFIG.dumpFull()) return;
@ -318,7 +371,7 @@ public class Repository {
String fileName = "";
if (txHash != null)
fileName = String.format("%d_%d_%s.dmp",
blockNumber, txNumber, txHash.substring(0, 8));
blockNumber, txNumber, Hex.toHexString(txHash).substring(0, 8));
else
fileName = String.format("%d_c.dmp", blockNumber);
@ -381,6 +434,8 @@ public class Repository {
worldState.sync();
}
if (this.chainDB != null)
chainDB.close();
if (this.stateDB != null)
stateDB.close();
if (this.detailsDB != null)

View File

@ -85,13 +85,13 @@ public class EthereumImpl implements Ethereum {
@Override
public Block getBlockByIndex(long index){
Block block = WorldManager.getInstance().getBlockChain().getByNumber(index);
Block block = WorldManager.getInstance().getBlockchain().getByNumber(index);
return block;
}
@Override
public long getBlockChainSize(){
return WorldManager.getInstance().getBlockChain().getSize();
return WorldManager.getInstance().getBlockchain().getSize();
}
@Override

View File

@ -74,9 +74,9 @@ public class BlockChainTable extends JFrame {
@Override
public void actionPerformed(ActionEvent e) {
if (WorldManager.getInstance().getBlockChain().getSize() - 1 < lastFindIndex) return;
if (WorldManager.getInstance().getBlockchain().getSize() - 1 < lastFindIndex) return;
Block block = WorldManager.getInstance().getBlockChain().getByNumber(lastFindIndex);
Block block = WorldManager.getInstance().getBlockchain().getByNumber(lastFindIndex);
StringSelection stsel = new StringSelection(block.toString());
Clipboard system = Toolkit.getDefaultToolkit().getSystemClipboard();
system.setContents(stsel,stsel);
@ -96,10 +96,10 @@ public class BlockChainTable extends JFrame {
return;
}
for (int i = lastFindIndex + 1; i < WorldManager.getInstance().getBlockChain().getSize(); ++i) {
for (int i = lastFindIndex + 1; i < WorldManager.getInstance().getBlockchain().getSize(); ++i) {
if (WorldManager.getInstance().getBlockChain().getSize() - 1 < i) return;
Block block = WorldManager.getInstance().getBlockChain().getByNumber(i);
if (WorldManager.getInstance().getBlockchain().getSize() - 1 < i) return;
Block block = WorldManager.getInstance().getBlockchain().getByNumber(i);
boolean found = block.toString().toLowerCase().contains(toFind.toLowerCase());
if (found) {
// TODO: now we find the first occur

View File

@ -16,7 +16,7 @@ public class BlockTableModel extends AbstractTableModel {
public int getRowCount() {
fireTableDataChanged();
int rowCount = WorldManager.getInstance().getBlockChain().getSize();
int rowCount = WorldManager.getInstance().getBlockchain().getSize();
return rowCount;
}
@ -31,7 +31,7 @@ public class BlockTableModel extends AbstractTableModel {
// byte[] hash = MainData.instance.getAllBlocks().get(rowIndex).getHash();
// return Hex.toHexString(hash);
Block block = WorldManager.getInstance().getBlockChain().getByNumber(rowIndex);
Block block = WorldManager.getInstance().getBlockchain().getByNumber(rowIndex);
if (block == null) return "";
return block.toString();

View File

@ -315,7 +315,7 @@ class ContractCallDialog extends JDialog implements MessageAwareDialog {
Transaction tx = createTransaction();
if (tx == null) return;
ProgramPlayDialog.createAndShowGUI(programCode, tx, WorldManager.getInstance().getBlockChain().getLastBlock());
ProgramPlayDialog.createAndShowGUI(programCode, tx, WorldManager.getInstance().getBlockchain().getLastBlock());
}
protected JRootPane createRootPane() {

View File

@ -107,7 +107,7 @@ class ContractSubmitDialog extends JDialog implements MessageAwareDialog {
contractAddrInput.setText(Hex.toHexString(tx.getContractAddress()));
ProgramPlayDialog.createAndShowGUI(tx.getData(), tx,
WorldManager.getInstance().getBlockChain().getLastBlock());
WorldManager.getInstance().getBlockchain().getLastBlock());
}}
);
@ -301,7 +301,7 @@ class ContractSubmitDialog extends JDialog implements MessageAwareDialog {
Account account = ((AccountWrapper)creatorAddressCombo.getSelectedItem()).getAccount();
BigInteger currentBalance = account.getBalance();
BigInteger gasPrice = BigInteger.valueOf(WorldManager.getInstance().getBlockChain().getGasPrice());
BigInteger gasPrice = BigInteger.valueOf(WorldManager.getInstance().getBlockchain().getGasPrice());
BigInteger gasInput = new BigInteger( this.gasInput.getText());
boolean canAfford = currentBalance.compareTo(gasPrice.multiply(gasInput)) >= 0;

View File

@ -118,7 +118,7 @@ class PayOutDialog extends JDialog implements MessageAwareDialog {
byte[] senderPrivKey = account.getEcKey().getPrivKeyBytes();
byte[] nonce = accountState.getNonce() == BigInteger.ZERO ? null : accountState.getNonce().toByteArray();
byte[] gasPrice = BigInteger.valueOf( WorldManager.getInstance().getBlockChain().getGasPrice()).toByteArray();
byte[] gasPrice = BigInteger.valueOf( WorldManager.getInstance().getBlockchain().getGasPrice()).toByteArray();
Transaction tx = new Transaction(nonce, gasPrice, BigIntegers
.asUnsignedByteArray(fee), address, BigIntegers
@ -193,7 +193,7 @@ class PayOutDialog extends JDialog implements MessageAwareDialog {
// check if the tx is affordable
BigInteger ammountValue = new BigInteger(amountText);
BigInteger feeValue = new BigInteger(feeText);
BigInteger gasPrice = BigInteger.valueOf(WorldManager.getInstance().getBlockChain().getGasPrice());
BigInteger gasPrice = BigInteger.valueOf(WorldManager.getInstance().getBlockchain().getGasPrice());
BigInteger currentBalance = accountState.getBalance();
boolean canAfford = gasPrice.multiply(feeValue).add(ammountValue).compareTo(currentBalance) != 1;

View File

@ -6,6 +6,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
@ -225,7 +226,7 @@ public class ToolBar extends JFrame {
cp.add(chainToggle);
cp.add(walletToggle);
WorldManager.getInstance();
WorldManager.getInstance().loadBlockchain();
}
public static void main(String args[]) {

View File

@ -8,7 +8,6 @@ import java.net.UnknownHostException;
import java.util.*;
import org.ethereum.core.AccountState;
import org.ethereum.core.Block;
import org.ethereum.core.Blockchain;
import org.ethereum.core.Transaction;
import org.ethereum.core.Wallet;
@ -16,12 +15,10 @@ import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.db.Repository;
import org.ethereum.listener.EthereumListener;
import org.ethereum.net.BlockQueue;
import org.ethereum.net.client.ClientPeer;
import org.ethereum.net.client.PeerData;
import org.ethereum.net.peerdiscovery.PeerDiscovery;
import org.ethereum.net.submit.WalletTransaction;
import org.ethereum.vm.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
@ -37,7 +34,6 @@ import org.spongycastle.util.encoders.Hex;
public class WorldManager {
private Logger logger = LoggerFactory.getLogger("main");
private Logger stateLogger = LoggerFactory.getLogger("state");
private Blockchain blockchain;
private Repository repository;
@ -47,9 +43,6 @@ public class WorldManager {
private List<PeerData> peers = Collections.synchronizedList(new ArrayList<PeerData>());
private ClientPeer activePeer;
private Map<String, Transaction> pendingTransactions = Collections
.synchronizedMap(new HashMap<String, Transaction>());
// This map of transaction designed
// to approve the tx by external trusted peer
private Map<String, WalletTransaction> walletTransactions =
@ -59,12 +52,10 @@ public class WorldManager {
private static WorldManager instance;
private BlockQueue blockQueue = new BlockQueue();
public WorldManager() {
this.blockchain = new Blockchain();
private WorldManager() {
this.repository = new Repository();
this.blockchain = new Blockchain(repository);
// Initialize PeerData
try {
InetAddress ip = InetAddress.getByName(CONFIG.peerDiscoveryIP());
@ -94,246 +85,9 @@ public class WorldManager {
public static WorldManager getInstance() {
if(instance == null) {
instance = new WorldManager();
instance.blockchain.load();
}
return instance;
}
public void applyTransaction(Block block, Transaction tx, byte[] coinbase) {
byte[] senderAddress = tx.getSender();
AccountState senderAccount = repository.getAccountState(senderAddress);
if (senderAccount == null) {
if (stateLogger.isWarnEnabled())
stateLogger.warn("No such address: {}",
Hex.toHexString(senderAddress));
return;
}
// 1. VALIDATE THE NONCE
BigInteger nonce = senderAccount.getNonce();
BigInteger txNonce = new BigInteger(1, tx.getNonce());
if (nonce.compareTo(txNonce) != 0) {
if (stateLogger.isWarnEnabled())
stateLogger.warn("Invalid nonce account.nonce={} tx.nonce={}",
nonce, txNonce);
return;
}
// 3. FIND OUT THE TRANSACTION TYPE
byte[] receiverAddress, code = null;
boolean isContractCreation = tx.isContractCreation();
if (isContractCreation) {
receiverAddress = tx.getContractAddress();
repository.createAccount(receiverAddress);
stateLogger.info("New contract created address={}",
Hex.toHexString(receiverAddress));
code = tx.getData(); // init code
if (logger.isInfoEnabled())
logger.info("running the init for contract: address={}",
Hex.toHexString(receiverAddress));
} else {
receiverAddress = tx.getReceiveAddress();
AccountState receiverState = repository.getAccountState(receiverAddress);
if (receiverState == null) {
repository.createAccount(receiverAddress);
if (stateLogger.isInfoEnabled())
stateLogger.info("New receiver account created address={}",
Hex.toHexString(receiverAddress));
} else {
code = repository.getCode(receiverAddress);
if (code != null) {
if (logger.isInfoEnabled())
logger.info("calling for existing contract: address={}",
Hex.toHexString(receiverAddress));
}
}
}
// 2.1 UPDATE THE NONCE
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
repository.increaseNonce(senderAddress);
// 2.2 PERFORM THE GAS VALUE TX
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
BigInteger gasDebit = tx.getTotalGasValueDebit();
// Debit the actual total gas value from the sender
// the purchased gas will be available for
// the contract in the execution state,
// it can be retrieved using GAS op
if (gasDebit.signum() == 1) {
BigInteger balance = senderAccount.getBalance();
if (balance.compareTo(gasDebit) == -1) {
logger.info("No gas to start the execution: sender={}",
Hex.toHexString(senderAddress));
return;
}
repository.addBalance(senderAddress, gasDebit.negate());
// The coinbase get the gas cost
if (coinbase != null)
repository.addBalance(coinbase, gasDebit);
if (stateLogger.isInfoEnabled())
stateLogger.info(
"Before contract execution debit the sender address with gas total cost, "
+ "\n sender={} \n gas_debit= {}",
Hex.toHexString(senderAddress), gasDebit);
}
// 3. START TRACKING FOR REVERT CHANGES OPTION !!!
Repository trackRepository = repository.getTrack();
trackRepository.startTracking();
try {
// 4. THE SIMPLE VALUE/BALANCE CHANGE
if (tx.getValue() != null) {
BigInteger senderBalance = senderAccount.getBalance();
if (senderBalance.compareTo(new BigInteger(1, tx.getValue())) >= 0) {
repository.addBalance(receiverAddress,
new BigInteger(1, tx.getValue()));
repository.addBalance(senderAddress,
new BigInteger(1, tx.getValue()).negate());
if (stateLogger.isInfoEnabled())
stateLogger.info("Update value balance \n "
+ "sender={}, receiver={}, value={}",
Hex.toHexString(senderAddress),
Hex.toHexString(receiverAddress),
new BigInteger(tx.getValue()));
}
}
// 5. CREATE OR EXECUTE PROGRAM
if (isContractCreation || code != null) {
Block currBlock = (block == null) ? blockchain.getLastBlock() : block;
ProgramInvoke programInvoke = ProgramInvokeFactory
.createProgramInvoke(tx, currBlock, trackRepository);
VM vm = new VM();
Program program = new Program(code, programInvoke);
if (CONFIG.playVM())
vm.play(program);
ProgramResult result = program.getResult();
applyProgramResult(result, gasDebit, trackRepository,
senderAddress, receiverAddress, coinbase, isContractCreation);
} else {
// refund everything except fee (500 + 5*txdata)
BigInteger gasPrice = new BigInteger(1, tx.getGasPrice());
long dataFee = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA;
long minTxFee = GasCost.TRANSACTION + dataFee;
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(
minTxFee).multiply(gasPrice));
if (refund.signum() > 0) {
// gas refund
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
}
} catch (RuntimeException e) {
trackRepository.rollback();
return;
}
trackRepository.commit();
pendingTransactions.put(Hex.toHexString(tx.getHash()), tx);
}
/**
* After any contract code finish the run the certain result should take
* place, according the given circumstances
*
* @param result
* @param gasDebit
* @param senderAddress
* @param contractAddress
*/
private void applyProgramResult(ProgramResult result, BigInteger gasDebit,
Repository repository, byte[] senderAddress,
byte[] contractAddress, byte[] coinbase, boolean initResults) {
if (result.getException() != null
&& result.getException() instanceof Program.OutOfGasException) {
logger.info("contract run halted by OutOfGas: contract={}",
Hex.toHexString(contractAddress));
throw result.getException();
}
BigInteger gasPrice = BigInteger.valueOf(blockchain.getGasPrice());
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(
result.getGasUsed()).multiply(gasPrice));
if (refund.signum() > 0) {
if (stateLogger.isInfoEnabled())
stateLogger
.info("After contract execution the sender address refunded with gas leftover, "
+ "\n sender={} \n contract={} \n gas_refund= {}",
Hex.toHexString(senderAddress),
Hex.toHexString(contractAddress), refund);
// gas refund
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
if (initResults) {
// Save the code created by init
byte[] bodyCode = null;
if (result.getHReturn() != null) {
bodyCode = result.getHReturn().array();
}
if (bodyCode != null) {
repository.saveCode(contractAddress, bodyCode);
if (stateLogger.isInfoEnabled())
stateLogger
.info("saving code of the contract to the db:\n contract={} code={}",
Hex.toHexString(contractAddress),
Hex.toHexString(bodyCode));
}
}
// delete the marked to die accounts
if (result.getDeleteAccounts() == null) return;
for (DataWord address : result.getDeleteAccounts()){
repository.delete(address.getNoLeadZeroesData());
}
}
public void applyBlock(Block block) {
if(block.getNumber() == 1157) {
logger.debug("Block 1157");
}
int i = 0;
for (Transaction tx : block.getTransactionsList()) {
logger.info("apply block: [ {} ] tx: [ {} ] ", block.getNumber(), i);
applyTransaction(block, tx, block.getCoinbase());
repository.dumpState(block.getNumber(), i,
Hex.toHexString(tx.getHash()));
++i;
}
// miner reward
if (repository.getAccountState(block.getCoinbase()) == null)
repository.createAccount(block.getCoinbase());
repository.addBalance(block.getCoinbase(), Block.BLOCK_REWARD);
for (Block uncle : block.getUncleList()) {
repository.addBalance(uncle.getCoinbase(), Block.UNCLE_REWARD);
}
repository.dumpState(block.getNumber(), 0,
null);
}
/***********************************************************************
* 1) the dialog put a pending transaction on the list
@ -361,6 +115,14 @@ public class WorldManager {
logger.info("pending transaction removed with hash: {} ", hash);
walletTransactions.remove(hash);
}
public void setRepository(Repository repository) {
this.repository = repository;
}
public void setBlockchain(Blockchain blockchain) {
this.blockchain = blockchain;
}
public void setWallet(Wallet wallet) {
this.wallet = wallet;
@ -370,9 +132,13 @@ public class WorldManager {
return repository;
}
public Blockchain getBlockChain() {
public Blockchain getBlockchain() {
return blockchain;
}
public void loadBlockchain() {
this.blockchain = repository.loadBlockchain();
}
public Wallet getWallet() {
return wallet;
@ -394,7 +160,6 @@ public class WorldManager {
this.listener = listener;
}
public void addPeers(List<PeerData> newPeers) {
for (PeerData peer : newPeers) {
if (this.peers.indexOf(peer) == -1) {
@ -420,12 +185,7 @@ public class WorldManager {
peerDiscovery.stop();
}
public BlockQueue getBlockQueue() {
return blockQueue;
}
public void close() {
blockchain.close();
repository.close();
}

View File

@ -20,10 +20,10 @@ public class BlockQueue {
private static Logger logger = LoggerFactory.getLogger("blockchain");
ConcurrentLinkedQueue<Block> blockQueue = new ConcurrentLinkedQueue<Block>();
Block lastBlock;
private Queue<Block> blockQueue = new ConcurrentLinkedQueue<>();
private Block lastBlock;
Timer timer = new Timer();
private Timer timer = new Timer();
public BlockQueue() {
@ -40,7 +40,7 @@ public class BlockQueue {
Block block = blockQueue.poll();
WorldManager.getInstance().getBlockChain().add(block);
WorldManager.getInstance().getBlockchain().add(block);
}
public void addBlocks(List<Block> blockList) {
@ -64,7 +64,7 @@ public class BlockQueue {
public Block getLast() {
if (blockQueue.isEmpty())
return WorldManager.getInstance().getBlockChain().getLastBlock();
return WorldManager.getInstance().getBlockchain().getLastBlock();
return lastBlock;
}

View File

@ -3,7 +3,6 @@ package org.ethereum.net.client;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -183,7 +183,8 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
List<Transaction> txList = transactionsMessage.getTransactions();
for(Transaction tx : txList)
WorldManager.getInstance().applyTransaction(null, tx, null);
WorldManager.getInstance().getBlockchain()
.applyTransaction(null, tx, null);
logger.info(transactionsMessage.toString());
if (peerListener != null) peerListener.console(transactionsMessage.toString());
@ -235,7 +236,7 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
}
if (blockList.isEmpty()) return;
WorldManager.getInstance().getBlockQueue().addBlocks(blockList);
WorldManager.getInstance().getBlockchain().getBlockQueue().addBlocks(blockList);
if (peerListener != null) peerListener.console(blocksMessage.toString());
}
@ -318,10 +319,10 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter {
private void sendGetChain() {
if (WorldManager.getInstance().getBlockQueue().size() >
if (WorldManager.getInstance().getBlockchain().getBlockQueue().size() >
SystemProperties.CONFIG.maxBlocksQueued()) return;
Block lastBlock = WorldManager.getInstance().getBlockQueue().getLast();
Block lastBlock = WorldManager.getInstance().getBlockchain().getBlockQueue().getLast();
byte[] hash = lastBlock.getHash();
GetChainMessage chainMessage =

View File

@ -88,7 +88,7 @@ public class GetChainMessage extends Message {
}
@Override
public Class getAnswerMessage() {
public Class<BlocksMessage> getAnswerMessage() {
return BlocksMessage.class;
}

View File

@ -27,7 +27,7 @@ public class GetPeersMessage extends Message {
}
@Override
public Class getAnswerMessage() {
public Class<PeersMessage> getAnswerMessage() {
return PeersMessage.class;
}
}

View File

@ -1,15 +1,7 @@
package org.ethereum.net.message;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import static org.ethereum.net.Command.HELLO;
/**
* www.ethereumJ.com
* @author: Roman Mandeleil
@ -35,7 +27,7 @@ public class PingMessage extends Message {
}
@Override
public Class getAnswerMessage() {
public Class<PongMessage> getAnswerMessage() {
return PongMessage.class;
}
}

View File

@ -16,13 +16,12 @@ import org.iq80.leveldb.DB;
*/
public class Cache {
private Map<ByteArrayWrapper, Node> nodes;
private Map<ByteArrayWrapper, Node> nodes = new HashMap<>();
private DB db;
private boolean isDirty;
public Cache(DB db) {
this.db = db;
nodes = new HashMap<ByteArrayWrapper, Node>();
}
/**
@ -88,7 +87,7 @@ public class Cache {
// If the nodes grows beyond the 200 entries we simple empty it
// FIXME come up with something better
if (this.nodes.size() > 200) {
this.nodes = new HashMap<ByteArrayWrapper, Node>();
this.nodes = new HashMap<>();
}
}

View File

@ -72,6 +72,10 @@ public class Trie implements TrieFacade {
return root;
}
public void setRoot(Object root) {
this.root = root;
}
public void setCache(Cache cache) {
this.cache = cache;
}

View File

@ -1,10 +1,7 @@
package org.ethereum.util;
import com.maxmind.geoip.Location;
import org.apache.log4j.PropertyConfigurator;
import org.ethereum.db.IpGeoDB;
import java.net.InetAddress;
import java.net.URL;
import static org.ethereum.config.SystemProperties.CONFIG;

View File

@ -24,7 +24,7 @@ public class ProgramInvokeFactory {
public static ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository) {
// https://ethereum.etherpad.mozilla.org/26
Block lastBlock = WorldManager.getInstance().getBlockChain().getLastBlock();
Block lastBlock = WorldManager.getInstance().getBlockchain().getLastBlock();
/*** ADDRESS op ***/
// YP: Get address of currently executing account.

View File

@ -23,9 +23,9 @@ log4j.logger.io.netty = ERROR
log4j.logger.wire = ERROR
log4j.logger.VM = DEBUG
log4j.logger.main = INFO
log4j.logger.state = ERROR
log4j.logger.state = DEBUG
log4j.logger.repository = DEBUG
log4j.logger.blockchain = DEBUG
log4j.logger.blockchain = INFO
log4j.logger.ui = ERROR
log4j.logger.gas = ERROR

View File

@ -24,8 +24,8 @@ log4j.logger.wire = ERROR
log4j.logger.VM = ERROR
log4j.logger.main = ERROR
log4j.logger.trie = ERROR
log4j.logger.state = ERROR
log4j.logger.repository = ERROR
log4j.logger.state = INFO
log4j.logger.repository = INFO
log4j.logger.blockchain = INFO
log4j.logger.txs = ERROR
log4j.logger.ui = ERROR

View File

@ -99,7 +99,7 @@ coinbase.secret = "monkey"
# in JSON form to [dump.dir]
# if [dump.full] = true
# posible values [true/false]
dump.full = true
dump.full = false
dump.dir = dmp
# clean the dump dir each start

View File

@ -115,7 +115,7 @@ public class BlockTest {
assertEquals(new BigInteger(1, Genesis.DIFFICULTY), difficulty);
// Storing genesis because the parent needs to be in the DB for calculation.
WorldManager.getInstance().getBlockChain().add(genesis);
WorldManager.getInstance().getBlockchain().add(genesis);
Block block1 = new Block(Hex.decode(block_1));
BigInteger calcDifficulty = new BigInteger(1, block1.calcDifficulty());
@ -137,7 +137,7 @@ public class BlockTest {
assertEquals(Genesis.GAS_LIMIT, gasLimit);
// Storing genesis because the parent needs to be in the DB for calculation.
WorldManager.getInstance().getBlockChain().add(genesis);
WorldManager.getInstance().getBlockchain().add(genesis);
// Test with block
Block block1 = new Block(Hex.decode(block_1));