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:
parent
63bb2aa852
commit
163036c5ea
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
@ -54,6 +59,12 @@ public class DefaultConfig {
|
|||
return new LevelDbDataSource();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public BlockStore blockStore(SessionFactory sessionFactory){
|
||||
return new BlockStoreImpl(sessionFactory);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public SessionFactory sessionFactory() throws SQLException {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue