Skip to content

Instantly share code, notes, and snippets.

@oscarguindzberg
Last active July 30, 2019 11:15
Show Gist options
  • Save oscarguindzberg/68e9526928d40545273d to your computer and use it in GitHub Desktop.
Save oscarguindzberg/68e9526928d40545273d to your computer and use it in GitHub Desktop.
Reference Miner for bitcoinj
public class MainClass {
public static void main(String[] args) throws Exception {
NetworkParameters params = ...
PeerGroup peers = ...
Wallet wallet = ...
FullPrunedBlockStore store = ...
AbstractBlockChain chain = ..,
Miner miner = new Miner(params, peers, wallet, store, chain);
miner.startAsync();
miner.awaitRunning();
new CountDownLatch(1).await();
}
}
public class Miner extends AbstractExecutionThreadService {
private static final Logger log = LoggerFactory.getLogger(Miner.class);
private NetworkParameters params;
private PeerGroup peers;
private Wallet wallet;
private FullPrunedBlockStore store;
private AbstractBlockChain chain;
private boolean newBestBlockArrivedFromAnotherNode = false;
public Miner(NetworkParameters params, PeerGroup peers, Wallet wallet, FullPrunedBlockStore store, AbstractBlockChain chain) {
this.params = params;
this.peers = peers;
this.wallet = wallet;
this.store = store;
this.chain = chain;
}
private class MinerBlockChainListener extends AbstractBlockChainListener {
@Override
public void notifyNewBestBlock(StoredBlock storedBlock) throws VerificationException {
handleNewBestBlock(storedBlock);
}
@Override
public void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks) throws VerificationException {
handleNewBestBlock(newBlocks.get(newBlocks.size()-1));
}
private void handleNewBestBlock(StoredBlock newBestStoredBlock) {
try {
boolean isMyBlock = false;
StoredUndoableBlock storedUndoableBlock = store.getUndoBlock(newBestStoredBlock.getHeader().getHash());
for (Transaction tx : storedUndoableBlock.getTransactions()) {
if (tx.isCoinBase() && tx.getOutput(0).isMine(wallet)) {
isMyBlock = true;
break;
}
}
if (!isMyBlock) {
log.info("Signaling mining to interrupt because this block arrived: " + newBestStoredBlock.getHeader().getHash());
newBestBlockArrivedFromAnotherNode = true;
}
} catch (BlockStoreException e) {
log.warn("Exception retrieving undoable block: " + newBestStoredBlock.getHeader().getHash(), e);
}
}
}
MinerBlockChainListener minerBlockChainListener = new MinerBlockChainListener();
@Override
protected void startUp() throws Exception {
super.startUp();
chain.addListener(minerBlockChainListener);
}
@Override
protected void shutDown() throws Exception {
super.shutDown();
chain.removeListener(minerBlockChainListener);
}
@Override
protected void run() throws Exception {
while (isRunning()) {
try {
//System.out.println("Press any key to mine 1 block...");
//System.in.read();
mine();
} catch (Exception e) {
log.error("Exception mining", e);
}
}
}
private void mine() throws Exception {
Transaction coinbaseTransaction = new Transaction(params);
String coibaseMessage = "Minining Bitcoin" + System.currentTimeMillis();
char[] chars = coibaseMessage.toCharArray();
byte[] bytes = new byte[chars.length];
for(int i=0;i<bytes.length;i++) bytes[i] = (byte) chars[i];
TransactionInput ti = new TransactionInput(params, coinbaseTransaction, bytes);
coinbaseTransaction.addInput(ti);
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
ECKey key = new ECKey();
wallet.addKey(key);
Script.writeBytes(scriptPubKeyBytes, key.getPubKey());
scriptPubKeyBytes.write(ScriptOpCodes.OP_CHECKSIG);
coinbaseTransaction.addOutput(new TransactionOutput(params, coinbaseTransaction, Utils.toNanoCoins(50, 0), scriptPubKeyBytes.toByteArray()));
StoredBlock prevBlock = null;
Block newBlock;
chain.getLock().lock();
try {
prevBlock = chain.getChainHead();
Sha256Hash prevBlockHash = prevBlock.getHeader().getHash();
long time = System.currentTimeMillis() / 1000;
long difficultyTarget = getDifficultyTargetForNewBlock(prevBlock, store, params, time);
newBlock = new Block(params, NetworkParameters.PROTOCOL_VERSION, prevBlockHash, time, difficultyTarget);
newBlock.addTransaction(coinbaseTransaction);
Set<Transaction> transactionsToInclude = getTransactionsToInclude(peers.getMemoryPool().getAll(), prevBlock.getHeight());
for (Transaction transaction : transactionsToInclude) {
newBlock.addTransaction(transaction);
}
log.info("Starting to mine block " + newBlock);
newBestBlockArrivedFromAnotherNode = false;
} finally {
chain.getLock().unlock();
}
while (!newBestBlockArrivedFromAnotherNode) {
try {
// Is our proof of work valid yet?
if (newBlock.checkProofOfWork(false))
break;
// No, so increment the nonce and try again.
newBlock.setNonce(newBlock.getNonce() + 1);
if (newBlock.getNonce() % 100000 == 0 ) {
log.info("Solving block. Nonce: " + newBlock.getNonce());
}
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
if (newBestBlockArrivedFromAnotherNode) {
log.info("Interrupted mining because another best block arrived");
return;
}
newBlock.verify();
log.info("Mined block: " + newBlock);
chain.add(newBlock);
log.info("Added mined block to blockchain: " + newBlock.getHash());
peers.broadcastMinedBlock(newBlock);
log.info("Sent mined block: " + newBlock.getHash());
}
private Set<Transaction> getTransactionsToInclude(Set<Transaction> allTransactions, int prevHeight) throws BlockStoreException {
checkState(chain.getLock().isHeldByCurrentThread());
Set<TransactionOutPoint> spentOutPointsInThisBlock = new HashSet<TransactionOutPoint>();
Set<Transaction> transactionsToInclude = new TreeSet<Transaction>(new TransactionPriorityComparator());
for (Transaction transaction : allTransactions) {
if (!store.hasUnspentOutputs(transaction.getHash(), transaction.getOutputs().size())) {
// Transaction was not already included in a block that is part of the best chain
boolean allOutPointsAreInTheBestChain = true;
boolean allOutPointsAreMature = true;
boolean doesNotDoubleSpend = true;
for (TransactionInput transactionInput : transaction.getInputs()) {
TransactionOutPoint outPoint = transactionInput.getOutpoint();
StoredTransactionOutput storedOutPoint = store.getTransactionOutput(outPoint.getHash(), outPoint.getIndex());
if (storedOutPoint == null) {
//Outpoint not in the best chain
allOutPointsAreInTheBestChain = false;
break;
}
if ((prevHeight+1) - storedOutPoint.getHeight() < params.getSpendableCoinbaseDepth()) {
//Outpoint is a non mature coinbase
allOutPointsAreMature = false;
break;
}
if (spentOutPointsInThisBlock.contains(outPoint)) {
doesNotDoubleSpend = false;
break;
} else {
spentOutPointsInThisBlock.add(outPoint);
}
}
if (allOutPointsAreInTheBestChain && allOutPointsAreMature && doesNotDoubleSpend) {
transactionsToInclude.add(transaction);
}
}
}
return ImmutableSet.copyOf(Iterables.limit(transactionsToInclude, 1000));
}
private static class TransactionPriorityComparator implements Comparator<Transaction>{
@Override
public int compare(Transaction tx1, Transaction tx2) {
int updateTimeComparison = tx1.getUpdateTime().compareTo(tx2.getUpdateTime());
//If time1==time2, compare by tx hash to make comparator consistent with equals
return updateTimeComparison!=0 ? updateTimeComparison : tx1.getHash().compareTo(tx2.getHash());
}
}
private long getDifficultyTargetForNewBlock(StoredBlock storedPrev, BlockStore blockStore, NetworkParameters params, long time) throws BlockStoreException {
if ((storedPrev.getHeight() + 1) % params.getInterval() != 0) {
return storedPrev.getHeader().getDifficultyTarget();
}
StoredBlock ancestorBlock = storedPrev;
for (int i = 0; i < params.getInterval() - 1; i++) {
if (ancestorBlock == null) {
// This should never happen. If it does, it means we are following an incorrect or busted chain.
throw new VerificationException(
"Difficulty transition point but we did not find a way back to the genesis block.");
}
ancestorBlock = blockStore.get(ancestorBlock.getHeader().getPrevBlockHash());
}
int timespan = (int) (storedPrev.getHeader().getTimeSeconds() - ancestorBlock.getHeader().getTimeSeconds());
log.debug("timespan: " + timespan);
// Limit the adjustment step.
final int targetTimespan = params.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newDifficulty = Utils.decodeCompactBits(storedPrev.getHeader().getDifficultyTarget());
newDifficulty = newDifficulty.multiply(BigInteger.valueOf(timespan));
newDifficulty = newDifficulty.divide(BigInteger.valueOf(targetTimespan));
if (newDifficulty.compareTo(params.getProofOfWorkLimit()) > 0) {
newDifficulty = params.getProofOfWorkLimit();
}
/*
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
BigInteger receivedDifficulty = nextBlock.getDifficultyTargetAsInteger();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newDifficulty = newDifficulty.and(mask);
*/
long newDifficultyCompact = Utils.encodeCompactBits(newDifficulty);
log.info("Difficulty changed to : " + Long.toHexString(newDifficultyCompact));
return newDifficultyCompact;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment