Browse Source

Merge pull request #202 from kennycud/master

Foreign Wallet Balance Enhancements
master
crowetic 2 weeks ago committed by GitHub
parent
commit
cbb171f859
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 46
      src/main/java/org/qortal/crosschain/Bitcoiny.java
  2. 89
      src/main/java/org/qortal/crosschain/BlockchainCache.java
  3. 8
      src/main/java/org/qortal/settings/Settings.java

46
src/main/java/org/qortal/crosschain/Bitcoiny.java

@ -55,6 +55,13 @@ public abstract class Bitcoiny implements ForeignBlockchain {
protected Coin feePerKb; protected Coin feePerKb;
/**
* Blockchain Cache
*
* To store blockchain data and reduce redundant RPCs to the ElectrumX servers
*/
private final BlockchainCache blockchainCache = new BlockchainCache();
// Constructors and instance // Constructors and instance
protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode, Coin feePerKb) { protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode, Coin feePerKb) {
@ -509,8 +516,22 @@ public abstract class Bitcoiny implements ForeignBlockchain {
if (!historicTransactionHashes.isEmpty()) { if (!historicTransactionHashes.isEmpty()) {
areAllKeysUnused = false; areAllKeysUnused = false;
for (TransactionHash transactionHash : historicTransactionHashes) for (TransactionHash transactionHash : historicTransactionHashes) {
walletTransactions.add(this.getTransaction(transactionHash.txHash));
Optional<BitcoinyTransaction> walletTransaction
= this.blockchainCache.getTransactionByHash( transactionHash.txHash );
// if the wallet transaction is already cached
if(walletTransaction.isPresent() ) {
walletTransactions.add( walletTransaction.get() );
}
// otherwise get the transaction from the blockchain server
else {
BitcoinyTransaction transaction = getTransaction(transactionHash.txHash);
walletTransactions.add( transaction );
this.blockchainCache.addTransactionByHash(transactionHash.txHash, transaction);
}
}
} }
} }
@ -602,9 +623,15 @@ public abstract class Bitcoiny implements ForeignBlockchain {
for (; ki < keys.size(); ++ki) { for (; ki < keys.size(); ++ki) {
DeterministicKey dKey = keys.get(ki); DeterministicKey dKey = keys.get(ki);
// Check for transactions
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH); Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
keySet.add(address.toString()); keySet.add(address.toString());
// if the key already has a verified transaction history
if( this.blockchainCache.keyHasHistory( dKey ) ){
areAllKeysUnused = false;
}
else {
// Check for transactions
byte[] script = ScriptBuilder.createOutputScript(address).getProgram(); byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
// Ask for transaction history - if it's empty then key has never been used // Ask for transaction history - if it's empty then key has never been used
@ -612,6 +639,8 @@ public abstract class Bitcoiny implements ForeignBlockchain {
if (!historicTransactionHashes.isEmpty()) { if (!historicTransactionHashes.isEmpty()) {
areAllKeysUnused = false; areAllKeysUnused = false;
this.blockchainCache.addKeyWithHistory(dKey);
}
} }
} }
@ -667,18 +696,25 @@ public abstract class Bitcoiny implements ForeignBlockchain {
do { do {
boolean areAllKeysUnused = true; boolean areAllKeysUnused = true;
for (; ki < keys.size(); ++ki) { for (; areAllKeysUnused && ki < keys.size(); ++ki) {
DeterministicKey dKey = keys.get(ki); DeterministicKey dKey = keys.get(ki);
// if the key already has a verified transaction history
if( this.blockchainCache.keyHasHistory(dKey)) {
areAllKeysUnused = false;
}
else {
// Check for transactions // Check for transactions
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH); Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
byte[] script = ScriptBuilder.createOutputScript(address).getProgram(); byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
// Ask for transaction history - if it's empty then key has never been used // Ask for transaction history - if it's empty then key has never been used
List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, false); List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, true);
if (!historicTransactionHashes.isEmpty()) { if (!historicTransactionHashes.isEmpty()) {
areAllKeysUnused = false; areAllKeysUnused = false;
this.blockchainCache.addKeyWithHistory(dKey);
}
} }
} }

89
src/main/java/org/qortal/crosschain/BlockchainCache.java

@ -0,0 +1,89 @@
package org.qortal.crosschain;
import org.bitcoinj.crypto.DeterministicKey;
import org.qortal.settings.Settings;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
/**
* Class BlockchainCache
*
* Cache blockchain information to reduce redundant RPCs to the ElectrumX servers.
*/
public class BlockchainCache {
/**
* Keys With History
*
* Deterministic Keys with any transaction history.
*/
private Queue<DeterministicKey> keysWithHistory = new ConcurrentLinkedDeque<>();
/**
* Transactions By Hash
*
* Transaction Hash -> Transaction
*/
private ConcurrentHashMap<String, BitcoinyTransaction> transactionByHash = new ConcurrentHashMap<>();
/**
* Cache Limit
*
* If this limit is reached, the cache will be cleared or reduced.
*/
private static final int CACHE_LIMIT = Settings.getInstance().getBlockchainCacheLimit();
/**
* Add Key With History
*
* @param key a deterministic key with a verified history
*/
public void addKeyWithHistory(DeterministicKey key) {
if( this.keysWithHistory.size() > CACHE_LIMIT ) {
this.keysWithHistory.remove();
}
this.keysWithHistory.add(key);
}
/**
* Key Has History?
*
* @param key the deterministic key
*
* @return true if the key has a history, otherwise false
*/
public boolean keyHasHistory( DeterministicKey key ) {
return this.keysWithHistory.contains(key);
}
/**
* Add Transaction By Hash
*
* @param hash the transaction hash
* @param transaction the transaction
*/
public void addTransactionByHash( String hash, BitcoinyTransaction transaction ) {
if( this.transactionByHash.size() > CACHE_LIMIT ) {
this.transactionByHash.clear();
}
this.transactionByHash.put(hash, transaction);
}
/**
* Get Transaction By Hash
*
* @param hash the transaction hash
*
* @return the transaction, empty if the hash is not in the cache
*/
public Optional<BitcoinyTransaction> getTransactionByHash( String hash ) {
return Optional.ofNullable( this.transactionByHash.get(hash) );
}
}

8
src/main/java/org/qortal/settings/Settings.java

@ -323,11 +323,14 @@ public class Settings {
/* Foreign chains */ /* Foreign chains */
/** The number of consecutive empty addresses required before treating a wallet's transaction set as complete */ /** The number of consecutive empty addresses required before treating a wallet's transaction set as complete */
private int gapLimit = 24; private int gapLimit = 3;
/** How many wallet keys to generate when using bitcoinj as the blockchain interface (e.g. when sending coins) */ /** How many wallet keys to generate when using bitcoinj as the blockchain interface (e.g. when sending coins) */
private int bitcoinjLookaheadSize = 50; private int bitcoinjLookaheadSize = 50;
/** How many units of data to be kept in a blockchain cache before the cache should be reduced or cleared. */
private int blockchainCacheLimit = 1000;
// Data storage (QDN) // Data storage (QDN)
/** Data storage enabled/disabled*/ /** Data storage enabled/disabled*/
@ -1049,6 +1052,9 @@ public class Settings {
return bitcoinjLookaheadSize; return bitcoinjLookaheadSize;
} }
public int getBlockchainCacheLimit() {
return blockchainCacheLimit;
}
public boolean isQdnEnabled() { public boolean isQdnEnabled() {
return this.qdnEnabled; return this.qdnEnabled;

Loading…
Cancel
Save