Fix for lint OutOfMemory error.

Finished implementing blockstore for android using ormlite.
This commit is contained in:
Adrian Tiberius 2015-06-10 19:38:43 +02:00
parent b3afe42f38
commit b36e784bd5
12 changed files with 470 additions and 196 deletions

View File

@ -80,6 +80,12 @@ public class MainActivity extends ActionBarActivity {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Override
protected void onDestroy() {
super.onDestroy();
ethereumManager.onDestroy();
}
// The definition of our task class // The definition of our task class
private class PostTask extends AsyncTask<Context, Integer, String> { private class PostTask extends AsyncTask<Context, Integer, String> {

View File

@ -2,6 +2,8 @@ package org.ethereum.android;
import android.content.Context; import android.content.Context;
import com.j256.ormlite.android.apptools.OpenHelperManager;
import org.ethereum.android.di.modules.EthereumModule; import org.ethereum.android.di.modules.EthereumModule;
import org.ethereum.android.di.components.DaggerEthereumComponent; import org.ethereum.android.di.components.DaggerEthereumComponent;
import org.ethereum.config.SystemProperties; import org.ethereum.config.SystemProperties;
@ -57,4 +59,8 @@ public class EthereumManager {
jsonRpcServer.start(); jsonRpcServer.start();
} }
public void onDestroy() {
OpenHelperManager.releaseHelper();
}
} }

View File

@ -1,84 +0,0 @@
package org.ethereum.android.db;
import java.sql.SQLException;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import org.ethereum.db.BlockVO;
/**
* Database helper class used to manage the creation and upgrading of your database. This class also usually provides
* the DAOs used by the other classes.
*/
public class BlockDatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String DATABASE_NAME = "blocks.db";
private static final int DATABASE_VERSION = 1;
// the DAO object we use to access the SimpleData table
private Dao<BlockVO, Integer> blockDao = null;
public BlockDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* This is called when the database is first created. Usually you should call createTable statements here to create
* the tables that will store your data.
*/
@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
try {
Log.i(BlockDatabaseHelper.class.getName(), "onCreate");
TableUtils.createTable(connectionSource, BlockVO.class);
} catch (SQLException e) {
Log.e(BlockDatabaseHelper.class.getName(), "Can't create database", e);
throw new RuntimeException(e);
}
}
/**
* This is called when your application is upgraded and it has a higher version number. This allows you to adjust
* the various data to match the new version number.
*/
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
Log.i(BlockDatabaseHelper.class.getName(), "onUpgrade");
TableUtils.dropTable(connectionSource, BlockVO.class, true);
// after we drop the old databases, we create the new ones
onCreate(db, connectionSource);
} catch (SQLException e) {
Log.e(BlockDatabaseHelper.class.getName(), "Can't drop databases", e);
throw new RuntimeException(e);
}
}
/**
* Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
* value.
*/
public Dao<BlockVO, Integer> getBlockDao() throws SQLException {
if (blockDao == null) {
blockDao = getDao(BlockVO.class);
}
return blockDao;
}
/**
* Close the database connections and clear any cached DAOs.
*/
@Override
public void close() {
super.close();
blockDao = null;
}
}

View File

@ -0,0 +1,34 @@
package org.ethereum.android.db;
import org.ethereum.core.Block;
import org.ethereum.core.TransactionReceipt;
import java.math.BigInteger;
import java.util.List;
public interface BlockStoreDatabase {
public List<BlockVO> getByNumber(Long number);
public List<BlockVO> getByHash(byte[] hash);
public List<byte[]> getHashListByNumberLimit(Long from, Long to);
public void deleteBlocksSince(long number);
public void save(BlockVO block);
public BigInteger getTotalDifficultySince(long number);
public BigInteger getTotalDifficulty();
public Block getBestBlock();
public List<Block> getAllBlocks();
public void reset();
public void save(TransactionReceiptVO transactionReceiptVO);
public TransactionReceipt getTransactionReceiptByHash(byte[] hash);
}

View File

@ -6,17 +6,16 @@ import org.ethereum.db.BlockStore;
import org.ethereum.util.ByteUtil; import org.ethereum.util.ByteUtil;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class BlockStoreImpl implements BlockStore { public class BlockStoreImpl implements BlockStore {
private BlockDatabaseHelper blockDao; private BlockStoreDatabase database;
private TransactionDatabaseHelper transactionDao;
public BlockStoreImpl(BlockDatabaseHelper blockDao, TransactionDatabaseHelper transactionDao) { public BlockStoreImpl(BlockStoreDatabase database) {
this.blockDao = blockDao; this.database = database;
this.transactionDao = transactionDao;
} }
public byte[] getBlockHashByNumber(long blockNumber) { public byte[] getBlockHashByNumber(long blockNumber) {
@ -29,65 +28,86 @@ public class BlockStoreImpl implements BlockStore {
public Block getBlockByNumber(long blockNumber) { public Block getBlockByNumber(long blockNumber) {
/*
List result = sessionFactory.getCurrentSession(). List result = database.getByNumber(blockNumber);
createQuery("from BlockVO where number = :number"). if (result.size() == 0) return null;
setParameter("number", blockNumber).list(); BlockVO vo = (BlockVO) result.get(0);
return new Block(vo.rlp);
}
public Block getBlockByHash(byte[] hash) {
List result = database.getByHash(hash);
if (result.size() == 0) return null; if (result.size() == 0) return null;
BlockVO vo = (BlockVO) result.get(0); BlockVO vo = (BlockVO) result.get(0);
return new Block(vo.rlp); return new Block(vo.rlp);
*/
return null;
} }
public Block getBlockByHash(byte[] hash) {
return null;
}
@SuppressWarnings("unchecked")
public List<byte[]> getListOfHashesStartFrom(byte[] hash, int qty) { public List<byte[]> getListOfHashesStartFrom(byte[] hash, int qty) {
return null; List<byte[]> hashes = new ArrayList<byte[]>();
// find block number of that block hash
Block block = getBlockByHash(hash);
if (block == null) return hashes;
hashes = database.getHashListByNumberLimit(block.getNumber(), block.getNumber() - qty);
return hashes;
} }
public void deleteBlocksSince(long number) { public void deleteBlocksSince(long number) {
database.deleteBlocksSince(number);
} }
public void saveBlock(Block block, List<TransactionReceipt> receipts) { public void saveBlock(Block block, List<TransactionReceipt> receipts) {
BlockVO blockVO = new BlockVO(block.getNumber(), block.getHash(),
block.getEncoded(), block.getCumulativeDifficulty());
for (TransactionReceipt receipt : receipts) {
byte[] hash = receipt.getTransaction().getHash();
byte[] rlp = receipt.getEncoded();
TransactionReceiptVO transactionReceiptVO = new TransactionReceiptVO(hash, rlp);
database.save(transactionReceiptVO);
}
database.save(blockVO);
} }
public BigInteger getTotalDifficultySince(long number) { public BigInteger getTotalDifficultySince(long number) {
return null; return database.getTotalDifficultySince(number);
} }
public BigInteger getTotalDifficulty() { public BigInteger getTotalDifficulty() {
return null; return database.getTotalDifficulty();
} }
public Block getBestBlock() { public Block getBestBlock() {
return null; return database.getBestBlock();
} }
@SuppressWarnings("unchecked")
public List<Block> getAllBlocks() { public List<Block> getAllBlocks() {
return null; return database.getAllBlocks();
} }
public void reset() { public void reset() {
database.reset();
} }
public TransactionReceipt getTransactionReceiptByHash(byte[] hash) { public TransactionReceipt getTransactionReceiptByHash(byte[] hash) {
return null; return database.getTransactionReceiptByHash(hash);
} }
} }

View File

@ -0,0 +1,66 @@
package org.ethereum.android.db;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.math.BigInteger;
@DatabaseTable(tableName = "block")
public class BlockVO {
@DatabaseField(index = true, dataType = DataType.BYTE_ARRAY)
byte[] hash;
@DatabaseField(index = true, dataType = DataType.LONG_OBJ)
Long number;
@DatabaseField(columnName = "cumulativedifficulty", dataType = DataType.BIG_INTEGER)
BigInteger cumulativeDifficulty;
@DatabaseField(dataType = DataType.BYTE_ARRAY)
byte[] rlp;
public BlockVO() {
}
public BlockVO(Long number, byte[] hash, byte[] rlp, BigInteger cumulativeDifficulty) {
this.number = number;
this.hash = hash;
this.rlp = rlp;
this.cumulativeDifficulty = cumulativeDifficulty;
}
public byte[] getHash() {
return hash;
}
public void setHash(byte[] hash) {
this.hash = hash;
}
public Long getIndex() {
return number;
}
public void setIndex(Long number) {
this.number = number;
}
public byte[] getRlp() {
return rlp;
}
public void setRlp(byte[] rlp) {
this.rlp = rlp;
}
public BigInteger getCumulativeDifficulty() {
return cumulativeDifficulty;
}
public void setCumulativeDifficulty(BigInteger cumulativeDifficulty) {
this.cumulativeDifficulty = cumulativeDifficulty;
}
}

View File

@ -0,0 +1,255 @@
package org.ethereum.android.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.GenericRawResults;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import org.ethereum.core.Block;
import org.ethereum.core.TransactionReceipt;
import java.math.BigInteger;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class OrmLiteBlockStoreDatabase extends OrmLiteSqliteOpenHelper implements BlockStoreDatabase {
private static final String DATABASE_NAME = "blockchain.db";
private static final int DATABASE_VERSION = 1;
private Dao<BlockVO, Integer> blockDao = null;
private Dao<TransactionReceiptVO, Integer> transactionDao = null;
public OrmLiteBlockStoreDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* This is called when the database is first created. Usually you should call createTable statements here to create
* the tables that will store your data.
*/
@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
try {
Log.i(OrmLiteBlockStoreDatabase.class.getName(), "onCreate");
TableUtils.createTable(connectionSource, BlockVO.class);
TableUtils.createTable(connectionSource, TransactionReceiptVO.class);
} catch (SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Can't create database", e);
throw new RuntimeException(e);
}
}
/**
* This is called when your application is upgraded and it has a higher version number. This allows you to adjust
* the various data to match the new version number.
*/
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
Log.i(OrmLiteBlockStoreDatabase.class.getName(), "onUpgrade");
TableUtils.dropTable(connectionSource, BlockVO.class, true);
TableUtils.dropTable(connectionSource, TransactionReceiptVO.class, true);
// after we drop the old databases, we create the new ones
onCreate(db, connectionSource);
} catch (SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Can't drop databases", e);
throw new RuntimeException(e);
}
}
/**
* Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
* value.
*/
public Dao<BlockVO, Integer> getBlockDao() throws SQLException {
if (blockDao == null) {
blockDao = getDao(BlockVO.class);
}
return blockDao;
}
/**
* Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
* value.
*/
public Dao<TransactionReceiptVO, Integer> getTransactionDao() throws SQLException {
if (transactionDao == null) {
transactionDao = getDao(TransactionReceiptVO.class);
}
return transactionDao;
}
/**
* Close the database connections and clear any cached DAOs.
*/
@Override
public void close() {
super.close();
blockDao = null;
transactionDao = null;
}
public List<BlockVO> getByNumber(Long number) {
List<BlockVO> list = new ArrayList<BlockVO>();
try {
list = getBlockDao().queryForEq("number", number);
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error querying for number", e);
}
return list;
}
public List<BlockVO> getByHash(byte[] hash) {
List<BlockVO> list = new ArrayList<BlockVO>();
try {
list = getBlockDao().queryForEq("hash", hash);
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error querying for hash", e);
}
return list;
}
public List<byte[]> getHashListByNumberLimit(Long from, Long to) {
List<byte[]> results = new ArrayList<byte[]>();
try {
List<BlockVO> list = new ArrayList<BlockVO>();
list = getBlockDao().queryBuilder().orderBy("number", false).limit(to - from).where().between("number", from, to).query();
for (BlockVO block : list) {
results.add(block.hash);
}
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error querying for hash list", e);
}
return results;
}
public void deleteBlocksSince(long number) {
try {
DeleteBuilder<BlockVO, Integer> deleteBuilder = getBlockDao().deleteBuilder();
deleteBuilder.where().gt("number", number);
deleteBuilder.delete();
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error deleting blocks since", e);
}
}
public void save(BlockVO block) {
try {
getBlockDao().create(block);
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error saving block", e);
}
}
public BigInteger getTotalDifficultySince(long number) {
try {
GenericRawResults<String[]> rawResults = getBlockDao().queryRaw("select sum(cumulativedifficulty) from block where number > " + number);
List<String[]> results = rawResults.getResults();
return new BigInteger(results.get(0)[0]);
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error getting total difficulty since", e);
}
return null;
}
public BigInteger getTotalDifficulty() {
try {
GenericRawResults<String[]> rawResults = getBlockDao().queryRaw("select sum(cumulativedifficulty) from block");
List<String[]> results = rawResults.getResults();
return new BigInteger(results.get(0)[0]);
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error getting total difficulty", e);
}
return null;
}
public Block getBestBlock() {
Long bestNumber = null;
try {
GenericRawResults<String[]> rawResults = getBlockDao().queryRaw("select max(number) from block");
List<String[]> results = rawResults.getResults();
if (results.size() > 0 && results.get(0).length > 0) {
bestNumber = Long.valueOf(results.get(0)[0]);
}
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Sql Error getting best block", e);
} catch (Exception e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error getting best block", e);
}
if (bestNumber == null) return null;
List result = getByNumber(bestNumber);
if (result.isEmpty()) return null;
BlockVO vo = (BlockVO) result.get(0);
return new Block(vo.rlp);
}
public List<Block> getAllBlocks() {
ArrayList<Block> blocks = new ArrayList<>();
try {
for (BlockVO blockVO : getBlockDao()) {
blocks.add(new Block(blockVO.getRlp()));
}
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error getting all blocks", e);
}
return blocks;
}
public void reset() {
deleteBlocksSince(Long.valueOf(0));
}
public void save(TransactionReceiptVO transactionReceiptVO) {
try {
getTransactionDao().create(transactionReceiptVO);
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error saving transaction", e);
}
}
public TransactionReceipt getTransactionReceiptByHash(byte[] hash) {
List<TransactionReceiptVO> list = new ArrayList<TransactionReceiptVO>();
try {
list = getTransactionDao().queryForEq("hash", hash);
} catch(java.sql.SQLException e) {
Log.e(OrmLiteBlockStoreDatabase.class.getName(), "Error querying for hash", e);
}
if (list.size() == 0) return null;
TransactionReceiptVO vo = list.get(0);
return new TransactionReceipt(vo.rlp);
}
}

View File

@ -1,84 +0,0 @@
package org.ethereum.android.db;
import java.sql.SQLException;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import org.ethereum.db.TransactionReceiptVO;
/**
* Database helper class used to manage the creation and upgrading of your database. This class also usually provides
* the DAOs used by the other classes.
*/
public class TransactionDatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String DATABASE_NAME = "transactions.db";
private static final int DATABASE_VERSION = 1;
// the DAO object we use to access the SimpleData table
private Dao<TransactionReceiptVO, Integer> dao = null;
public TransactionDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* This is called when the database is first created. Usually you should call createTable statements here to create
* the tables that will store your data.
*/
@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
try {
Log.i(BlockDatabaseHelper.class.getName(), "onCreate");
TableUtils.createTable(connectionSource, TransactionReceiptVO.class);
} catch (SQLException e) {
Log.e(BlockDatabaseHelper.class.getName(), "Can't create database", e);
throw new RuntimeException(e);
}
}
/**
* This is called when your application is upgraded and it has a higher version number. This allows you to adjust
* the various data to match the new version number.
*/
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
Log.i(BlockDatabaseHelper.class.getName(), "onUpgrade");
TableUtils.dropTable(connectionSource, TransactionReceiptVO.class, true);
// after we drop the old databases, we create the new ones
onCreate(db, connectionSource);
} catch (SQLException e) {
Log.e(BlockDatabaseHelper.class.getName(), "Can't drop databases", e);
throw new RuntimeException(e);
}
}
/**
* Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
* value.
*/
public Dao<TransactionReceiptVO, Integer> getBlockDao() throws SQLException {
if (dao == null) {
dao = getDao(TransactionReceiptVO.class);
}
return dao;
}
/**
* Close the database connections and clear any cached DAOs.
*/
@Override
public void close() {
super.close();
dao = null;
}
}

View File

@ -0,0 +1,49 @@
package org.ethereum.android.db;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
/**
* @author Roman Mandeleil
* @since 14.11.2014
*/
@DatabaseTable(tableName = "transaction_receipt")
public class TransactionReceiptVO {
@DatabaseField(index = true, dataType = DataType.BYTE_ARRAY)
byte[] hash;
@DatabaseField(dataType = DataType.BYTE_ARRAY)
byte[] rlp;
public TransactionReceiptVO() {
}
public TransactionReceiptVO(byte[] hash, byte[] rlp) {
this.hash = hash;
this.rlp = rlp;
}
public byte[] getHash() {
return hash;
}
public void setHash(byte[] hash) {
this.hash = hash;
}
public byte[] getRlp() {
return rlp;
}
public void setRlp(byte[] rlp) {
this.rlp = rlp;
}
}

View File

@ -2,12 +2,15 @@ package org.ethereum.android.di.modules;
import android.content.Context; import android.content.Context;
import com.j256.ormlite.android.apptools.OpenHelperManager;
import org.ethereum.android.datasource.LevelDbDataSource; import org.ethereum.android.datasource.LevelDbDataSource;
import org.ethereum.android.db.OrmLiteBlockStoreDatabase;
import org.ethereum.android.db.BlockStoreImpl;
import org.ethereum.config.SystemProperties; import org.ethereum.config.SystemProperties;
import org.ethereum.core.BlockchainImpl; import org.ethereum.core.BlockchainImpl;
import org.ethereum.core.Wallet; import org.ethereum.core.Wallet;
import org.ethereum.db.BlockStore; import org.ethereum.db.BlockStore;
import org.ethereum.db.InMemoryBlockStore;
import org.ethereum.db.RepositoryImpl; import org.ethereum.db.RepositoryImpl;
import org.ethereum.facade.Blockchain; import org.ethereum.facade.Blockchain;
import org.ethereum.facade.Ethereum; import org.ethereum.facade.Ethereum;
@ -72,7 +75,8 @@ public class EthereumModule {
@Provides @Provides
@Singleton @Singleton
BlockStore provideBlockStore() { BlockStore provideBlockStore() {
return new InMemoryBlockStore(); OrmLiteBlockStoreDatabase database = OpenHelperManager.getHelper(context, OrmLiteBlockStoreDatabase.class);
return new BlockStoreImpl(database);
} }
@Provides @Provides

View File

@ -100,7 +100,7 @@ test {
logger.lifecycle("Running test: ${descriptor}") logger.lifecycle("Running test: ${descriptor}")
} }
jvmArgs '-Xss32m' jvmArgs += [ "-Xss32m", "-XX:MaxPermSize=256m" ]
testLogging { testLogging {
events "failed" events "failed"

View File

@ -16,3 +16,5 @@
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
org.gradle.jvmargs=-XX:MaxPermSize=512m