mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-02 19:56:23 -04:00
Reorganize savingswallet
This commit is contained in:
parent
8832f2873b
commit
7ce5beb54f
58 changed files with 1143 additions and 652 deletions
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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{" +
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package io.bitsquare.btc;
|
||||
|
||||
public class AddressEntryException extends Exception {
|
||||
public AddressEntryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
130
core/src/main/java/io/bitsquare/btc/BitsquareCoinSelector.java
Normal file
130
core/src/main/java/io/bitsquare/btc/BitsquareCoinSelector.java
Normal 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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue