Add interfaces and generic support

Make API more clear and helps for test setup
This commit is contained in:
chimp1984 2020-09-02 11:12:49 -05:00
parent adbb262c1b
commit 37241f98d8
No known key found for this signature in database
GPG Key ID: 9801B4EC591F90E3
10 changed files with 234 additions and 42 deletions

View File

@ -0,0 +1,21 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.txproof;
public interface AssetTxProofModel {
}

View File

@ -0,0 +1,22 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.txproof;
public interface AssetTxProofParser<T extends AssetTxProofModel, R extends AssetTxProofRequest.Result> {
R parse(T model, String jsonTxt);
}

View File

@ -0,0 +1,31 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.txproof;
import bisq.common.handlers.FaultHandler;
import java.util.function.Consumer;
public interface AssetTxProofRequest<R extends AssetTxProofRequest.Result> {
interface Result {
}
void requestFromService(Consumer<R> resultHandler, FaultHandler faultHandler);
void terminate();
}

View File

@ -0,0 +1,28 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.txproof;
import bisq.common.handlers.FaultHandler;
import java.util.function.Consumer;
public interface AssetTxProofRequestsPerTrade {
void requestFromAllServices(Consumer<AssetTxProofResult> resultHandler, FaultHandler faultHandler);
void terminate();
}

View File

@ -0,0 +1,34 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.txproof;
import bisq.core.trade.Trade;
import bisq.common.handlers.FaultHandler;
import java.util.List;
import java.util.function.Consumer;
public interface AssetTxProofService {
void maybeStartRequests(Trade trade,
List<Trade> activeTrades,
Consumer<AssetTxProofResult> resultHandler,
FaultHandler faultHandler);
void shutDown();
}

View File

@ -21,6 +21,7 @@ import bisq.core.monetary.Volume;
import bisq.core.payment.payload.AssetsAccountPayload;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.txproof.AssetTxProofModel;
import bisq.core.user.AutoConfirmSettings;
import bisq.common.app.DevEnv;
@ -39,7 +40,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
@SuppressWarnings("SpellCheckingInspection")
@Slf4j
@Value
public class XmrTxProofModel {
public class XmrTxProofModel implements AssetTxProofModel {
// Those are values from a valid tx which are set automatically if DevEnv.isDevMode is enabled
public static final String DEV_ADDRESS = "85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub";
public static final String DEV_TX_KEY = "f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906";
@ -81,6 +82,7 @@ public class XmrTxProofModel {
}
// Used only for testing
// TODO Use mocking framework in testing to avoid that constructor...
@VisibleForTesting
XmrTxProofModel(String tradeId,
String txHash,

View File

@ -17,6 +17,8 @@
package bisq.core.trade.txproof.xmr;
import bisq.core.trade.txproof.AssetTxProofParser;
import bisq.asset.CryptoNoteUtils;
import bisq.common.app.DevEnv;
@ -27,15 +29,27 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class XmrTxProofParser {
class XmrTxProofParser implements AssetTxProofParser<XmrTxProofModel, XmrTxProofRequest.Result> {
@Inject
public XmrTxProofParser() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("SpellCheckingInspection")
static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) {
String txHash = xmrTxProofModel.getTxHash();
@Override
public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) {
String txHash = model.getTxHash();
try {
JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class);
if (json == null) {
@ -61,7 +75,7 @@ class XmrTxProofParser {
if (jsonAddress == null) {
return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing address field"));
} else {
String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrTxProofModel.getRecipientAddress());
String expectedAddressHex = CryptoNoteUtils.convertToRawHex(model.getRecipientAddress());
if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) {
log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex);
return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.ADDRESS_INVALID);
@ -84,8 +98,8 @@ class XmrTxProofParser {
if (jsonViewkey == null) {
return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing viewkey field"));
} else {
if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrTxProofModel.getTxKey())) {
log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrTxProofModel.getTxKey());
if (!jsonViewkey.getAsString().equalsIgnoreCase(model.getTxKey())) {
log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), model.getTxKey());
return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TX_KEY_INVALID);
}
}
@ -96,7 +110,7 @@ class XmrTxProofParser {
if (jsonTimestamp == null) {
return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_timestamp field"));
} else {
long tradeDateSeconds = xmrTxProofModel.getTradeDate().getTime() / 1000;
long tradeDateSeconds = model.getTradeDate().getTime() / 1000;
long difference = tradeDateSeconds - jsonTimestamp.getAsLong();
// Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync
if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) {
@ -127,7 +141,7 @@ class XmrTxProofParser {
if (out.get("match").getAsBoolean()) {
anyMatchFound = true;
long jsonAmount = out.get("amount").getAsLong();
amountMatches = jsonAmount == xmrTxProofModel.getAmount();
amountMatches = jsonAmount == model.getAmount();
if (amountMatches) {
break;
}
@ -144,7 +158,7 @@ class XmrTxProofParser {
return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING);
}
int confirmsRequired = xmrTxProofModel.getNumRequiredConfirmations();
int confirmsRequired = model.getNumRequiredConfirmations();
if (confirmations < confirmsRequired) {
return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations));
} else {

View File

@ -17,6 +17,8 @@
package bisq.core.trade.txproof.xmr;
import bisq.core.trade.txproof.AssetTxProofRequest;
import bisq.network.Socks5ProxyProvider;
import bisq.common.UserThread;
@ -49,13 +51,13 @@ import javax.annotation.Nullable;
*/
@Slf4j
@EqualsAndHashCode
class XmrTxProofRequest {
class XmrTxProofRequest implements AssetTxProofRequest<XmrTxProofRequest.Result> {
///////////////////////////////////////////////////////////////////////////////////////////
// Enums
///////////////////////////////////////////////////////////////////////////////////////////
enum Result {
enum Result implements AssetTxProofRequest.Result {
PENDING, // Tx not visible in network yet, unconfirmed or not enough confirmations
SUCCESS, // Proof succeeded
FAILED, // Proof failed
@ -124,15 +126,20 @@ class XmrTxProofRequest {
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
// Static fields
///////////////////////////////////////////////////////////////////////////////////////////
private static final long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90);
private static final long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12);
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final ListeningExecutorService executorService = Utilities.getListeningExecutorService(
"XmrTransferProofRequester", 3, 5, 10 * 60);
private final XmrTxProofHttpClient httpClient;
private final XmrTxProofParser xmrTxProofParser;
private final XmrTxProofModel xmrTxProofModel;
private final long firstRequest;
@ -146,7 +153,10 @@ class XmrTxProofRequest {
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, XmrTxProofModel xmrTxProofModel) {
XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider,
XmrTxProofParser xmrTxProofParser,
XmrTxProofModel xmrTxProofModel) {
this.xmrTxProofParser = xmrTxProofParser;
this.xmrTxProofModel = xmrTxProofModel;
httpClient = new XmrTxProofHttpClient(socks5ProxyProvider);
@ -165,7 +175,8 @@ class XmrTxProofRequest {
///////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("SpellCheckingInspection")
public void start(Consumer<Result> resultHandler, FaultHandler faultHandler) {
@Override
public void requestFromService(Consumer<Result> resultHandler, FaultHandler faultHandler) {
if (terminated) {
// the XmrTransferProofService has asked us to terminate i.e. not make any further api calls
// this scenario may happen if a re-request is scheduled from the callback below
@ -185,7 +196,7 @@ class XmrTxProofRequest {
String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION);
String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json));
log.info("Response json from {}\n{}", this, prettyJson);
Result result = XmrTxProofParser.parse(xmrTxProofModel, json);
Result result = xmrTxProofParser.parse(xmrTxProofModel, json);
log.info("Result from {}\n{}", this, result);
return result;
});
@ -205,9 +216,9 @@ class XmrTxProofRequest {
log.warn("{} took too long without a success or failure/error result We give up. " +
"Might be that the transaction was never published.", this);
// If we reached out timeout we return with an error.
UserThread.execute(() -> resultHandler.accept(Result.ERROR.with(Detail.NO_RESULTS_TIMEOUT)));
UserThread.execute(() -> resultHandler.accept(XmrTxProofRequest.Result.ERROR.with(Detail.NO_RESULTS_TIMEOUT)));
} else {
UserThread.runAfter(() -> start(resultHandler, faultHandler), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS);
UserThread.runAfter(() -> requestFromService(resultHandler, faultHandler), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS);
// We update our listeners
UserThread.execute(() -> resultHandler.accept(result));
}
@ -232,12 +243,13 @@ class XmrTxProofRequest {
String errorMessage = this + " failed with error " + throwable.toString();
faultHandler.handleFault(errorMessage, throwable);
UserThread.execute(() ->
resultHandler.accept(Result.ERROR.with(Detail.CONNECTION_FAILURE.error(errorMessage))));
resultHandler.accept(XmrTxProofRequest.Result.ERROR.with(Detail.CONNECTION_FAILURE.error(errorMessage))));
}
});
}
void terminate() {
@Override
public void terminate() {
terminated = true;
}
@ -247,6 +259,10 @@ class XmrTxProofRequest {
return "Request at: " + xmrTxProofModel.getServiceAddress() + " for trade: " + xmrTxProofModel.getTradeId();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private String getShortId() {
return Utilities.getShortId(xmrTxProofModel.getTradeId()) + " @ " +
xmrTxProofModel.getServiceAddress().substring(0, 6);

View File

@ -19,6 +19,7 @@ package bisq.core.trade.txproof.xmr;
import bisq.core.locale.Res;
import bisq.core.trade.Trade;
import bisq.core.trade.txproof.AssetTxProofRequestsPerTrade;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.user.AutoConfirmSettings;
@ -42,8 +43,9 @@ import lombok.extern.slf4j.Slf4j;
* Handles the XMR tx proof requests for multiple services per trade.
*/
@Slf4j
class XmrTxProofRequestsPerTrade {
class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
private final Socks5ProxyProvider socks5ProxyProvider;
private final XmrTxProofParser xmrTxProofParser;
private final Trade trade;
private final AutoConfirmSettings autoConfirmSettings;
@ -54,15 +56,28 @@ class XmrTxProofRequestsPerTrade {
private ChangeListener<Trade.State> tradeStateListener;
private AutoConfirmSettings.Listener autoConfirmSettingsListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider,
XmrTxProofParser xmrTxProofParser,
Trade trade,
AutoConfirmSettings autoConfirmSettings) {
this.socks5ProxyProvider = socks5ProxyProvider;
this.xmrTxProofParser = xmrTxProofParser;
this.trade = trade;
this.autoConfirmSettings = autoConfirmSettings;
}
void requestFromAllServices(Consumer<AssetTxProofResult> resultHandler, FaultHandler faultHandler) {
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void requestFromAllServices(Consumer<AssetTxProofResult> resultHandler, FaultHandler faultHandler) {
// We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started
// it will have no impact on serviceAddresses and numRequiredSuccessResults.
// Thought numRequiredConfirmations can be changed during request process and will be read from
@ -103,12 +118,12 @@ class XmrTxProofRequestsPerTrade {
for (String serviceAddress : serviceAddresses) {
XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings);
XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model);
XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, xmrTxProofParser, model);
log.info("{} created", request);
requests.add(request);
request.start(result -> {
request.requestFromService(result -> {
AssetTxProofResult assetTxProofResult;
if (trade.isPayoutPublished()) {
assetTxProofResult = AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED;
@ -162,6 +177,23 @@ class XmrTxProofRequestsPerTrade {
}
}
@Override
public void terminate() {
requests.forEach(XmrTxProofRequest::terminate);
requests.clear();
if (tradeStateListener != null) {
UserThread.execute(() -> trade.stateProperty().removeListener(tradeStateListener));
}
if (autoConfirmSettingsListener != null) {
UserThread.execute(() -> autoConfirmSettings.removeListener(autoConfirmSettingsListener));
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void callResultHandlerAndMaybeTerminate(Consumer<AssetTxProofResult> resultHandler,
AssetTxProofResult assetTxProofResult) {
resultHandler.accept(assetTxProofResult);
@ -191,17 +223,6 @@ class XmrTxProofRequestsPerTrade {
.details(detailString);
}
void terminate() {
requests.forEach(XmrTxProofRequest::terminate);
requests.clear();
if (tradeStateListener != null) {
UserThread.execute(() -> trade.stateProperty().removeListener(tradeStateListener));
}
if (autoConfirmSettingsListener != null) {
UserThread.execute(() -> autoConfirmSettings.removeListener(autoConfirmSettingsListener));
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Validation

View File

@ -26,6 +26,7 @@ import bisq.core.trade.Trade;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.trade.txproof.AssetTxProofService;
import bisq.core.user.AutoConfirmSettings;
import bisq.core.user.Preferences;
@ -55,9 +56,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
@Slf4j
@Singleton
public class XmrTxProofService {
public class XmrTxProofService implements AssetTxProofService {
private final FilterManager filterManager;
private final Socks5ProxyProvider socks5ProxyProvider;
private final XmrTxProofParser xmrTxProofParser;
private final Preferences preferences;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
@ -79,7 +81,8 @@ public class XmrTxProofService {
FailedTradesManager failedTradesManager,
P2PService p2PService,
WalletsSetup walletsSetup,
Socks5ProxyProvider socks5ProxyProvider) {
Socks5ProxyProvider socks5ProxyProvider,
XmrTxProofParser xmrTxProofParser) {
this.filterManager = filterManager;
this.preferences = preferences;
this.closedTradableManager = closedTradableManager;
@ -87,6 +90,7 @@ public class XmrTxProofService {
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
this.socks5ProxyProvider = socks5ProxyProvider;
this.xmrTxProofParser = xmrTxProofParser;
}
@ -94,6 +98,7 @@ public class XmrTxProofService {
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void maybeStartRequests(Trade trade,
List<Trade> activeTrades,
Consumer<AssetTxProofResult> resultHandler,
@ -134,6 +139,7 @@ public class XmrTxProofService {
}
XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(socks5ProxyProvider,
xmrTxProofParser,
trade,
autoConfirmSettings);
servicesByTradeId.put(trade.getId(), service);
@ -142,7 +148,7 @@ public class XmrTxProofService {
trade.setAssetTxProofResult(assetTxProofResult);
if (assetTxProofResult.isTerminal()) {
cleanUp(trade);
servicesByTradeId.remove(trade.getId());
}
resultHandler.accept(assetTxProofResult);
@ -150,15 +156,12 @@ public class XmrTxProofService {
faultHandler);
}
@Override
public void shutDown() {
servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate);
servicesByTradeId.clear();
}
private void cleanUp(Trade trade) {
servicesByTradeId.remove(trade.getId());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Validation