package network.thunder.core.communication.objects.messages.impl.blockchainlistener;
import network.thunder.core.communication.objects.messages.impl.blockchainlistener.bciapi.BlockExplorer;
import network.thunder.core.communication.objects.messages.interfaces.helper.BlockchainHelper;
import network.thunder.core.communication.objects.messages.interfaces.helper.etc.OnBlockCommand;
import network.thunder.core.communication.objects.messages.interfaces.helper.etc.OnTxCommand;
import network.thunder.core.etc.Constants;
import org.bitcoinj.core.*;
import org.bitcoinj.net.discovery.DnsDiscovery;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.store.SPVBlockStore;
import org.bitcoinj.utils.Threading;
import javax.annotation.Nullable;
import java.io.File;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* BlockchainHelper based upon connecting directly to p2p network with bitcoinJ.
* Using BC.i for getTransaction, as this functionality is not generally accessible normally.
* <p>
* Would be great to have another implementation in the future that only connects to a local bitcoind,
* to greatly improve privacy and reduce dependency on third parties.
*/
public class BlockchainHelperImpl implements BlockchainHelper {
PeerGroup peerGroup;
BlockStore blockStore;
BlockChain blockChain;
Set<Sha256Hash> processedMessages = new HashSet<>();
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(8, 8, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
List<OnBlockCommand> blockListener = Collections.synchronizedList(new ArrayList<>());
List<OnTxCommand> txListener = Collections.synchronizedList(new ArrayList<>());
BlockExplorer blockExplorer = new BlockExplorer();
Boolean initialized = new Boolean(false);
public BlockchainHelperImpl () {
init();
}
@Override
public boolean broadcastTransaction (Transaction tx) {
try {
TransactionBroadcast broadcast = peerGroup.broadcastTransaction(tx);
broadcast.future().get(10, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@Override
public void addTxListener (OnTxCommand executor) {
txListener.add(executor);
}
@Override
public void addBlockListener (OnBlockCommand executor) {
blockListener.add(executor);
}
@Override
public Transaction getTransaction (Sha256Hash hash) {
try {
Transaction transaction = blockExplorer.getBitcoinJTransaction(hash.toString());
double height = blockExplorer.getTransaction(hash.toString()).getBlockHeight();
if (height > 0) {
transaction.getConfidence().setDepthInBlocks((int) (blockChain.getChainHead().getHeight() - height));
}
//TODO maybe get the number of confirmations in here somehow
return transaction;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void init () {
synchronized (initialized) {
if (!initialized) {
try {
blockStore = new SPVBlockStore(Constants.getNetwork(), new File("blockheaders"));
} catch (Exception e) {
blockStore = new MemoryBlockStore(Constants.getNetwork());
}
try {
blockChain = new BlockChain(Constants.getNetwork(), blockStore);
peerGroup = new PeerGroup(Constants.getNetwork(), blockChain);
peerGroup.addPeerDiscovery(new DnsDiscovery(Constants.getNetwork()));
peerGroup.setDownloadTxDependencies(false);
peerGroup.setBloomFilteringEnabled(false);
peerGroup.setFastCatchupTimeSecs(System.currentTimeMillis());
peerGroup.start();
peerGroup.addEventListener(new EventListener(), Threading.SAME_THREAD);
registerShutdownHook();
System.out.println("Download BlockHeaders..");
final DownloadProgressTracker listener = new DownloadProgressTracker();
peerGroup.startBlockChainDownload(listener);
listener.await();
System.out.println("Download BlockHeaders done..");
} catch (Exception e) {
throw new RuntimeException(e);
}
initialized = true;
}
}
}
private void registerShutdownHook () {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
shutdown();
} catch (BlockStoreException e) {
e.printStackTrace();
}
}));
}
public void shutdown () throws BlockStoreException {
peerGroup.stop();
blockStore.close();
}
class EventListener implements PeerEventListener {
@Override
public void onPeersDiscovered (Set<PeerAddress> peerAddresses) {
}
@Override
public void onBlocksDownloaded (Peer peer, Block block, @Nullable FilteredBlock filteredBlock, int blocksLeft) {
}
@Override
public void onChainDownloadStarted (Peer peer, int blocksLeft) {
}
@Override
public void onPeerConnected (Peer peer, int peerCount) {
}
@Override
public void onPeerDisconnected (Peer peer, int peerCount) {
}
@Override
public Message onPreMessageReceived (Peer peer, Message m) {
if (m instanceof Block || m instanceof Transaction) {
if (processedMessages.add(m.getHash())) {
poolExecutor.submit((Runnable) () -> {
if (m instanceof Block) {
Iterator<OnBlockCommand> iterator = blockListener.iterator();
while (iterator.hasNext()) {
OnBlockCommand onBlockCommand = iterator.next();
if (onBlockCommand.execute((Block) m)) {
iterator.remove();
}
}
} else {
Transaction transaction = (Transaction) m;
Iterator<OnTxCommand> iterator = txListener.iterator();
while (iterator.hasNext()) {
OnTxCommand onTxCommand = iterator.next();
if (onTxCommand.compare(transaction)) {
onTxCommand.execute(transaction);
iterator.remove();
}
}
}
});
}
}
return m;
}
@Override
public void onTransaction (Peer peer, Transaction t) {
}
@Nullable
@Override
public List<Message> getData (Peer peer, GetDataMessage m) {
return null;
}
}
}