下面列出了怎么用org.bitcoinj.core.VerificationException的API类实例代码及写法,或者点击链接到github查看源代码。
/**
* Returns a decoded signature.
*
* @param requireCanonicalEncoding if the encoding of the signature must
* be canonical.
* @param requireCanonicalSValue if the S-value must be canonical (below half
* the order of the curve).
* @throws RuntimeException if the signature is invalid or unparseable in some way.
*/
public static TransactionSignature decodeFromBitcoin(byte[] bytes,
boolean requireCanonicalEncoding,
boolean requireCanonicalSValue) throws VerificationException {
// Bitcoin encoding is DER signature + sighash byte.
if (requireCanonicalEncoding && !isEncodingCanonical(bytes))
throw new VerificationException("Signature encoding is not canonical.");
ECKey.ECDSASignature sig;
try {
sig = ECKey.ECDSASignature.decodeFromDER(bytes);
} catch (IllegalArgumentException e) {
throw new VerificationException("Could not decode DER", e);
}
if (requireCanonicalSValue && !sig.isCanonical())
throw new VerificationException("S-value is not canonical.");
// In Bitcoin, any value of the final byte is valid, but not necessarily canonical. See javadocs for
// isEncodingCanonical to learn more about this. So we must store the exact byte found.
return new TransactionSignature(sig.r, sig.s, bytes[bytes.length - 1]);
}
private void createNewStore(NetworkParameters params) throws BlockStoreException {
try {
// Set up the genesis block. When we start out fresh, it is by
// definition the top of the chain.
StoredBlock storedGenesisHeader = new StoredBlock(params.getGenesisBlock().cloneAsHeader(),
params.getGenesisBlock().getWork(), 0);
// The coinbase in the genesis block is not spendable. This is
// because of how the reference client inits
// its database - the genesis transaction isn't actually in the db
// so its spent flags can never be updated.
List<Transaction> genesisTransactions = Lists.newLinkedList();
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.getGenesisBlock().getHash(),
genesisTransactions);
beginDatabaseBatchWrite();
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);
setVerifiedChainHead(storedGenesisHeader);
batchPut(getKey(KeyType.CREATED), bytes("done"));
commitDatabaseBatchWrite();
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
/**
* Returns a decoded signature.
*
* @param requireCanonicalEncoding if the encoding of the signature must
* be canonical.
* @param requireCanonicalSValue if the S-value must be canonical (below half
* the order of the curve).
* @throws RuntimeException if the signature is invalid or unparseable in some way.
*/
public static TransactionSignature decodeFromBitcoin(byte[] bytes,
boolean requireCanonicalEncoding,
boolean requireCanonicalSValue) throws VerificationException {
// Bitcoin encoding is DER signature + sighash byte.
if (requireCanonicalEncoding && !isEncodingCanonical(bytes))
throw new VerificationException("Signature encoding is not canonical.");
ECKey.ECDSASignature sig;
try {
sig = ECKey.ECDSASignature.decodeFromDER(bytes);
} catch (IllegalArgumentException e) {
throw new VerificationException("Could not decode DER", e);
}
if (requireCanonicalSValue && !sig.isCanonical())
throw new VerificationException("S-value is not canonical.");
// In Bitcoin, any value of the final byte is valid, but not necessarily canonical. See javadocs for
// isEncodingCanonical to learn more about this. So we must store the exact byte found.
return new TransactionSignature(sig.r, sig.s, bytes[bytes.length - 1]);
}
private void createNewStore(NetworkParameters params) throws BlockStoreException {
try {
// Set up the genesis block. When we start out fresh, it is by
// definition the top of the chain.
StoredBlock storedGenesisHeader = new StoredBlock(params.getGenesisBlock().cloneAsHeader(),
params.getGenesisBlock().getWork(), 0);
// The coinbase in the genesis block is not spendable. This is
// because of how the reference client inits
// its database - the genesis transaction isn't actually in the db
// so its spent flags can never be updated.
List<Transaction> genesisTransactions = Lists.newLinkedList();
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.getGenesisBlock().getHash(),
genesisTransactions);
beginDatabaseBatchWrite();
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);
setVerifiedChainHead(storedGenesisHeader);
batchPut(getKey(KeyType.CREATED), bytes("done"));
commitDatabaseBatchWrite();
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
/**
* Returns a decoded signature.
*
* @param requireCanonicalEncoding if the encoding of the signature must
* be canonical.
* @param requireCanonicalSValue if the S-value must be canonical (below half
* the order of the curve).
* @throws RuntimeException if the signature is invalid or unparseable in some way.
*/
public static TransactionSignature decodeFromBitcoin(byte[] bytes,
boolean requireCanonicalEncoding,
boolean requireCanonicalSValue) throws VerificationException {
// Bitcoin encoding is DER signature + sighash byte.
if (requireCanonicalEncoding && !isEncodingCanonical(bytes))
throw new VerificationException("Signature encoding is not canonical.");
ECKey.ECDSASignature sig;
try {
sig = ECKey.ECDSASignature.decodeFromDER(bytes);
} catch (IllegalArgumentException e) {
throw new VerificationException("Could not decode DER", e);
}
if (requireCanonicalSValue && !sig.isCanonical())
throw new VerificationException("S-value is not canonical.");
// In Bitcoin, any value of the final byte is valid, but not necessarily canonical. See javadocs for
// isEncodingCanonical to learn more about this. So we must store the exact byte found.
return new TransactionSignature(sig.r, sig.s, bytes[bytes.length - 1]);
}
private void createNewStore(NetworkParameters params) throws BlockStoreException {
try {
// Set up the genesis block. When we start out fresh, it is by
// definition the top of the chain.
StoredBlock storedGenesisHeader = new StoredBlock(params.getGenesisBlock().cloneAsHeader(),
params.getGenesisBlock().getWork(), 0);
// The coinbase in the genesis block is not spendable. This is
// because of how the reference client inits
// its database - the genesis transaction isn't actually in the db
// so its spent flags can never be updated.
List<Transaction> genesisTransactions = Lists.newLinkedList();
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.getGenesisBlock().getHash(),
genesisTransactions);
beginDatabaseBatchWrite();
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);
setVerifiedChainHead(storedGenesisHeader);
batchPut(getKey(KeyType.CREATED), bytes("done"));
commitDatabaseBatchWrite();
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
/**
* @param serializedTransaction The serialized transaction to be added to the wallet
* @return The transaction we added to the wallet, which is different as the one we passed as argument!
* @throws VerificationException
*/
public static Transaction maybeAddTxToWallet(byte[] serializedTransaction,
Wallet wallet,
TransactionConfidence.Source source) throws VerificationException {
Transaction tx = new Transaction(wallet.getParams(), serializedTransaction);
Transaction walletTransaction = wallet.getTransaction(tx.getHash());
if (walletTransaction == null) {
// We need to recreate the transaction otherwise we get a null pointer...
tx.getConfidence(Context.get()).setSource(source);
//wallet.maybeCommitTx(tx);
wallet.receivePending(tx, null, true);
return tx;
} else {
return walletTransaction;
}
}
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
if (!isDifficultyTransitionPoint(storedPrev.getHeight()) && nextBlock.getTime().after(testnetDiffDate)) {
Block prev = storedPrev.getHeader();
// After 15th February 2012 the rules on the testnet change to avoid people running up the difficulty
// and then leaving, making it too hard to mine a block. On non-difficulty transition points, easy
// blocks are allowed if there has been a span of 20 minutes without one.
final long timeDelta = nextBlock.getTimeSeconds() - prev.getTimeSeconds();
// There is an integer underflow bug in bitcoin-qt that means mindiff blocks are accepted when time
// goes backwards.
if (timeDelta >= 0 && timeDelta <= NetworkParameters.TARGET_SPACING * 2) {
// Walk backwards until we find a block that doesn't have the easiest proof of work, then check
// that difficulty is equal to that one.
StoredBlock cursor = storedPrev;
while (!cursor.getHeader().equals(getGenesisBlock()) &&
cursor.getHeight() % getInterval() != 0 &&
cursor.getHeader().getDifficultyTargetAsInteger().equals(getMaxTarget()))
cursor = cursor.getPrev(blockStore);
BigInteger cursorTarget = cursor.getHeader().getDifficultyTargetAsInteger();
BigInteger newTarget = nextBlock.getDifficultyTargetAsInteger();
if (!cursorTarget.equals(newTarget))
throw new VerificationException("Testnet block transition that is not allowed: " +
Long.toHexString(cursor.getHeader().getDifficultyTarget()) + " vs " +
Long.toHexString(nextBlock.getDifficultyTarget()));
}
} else {
super.checkDifficultyTransitions(storedPrev, nextBlock, blockStore);
}
}
private void basicSanityChecks(Wallet wallet, Transaction t, Address destination) throws VerificationException {
assertEquals("Wrong number of tx inputs", 1, t.getInputs().size());
assertEquals("Wrong number of tx outputs", 2, t.getOutputs().size());
assertEquals(destination, t.getOutput(0).getScriptPubKey().getToAddress(UNITTEST));
assertEquals(wallet.currentChangeAddress(), t.getOutputs().get(1).getScriptPubKey().getToAddress(UNITTEST));
assertEquals(valueOf(0, 50), t.getOutputs().get(1).getValue());
// Check the script runs and signatures verify.
t.getInputs().get(0).verify();
}
/**
* @param transaction The transaction to be added to the wallet
* @return The transaction we added to the wallet, which is different as the one we passed as argument!
* @throws VerificationException
*/
public Transaction addTxToWallet(Transaction transaction) throws VerificationException {
Log.traceCall("transaction " + transaction.toString());
// We need to recreate the transaction otherwise we get a null pointer...
Transaction result = new Transaction(params, transaction.bitcoinSerialize());
result.getConfidence(Context.get()).setSource(TransactionConfidence.Source.SELF);
if (wallet != null)
wallet.receivePending(result, null, true);
return result;
}
/**
* @param serializedTransaction The serialized transaction to be added to the wallet
* @return The transaction we added to the wallet, which is different as the one we passed as argument!
* @throws VerificationException
*/
public Transaction addTxToWallet(byte[] serializedTransaction) throws VerificationException {
Log.traceCall();
// We need to recreate the tx otherwise we get a null pointer...
Transaction transaction = new Transaction(params, serializedTransaction);
transaction.getConfidence(Context.get()).setSource(TransactionConfidence.Source.NETWORK);
log.trace("transaction from serializedTransaction: " + transaction.toString());
if (wallet != null)
wallet.receivePending(transaction, null, true);
return transaction;
}
@Override
public void receiveFromBlock(final Transaction tx, final StoredBlock block,
final BlockChain.NewBlockType blockType,
final int relativityOffset) throws VerificationException {
if (tx == null)
throw new RuntimeException("receiveFromBlock got null tx");
Log.d(TAG, "receiveFromBlock " + tx.getHash().toString());
addUtxoToValues(tx.getHash(), true);
}
@Override
public boolean notifyTransactionIsInBlock(final Sha256Hash txHash, final StoredBlock block,
final BlockChain.NewBlockType blockType,
final int relativityOffset) throws VerificationException {
Log.d(TAG, "notifyTransactionIsInBlock " + txHash.toString());
return isUnspentOutpoint(txHash);
}
private void basicSanityChecks(Wallet wallet, Transaction t, Address destination) throws VerificationException {
assertEquals("Wrong number of tx inputs", 1, t.getInputs().size());
assertEquals("Wrong number of tx outputs",2, t.getOutputs().size());
assertEquals(destination, t.getOutput(0).getScriptPubKey().getToAddress(PARAMS));
assertEquals(wallet.currentChangeAddress(), t.getOutputs().get(1).getScriptPubKey().getToAddress(PARAMS));
assertEquals(valueOf(0, 50), t.getOutputs().get(1).getValue());
// Check the script runs and signatures verify.
t.getInputs().get(0).verify();
}
@Override
public void receiveFromBlock(final Transaction tx, final StoredBlock block,
final BlockChain.NewBlockType blockType,
final int relativityOffset) throws VerificationException {
if (tx == null)
throw new RuntimeException("receiveFromBlock got null tx");
getService().notifyObservers(tx.getHash());
}
private void basicSanityChecks(Wallet wallet, Transaction t, Address destination) throws VerificationException {
assertEquals("Wrong number of tx inputs", 1, t.getInputs().size());
assertEquals("Wrong number of tx outputs",2, t.getOutputs().size());
assertEquals(destination, t.getOutput(0).getScriptPubKey().getToAddress(PARAMS));
assertEquals(wallet.currentChangeAddress(), t.getOutputs().get(1).getScriptPubKey().getToAddress(PARAMS));
assertEquals(valueOf(0, 50), t.getOutputs().get(1).getValue());
// Check the script runs and signatures verify.
t.getInputs().get(0).verify();
}
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
final Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point?
if (!isDifficultyTransitionPoint(storedPrev.getHeight())) {
// No ... so check the difficulty didn't actually change.
if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() +
": " + Long.toHexString(nextBlock.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.
final Stopwatch watch = Stopwatch.createStarted();
Sha256Hash hash = prev.getHash();
StoredBlock cursor = null;
final int interval = this.getInterval();
for (int i = 0; i < interval; i++) {
cursor = blockStore.get(hash);
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 last transition point. Not found: " + hash);
}
hash = cursor.getHeader().getPrevBlockHash();
}
checkState(cursor != null && isDifficultyTransitionPoint(cursor.getHeight() - 1),
"Didn't arrive at a transition point.");
watch.stop();
if (watch.elapsed(TimeUnit.MILLISECONDS) > 50)
log.info("Difficulty transition traversal took {}", watch);
Block blockIntervalAgo = cursor.getHeader();
int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
// Limit the adjustment step.
final int targetTimespan = this.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
long receivedTargetCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
long newTargetCompact = Utils.encodeCompactBits(newTarget);
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact));
}
@Nullable
protected Transaction sendMoneyToWallet(Wallet wallet, AbstractBlockChain.NewBlockType type, Coin value, Address toAddress) throws VerificationException {
return sendMoneyToWallet(wallet, type, createFakeTx(UNITTEST, value, toAddress));
}
@Nullable
protected Transaction sendMoneyToWallet(Wallet wallet, AbstractBlockChain.NewBlockType type, Coin value, ECKey toPubKey) throws VerificationException {
return sendMoneyToWallet(wallet, type, createFakeTx(UNITTEST, value, toPubKey));
}
@Nullable
protected Transaction sendMoneyToWallet(AbstractBlockChain.NewBlockType type, Transaction... transactions) throws VerificationException {
return sendMoneyToWallet(this.wallet, type, transactions);
}
@Nullable
protected Transaction sendMoneyToWallet(AbstractBlockChain.NewBlockType type, Coin value) throws VerificationException {
return sendMoneyToWallet(this.wallet, type, value, myAddress);
}
@Nullable
protected Transaction sendMoneyToWallet(AbstractBlockChain.NewBlockType type, Coin value, Address toAddress) throws VerificationException {
return sendMoneyToWallet(this.wallet, type, value, toAddress);
}
@Nullable
protected Transaction sendMoneyToWallet(AbstractBlockChain.NewBlockType type, Coin value, ECKey toPubKey) throws VerificationException {
return sendMoneyToWallet(this.wallet, type, value, toPubKey);
}
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point?
if (!isDifficultyTransitionPoint(storedPrev)) {
// No ... so check the difficulty didn't actually change.
if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() +
": " + Long.toHexString(nextBlock.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.
final Stopwatch watch = Stopwatch.createStarted();
StoredBlock cursor = blockStore.get(prev.getHash());
for (int i = 0; i < this.getInterval() - 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());
}
watch.stop();
if (watch.elapsed(TimeUnit.MILLISECONDS) > 50)
log.info("Difficulty transition traversal took {}", watch);
Block blockIntervalAgo = cursor.getHeader();
int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
// Limit the adjustment step.
final int targetTimespan = this.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
long receivedTargetCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
long newTargetCompact = Utils.encodeCompactBits(newTarget);
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact));
}
@Override
public void checkDifficultyTransitions(StoredBlock storedPrev, Block next, BlockStore blockStore)
throws VerificationException {
}
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
final Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point?
if (!isDifficultyTransitionPoint(storedPrev.getHeight())) {
// No ... so check the difficulty didn't actually change.
if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() +
": " + Long.toHexString(nextBlock.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.
final Stopwatch watch = Stopwatch.createStarted();
Sha256Hash hash = prev.getHash();
StoredBlock cursor = null;
final int interval = this.getInterval();
for (int i = 0; i < interval; i++) {
cursor = blockStore.get(hash);
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 last transition point. Not found: " + hash);
}
hash = cursor.getHeader().getPrevBlockHash();
}
checkState(cursor != null && isDifficultyTransitionPoint(cursor.getHeight() - 1),
"Didn't arrive at a transition point.");
watch.stop();
if (watch.elapsed(TimeUnit.MILLISECONDS) > 50)
log.info("Difficulty transition traversal took {}", watch);
Block blockIntervalAgo = cursor.getHeader();
int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
// Limit the adjustment step.
final int targetTimespan = this.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
long receivedTargetCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
long newTargetCompact = Utils.encodeCompactBits(newTarget);
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact));
}
public static void isValidAndCanonical(byte[] signature) throws VerificationException, SignatureDecodeException {
TransactionSignature.decodeFromBitcoin(signature, true, true);
}
@Override
public boolean notifyTransactionIsInBlock(final Sha256Hash txHash, final StoredBlock block, final BlockChain.NewBlockType blockType, final int relativityOffset) throws VerificationException {
getService().notifyObservers(txHash);
return isUnspentOutpoint(txHash);
}
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
final Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point?
if (!isDifficultyTransitionPoint(storedPrev.getHeight())) {
// No ... so check the difficulty didn't actually change.
if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() +
": " + Long.toHexString(nextBlock.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.
final Stopwatch watch = Stopwatch.createStarted();
Sha256Hash hash = prev.getHash();
StoredBlock cursor = null;
final int interval = this.getInterval();
for (int i = 0; i < interval; i++) {
cursor = blockStore.get(hash);
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 last transition point. Not found: " + hash);
}
hash = cursor.getHeader().getPrevBlockHash();
}
checkState(cursor != null && isDifficultyTransitionPoint(cursor.getHeight() - 1),
"Didn't arrive at a transition point.");
watch.stop();
if (watch.elapsed(TimeUnit.MILLISECONDS) > 50)
log.info("Difficulty transition traversal took {}", watch);
Block blockIntervalAgo = cursor.getHeader();
int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
// Limit the adjustment step.
final int targetTimespan = this.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
long receivedTargetCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
long newTargetCompact = Utils.encodeCompactBits(newTarget);
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact));
}
@Override
public void checkDifficultyTransitions(StoredBlock storedPrev, Block next, BlockStore blockStore)
throws VerificationException {
}