add offer validation

This commit is contained in:
Manfred Karrer 2014-08-07 14:33:30 +02:00
parent 7069fb6c4d
commit 2cd3a5bae4
7 changed files with 79 additions and 117 deletions

View File

@ -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
}

View File

@ -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<Transaction> 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();
}
}

View File

@ -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<OrderBookListener> 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();

View File

@ -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);
}

View File

@ -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);

View File

@ -16,7 +16,7 @@ public class PublishOfferToDHT
messageFacade.addOffer(offer, new MessageFacade.AddOfferListener()
{
@Override
public void onComplete(String offerId)
public void onComplete()
{
resultHandler.onResult();
}

View File

@ -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);
}
}
}