package jelectrum;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Block;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import java.util.concurrent.atomic.AtomicInteger;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Set;
public class BlockMadScan
{
public static void main(String args[]) throws Exception
{
new BlockMadScan(new Config(args[0]));
}
private Jelectrum jelly;
private LinkedBlockingQueue<Sha256Hash> queue;
private Semaphore sem;
private AtomicInteger blocks_scanned;
private AtomicInteger transactions_scanned;
private String rescan_operation="zing-" + System.currentTimeMillis();
private TXUtil tx_util;
private LRUCache<String, Set<Sha256Hash> > addr_to_tx_cache;
public BlockMadScan(Config config)
throws Exception
{
jelly = new Jelectrum(config);
tx_util = new TXUtil(jelly.getDB(), jelly.getNetworkParameters());
queue = new LinkedBlockingQueue<Sha256Hash>();
sem = new Semaphore(0);
blocks_scanned = new AtomicInteger(0);
transactions_scanned = new AtomicInteger(0);
addr_to_tx_cache = new LRUCache<String, Set<Sha256Hash> >(250000);
BlockStore block_store = jelly.getBlockStore();
//StoredBlock head = block_store.getChainHead();
//StoredBlock curr_block = head;
//Sha256Hash genisis_hash = jelly.getNetworkParameters().getGenesisBlock().getHash();
int queued=0;
new RateThread("1-minute", 60000L).start();
new RateThread("5-minute", 60000L * 5L).start();
new RateThread("1-hour", 60000L * 60L).start();
for(int i=0; i<500; i++)
{
new WorkerThread().start();
}
int h =0;
while(true)
{
Sha256Hash curr_hash = jelly.getBlockChainCache().getBlockHashAtHeight(h);
if (curr_hash == null) break;
queue.put(curr_hash);
queued++;
h++;
if (h % 10000 == 0)
{
System.out.println("Block: " + h);
}
}
/*while(true)
{
Sha256Hash curr_hash = curr_block.getHeader().getHash();
queue.put(curr_hash);
if (curr_block.getHeight() % 10000 == 0)
{
System.out.println("Block: " + curr_block.getHeight());
}
queued++;
if (curr_hash.equals(genisis_hash)) break;
curr_block = curr_block.getPrev(block_store);
}*/
System.out.println("Enqueued " + queued + " blocks");
while(queued>0)
{
sem.acquire();
queued--;
if (queued % 1000 == 0) System.out.println("" + queued + " blocks left");
}
}
public Set<Sha256Hash> getAddressToTxSet(String addr)
{
/*synchronized(addr_to_tx_cache)
{
if (addr_to_tx_cache.containsKey(addr)) return addr_to_tx_cache.get(addr);
}*/
Set<Sha256Hash> set = jelly.getDB().getAddressToTxSet(addr);
/*synchronized(addr_to_tx_cache)
{
addr_to_tx_cache.put(addr, set);
}*/
return set;
}
public class WorkerThread extends Thread
{
public WorkerThread()
{
setName("WorkerThread");
setDaemon(true);
}
public void run()
{
while(true)
{
Sha256Hash blk_hash = null;
try
{
blk_hash = queue.take();
String op = jelly.getDB().getBlockRescanMap().get(blk_hash);
if ((op == null) || (!op.equals(rescan_operation)))
{
Block blk = jelly.getDB().getBlockMap().get(blk_hash).getBlock(jelly.getNetworkParameters());
for(Transaction tx : blk.getTransactions())
{
SerializedTransaction tx2 = jelly.getDB().getTransactionMap().get(tx.getHash());
if (tx2 == null)
{
System.out.println("Transaction map does not contain " + tx.getHash());
System.exit(-1);
}
Collection<String> addresses = tx_util.getAllAddresses(tx, true, null);
for(String addr : addresses)
{
Set<Sha256Hash> tx_set = getAddressToTxSet(addr);
if (!tx_set.contains(tx.getHash()))
{
System.out.println("Address list for " + addr + " does not contain " + tx.getHash());
System.out.println(tx_set);
System.exit(-1);
}
}
Set<Sha256Hash> blk_set = jelly.getDB().getTxToBlockMap(tx.getHash());
if (!blk_set.contains(blk.getHash()))
{
System.out.println("TX Block list doesn't contain " + blk.getHash() + " for " + tx.getHash());
System.exit(-1);
}
transactions_scanned.incrementAndGet();
}
jelly.getDB().getBlockRescanMap().put(blk_hash, rescan_operation);
}
sem.release(1);
blocks_scanned.incrementAndGet();
}
catch(Throwable t)
{
t.printStackTrace();
if (blk_hash != null) queue.offer(blk_hash);
}
}
}
}
public class RateThread extends Thread
{
private long delay;
private String name;
public RateThread(String name, long delay)
{
this.name = name;
this.delay = delay;
setDaemon(true);
setName("BlockMadScane/RateThread/"+name);
}
public void run()
{
long transactions = 0;
long blocks = 0;
long last_run = System.currentTimeMillis();
DecimalFormat df =new DecimalFormat("0.000");
while(true)
{
try{Thread.sleep(delay);}catch(Exception e){}
long now = System.currentTimeMillis();
long blocks_now = blocks_scanned.get();
long transactions_now = transactions_scanned.get();
double sec = (now - last_run) / 1000.0;
double block_rate = (blocks_now - blocks) / sec;
double tx_rate = (transactions_now - transactions) / sec;
System.out.println(name + " Block rate: " + df.format(block_rate) + "/s Transaction rate: " + df.format(tx_rate) + "/s");
blocks = blocks_now;
transactions = transactions_now;
last_run= now;
}
}
}
}