From 2cd3a5bae4b4cc135c1ad7acb814473fe2a225a7 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Thu, 7 Aug 2014 14:33:30 +0200 Subject: [PATCH] add offer validation --- .../java/io/bitsquare/btc/Restritions.java | 10 +++ .../io/bitsquare/btc/tasks/PayFeeTask.java | 86 ------------------- .../java/io/bitsquare/msg/MessageFacade.java | 25 +++--- .../createoffer/CreateOfferCoordinator.java | 21 +++-- .../createoffer/tasks/CreateOfferFeeTx.java | 7 +- .../createoffer/tasks/PublishOfferToDHT.java | 2 +- .../createoffer/tasks/ValidateOffer.java | 45 +++++++--- 7 files changed, 79 insertions(+), 117 deletions(-) create mode 100644 src/main/java/io/bitsquare/btc/Restritions.java delete mode 100644 src/main/java/io/bitsquare/btc/tasks/PayFeeTask.java diff --git a/src/main/java/io/bitsquare/btc/Restritions.java b/src/main/java/io/bitsquare/btc/Restritions.java new file mode 100644 index 0000000000..297d7bb2e4 --- /dev/null +++ b/src/main/java/io/bitsquare/btc/Restritions.java @@ -0,0 +1,10 @@ +package io.bitsquare.btc; + +import com.google.bitcoin.core.Coin; + +public class Restritions +{ + public static final Coin MIN_TRADE_AMOUNT = Coin.CENT; // 0.01 Bitcoins + + +} diff --git a/src/main/java/io/bitsquare/btc/tasks/PayFeeTask.java b/src/main/java/io/bitsquare/btc/tasks/PayFeeTask.java deleted file mode 100644 index 2adc2ae71e..0000000000 --- a/src/main/java/io/bitsquare/btc/tasks/PayFeeTask.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.bitsquare.btc.tasks; - -import com.google.bitcoin.core.*; -import com.google.common.util.concurrent.FutureCallback; -import io.bitsquare.btc.AddressBasedCoinSelector; -import io.bitsquare.btc.AddressEntry; -import io.bitsquare.btc.FeePolicy; -import io.bitsquare.btc.WalletFacade; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PayFeeTask -{ - private static final Logger log = LoggerFactory.getLogger(PayFeeTask.class); - - private enum State - { - INIT, - TX_COMPLETED, - TX_COMMITTED, - TX_BROAD_CASTED, - } - - private State state; - - public String start(Wallet wallet, FeePolicy feePolicy, AddressEntry addressEntry, FutureCallback callback) - { - state = State.INIT; - - Transaction tx = new Transaction(wallet.getParams()); - Coin fee = FeePolicy.CREATE_OFFER_FEE.subtract(FeePolicy.TX_FEE); - log.trace("fee: " + fee.toFriendlyString()); - tx.addOutput(fee, feePolicy.getAddressForCreateOfferFee()); - - Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); - sendRequest.shuffleOutputs = false; - // 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(wallet.getParams(), addressEntry, true); - sendRequest.changeAddress = addressEntry.getAddress(); - - try - { - Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); - state = State.TX_COMPLETED; - - - } catch (IllegalStateException e) - { - e.printStackTrace(); - } catch (InsufficientMoneyException e) - { - e.printStackTrace(); - } catch (IllegalArgumentException e) - { - e.printStackTrace(); - } catch (Wallet.DustySendRequested e) - { - e.printStackTrace(); - } catch (Wallet.CouldNotAdjustDownwards e) - { - e.printStackTrace(); - } catch (Wallet.ExceededMaxTransactionSize e) - { - e.printStackTrace(); - } catch (VerificationException e) - { - e.printStackTrace(); - } - /* - * @throws IllegalStateException if no transaction broadcaster has been configured. - * @throws InsufficientMoneyException if the request could not be completed due to not enough balance. - * @throws IllegalArgumentException if you try and complete the same SendRequest twice - * @throws DustySendRequested if the resultant transaction would violate the dust rules (an output that's too small to be worthwhile) - * @throws CouldNotAdjustDownwards if emptying the wallet was requested and the output can't be shrunk for fees without violating a protocol rule. - * @throws ExceededMaxTransactionSize if the resultant transaction is too big for Bitcoin to process (try breaking up the amounts of value) - */ - - - WalletFacade.printInputs("payCreateOfferFee", tx); - log.debug("tx=" + tx); - - return tx.getHashAsString(); - } - - -} diff --git a/src/main/java/io/bitsquare/msg/MessageFacade.java b/src/main/java/io/bitsquare/msg/MessageFacade.java index eca928b07e..2a3d31c671 100644 --- a/src/main/java/io/bitsquare/msg/MessageFacade.java +++ b/src/main/java/io/bitsquare/msg/MessageFacade.java @@ -46,7 +46,7 @@ public class MessageFacade implements MessageBroker public static interface AddOfferListener { - void onComplete(String offerId); + void onComplete(); void onFailed(String reason, Throwable throwable); } @@ -54,6 +54,11 @@ public class MessageFacade implements MessageBroker private static final Logger log = LoggerFactory.getLogger(MessageFacade.class); private static final String ARBITRATORS_ROOT = "ArbitratorsRoot"; + public P2PNode getP2pNode() + { + return p2pNode; + } + private P2PNode p2pNode; private final List orderBookListeners = new ArrayList<>(); @@ -165,16 +170,16 @@ public class MessageFacade implements MessageBroker @Override public void operationComplete(BaseFuture future) throws Exception { - Platform.runLater(() -> { - addOfferListener.onComplete(offer.getId()); - orderBookListeners.stream().forEach(listener -> listener.onOfferAdded(data, future.isSuccess())); - - // TODO will be removed when we don't use polling anymore - setDirty(locationKey); - }); if (future.isSuccess()) { - Platform.runLater(() -> log.trace("Add offer to DHT was successful. Stored data: [key: " + locationKey + ", value: " + data + "]")); + Platform.runLater(() -> { + addOfferListener.onComplete(); + orderBookListeners.stream().forEach(listener -> listener.onOfferAdded(data, future.isSuccess())); + + // TODO will be removed when we don't use polling anymore + setDirty(locationKey); + log.trace("Add offer to DHT was successful. Stored data: [key: " + locationKey + ", value: " + data + "]"); + }); } else { @@ -487,7 +492,7 @@ public class MessageFacade implements MessageBroker } } - private void setDirty(Number160 locationKey) + public void setDirty(Number160 locationKey) { // we don't want to get an update from dirty for own changes, so update the lastTimeStamp to omit a change trigger lastTimeStamp = System.currentTimeMillis(); diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java index e55f1fc9ec..a3343919ac 100644 --- a/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java @@ -2,16 +2,17 @@ package io.bitsquare.trade.protocol.createoffer; import com.google.bitcoin.core.Transaction; import io.bitsquare.btc.WalletFacade; +import io.bitsquare.trade.protocol.createoffer.tasks.BroadCastOfferFeeTx; +import io.bitsquare.trade.protocol.createoffer.tasks.CreateOfferFeeTx; import io.bitsquare.msg.MessageFacade; +import io.bitsquare.trade.protocol.createoffer.tasks.PublishOfferToDHT; import io.bitsquare.storage.Persistence; import io.bitsquare.trade.Offer; import io.bitsquare.trade.handlers.FaultHandler; import io.bitsquare.trade.handlers.TransactionResultHandler; -import io.bitsquare.trade.protocol.createoffer.tasks.BroadCastOfferFeeTx; -import io.bitsquare.trade.protocol.createoffer.tasks.CreateOfferFeeTx; -import io.bitsquare.trade.protocol.createoffer.tasks.PublishOfferToDHT; import io.bitsquare.trade.protocol.createoffer.tasks.ValidateOffer; import java.io.Serializable; +import javax.annotation.concurrent.Immutable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +21,8 @@ import org.slf4j.LoggerFactory; * It holds the model.state of the current process and support recovery if possible. */ //TODO recover policy, timer + +@Immutable public class CreateOfferCoordinator { public enum State @@ -32,11 +35,14 @@ public class CreateOfferCoordinator OFFER_PUBLISHED_TO_DHT } + /** + * The model is not immutable but only exposed to the CreateOfferCoordinator + */ static class Model implements Serializable { private static final long serialVersionUID = 3027720554200858916L; - private Persistence persistence; + private final Persistence persistence; private State state; //TODO use tx id and make Transaction transient Transaction transaction; @@ -54,6 +60,8 @@ public class CreateOfferCoordinator public void setState(State state) { this.state = state; + + //TODO will have performance issues, but could be handled inside the persistence solution (queue up save requests and exec. them on dedicated thread) persistence.write(this); } } @@ -94,7 +102,7 @@ public class CreateOfferCoordinator private void onOfferValidated() { model.setState(State.VALIDATED); - CreateOfferFeeTx.run(this::onOfferFeeTxCreated, this::onFailed, walletFacade, offer); + CreateOfferFeeTx.run(this::onOfferFeeTxCreated, this::onFailed, walletFacade, offer.getId()); } private void onOfferFeeTxCreated(Transaction transaction) @@ -114,6 +122,9 @@ public class CreateOfferCoordinator private void onOfferPublishedToDHT() { model.setState(State.OFFER_PUBLISHED_TO_DHT); + // TODO + //orderBookListeners.stream().forEach(listener -> listener.onOfferAdded(data, future.isSuccess())); + resultHandler.onResult(model.transaction); } diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java index a78ba823b9..f93fb1df28 100644 --- a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java @@ -2,9 +2,8 @@ package io.bitsquare.trade.protocol.createoffer.tasks; import com.google.bitcoin.core.InsufficientMoneyException; import io.bitsquare.btc.WalletFacade; -import io.bitsquare.trade.Offer; -import io.bitsquare.trade.handlers.TransactionResultHandler; import io.bitsquare.trade.handlers.FaultHandler; +import io.bitsquare.trade.handlers.TransactionResultHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,11 +11,11 @@ public class CreateOfferFeeTx { private static final Logger log = LoggerFactory.getLogger(CreateOfferFeeTx.class); - public static void run(TransactionResultHandler resultHandler, FaultHandler faultHandler, WalletFacade walletFacade, Offer offer) + public static void run(TransactionResultHandler resultHandler, FaultHandler faultHandler, WalletFacade walletFacade, String offerId) { try { - resultHandler.onResult(walletFacade.createOfferFeeTx(offer.getId())); + resultHandler.onResult(walletFacade.createOfferFeeTx(offerId)); } catch (InsufficientMoneyException e) { faultHandler.onFault("Offer fee payment failed because there is insufficient money in the trade pocket. ", e); diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/PublishOfferToDHT.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/PublishOfferToDHT.java index 2a602fd3d4..126f4dfee6 100644 --- a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/PublishOfferToDHT.java +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/PublishOfferToDHT.java @@ -16,7 +16,7 @@ public class PublishOfferToDHT messageFacade.addOffer(offer, new MessageFacade.AddOfferListener() { @Override - public void onComplete(String offerId) + public void onComplete() { resultHandler.onResult(); } diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java index a439f4288b..20edd47a7a 100644 --- a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java @@ -1,31 +1,54 @@ package io.bitsquare.trade.protocol.createoffer.tasks; +import io.bitsquare.btc.Restritions; import io.bitsquare.trade.Offer; import io.bitsquare.trade.handlers.FaultHandler; import io.bitsquare.trade.handlers.ResultHandler; +import javax.annotation.concurrent.Immutable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +@Immutable public class ValidateOffer { private static final Logger log = LoggerFactory.getLogger(ValidateOffer.class); public static void run(ResultHandler resultHandler, FaultHandler faultHandler, Offer offer) { - boolean isValid = offer.getAmount().isGreaterThan(offer.getAmount()); + try + { + checkNotNull(offer.getAcceptedCountries()); + checkNotNull(offer.getAcceptedLanguageLocales()); + checkNotNull(offer.getAmount()); + checkNotNull(offer.getArbitrator()); + checkNotNull(offer.getBankAccountCountry()); + checkNotNull(offer.getBankAccountId()); + checkNotNull(offer.getCollateral()); + checkNotNull(offer.getCreationDate()); + checkNotNull(offer.getCurrency()); + checkNotNull(offer.getDirection()); + checkNotNull(offer.getId()); + checkNotNull(offer.getMessagePublicKey()); + checkNotNull(offer.getMinAmount()); + checkNotNull(offer.getOfferFeePaymentTxID()); + checkNotNull(offer.getPrice()); - if (offer.getAmount().compareTo(offer.getMinAmount()) < 0) - { - faultHandler.onFault("Offer validation failed: Min. amount is larger than amount.", new Exception("Offer validation failed: Min. amount is larger than amount.")); + checkArgument(offer.getAcceptedCountries().size() > 0); + checkArgument(offer.getAcceptedLanguageLocales().size() > 0); + checkArgument(offer.getAmount().isGreaterThan(Restritions.MIN_TRADE_AMOUNT)); + checkArgument(offer.getAmount().compareTo(Restritions.MIN_TRADE_AMOUNT) >= 0); + checkArgument(offer.getAmount().compareTo(offer.getMinAmount()) >= 0); + checkArgument(offer.getCollateral() > 0); + checkArgument(offer.getPrice() > 0); + // TODO when offer is flattened continue here... - } - else if (offer.getAcceptedCountries() == null || offer.getAcceptedCountries().size() == 0) - { - faultHandler.onFault("Offer validation failed: No accepted countries are defined.", new Exception("Offer validation failed: No accepted countries are defined.")); - } //TODO... - else - { resultHandler.onResult(); + } catch (Throwable t) + { + faultHandler.onFault("Offer validation failed with exception: " + t.getMessage(), t); } } }