Add create proposal view and fee payment tx. Add tabs for other dao subviews

This commit is contained in:
Manfred Karrer 2016-12-12 21:51:23 +01:00
parent 44f8baecef
commit 790563e997
27 changed files with 1565 additions and 123 deletions

View file

@ -116,4 +116,9 @@ public class FeeService {
return Coin.valueOf(feeData.takeOfferFee);
}
public Coin getCreateProposalFee() {
//TODO
return Coin.valueOf(1000);
}
}

View file

@ -0,0 +1,128 @@
/*
* 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.wallet;
import com.google.common.annotations.VisibleForTesting;
import org.bitcoinj.core.*;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.wallet.CoinSelection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 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 BtcProposalFeeCoinSelector {
private static final Logger log = LoggerFactory.getLogger(BtcProposalFeeCoinSelector.class);
private Wallet wallet;
public BtcProposalFeeCoinSelector(Wallet wallet) {
this.wallet = wallet;
}
public CoinSelection getCoinSelection(Coin target) throws InsufficientMoneyException, ChangeBelowDustException {
return select(target, getCandidates());
}
private List<TransactionOutput> getCandidates() {
return wallet.calculateAllSpendCandidates(true, true);
}
private CoinSelection select(Coin target, List<TransactionOutput> candidates) throws InsufficientMoneyException, ChangeBelowDustException {
ArrayList<TransactionOutput> selected = new ArrayList<>();
// Sort the inputs by value so we get the lowest values first.
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))
sortOutputsByValue(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;
long dust = Transaction.MIN_NONDUST_OUTPUT.value;
long targetValue = target.value;
for (TransactionOutput output : sortedOutputs) {
if (total >= targetValue) {
long change = total - targetValue;
// If we get a change it must not be smaller than dust
if (change >= dust || change == 0)
break;
}
// Only pick chain-included transactions, or transactions that are ours and pending.
if (!isSelectable(output.getParentTransaction()))
continue;
selected.add(output);
total += output.getValue().value;
}
long missing = targetValue - total;
if (missing > 0)
throw new InsufficientMoneyException(Coin.valueOf(missing));
long change = total - targetValue;
if (change > 0 && change < dust)
throw new ChangeBelowDustException(Coin.valueOf(change));
return new CoinSelection(Coin.valueOf(total), selected);
}
@VisibleForTesting
void sortOutputsByValue(ArrayList<TransactionOutput> outputs) {
Collections.sort(outputs, (a, b) -> {
int c2 = a.getValue().compareTo(b.getValue());
if (c2 != 0) return c2;
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
Sha256Hash parentTransactionHash = a.getParentTransactionHash();
Sha256Hash parentTransactionHash1 = b.getParentTransactionHash();
if (parentTransactionHash != null && parentTransactionHash1 != null) {
BigInteger aHash = parentTransactionHash.toBigInteger();
BigInteger bHash = parentTransactionHash1.toBigInteger();
return aHash.compareTo(bHash);
} else {
return 0;
}
});
}
public boolean isSelectable(Transaction tx) {
if (tx != null) {
// Only pick chain-included transactions, or transactions that are ours and pending.
TransactionConfidence confidence = tx.getConfidence();
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
type.equals(TransactionConfidence.ConfidenceType.PENDING) &&
confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
// In regtest mode we expect to have only one peer, so we won't see transactions propagate.
// TODO: The value 1 below dates from a time when transactions we broadcast *to* were counted, set to 0
(confidence.numBroadcastPeers() > 1 || tx.getParams() == RegTestParams.get());
} else {
return true;
}
}
}

View file

@ -17,7 +17,6 @@
package io.bitsquare.btc.wallet;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
@ -30,6 +29,7 @@ import io.bitsquare.user.Preferences;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.script.ScriptBuilder;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -43,6 +43,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
@ -160,8 +161,10 @@ public class BtcWalletService extends WalletService {
wallet.completeTx(sendRequest);
resultTx = sendRequest.tx;
//TODO add estimated signature size
txSize = resultTx.bitcoinSerialize().length;
}
while (counter < 10 && Math.abs(resultTx.getFee().value - txFeePerByte.multiply(resultTx.bitcoinSerialize().length).value) > 1000);
while (counter < 10 && Math.abs(resultTx.getFee().value - txFeePerByte.multiply(txSize).value) > 1000);
if (counter == 10) {
log.error("Could not calculate the fee. Tx=" + resultTx);
}
@ -185,6 +188,119 @@ public class BtcWalletService extends WalletService {
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Add mining fee input and mandatory change output for prepared proposal fee tx
///////////////////////////////////////////////////////////////////////////////////////////
public Transaction addInputAndOutputToPreparedSquProposalFeeTx(Transaction preparedSquTx, byte[] hash) throws
TransactionVerificationException, WalletException, InsufficientFundsException {
// preparedSquTx has following structure:
// inputs [1-n] SQU inputs
// outputs [0-1] SQU change output
// mining fee: SQU proposal fee
try {
int counter = 0;
final int sigSizePerInput = 106;
int txSize = 203; // typical size for a tx with 2 unsigned inputs and 3 outputs
final Coin txFeePerByte = feeService.getTxFeePerByte();
Coin valueToForceChange = Coin.ZERO;
Address changeAddress = getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
checkNotNull(changeAddress, "changeAddress must not be null");
final BtcCoinSelector coinSelector = new BtcCoinSelector(params, changeAddress);
final List<TransactionInput> preparedSquTxInputs = preparedSquTx.getInputs();
final List<TransactionOutput> preparedSquTxOutputs = preparedSquTx.getOutputs();
Transaction resultTx = null;
int numInputs = preparedSquTxInputs.size() + 1;
boolean validTx;
do {
counter++;
if (counter >= 10) {
checkNotNull(resultTx, "resultTx must not be null");
log.error("Could not calculate the fee. Tx=" + resultTx);
break;
}
Transaction tx = new Transaction(params);
preparedSquTxInputs.stream().forEach(tx::addInput);
if (valueToForceChange.isZero()) {
preparedSquTxOutputs.stream().forEach(tx::addOutput);
} else {
//TODO test that case
// We have only 1 output at preparedSquTxOutputs otherwise the valueToForceChange must be 0
checkArgument(preparedSquTxOutputs.size() == 1);
tx.addOutput(valueToForceChange, changeAddress);
tx.addOutput(preparedSquTxOutputs.get(0));
}
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
// Need to be false as it would try to sign all inputs (SQU inputs are not in this wallet)
sendRequest.signInputs = false;
sendRequest.ensureMinRequiredFee = false;
sendRequest.feePerKb = Coin.ZERO;
Coin fee = txFeePerByte.multiply(txSize + sigSizePerInput * numInputs);
sendRequest.fee = fee;
sendRequest.coinSelector = coinSelector;
sendRequest.changeAddress = changeAddress;
wallet.completeTx(sendRequest);
Transaction txWithBtcInputs = sendRequest.tx;
// add OP_RETURN output
txWithBtcInputs.addOutput(new TransactionOutput(params, txWithBtcInputs, Coin.ZERO, ScriptBuilder.createOpReturnScript(hash).getProgram()));
// As we cannot remove an outut we recreate the tx
resultTx = new Transaction(params);
txWithBtcInputs.getInputs().stream().forEach(resultTx::addInput);
txWithBtcInputs.getOutputs().stream().forEach(resultTx::addOutput);
numInputs = resultTx.getInputs().size();
printTx("resultTx", resultTx);
txSize = resultTx.bitcoinSerialize().length + sigSizePerInput * numInputs;
log.error("txSize " + txSize);
// We need min. 1 output beside op_return
// calculated fee must be inside of a tolerance range with tx fee
final int tolerance = 1000;
final long txFeeAsLong = resultTx.getFee().value;
final long calculatedFeeAsLong = txFeePerByte.multiply(txSize + sigSizePerInput * numInputs).value;
final boolean correctOutputs = resultTx.getOutputs().size() > 1;
validTx = correctOutputs && Math.abs(txFeeAsLong - calculatedFeeAsLong) < tolerance;
if (resultTx.getOutputs().size() == 1) {
// We have the rare case that both inputs equalled the required fees, so both did not render
// a change output.
// We need to add artificially a change output as the OP_RETURN is not allowed as only output
valueToForceChange = Transaction.MIN_NONDUST_OUTPUT;
}
}
while (!validTx);
// Sign all BTC inputs
int startIndex = preparedSquTxInputs.size();
int endIndex = resultTx.getInputs().size();
for (int i = startIndex; i < endIndex; i++) {
TransactionInput txIn = resultTx.getInputs().get(i);
signTransactionInput(resultTx, txIn, i);
checkScriptSig(resultTx, txIn, i);
}
checkWalletConsistency();
verifyTransaction(resultTx);
printTx("Signed resultTx", resultTx);
return resultTx;
} catch (InsufficientMoneyException e) {
throw new InsufficientFundsException("The fees for that preparedSquTx exceed the available funds " +
"or the resulting output value is below the min. dust value:\n" +
"Missing " + (e.missing != null ? e.missing.toFriendlyString() : "null"));
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// AddressEntry
@ -571,7 +687,7 @@ public class BtcWalletService extends WalletService {
AddressEntry.Context context) throws AddressFormatException,
AddressEntryException {
Transaction tx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isAboveDust(amount, fee),
checkArgument(Restrictions.isAboveDust(amount, fee),
"The amount is too low (dust limit).");
tx.addOutput(amount.subtract(fee), new Address(params, toAddress));
@ -599,7 +715,7 @@ public class BtcWalletService extends WalletService {
@Nullable KeyParameter aesKey) throws
AddressFormatException, AddressEntryException {
Transaction tx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
checkArgument(Restrictions.isAboveDust(amount),
"The amount is too low (dust limit).");
tx.addOutput(amount.subtract(fee), new Address(params, toAddress));

View file

@ -0,0 +1,37 @@
/*
* 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.wallet;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChangeBelowDustException extends Exception {
private static final Logger log = LoggerFactory.getLogger(ChangeBelowDustException.class);
private final Coin change;
public ChangeBelowDustException(Coin change) {
this(change, "Change value would be below min. dust. Change: " + change + " satoshis");
}
public ChangeBelowDustException(Coin change, String message) {
super(message);
this.change = change;
}
}

View file

@ -0,0 +1,132 @@
/*
* 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.wallet;
import com.google.common.annotations.VisibleForTesting;
import org.bitcoinj.core.*;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.wallet.CoinSelection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 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 SquFeeCoinSelector {
private static final Logger log = LoggerFactory.getLogger(SquFeeCoinSelector.class);
private Wallet wallet;
private final boolean allowUnconfirmedSpend;
public SquFeeCoinSelector(Wallet wallet, boolean allowUnconfirmedSpend) {
this.wallet = wallet;
this.allowUnconfirmedSpend = allowUnconfirmedSpend;
}
public CoinSelection getCoinSelection(Coin target) throws InsufficientMoneyException, ChangeBelowDustException {
return select(target, getCandidates());
}
private List<TransactionOutput> getCandidates() {
//TODO
wallet.setUTXOProvider(null);
return wallet.calculateAllSpendCandidates(true, true);
}
private CoinSelection select(Coin target, List<TransactionOutput> candidates) throws InsufficientMoneyException, ChangeBelowDustException {
ArrayList<TransactionOutput> selected = new ArrayList<>();
// Sort the inputs by value so we get the lowest values first.
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))
sortOutputsByValue(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;
long dust = Transaction.MIN_NONDUST_OUTPUT.value;
long targetValue = target.value;
for (TransactionOutput output : sortedOutputs) {
if (total >= targetValue) {
long change = total - targetValue;
// If we get a change it must not be smaller than dust
if (change >= dust || change == 0)
break;
}
// Only pick chain-included transactions, or transactions that are ours and pending.
if (!isSelectable(output.getParentTransaction()))
continue;
selected.add(output);
total += output.getValue().value;
}
long missing = targetValue - total;
if (missing > 0)
throw new InsufficientMoneyException(Coin.valueOf(missing));
long change = total - targetValue;
if (change > 0 && change < dust)
throw new ChangeBelowDustException(Coin.valueOf(change));
return new CoinSelection(Coin.valueOf(total), selected);
}
@VisibleForTesting
void sortOutputsByValue(ArrayList<TransactionOutput> outputs) {
Collections.sort(outputs, (a, b) -> {
int c2 = a.getValue().compareTo(b.getValue());
if (c2 != 0) return c2;
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
Sha256Hash parentTransactionHash = a.getParentTransactionHash();
Sha256Hash parentTransactionHash1 = b.getParentTransactionHash();
if (parentTransactionHash != null && parentTransactionHash1 != null) {
BigInteger aHash = parentTransactionHash.toBigInteger();
BigInteger bHash = parentTransactionHash1.toBigInteger();
return aHash.compareTo(bHash);
} else {
return 0;
}
});
}
public boolean isSelectable(Transaction tx) {
if (tx != null) {
// Only pick chain-included transactions, or transactions that are ours and pending.
TransactionConfidence confidence = tx.getConfidence();
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
boolean isMine = confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
// In regtest mode we expect to have only one peer, so we won't see transactions propagate.
// TODO: The value 1 below dates from a time when transactions we broadcast *to* were counted, set to 0
(confidence.numBroadcastPeers() > 1 || tx.getParams() == RegTestParams.get());
return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
(type.equals(TransactionConfidence.ConfidenceType.PENDING) && (allowUnconfirmedSpend || isMine));
} else {
return true;
}
}
}

View file

@ -23,15 +23,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class SquUTXOProvider implements UTXOProvider {
private static final Logger log = LoggerFactory.getLogger(SquUTXOProvider.class);
private Map<String, UTXO> utxoByAddressAsStringMap = new HashMap<>();
private Set<String> addressesAsStringSet = new HashSet<>();
private Set<UTXO> transactionOutputSet = new HashSet<>();
private NetworkParameters params;
@Inject
@ -45,22 +46,7 @@ public class SquUTXOProvider implements UTXOProvider {
@Override
public List<UTXO> getOpenTransactionOutputs(List<Address> addresses) throws UTXOProviderException {
// addressesAsStringSet.clear();
// addressesAsStringSet.addAll(addresses.stream().map(Address::toString).collect(Collectors.toSet()));
List<UTXO> foundOutputs = addresses.stream().map(e -> utxoByAddressAsStringMap.get(e.toString())).filter(e -> e != null).collect(Collectors.toList());
// List<UTXO> foundOutputs = transactionOutputSet.stream().filter(e -> addressesAsStringSet.contains(e.getAddress())).collect(Collectors.toList());
/*
List<UTXO> foundOutputs = new ArrayList<UTXO>();
Collection<UTXO> outputsList = transactionOutputMap.values();
for (UTXO output : outputsList) {
for (Address address : addresses) {
if (output.getAddress().equals(address.toString())) {
foundOutputs.add(output);
}
}
}*/
return foundOutputs;
return addresses.stream().map(e -> utxoByAddressAsStringMap.get(e.toString())).filter(e -> e != null).collect(Collectors.toList());
}
@Override
@ -73,23 +59,4 @@ public class SquUTXOProvider implements UTXOProvider {
public NetworkParameters getParams() {
return params;
}
/*
private String getScriptAddress(@Nullable Script script) {
String address = "";
try {
if (script != null) {
address = script.getToAddress(params, true).toString();
}
} catch (Exception e) {
}
return address;
}
private Script getScript(byte[] scriptBytes) {
try {
return new Script(scriptBytes);
} catch (Exception e) {
return new Script(new byte[0]);
}
}*/
}

View file

@ -17,7 +17,6 @@
package io.bitsquare.btc.wallet;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import io.bitsquare.btc.Restrictions;
@ -30,6 +29,8 @@ import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.*;
import org.bitcoinj.script.Script;
import org.bitcoinj.wallet.CoinSelection;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -38,6 +39,8 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
/**
* WalletService handles all non trade specific wallet and bitcoin related services.
* It startup the wallet app kit and initialized the wallet.
@ -139,16 +142,20 @@ public class SquWalletService extends WalletService {
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
}
public void requestSquUtxo(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
public void requestSquUtxo(@Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
Set<UTXO> utxoSet = squUtxoFeedService.getUtxoSet();
if (utxoSet != null)
squUTXOProvider.setUtxoSet(utxoSet);
squUtxoFeedService.requestSquUtxo(utxos -> {
squUTXOProvider.setUtxoSet(utxos);
resultHandler.handleResult();
if (resultHandler != null)
resultHandler.handleResult();
},
(errorMessage, throwable) -> errorMessageHandler.handleErrorMessage(errorMessage));
(errorMessage, throwable) -> {
if (errorMessageHandler != null)
errorMessageHandler.handleErrorMessage(errorMessage);
});
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -161,7 +168,7 @@ public class SquWalletService extends WalletService {
InsufficientMoneyException, WalletException, TransactionVerificationException {
Transaction tx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isAboveDust(receiverAmount),
checkArgument(Restrictions.isAboveDust(receiverAmount),
"The amount is too low (dust limit).");
tx.addOutput(receiverAmount, new Address(params, receiverAddress));
@ -203,4 +210,44 @@ public class SquWalletService extends WalletService {
return tx;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Burn SQU for proposal fee
///////////////////////////////////////////////////////////////////////////////////////////
public Transaction getPreparedProposalFeeTx(Coin createProposalFee) throws WalletException, TransactionVerificationException, InsufficientMoneyException, ChangeBelowDustException {
Transaction tx = new Transaction(params);
SquFeeCoinSelector squFeeCoinSelector = new SquFeeCoinSelector(wallet, true);
CoinSelection coinSelection = squFeeCoinSelector.getCoinSelection(createProposalFee);
coinSelection.gathered.stream().forEach(tx::addInput);
Coin change = coinSelection.valueGathered.subtract(createProposalFee);
if (change.isPositive())
tx.addOutput(change, wallet.freshReceiveAddress());
printTx("preparedProposalFeeTx", tx);
return tx;
}
public void signAndBroadcastProposalFeeTx(Transaction tx, FutureCallback<Transaction> callback) throws WalletException, TransactionVerificationException {
// Sign all SQU inputs
int endIndex = tx.getInputs().size();
for (int i = 0; i < endIndex; i++) {
TransactionInput txIn = tx.getInputs().get(i);
TransactionOutput connectedOutput = txIn.getConnectedOutput();
if (connectedOutput != null && connectedOutput.isMine(wallet)) {
signTransactionInput(tx, txIn, i);
checkScriptSig(tx, txIn, i);
}
}
checkWalletConsistency();
verifyTransaction(tx);
printTx("signAndBroadcastProposalFeeTx", tx);
wallet.commitTx(tx);
checkWalletConsistency();
Futures.addCallback(walletsSetup.getPeerGroup().broadcastTransaction(tx).future(), callback);
printTx("signAndBroadcastProposalFeeTx", tx);
}
}

View file

@ -59,13 +59,13 @@ import static com.google.common.base.Preconditions.checkState;
public abstract class WalletService {
private static final Logger log = LoggerFactory.getLogger(WalletService.class);
private final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<BalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<BalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
protected final WalletsSetup walletsSetup;
private final Preferences preferences;
private final FeeService feeService;
protected final Preferences preferences;
protected final FeeService feeService;
protected final WalletEventListener walletEventListener = new BitsquareWalletEventListener();
protected final NetworkParameters params;
@ -324,7 +324,7 @@ public abstract class WalletService {
return null;
}
private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
protected TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
List<TransactionOutput> mergedOutputs = getOutputsWithConnectedOutputs(tx);
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
@ -339,7 +339,7 @@ public abstract class WalletService {
}
private List<TransactionOutput> getOutputsWithConnectedOutputs(Transaction tx) {
protected List<TransactionOutput> getOutputsWithConnectedOutputs(Transaction tx) {
List<TransactionOutput> transactionOutputs = tx.getOutputs();
List<TransactionOutput> connectedOutputs = new ArrayList<>();
@ -359,7 +359,7 @@ public abstract class WalletService {
}
private TransactionConfidence getMostRecentConfidence(List<TransactionConfidence> transactionConfidenceList) {
protected TransactionConfidence getMostRecentConfidence(List<TransactionConfidence> transactionConfidenceList) {
TransactionConfidence transactionConfidence = null;
for (TransactionConfidence confidence : transactionConfidenceList) {
if (confidence != null) {
@ -390,7 +390,7 @@ public abstract class WalletService {
return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO;
}
private Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
protected Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
Coin balance = Coin.ZERO;
for (TransactionOutput transactionOutput : transactionOutputs) {
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
@ -478,7 +478,7 @@ public abstract class WalletService {
StringBuilder sb = new StringBuilder();
sb.append("\n").append(tracePrefix).append(":").append("\n").append(tx.toString()).append("\n");
sb.append("Size: ").append(tx.bitcoinSerialize().length);
log.info(sb.toString());
log.error(sb.toString());
}

View file

@ -0,0 +1,132 @@
/*
* 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.dao.proposals;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.storage.payload.LazyProcessedStoragePayload;
import io.bitsquare.p2p.storage.payload.PersistedStoragePayload;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Proposal implements LazyProcessedStoragePayload, PersistedStoragePayload {
private static final Logger log = LoggerFactory.getLogger(Proposal.class);
public static final long TTL = TimeUnit.DAYS.toMillis(30);
public final String uid;
public final String text;
public final String title;
public final String category;
public final String description;
public final String link;
public final Date startDate;
public final Date endDate;
public final Coin requestedBtc;
public final String btcAddress;
public final NodeAddress nodeAddress;
private PublicKey p2pStorageSignaturePubKey;
public final byte[] squPubKey;
// Signature of proposal data without signature, hash and feeTxId
public byte[] signature;
// Sha256Hash of proposal data including the signature but without feeTxId and hash
public byte[] hash;
// Set after we signed and set the hash. The hash is used in the OP_RETURN of the fee tx
public String feeTxId;
public Proposal(String uid,
String text,
String title,
String category,
String description,
String link,
Date startDate,
Date endDate,
Coin requestedBtc,
String btcAddress,
NodeAddress nodeAddress,
PublicKey p2pStorageSignaturePubKey,
byte[] squPubKey) {
this.uid = uid;
this.text = text;
this.title = title;
this.category = category;
this.description = description;
this.link = link;
this.startDate = startDate;
this.endDate = endDate;
this.requestedBtc = requestedBtc;
this.btcAddress = btcAddress;
this.nodeAddress = nodeAddress;
this.p2pStorageSignaturePubKey = p2pStorageSignaturePubKey;
this.squPubKey = squPubKey;
}
@Override
public long getTTL() {
return TTL;
}
@Override
public PublicKey getOwnerPubKey() {
return p2pStorageSignaturePubKey;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
public void setHash(byte[] hash) {
this.hash = hash;
}
// Called after sig and hash
public void setFeeTxId(String feeTxId) {
this.feeTxId = feeTxId;
}
@Override
public String toString() {
return "Proposal{" +
"uid='" + uid + '\'' +
", text='" + text + '\'' +
", title='" + title + '\'' +
", category='" + category + '\'' +
", description='" + description + '\'' +
", link='" + link + '\'' +
", startDate=" + startDate +
", endDate=" + endDate +
", requestedBtc=" + requestedBtc +
", btcAddress='" + btcAddress + '\'' +
", nodeAddress=" + nodeAddress +
", pubKey=" + Arrays.toString(squPubKey) +
", signature=" + signature +
", hash=" + hash +
", feeTxId=" + feeTxId +
'}';
}
}

View file

@ -17,6 +17,7 @@
package io.bitsquare.gui.main.dao;
import io.bitsquare.btc.wallet.SquWalletService;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.Activatable;
import io.bitsquare.gui.common.view.*;
@ -45,15 +46,17 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
private final ViewLoader viewLoader;
private final Navigation navigation;
private Preferences preferences;
private SquWalletService squWalletService;
private Tab selectedTab;
private TokenWalletView tokenWalletView;
@Inject
private DaoView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) {
private DaoView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences, SquWalletService squWalletService) {
this.viewLoader = viewLoader;
this.navigation = navigation;
this.preferences = preferences;
this.squWalletService = squWalletService;
}
@Override
@ -87,6 +90,8 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
navigation.addListener(navigationListener);
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
squWalletService.requestSquUtxo(null, null);
if (navigation.getCurrentPath().size() == 2 && navigation.getCurrentPath().get(1) == DaoView.class) {
Tab selectedItem = root.getSelectionModel().getSelectedItem();
if (selectedItem == tokenWalletTab)

View file

@ -18,21 +18,14 @@
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.ProposalsView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="10.0" left="25.0" top="30.0" right="25"/>
</padding>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.ProposalsView"
prefHeight="660.0" prefWidth="1000.0"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" spacing="5" prefWidth="240" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="20"/>
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="290"
AnchorPane.topAnchor="30"/>
</AnchorPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -17,44 +17,171 @@
package io.bitsquare.gui.main.dao.proposals;
import io.bitsquare.gui.common.model.Activatable;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.util.BSFormatter;
import javafx.scene.layout.GridPane;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.*;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.dao.DaoView;
import io.bitsquare.gui.main.dao.proposals.active.ActiveProposalsView;
import io.bitsquare.gui.main.dao.proposals.create.CreateProposalView;
import io.bitsquare.gui.main.dao.proposals.past.PastProposalsView;
import io.bitsquare.gui.util.Colors;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Paint;
import javax.inject.Inject;
@FxmlView
public class ProposalsView extends ActivatableView<GridPane, Activatable> {
public class ProposalsView extends ActivatableViewAndModel {
private final ViewLoader viewLoader;
private final Navigation navigation;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialisation
///////////////////////////////////////////////////////////////////////////////////////////
private MenuItem create, active, past;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
@Inject
public ProposalsView(BSFormatter formatter) {
super();
private ProposalsView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
}
listener = viewPath -> {
if (viewPath.size() != 4 || viewPath.indexOf(ProposalsView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
ToggleGroup toggleGroup = new ToggleGroup();
create = new MenuItem(navigation, toggleGroup, "Create Proposal", CreateProposalView.class, AwesomeIcon.EDIT);
active = new MenuItem(navigation, toggleGroup, "Active proposals", ActiveProposalsView.class, AwesomeIcon.ARROW_RIGHT);
past = new MenuItem(navigation, toggleGroup, "Past proposals", PastProposalsView.class, AwesomeIcon.LIST);
leftVBox.getChildren().addAll(create, active, past);
}
@Override
protected void activate() {
create.activate();
active.activate();
past.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(ProposalsView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = CreateProposalView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(ProposalsView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@Override
protected void deactivate() {
navigation.removeListener(listener);
create.deactivate();
active.deactivate();
past.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
///////////////////////////////////////////////////////////////////////////////////////////
// Initialize
///////////////////////////////////////////////////////////////////////////////////////////
if (view instanceof CreateProposalView) create.setSelected(true);
else if (view instanceof ActiveProposalsView) active.setSelected(true);
else if (view instanceof PastProposalsView) past.setSelected(true);
}
public Class<? extends View> getSelectedViewClass() {
return selectedViewClass;
}
}
class MenuItem extends ToggleButton {
private final ChangeListener<Boolean> selectedPropertyChangeListener;
private final ChangeListener<Boolean> disablePropertyChangeListener;
private final Navigation navigation;
private final Class<? extends View> viewClass;
MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass, AwesomeIcon awesomeIcon) {
this.navigation = navigation;
this.viewClass = viewClass;
setToggleGroup(toggleGroup);
setText(title);
setId("account-settings-item-background-active");
setPrefHeight(40);
setPrefWidth(240);
setAlignment(Pos.CENTER_LEFT);
Label icon = new Label();
AwesomeDude.setIcon(icon, awesomeIcon);
icon.setTextFill(Paint.valueOf("#333"));
icon.setPadding(new Insets(0, 5, 0, 0));
icon.setAlignment(Pos.CENTER);
icon.setMinWidth(25);
icon.setMaxWidth(25);
setGraphic(icon);
selectedPropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-selected");
icon.setTextFill(Colors.BLUE);
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
disablePropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-disabled");
icon.setTextFill(Paint.valueOf("#ccc"));
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
}
public void activate() {
setOnAction((event) -> navigation.navigateTo(MainView.class, DaoView.class, ProposalsView.class, viewClass));
selectedProperty().addListener(selectedPropertyChangeListener);
disableProperty().addListener(disablePropertyChangeListener);
}
public void deactivate() {
setOnAction(null);
selectedProperty().removeListener(selectedPropertyChangeListener);
disableProperty().removeListener(disablePropertyChangeListener);
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.active.ActiveProposalsView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,50 @@
/*
* 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.gui.main.dao.proposals.active;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import javafx.scene.layout.GridPane;
import javax.inject.Inject;
@FxmlView
public class ActiveProposalsView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ActiveProposalsView() {
}
@Override
public void initialize() {
}
@Override
protected void activate() {
}
@Override
protected void deactivate() {
}
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.create.CreateProposalView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,195 @@
/*
* 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.gui.main.dao.proposals.create;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.btc.InsufficientFundsException;
import io.bitsquare.btc.exceptions.TransactionVerificationException;
import io.bitsquare.btc.exceptions.WalletException;
import io.bitsquare.btc.provider.fee.FeeService;
import io.bitsquare.btc.wallet.BtcWalletService;
import io.bitsquare.btc.wallet.ChangeBelowDustException;
import io.bitsquare.btc.wallet.SquWalletService;
import io.bitsquare.common.ByteArrayUtils;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.dao.proposals.Proposal;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.wallet.KeyChain;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.security.PublicKey;
import java.util.Date;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.bitsquare.gui.util.FormBuilder.*;
@FxmlView
public class CreateProposalView extends ActivatableView<GridPane, Void> {
private final NodeAddress nodeAddress;
private final PublicKey p2pStorageSignaturePubKey;
private InputTextField nameTextField, titleTextField, categoryTextField, descriptionTextField, linkTextField,
startDateTextField, endDateTextField, requestedBTCTextField, btcAddressTextField;
private Button createButton;
private final SquWalletService squWalletService;
private final BtcWalletService btcWalletService;
private FeeService feeService;
private final P2PService p2PService;
private final BSFormatter btcFormatter;
private int gridRow = 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private CreateProposalView(SquWalletService squWalletService, BtcWalletService btcWalletService, FeeService feeService, P2PService p2PService, KeyRing keyRing, BSFormatter btcFormatter) {
this.squWalletService = squWalletService;
this.btcWalletService = btcWalletService;
this.feeService = feeService;
this.p2PService = p2PService;
this.btcFormatter = btcFormatter;
nodeAddress = p2PService.getAddress();
p2pStorageSignaturePubKey = keyRing.getPubKeyRing().getSignaturePubKey();
}
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 9, "Create new funding proposal");
nameTextField = addLabelInputTextField(root, gridRow, "Name/nickname:", Layout.FIRST_ROW_DISTANCE).second;
titleTextField = addLabelInputTextField(root, ++gridRow, "Title:").second;
categoryTextField = addLabelInputTextField(root, ++gridRow, "Category:").second;
descriptionTextField = addLabelInputTextField(root, ++gridRow, "Description:").second;
linkTextField = addLabelInputTextField(root, ++gridRow, "Link to detail info:").second;
startDateTextField = addLabelInputTextField(root, ++gridRow, "Start date:").second;
endDateTextField = addLabelInputTextField(root, ++gridRow, "Delivery date:").second;
requestedBTCTextField = addLabelInputTextField(root, ++gridRow, "Requested funds in BTC:").second;
btcAddressTextField = addLabelInputTextField(root, ++gridRow, "Bitcoin address:").second;
createButton = addButtonAfterGroup(root, ++gridRow, "Create proposal");
//TODO
nameTextField.setText("Mock name");
titleTextField.setText("Mock Title");
categoryTextField.setText("Mock Category");
descriptionTextField.setText("Mock Description");
linkTextField.setText("Mock Link");
startDateTextField.setText("Mock Start date");
endDateTextField.setText("Mock Delivery date");
requestedBTCTextField.setText("Mock Requested funds");
btcAddressTextField.setText("Mock Bitcoin address");
}
@Override
protected void activate() {
createButton.setOnAction(event -> {
DeterministicKey squKeyPair = squWalletService.getWallet().freshKey(KeyChain.KeyPurpose.AUTHENTICATION);
checkNotNull(squKeyPair, "squKeyPair must not be null");
//TODO
Date startDate = new Date();
Date endDate = new Date();
Proposal proposal = new Proposal(UUID.randomUUID().toString(),
nameTextField.getText(),
titleTextField.getText(),
categoryTextField.getText(),
descriptionTextField.getText(),
linkTextField.getText(),
startDate,
endDate,
btcFormatter.parseToCoin(requestedBTCTextField.getText()),
btcAddressTextField.getText(),
nodeAddress,
p2pStorageSignaturePubKey,
squKeyPair.getPubKey()
);
Sha256Hash hash = Sha256Hash.of(ByteArrayUtils.objectToByteArray(proposal));
proposal.setSignature(squKeyPair.sign(hash).encodeToDER());
hash = Sha256Hash.of(ByteArrayUtils.objectToByteArray(proposal));
proposal.setHash(hash.getBytes());
try {
Coin createProposalFee = feeService.getCreateProposalFee();
Transaction preparedSendTx = squWalletService.getPreparedProposalFeeTx(createProposalFee);
Transaction txWithBtcFee = btcWalletService.addInputAndOutputToPreparedSquProposalFeeTx(preparedSendTx, hash.getBytes());
squWalletService.signAndBroadcastProposalFeeTx(txWithBtcFee, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction result) {
checkNotNull(result, "Transaction must not be null at signAndBroadcastProposalFeeTx callback.");
publishToP2PNetwork(proposal);
clearForm();
new Popup<>().confirmation("Your proposal has been successfully published.").show();
}
@Override
public void onFailure(@NotNull Throwable t) {
log.error(t.toString());
new Popup<>().warning(t.toString()).show();
}
});
} catch (InsufficientFundsException |
TransactionVerificationException | WalletException | InsufficientMoneyException | ChangeBelowDustException e) {
log.error(e.toString());
e.printStackTrace();
new Popup<>().warning(e.toString()).show();
}
});
}
private void clearForm() {
nameTextField.setText("");
titleTextField.setText("");
categoryTextField.setText("");
descriptionTextField.setText("");
linkTextField.setText("");
startDateTextField.setText("");
endDateTextField.setText("");
requestedBTCTextField.setText("");
btcAddressTextField.setText("");
}
private void publishToP2PNetwork(Proposal proposal) {
p2PService.addData(proposal, true);
}
@Override
protected void deactivate() {
createButton.setOnAction(null);
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.past.PastProposalsView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,50 @@
/*
* 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.gui.main.dao.proposals.past;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import javafx.scene.layout.GridPane;
import javax.inject.Inject;
@FxmlView
public class PastProposalsView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private PastProposalsView() {
}
@Override
public void initialize() {
}
@Override
protected void activate() {
}
@Override
protected void deactivate() {
}
}

View file

@ -18,21 +18,14 @@
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.VotingView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="10.0" left="25.0" top="30.0" right="25"/>
</padding>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.VotingView"
prefHeight="660.0" prefWidth="1000.0"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" spacing="5" prefWidth="240" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="20"/>
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="290"
AnchorPane.topAnchor="30"/>
</AnchorPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -17,44 +17,171 @@
package io.bitsquare.gui.main.dao.voting;
import io.bitsquare.gui.common.model.Activatable;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.util.BSFormatter;
import javafx.scene.layout.GridPane;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.*;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.dao.DaoView;
import io.bitsquare.gui.main.dao.voting.dashboard.VotingDashboardView;
import io.bitsquare.gui.main.dao.voting.history.VotingHistoryView;
import io.bitsquare.gui.main.dao.voting.vote.VoteView;
import io.bitsquare.gui.util.Colors;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Paint;
import javax.inject.Inject;
@FxmlView
public class VotingView extends ActivatableView<GridPane, Activatable> {
public class VotingView extends ActivatableViewAndModel {
private final ViewLoader viewLoader;
private final Navigation navigation;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialisation
///////////////////////////////////////////////////////////////////////////////////////////
private MenuItem dashboard, vote, history;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
@Inject
public VotingView(BSFormatter formatter) {
super();
private VotingView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
}
listener = viewPath -> {
if (viewPath.size() != 4 || viewPath.indexOf(VotingView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
ToggleGroup toggleGroup = new ToggleGroup();
dashboard = new MenuItem(navigation, toggleGroup, "Dashboard", VotingDashboardView.class, AwesomeIcon.DASHBOARD);
vote = new MenuItem(navigation, toggleGroup, "Vote", VoteView.class, AwesomeIcon.EDIT);
history = new MenuItem(navigation, toggleGroup, "History", VotingHistoryView.class, AwesomeIcon.TABLE);
leftVBox.getChildren().addAll(dashboard, vote, history);
}
@Override
protected void activate() {
dashboard.activate();
vote.activate();
history.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(VotingView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = VotingDashboardView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(VotingView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@Override
protected void deactivate() {
navigation.removeListener(listener);
dashboard.deactivate();
vote.deactivate();
history.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
///////////////////////////////////////////////////////////////////////////////////////////
// Initialize
///////////////////////////////////////////////////////////////////////////////////////////
if (view instanceof VotingDashboardView) dashboard.setSelected(true);
else if (view instanceof VoteView) vote.setSelected(true);
else if (view instanceof VotingHistoryView) history.setSelected(true);
}
public Class<? extends View> getSelectedViewClass() {
return selectedViewClass;
}
}
class MenuItem extends ToggleButton {
private final ChangeListener<Boolean> selectedPropertyChangeListener;
private final ChangeListener<Boolean> disablePropertyChangeListener;
private final Navigation navigation;
private final Class<? extends View> viewClass;
MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass, AwesomeIcon awesomeIcon) {
this.navigation = navigation;
this.viewClass = viewClass;
setToggleGroup(toggleGroup);
setText(title);
setId("account-settings-item-background-active");
setPrefHeight(40);
setPrefWidth(240);
setAlignment(Pos.CENTER_LEFT);
Label icon = new Label();
AwesomeDude.setIcon(icon, awesomeIcon);
icon.setTextFill(Paint.valueOf("#333"));
icon.setPadding(new Insets(0, 5, 0, 0));
icon.setAlignment(Pos.CENTER);
icon.setMinWidth(25);
icon.setMaxWidth(25);
setGraphic(icon);
selectedPropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-selected");
icon.setTextFill(Colors.BLUE);
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
disablePropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-disabled");
icon.setTextFill(Paint.valueOf("#ccc"));
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
}
public void activate() {
setOnAction((event) -> navigation.navigateTo(MainView.class, DaoView.class, VotingView.class, viewClass));
selectedProperty().addListener(selectedPropertyChangeListener);
disableProperty().addListener(disablePropertyChangeListener);
}
public void deactivate() {
setOnAction(null);
selectedProperty().removeListener(selectedPropertyChangeListener);
disableProperty().removeListener(disablePropertyChangeListener);
}
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.dashboard.VotingDashboardView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,50 @@
/*
* 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.gui.main.dao.voting.dashboard;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import javafx.scene.layout.GridPane;
import javax.inject.Inject;
@FxmlView
public class VotingDashboardView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private VotingDashboardView() {
}
@Override
public void initialize() {
}
@Override
protected void activate() {
}
@Override
protected void deactivate() {
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.history.VotingHistoryView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,50 @@
/*
* 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.gui.main.dao.voting.history;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import javafx.scene.layout.GridPane;
import javax.inject.Inject;
@FxmlView
public class VotingHistoryView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private VotingHistoryView() {
}
@Override
public void initialize() {
}
@Override
protected void activate() {
}
@Override
protected void deactivate() {
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.vote.VoteView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,50 @@
/*
* 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.gui.main.dao.voting.vote;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import javafx.scene.layout.GridPane;
import javax.inject.Inject;
@FxmlView
public class VoteView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private VoteView() {
}
@Override
public void initialize() {
}
@Override
protected void activate() {
}
@Override
protected void deactivate() {
}
}

View file

@ -93,7 +93,7 @@ public class TokenWalletView extends ActivatableViewAndModel {
if (viewPath.size() == 3 && viewPath.indexOf(TokenWalletView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = TokenSendView.class;
selectedViewClass = TokenDashboardView.class;
loadView(selectedViewClass);