/* * Copyright 2013 Google Inc. * Copyright 2014 Andreas Schildbach * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.matthewmitchell.nubitsj.tools; import com.matthewmitchell.nubitsj.core.*; import com.matthewmitchell.nubitsj.params.MainNetParams; import com.matthewmitchell.nubitsj.store.BlockStore; import com.matthewmitchell.nubitsj.store.MemoryBlockStore; import com.matthewmitchell.nubitsj.utils.BriefLogFormatter; import com.matthewmitchell.nubitsj.utils.Threading; import com.google.common.base.Charsets; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.InetAddress; import java.nio.ByteBuffer; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.util.Date; import java.util.TreeMap; 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 { private static final NetworkParameters PARAMS = MainNetParams.get(); private static final File PLAIN_CHECKPOINTS_FILE = new File("checkpoints"); private static final File TEXTUAL_CHECKPOINTS_FILE = new File("checkpoints.txt"); public static void main(String[] args) throws Exception { BriefLogFormatter.initWithSilentNubitsJ(); // Sorted map of block height to StoredBlock object. final TreeMap<Integer, StoredBlock> checkpoints = new TreeMap<Integer, StoredBlock>(); // Configure nubitsj 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); final InetAddress peerAddress = InetAddress.getLocalHost(); System.out.println("Connecting to " + peerAddress + "..."); peerGroup.addAddress(peerAddress); 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.start(); peerGroup.downloadBlockChain(); checkState(checkpoints.size() > 0); // Write checkpoint data out. writeBinaryCheckpoints(checkpoints, PLAIN_CHECKPOINTS_FILE); writeTextualCheckpoints(checkpoints, TEXTUAL_CHECKPOINTS_FILE); peerGroup.stop(); store.close(); // Sanity check the created files. sanityCheck(PLAIN_CHECKPOINTS_FILE, checkpoints.size()); sanityCheck(TEXTUAL_CHECKPOINTS_FILE, checkpoints.size()); } private static void writeBinaryCheckpoints(TreeMap<Integer, StoredBlock> checkpoints, File file) throws Exception { final FileOutputStream fileOutputStream = new FileOutputStream(file, false); MessageDigest digest = Sha256Hash.newDigest(); 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 = Sha256Hash.wrap(digest.digest()); System.out.println("Hash of checkpoints data is " + checkpointsHash); digestOutputStream.close(); fileOutputStream.close(); System.out.println("Checkpoints written to '" + file.getCanonicalPath() + "'."); } private static void writeTextualCheckpoints(TreeMap<Integer, StoredBlock> checkpoints, File file) throws IOException { PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.US_ASCII)); writer.println("TXT CHECKPOINTS 1"); writer.println("0"); // Number of signatures to read. Do this later. writer.println(checkpoints.size()); ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); for (StoredBlock block : checkpoints.values()) { block.serializeCompact(buffer); writer.println(CheckpointManager.BASE64.encode(buffer.array())); buffer.position(0); } writer.close(); System.out.println("Checkpoints written to '" + file.getCanonicalPath() + "'."); } private static void sanityCheck(File file, int expectedSize) throws IOException { CheckpointManager manager = new CheckpointManager(PARAMS, new FileInputStream(file)); checkState(manager.numCheckpoints() == expectedSize); if (PARAMS.getId().equals(NetworkParameters.ID_MAINNET)) { StoredBlock test = manager.getCheckpointBefore(1390500000); // Thu Jan 23 19:00:00 CET 2014 checkState(test.getHeight() == 280224); checkState(test.getHeader().getHashAsString() .equals("00000000000000000b5d59a15f831e1c45cb688a4db6b0a60054d49a9997fa34")); } else if (PARAMS.getId().equals(NetworkParameters.ID_TESTNET)) { StoredBlock test = manager.getCheckpointBefore(1390500000); // Thu Jan 23 19:00:00 CET 2014 checkState(test.getHeight() == 167328); checkState(test.getHeader().getHashAsString() .equals("0000000000035ae7d5025c2538067fe7adb1cf5d5d9c31b024137d9090ed13a9")); } } }