Reorganize savingswallet

This commit is contained in:
Manfred Karrer 2016-04-06 00:28:43 +02:00
parent 8832f2873b
commit 7ce5beb54f
58 changed files with 1143 additions and 652 deletions

View file

@ -22,6 +22,7 @@ import com.google.inject.Inject;
import io.bitsquare.app.Log;
import io.bitsquare.arbitration.messages.*;
import io.bitsquare.arbitration.payload.Attachment;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.exceptions.TransactionVerificationException;
@ -490,7 +491,7 @@ public class DisputeManager {
try {
log.debug("do payout Transaction ");
Transaction signedDisputedPayoutTx = tradeWalletService.signAndFinalizeDisputedPayoutTx(
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
dispute.getDepositTxSerialized(),
disputeResult.getArbitratorSignature(),
disputeResult.getBuyerPayoutAmount(),
@ -499,7 +500,7 @@ public class DisputeManager {
contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(),
disputeResult.getArbitratorAddressAsString(),
walletService.getTradeAddressEntry(dispute.getTradeId()),
walletService.getOrCreateAddressEntry(dispute.getTradeId(), AddressEntry.Context.MULTI_SIG),
contract.getBuyerBtcPubKey(),
contract.getSellerBtcPubKey(),
disputeResult.getArbitratorPubKey()
@ -576,7 +577,7 @@ public class DisputeManager {
}
private boolean isArbitrator(DisputeResult disputeResult) {
return disputeResult.getArbitratorAddressAsString().equals(walletService.getArbitratorAddressEntry().getAddressString());
return disputeResult.getArbitratorAddressAsString().equals(walletService.getOrCreateAddressEntry(AddressEntry.Context.ARBITRATOR).getAddressString());
}

View file

@ -20,6 +20,7 @@ package io.bitsquare.btc;
import io.bitsquare.app.Version;
import io.bitsquare.common.persistance.Persistable;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.params.MainNetParams;
@ -44,10 +45,17 @@ public final class AddressEntry implements Persistable {
private static final Logger log = LoggerFactory.getLogger(AddressEntry.class);
public enum Context {
SAVINGS,
TRADE,
ARBITRATOR,
DAO
AVAILABLE,
OFFER_FUNDING,
RESERVED_FOR_TRADE, //reserved
MULTI_SIG, //locked
TRADE_PAYOUT,
DAO_SHARE,
DAO_DIVIDEND
}
// keyPair can be null in case the object is created from deserialization as it is transient.
@ -64,6 +72,8 @@ public final class AddressEntry implements Persistable {
private final byte[] pubKey;
private final byte[] pubKeyHash;
private final String paramId;
@Nullable
private Coin lockedTradeAmount;
transient private NetworkParameters params;
@ -77,7 +87,7 @@ public final class AddressEntry implements Persistable {
}
// If created with offerId
public AddressEntry(DeterministicKey keyPair, NetworkParameters params, Context context, @Nullable String offerId) {
public AddressEntry(@Nullable DeterministicKey keyPair, NetworkParameters params, Context context, @Nullable String offerId) {
this.keyPair = keyPair;
this.params = params;
this.context = context;
@ -153,6 +163,26 @@ public final class AddressEntry implements Persistable {
return pubKey;
}
public boolean isOpenOffer() {
return context == Context.OFFER_FUNDING || context == Context.RESERVED_FOR_TRADE;
}
public boolean isTrade() {
return context == Context.MULTI_SIG || context == Context.TRADE_PAYOUT;
}
public boolean isTradable() {
return isOpenOffer() || isTrade();
}
public void setLockedTradeAmount(Coin lockedTradeAmount) {
this.lockedTradeAmount = lockedTradeAmount;
}
public Coin getLockedTradeAmount() {
return lockedTradeAmount;
}
@Override
public String toString() {
return "AddressEntry{" +

View file

@ -0,0 +1,7 @@
package io.bitsquare.btc;
public class AddressEntryException extends Exception {
public AddressEntryException(String message) {
super(message);
}
}

View file

@ -67,16 +67,7 @@ public final class AddressEntryList extends ArrayList<AddressEntry> implements P
}
}
public AddressEntry getNewTradeAddressEntry(String offerId) {
log.trace("getNewAddressEntry called with offerId " + offerId);
AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.TRADE, offerId);
add(addressEntry);
storage.queueUpForSave();
return addressEntry;
}
public AddressEntry getNewSavingsAddressEntry() {
AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.SAVINGS);
public AddressEntry addAddressEntry(AddressEntry addressEntry) {
add(addressEntry);
storage.queueUpForSave();
return addressEntry;
@ -87,17 +78,20 @@ public final class AddressEntryList extends ArrayList<AddressEntry> implements P
Optional<AddressEntry> addressEntryOptional = this.stream().filter(addressEntry -> offerId.equals(addressEntry.getOfferId())).findAny();
if (addressEntryOptional.isPresent()) {
AddressEntry addressEntry = addressEntryOptional.get();
add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.SAVINGS));
add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.AVAILABLE));
remove(addressEntry);
storage.queueUpForSave();
}
}
public void swapToAvailable(AddressEntry addressEntry) {
remove(addressEntry);
add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.AVAILABLE));
remove(addressEntry);
storage.queueUpForSave();
}
public AddressEntry getArbitratorAddressEntry() {
if (size() > 0)
return get(0);
else
return null;
public void queueUpForSave() {
storage.queueUpForSave();
}
}

View file

@ -0,0 +1,130 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc;
import com.google.common.annotations.VisibleForTesting;
import org.bitcoinj.core.*;
import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.CoinSelector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
* We lookup for spendable outputs which matches our address of our addressEntry.
*/
abstract class BitsquareCoinSelector implements CoinSelector {
private static final Logger log = LoggerFactory.getLogger(BitsquareCoinSelector.class);
protected final NetworkParameters params;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
protected BitsquareCoinSelector(NetworkParameters params) {
this.params = params;
}
abstract protected boolean matchesRequirement(TransactionOutput transactionOutput);
private static boolean isInBlockChainOrPending(Transaction tx) {
// Pick chain-included transactions and transactions that are pending.
TransactionConfidence confidence = tx.getConfidence();
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers());
return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
type.equals(TransactionConfidence.ConfidenceType.PENDING);
}
/**
* Sub-classes can override this to just customize whether transactions are usable, but keep age sorting.
*/
protected boolean shouldSelect(Transaction tx) {
return isInBlockChainOrPending(tx);
}
@Override
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
log.trace("candidates.size: " + candidates.size());
long targetAsLong = target.longValue();
log.trace("value needed: " + targetAsLong);
HashSet<TransactionOutput> selected = new HashSet<>();
// Sort the inputs by age*value so we get the highest "coindays" spent.
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<>(candidates);
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
// them in order to improve performance.
if (!target.equals(NetworkParameters.MAX_MONEY)) {
sortOutputs(sortedOutputs);
}
// Now iterate over the sorted outputs until we have got as close to the target as possible or a little
// bit over (excessive value will be change).
long total = 0;
for (TransactionOutput output : sortedOutputs) {
if (total >= targetAsLong) {
break;
}
// Only pick chain-included transactions, or transactions that are ours and pending.
// Only select outputs from our defined address(es)
if (!shouldSelect(output.getParentTransaction()) || !matchesRequirement(output)) {
continue;
}
selected.add(output);
total += output.getValue().longValue();
log.debug("adding up outputs: output/total: " + output.getValue().longValue() + "/" + total);
}
// Total may be lower than target here, if the given candidates were insufficient to create to requested
// transaction.
return new CoinSelection(Coin.valueOf(total), selected);
}
@VisibleForTesting
private static void sortOutputs(ArrayList<TransactionOutput> outputs) {
Collections.sort(outputs, (a, b) -> {
int depth1 = a.getParentTransactionDepthInBlocks();
int depth2 = b.getParentTransactionDepthInBlocks();
Coin aValue = a.getValue();
Coin bValue = b.getValue();
BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1));
BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2));
int c1 = bCoinDepth.compareTo(aCoinDepth);
if (c1 != 0) return c1;
// The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size.
int c2 = bValue.compareTo(aValue);
if (c2 != 0) return c2;
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
checkNotNull(a.getParentTransactionHash(), "a.getParentTransactionHash() must not be null");
checkNotNull(b.getParentTransactionHash(), "b.getParentTransactionHash() must not be null");
BigInteger aHash = a.getParentTransactionHash().toBigInteger();
BigInteger bHash = b.getParentTransactionHash().toBigInteger();
return aHash.compareTo(bHash);
});
}
}

View file

@ -84,8 +84,8 @@ public class FeePolicy {
// TODO will be increased once we get higher limits
// 0.02 BTC; about 8 EUR @ 400 EUR/BTC
// 0.01 BTC; about 4 EUR @ 400 EUR/BTC
public static Coin getSecurityDeposit() {
return Coin.valueOf(2_000_000);
return Coin.valueOf(1_000_000);
}
}

View file

@ -20,37 +20,26 @@ package io.bitsquare.btc;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.TransactionOutput;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Set;
/**
* We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
* We lookup for spendable outputs which matches our address of our addressEntry.
*/
class AddressBasedCoinSelector extends SavingsWalletCoinSelector {
private static final Logger log = LoggerFactory.getLogger(AddressBasedCoinSelector.class);
@Nullable
private Set<AddressEntry> addressEntries;
@Nullable
private AddressEntry addressEntry;
class MultiAddressesCoinSelector extends BitsquareCoinSelector {
private static final Logger log = LoggerFactory.getLogger(MultiAddressesCoinSelector.class);
private final Set<AddressEntry> addressEntries;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public AddressBasedCoinSelector(NetworkParameters params) {
super(params);
}
public AddressBasedCoinSelector(NetworkParameters params, @Nullable AddressEntry addressEntry) {
super(params);
this.addressEntry = addressEntry;
}
public AddressBasedCoinSelector(NetworkParameters params, @Nullable Set<AddressEntry> addressEntries) {
public MultiAddressesCoinSelector(NetworkParameters params, @NotNull Set<AddressEntry> addressEntries) {
super(params);
this.addressEntries = addressEntries;
}
@ -61,26 +50,19 @@ class AddressBasedCoinSelector extends SavingsWalletCoinSelector {
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
log.trace("matchesRequiredAddress(es)?");
log.trace(addressOutput.toString());
if (addressEntry != null && addressEntry.getAddress() != null) {
log.trace(addressEntry.getAddress().toString());
if (addressOutput.equals(addressEntry.getAddress()))
log.trace(addressEntries.toString());
for (AddressEntry entry : addressEntries) {
if (addressOutput.equals(entry.getAddress()))
return true;
else {
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntry " + addressOutput.toString
() + " / " + addressEntry.getAddress().toString());
}
} else if (addressEntries != null) {
log.trace(addressEntries.toString());
for (AddressEntry entry : addressEntries) {
if (addressOutput.equals(entry.getAddress()))
return true;
}
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString
() + " / " + addressEntries.toString());
}
}
return false;
}
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString
() + " / " + addressEntries.toString());
return false;
} else {
log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash");
return false;
}
}
}

View file

@ -17,144 +17,53 @@
package io.bitsquare.btc;
import com.google.common.annotations.VisibleForTesting;
import org.bitcoinj.core.*;
import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.CoinSelector;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.TransactionOutput;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
* We lookup for spendable outputs which matches our address of our addressEntry.
*/
class SavingsWalletCoinSelector implements CoinSelector {
class SavingsWalletCoinSelector extends BitsquareCoinSelector {
private static final Logger log = LoggerFactory.getLogger(SavingsWalletCoinSelector.class);
protected final NetworkParameters params;
@Nullable
private AddressEntryList addressEntryList;
@Nullable
private Set<Address> savingsWalletAddressSet;
private final Set<Address> savingsWalletAddressSet;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public SavingsWalletCoinSelector(NetworkParameters params, AddressEntryList addressEntryList) {
this.params = params;
this.addressEntryList = addressEntryList;
}
protected SavingsWalletCoinSelector(NetworkParameters params) {
this.params = params;
public SavingsWalletCoinSelector(NetworkParameters params, @NotNull List<AddressEntry> addressEntryList) {
super(params);
savingsWalletAddressSet = addressEntryList.stream()
.filter(addressEntry -> addressEntry.getContext() == AddressEntry.Context.AVAILABLE)
.map(AddressEntry::getAddress)
.collect(Collectors.toSet());
}
protected boolean matchesRequirement(TransactionOutput transactionOutput) {
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
Address address = transactionOutput.getScriptPubKey().getToAddress(params);
log.trace("only lookup in savings wallet address entries");
log.trace(addressOutput.toString());
log.trace(address.toString());
if (savingsWalletAddressSet != null && savingsWalletAddressSet.contains(addressOutput)) {
return true;
} else {
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntry " +
addressOutput.toString() + " / " + addressOutput.toString());
return false;
}
boolean matches = savingsWalletAddressSet.contains(address);
if (!matches)
log.trace("No match found at matchesRequiredAddress address / addressEntry " +
address.toString() + " / " + address.toString());
return matches;
} else {
log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash");
return false;
}
}
private static boolean isInBlockChainOrPending(Transaction tx) {
// Pick chain-included transactions and transactions that are pending.
TransactionConfidence confidence = tx.getConfidence();
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers());
return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
type.equals(TransactionConfidence.ConfidenceType.PENDING);
}
/**
* Sub-classes can override this to just customize whether transactions are usable, but keep age sorting.
*/
protected boolean shouldSelect(Transaction tx) {
return isInBlockChainOrPending(tx);
}
@Override
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
log.trace("candidates.size: " + candidates.size());
long targetAsLong = target.longValue();
log.trace("value needed: " + targetAsLong);
HashSet<TransactionOutput> selected = new HashSet<>();
// Sort the inputs by age*value so we get the highest "coindays" spent.
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<>(candidates);
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
// them in order to improve performance.
if (!target.equals(NetworkParameters.MAX_MONEY)) {
sortOutputs(sortedOutputs);
}
// Now iterate over the sorted outputs until we have got as close to the target as possible or a little
// bit over (excessive value will be change).
long total = 0;
if (addressEntryList != null) {
savingsWalletAddressSet = addressEntryList.stream()
.filter(addressEntry1 -> addressEntry1.getContext() == AddressEntry.Context.SAVINGS)
.map(AddressEntry::getAddress)
.collect(Collectors.toSet());
}
for (TransactionOutput output : sortedOutputs) {
if (total >= targetAsLong) {
break;
}
// Only pick chain-included transactions, or transactions that are ours and pending.
// Only select outputs from our defined address(es)
if (!shouldSelect(output.getParentTransaction()) || !matchesRequirement(output)) {
continue;
}
selected.add(output);
total += output.getValue().longValue();
log.debug("adding up outputs: output/total: " + output.getValue().longValue() + "/" + total);
}
// Total may be lower than target here, if the given candidates were insufficient to create to requested
// transaction.
return new CoinSelection(Coin.valueOf(total), selected);
}
@VisibleForTesting
private static void sortOutputs(ArrayList<TransactionOutput> outputs) {
Collections.sort(outputs, (a, b) -> {
int depth1 = a.getParentTransactionDepthInBlocks();
int depth2 = b.getParentTransactionDepthInBlocks();
Coin aValue = a.getValue();
Coin bValue = b.getValue();
BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1));
BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2));
int c1 = bCoinDepth.compareTo(aCoinDepth);
if (c1 != 0) return c1;
// The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size.
int c2 = bValue.compareTo(aValue);
if (c2 != 0) return c2;
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
checkNotNull(a.getParentTransactionHash(), "a.getParentTransactionHash() must not be null");
checkNotNull(b.getParentTransactionHash(), "b.getParentTransactionHash() must not be null");
BigInteger aHash = a.getParentTransactionHash().toBigInteger();
BigInteger bHash = b.getParentTransactionHash().toBigInteger();
return aHash.compareTo(bHash);
});
}
}

View file

@ -0,0 +1,64 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.TransactionOutput;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
* We lookup for spendable outputs which matches our address of our address.
*/
class TradeWalletCoinSelector extends BitsquareCoinSelector {
private static final Logger log = LoggerFactory.getLogger(TradeWalletCoinSelector.class);
private final Address address;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TradeWalletCoinSelector(NetworkParameters params, @NotNull Address address) {
super(params);
this.address = address;
}
@Override
protected boolean matchesRequirement(TransactionOutput transactionOutput) {
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
log.trace("matchesRequiredAddress?");
log.trace("addressOutput " + addressOutput.toString());
log.trace("address " + address.toString());
boolean matches = addressOutput.equals(address);
if (!matches)
log.trace("No match found at matchesRequiredAddress addressOutput / address " + addressOutput.toString
() + " / " + address.toString());
return matches;
} else {
log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash");
return false;
}
}
}

View file

@ -134,14 +134,14 @@ public class TradeWalletService {
///////////////////////////////////////////////////////////////////////////////////////////
/**
* @param addressEntry From where we want to spend the transaction fee. Used also as change address.
* @param reservedForTradeAddress From where we want to spend the transaction fee. Used also as change reservedForTradeAddress.
* @param useSavingsWallet
* @param tradingFee The amount of the trading fee.
* @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator). @return The broadcasted transaction
* @param tradingFee The amount of the trading fee.
* @param feeReceiverAddresses The reservedForTradeAddress of the receiver of the trading fee (arbitrator). @return The broadcasted transaction
* @throws InsufficientMoneyException
* @throws AddressFormatException
*/
public Transaction createTradingFeeTx(AddressEntry addressEntry, Address changeAddress, Coin reservedFundsForOffer,
public Transaction createTradingFeeTx(Address fundingAddress, Address reservedForTradeAddress, Address changeAddress, Coin reservedFundsForOffer,
boolean useSavingsWallet, Coin tradingFee, String feeReceiverAddresses)
throws InsufficientMoneyException, AddressFormatException {
Transaction tradingFeeTx = new Transaction(params);
@ -149,8 +149,8 @@ public class TradeWalletService {
"You cannot send an amount which are smaller than the fee + dust output.");
Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades());
tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses));
// the reserved amount we need for the trade we send to our trade address
tradingFeeTx.addOutput(reservedFundsForOffer, addressEntry.getAddress());
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
// wait for 1 confirmation)
@ -159,9 +159,9 @@ public class TradeWalletService {
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
if (useSavingsWallet)
sendRequest.coinSelector = new SavingsWalletCoinSelector(params, addressEntryList);
sendRequest.coinSelector = new SavingsWalletCoinSelector(params, getAddressEntryListAsImmutableList());
else
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
sendRequest.coinSelector = new TradeWalletCoinSelector(params, fundingAddress);
// We use a fixed fee
sendRequest.feePerKb = Coin.ZERO;
sendRequest.fee = FeePolicy.getFixedTxFeeForTrades();
@ -199,7 +199,7 @@ public class TradeWalletService {
*/
public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry takersAddressEntry, Address takersChangeAddress) throws
TransactionVerificationException, WalletException, AddressFormatException {
log.trace("createTakerDepositTxInputs called");
log.trace("takerCreatesDepositsTxInputs called");
log.trace("inputAmount " + inputAmount.toFriendlyString());
log.trace("takersAddressEntry " + takersAddressEntry.toString());
@ -297,7 +297,7 @@ public class TradeWalletService {
byte[] sellerPubKey,
byte[] arbitratorPubKey)
throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
log.trace("createAndSignDepositTx called");
log.trace("offererCreatesAndSignsDepositTx called");
log.trace("offererIsBuyer " + offererIsBuyer);
log.trace("offererInputAmount " + offererInputAmount.toFriendlyString());
log.trace("msOutputAmount " + msOutputAmount.toFriendlyString());
@ -522,8 +522,8 @@ public class TradeWalletService {
* @param depositTx Deposit transaction
* @param buyerPayoutAmount Payout amount for buyer
* @param sellerPayoutAmount Payout amount for seller
* @param buyerAddressString Address for buyer
* @param sellerAddressEntry AddressEntry for seller
* @param buyerPayoutAddressString Address for buyer
* @param sellerPayoutAddressEntry AddressEntry for seller
* @param lockTime Lock time
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
@ -535,19 +535,21 @@ public class TradeWalletService {
public byte[] sellerSignsPayoutTx(Transaction depositTx,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
String buyerAddressString,
AddressEntry sellerAddressEntry,
String buyerPayoutAddressString,
AddressEntry sellerPayoutAddressEntry,
AddressEntry multiSigAddressEntry,
long lockTime,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException {
log.trace("signPayoutTx called");
log.trace("sellerSignsPayoutTx called");
log.trace("depositTx " + depositTx.toString());
log.trace("buyerPayoutAmount " + buyerPayoutAmount.toFriendlyString());
log.trace("sellerPayoutAmount " + sellerPayoutAmount.toFriendlyString());
log.trace("buyerAddressString " + buyerAddressString);
log.trace("sellerAddressEntry " + sellerAddressEntry.toString());
log.trace("buyerPayoutAddressString " + buyerPayoutAddressString);
log.trace("sellerPayoutAddressEntry " + sellerPayoutAddressEntry.toString());
log.trace("multiSigAddressEntry " + multiSigAddressEntry.toString());
log.trace("lockTime " + lockTime);
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
@ -556,15 +558,15 @@ public class TradeWalletService {
Transaction preparedPayoutTx = createPayoutTx(depositTx,
buyerPayoutAmount,
sellerPayoutAmount,
buyerAddressString,
sellerAddressEntry.getAddressString(),
buyerPayoutAddressString,
sellerPayoutAddressEntry.getAddressString(),
lockTime
);
// MS redeemScript
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
DeterministicKey keyPair = sellerAddressEntry.getKeyPair();
DeterministicKey keyPair = multiSigAddressEntry.getKeyPair();
checkNotNull(keyPair);
ECKey.ECDSASignature sellerSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
@ -582,7 +584,7 @@ public class TradeWalletService {
* @param sellerSignature DER encoded canonical signature of seller
* @param buyerPayoutAmount Payout amount for buyer
* @param sellerPayoutAmount Payout amount for seller
* @param buyerAddressEntry AddressEntry for buyer
* @param buyerPayoutAddressEntry AddressEntry for buyer
* @param sellerAddressString Address for seller
* @param lockTime Lock time
* @param buyerPubKey The public key of the buyer.
@ -597,20 +599,22 @@ public class TradeWalletService {
byte[] sellerSignature,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
AddressEntry buyerAddressEntry,
AddressEntry buyerPayoutAddressEntry,
AddressEntry multiSigAddressEntry,
String sellerAddressString,
long lockTime,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
log.trace("signAndFinalizePayoutTx called");
log.trace("buyerSignsAndFinalizesPayoutTx called");
log.trace("depositTx " + depositTx.toString());
log.trace("sellerSignature r " + ECKey.ECDSASignature.decodeFromDER(sellerSignature).r.toString());
log.trace("sellerSignature s " + ECKey.ECDSASignature.decodeFromDER(sellerSignature).s.toString());
log.trace("buyerPayoutAmount " + buyerPayoutAmount.toFriendlyString());
log.trace("sellerPayoutAmount " + sellerPayoutAmount.toFriendlyString());
log.trace("buyerAddressEntry " + buyerAddressEntry);
log.trace("buyerPayoutAddressEntry " + buyerPayoutAddressEntry);
log.trace("multiSigAddressEntry " + multiSigAddressEntry);
log.trace("sellerAddressString " + sellerAddressString);
log.trace("lockTime " + lockTime);
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
@ -620,15 +624,15 @@ public class TradeWalletService {
Transaction payoutTx = createPayoutTx(depositTx,
buyerPayoutAmount,
sellerPayoutAmount,
buyerAddressEntry.getAddressString(),
buyerPayoutAddressEntry.getAddressString(),
sellerAddressString,
lockTime);
// MS redeemScript
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(buyerAddressEntry.getKeyPair(), "buyerAddressEntry.getKeyPair() must not be null");
ECKey.ECDSASignature buyerSignature = buyerAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised();
checkNotNull(multiSigAddressEntry.getKeyPair(), "multiSigAddressEntry.getKeyPair() must not be null");
ECKey.ECDSASignature buyerSignature = multiSigAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised();
TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature), Transaction.SigHash.ALL, false);
TransactionSignature buyerTxSig = new TransactionSignature(buyerSignature, Transaction.SigHash.ALL, false);
@ -638,14 +642,14 @@ public class TradeWalletService {
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
printTxWithInputs("payoutTx", payoutTx);
verifyTransaction(payoutTx);
checkWalletConsistency();
checkScriptSig(payoutTx, input, 0);
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
input.verify(input.getConnectedOutput());
printTxWithInputs("payoutTx", payoutTx);
// As we use lockTime the tx will not be relayed as it is not considered standard.
// We need to broadcast on our own when we reahced the block height. Both peers will do the broadcast.
return payoutTx;
@ -733,7 +737,7 @@ public class TradeWalletService {
* @param buyerAddressString The address of the buyer.
* @param sellerAddressString The address of the seller.
* @param arbitratorAddressString The address of the arbitrator.
* @param tradersAddressEntry The addressEntry of the trader who calls that method
* @param tradersMultiSigAddressEntry The addressEntry of the trader who calls that method
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
@ -742,18 +746,18 @@ public class TradeWalletService {
* @throws TransactionVerificationException
* @throws WalletException
*/
public Transaction signAndFinalizeDisputedPayoutTx(byte[] depositTxSerialized,
byte[] arbitratorSignature,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
Coin arbitratorPayoutAmount,
String buyerAddressString,
String sellerAddressString,
String arbitratorAddressString,
AddressEntry tradersAddressEntry,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSerialized,
byte[] arbitratorSignature,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
Coin arbitratorPayoutAmount,
String buyerAddressString,
String sellerAddressString,
String arbitratorAddressString,
AddressEntry tradersMultiSigAddressEntry,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
Transaction depositTx = new Transaction(params, depositTxSerialized);
@ -767,7 +771,7 @@ public class TradeWalletService {
log.trace("buyerAddressString " + buyerAddressString);
log.trace("sellerAddressString " + sellerAddressString);
log.trace("arbitratorAddressString " + arbitratorAddressString);
log.trace("tradersAddressEntry " + tradersAddressEntry);
log.trace("tradersMultiSigAddressEntry " + tradersMultiSigAddressEntry);
log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
@ -786,7 +790,7 @@ public class TradeWalletService {
// take care of sorting!
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
DeterministicKey keyPair = tradersAddressEntry.getKeyPair();
DeterministicKey keyPair = tradersMultiSigAddressEntry.getKeyPair();
checkNotNull(keyPair);
ECKey.ECDSASignature tradersSignature = keyPair.sign(sigHash, aesKey).toCanonicalised();
@ -1053,7 +1057,7 @@ public class TradeWalletService {
sendRequest.feePerKb = Coin.ZERO;
sendRequest.fee = FeePolicy.getFixedTxFeeForTrades();
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation)
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.getAddress());
// We use always the same address in a trade for all transactions
sendRequest.changeAddress = changeAddress;
// With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection.
@ -1069,4 +1073,9 @@ public class TradeWalletService {
public void setAddressEntryList(AddressEntryList addressEntryList) {
this.addressEntryList = addressEntryList;
}
public List<AddressEntry> getAddressEntryListAsImmutableList() {
return ImmutableList.copyOf(addressEntryList);
}
}

View file

@ -89,7 +89,6 @@ public class WalletService {
private WalletAppKit walletAppKit;
private Wallet wallet;
private AddressEntry arbitratorAddressEntry;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final ObjectProperty<List<Peer>> connectedPeers = new SimpleObjectProperty<>();
public final BooleanProperty shutDownDone = new SimpleBooleanProperty();
@ -149,7 +148,6 @@ public class WalletService {
wallet.addEventListener(walletEventListener);
addressEntryList.onWalletReady(wallet);
arbitratorAddressEntry = addressEntryList.getArbitratorAddressEntry();
walletAppKit.peerGroup().addEventListener(new PeerEventListener() {
@Override
@ -325,105 +323,76 @@ public class WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
// Trade AddressEntry
// AddressEntry
///////////////////////////////////////////////////////////////////////////////////////////
public List<AddressEntry> getAddressEntryList() {
return ImmutableList.copyOf(addressEntryList);
}
public AddressEntry getArbitratorAddressEntry() {
return arbitratorAddressEntry;
}
public AddressEntry getTradeAddressEntry(String offerId) {
Optional<AddressEntry> addressEntry = getAddressEntryList().stream()
public AddressEntry getOrCreateAddressEntry(String offerId, AddressEntry.Context context) {
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
if (addressEntry.isPresent())
return addressEntry.get();
else
return addressEntryList.getNewTradeAddressEntry(offerId);
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId));
}
private Optional<AddressEntry> getAddressEntryByAddress(String address) {
return getAddressEntryList().stream()
.filter(e -> e.getAddressString() != null && e.getAddressString().equals(address))
public AddressEntry getOrCreateAddressEntry(AddressEntry.Context context) {
Optional<AddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> context == e.getContext())
.findAny();
if (addressEntry.isPresent())
return addressEntry.get();
else
return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
}
public Optional<AddressEntry> findAddressEntry(String address, AddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream()
.filter(e -> address.equals(e.getAddressString()))
.filter(e -> context == e.getContext())
.findAny();
}
public List<AddressEntry> getTradeAddressEntryList() {
return getAddressEntryList().stream()
.filter(e -> e.getContext().equals(AddressEntry.Context.TRADE))
public List<AddressEntry> getAvailableAddressEntries() {
return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> AddressEntry.Context.AVAILABLE == addressEntry.getContext())
.collect(Collectors.toList());
}
///////////////////////////////////////////////////////////////////////////////////////////
// SavingsAddressEntry
///////////////////////////////////////////////////////////////////////////////////////////
public AddressEntry getNewSavingsAddressEntry() {
return addressEntryList.getNewSavingsAddressEntry();
}
public List<AddressEntry> getSavingsAddressEntryList() {
return getAddressEntryList().stream()
.filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS))
public List<AddressEntry> getAddressEntries(AddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> context == addressEntry.getContext())
.collect(Collectors.toList());
}
public AddressEntry getUnusedSavingsAddressEntry() {
List<AddressEntry> unusedSavingsAddressEntries = getUnusedSavingsAddressEntries();
if (!unusedSavingsAddressEntries.isEmpty())
return unusedSavingsAddressEntries.get(0);
else
return getNewSavingsAddressEntry();
}
public List<AddressEntry> getUnusedSavingsAddressEntries() {
return getSavingsAddressEntryList().stream()
.filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) == 0)
public List<AddressEntry> getFundedAvailableAddressEntries() {
return getAvailableAddressEntries().stream()
.filter(addressEntry -> getBalanceForAddress(addressEntry.getAddress()).isPositive())
.collect(Collectors.toList());
}
public List<AddressEntry> getUsedSavingsAddressEntries() {
return getSavingsAddressEntryList().stream()
.filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0)
.collect(Collectors.toList());
}
public List<Address> getUsedSavingsAddresses() {
return getSavingsAddressEntryList().stream()
.filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0)
.map(addressEntry -> addressEntry.getAddress())
.collect(Collectors.toList());
}
public List<Transaction> getUsedSavingWalletTransactions() {
List<Transaction> transactions = new ArrayList<>();
List<TransactionOutput> transactionOutputs = new ArrayList<>();
List<Address> usedSavingsAddresses = getUsedSavingsAddresses();
log.debug("usedSavingsAddresses = " + usedSavingsAddresses);
wallet.getTransactions(true).stream().forEach(transaction -> transactionOutputs.addAll(transaction.getOutputs()));
for (TransactionOutput transactionOutput : transactionOutputs) {
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
if (usedSavingsAddresses.contains(addressOutput) && transactionOutput.getParentTransaction() != null) {
log.debug("transactionOutput.getParentTransaction() = " + transactionOutput.getParentTransaction().getHashAsString());
transactions.add(transactionOutput.getParentTransaction());
}
}
}
return transactions;
public List<AddressEntry> getAddressEntryListAsImmutableList() {
return ImmutableList.copyOf(addressEntryList);
}
public void swapTradeToSavings(String offerId) {
getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
addressEntryList.swapTradeToSavings(offerId);
}
public void swapTradeEntryToAvailableEntry(String offerId, AddressEntry.Context context) {
Optional<AddressEntry> addressEntryOptional = getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
addressEntryOptional.ifPresent(addressEntryList::swapToAvailable);
}
public void saveAddressEntryList() {
addressEntryList.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// TransactionConfidence
@ -518,10 +487,6 @@ public class WalletService {
return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO;
}
public Coin getBalanceForAddressEntryWithTradeId(String tradeId) {
return getBalanceForAddress(getTradeAddressEntry(tradeId).getAddress());
}
private Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
Coin balance = Coin.ZERO;
for (TransactionOutput transactionOutput : transactionOutputs) {
@ -535,11 +500,9 @@ public class WalletService {
}
public Coin getSavingWalletBalance() {
Coin balance = Coin.ZERO;
for (AddressEntry addressEntry : getSavingsAddressEntryList()) {
balance = balance.add(getBalanceForAddress(addressEntry.getAddress()));
}
return balance;
return Coin.valueOf(getFundedAvailableAddressEntries().stream()
.mapToLong(addressEntry -> getBalanceForAddress(addressEntry.getAddress()).value)
.sum());
}
public int getNumTxOutputsForAddress(Address address) {
@ -564,10 +527,11 @@ public class WalletService {
public Coin getRequiredFee(String fromAddress,
String toAddress,
Coin amount,
@Nullable KeyParameter aesKey) throws AddressFormatException, IllegalArgumentException {
@Nullable KeyParameter aesKey,
AddressEntry.Context context) throws AddressFormatException, AddressEntryException {
Coin fee;
try {
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey));
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey, context));
// We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases...
fee = FeePolicy.getFixedTxFeeForTrades();
} catch (InsufficientMoneyException e) {
@ -582,7 +546,7 @@ public class WalletService {
String toAddress,
Coin amount,
@Nullable KeyParameter aesKey) throws AddressFormatException,
IllegalArgumentException {
AddressEntryException {
Coin fee;
try {
wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey));
@ -599,8 +563,9 @@ public class WalletService {
private Wallet.SendRequest getSendRequest(String fromAddress,
String toAddress,
Coin amount,
@Nullable KeyParameter aesKey) throws AddressFormatException,
IllegalArgumentException, InsufficientMoneyException {
@Nullable KeyParameter aesKey,
AddressEntry.Context context) throws AddressFormatException,
AddressEntryException, InsufficientMoneyException {
Transaction tx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
"You cannot send an amount which are smaller than 546 satoshis.");
@ -609,11 +574,12 @@ public class WalletService {
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
sendRequest.aesKey = aesKey;
sendRequest.shuffleOutputs = false;
Optional<AddressEntry> addressEntry = getAddressEntryByAddress(fromAddress);
Optional<AddressEntry> addressEntry = findAddressEntry(fromAddress, context);
if (!addressEntry.isPresent())
throw new IllegalArgumentException("WithdrawFromAddress is not found in our wallets.");
throw new AddressEntryException("WithdrawFromAddress is not found in our wallet.");
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry.get());
checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null");
sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress());
sendRequest.changeAddress = addressEntry.get().getAddress();
sendRequest.feePerKb = FeePolicy.getFeePerKb();
return sendRequest;
@ -624,7 +590,7 @@ public class WalletService {
Coin amount,
@Nullable String changeAddress,
@Nullable KeyParameter aesKey) throws
AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
AddressFormatException, AddressEntryException, InsufficientMoneyException {
Transaction tx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
"You cannot send an amount which are smaller than 546 satoshis.");
@ -634,18 +600,23 @@ public class WalletService {
sendRequest.aesKey = aesKey;
sendRequest.shuffleOutputs = false;
Set<AddressEntry> addressEntries = fromAddresses.stream()
.map(this::getAddressEntryByAddress)
.map(address -> {
Optional<AddressEntry> addressEntryOptional = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
if (!addressEntryOptional.isPresent())
addressEntryOptional = findAddressEntry(address, AddressEntry.Context.OFFER_FUNDING);
return addressEntryOptional;
})
.filter(Optional::isPresent)
.map(Optional::get).collect(Collectors.toSet());
.map(Optional::get)
.collect(Collectors.toSet());
if (addressEntries.isEmpty())
throw new IllegalArgumentException("No withdrawFromAddresses not found in our wallets.\n\t" +
"fromAddresses=" + fromAddresses);
throw new AddressEntryException("No Addresses for withdraw found in our wallet");
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntries);
sendRequest.coinSelector = new MultiAddressesCoinSelector(params, addressEntries);
Optional<AddressEntry> addressEntryOptional = Optional.empty();
AddressEntry changeAddressAddressEntry = null;
if (changeAddress != null)
addressEntryOptional = getAddressEntryByAddress(changeAddress);
addressEntryOptional = findAddressEntry(changeAddress, AddressEntry.Context.AVAILABLE);
if (addressEntryOptional.isPresent()) {
changeAddressAddressEntry = addressEntryOptional.get();
@ -664,10 +635,11 @@ public class WalletService {
String toAddress,
Coin amount,
@Nullable KeyParameter aesKey,
AddressEntry.Context context,
FutureCallback<Transaction> callback) throws AddressFormatException,
IllegalArgumentException, InsufficientMoneyException {
Coin fee = getRequiredFee(fromAddress, toAddress, amount, aesKey);
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey));
AddressEntryException, InsufficientMoneyException {
Coin fee = getRequiredFee(fromAddress, toAddress, amount, aesKey, context);
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey, context));
Futures.addCallback(sendResult.broadcastComplete, callback);
printTxWithInputs("sendFunds", sendResult.tx);
@ -680,7 +652,7 @@ public class WalletService {
@Nullable String changeAddress,
@Nullable KeyParameter aesKey,
FutureCallback<Transaction> callback) throws AddressFormatException,
IllegalArgumentException, InsufficientMoneyException {
AddressEntryException, InsufficientMoneyException {
Coin fee = getRequiredFeeForMultipleAddresses(fromAddresses, toAddress, amount, aesKey);
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequestForMultipleAddresses(fromAddresses, toAddress,
amount.subtract(fee), changeAddress, aesKey));

View file

@ -1,110 +1,35 @@
package io.bitsquare.trade;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.failed.FailedTradesManager;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OpenOffer;
import io.bitsquare.trade.offer.OpenOfferManager;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TradableHelper {
private static final Logger log = LoggerFactory.getLogger(TradableHelper.class);
public static List<AddressEntry> getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) {
Set<String> reservedTradeIds = getNotCompletedTradableItems(openOfferManager, tradeManager).stream()
.map(tradable -> tradable.getOffer().getId())
.collect(Collectors.toSet());
return walletService.getAddressEntryList().stream()
.filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive())
.filter(e -> !reservedTradeIds.contains(e.getOfferId()))
.collect(Collectors.toList());
public static Stream<AddressEntry> getAddressEntriesForAvailableBalanceStream(WalletService walletService) {
Stream<AddressEntry> availableOrPayout = Stream.concat(walletService.getAddressEntries(AddressEntry.Context.TRADE_PAYOUT).stream(), walletService.getFundedAvailableAddressEntries().stream());
Stream<AddressEntry> available = Stream.concat(availableOrPayout, walletService.getAddressEntries(AddressEntry.Context.ARBITRATOR).stream());
available = Stream.concat(available, walletService.getAddressEntries(AddressEntry.Context.OFFER_FUNDING).stream());
return available
.filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isPositive());
}
public static Set<Tradable> getNotCompletedTradableItems(OpenOfferManager openOfferManager, TradeManager tradeManager) {
return Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
.filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID)
.collect(Collectors.toSet());
public static Stream<Trade> getLockedTradeStream(TradeManager tradeManager) {
return tradeManager.getTrades().stream()
.filter(trade -> trade.getState().getPhase().ordinal() >= Trade.Phase.DEPOSIT_PAID.ordinal() &&
trade.getState().getPhase().ordinal() < Trade.Phase.PAYOUT_PAID.ordinal());
}
public static Coin getBalanceInOpenOffer(OpenOffer openOffer) {
Offer offer = openOffer.getOffer();
Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb());
// For the seller we add the trade amount
if (offer.getDirection() == Offer.Direction.SELL)
balance = balance.add(offer.getAmount());
return balance;
}
public static Coin getBalanceInTrade(Trade trade, WalletService walletService) {
AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId());
Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb());
// For the seller we add the trade amount
if (trade.getContract() != null &&
trade.getTradeAmount() != null &&
trade.getContract().getSellerPayoutAddressString().equals(addressEntry.getAddressString()))
balance = balance.add(trade.getTradeAmount());
return balance;
}
public static Coin getAvailableBalance(AddressEntry addressEntry, WalletService walletService,
OpenOfferManager openOfferManager, TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager) {
Coin balance;
Coin totalBalance = walletService.getBalanceForAddress(addressEntry.getAddress());
String id = addressEntry.getOfferId();
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(id);
Optional<Trade> tradeOptional = tradeManager.getTradeById(id);
Optional<Tradable> closedTradableOptional = closedTradableManager.getTradableById(id);
Optional<Trade> failedTradesOptional = failedTradesManager.getTradeById(id);
if (openOfferOptional.isPresent()) {
balance = totalBalance.subtract(TradableHelper.getBalanceInOpenOffer(openOfferOptional.get()));
} else if (tradeOptional.isPresent()) {
Trade trade = tradeOptional.get();
if (trade.getState().getPhase() != Trade.Phase.PAYOUT_PAID)
balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(trade, walletService));
else
balance = totalBalance;
} else if (closedTradableOptional.isPresent()) {
Tradable tradable = closedTradableOptional.get();
Coin balanceInTrade = Coin.ZERO;
if (tradable instanceof OpenOffer)
balanceInTrade = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable);
else if (tradable instanceof Trade)
balanceInTrade = TradableHelper.getBalanceInTrade((Trade) tradable, walletService);
balance = totalBalance.subtract(balanceInTrade);
} else if (failedTradesOptional.isPresent()) {
balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(failedTradesOptional.get(), walletService));
} else {
balance = totalBalance;
}
return balance;
public static AddressEntry getLockedTradeAddressEntry(Trade trade, WalletService walletService) {
return walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
}
public static Coin getReservedBalance(Tradable tradable, WalletService walletService) {
AddressEntry addressEntry = walletService.getTradeAddressEntry(tradable.getId());
Coin balance = walletService.getBalanceForAddress(addressEntry.getAddress());
if (tradable instanceof Trade) {
Trade trade = (Trade) tradable;
if (trade.getState().getPhase().ordinal() < Trade.Phase.PAYOUT_PAID.ordinal())
balance = TradableHelper.getBalanceInTrade(trade, walletService);
} else if (tradable instanceof OpenOffer) {
balance = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable);
}
return balance;
return walletService.getBalanceForAddress(walletService.getOrCreateAddressEntry(tradable.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress());
}
}

View file

@ -21,6 +21,7 @@ import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.app.Log;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.AddressEntryException;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.crypto.KeyRing;
@ -306,8 +307,7 @@ public class TradeManager {
///////////////////////////////////////////////////////////////////////////////////////////
public void onWithdrawRequest(String toAddress, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId());
String fromAddress = addressEntry.getAddressString();
String fromAddress = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString();
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
@Override
@ -328,8 +328,8 @@ public class TradeManager {
}
};
try {
walletService.sendFunds(fromAddress, toAddress, trade.getPayoutAmount(), aesKey, callback);
} catch (AddressFormatException | InsufficientMoneyException e) {
walletService.sendFunds(fromAddress, toAddress, trade.getPayoutAmount(), aesKey, AddressEntry.Context.TRADE_PAYOUT, callback);
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
e.printStackTrace();
log.error(e.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw.", e);

View file

@ -19,6 +19,7 @@ package io.bitsquare.trade.offer;
import com.google.inject.Inject;
import io.bitsquare.app.Log;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.Timer;
@ -275,7 +276,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer);
closedTradableManager.add(openOffer);
walletService.swapTradeToSavings(offer.getId());
walletService.swapTradeEntryToAvailableEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING);
walletService.swapTradeEntryToAvailableEntry(offer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE);
resultHandler.handleResult();
},
errorMessageHandler);

View file

@ -18,7 +18,9 @@
package io.bitsquare.trade.protocol.placeoffer.tasks;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.taskrunner.Task;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.NodeAddress;
@ -46,9 +48,12 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateOfferFeeTx");
WalletService walletService = model.walletService;
String id = model.offer.getId();
Transaction transaction = model.tradeWalletService.createTradingFeeTx(
model.walletService.getTradeAddressEntry(model.offer.getId()),
model.walletService.getUnusedSavingsAddressEntry().getAddress(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(),
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
model.reservedFundsForOffer,
model.useSavingsWallet,
FeePolicy.getCreateOfferFee(),

View file

@ -20,7 +20,6 @@ package io.bitsquare.trade.protocol.trade;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.data.RawTransactionInput;
@ -38,7 +37,6 @@ import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.user.User;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
@ -201,18 +199,6 @@ public class ProcessModel implements Model, Serializable {
return user.getAccountId();
}
public AddressEntry getAddressEntry() {
return walletService.getTradeAddressEntry(offer.getId());
}
public Address getUnusedSavingsAddress() {
return walletService.getUnusedSavingsAddressEntry().getAddress();
}
public byte[] getTradeWalletPubKey() {
return getAddressEntry().getPubKey();
}
@Nullable
public byte[] getPayoutTxSignature() {
return payoutTxSignature;

View file

@ -46,7 +46,7 @@ public final class TradingPeer implements Persistable {
private String contractSignature;
private byte[] signature;
private PubKeyRing pubKeyRing;
private byte[] tradeWalletPubKey;
private byte[] multiSigPubKey;
private List<RawTransactionInput> rawTransactionInputs;
private long changeOutputValue;
@Nullable
@ -81,12 +81,12 @@ public final class TradingPeer implements Persistable {
this.accountId = accountId;
}
public byte[] getTradeWalletPubKey() {
return tradeWalletPubKey;
public byte[] getMultiSigPubKey() {
return multiSigPubKey;
}
public void setTradeWalletPubKey(byte[] tradeWalletPubKey) {
this.tradeWalletPubKey = tradeWalletPubKey;
public void setMultiSigPubKey(byte[] multiSigPubKey) {
this.multiSigPubKey = multiSigPubKey;
}
public PaymentAccountContractData getPaymentAccountContractData() {

View file

@ -35,7 +35,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
public final long tradeAmount;
public final byte[] takerTradeWalletPubKey;
public final byte[] takerMultiSigPubKey;
public final ArrayList<RawTransactionInput> rawTransactionInputs;
public final long changeOutputValue;
public final String changeOutputAddress;
@ -55,7 +55,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
ArrayList<RawTransactionInput> rawTransactionInputs,
long changeOutputValue,
String changeOutputAddress,
byte[] takerTradeWalletPubKey,
byte[] takerMultiSigPubKey,
String takerPayoutAddressString,
PubKeyRing takerPubKeyRing,
PaymentAccountContractData takerPaymentAccountContractData,
@ -71,7 +71,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
this.changeOutputAddress = changeOutputAddress;
this.takerPayoutAddressString = takerPayoutAddressString;
this.takerPubKeyRing = takerPubKeyRing;
this.takerTradeWalletPubKey = takerTradeWalletPubKey;
this.takerMultiSigPubKey = takerMultiSigPubKey;
this.takerPaymentAccountContractData = takerPaymentAccountContractData;
this.takerAccountId = takerAccountId;
this.takeOfferFeeTxId = takeOfferFeeTxId;
@ -100,7 +100,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
if (tradeAmount != that.tradeAmount) return false;
if (changeOutputValue != that.changeOutputValue) return false;
if (!Arrays.equals(takerTradeWalletPubKey, that.takerTradeWalletPubKey)) return false;
if (!Arrays.equals(takerMultiSigPubKey, that.takerMultiSigPubKey)) return false;
if (rawTransactionInputs != null ? !rawTransactionInputs.equals(that.rawTransactionInputs) : that.rawTransactionInputs != null)
return false;
if (changeOutputAddress != null ? !changeOutputAddress.equals(that.changeOutputAddress) : that.changeOutputAddress != null)
@ -129,7 +129,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32));
result = 31 * result + (takerTradeWalletPubKey != null ? Arrays.hashCode(takerTradeWalletPubKey) : 0);
result = 31 * result + (takerMultiSigPubKey != null ? Arrays.hashCode(takerMultiSigPubKey) : 0);
result = 31 * result + (rawTransactionInputs != null ? rawTransactionInputs.hashCode() : 0);
result = 31 * result + (int) (changeOutputValue ^ (changeOutputValue >>> 32));
result = 31 * result + (changeOutputAddress != null ? changeOutputAddress.hashCode() : 0);

View file

@ -43,12 +43,12 @@ public final class PublishDepositTxRequest extends TradeMessage {
public final String offererPayoutAddressString;
public final byte[] preparedDepositTx;
public final ArrayList<RawTransactionInput> offererInputs;
public final byte[] offererTradeWalletPubKey;
public final byte[] offererMultiSigPubKey;
public PublishDepositTxRequest(String tradeId,
PaymentAccountContractData offererPaymentAccountContractData,
String offererAccountId,
byte[] offererTradeWalletPubKey,
byte[] offererMultiSigPubKey,
String offererContractAsJson,
String offererContractSignature,
String offererPayoutAddressString,
@ -57,7 +57,7 @@ public final class PublishDepositTxRequest extends TradeMessage {
super(tradeId);
this.offererPaymentAccountContractData = offererPaymentAccountContractData;
this.offererAccountId = offererAccountId;
this.offererTradeWalletPubKey = offererTradeWalletPubKey;
this.offererMultiSigPubKey = offererMultiSigPubKey;
this.offererContractAsJson = offererContractAsJson;
this.offererContractSignature = offererContractSignature;
this.offererPayoutAddressString = offererPayoutAddressString;
@ -65,7 +65,7 @@ public final class PublishDepositTxRequest extends TradeMessage {
this.offererInputs = offererInputs;
log.trace("offererPaymentAccount size " + Utilities.serialize(offererPaymentAccountContractData).length);
log.trace("offererTradeWalletPubKey size " + offererTradeWalletPubKey.length);
log.trace("offererTradeWalletPubKey size " + offererMultiSigPubKey.length);
log.trace("preparedDepositTx size " + preparedDepositTx.length);
log.trace("offererInputs size " + Utilities.serialize(new ArrayList<>(offererInputs)).length);
}

View file

@ -17,7 +17,9 @@
package io.bitsquare.trade.protocol.trade.tasks.buyer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.data.PreparedDepositTxAndOffererInputs;
import io.bitsquare.common.crypto.Hash;
import io.bitsquare.common.taskrunner.TaskRunner;
@ -51,6 +53,11 @@ public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask {
byte[] contractHash = Hash.getHash(trade.getContractAsJson());
trade.setContractHash(contractHash);
WalletService walletService = processModel.getWalletService();
String id = processModel.getOffer().getId();
AddressEntry buyerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG);
buyerMultiSigAddressEntry.setLockedTradeAmount(buyerInputAmount);
walletService.saveAddressEntryList();
PreparedDepositTxAndOffererInputs result = processModel.getTradeWalletService().offererCreatesAndSignsDepositTx(
true,
contractHash,
@ -59,10 +66,10 @@ public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask {
processModel.tradingPeer.getRawTransactionInputs(),
processModel.tradingPeer.getChangeOutputValue(),
processModel.tradingPeer.getChangeOutputAddress(),
processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
processModel.getTradeWalletPubKey(),
processModel.tradingPeer.getTradeWalletPubKey(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE),
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
buyerMultiSigAddressEntry.getPubKey(),
processModel.tradingPeer.getMultiSigPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()));
processModel.setPreparedDepositTx(result.depositTransaction);

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.buyer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
import io.bitsquare.trade.Trade;
@ -41,7 +42,7 @@ public class SendFiatTransferStartedMessage extends TradeTask {
processModel.tradingPeer.getPubKeyRing(),
new FiatTransferStartedMessage(
processModel.getId(),
processModel.getAddressEntry().getAddressString(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
processModel.getMyAddress()
),
new SendMailboxMessageListener() {

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.buyer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
@ -48,11 +49,12 @@ public class SignAndFinalizePayoutTx extends TradeTask {
processModel.tradingPeer.getSignature(),
buyerPayoutAmount,
sellerPayoutAmount,
processModel.getAddressEntry(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG),
processModel.tradingPeer.getPayoutAddressString(),
trade.getLockTimeAsBlockHeight(),
processModel.getTradeWalletPubKey(),
processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
processModel.tradingPeer.getMultiSigPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())
);

View file

@ -18,15 +18,22 @@
package io.bitsquare.trade.protocol.trade.tasks.buyer;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.data.RawTransactionInput;
import io.bitsquare.common.crypto.Hash;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.TradingPeer;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
public class SignAndPublishDepositTxAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsBuyer.class);
@ -47,14 +54,20 @@ public class SignAndPublishDepositTxAsBuyer extends TradeTask {
byte[] contractHash = Hash.getHash(trade.getContractAsJson());
trade.setContractHash(contractHash);
ArrayList<RawTransactionInput> buyerInputs = processModel.getRawTransactionInputs();
WalletService walletService = processModel.getWalletService();
AddressEntry buyerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG);
buyerMultiSigAddressEntry.setLockedTradeAmount(Coin.valueOf(buyerInputs.stream().mapToLong(input -> input.value).sum()));
walletService.saveAddressEntryList();
TradingPeer tradingPeer = processModel.tradingPeer;
processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
false,
contractHash,
processModel.getPreparedDepositTx(),
processModel.getRawTransactionInputs(),
processModel.tradingPeer.getRawTransactionInputs(),
processModel.getTradeWalletPubKey(),
processModel.tradingPeer.getTradeWalletPubKey(),
buyerInputs,
tradingPeer.getRawTransactionInputs(),
buyerMultiSigAddressEntry.getPubKey(),
tradingPeer.getMultiSigPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()),
new FutureCallback<Transaction>() {
@Override

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.buyer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.data.InputsAndChangeOutput;
import io.bitsquare.common.taskrunner.TaskRunner;
@ -38,9 +39,10 @@ public class TakerCreatesDepositTxInputsAsBuyer extends TradeTask {
try {
runInterceptHook();
Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades());
InputsAndChangeOutput result = processModel.getTradeWalletService()
.takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress());
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(
takerInputAmount,
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE),
processModel.getWalletService().getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress());
processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue);
processModel.setChangeOutputAddress(result.changeOutputAddress);

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.offerer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.common.crypto.Sig;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.common.util.Utilities;
@ -70,10 +71,10 @@ public class CreateAndSignContract extends TradeTask {
takerPaymentAccountContractData,
processModel.getPubKeyRing(),
taker.getPubKeyRing(),
processModel.getAddressEntry().getAddressString(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
taker.getPayoutAddressString(),
processModel.getTradeWalletPubKey(),
taker.getTradeWalletPubKey()
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
taker.getMultiSigPubKey()
);
String contractAsJson = Utilities.objectToJson(contract);
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);

View file

@ -57,7 +57,7 @@ public class ProcessPayDepositRequest extends TradeTask {
if (payDepositRequest.changeOutputAddress != null)
processModel.tradingPeer.setChangeOutputAddress(payDepositRequest.changeOutputAddress);
processModel.tradingPeer.setTradeWalletPubKey(checkNotNull(payDepositRequest.takerTradeWalletPubKey));
processModel.tradingPeer.setMultiSigPubKey(checkNotNull(payDepositRequest.takerMultiSigPubKey));
processModel.tradingPeer.setPayoutAddressString(nonEmptyStringOf(payDepositRequest.takerPayoutAddressString));
processModel.tradingPeer.setPubKeyRing(checkNotNull(payDepositRequest.takerPubKeyRing));

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.offerer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.messaging.SendDirectMessageListener;
import io.bitsquare.trade.Trade;
@ -40,10 +41,10 @@ public class SendPublishDepositTxRequest extends TradeTask {
processModel.getId(),
processModel.getPaymentAccountContractData(trade),
processModel.getAccountId(),
processModel.getTradeWalletPubKey(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
trade.getContractAsJson(),
trade.getOffererContractSignature(),
processModel.getAddressEntry().getAddressString(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
processModel.getPreparedDepositTx(),
processModel.getRawTransactionInputs()
);

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.offerer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.UserThread;
@ -51,7 +52,7 @@ public class SetupDepositBalanceListener extends TradeTask {
runInterceptHook();
WalletService walletService = processModel.getWalletService();
Address address = walletService.getTradeAddressEntry(trade.getId()).getAddress();
Address address = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {

View file

@ -17,7 +17,9 @@
package io.bitsquare.trade.protocol.trade.tasks.seller;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.data.PreparedDepositTxAndOffererInputs;
import io.bitsquare.common.crypto.Hash;
import io.bitsquare.common.taskrunner.TaskRunner;
@ -51,6 +53,11 @@ public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask {
byte[] contractHash = Hash.getHash(trade.getContractAsJson());
trade.setContractHash(contractHash);
WalletService walletService = processModel.getWalletService();
String id = processModel.getOffer().getId();
AddressEntry sellerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG);
sellerMultiSigAddressEntry.setLockedTradeAmount(sellerInputAmount);
walletService.saveAddressEntryList();
PreparedDepositTxAndOffererInputs result = processModel.getTradeWalletService().offererCreatesAndSignsDepositTx(
false,
contractHash,
@ -59,10 +66,10 @@ public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask {
processModel.tradingPeer.getRawTransactionInputs(),
processModel.tradingPeer.getChangeOutputValue(),
processModel.tradingPeer.getChangeOutputAddress(),
processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getTradeWalletPubKey(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE),
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
processModel.tradingPeer.getMultiSigPubKey(),
sellerMultiSigAddressEntry.getPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()));
processModel.setPreparedDepositTx(result.depositTransaction);

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.seller;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
import io.bitsquare.trade.Trade;
@ -40,7 +41,7 @@ public class SendFinalizePayoutTxRequest extends TradeTask {
FinalizePayoutTxRequest message = new FinalizePayoutTxRequest(
processModel.getId(),
processModel.getPayoutTxSignature(),
processModel.getAddressEntry().getAddressString(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
trade.getLockTimeAsBlockHeight(),
processModel.getMyAddress()
);

View file

@ -18,15 +18,22 @@
package io.bitsquare.trade.protocol.trade.tasks.seller;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.data.RawTransactionInput;
import io.bitsquare.common.crypto.Hash;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.TradingPeer;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
public class SignAndPublishDepositTxAsSeller extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsSeller.class);
@ -45,14 +52,21 @@ public class SignAndPublishDepositTxAsSeller extends TradeTask {
byte[] contractHash = Hash.getHash(trade.getContractAsJson());
trade.setContractHash(contractHash);
ArrayList<RawTransactionInput> sellerInputs = processModel.getRawTransactionInputs();
WalletService walletService = processModel.getWalletService();
AddressEntry sellerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG);
sellerMultiSigAddressEntry.setLockedTradeAmount(Coin.valueOf(sellerInputs.stream().mapToLong(input -> input.value).sum()));
walletService.saveAddressEntryList();
TradingPeer tradingPeer = processModel.tradingPeer;
processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
true,
contractHash,
processModel.getPreparedDepositTx(),
processModel.tradingPeer.getRawTransactionInputs(),
processModel.getRawTransactionInputs(),
processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getTradeWalletPubKey(),
tradingPeer.getRawTransactionInputs(),
sellerInputs,
tradingPeer.getMultiSigPubKey(),
sellerMultiSigAddressEntry.getPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()),
new FutureCallback<Transaction>() {
@Override

View file

@ -17,7 +17,9 @@
package io.bitsquare.trade.protocol.trade.tasks.seller;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
@ -53,15 +55,18 @@ public class SignPayoutTx extends TradeTask {
lockTimeAsBlockHeight = processModel.getTradeWalletService().getLastBlockSeenHeight() + lockTime;
trade.setLockTimeAsBlockHeight(lockTimeAsBlockHeight);
WalletService walletService = processModel.getWalletService();
String id = processModel.getOffer().getId();
byte[] payoutTxSignature = processModel.getTradeWalletService().sellerSignsPayoutTx(
trade.getDepositTx(),
buyerPayoutAmount,
sellerPayoutAmount,
processModel.tradingPeer.getPayoutAddressString(),
processModel.getAddressEntry(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG),
lockTimeAsBlockHeight,
processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getTradeWalletPubKey(),
processModel.tradingPeer.getMultiSigPubKey(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()));
processModel.setPayoutTxSignature(payoutTxSignature);

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.seller;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.data.InputsAndChangeOutput;
import io.bitsquare.common.taskrunner.TaskRunner;
@ -40,10 +41,9 @@ public class TakerCreatesDepositTxInputsAsSeller extends TradeTask {
if (trade.getTradeAmount() != null) {
Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount());
InputsAndChangeOutput result = processModel.getTradeWalletService()
.takerCreatesDepositsTxInputs(takerInputAmount,
processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress());
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount,
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE),
processModel.getWalletService().getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress());
processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue);
processModel.setChangeOutputAddress(result.changeOutputAddress);

View file

@ -18,7 +18,9 @@
package io.bitsquare.trade.protocol.trade.tasks.taker;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.trade.Trade;
@ -48,9 +50,12 @@ public class CreateTakeOfferFeeTx extends TradeTask {
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateTakeOfferFeeTx");
WalletService walletService = processModel.getWalletService();
String id = model.getOffer().getId();
Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx(
processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(),
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(),
walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(),
processModel.getFundsNeededForTrade(),
processModel.getUseSavingsWallet(),
FeePolicy.getTakeOfferFee(),

View file

@ -59,7 +59,7 @@ public class ProcessPublishDepositTxRequest extends TradeTask {
}
processModel.tradingPeer.setAccountId(nonEmptyStringOf(publishDepositTxRequest.offererAccountId));
processModel.tradingPeer.setTradeWalletPubKey(checkNotNull(publishDepositTxRequest.offererTradeWalletPubKey));
processModel.tradingPeer.setMultiSigPubKey(checkNotNull(publishDepositTxRequest.offererMultiSigPubKey));
processModel.tradingPeer.setContractAsJson(nonEmptyStringOf(publishDepositTxRequest.offererContractAsJson));
processModel.tradingPeer.setContractSignature(nonEmptyStringOf(publishDepositTxRequest.offererContractSignature));
processModel.tradingPeer.setPayoutAddressString(nonEmptyStringOf(publishDepositTxRequest.offererPayoutAddressString));

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.taker;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
import io.bitsquare.trade.Trade;
@ -43,7 +44,6 @@ public class SendPayDepositRequest extends TradeTask {
checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null");
checkNotNull(trade.getTakeOfferFeeTxId(), "TakeOfferFeeTxId must not be null");
checkNotNull(processModel.getAddressEntry(), "AddressEntry must not be null");
PayDepositRequest payDepositRequest = new PayDepositRequest(
processModel.getMyAddress(),
@ -52,8 +52,8 @@ public class SendPayDepositRequest extends TradeTask {
processModel.getRawTransactionInputs(),
processModel.getChangeOutputValue(),
processModel.getChangeOutputAddress(),
processModel.getTradeWalletPubKey(),
processModel.getAddressEntry().getAddressString(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
processModel.getPubKeyRing(),
processModel.getPaymentAccountContractData(trade),
processModel.getAccountId(),

View file

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.tasks.taker;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.common.crypto.Sig;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.common.util.Utilities;
@ -72,9 +73,9 @@ public class VerifyAndSignContract extends TradeTask {
offerer.getPubKeyRing(),
processModel.getPubKeyRing(),
offerer.getPayoutAddressString(),
processModel.getAddressEntry().getAddressString(),
offerer.getTradeWalletPubKey(),
processModel.getTradeWalletPubKey()
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(),
offerer.getMultiSigPubKey(),
processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey()
);
String contractAsJson = Utilities.objectToJson(contract);
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);