Пример #1
0
  private synchronized boolean add(Block block, boolean tryConnecting)
      throws BlockStoreException, VerificationException, ScriptException {
    if (System.currentTimeMillis() - statsLastTime > 1000) {
      // More than a second passed since last stats logging.
      log.info("{} blocks per second", statsBlocksAdded);
      statsLastTime = System.currentTimeMillis();
      statsBlocksAdded = 0;
    }
    // We check only the chain head for double adds here to avoid potentially expensive block chain
    // misses.
    if (block.equals(chainHead.getHeader())) {
      // Duplicate add of the block at the top of the chain, can be a natural artifact of the
      // download process.
      return true;
    }

    // Prove the block is internally valid: hash is lower than target, merkle root is correct and so
    // on.
    try {
      block.verify();
    } catch (VerificationException e) {
      log.error("Failed to verify block:", e);
      log.error(block.toString());
      throw e;
    }

    // Try linking it to a place in the currently known blocks.
    StoredBlock storedPrev = blockStore.get(block.getPrevBlockHash());

    if (storedPrev == null) {
      // We can't find the previous block. Probably we are still in the process of downloading the
      // chain and a
      // block was solved whilst we were doing it. We put it to one side and try to connect it later
      // when we
      // have more blocks.
      log.warn("Block does not connect: {}", block.getHashAsString());
      unconnectedBlocks.add(block);
      return false;
    } else {
      // It connects to somewhere on the chain. Not necessarily the top of the best known chain.
      //
      // Create a new StoredBlock from this block. It will throw away the transaction data so when
      // block goes
      // out of scope we will reclaim the used memory.
      StoredBlock newStoredBlock = storedPrev.build(block);
      checkDifficultyTransitions(storedPrev, newStoredBlock);
      blockStore.put(newStoredBlock);
      // block.transactions may be null here if we received only a header and not a full block. This
      // does not
      // happen currently but might in future if getheaders is implemented.
      connectBlock(newStoredBlock, storedPrev, block.transactions);
    }

    if (tryConnecting) tryConnectingUnconnected();

    statsBlocksAdded++;
    return true;
  }
Пример #2
0
 public void flush() {
   Iterator<BlockPayload> iterator = dirty.values().iterator();
   while (iterator.hasNext()) {
     BlockPayload block = iterator.next();
     iterator.remove();
     store.write(block);
   }
   store.flush();
 }
Пример #3
0
 /**
  * For each block in unconnectedBlocks, see if we can now fit it on top of the chain and if so, do
  * so.
  */
 private void tryConnectingUnconnected()
     throws VerificationException, ScriptException, BlockStoreException {
   // For each block in our unconnected list, try and fit it onto the head of the chain. If we
   // succeed remove it
   // from the list and keep going. If we changed the head of the list at the end of the round try
   // again until
   // we can't fit anything else on the top.
   int blocksConnectedThisRound;
   do {
     blocksConnectedThisRound = 0;
     Iterator<Block> iter = unconnectedBlocks.iterator();
     while (iter.hasNext()) {
       Block block = iter.next();
       // Look up the blocks previous.
       StoredBlock prev = blockStore.get(block.getPrevBlockHash());
       if (prev == null) {
         // This is still an unconnected/orphan block.
         continue;
       }
       // Otherwise we can connect it now.
       // False here ensures we don't recurse infinitely downwards when connecting huge chains.
       add(block, false);
       iter.remove();
       blocksConnectedThisRound++;
     }
     if (blocksConnectedThisRound > 0) {
       log.info("Connected {} floating blocks.", blocksConnectedThisRound);
     }
   } while (blocksConnectedThisRound > 0);
 }
Пример #4
0
 private void setChainHead(StoredBlock chainHead) {
   this.chainHead = chainHead;
   try {
     blockStore.setChainHead(chainHead);
   } catch (BlockStoreException e) {
     throw new RuntimeException(e);
   }
 }
Пример #5
0
  /**
   * Constructs a BlockChain connected to the given wallet and store. To obtain a {@link Wallet} you
   * can construct one from scratch, or you can deserialize a saved wallet from disk using {@link
   * Wallet#loadFromFile(java.io.File)}
   *
   * <p>For the store you can use a {@link MemoryBlockStore} if you don't care about saving the
   * downloaded data, or a {@link BoundedOverheadBlockStore} if you'd like to ensure fast startup
   * the next time you run the program.
   */
  public BlockChain(NetworkParameters params, Wallet wallet, BlockStore blockStore) {
    try {
      this.blockStore = blockStore;
      chainHead = blockStore.getChainHead();
      log.info("chain head is:\n{}", chainHead.getHeader());
    } catch (BlockStoreException e) {
      throw new RuntimeException(e);
    }

    this.params = params;
    this.wallet = wallet;
  }
Пример #6
0
 public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
   T block = payloadType.cast(dirty.get(pos));
   if (block != null) {
     return block;
   }
   block = payloadType.cast(indexBlockCache.get(pos));
   if (block != null) {
     return block;
   }
   block = store.read(pos, payloadType);
   maybeCache(block);
   return block;
 }
Пример #7
0
 public void write(BlockPayload block) {
   store.attach(block);
   maybeCache(block);
   dirty.put(block.getPos(), block);
 }
Пример #8
0
 public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
   T block = store.readFirst(payloadType);
   maybeCache(block);
   return block;
 }
Пример #9
0
 public void remove(BlockPayload block) {
   dirty.remove(block.getPos());
   indexBlockCache.remove(block.getPos());
   store.remove(block);
 }
Пример #10
0
 public void attach(BlockPayload block) {
   store.attach(block);
 }
Пример #11
0
 public void clear() {
   dirty.clear();
   indexBlockCache.clear();
   store.clear();
 }
Пример #12
0
 public void close() {
   flush();
   indexBlockCache.clear();
   store.close();
 }
Пример #13
0
 public void open(Runnable initAction, Factory factory) {
   store.open(initAction, factory);
 }
Пример #14
0
  /** Throws an exception if the blocks difficulty is not correct. */
  private void checkDifficultyTransitions(StoredBlock storedPrev, StoredBlock storedNext)
      throws BlockStoreException, VerificationException {
    Block prev = storedPrev.getHeader();
    Block next = storedNext.getHeader();
    // Is this supposed to be a difficulty transition point?
    if ((storedPrev.getHeight() + 1) % params.interval != 0) {
      // No ... so check the difficulty didn't actually change.
      if (next.getDifficultyTarget() != prev.getDifficultyTarget())
        throw new VerificationException(
            "Unexpected change in difficulty at height "
                + storedPrev.getHeight()
                + ": "
                + Long.toHexString(next.getDifficultyTarget())
                + " vs "
                + Long.toHexString(prev.getDifficultyTarget()));
      return;
    }

    // We need to find a block far back in the chain. It's OK that this is expensive because it only
    // occurs every
    // two weeks after the initial block chain download.
    long now = System.currentTimeMillis();
    StoredBlock cursor = blockStore.get(prev.getHash());
    for (int i = 0; i < params.interval - 1; i++) {
      if (cursor == 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.");
      }
      cursor = blockStore.get(cursor.getHeader().getPrevBlockHash());
    }
    log.info("Difficulty transition traversal took {}msec", System.currentTimeMillis() - now);

    Block blockIntervalAgo = cursor.getHeader();
    int timespan = (int) (prev.getTime() - blockIntervalAgo.getTime());
    // Limit the adjustment step.
    if (timespan < params.targetTimespan / 4) timespan = params.targetTimespan / 4;
    if (timespan > params.targetTimespan * 4) timespan = params.targetTimespan * 4;

    BigInteger newDifficulty = Utils.decodeCompactBits(blockIntervalAgo.getDifficultyTarget());
    newDifficulty = newDifficulty.multiply(BigInteger.valueOf(timespan));
    newDifficulty = newDifficulty.divide(BigInteger.valueOf(params.targetTimespan));

    if (newDifficulty.compareTo(params.proofOfWorkLimit) > 0) {
      log.warn("Difficulty hit proof of work limit: {}", newDifficulty.toString(16));
      newDifficulty = params.proofOfWorkLimit;
    }

    int accuracyBytes = (int) (next.getDifficultyTarget() >>> 24) - 3;
    BigInteger receivedDifficulty = next.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);

    if (newDifficulty.compareTo(receivedDifficulty) != 0)
      throw new VerificationException(
          "Network provided difficulty bits do not match what was calculated: "
              + receivedDifficulty.toString(16)
              + " vs "
              + newDifficulty.toString(16));
  }