/*
* 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.google.bitcoin.wallet;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.bitcoin.core.TransactionOutput;
import com.google.bitcoin.core.Wallet;
import javax.annotation.Nullable;
import java.util.List;
import static com.google.common.base.Preconditions.checkState;
/**
* The default risk analysis. Currently, it only is concerned with whether a tx/dependency is non-final or not. Outside
* of specialised protocols you should not encounter non-final transactions.
*/
public class DefaultRiskAnalysis implements RiskAnalysis {
protected final Transaction tx;
protected final List<Transaction> dependencies;
protected final Wallet wallet;
private Transaction nonStandard;
protected Transaction nonFinal;
protected boolean analyzed;
private DefaultRiskAnalysis(Wallet wallet, Transaction tx, List<Transaction> dependencies) {
this.tx = tx;
this.dependencies = dependencies;
this.wallet = wallet;
}
@Override
public Result analyze() {
checkState(!analyzed);
analyzed = true;
Result result = analyzeIsFinal();
if (result != Result.OK)
return result;
return analyzeIsStandard();
}
private Result analyzeIsFinal() {
// Transactions we create ourselves are, by definition, not at risk of double spending against us.
if (tx.getConfidence().getSource() == TransactionConfidence.Source.SELF)
return Result.OK;
final int height = wallet.getLastBlockSeenHeight();
final long time = wallet.getLastBlockSeenTimeSecs();
// If the transaction has a lock time specified in blocks, we consider that if the tx would become final in the
// next block it is not risky (as it would confirm normally).
final int adjustedHeight = height + 1;
if (!tx.isFinal(adjustedHeight, time)) {
nonFinal = tx;
return Result.NON_FINAL;
}
for (Transaction dep : dependencies) {
if (!dep.isFinal(adjustedHeight, time)) {
nonFinal = dep;
return Result.NON_FINAL;
}
}
return Result.OK;
}
private Result analyzeIsStandard() {
if (!wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET))
return Result.OK;
nonStandard = isStandard(tx);
if (nonStandard != null)
return Result.NON_STANDARD;
for (Transaction dep : dependencies) {
nonStandard = isStandard(dep);
if (nonStandard != null)
return Result.NON_STANDARD;
}
return Result.OK;
}
/**
* <p>Checks if a transaction is considered "standard" by the reference client's IsStandardTx and AreInputsStandard
* functions.</p>
*
* <p>Note that this method currently only implements a minimum of checks. More to be added later.</p>
*
* @return Either null if the transaction is standard, or the first transaction found which is considered nonstandard
*/
public Transaction isStandard(Transaction tx) {
if (tx.getVersion() > 1 || tx.getVersion() < 1)
return tx;
for (TransactionOutput output : tx.getOutputs()) {
if (output.getMinNonDustValue().compareTo(output.getValue()) > 0)
return tx;
}
return null;
}
/** Returns the transaction that was found to be non-standard, or null. */
@Nullable
public Transaction getNonStandard() {
return nonStandard;
}
/** Returns the transaction that was found to be non-final, or null. */
@Nullable
public Transaction getNonFinal() {
return nonFinal;
}
@Override
public String toString() {
if (!analyzed)
return "Pending risk analysis for " + tx.getHashAsString();
else if (nonFinal != null)
return "Risky due to non-finality of " + nonFinal.getHashAsString();
else if (nonStandard != null)
return "Risky due to non-standard tx " + nonStandard.getHashAsString();
else
return "Non-risky";
}
public static class Analyzer implements RiskAnalysis.Analyzer {
@Override
public DefaultRiskAnalysis create(Wallet wallet, Transaction tx, List<Transaction> dependencies) {
return new DefaultRiskAnalysis(wallet, tx, dependencies);
}
}
public static Analyzer FACTORY = new Analyzer();
}