Introduce remote config + in memory block store

The library now can be initialized with just in memory
block saving with eviction policy of keep only last 1000 blocks,
that kind of block store is useful in on server usage of library.
In order to change the configuration for on server usage
use: `ethereum =  EthereumFactory.createEthereum(RemoteConfig.class);`
This commit is contained in:
Roman Mandeleil 2015-01-30 19:18:41 +02:00
parent 63bb2aa852
commit 163036c5ea
8 changed files with 18159 additions and 8 deletions

View File

@ -21,17 +21,12 @@ import java.util.List;
* @author Roman Mandeleil
* @since 12.11.2014
*/
@Repository("blockStore")
@Transactional(propagation = Propagation.SUPPORTS)
public class BlockStoreImpl implements BlockStore {
@Autowired
private SessionFactory sessionFactory;
@Autowired
ApplicationContext ctx;
public BlockStoreImpl() {
public BlockStoreImpl(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override

View File

@ -0,0 +1,118 @@
package org.ethereum.db;
import org.ethereum.core.Block;
import org.ethereum.core.Genesis;
import org.ethereum.core.TransactionReceipt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.*;
import static org.ethereum.util.ByteUtil.*;
/**
* @author: Roman Mandeleil
* Created on: 29/01/2015 20:43
*/
public class InMemoryBlockStore implements BlockStore{
final static public int MAX_BLOCKS = 1000;
Map<ByteArrayWrapper, Block> hashIndex = new HashMap<>();
Map<Long, Block> numberIndex = new HashMap<>();
List<Block> blocks = new ArrayList<>();
public InMemoryBlockStore(){
}
@Override
public byte[] getBlockHashByNumber(long blockNumber) {
Block block = numberIndex.get(blockNumber);
if (block == null) return null;
return block.getHash();
}
@Override
public Block getBlockByNumber(long blockNumber) {
return numberIndex.get(blockNumber);
}
@Override
public Block getBlockByHash(byte[] hash) {
return hashIndex.get(wrap(hash));
}
@Override
public List<byte[]> getListOfHashesStartFrom(byte[] hash, int qty) {
Block startBlock = hashIndex.get(wrap(hash));
long endIndex = startBlock.getNumber() + qty;
endIndex = getBestBlock().getNumber() < endIndex ? getBestBlock().getNumber() : endIndex;
List<byte[]> hashes = new ArrayList<>();
for (long i = startBlock.getNumber(); i <= endIndex; ++i){
Block block = getBlockByNumber(i);
hashes.add(block.getHash() );
}
return hashes;
}
@Override
public void deleteBlocksSince(long number) {
// todo: delete blocks sinse
}
@Override
public void saveBlock(Block block, List<TransactionReceipt> receipts) {
ByteArrayWrapper wHash = wrap(block.getHash());
blocks.add(block);
hashIndex.put(wHash, block);
numberIndex.put(block.getNumber(), block);
if (blocks.size() > MAX_BLOCKS){
Block rBlock = blocks.remove(0);
hashIndex.remove(wrap(rBlock.getHash()));
numberIndex.remove(rBlock.getNumber());
}
}
@Override
public BigInteger getTotalDifficultySince(long number) {
return BigInteger.ZERO;
}
@Override
public BigInteger getTotalDifficulty() {
return BigInteger.ZERO;
}
@Override
public Block getBestBlock() {
if (blocks.size() == 0) return null;
return blocks.get(blocks.size() - 1);
}
@Override
public List<Block> getAllBlocks() {
return blocks;
}
@Override
public void reset() {
blocks.clear();
hashIndex.clear();
numberIndex.clear();
}
@Override
public TransactionReceipt getTransactionReceiptByHash(byte[] hash) {
return null;
}
}

View File

@ -4,7 +4,10 @@ import org.ethereum.config.SystemProperties;
import org.ethereum.datasource.KeyValueDataSource;
import org.ethereum.datasource.LevelDbDataSource;
import org.ethereum.datasource.RedisDataSource;
import org.ethereum.db.BlockStore;
import org.ethereum.db.BlockStoreImpl;
import org.ethereum.db.RepositoryImpl;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -17,6 +20,8 @@ import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.sql.SQLException;
import java.util.Properties;
@ -53,6 +58,12 @@ public class DefaultConfig {
return new LevelDbDataSource();
}
@Bean
@Transactional(propagation = Propagation.SUPPORTS)
public BlockStore blockStore(SessionFactory sessionFactory){
return new BlockStoreImpl(sessionFactory);
}
@Bean

View File

@ -0,0 +1,131 @@
package org.ethereum.facade;
import org.ethereum.config.SystemProperties;
import org.ethereum.datasource.KeyValueDataSource;
import org.ethereum.datasource.LevelDbDataSource;
import org.ethereum.datasource.RedisDataSource;
import org.ethereum.db.BlockStore;
import org.ethereum.db.BlockStoreImpl;
import org.ethereum.db.InMemoryBlockStore;
import org.ethereum.db.RepositoryImpl;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.sql.SQLException;
import java.util.Properties;
import static org.ethereum.config.SystemProperties.CONFIG;
/**
*
* @author: Roman Mandeleil
* Created on: 27/01/2015 01:05
*/
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "org.ethereum")
public class RemoteConfig {
private static final Logger logger = LoggerFactory.getLogger("general");
@Autowired
Ethereum eth;
// todo: init total difficulty
// todo: init last 1000 blocks
@Bean
Repository repository(){
return new RepositoryImpl(keyValueDataSource(), keyValueDataSource());
}
@Bean
@Scope("prototype")
public KeyValueDataSource keyValueDataSource(){
if (CONFIG.getKeyValueDataSource().equals("redis")) {
return new RedisDataSource();
}
return new LevelDbDataSource();
}
@Bean
@Transactional(propagation = Propagation.SUPPORTS)
public BlockStore blockStore(SessionFactory sessionFactory){
return new InMemoryBlockStore();
}
@Bean
public SessionFactory sessionFactory() throws SQLException {
LocalSessionFactoryBuilder builder =
new LocalSessionFactoryBuilder(dataSource());
builder.scanPackages("org.ethereum.db")
.addProperties(getHibernateProperties());
return builder.buildSessionFactory();
}
private Properties getHibernateProperties() {
Properties prop = new Properties();
if (SystemProperties.CONFIG.databaseReset())
prop.put("hibernate.hbm2ddl.auto", "create");
prop.put("hibernate.format_sql", "true");
// todo: useful but annoying consider define by system.properties
// prop.put("hibernate.show_sql", "true");
prop.put("hibernate.dialect",
"org.hibernate.dialect.HSQLDialect");
return prop;
}
@Bean
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource());
return dataSourceTransactionManager;
}
@Bean(name = "dataSource")
public DriverManagerDataSource dataSource() {
logger.info("Connecting to the block store");
System.setProperty("hsqldb.reconfig_logging", "false");
String url =
String.format("jdbc:hsqldb:file:./%s/blockchain/blockchain.db;" +
"create=%s;hsqldb.default_table_type=cached",
SystemProperties.CONFIG.databaseDir(),
SystemProperties.CONFIG.databaseReset());
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.hsqldb.jdbcDriver");
ds.setUrl(url);
ds.setUsername("sa");
return ds;
}
}

View File

@ -160,7 +160,7 @@ public class WorldManager {
public boolean isBlockchainLoading() {
return blockchain.getQueue().size() > 2;
}
public void loadBlockchain() {
Block bestBlock = blockStore.getBestBlock();

View File

@ -0,0 +1,91 @@
package test.ethereum.blockstore;
import org.ethereum.core.Block;
import org.ethereum.core.Genesis;
import org.ethereum.db.InMemoryBlockStore;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import static org.junit.Assert.*;
/**
* @author: Roman Mandeleil
* Created on: 30/01/2015 11:04
*/
public class InMemoryBlockStoreTest {
private static final Logger logger = LoggerFactory.getLogger("test");
private InMemoryBlockStore blockStore;
@Before
public void setup() throws URISyntaxException, IOException {
blockStore = new InMemoryBlockStore();
URL scenario1 = ClassLoader
.getSystemResource("blockstore/load.dmp");
File file = new File(scenario1.toURI());
List<String> strData = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
for (String blockRLP : strData) {
Block block = new Block(
Hex.decode(blockRLP));
logger.info("adding block.hash: {}", Hex.toHexString(block.getHash()).substring(6));
blockStore.saveBlock(block, null);
}
}
@Test
public void testSaving8001Blocks() {
Block bestBlock = blockStore.getBestBlock();
Long bestIndex = blockStore.getBestBlock().getNumber();
Long firstIndex = bestIndex - InMemoryBlockStore.MAX_BLOCKS;
assertTrue(bestIndex == 8001);
assertTrue(firstIndex == 7001);
assertTrue(blockStore.getBlockByNumber(7000) == null);
assertTrue(blockStore.getBlockByNumber(8002) == null);
Block byHashBlock = blockStore.getBlockByHash(bestBlock.getHash());
assertTrue(bestBlock.getNumber() == byHashBlock.getNumber());
byte[] hashFor8500 = blockStore.getBlockByNumber(7500).getHash();
Block block8500 = blockStore.getBlockByHash(hashFor8500);
assertTrue(block8500.getNumber() == 7500);
}
@Test
public void testListOfHashes(){
Block block = blockStore.getBlockByNumber(7500);
byte[] hash = block.getHash();
List<byte[]> hashes = blockStore.getListOfHashesStartFrom(hash, 700);
byte[] lastHash = hashes.get(hashes.size() - 1);
assertEquals(Hex.toHexString(blockStore.getBestBlock().getHash()),
Hex.toHexString(lastHash));
assertTrue(hashes.size() == 502);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff