package com.google.devcoin.tools; import com.google.devcoin.core.*; import com.google.devcoin.params.MainNetParams; import com.google.devcoin.store.BlockStore; import com.google.devcoin.store.MemoryBlockStore; import com.google.devcoin.utils.BriefLogFormatter; import com.google.devcoin.utils.Threading; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.net.InetAddress; import java.nio.ByteBuffer; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.util.TreeMap; import java.util.Date; import static com.google.common.base.Preconditions.checkState; /** * Downloads and verifies a full chain from your local peer, emitting checkpoints at each difficulty transition period * to a file which is then signed with your key. */ public class BuildCheckpoints { public static void main(String[] args) throws Exception { BriefLogFormatter.init(); final NetworkParameters params = MainNetParams.get(); // Sorted map of UNIX time of block to StoredBlock object. final TreeMap<Integer, StoredBlock> checkpoints = new TreeMap<Integer, StoredBlock>(); // Configure bitcoinj to fetch only headers, not save them to disk, connect to a local fully synced/validated // node and to save block headers that are on interval boundaries, as long as they are <1 month old. final BlockStore store = new MemoryBlockStore(params); final BlockChain chain = new BlockChain(params, store); final PeerGroup peerGroup = new PeerGroup(params, chain); peerGroup.addAddress(InetAddress.getLocalHost()); long now = new Date().getTime() / 1000; peerGroup.setFastCatchupTimeSecs(now); final long oneMonthAgo = now - (86400 * 30); chain.addListener(new AbstractBlockChainListener() { @Override public void notifyNewBestBlock(StoredBlock block) throws VerificationException { int height = block.getHeight(); if ((height % params.getInterval() == 0) && block.getHeader().getTimeSeconds() < oneMonthAgo) { System.out.println(String.format("Checkpointing block %s at height %d", block.getHeader().getHash(),block.getHeight())); checkpoints.put(height, block); } } }, Threading.SAME_THREAD); peerGroup.startAndWait(); peerGroup.downloadBlockChain(); checkState(checkpoints.size() > 0); // Write checkpoint data out. final FileOutputStream fileOutputStream = new FileOutputStream("checkpoints", false); MessageDigest digest = MessageDigest.getInstance("SHA-256"); final DigestOutputStream digestOutputStream = new DigestOutputStream(fileOutputStream, digest); digestOutputStream.on(false); final DataOutputStream dataOutputStream = new DataOutputStream(digestOutputStream); dataOutputStream.writeBytes("CHECKPOINTS 1"); dataOutputStream.writeInt(0); // Number of signatures to read. Do this later. digestOutputStream.on(true); dataOutputStream.writeInt(checkpoints.size()); ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); for (StoredBlock block : checkpoints.values()) { block.serializeCompact(buffer); dataOutputStream.write(buffer.array()); buffer.position(0); } dataOutputStream.close(); Sha256Hash checkpointsHash = new Sha256Hash(digest.digest()); System.out.println("Hash of checkpoints data is " + checkpointsHash); digestOutputStream.close(); fileOutputStream.close(); peerGroup.stopAndWait(); store.close(); // Sanity check the created file. CheckpointManager manager = new CheckpointManager(params, new FileInputStream("checkpoints")); checkState(manager.numCheckpoints() == checkpoints.size()); StoredBlock test = manager.getCheckpointBefore(1375479729); // block 100,000 checkState(test.getHeight() == 98784); checkState(test.getHeader().getHashAsString().equals("c5c39af1011f455c8d181f7bb40cc37ed3c3f6c1335d508ed0568f1d4fe05d56")); } }