package network.thunder.core.communication.objects.messages.impl;
import network.thunder.core.communication.objects.messages.interfaces.helper.WalletHelper;
import network.thunder.core.etc.Constants;
import network.thunder.core.etc.Tools;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.ScriptBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by matsjerratsch on 19/01/2016.
*/
public class WalletHelperImpl implements WalletHelper {
final static int TIME_LOCK_IN_SECONDS = 60;
Wallet wallet;
Map<TransactionOutput, Integer> lockedOutputs = new HashMap<>();
public WalletHelperImpl (Wallet wallet) {
this.wallet = wallet;
}
@Override
public long getSpendableAmount () {
return Tools.getCoinValueFromOutput(getUnlockedOutputs());
}
@Override
public Transaction completeInputs (Transaction transaction) {
return addOutAndInputs(transaction);
}
private Transaction addOutAndInputs (Transaction transaction) {
long totalInput = 0;
long value = Tools.getCoinValueFromOutput(transaction.getOutputs());
long neededAmount = value + Tools.getTransactionFees(20, 2);
List<TransactionOutput> outputList = new ArrayList<>();
List<TransactionOutput> spendable = getUnlockedOutputs();
for (TransactionOutput o : spendable) {
if (o.getValue().value > neededAmount) {
/*
* Ok, found a suitable output, need to split the change
* TODO: Change (a few things), such that there will be no output < 500...
*/
outputList.add(o);
totalInput += o.getValue().value;
}
}
if (totalInput == 0) {
/*
* None of our outputs alone is sufficient, have to add multiples..
*/
for (TransactionOutput o : spendable) {
if (totalInput >= neededAmount) {
continue;
}
totalInput += o.getValue().value;
outputList.add(o);
}
}
if (totalInput < neededAmount) {
/*
* Not enough outputs in total to pay for the channel..
*/
throw new RuntimeException("Wallet Balance not sufficient. " + totalInput + "<" + neededAmount); //TODO
} else {
transaction.addOutput(Coin.valueOf(totalInput - value - Tools.getTransactionFees(2, 2)), wallet.freshReceiveAddress());
for (TransactionOutput o : outputList) {
transaction.addInput(o);
}
/*
* Sign all of our inputs..
*/
int j = 0;
for (int i = 0; i < outputList.size(); i++) {
TransactionOutput o = outputList.get(i);
ECKey key = wallet.findKeyFromPubHash(o.getAddressFromP2PKHScript(Constants.getNetwork()).getHash160());
TransactionSignature sig = Tools.getSignature(transaction, i, o, key);
byte[] s = sig.encodeToBitcoin();
ScriptBuilder builder = new ScriptBuilder();
builder.data(s);
builder.data(key.getPubKey());
transaction.getInput(i).setScriptSig(builder.build());
//TODO: Currently only working if we have P2PKH outputs in our wallet
}
for (TransactionOutput output : outputList) {
lockOutput(output);
}
}
return transaction;
}
private List<TransactionOutput> getUnlockedOutputs () {
List<TransactionOutput> spendable = new ArrayList<>(wallet.calculateAllSpendCandidates());
List<TransactionOutput> tempList = new ArrayList<>(spendable);
for (TransactionOutput o : tempList) {
Integer timeLock = lockedOutputs.get(o);
if (timeLock != null && timeLock > Tools.currentTime()) {
spendable.remove(o);
}
}
return spendable;
}
private void lockOutput (TransactionOutput output) {
lockedOutputs.put(output, Tools.currentTime() + TIME_LOCK_IN_SECONDS);
}
}