mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 14:42:37 +01:00
Merge pull request #4846 from bisq-network/release/v1.5.0
Release/v1.5.0
This commit is contained in:
commit
3cc9e63593
181 changed files with 9905 additions and 3032 deletions
|
@ -28,7 +28,7 @@ configure(subprojects) {
|
|||
|
||||
ext { // in alphabetical order
|
||||
bcVersion = '1.63'
|
||||
bitcoinjVersion = 'a733034'
|
||||
bitcoinjVersion = '7752cb7'
|
||||
btcdCli4jVersion = '27b94333'
|
||||
codecVersion = '1.13'
|
||||
easybindVersion = '1.0.3'
|
||||
|
@ -386,7 +386,7 @@ configure(project(':desktop')) {
|
|||
apply plugin: 'witness'
|
||||
apply from: '../gradle/witness/gradle-witness.gradle'
|
||||
|
||||
version = '1.4.2-SNAPSHOT'
|
||||
version = '1.5.0-SNAPSHOT'
|
||||
|
||||
mainClassName = 'bisq.desktop.app.BisqAppMain'
|
||||
|
||||
|
|
|
@ -30,14 +30,14 @@ public class Version {
|
|||
// VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update
|
||||
// Therefore all sub versions start again with 1
|
||||
// We use semantic versioning with major, minor and patch
|
||||
public static final String VERSION = "1.4.2";
|
||||
public static final String VERSION = "1.5.0";
|
||||
|
||||
/**
|
||||
* Holds a list of the tagged resource files for optimizing the getData requests.
|
||||
* This must not contain each version but only those where we add new version-tagged resource files for
|
||||
* historical data stores.
|
||||
*/
|
||||
public static final List<String> HISTORICAL_RESOURCE_FILE_VERSION_TAGS = Arrays.asList("1.4.0");
|
||||
public static final List<String> HISTORICAL_RESOURCE_FILE_VERSION_TAGS = Arrays.asList("1.4.0", "1.5.0");
|
||||
|
||||
public static int getMajorVersion(String version) {
|
||||
return getSubVersion(version, 0);
|
||||
|
@ -92,10 +92,13 @@ public class Version {
|
|||
|
||||
// The version no. of the current protocol. The offer holds that version.
|
||||
// A taker will check the version of the offers to see if his version is compatible.
|
||||
// Offers created with the old version will become invalid and have to be canceled.
|
||||
// For the switch to version 2, offers created with the old version will become invalid and have to be canceled.
|
||||
// For the switch to version 3, offers created with the old version can be migrated to version 3 just by opening
|
||||
// the Bisq app.
|
||||
// VERSION = 0.5.0 -> TRADE_PROTOCOL_VERSION = 1
|
||||
// Version 1.2.2 -> TRADE_PROTOCOL_VERSION = 2
|
||||
public static final int TRADE_PROTOCOL_VERSION = 2;
|
||||
// Version 1.5.0 -> TRADE_PROTOCOL_VERSION = 3
|
||||
public static final int TRADE_PROTOCOL_VERSION = 3;
|
||||
private static int p2pMessageVersion;
|
||||
|
||||
public static final String BSQ_TX_VERSION = "1";
|
||||
|
|
|
@ -72,7 +72,7 @@ public enum BaseCurrencyNetwork {
|
|||
return "BTC_REGTEST".equals(name());
|
||||
}
|
||||
|
||||
public long getDefaultMinFeePerByte() {
|
||||
public long getDefaultMinFeePerVbyte() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import java.util.HashSet;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
@ -80,43 +81,59 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static final Map<String, PersistenceManager<?>> ALL_PERSISTENCE_MANAGERS = new HashMap<>();
|
||||
public static boolean FLUSH_ALL_DATA_TO_DISK_CALLED = false;
|
||||
|
||||
// We don't know from which thread we are called so we map back to user thread
|
||||
|
||||
// We require being called only once from the global shutdown routine. As the shutdown routine has a timeout
|
||||
// and error condition where we call the method as well beside the standard path and it could be that those
|
||||
// alternative code paths call our method after it was called already, so it is a valid but rare case.
|
||||
// We add a guard to prevent repeated calls.
|
||||
public static void flushAllDataToDisk(ResultHandler completeHandler) {
|
||||
log.info("Start flushAllDataToDisk at shutdown");
|
||||
AtomicInteger openInstances = new AtomicInteger(ALL_PERSISTENCE_MANAGERS.size());
|
||||
// We don't know from which thread we are called so we map to user thread
|
||||
UserThread.execute(() -> {
|
||||
if (FLUSH_ALL_DATA_TO_DISK_CALLED) {
|
||||
log.warn("We got flushAllDataToDisk called again. This can happen in some rare cases. We ignore the repeated call.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (openInstances.get() == 0) {
|
||||
log.info("flushAllDataToDisk completed");
|
||||
UserThread.execute(completeHandler::handleResult);
|
||||
}
|
||||
FLUSH_ALL_DATA_TO_DISK_CALLED = true;
|
||||
|
||||
new HashSet<>(ALL_PERSISTENCE_MANAGERS.values()).forEach(persistenceManager -> {
|
||||
// For Priority.HIGH data we want to write to disk in any case to be on the safe side if we might have missed
|
||||
// a requestPersistence call after an important state update. Those are usually rather small data stores.
|
||||
// Otherwise we only persist if requestPersistence was called since the last persist call.
|
||||
if (persistenceManager.source.flushAtShutDown || persistenceManager.persistenceRequested) {
|
||||
// We don't know from which thread we are called so we map back to user thread when calling persistNow
|
||||
UserThread.execute(() -> {
|
||||
log.info("Start flushAllDataToDisk at shutdown");
|
||||
AtomicInteger openInstances = new AtomicInteger(ALL_PERSISTENCE_MANAGERS.size());
|
||||
|
||||
if (openInstances.get() == 0) {
|
||||
log.info("No PersistenceManager instances have been created yet.");
|
||||
completeHandler.handleResult();
|
||||
}
|
||||
|
||||
new HashSet<>(ALL_PERSISTENCE_MANAGERS.values()).forEach(persistenceManager -> {
|
||||
// For Priority.HIGH data we want to write to disk in any case to be on the safe side if we might have missed
|
||||
// a requestPersistence call after an important state update. Those are usually rather small data stores.
|
||||
// Otherwise we only persist if requestPersistence was called since the last persist call.
|
||||
if (persistenceManager.source.flushAtShutDown || persistenceManager.persistenceRequested) {
|
||||
// We always get our completeHandler called even if exceptions happen. In case a file write fails
|
||||
// we still call our shutdown and count down routine as the completeHandler is triggered in any case.
|
||||
|
||||
// We get our result handler called from the write thread so we map back to user thread.
|
||||
persistenceManager.persistNow(() ->
|
||||
onWriteCompleted(completeHandler, openInstances, persistenceManager));
|
||||
});
|
||||
} else {
|
||||
onWriteCompleted(completeHandler, openInstances, persistenceManager);
|
||||
}
|
||||
UserThread.execute(() -> onWriteCompleted(completeHandler, openInstances, persistenceManager)));
|
||||
} else {
|
||||
onWriteCompleted(completeHandler, openInstances, persistenceManager);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// We get called always from user thread here.
|
||||
private static void onWriteCompleted(ResultHandler completeHandler,
|
||||
AtomicInteger openInstances,
|
||||
PersistenceManager<?> persistenceManager) {
|
||||
persistenceManager.shutdown();
|
||||
if (openInstances.decrementAndGet() == 0) {
|
||||
log.info("flushAllDataToDisk completed");
|
||||
UserThread.execute(completeHandler::handleResult);
|
||||
completeHandler.handleResult();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -126,25 +143,25 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
|
||||
public enum Source {
|
||||
// For data stores we received from the network and which could be rebuilt. We store only for avoiding too much network traffic.
|
||||
NETWORK(1, TimeUnit.HOURS.toSeconds(1), false),
|
||||
NETWORK(1, TimeUnit.MINUTES.toMillis(5), false),
|
||||
|
||||
// For data stores which are created from private local data. This data could only be rebuilt from backup files.
|
||||
PRIVATE(10, TimeUnit.SECONDS.toSeconds(30), true),
|
||||
PRIVATE(10, 200, true),
|
||||
|
||||
// For data stores which are created from private local data. Loss of that data would not have any critical consequences.
|
||||
PRIVATE_LOW_PRIO(4, TimeUnit.HOURS.toSeconds(2), false);
|
||||
// For data stores which are created from private local data. Loss of that data would not have critical consequences.
|
||||
PRIVATE_LOW_PRIO(4, TimeUnit.MINUTES.toMillis(1), false);
|
||||
|
||||
|
||||
@Getter
|
||||
private final int numMaxBackupFiles;
|
||||
@Getter
|
||||
private final long delayInSec;
|
||||
private final long delay;
|
||||
@Getter
|
||||
private final boolean flushAtShutDown;
|
||||
|
||||
Source(int numMaxBackupFiles, long delayInSec, boolean flushAtShutDown) {
|
||||
Source(int numMaxBackupFiles, long delay, boolean flushAtShutDown) {
|
||||
this.numMaxBackupFiles = numMaxBackupFiles;
|
||||
this.delayInSec = delayInSec;
|
||||
this.delay = delay;
|
||||
this.flushAtShutDown = flushAtShutDown;
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +183,7 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
@Nullable
|
||||
private Timer timer;
|
||||
private ExecutorService writeToDiskExecutor;
|
||||
public final AtomicBoolean initCalled = new AtomicBoolean(false);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -190,6 +208,29 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
}
|
||||
|
||||
public void initialize(T persistable, String fileName, Source source) {
|
||||
if (FLUSH_ALL_DATA_TO_DISK_CALLED) {
|
||||
log.warn("We have started the shut down routine already. We ignore that initialize call.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ALL_PERSISTENCE_MANAGERS.containsKey(fileName)) {
|
||||
RuntimeException runtimeException = new RuntimeException("We must not create multiple " +
|
||||
"PersistenceManager instances for file " + fileName + ".");
|
||||
// We want to get logged from where we have been called so lets print the stack trace.
|
||||
runtimeException.printStackTrace();
|
||||
throw runtimeException;
|
||||
}
|
||||
|
||||
if (initCalled.get()) {
|
||||
RuntimeException runtimeException = new RuntimeException("We must not call initialize multiple times. " +
|
||||
"PersistenceManager for file: " + fileName + ".");
|
||||
// We want to get logged from where we have been called so lets print the stack trace.
|
||||
runtimeException.printStackTrace();
|
||||
throw runtimeException;
|
||||
}
|
||||
|
||||
initCalled.set(true);
|
||||
|
||||
this.persistable = persistable;
|
||||
this.fileName = fileName;
|
||||
this.source = source;
|
||||
|
@ -233,6 +274,11 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
* @param orElse Called if no file exists or reading of file failed.
|
||||
*/
|
||||
public void readPersisted(String fileName, Consumer<T> resultHandler, Runnable orElse) {
|
||||
if (FLUSH_ALL_DATA_TO_DISK_CALLED) {
|
||||
log.warn("We have started the shut down routine already. We ignore that readPersisted call.");
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
T persisted = getPersisted(fileName);
|
||||
if (persisted != null) {
|
||||
|
@ -252,6 +298,11 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
|
||||
@Nullable
|
||||
public T getPersisted(String fileName) {
|
||||
if (FLUSH_ALL_DATA_TO_DISK_CALLED) {
|
||||
log.warn("We have started the shut down routine already. We ignore that getPersisted call.");
|
||||
return null;
|
||||
}
|
||||
|
||||
File storageFile = new File(dir, fileName);
|
||||
if (!storageFile.exists()) {
|
||||
return null;
|
||||
|
@ -288,6 +339,11 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestPersistence() {
|
||||
if (FLUSH_ALL_DATA_TO_DISK_CALLED) {
|
||||
log.warn("We have started the shut down routine already. We ignore that requestPersistence call.");
|
||||
return;
|
||||
}
|
||||
|
||||
persistenceRequested = true;
|
||||
|
||||
// We write to disk with a delay to avoid frequent write operations. Depending on the priority those delays
|
||||
|
@ -296,7 +352,7 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
timer = UserThread.runAfter(() -> {
|
||||
persistNow(null);
|
||||
UserThread.execute(() -> timer = null);
|
||||
}, source.delayInSec, TimeUnit.SECONDS);
|
||||
}, source.delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,7 +454,7 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
|||
",\n dir=" + dir +
|
||||
",\n storageFile=" + storageFile +
|
||||
",\n persistable=" + persistable +
|
||||
",\n priority=" + source +
|
||||
",\n source=" + source +
|
||||
",\n usedTempFilePath=" + usedTempFilePath +
|
||||
",\n persistenceRequested=" + persistenceRequested +
|
||||
"\n}";
|
||||
|
|
|
@ -133,7 +133,7 @@ public class PrivateNotificationManager {
|
|||
}
|
||||
|
||||
public void removePrivateNotification() {
|
||||
p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey);
|
||||
p2PService.removeMailboxMsg(decryptedMessageWithPubKey);
|
||||
}
|
||||
|
||||
private boolean isKeyValid(String privKeyString) {
|
||||
|
|
|
@ -68,6 +68,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
|||
protected AppModule module;
|
||||
protected Config config;
|
||||
private boolean isShutdownInProgress;
|
||||
private boolean hasDowngraded;
|
||||
|
||||
public BisqExecutable(String fullName, String scriptName, String appName, String version) {
|
||||
this.fullName = fullName;
|
||||
|
@ -133,9 +134,17 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
|||
CommonSetup.setupUncaughtExceptionHandler(this);
|
||||
setupGuice();
|
||||
setupAvoidStandbyMode();
|
||||
readAllPersisted(this::startApplication);
|
||||
}
|
||||
|
||||
hasDowngraded = BisqSetup.hasDowngraded();
|
||||
if (hasDowngraded) {
|
||||
// If user tried to downgrade we do not read the persisted data to avoid data corruption
|
||||
// We call startApplication to enable UI to show popup. We prevent in BisqSetup to go further
|
||||
// in the process and require a shut down.
|
||||
startApplication();
|
||||
} else {
|
||||
readAllPersisted(this::startApplication);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// We continue with a series of synchronous execution tasks
|
||||
|
@ -236,11 +245,16 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
|||
injector.getInstance(P2PService.class).shutDown(() -> {
|
||||
log.info("P2PService shutdown completed");
|
||||
module.close(injector);
|
||||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
log.info("Graceful shutdown completed. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
System.exit(EXIT_SUCCESS);
|
||||
});
|
||||
if (!hasDowngraded) {
|
||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
log.info("Graceful shutdown completed. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
});
|
||||
} else {
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
walletsSetup.shutDown();
|
||||
|
@ -250,20 +264,31 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
|||
// Wait max 20 sec.
|
||||
UserThread.runAfter(() -> {
|
||||
log.warn("Timeout triggered resultHandler");
|
||||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
log.info("Graceful shutdown resulted in a timeout. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
System.exit(EXIT_SUCCESS);
|
||||
});
|
||||
if (!hasDowngraded) {
|
||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
log.info("Graceful shutdown resulted in a timeout. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
});
|
||||
} else {
|
||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
||||
}
|
||||
|
||||
}, 20);
|
||||
} catch (Throwable t) {
|
||||
log.error("App shutdown failed with exception {}", t.toString());
|
||||
t.printStackTrace();
|
||||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
log.info("Graceful shutdown resulted in an error. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
System.exit(EXIT_FAILURE);
|
||||
});
|
||||
if (!hasDowngraded) {
|
||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
log.info("Graceful shutdown resulted in an error. Exiting now.");
|
||||
resultHandler.handleResult();
|
||||
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
|
||||
});
|
||||
} else {
|
||||
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package bisq.core.app;
|
|||
import bisq.core.trade.TradeManager;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.file.CorruptedStorageFileHandler;
|
||||
import bisq.common.setup.GracefulShutDownHandler;
|
||||
|
||||
|
@ -94,6 +95,8 @@ public class BisqHeadlessApp implements HeadlessApp {
|
|||
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
|
||||
bisqSetup.setOsxKeyLoggerWarningHandler(() -> log.info("setOsxKeyLoggerWarningHandler"));
|
||||
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
||||
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
||||
lastVersion, Version.VERSION));
|
||||
|
||||
corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
||||
tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("onTakeOfferRequestErrorMessageHandler"));
|
||||
|
|
|
@ -48,6 +48,7 @@ import bisq.common.Timer;
|
|||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.app.Log;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.InvalidVersionException;
|
||||
import bisq.common.util.Utilities;
|
||||
|
@ -71,11 +72,15 @@ import javafx.collections.SetChangeListener;
|
|||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
|
@ -92,6 +97,7 @@ import javax.annotation.Nullable;
|
|||
@Slf4j
|
||||
@Singleton
|
||||
public class BisqSetup {
|
||||
private static final String VERSION_FILE_NAME = "version";
|
||||
|
||||
public interface BisqSetupListener {
|
||||
default void onInitP2pNetwork() {
|
||||
|
@ -172,6 +178,9 @@ public class BisqSetup {
|
|||
@Setter
|
||||
@Nullable
|
||||
private Runnable qubesOSInfoHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<String> downGradePreventionHandler;
|
||||
|
||||
@Getter
|
||||
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
|
||||
|
@ -255,6 +264,12 @@ public class BisqSetup {
|
|||
}
|
||||
|
||||
public void start() {
|
||||
// If user tried to downgrade we require a shutdown
|
||||
if (hasDowngraded(downGradePreventionHandler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
persistBisqVersion();
|
||||
maybeReSyncSPVChain();
|
||||
maybeShowTac(this::step2);
|
||||
}
|
||||
|
@ -387,7 +402,7 @@ public class BisqSetup {
|
|||
requestWalletPasswordHandler.accept(aesKey -> {
|
||||
walletsManager.setAesKey(aesKey);
|
||||
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(),
|
||||
aesKey);
|
||||
aesKey);
|
||||
if (preferences.isResyncSpvRequested()) {
|
||||
if (showFirstPopupIfResyncSPVRequestedHandler != null)
|
||||
showFirstPopupIfResyncSPVRequestedHandler.run();
|
||||
|
@ -487,6 +502,68 @@ public class BisqSetup {
|
|||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getLastBisqVersion() {
|
||||
File versionFile = getVersionFile();
|
||||
if (!versionFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
try (Scanner scanner = new Scanner(versionFile)) {
|
||||
// We only expect 1 line
|
||||
if (scanner.hasNextLine()) {
|
||||
return scanner.nextLine();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static File getVersionFile() {
|
||||
return new File(Config.appDataDir(), VERSION_FILE_NAME);
|
||||
}
|
||||
|
||||
public static boolean hasDowngraded() {
|
||||
return hasDowngraded(getLastBisqVersion());
|
||||
}
|
||||
|
||||
public static boolean hasDowngraded(String lastVersion) {
|
||||
return lastVersion != null && Version.isNewVersion(lastVersion, Version.VERSION);
|
||||
}
|
||||
|
||||
public static boolean hasDowngraded(@Nullable Consumer<String> downGradePreventionHandler) {
|
||||
String lastVersion = getLastBisqVersion();
|
||||
boolean hasDowngraded = hasDowngraded(lastVersion);
|
||||
if (hasDowngraded) {
|
||||
log.error("Downgrade from version {} to version {} is not supported", lastVersion, Version.VERSION);
|
||||
if (downGradePreventionHandler != null) {
|
||||
downGradePreventionHandler.accept(lastVersion);
|
||||
}
|
||||
}
|
||||
return hasDowngraded;
|
||||
}
|
||||
|
||||
public static void persistBisqVersion() {
|
||||
File versionFile = getVersionFile();
|
||||
if (!versionFile.exists()) {
|
||||
try {
|
||||
if (!versionFile.createNewFile()) {
|
||||
log.error("Version file could not be created");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.error("Version file could not be created. {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
try (FileWriter fileWriter = new FileWriter(versionFile, false)) {
|
||||
fileWriter.write(Version.VERSION);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.error("Writing Version failed. {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForCorrectOSArchitecture() {
|
||||
if (!Utilities.isCorrectOSArchitecture() && wrongOSArchitectureHandler != null) {
|
||||
String osArchitecture = Utilities.getOSArchitecture();
|
||||
|
|
|
@ -22,8 +22,8 @@ import bisq.core.btc.exceptions.RejectedTxException;
|
|||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.WalletsManager;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
|
@ -107,7 +107,7 @@ public class WalletAppSetup {
|
|||
Runnable downloadCompleteHandler,
|
||||
Runnable walletInitializedHandler) {
|
||||
log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}",
|
||||
VersionMessage.BITCOINJ_VERSION, "a733034");
|
||||
VersionMessage.BITCOINJ_VERSION, "7752cb7");
|
||||
|
||||
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
|
||||
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),
|
||||
|
@ -247,6 +247,7 @@ public class WalletAppSetup {
|
|||
String finalDetails = details;
|
||||
UserThread.runAfter(() -> {
|
||||
trade.setErrorMessage(newValue.getMessage());
|
||||
tradeManager.requestPersistence();
|
||||
if (rejectedTxErrorMessageHandler != null) {
|
||||
rejectedTxErrorMessageHandler.accept(Res.get("popup.warning.trade.txRejected",
|
||||
finalDetails, trade.getShortId(), txId));
|
||||
|
|
|
@ -95,7 +95,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
|||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
resultHandler.handleResult();
|
||||
log.info("Graceful shutdown completed. Exiting now.");
|
||||
System.exit(BisqExecutable.EXIT_SUCCESS);
|
||||
UserThread.runAfter(() -> System.exit(BisqExecutable.EXIT_SUCCESS), 1);
|
||||
});
|
||||
});
|
||||
injector.getInstance(WalletsSetup.class).shutDown();
|
||||
|
@ -107,7 +107,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
|||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
resultHandler.handleResult();
|
||||
log.info("Graceful shutdown caused a timeout. Exiting now.");
|
||||
System.exit(BisqExecutable.EXIT_SUCCESS);
|
||||
UserThread.runAfter(() -> System.exit(BisqExecutable.EXIT_SUCCESS), 1);
|
||||
});
|
||||
}, 5);
|
||||
} else {
|
||||
|
@ -122,7 +122,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
|||
PersistenceManager.flushAllDataToDisk(() -> {
|
||||
resultHandler.handleResult();
|
||||
log.info("Graceful shutdown resulted in an error. Exiting now.");
|
||||
System.exit(BisqExecutable.EXIT_FAILURE);
|
||||
UserThread.runAfter(() -> System.exit(BisqExecutable.EXIT_FAILURE), 1);
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -41,9 +41,23 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||
*/
|
||||
@Slf4j
|
||||
public class TxFeeEstimationService {
|
||||
public static int TYPICAL_TX_WITH_1_INPUT_SIZE = 260;
|
||||
private static int DEPOSIT_TX_SIZE = 320;
|
||||
private static int PAYOUT_TX_SIZE = 380;
|
||||
|
||||
// Size/vsize of typical trade txs
|
||||
// Real txs size/vsize may vary in 1 or 2 bytes from the estimated values.
|
||||
// Values calculated with https://gist.github.com/oscarguindzberg/3d1349cb65d9fd9af9de0feaa3fd27ac
|
||||
// legacy fee tx with 1 input, maker/taker fee paid in btc size/vsize = 258
|
||||
// legacy deposit tx without change size/vsize = 381
|
||||
// legacy deposit tx with change size/vsize = 414
|
||||
// legacy payout tx size/vsize = 337
|
||||
// legacy delayed payout tx size/vsize = 302
|
||||
// segwit fee tx with 1 input, maker/taker fee paid in btc vsize = 173
|
||||
// segwit deposit tx without change vsize = 232
|
||||
// segwit deposit tx with change vsize = 263
|
||||
// segwit payout tx vsize = 169
|
||||
// segwit delayed payout tx vsize = 139
|
||||
public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
|
||||
private static int DEPOSIT_TX_VSIZE = 233;
|
||||
|
||||
private static int BSQ_INPUT_INCREASE = 150;
|
||||
private static int MAX_ITERATIONS = 10;
|
||||
|
||||
|
@ -61,8 +75,8 @@ public class TxFeeEstimationService {
|
|||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSizeForTaker(Coin fundsNeededForTrade, Coin tradeFee) {
|
||||
return getEstimatedFeeAndTxSize(true,
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsizeForTaker(Coin fundsNeededForTrade, Coin tradeFee) {
|
||||
return getEstimatedFeeAndTxVsize(true,
|
||||
fundsNeededForTrade,
|
||||
tradeFee,
|
||||
feeService,
|
||||
|
@ -70,9 +84,9 @@ public class TxFeeEstimationService {
|
|||
preferences);
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSizeForMaker(Coin reservedFundsForOffer,
|
||||
Coin tradeFee) {
|
||||
return getEstimatedFeeAndTxSize(false,
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsizeForMaker(Coin reservedFundsForOffer,
|
||||
Coin tradeFee) {
|
||||
return getEstimatedFeeAndTxVsize(false,
|
||||
reservedFundsForOffer,
|
||||
tradeFee,
|
||||
feeService,
|
||||
|
@ -80,120 +94,120 @@ public class TxFeeEstimationService {
|
|||
preferences);
|
||||
}
|
||||
|
||||
private Tuple2<Coin, Integer> getEstimatedFeeAndTxSize(boolean isTaker,
|
||||
Coin amount,
|
||||
Coin tradeFee,
|
||||
FeeService feeService,
|
||||
BtcWalletService btcWalletService,
|
||||
Preferences preferences) {
|
||||
Coin txFeePerByte = feeService.getTxFeePerByte();
|
||||
// We start with min taker fee size of 260
|
||||
int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
|
||||
private Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(boolean isTaker,
|
||||
Coin amount,
|
||||
Coin tradeFee,
|
||||
FeeService feeService,
|
||||
BtcWalletService btcWalletService,
|
||||
Preferences preferences) {
|
||||
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
|
||||
// We start with min taker fee vsize of 175
|
||||
int estimatedTxVsize = TYPICAL_TX_WITH_1_INPUT_VSIZE;
|
||||
try {
|
||||
estimatedTxSize = getEstimatedTxSize(List.of(tradeFee, amount), estimatedTxSize, txFeePerByte, btcWalletService);
|
||||
estimatedTxVsize = getEstimatedTxVsize(List.of(tradeFee, amount), estimatedTxVsize, txFeePerVbyte, btcWalletService);
|
||||
} catch (InsufficientMoneyException e) {
|
||||
if (isTaker) {
|
||||
// if we cannot do the estimation we use the payout tx size
|
||||
estimatedTxSize = PAYOUT_TX_SIZE;
|
||||
// If we cannot do the estimation, we use the vsize o the largest of our txs which is the deposit tx.
|
||||
estimatedTxVsize = DEPOSIT_TX_VSIZE;
|
||||
}
|
||||
log.info("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " +
|
||||
"if the user pays from an external wallet. In that case we use an estimated tx size of {} bytes.", estimatedTxSize);
|
||||
"if the user pays from an external wallet. In that case we use an estimated tx vsize of {} vbytes.", estimatedTxVsize);
|
||||
}
|
||||
|
||||
if (!preferences.isPayFeeInBtc()) {
|
||||
// If we pay the fee in BSQ we have one input more which adds about 150 bytes
|
||||
// TODO: Clarify if there is always just one additional input or if there can be more.
|
||||
estimatedTxSize += BSQ_INPUT_INCREASE;
|
||||
estimatedTxVsize += BSQ_INPUT_INCREASE;
|
||||
}
|
||||
|
||||
Coin txFee;
|
||||
int size;
|
||||
int vsize;
|
||||
if (isTaker) {
|
||||
int averageSize = (estimatedTxSize + DEPOSIT_TX_SIZE) / 2; // deposit tx has about 320 bytes
|
||||
// We use at least the size of the payout tx to not underpay at payout.
|
||||
size = Math.max(PAYOUT_TX_SIZE, averageSize);
|
||||
txFee = txFeePerByte.multiply(size);
|
||||
log.info("Fee estimation resulted in a tx size of {} bytes.\n" +
|
||||
"We use an average between the taker fee tx and the deposit tx (320 bytes) which results in {} bytes.\n" +
|
||||
"The payout tx has 380 bytes, we use that as our min value. Size for fee calculation is {} bytes.\n" +
|
||||
"The tx fee of {} Sat", estimatedTxSize, averageSize, size, txFee.value);
|
||||
int averageVsize = (estimatedTxVsize + DEPOSIT_TX_VSIZE) / 2; // deposit tx has about 233 vbytes
|
||||
// We use at least the vsize of the deposit tx to not underpay it.
|
||||
vsize = Math.max(DEPOSIT_TX_VSIZE, averageVsize);
|
||||
txFee = txFeePerVbyte.multiply(vsize);
|
||||
log.info("Fee estimation resulted in a tx vsize of {} vbytes.\n" +
|
||||
"We use an average between the taker fee tx and the deposit tx (233 vbytes) which results in {} vbytes.\n" +
|
||||
"The deposit tx has 233 vbytes, we use that as our min value. Vsize for fee calculation is {} vbytes.\n" +
|
||||
"The tx fee of {} Sat", estimatedTxVsize, averageVsize, vsize, txFee.value);
|
||||
} else {
|
||||
size = estimatedTxSize;
|
||||
txFee = txFeePerByte.multiply(size);
|
||||
log.info("Fee estimation resulted in a tx size of {} bytes and a tx fee of {} Sat.", size, txFee.value);
|
||||
vsize = estimatedTxVsize;
|
||||
txFee = txFeePerVbyte.multiply(vsize);
|
||||
log.info("Fee estimation resulted in a tx vsize of {} vbytes and a tx fee of {} Sat.", vsize, txFee.value);
|
||||
}
|
||||
|
||||
return new Tuple2<>(txFee, size);
|
||||
return new Tuple2<>(txFee, vsize);
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSize(Coin amount,
|
||||
FeeService feeService,
|
||||
BtcWalletService btcWalletService) {
|
||||
Coin txFeePerByte = feeService.getTxFeePerByte();
|
||||
// We start with min taker fee size of 260
|
||||
int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(Coin amount,
|
||||
FeeService feeService,
|
||||
BtcWalletService btcWalletService) {
|
||||
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
|
||||
// We start with min taker fee vsize of 175
|
||||
int estimatedTxVsize = TYPICAL_TX_WITH_1_INPUT_VSIZE;
|
||||
try {
|
||||
estimatedTxSize = getEstimatedTxSize(List.of(amount), estimatedTxSize, txFeePerByte, btcWalletService);
|
||||
estimatedTxVsize = getEstimatedTxVsize(List.of(amount), estimatedTxVsize, txFeePerVbyte, btcWalletService);
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.info("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " +
|
||||
"if the user pays from an external wallet. In that case we use an estimated tx size of {} bytes.", estimatedTxSize);
|
||||
"if the user pays from an external wallet. In that case we use an estimated tx vsize of {} vbytes.", estimatedTxVsize);
|
||||
}
|
||||
|
||||
Coin txFee = txFeePerByte.multiply(estimatedTxSize);
|
||||
log.info("Fee estimation resulted in a tx size of {} bytes and a tx fee of {} Sat.", estimatedTxSize, txFee.value);
|
||||
Coin txFee = txFeePerVbyte.multiply(estimatedTxVsize);
|
||||
log.info("Fee estimation resulted in a tx vsize of {} vbytes and a tx fee of {} Sat.", estimatedTxVsize, txFee.value);
|
||||
|
||||
return new Tuple2<>(txFee, estimatedTxSize);
|
||||
return new Tuple2<>(txFee, estimatedTxVsize);
|
||||
}
|
||||
|
||||
// We start with the initialEstimatedTxSize for a tx with 1 input (260) bytes and get from BitcoinJ a tx back which
|
||||
// We start with the initialEstimatedTxVsize for a tx with 1 input (175) vbytes and get from BitcoinJ a tx back which
|
||||
// contains the required inputs to fund that tx (outputs + miner fee). The miner fee in that case is based on
|
||||
// the assumption that we only need 1 input. Once we receive back the real tx size from the tx BitcoinJ has created
|
||||
// with the required inputs we compare if the size is not more then 20% different to our assumed tx size. If we are inside
|
||||
// that tolerance we use that tx size for our fee estimation, if not (if there has been more then 1 inputs) we
|
||||
// apply the new fee based on the reported tx size and request again from BitcoinJ to fill that tx with the inputs
|
||||
// the assumption that we only need 1 input. Once we receive back the real tx vsize from the tx BitcoinJ has created
|
||||
// with the required inputs we compare if the vsize is not more then 20% different to our assumed tx vsize. If we are inside
|
||||
// that tolerance we use that tx vsize for our fee estimation, if not (if there has been more then 1 inputs) we
|
||||
// apply the new fee based on the reported tx vsize and request again from BitcoinJ to fill that tx with the inputs
|
||||
// to be sufficiently funded. The algorithm how BitcoinJ selects utxos is complex and contains several aspects
|
||||
// (minimize fee, don't create too many tiny utxos,...). We treat that algorithm as an unknown and it is not
|
||||
// guaranteed that there are more inputs required if we increase the fee (it could be that there is a better
|
||||
// selection of inputs chosen if we have increased the fee and therefore less inputs and smaller tx size). As the increased fee might
|
||||
// selection of inputs chosen if we have increased the fee and therefore less inputs and smaller tx vsize). As the increased fee might
|
||||
// change the number of inputs we need to repeat that process until we are inside of a certain tolerance. To avoid
|
||||
// potential endless loops we add a counter (we use 10, usually it takes just very few iterations).
|
||||
// Worst case would be that the last size we got reported is > 20% off to
|
||||
// the real tx size but as fee estimation is anyway a educated guess in the best case we don't worry too much.
|
||||
// Worst case would be that the last vsize we got reported is > 20% off to
|
||||
// the real tx vsize but as fee estimation is anyway a educated guess in the best case we don't worry too much.
|
||||
// If we have underpaid the tx might take longer to get confirmed.
|
||||
@VisibleForTesting
|
||||
static int getEstimatedTxSize(List<Coin> outputValues,
|
||||
int initialEstimatedTxSize,
|
||||
Coin txFeePerByte,
|
||||
BtcWalletService btcWalletService)
|
||||
static int getEstimatedTxVsize(List<Coin> outputValues,
|
||||
int initialEstimatedTxVsize,
|
||||
Coin txFeePerVbyte,
|
||||
BtcWalletService btcWalletService)
|
||||
throws InsufficientMoneyException {
|
||||
boolean isInTolerance;
|
||||
int estimatedTxSize = initialEstimatedTxSize;
|
||||
int realTxSize;
|
||||
int estimatedTxVsize = initialEstimatedTxVsize;
|
||||
int realTxVsize;
|
||||
int counter = 0;
|
||||
do {
|
||||
Coin txFee = txFeePerByte.multiply(estimatedTxSize);
|
||||
realTxSize = btcWalletService.getEstimatedFeeTxSize(outputValues, txFee);
|
||||
isInTolerance = isInTolerance(estimatedTxSize, realTxSize, 0.2);
|
||||
Coin txFee = txFeePerVbyte.multiply(estimatedTxVsize);
|
||||
realTxVsize = btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee);
|
||||
isInTolerance = isInTolerance(estimatedTxVsize, realTxVsize, 0.2);
|
||||
if (!isInTolerance) {
|
||||
estimatedTxSize = realTxSize;
|
||||
estimatedTxVsize = realTxVsize;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
while (!isInTolerance && counter < MAX_ITERATIONS);
|
||||
if (!isInTolerance) {
|
||||
log.warn("We could not find a tx which satisfies our tolerance requirement of 20%. " +
|
||||
"realTxSize={}, estimatedTxSize={}",
|
||||
realTxSize, estimatedTxSize);
|
||||
"realTxVsize={}, estimatedTxVsize={}",
|
||||
realTxVsize, estimatedTxVsize);
|
||||
}
|
||||
return estimatedTxSize;
|
||||
return estimatedTxVsize;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean isInTolerance(int estimatedSize, int txSize, double tolerance) {
|
||||
checkArgument(estimatedSize > 0, "estimatedSize must be positive");
|
||||
checkArgument(txSize > 0, "txSize must be positive");
|
||||
static boolean isInTolerance(int estimatedVsize, int txVsize, double tolerance) {
|
||||
checkArgument(estimatedVsize > 0, "estimatedVsize must be positive");
|
||||
checkArgument(txVsize > 0, "txVsize must be positive");
|
||||
checkArgument(tolerance > 0, "tolerance must be positive");
|
||||
double deviation = Math.abs(1 - ((double) estimatedSize / (double) txSize));
|
||||
double deviation = Math.abs(1 - ((double) estimatedVsize / (double) txVsize));
|
||||
return deviation <= tolerance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,10 +97,6 @@ public final class AddressEntry implements PersistablePayload {
|
|||
Context context,
|
||||
@Nullable String offerId,
|
||||
boolean segwit) {
|
||||
if (segwit && (!Context.AVAILABLE.equals(context) || offerId != null)) {
|
||||
throw new IllegalArgumentException("Segwit addresses are only allowed for " +
|
||||
"AVAILABLE entries without an offerId");
|
||||
}
|
||||
this.keyPair = keyPair;
|
||||
this.context = context;
|
||||
this.offerId = offerId;
|
||||
|
|
|
@ -28,13 +28,14 @@ import bisq.core.provider.fee.FeeService;
|
|||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.SegwitAddress;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
|
@ -44,6 +45,7 @@ import org.bitcoinj.crypto.DeterministicKey;
|
|||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.script.ScriptPattern;
|
||||
import org.bitcoinj.wallet.SendRequest;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
|
||||
|
@ -218,8 +220,8 @@ public class BtcWalletService extends WalletService {
|
|||
// estimated size of input sig
|
||||
int sigSizePerInput = 106;
|
||||
// typical size for a tx with 3 inputs
|
||||
int txSizeWithUnsignedInputs = 300;
|
||||
Coin txFeePerByte = feeService.getTxFeePerByte();
|
||||
int txVsizeWithUnsignedInputs = 300;
|
||||
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
|
||||
|
||||
Address changeAddress = getFreshAddressEntry().getAddress();
|
||||
checkNotNull(changeAddress, "changeAddress must not be null");
|
||||
|
@ -228,7 +230,9 @@ public class BtcWalletService extends WalletService {
|
|||
preferences.getIgnoreDustThreshold());
|
||||
List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
|
||||
List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
|
||||
int numInputs = preparedBsqTxInputs.size();
|
||||
Tuple2<Integer, Integer> numInputs = getNumInputs(preparedTx);
|
||||
int numLegacyInputs = numInputs.first;
|
||||
int numSegwitInputs = numInputs.second;
|
||||
Transaction resultTx = null;
|
||||
boolean isFeeOutsideTolerance;
|
||||
do {
|
||||
|
@ -249,7 +253,10 @@ public class BtcWalletService extends WalletService {
|
|||
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
|
||||
sendRequest.signInputs = false;
|
||||
|
||||
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
|
||||
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
||||
sigSizePerInput * numLegacyInputs +
|
||||
sigSizePerInput * numSegwitInputs / 4);
|
||||
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
sendRequest.ensureMinRequiredFee = false;
|
||||
|
||||
|
@ -262,9 +269,14 @@ public class BtcWalletService extends WalletService {
|
|||
// add OP_RETURN output
|
||||
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
|
||||
|
||||
numInputs = resultTx.getInputs().size();
|
||||
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
|
||||
long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
|
||||
numInputs = getNumInputs(resultTx);
|
||||
numLegacyInputs = numInputs.first;
|
||||
numSegwitInputs = numInputs.second;
|
||||
txVsizeWithUnsignedInputs = resultTx.getVsize();
|
||||
long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
||||
sigSizePerInput * numLegacyInputs +
|
||||
sigSizePerInput * numSegwitInputs / 4).value;
|
||||
|
||||
// calculated fee must be inside of a tolerance range with tx fee
|
||||
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
||||
}
|
||||
|
@ -328,8 +340,8 @@ public class BtcWalletService extends WalletService {
|
|||
// estimated size of input sig
|
||||
int sigSizePerInput = 106;
|
||||
// typical size for a tx with 3 inputs
|
||||
int txSizeWithUnsignedInputs = 300;
|
||||
Coin txFeePerByte = feeService.getTxFeePerByte();
|
||||
int txVsizeWithUnsignedInputs = 300;
|
||||
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
|
||||
|
||||
Address changeAddress = getFreshAddressEntry().getAddress();
|
||||
checkNotNull(changeAddress, "changeAddress must not be null");
|
||||
|
@ -338,7 +350,9 @@ public class BtcWalletService extends WalletService {
|
|||
preferences.getIgnoreDustThreshold());
|
||||
List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
|
||||
List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
|
||||
int numInputs = preparedBsqTxInputs.size();
|
||||
Tuple2<Integer, Integer> numInputs = getNumInputs(preparedTx);
|
||||
int numLegacyInputs = numInputs.first;
|
||||
int numSegwitInputs = numInputs.second;
|
||||
Transaction resultTx = null;
|
||||
boolean isFeeOutsideTolerance;
|
||||
do {
|
||||
|
@ -359,7 +373,9 @@ public class BtcWalletService extends WalletService {
|
|||
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
|
||||
sendRequest.signInputs = false;
|
||||
|
||||
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
|
||||
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
||||
sigSizePerInput * numLegacyInputs +
|
||||
sigSizePerInput * numSegwitInputs / 4);
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
sendRequest.ensureMinRequiredFee = false;
|
||||
|
||||
|
@ -372,9 +388,13 @@ public class BtcWalletService extends WalletService {
|
|||
// add OP_RETURN output
|
||||
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
|
||||
|
||||
numInputs = resultTx.getInputs().size();
|
||||
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
|
||||
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
|
||||
numInputs = getNumInputs(resultTx);
|
||||
numLegacyInputs = numInputs.first;
|
||||
numSegwitInputs = numInputs.second;
|
||||
txVsizeWithUnsignedInputs = resultTx.getVsize();
|
||||
final long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
||||
sigSizePerInput * numLegacyInputs +
|
||||
sigSizePerInput * numSegwitInputs / 4).value;
|
||||
// calculated fee must be inside of a tolerance range with tx fee
|
||||
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
||||
}
|
||||
|
@ -466,9 +486,9 @@ public class BtcWalletService extends WalletService {
|
|||
// estimated size of input sig
|
||||
int sigSizePerInput = 106;
|
||||
// typical size for a tx with 2 inputs
|
||||
int txSizeWithUnsignedInputs = 203;
|
||||
int txVsizeWithUnsignedInputs = 203;
|
||||
// If useCustomTxFee we allow overriding the estimated fee from preferences
|
||||
Coin txFeePerByte = useCustomTxFee ? getTxFeeForWithdrawalPerByte() : feeService.getTxFeePerByte();
|
||||
Coin txFeePerVbyte = useCustomTxFee ? getTxFeeForWithdrawalPerVbyte() : feeService.getTxFeePerVbyte();
|
||||
// In case there are no change outputs we force a change by adding min dust to the BTC input
|
||||
Coin forcedChangeValue = Coin.ZERO;
|
||||
|
||||
|
@ -479,7 +499,10 @@ public class BtcWalletService extends WalletService {
|
|||
preferences.getIgnoreDustThreshold());
|
||||
List<TransactionInput> preparedBsqTxInputs = preparedBsqTx.getInputs();
|
||||
List<TransactionOutput> preparedBsqTxOutputs = preparedBsqTx.getOutputs();
|
||||
int numInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input
|
||||
// We don't know at this point what type the btc input would be (segwit/legacy).
|
||||
// We use legacy to be on the safe side.
|
||||
int numLegacyInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input
|
||||
int numSegwitInputs = 0;
|
||||
Transaction resultTx = null;
|
||||
boolean isFeeOutsideTolerance;
|
||||
boolean opReturnIsOnlyOutput;
|
||||
|
@ -508,7 +531,9 @@ public class BtcWalletService extends WalletService {
|
|||
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
|
||||
sendRequest.signInputs = false;
|
||||
|
||||
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
|
||||
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
||||
sigSizePerInput * numLegacyInputs +
|
||||
sigSizePerInput * numSegwitInputs / 4);
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
sendRequest.ensureMinRequiredFee = false;
|
||||
|
||||
|
@ -528,15 +553,19 @@ public class BtcWalletService extends WalletService {
|
|||
if (opReturnData != null)
|
||||
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
|
||||
|
||||
numInputs = resultTx.getInputs().size();
|
||||
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
|
||||
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
|
||||
Tuple2<Integer, Integer> numInputs = getNumInputs(resultTx);
|
||||
numLegacyInputs = numInputs.first;
|
||||
numSegwitInputs = numInputs.second;
|
||||
txVsizeWithUnsignedInputs = resultTx.getVsize();
|
||||
final long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
||||
sigSizePerInput * numLegacyInputs +
|
||||
sigSizePerInput * numSegwitInputs / 4).value;
|
||||
// calculated fee must be inside of a tolerance range with tx fee
|
||||
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
||||
}
|
||||
while (opReturnIsOnlyOutput ||
|
||||
isFeeOutsideTolerance ||
|
||||
resultTx.getFee().value < txFeePerByte.multiply(resultTx.bitcoinSerialize().length).value);
|
||||
resultTx.getFee().value < txFeePerVbyte.multiply(resultTx.getVsize()).value);
|
||||
|
||||
// Sign all BTC inputs
|
||||
signAllBtcInputs(preparedBsqTxInputs.size(), resultTx);
|
||||
|
@ -548,6 +577,25 @@ public class BtcWalletService extends WalletService {
|
|||
return resultTx;
|
||||
}
|
||||
|
||||
private Tuple2<Integer, Integer> getNumInputs(Transaction tx) {
|
||||
int numLegacyInputs = 0;
|
||||
int numSegwitInputs = 0;
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
TransactionOutput connectedOutput = input.getConnectedOutput();
|
||||
if (connectedOutput == null || ScriptPattern.isP2PKH(connectedOutput.getScriptPubKey()) ||
|
||||
ScriptPattern.isP2PK(connectedOutput.getScriptPubKey())) {
|
||||
// If connectedOutput is null, we don't know here the input type. To avoid underpaying fees,
|
||||
// we treat it as a legacy input which will result in a higher fee estimation.
|
||||
numLegacyInputs++;
|
||||
} else if (ScriptPattern.isP2WPKH(connectedOutput.getScriptPubKey())) {
|
||||
numSegwitInputs++;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Inputs should spend a P2PKH, P2PK or P2WPKH ouput");
|
||||
}
|
||||
}
|
||||
return new Tuple2(numLegacyInputs, numSegwitInputs);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Commit tx
|
||||
|
@ -579,18 +627,17 @@ public class BtcWalletService extends WalletService {
|
|||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
// We still use non-segwit addresses for the trade protocol.
|
||||
// We try to use available and not yet used entries
|
||||
Optional<AddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> AddressEntry.Context.AVAILABLE == e.getContext())
|
||||
.filter(e -> isAddressUnused(e.getAddress()))
|
||||
.filter(e -> Script.ScriptType.P2PKH.equals(e.getAddress().getOutputScriptType()))
|
||||
.filter(e -> Script.ScriptType.P2WPKH.equals(e.getAddress().getOutputScriptType()))
|
||||
.findAny();
|
||||
if (emptyAvailableAddressEntry.isPresent()) {
|
||||
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
|
||||
} else {
|
||||
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH));
|
||||
AddressEntry entry = new AddressEntry(key, context, offerId, false);
|
||||
DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2WPKH));
|
||||
AddressEntry entry = new AddressEntry(key, context, offerId, true);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
|
@ -810,7 +857,7 @@ public class BtcWalletService extends WalletService {
|
|||
);
|
||||
|
||||
log.info("newTransaction no. of inputs " + newTransaction.getInputs().size());
|
||||
log.info("newTransaction size in kB " + newTransaction.bitcoinSerialize().length / 1024);
|
||||
log.info("newTransaction vsize in vkB " + newTransaction.getVsize() / 1024);
|
||||
|
||||
if (!newTransaction.getInputs().isEmpty()) {
|
||||
Coin amount = Coin.valueOf(newTransaction.getInputs().stream()
|
||||
|
@ -821,13 +868,13 @@ public class BtcWalletService extends WalletService {
|
|||
try {
|
||||
Coin fee;
|
||||
int counter = 0;
|
||||
int txSize = 0;
|
||||
int txVsize = 0;
|
||||
Transaction tx;
|
||||
SendRequest sendRequest;
|
||||
Coin txFeeForWithdrawalPerByte = getTxFeeForWithdrawalPerByte();
|
||||
Coin txFeeForWithdrawalPerVbyte = getTxFeeForWithdrawalPerVbyte();
|
||||
do {
|
||||
counter++;
|
||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||
fee = txFeeForWithdrawalPerVbyte.multiply(txVsize);
|
||||
newTransaction.clearOutputs();
|
||||
newTransaction.addOutput(amount.subtract(fee), toAddress);
|
||||
|
||||
|
@ -840,7 +887,7 @@ public class BtcWalletService extends WalletService {
|
|||
sendRequest.changeAddress = toAddress;
|
||||
wallet.completeTx(sendRequest);
|
||||
tx = sendRequest.tx;
|
||||
txSize = tx.bitcoinSerialize().length;
|
||||
txVsize = tx.getVsize();
|
||||
printTx("FeeEstimationTransaction", tx);
|
||||
sendRequest.tx.getOutputs().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString()));
|
||||
}
|
||||
|
@ -939,16 +986,16 @@ public class BtcWalletService extends WalletService {
|
|||
try {
|
||||
Coin fee;
|
||||
int counter = 0;
|
||||
int txSize = 0;
|
||||
int txVsize = 0;
|
||||
Transaction tx;
|
||||
Coin txFeeForWithdrawalPerByte = getTxFeeForWithdrawalPerByte();
|
||||
Coin txFeeForWithdrawalPerVbyte = getTxFeeForWithdrawalPerVbyte();
|
||||
do {
|
||||
counter++;
|
||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||
fee = txFeeForWithdrawalPerVbyte.multiply(txVsize);
|
||||
SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, aesKey, context);
|
||||
wallet.completeTx(sendRequest);
|
||||
tx = sendRequest.tx;
|
||||
txSize = tx.bitcoinSerialize().length;
|
||||
txVsize = tx.getVsize();
|
||||
printTx("FeeEstimationTransaction", tx);
|
||||
}
|
||||
while (feeEstimationNotSatisfied(counter, tx));
|
||||
|
@ -986,18 +1033,20 @@ public class BtcWalletService extends WalletService {
|
|||
try {
|
||||
Coin fee;
|
||||
int counter = 0;
|
||||
int txSize = 0;
|
||||
int txVsize = 0;
|
||||
Transaction tx;
|
||||
Coin txFeeForWithdrawalPerByte = getTxFeeForWithdrawalPerByte();
|
||||
Coin txFeeForWithdrawalPerVbyte = getTxFeeForWithdrawalPerVbyte();
|
||||
do {
|
||||
counter++;
|
||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||
fee = txFeeForWithdrawalPerVbyte.multiply(txVsize);
|
||||
// We use a dummy address for the output
|
||||
final String dummyReceiver = LegacyAddress.fromKey(params, new ECKey()).toBase58();
|
||||
// We don't know here whether the output is segwit or not but we don't care too much because the size of
|
||||
// a segwit ouput is just 3 byte smaller than the size of a legacy ouput.
|
||||
final String dummyReceiver = SegwitAddress.fromKey(params, new ECKey()).toString();
|
||||
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey);
|
||||
wallet.completeTx(sendRequest);
|
||||
tx = sendRequest.tx;
|
||||
txSize = tx.bitcoinSerialize().length;
|
||||
txVsize = tx.getVsize();
|
||||
printTx("FeeEstimationTransactionForMultipleAddresses", tx);
|
||||
}
|
||||
while (feeEstimationNotSatisfied(counter, tx));
|
||||
|
@ -1013,16 +1062,18 @@ public class BtcWalletService extends WalletService {
|
|||
}
|
||||
|
||||
private boolean feeEstimationNotSatisfied(int counter, Transaction tx) {
|
||||
long targetFee = getTxFeeForWithdrawalPerByte().multiply(tx.bitcoinSerialize().length).value;
|
||||
long targetFee = getTxFeeForWithdrawalPerVbyte().multiply(tx.getVsize()).value;
|
||||
return counter < 10 &&
|
||||
(tx.getFee().value < targetFee ||
|
||||
tx.getFee().value - targetFee > 1000);
|
||||
}
|
||||
|
||||
public int getEstimatedFeeTxSize(List<Coin> outputValues, Coin txFee)
|
||||
public int getEstimatedFeeTxVsize(List<Coin> outputValues, Coin txFee)
|
||||
throws InsufficientMoneyException, AddressFormatException {
|
||||
Transaction transaction = new Transaction(params);
|
||||
Address dummyAddress = LegacyAddress.fromKey(params, new ECKey());
|
||||
// In reality txs have a mix of segwit/legacy ouputs, but we don't care too much because the size of
|
||||
// a segwit ouput is just 3 byte smaller than the size of a legacy ouput.
|
||||
Address dummyAddress = SegwitAddress.fromKey(params, new ECKey());
|
||||
outputValues.forEach(outputValue -> transaction.addOutput(outputValue, dummyAddress));
|
||||
|
||||
SendRequest sendRequest = SendRequest.forTx(transaction);
|
||||
|
@ -1035,7 +1086,7 @@ public class BtcWalletService extends WalletService {
|
|||
sendRequest.ensureMinRequiredFee = false;
|
||||
sendRequest.changeAddress = dummyAddress;
|
||||
wallet.completeTx(sendRequest);
|
||||
return transaction.bitcoinSerialize().length;
|
||||
return transaction.getVsize();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ import org.bitcoinj.core.AddressFormatException;
|
|||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.SegwitAddress;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.SignatureDecodeException;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
@ -76,6 +76,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
public class TradeWalletService {
|
||||
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
|
||||
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
|
||||
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final Preferences preferences;
|
||||
|
@ -336,7 +337,7 @@ public class TradeWalletService {
|
|||
Transaction dummyTX = new Transaction(params);
|
||||
// The output is just used to get the right inputs and change outputs, so we use an anonymous ECKey, as it will never be used for anything.
|
||||
// We don't care about fee calculation differences between the real tx and that dummy tx as we use a static tx fee.
|
||||
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, LegacyAddress.fromKey(params, new ECKey()));
|
||||
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, SegwitAddress.fromKey(params, new ECKey()));
|
||||
dummyTX.addOutput(dummyOutput);
|
||||
|
||||
// Find the needed inputs to pay the output, optionally add 1 change output.
|
||||
|
@ -455,7 +456,7 @@ public class TradeWalletService {
|
|||
// First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx.
|
||||
// Similar to the way we did in the createTakerDepositTxInputs method.
|
||||
Transaction dummyTx = new Transaction(params);
|
||||
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, LegacyAddress.fromKey(params, new ECKey()));
|
||||
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, SegwitAddress.fromKey(params, new ECKey()));
|
||||
dummyTx.addOutput(dummyOutput);
|
||||
addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress);
|
||||
// Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions.
|
||||
|
@ -502,12 +503,12 @@ public class TradeWalletService {
|
|||
|
||||
|
||||
// Add MultiSig output
|
||||
Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
|
||||
Script hashedMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey, false);
|
||||
|
||||
// Tx fee for deposit tx will be paid by buyer.
|
||||
TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount,
|
||||
p2SHMultiSigOutputScript.getProgram());
|
||||
preparedDepositTx.addOutput(p2SHMultiSigOutput);
|
||||
TransactionOutput hashedMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount,
|
||||
hashedMultiSigOutputScript.getProgram());
|
||||
preparedDepositTx.addOutput(hashedMultiSigOutput);
|
||||
|
||||
// We add the hash ot OP_RETURN with a 0 amount output
|
||||
TransactionOutput contractHashOutput = new TransactionOutput(params, preparedDepositTx, Coin.ZERO,
|
||||
|
@ -569,8 +570,9 @@ public class TradeWalletService {
|
|||
* @param buyerPubKey the public key of the buyer
|
||||
* @param sellerPubKey the public key of the seller
|
||||
* @throws SigningException if (one of) the taker input(s) was of an unrecognized type for signing
|
||||
* @throws TransactionVerificationException if a maker input wasn't signed, their MultiSig script or contract hash
|
||||
* doesn't match the taker's, or there was an unexpected problem with the final deposit tx or its signatures
|
||||
* @throws TransactionVerificationException if a non-P2WH maker-as-buyer input wasn't signed, the maker's MultiSig
|
||||
* script or contract hash doesn't match the taker's, or there was an unexpected problem with the final deposit tx
|
||||
* or its signatures
|
||||
* @throws WalletException if the taker's wallet is null or structurally inconsistent
|
||||
*/
|
||||
public Transaction takerSignsDepositTx(boolean takerIsSeller,
|
||||
|
@ -587,9 +589,9 @@ public class TradeWalletService {
|
|||
checkArgument(!sellerInputs.isEmpty());
|
||||
|
||||
// Check if maker's MultiSig script is identical to the takers
|
||||
Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
|
||||
if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript)) {
|
||||
throw new TransactionVerificationException("Maker's p2SHMultiSigOutputScript does not match to takers p2SHMultiSigOutputScript");
|
||||
Script hashedMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey, false);
|
||||
if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(hashedMultiSigOutputScript)) {
|
||||
throw new TransactionVerificationException("Maker's hashedMultiSigOutputScript does not match to takers hashedMultiSigOutputScript");
|
||||
}
|
||||
|
||||
// The outpoints are not available from the serialized makersDepositTx, so we cannot use that tx directly, but we use it to construct a new
|
||||
|
@ -601,8 +603,12 @@ public class TradeWalletService {
|
|||
// We grab the signature from the makersDepositTx and apply it to the new tx input
|
||||
for (int i = 0; i < buyerInputs.size(); i++) {
|
||||
TransactionInput makersInput = makersDepositTx.getInputs().get(i);
|
||||
byte[] makersScriptSigProgram = getMakersScriptSigProgram(makersInput);
|
||||
byte[] makersScriptSigProgram = makersInput.getScriptSig().getProgram();
|
||||
TransactionInput input = getTransactionInput(depositTx, makersScriptSigProgram, buyerInputs.get(i));
|
||||
Script scriptPubKey = checkNotNull(input.getConnectedOutput()).getScriptPubKey();
|
||||
if (makersScriptSigProgram.length == 0 && !ScriptPattern.isP2WH(scriptPubKey)) {
|
||||
throw new TransactionVerificationException("Non-segwit inputs from maker not signed.");
|
||||
}
|
||||
if (!TransactionWitness.EMPTY.equals(makersInput.getWitness())) {
|
||||
input.setWitness(makersInput.getWitness());
|
||||
}
|
||||
|
@ -683,6 +689,21 @@ public class TradeWalletService {
|
|||
}
|
||||
|
||||
|
||||
public void sellerAddsBuyerWitnessesToDepositTx(Transaction myDepositTx,
|
||||
Transaction buyersDepositTxWithWitnesses) {
|
||||
int numberInputs = myDepositTx.getInputs().size();
|
||||
for (int i = 0; i < numberInputs; i++) {
|
||||
var txInput = myDepositTx.getInput(i);
|
||||
var witnessFromBuyer = buyersDepositTxWithWitnesses.getInput(i).getWitness();
|
||||
|
||||
if (TransactionWitness.EMPTY.equals(txInput.getWitness()) &&
|
||||
!TransactionWitness.EMPTY.equals(witnessFromBuyer)) {
|
||||
txInput.setWitness(witnessFromBuyer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Delayed payout tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -692,11 +713,11 @@ public class TradeWalletService {
|
|||
Coin minerFee,
|
||||
long lockTime)
|
||||
throws AddressFormatException, TransactionVerificationException {
|
||||
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
|
||||
TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0);
|
||||
Transaction delayedPayoutTx = new Transaction(params);
|
||||
delayedPayoutTx.addInput(p2SHMultiSigOutput);
|
||||
delayedPayoutTx.addInput(hashedMultiSigOutput);
|
||||
applyLockTime(lockTime, delayedPayoutTx);
|
||||
Coin outputAmount = p2SHMultiSigOutput.getValue().subtract(minerFee);
|
||||
Coin outputAmount = hashedMultiSigOutput.getValue().subtract(minerFee);
|
||||
delayedPayoutTx.addOutput(outputAmount, Address.fromString(params, donationAddressString));
|
||||
WalletService.printTx("Unsigned delayedPayoutTx ToDonationAddress", delayedPayoutTx);
|
||||
WalletService.verifyTransaction(delayedPayoutTx);
|
||||
|
@ -704,13 +725,17 @@ public class TradeWalletService {
|
|||
}
|
||||
|
||||
public byte[] signDelayedPayoutTx(Transaction delayedPayoutTx,
|
||||
Transaction preparedDepositTx,
|
||||
DeterministicKey myMultiSigKeyPair,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey)
|
||||
throws AddressFormatException, TransactionVerificationException {
|
||||
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
Sha256Hash sigHash = delayedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
Sha256Hash sigHash;
|
||||
Coin delayedPayoutTxInputValue = preparedDepositTx.getOutput(0).getValue();
|
||||
sigHash = delayedPayoutTx.hashForWitnessSignature(0, redeemScript,
|
||||
delayedPayoutTxInputValue, Transaction.SigHash.ALL, false);
|
||||
checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
|
||||
if (myMultiSigKeyPair.isEncrypted()) {
|
||||
checkNotNull(aesKey);
|
||||
|
@ -722,24 +747,45 @@ public class TradeWalletService {
|
|||
return mySignature.encodeToDER();
|
||||
}
|
||||
|
||||
public Transaction finalizeUnconnectedDelayedPayoutTx(Transaction delayedPayoutTx,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
byte[] buyerSignature,
|
||||
byte[] sellerSignature,
|
||||
Coin inputValue)
|
||||
throws AddressFormatException, TransactionVerificationException, SignatureDecodeException {
|
||||
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
ECKey.ECDSASignature buyerECDSASignature = ECKey.ECDSASignature.decodeFromDER(buyerSignature);
|
||||
ECKey.ECDSASignature sellerECDSASignature = ECKey.ECDSASignature.decodeFromDER(sellerSignature);
|
||||
TransactionSignature buyerTxSig = new TransactionSignature(buyerECDSASignature, Transaction.SigHash.ALL, false);
|
||||
TransactionSignature sellerTxSig = new TransactionSignature(sellerECDSASignature, Transaction.SigHash.ALL, false);
|
||||
TransactionInput input = delayedPayoutTx.getInput(0);
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
||||
input.setWitness(witness);
|
||||
WalletService.printTx("finalizeDelayedPayoutTx", delayedPayoutTx);
|
||||
WalletService.verifyTransaction(delayedPayoutTx);
|
||||
|
||||
if (checkNotNull(inputValue).isLessThan(delayedPayoutTx.getOutputSum().add(MIN_DELAYED_PAYOUT_TX_FEE))) {
|
||||
throw new TransactionVerificationException("Delayed payout tx is paying less than the minimum allowed tx fee");
|
||||
}
|
||||
Script scriptPubKey = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey, false);
|
||||
input.getScriptSig().correctlySpends(delayedPayoutTx, 0, witness, inputValue, scriptPubKey, Script.ALL_VERIFY_FLAGS);
|
||||
return delayedPayoutTx;
|
||||
}
|
||||
|
||||
public Transaction finalizeDelayedPayoutTx(Transaction delayedPayoutTx,
|
||||
byte[] buyerPubKey,
|
||||
byte[] sellerPubKey,
|
||||
byte[] buyerSignature,
|
||||
byte[] sellerSignature)
|
||||
throws AddressFormatException, TransactionVerificationException, WalletException, SignatureDecodeException {
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
ECKey.ECDSASignature buyerECDSASignature = ECKey.ECDSASignature.decodeFromDER(buyerSignature);
|
||||
ECKey.ECDSASignature sellerECDSASignature = ECKey.ECDSASignature.decodeFromDER(sellerSignature);
|
||||
TransactionSignature buyerTxSig = new TransactionSignature(buyerECDSASignature, Transaction.SigHash.ALL, false);
|
||||
TransactionSignature sellerTxSig = new TransactionSignature(sellerECDSASignature, Transaction.SigHash.ALL, false);
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
|
||||
|
||||
TransactionInput input = delayedPayoutTx.getInput(0);
|
||||
input.setScriptSig(inputScript);
|
||||
WalletService.printTx("finalizeDelayedPayoutTx", delayedPayoutTx);
|
||||
WalletService.verifyTransaction(delayedPayoutTx);
|
||||
finalizeUnconnectedDelayedPayoutTx(delayedPayoutTx, buyerPubKey, sellerPubKey, buyerSignature, sellerSignature, input.getValue());
|
||||
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
WalletService.checkScriptSig(delayedPayoutTx, input, 0);
|
||||
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
|
||||
input.verify(input.getConnectedOutput());
|
||||
return delayedPayoutTx;
|
||||
|
@ -779,7 +825,15 @@ public class TradeWalletService {
|
|||
// MS redeemScript
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
// MS output from prev. tx is index 0
|
||||
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
Sha256Hash sigHash;
|
||||
TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0);
|
||||
if (ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey())) {
|
||||
sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
} else {
|
||||
Coin inputValue = hashedMultiSigOutput.getValue();
|
||||
sigHash = preparedPayoutTx.hashForWitnessSignature(0, redeemScript,
|
||||
inputValue, Transaction.SigHash.ALL, false);
|
||||
}
|
||||
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
|
||||
if (multiSigKeyPair.isEncrypted()) {
|
||||
checkNotNull(aesKey);
|
||||
|
@ -822,7 +876,16 @@ public class TradeWalletService {
|
|||
// MS redeemScript
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
// MS output from prev. tx is index 0
|
||||
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0);
|
||||
boolean hashedMultiSigOutputIsLegacy = ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey());
|
||||
Sha256Hash sigHash;
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
} else {
|
||||
Coin inputValue = hashedMultiSigOutput.getValue();
|
||||
sigHash = payoutTx.hashForWitnessSignature(0, redeemScript,
|
||||
inputValue, Transaction.SigHash.ALL, false);
|
||||
}
|
||||
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
|
||||
if (multiSigKeyPair.isEncrypted()) {
|
||||
checkNotNull(aesKey);
|
||||
|
@ -832,10 +895,16 @@ public class TradeWalletService {
|
|||
Transaction.SigHash.ALL, false);
|
||||
TransactionSignature sellerTxSig = new TransactionSignature(sellerSignature, Transaction.SigHash.ALL, false);
|
||||
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (seller, buyer)
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig),
|
||||
redeemScript);
|
||||
TransactionInput input = payoutTx.getInput(0);
|
||||
input.setScriptSig(inputScript);
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig),
|
||||
redeemScript);
|
||||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
||||
input.setWitness(witness);
|
||||
}
|
||||
WalletService.printTx("payoutTx", payoutTx);
|
||||
WalletService.verifyTransaction(payoutTx);
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
|
@ -863,7 +932,16 @@ public class TradeWalletService {
|
|||
// MS redeemScript
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
// MS output from prev. tx is index 0
|
||||
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0);
|
||||
boolean hashedMultiSigOutputIsLegacy = ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey());
|
||||
Sha256Hash sigHash;
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
} else {
|
||||
Coin inputValue = hashedMultiSigOutput.getValue();
|
||||
sigHash = preparedPayoutTx.hashForWitnessSignature(0, redeemScript,
|
||||
inputValue, Transaction.SigHash.ALL, false);
|
||||
}
|
||||
checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
|
||||
if (myMultiSigKeyPair.isEncrypted()) {
|
||||
checkNotNull(aesKey);
|
||||
|
@ -895,9 +973,18 @@ public class TradeWalletService {
|
|||
TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature),
|
||||
Transaction.SigHash.ALL, false);
|
||||
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (seller, buyer)
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
|
||||
TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0);
|
||||
boolean hashedMultiSigOutputIsLegacy = ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey());
|
||||
TransactionInput input = payoutTx.getInput(0);
|
||||
input.setScriptSig(inputScript);
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig),
|
||||
redeemScript);
|
||||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
||||
input.setWitness(witness);
|
||||
}
|
||||
WalletService.printTx("mediated payoutTx", payoutTx);
|
||||
WalletService.verifyTransaction(payoutTx);
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
|
@ -945,9 +1032,9 @@ public class TradeWalletService {
|
|||
byte[] arbitratorPubKey)
|
||||
throws AddressFormatException, TransactionVerificationException, WalletException, SignatureDecodeException {
|
||||
Transaction depositTx = new Transaction(params, depositTxSerialized);
|
||||
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
|
||||
TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0);
|
||||
Transaction payoutTx = new Transaction(params);
|
||||
payoutTx.addInput(p2SHMultiSigOutput);
|
||||
payoutTx.addInput(hashedMultiSigOutput);
|
||||
if (buyerPayoutAmount.isPositive()) {
|
||||
payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||
}
|
||||
|
@ -957,7 +1044,15 @@ public class TradeWalletService {
|
|||
|
||||
// take care of sorting!
|
||||
Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
|
||||
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
Sha256Hash sigHash;
|
||||
boolean hashedMultiSigOutputIsLegacy = !ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey());
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
} else {
|
||||
Coin inputValue = hashedMultiSigOutput.getValue();
|
||||
sigHash = payoutTx.hashForWitnessSignature(0, redeemScript,
|
||||
inputValue, Transaction.SigHash.ALL, false);
|
||||
}
|
||||
checkNotNull(tradersMultiSigKeyPair, "tradersMultiSigKeyPair must not be null");
|
||||
if (tradersMultiSigKeyPair.isEncrypted()) {
|
||||
checkNotNull(aesKey);
|
||||
|
@ -966,11 +1061,18 @@ public class TradeWalletService {
|
|||
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
|
||||
TransactionSignature arbitratorTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(arbitratorSignature),
|
||||
Transaction.SigHash.ALL, false);
|
||||
// Take care of order of signatures. See comment below at getMultiSigRedeemScript (sort order needed here: arbitrator, seller, buyer)
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(arbitratorTxSig, tradersTxSig),
|
||||
redeemScript);
|
||||
TransactionInput input = payoutTx.getInput(0);
|
||||
input.setScriptSig(inputScript);
|
||||
// Take care of order of signatures. See comment below at getMultiSigRedeemScript (sort order needed here: arbitrator, seller, buyer)
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(
|
||||
ImmutableList.of(arbitratorTxSig, tradersTxSig),
|
||||
redeemScript);
|
||||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig);
|
||||
input.setWitness(witness);
|
||||
}
|
||||
WalletService.printTx("disputed payoutTx", payoutTx);
|
||||
WalletService.verifyTransaction(payoutTx);
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
|
@ -995,21 +1097,23 @@ public class TradeWalletService {
|
|||
String sellerPrivateKeyAsHex,
|
||||
String buyerPubKeyAsHex,
|
||||
String sellerPubKeyAsHex,
|
||||
boolean hashedMultiSigOutputIsLegacy,
|
||||
TxBroadcaster.Callback callback)
|
||||
throws AddressFormatException, TransactionVerificationException, WalletException {
|
||||
byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey();
|
||||
byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey();
|
||||
|
||||
Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
|
||||
Script hashedMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey,
|
||||
hashedMultiSigOutputIsLegacy);
|
||||
|
||||
Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(txFee);
|
||||
TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram());
|
||||
Coin msOutputValue = buyerPayoutAmount.add(sellerPayoutAmount).add(txFee);
|
||||
TransactionOutput hashedMultiSigOutput = new TransactionOutput(params, null, msOutputValue, hashedMultiSigOutputScript.getProgram());
|
||||
Transaction depositTx = new Transaction(params);
|
||||
depositTx.addOutput(p2SHMultiSigOutput);
|
||||
depositTx.addOutput(hashedMultiSigOutput);
|
||||
|
||||
Transaction payoutTx = new Transaction(params);
|
||||
Sha256Hash spendTxHash = Sha256Hash.wrap(depositTxHex);
|
||||
payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
|
||||
payoutTx.addInput(new TransactionInput(params, depositTx, null, new TransactionOutPoint(params, 0, spendTxHash), msOutputValue));
|
||||
|
||||
if (buyerPayoutAmount.isPositive()) {
|
||||
payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||
|
@ -1020,7 +1124,14 @@ public class TradeWalletService {
|
|||
|
||||
// take care of sorting!
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
Sha256Hash sigHash;
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
|
||||
} else {
|
||||
Coin inputValue = msOutputValue;
|
||||
sigHash = payoutTx.hashForWitnessSignature(0, redeemScript,
|
||||
inputValue, Transaction.SigHash.ALL, false);
|
||||
}
|
||||
|
||||
ECKey buyerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(buyerPrivateKeyAsHex));
|
||||
checkNotNull(buyerPrivateKey, "key must not be null");
|
||||
|
@ -1032,10 +1143,18 @@ public class TradeWalletService {
|
|||
|
||||
TransactionSignature buyerTxSig = new TransactionSignature(buyerECDSASignature, Transaction.SigHash.ALL, false);
|
||||
TransactionSignature sellerTxSig = new TransactionSignature(sellerECDSASignature, Transaction.SigHash.ALL, false);
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
|
||||
|
||||
TransactionInput input = payoutTx.getInput(0);
|
||||
input.setScriptSig(inputScript);
|
||||
if (hashedMultiSigOutputIsLegacy) {
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig),
|
||||
redeemScript);
|
||||
input.setScriptSig(inputScript);
|
||||
} else {
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig);
|
||||
input.setWitness(witness);
|
||||
}
|
||||
|
||||
WalletService.printTx("payoutTx", payoutTx);
|
||||
WalletService.verifyTransaction(payoutTx);
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
|
@ -1092,28 +1211,32 @@ public class TradeWalletService {
|
|||
"input.getConnectedOutput().getParentTransaction() must not be null");
|
||||
checkNotNull(input.getValue(), "input.getValue() must not be null");
|
||||
|
||||
// bitcoinSerialize(false) is used just in case the serialized tx is parsed by a bisq node still using
|
||||
// bitcoinj 0.14. This is not supposed to happen ever since Version.TRADE_PROTOCOL_VERSION was set to 3,
|
||||
// but it costs nothing to be on the safe side.
|
||||
// The serialized tx is just used to obtain its hash, so the witness data is not relevant.
|
||||
return new RawTransactionInput(input.getOutpoint().getIndex(),
|
||||
input.getConnectedOutput().getParentTransaction().bitcoinSerialize(false),
|
||||
input.getValue().value);
|
||||
}
|
||||
|
||||
private byte[] getMakersScriptSigProgram(TransactionInput transactionInput) throws TransactionVerificationException {
|
||||
byte[] scriptProgram = transactionInput.getScriptSig().getProgram();
|
||||
if (scriptProgram.length == 0) {
|
||||
throw new TransactionVerificationException("Inputs from maker not signed.");
|
||||
}
|
||||
|
||||
return scriptProgram;
|
||||
}
|
||||
|
||||
private TransactionInput getTransactionInput(Transaction depositTx,
|
||||
byte[] scriptProgram,
|
||||
RawTransactionInput rawTransactionInput) {
|
||||
return new TransactionInput(params, depositTx, scriptProgram, new TransactionOutPoint(params,
|
||||
rawTransactionInput.index, new Transaction(params, rawTransactionInput.parentTransaction)),
|
||||
return new TransactionInput(params, depositTx, scriptProgram, getConnectedOutPoint(rawTransactionInput),
|
||||
Coin.valueOf(rawTransactionInput.value));
|
||||
}
|
||||
|
||||
private TransactionOutPoint getConnectedOutPoint(RawTransactionInput rawTransactionInput) {
|
||||
return new TransactionOutPoint(params, rawTransactionInput.index,
|
||||
new Transaction(params, rawTransactionInput.parentTransaction));
|
||||
}
|
||||
|
||||
public boolean isP2WH(RawTransactionInput rawTransactionInput) {
|
||||
return ScriptPattern.isP2WH(
|
||||
checkNotNull(getConnectedOutPoint(rawTransactionInput).getConnectedOutput()).getScriptPubKey());
|
||||
}
|
||||
|
||||
|
||||
// TODO: Once we have removed legacy arbitrator from dispute domain we can remove that method as well.
|
||||
// Atm it is still used by traderSignAndFinalizeDisputedPayoutTx which is used by ArbitrationManager.
|
||||
|
@ -1144,8 +1267,13 @@ public class TradeWalletService {
|
|||
return ScriptBuilder.createMultiSigOutputScript(2, keys);
|
||||
}
|
||||
|
||||
private Script get2of2MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey) {
|
||||
return ScriptBuilder.createP2SHOutputScript(get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey));
|
||||
private Script get2of2MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey, boolean legacy) {
|
||||
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
|
||||
if (legacy) {
|
||||
return ScriptBuilder.createP2SHOutputScript(redeemScript);
|
||||
} else {
|
||||
return ScriptBuilder.createP2WSHOutputScript(redeemScript);
|
||||
}
|
||||
}
|
||||
|
||||
private Transaction createPayoutTx(Transaction depositTx,
|
||||
|
@ -1153,9 +1281,9 @@ public class TradeWalletService {
|
|||
Coin sellerPayoutAmount,
|
||||
String buyerAddressString,
|
||||
String sellerAddressString) throws AddressFormatException {
|
||||
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
|
||||
TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0);
|
||||
Transaction transaction = new Transaction(params);
|
||||
transaction.addInput(p2SHMultiSigOutput);
|
||||
transaction.addInput(hashedMultiSigOutput);
|
||||
if (buyerPayoutAmount.isPositive()) {
|
||||
transaction.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString));
|
||||
}
|
||||
|
@ -1187,13 +1315,10 @@ public class TradeWalletService {
|
|||
input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey));
|
||||
}
|
||||
} else if (ScriptPattern.isP2WPKH(scriptPubKey)) {
|
||||
// TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master)
|
||||
// Script scriptCode = ScriptBuilder.createP2PKHOutputScript(sigKey)
|
||||
Script scriptCode = new ScriptBuilder().data(
|
||||
ScriptBuilder.createOutputScript(LegacyAddress.fromKey(transaction.getParams(), sigKey)).getProgram())
|
||||
.build();
|
||||
// scriptCode is expected to have the format of a legacy P2PKH output script
|
||||
Script scriptCode = ScriptBuilder.createP2PKHOutputScript(sigKey);
|
||||
Coin value = input.getValue();
|
||||
TransactionSignature txSig = transaction.calculateWitnessSignature(inputIndex, sigKey, scriptCode, value,
|
||||
TransactionSignature txSig = transaction.calculateWitnessSignature(inputIndex, sigKey, aesKey, scriptCode, value,
|
||||
Transaction.SigHash.ALL, false);
|
||||
input.setScriptSig(ScriptBuilder.createEmpty());
|
||||
input.setWitness(TransactionWitness.redeemP2WPKH(txSig, sigKey));
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.bitcoinj.core.Coin;
|
|||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
@ -325,13 +324,10 @@ public abstract class WalletService {
|
|||
}
|
||||
} else if (ScriptPattern.isP2WPKH(scriptPubKey)) {
|
||||
try {
|
||||
// TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master)
|
||||
// Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key);
|
||||
Script scriptCode = new ScriptBuilder().data(
|
||||
ScriptBuilder.createOutputScript(LegacyAddress.fromKey(tx.getParams(), key)).getProgram())
|
||||
.build();
|
||||
// scriptCode is expected to have the format of a legacy P2PKH output script
|
||||
Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key);
|
||||
Coin value = txIn.getValue();
|
||||
TransactionSignature txSig = tx.calculateWitnessSignature(index, key, scriptCode, value,
|
||||
TransactionSignature txSig = tx.calculateWitnessSignature(index, key, aesKey, scriptCode, value,
|
||||
Transaction.SigHash.ALL, false);
|
||||
txIn.setScriptSig(ScriptBuilder.createEmpty());
|
||||
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
|
||||
|
@ -479,10 +475,10 @@ public abstract class WalletService {
|
|||
return getBalanceForAddress(getAddressFromOutput(output));
|
||||
}
|
||||
|
||||
public Coin getTxFeeForWithdrawalPerByte() {
|
||||
public Coin getTxFeeForWithdrawalPerVbyte() {
|
||||
Coin fee = (preferences.isUseCustomWithdrawalTxFee()) ?
|
||||
Coin.valueOf(preferences.getWithdrawalTxFeeInBytes()) :
|
||||
feeService.getTxFeePerByte();
|
||||
Coin.valueOf(preferences.getWithdrawalTxFeeInVbytes()) :
|
||||
feeService.getTxFeePerVbyte();
|
||||
log.info("tx fee = " + fee.toFriendlyString());
|
||||
return fee;
|
||||
}
|
||||
|
@ -521,7 +517,7 @@ public abstract class WalletService {
|
|||
throws InsufficientMoneyException, AddressFormatException {
|
||||
SendRequest sendRequest = SendRequest.emptyWallet(Address.fromString(params, toAddress));
|
||||
sendRequest.fee = Coin.ZERO;
|
||||
sendRequest.feePerKb = getTxFeeForWithdrawalPerByte().multiply(1000);
|
||||
sendRequest.feePerKb = getTxFeeForWithdrawalPerVbyte().multiply(1000);
|
||||
sendRequest.aesKey = aesKey;
|
||||
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
|
||||
printTx("empty btc wallet", sendResult.tx);
|
||||
|
|
|
@ -382,9 +382,9 @@ public class DaoFacade implements DaoSetupService {
|
|||
return BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight());
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getBlindVoteMiningFeeAndTxSize(Coin stake)
|
||||
public Tuple2<Coin, Integer> getBlindVoteMiningFeeAndTxVsize(Coin stake)
|
||||
throws WalletException, InsufficientMoneyException, TransactionVerificationException {
|
||||
return myBlindVoteListService.getMiningFeeAndTxSize(stake);
|
||||
return myBlindVoteListService.getMiningFeeAndTxVsize(stake);
|
||||
}
|
||||
|
||||
// Publish blindVote tx and broadcast blindVote to p2p network and store to blindVoteList.
|
||||
|
@ -532,12 +532,12 @@ public class DaoFacade implements DaoSetupService {
|
|||
lockupTxService.publishLockupTx(lockupAmount, lockTime, lockupReason, hash, resultHandler, exceptionHandler);
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getLockupTxMiningFeeAndTxSize(Coin lockupAmount,
|
||||
int lockTime,
|
||||
LockupReason lockupReason,
|
||||
byte[] hash)
|
||||
public Tuple2<Coin, Integer> getLockupTxMiningFeeAndTxVsize(Coin lockupAmount,
|
||||
int lockTime,
|
||||
LockupReason lockupReason,
|
||||
byte[] hash)
|
||||
throws InsufficientMoneyException, IOException, TransactionVerificationException, WalletException {
|
||||
return lockupTxService.getMiningFeeAndTxSize(lockupAmount, lockTime, lockupReason, hash);
|
||||
return lockupTxService.getMiningFeeAndTxVsize(lockupAmount, lockTime, lockupReason, hash);
|
||||
}
|
||||
|
||||
public void publishUnlockTx(String lockupTxId, Consumer<String> resultHandler,
|
||||
|
@ -545,9 +545,9 @@ public class DaoFacade implements DaoSetupService {
|
|||
unlockTxService.publishUnlockTx(lockupTxId, resultHandler, exceptionHandler);
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getUnlockTxMiningFeeAndTxSize(String lockupTxId)
|
||||
public Tuple2<Coin, Integer> getUnlockTxMiningFeeAndTxVsize(String lockupTxId)
|
||||
throws InsufficientMoneyException, TransactionVerificationException, WalletException {
|
||||
return unlockTxService.getMiningFeeAndTxSize(lockupTxId);
|
||||
return unlockTxService.getMiningFeeAndTxVsize(lockupTxId);
|
||||
}
|
||||
|
||||
public long getTotalLockupAmount() {
|
||||
|
|
|
@ -189,14 +189,14 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Tuple2<Coin, Integer> getMiningFeeAndTxSize(Coin stake)
|
||||
public Tuple2<Coin, Integer> getMiningFeeAndTxVsize(Coin stake)
|
||||
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||
// We set dummy opReturn data
|
||||
Coin blindVoteFee = BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight());
|
||||
Transaction dummyTx = getBlindVoteTx(stake, blindVoteFee, new byte[22]);
|
||||
Coin miningFee = dummyTx.getFee();
|
||||
int txSize = dummyTx.bitcoinSerialize().length;
|
||||
return new Tuple2<>(miningFee, txSize);
|
||||
int txVsize = dummyTx.getVsize();
|
||||
return new Tuple2<>(miningFee, txVsize);
|
||||
}
|
||||
|
||||
public void publishBlindVote(Coin stake, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
|
||||
|
|
|
@ -91,12 +91,12 @@ public class LockupTxService {
|
|||
}
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getMiningFeeAndTxSize(Coin lockupAmount, int lockTime, LockupReason lockupReason, byte[] hash)
|
||||
public Tuple2<Coin, Integer> getMiningFeeAndTxVsize(Coin lockupAmount, int lockTime, LockupReason lockupReason, byte[] hash)
|
||||
throws InsufficientMoneyException, WalletException, TransactionVerificationException, IOException {
|
||||
Transaction tx = getLockupTx(lockupAmount, lockTime, lockupReason, hash);
|
||||
Coin miningFee = tx.getFee();
|
||||
int txSize = tx.bitcoinSerialize().length;
|
||||
return new Tuple2<>(miningFee, txSize);
|
||||
int txVsize = tx.getVsize();
|
||||
return new Tuple2<>(miningFee, txVsize);
|
||||
}
|
||||
|
||||
private Transaction getLockupTx(Coin lockupAmount, int lockTime, LockupReason lockupReason, byte[] hash)
|
||||
|
|
|
@ -89,12 +89,12 @@ public class UnlockTxService {
|
|||
}
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getMiningFeeAndTxSize(String lockupTxId)
|
||||
public Tuple2<Coin, Integer> getMiningFeeAndTxVsize(String lockupTxId)
|
||||
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||
Transaction tx = getUnlockTx(lockupTxId);
|
||||
Coin miningFee = tx.getFee();
|
||||
int txSize = tx.bitcoinSerialize().length;
|
||||
return new Tuple2<>(miningFee, txSize);
|
||||
int txVsize = tx.getVsize();
|
||||
return new Tuple2<>(miningFee, txVsize);
|
||||
}
|
||||
|
||||
private Transaction getUnlockTx(String lockupTxId)
|
||||
|
|
|
@ -319,6 +319,24 @@ public class CurrencyUtil {
|
|||
return currencies;
|
||||
}
|
||||
|
||||
public static List<TradeCurrency> getAllAmazonGiftCardCurrencies() {
|
||||
List<TradeCurrency> currencies = new ArrayList<>(Arrays.asList(
|
||||
new FiatCurrency("AUD"),
|
||||
new FiatCurrency("CAD"),
|
||||
new FiatCurrency("EUR"),
|
||||
new FiatCurrency("GBP"),
|
||||
new FiatCurrency("INR"),
|
||||
new FiatCurrency("JPY"),
|
||||
new FiatCurrency("SAR"),
|
||||
new FiatCurrency("SEK"),
|
||||
new FiatCurrency("SGD"),
|
||||
new FiatCurrency("TRY"),
|
||||
new FiatCurrency("USD")
|
||||
));
|
||||
currencies.sort(Comparator.comparing(TradeCurrency::getCode));
|
||||
return currencies;
|
||||
}
|
||||
|
||||
// https://www.revolut.com/help/getting-started/exchanging-currencies/what-fiat-currencies-are-supported-for-holding-and-exchange
|
||||
public static List<TradeCurrency> getAllRevolutCurrencies() {
|
||||
ArrayList<TradeCurrency> currencies = new ArrayList<>(Arrays.asList(
|
||||
|
|
|
@ -41,7 +41,9 @@ public class LanguageUtil {
|
|||
"vi", // Vietnamese
|
||||
"th", // Thai
|
||||
"ja", // Japanese
|
||||
"fa" // Persian
|
||||
"fa", // Persian
|
||||
"it", // Italian
|
||||
"cs" // Czech
|
||||
/*
|
||||
// not translated yet
|
||||
"el", // Greek
|
||||
|
@ -49,7 +51,6 @@ public class LanguageUtil {
|
|||
"hu", // Hungarian
|
||||
"ro", // Romanian
|
||||
"tr" // Turkish
|
||||
"it", // Italian
|
||||
"iw", // Hebrew
|
||||
"hi", // Hindi
|
||||
"ko", // Korean
|
||||
|
@ -77,7 +78,6 @@ public class LanguageUtil {
|
|||
"ms", // Malay
|
||||
"is", // Icelandic
|
||||
"et", // Estonian
|
||||
"cs", // Czech
|
||||
"ar", // Arabic
|
||||
"vi", // Vietnamese
|
||||
"th", // Thai
|
||||
|
|
|
@ -163,7 +163,7 @@ public class CreateOfferService {
|
|||
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
|
||||
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
|
||||
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble);
|
||||
Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
|
||||
Coin txFeeFromFeeService = getEstimatedFeeAndTxVsize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
|
||||
Coin txFeeToUse = txFee.isPositive() ? txFee : txFeeFromFeeService;
|
||||
Coin makerFeeAsCoin = offerUtil.getMakerFee(amount);
|
||||
boolean isCurrencyForMakerFeeBtc = offerUtil.isCurrencyForMakerFeeBtc(amount);
|
||||
|
@ -233,15 +233,15 @@ public class CreateOfferService {
|
|||
return offer;
|
||||
}
|
||||
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSize(Coin amount,
|
||||
OfferPayload.Direction direction,
|
||||
double buyerSecurityDeposit,
|
||||
double sellerSecurityDeposit) {
|
||||
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(Coin amount,
|
||||
OfferPayload.Direction direction,
|
||||
double buyerSecurityDeposit,
|
||||
double sellerSecurityDeposit) {
|
||||
Coin reservedFundsForOffer = getReservedFundsForOffer(direction,
|
||||
amount,
|
||||
buyerSecurityDeposit,
|
||||
sellerSecurityDeposit);
|
||||
return txFeeEstimationService.getEstimatedFeeAndTxSizeForMaker(reservedFundsForOffer,
|
||||
return txFeeEstimationService.getEstimatedFeeAndTxVsizeForMaker(reservedFundsForOffer,
|
||||
offerUtil.getMakerFee(amount));
|
||||
}
|
||||
|
||||
|
|
|
@ -177,8 +177,8 @@ public class OfferUtil {
|
|||
return CoinUtil.getMakerFee(isCurrencyForMakerFeeBtc, amount);
|
||||
}
|
||||
|
||||
public Coin getTxFeeBySize(Coin txFeePerByteFromFeeService, int sizeInBytes) {
|
||||
return txFeePerByteFromFeeService.multiply(getAverageTakerFeeTxSize(sizeInBytes));
|
||||
public Coin getTxFeeByVsize(Coin txFeePerVbyteFromFeeService, int vsizeInVbytes) {
|
||||
return txFeePerVbyteFromFeeService.multiply(getAverageTakerFeeTxVsize(vsizeInVbytes));
|
||||
}
|
||||
|
||||
// We use the sum of the size of the trade fee and the deposit tx to get an average.
|
||||
|
@ -186,8 +186,8 @@ public class OfferUtil {
|
|||
// enough. With that we avoid that we overpay in case that the trade fee has many
|
||||
// inputs and we would apply that fee for the other 2 txs as well. We still might
|
||||
// overpay a bit for the payout tx.
|
||||
public int getAverageTakerFeeTxSize(int txSize) {
|
||||
return (txSize + 320) / 2;
|
||||
public int getAverageTakerFeeTxVsize(int txVsize) {
|
||||
return (txVsize + 233) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -77,10 +77,9 @@ public class TakeOfferModel implements Model {
|
|||
private Coin securityDeposit;
|
||||
private boolean useSavingsWallet;
|
||||
|
||||
// 260 kb is typical trade fee tx size with 1 input, but trade tx (deposit + payout)
|
||||
// are larger so we adjust to 320.
|
||||
private final int feeTxSize = 320;
|
||||
private Coin txFeePerByteFromFeeService;
|
||||
// Use an average of a typical trade fee tx with 1 input, deposit tx and payout tx.
|
||||
private final int feeTxVsize = 192; // (175+233+169)/3
|
||||
private Coin txFeePerVbyteFromFeeService;
|
||||
@Getter
|
||||
private Coin txFeeFromFeeService;
|
||||
@Getter
|
||||
|
@ -150,26 +149,26 @@ public class TakeOfferModel implements Model {
|
|||
// payout tx with different fees might be an option but RBF is not supported yet
|
||||
// in BitcoinJ and batched txs would add more complexity to the trade protocol.
|
||||
|
||||
// A typical trade fee tx has about 260 bytes (if one input). The trade txs has
|
||||
// about 336-414 bytes. We use 320 as a average value.
|
||||
// A typical trade fee tx has about 175 vbytes (if one input). The trade txs has
|
||||
// about 169-263 vbytes. We use 192 as a average value.
|
||||
|
||||
// Fee calculations:
|
||||
// Trade fee tx: 260 bytes (1 input)
|
||||
// Deposit tx: 336 bytes (1 MS output+ OP_RETURN) - 414 bytes
|
||||
// Trade fee tx: 175 vbytes (1 input)
|
||||
// Deposit tx: 233 vbytes (1 MS output+ OP_RETURN) - 263 vbytes
|
||||
// (1 MS output + OP_RETURN + change in case of smaller trade amount)
|
||||
// Payout tx: 371 bytes
|
||||
// Disputed payout tx: 408 bytes
|
||||
// Payout tx: 169 vbytes
|
||||
// Disputed payout tx: 139 vbytes
|
||||
|
||||
txFeePerByteFromFeeService = getTxFeePerByte();
|
||||
txFeeFromFeeService = offerUtil.getTxFeeBySize(txFeePerByteFromFeeService, feeTxSize);
|
||||
log.info("{} txFeePerByte = {}", feeService.getClass().getSimpleName(), txFeePerByteFromFeeService);
|
||||
txFeePerVbyteFromFeeService = getTxFeePerVbyte();
|
||||
txFeeFromFeeService = offerUtil.getTxFeeByVsize(txFeePerVbyteFromFeeService, feeTxVsize);
|
||||
log.info("{} txFeePerVbyte = {}", feeService.getClass().getSimpleName(), txFeePerVbyteFromFeeService);
|
||||
}
|
||||
|
||||
private Coin getTxFeePerByte() {
|
||||
private Coin getTxFeePerVbyte() {
|
||||
try {
|
||||
CompletableFuture<Void> feeRequestFuture = CompletableFuture.runAsync(feeService::requestFees);
|
||||
feeRequestFuture.get(); // Block until async fee request is complete.
|
||||
return feeService.getTxFeePerByte();
|
||||
return feeService.getTxFeePerVbyte();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new IllegalStateException("Could not request fees from fee service.", e);
|
||||
}
|
||||
|
@ -284,7 +283,7 @@ public class TakeOfferModel implements Model {
|
|||
this.totalAvailableBalance = null;
|
||||
this.totalToPayAsCoin = null;
|
||||
this.txFeeFromFeeService = null;
|
||||
this.txFeePerByteFromFeeService = null;
|
||||
this.txFeePerVbyteFromFeeService = null;
|
||||
this.useSavingsWallet = true;
|
||||
this.volume = null;
|
||||
}
|
||||
|
@ -300,8 +299,8 @@ public class TakeOfferModel implements Model {
|
|||
", addressEntry=" + addressEntry + "\n" +
|
||||
", amount=" + amount + "\n" +
|
||||
", securityDeposit=" + securityDeposit + "\n" +
|
||||
", feeTxSize=" + feeTxSize + "\n" +
|
||||
", txFeePerByteFromFeeService=" + txFeePerByteFromFeeService + "\n" +
|
||||
", feeTxVsize=" + feeTxVsize + "\n" +
|
||||
", txFeePerVbyteFromFeeService=" + txFeePerVbyteFromFeeService + "\n" +
|
||||
", txFeeFromFeeService=" + txFeeFromFeeService + "\n" +
|
||||
", takerFee=" + takerFee + "\n" +
|
||||
", totalToPayAsCoin=" + totalToPayAsCoin + "\n" +
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.payment;
|
||||
|
||||
import bisq.core.payment.payload.AmazonGiftCardAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
|
||||
public final class AmazonGiftCardAccount extends PaymentAccount {
|
||||
|
||||
public AmazonGiftCardAccount() {
|
||||
super(PaymentMethod.AMAZON_GIFT_CARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PaymentAccountPayload createPayload() {
|
||||
return new AmazonGiftCardAccountPayload(paymentMethod.getId(), id);
|
||||
}
|
||||
|
||||
public String getEmailOrMobileNr() {
|
||||
return getAmazonGiftCardAccountPayload().getEmailOrMobileNr();
|
||||
}
|
||||
|
||||
public void setEmailOrMobileNr(String emailOrMobileNr) {
|
||||
getAmazonGiftCardAccountPayload().setEmailOrMobileNr(emailOrMobileNr);
|
||||
}
|
||||
|
||||
private AmazonGiftCardAccountPayload getAmazonGiftCardAccountPayload() {
|
||||
return (AmazonGiftCardAccountPayload) paymentAccountPayload;
|
||||
}
|
||||
}
|
|
@ -80,6 +80,8 @@ public class PaymentAccountFactory {
|
|||
return new AdvancedCashAccount();
|
||||
case PaymentMethod.TRANSFERWISE_ID:
|
||||
return new TransferwiseAccount();
|
||||
case PaymentMethod.AMAZON_GIFT_CARD_ID:
|
||||
return new AmazonGiftCardAccount();
|
||||
case PaymentMethod.BLOCK_CHAINS_INSTANT_ID:
|
||||
return new InstantCryptoCurrencyAccount();
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.payment.payload;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
@Setter
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class AmazonGiftCardAccountPayload extends PaymentAccountPayload {
|
||||
private String emailOrMobileNr;
|
||||
|
||||
public AmazonGiftCardAccountPayload(String paymentMethod, String id) {
|
||||
super(paymentMethod, id);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private AmazonGiftCardAccountPayload(String paymentMethodName,
|
||||
String id,
|
||||
String emailOrMobileNr,
|
||||
long maxTradePeriod,
|
||||
Map<String, String> excludeFromJsonDataMap) {
|
||||
super(paymentMethodName,
|
||||
id,
|
||||
maxTradePeriod,
|
||||
excludeFromJsonDataMap);
|
||||
this.emailOrMobileNr = emailOrMobileNr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
protobuf.AmazonGiftCardAccountPayload.Builder builder =
|
||||
protobuf.AmazonGiftCardAccountPayload.newBuilder()
|
||||
.setEmailOrMobileNr(emailOrMobileNr);
|
||||
return getPaymentAccountPayloadBuilder()
|
||||
.setAmazonGiftCardAccountPayload(builder)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static PaymentAccountPayload fromProto(protobuf.PaymentAccountPayload proto) {
|
||||
protobuf.AmazonGiftCardAccountPayload amazonGiftCardAccountPayload = proto.getAmazonGiftCardAccountPayload();
|
||||
return new AmazonGiftCardAccountPayload(proto.getPaymentMethodId(),
|
||||
proto.getId(),
|
||||
amazonGiftCardAccountPayload.getEmailOrMobileNr(),
|
||||
proto.getMaxTradePeriod(),
|
||||
new HashMap<>(proto.getExcludeFromJsonDataMap()));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getPaymentDetails() {
|
||||
return Res.get(paymentMethodId) + " - " + getPaymentDetailsForTradePopup().replace("\n", ", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentDetailsForTradePopup() {
|
||||
return Res.getWithCol("payment.email.mobile") + " " + emailOrMobileNr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAgeWitnessInputData() {
|
||||
String data = "AmazonGiftCard" + emailOrMobileNr;
|
||||
return super.getAgeWitnessInputData(data.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
|
@ -93,6 +93,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
|||
public static final String PROMPT_PAY_ID = "PROMPT_PAY";
|
||||
public static final String ADVANCED_CASH_ID = "ADVANCED_CASH";
|
||||
public static final String TRANSFERWISE_ID = "TRANSFERWISE";
|
||||
public static final String AMAZON_GIFT_CARD_ID = "AMAZON_GIFT_CARD";
|
||||
public static final String BLOCK_CHAINS_INSTANT_ID = "BLOCK_CHAINS_INSTANT";
|
||||
|
||||
// Cannot be deleted as it would break old trade history entries
|
||||
|
@ -132,6 +133,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
|||
public static PaymentMethod PROMPT_PAY;
|
||||
public static PaymentMethod ADVANCED_CASH;
|
||||
public static PaymentMethod TRANSFERWISE;
|
||||
public static PaymentMethod AMAZON_GIFT_CARD;
|
||||
public static PaymentMethod BLOCK_CHAINS_INSTANT;
|
||||
|
||||
// Cannot be deleted as it would break old trade history entries
|
||||
|
@ -176,6 +178,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
|||
SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK),
|
||||
HAL_CASH = new PaymentMethod(HAL_CASH_ID, DAY, DEFAULT_TRADE_LIMIT_LOW_RISK),
|
||||
F2F = new PaymentMethod(F2F_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_LOW_RISK),
|
||||
AMAZON_GIFT_CARD = new PaymentMethod(AMAZON_GIFT_CARD_ID, DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK),
|
||||
|
||||
// Trans national
|
||||
UPHOLD = new PaymentMethod(UPHOLD_ID, DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK),
|
||||
|
|
|
@ -23,6 +23,7 @@ import bisq.core.dao.governance.blindvote.storage.BlindVotePayload;
|
|||
import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload;
|
||||
import bisq.core.payment.payload.AdvancedCashAccountPayload;
|
||||
import bisq.core.payment.payload.AliPayAccountPayload;
|
||||
import bisq.core.payment.payload.AmazonGiftCardAccountPayload;
|
||||
import bisq.core.payment.payload.AustraliaPayidPayload;
|
||||
import bisq.core.payment.payload.CashAppAccountPayload;
|
||||
import bisq.core.payment.payload.CashDepositAccountPayload;
|
||||
|
@ -151,6 +152,8 @@ public class CoreProtoResolver implements ProtoResolver {
|
|||
return AdvancedCashAccountPayload.fromProto(proto);
|
||||
case TRANSFERWISE_ACCOUNT_PAYLOAD:
|
||||
return TransferwiseAccountPayload.fromProto(proto);
|
||||
case AMAZON_GIFT_CARD_ACCOUNT_PAYLOAD:
|
||||
return AmazonGiftCardAccountPayload.fromProto(proto);
|
||||
case INSTANT_CRYPTO_CURRENCY_ACCOUNT_PAYLOAD:
|
||||
return InstantCryptoCurrencyPayload.fromProto(proto);
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import bisq.core.trade.statistics.TradeStatistics3Store;
|
|||
import bisq.core.user.PreferencesPayload;
|
||||
import bisq.core.user.UserPayload;
|
||||
|
||||
import bisq.network.p2p.MailboxMessageList;
|
||||
import bisq.network.p2p.peers.peerexchange.PeerList;
|
||||
import bisq.network.p2p.storage.persistence.SequenceNumberMap;
|
||||
|
||||
|
@ -129,6 +130,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
|
|||
return SignedWitnessStore.fromProto(proto.getSignedWitnessStore());
|
||||
case TRADE_STATISTICS3_STORE:
|
||||
return TradeStatistics3Store.fromProto(proto.getTradeStatistics3Store());
|
||||
case MAILBOX_MESSAGE_LIST:
|
||||
return MailboxMessageList.fromProto(proto.getMailboxMessageList(), networkProtoResolver);
|
||||
|
||||
default:
|
||||
throw new ProtobufferRuntimeException("Unknown proto message case(PB.PersistableEnvelope). " +
|
||||
|
|
|
@ -60,7 +60,7 @@ public class FeeService {
|
|||
// Static
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Miner fees are between 1-600 sat/byte. We try to stay on the safe side. BTC_DEFAULT_TX_FEE is only used if our
|
||||
// Miner fees are between 1-600 sat/vbyte. We try to stay on the safe side. BTC_DEFAULT_TX_FEE is only used if our
|
||||
// fee service would not deliver data.
|
||||
private static final long BTC_DEFAULT_TX_FEE = 50;
|
||||
private static final long MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN = 2;
|
||||
|
@ -94,10 +94,10 @@ public class FeeService {
|
|||
|
||||
private final FeeProvider feeProvider;
|
||||
private final IntegerProperty feeUpdateCounter = new SimpleIntegerProperty(0);
|
||||
private long txFeePerByte = BTC_DEFAULT_TX_FEE;
|
||||
private long txFeePerVbyte = BTC_DEFAULT_TX_FEE;
|
||||
private Map<String, Long> timeStampMap;
|
||||
private long lastRequest;
|
||||
private long minFeePerByte;
|
||||
private long minFeePerVByte;
|
||||
private long epochInSecondAtLastRequest;
|
||||
|
||||
|
||||
|
@ -118,7 +118,7 @@ public class FeeService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
minFeePerByte = Config.baseCurrencyNetwork().getDefaultMinFeePerByte();
|
||||
minFeePerVByte = Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte();
|
||||
|
||||
requestFees();
|
||||
|
||||
|
@ -150,15 +150,15 @@ public class FeeService {
|
|||
timeStampMap = result.first;
|
||||
epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs");
|
||||
final Map<String, Long> map = result.second;
|
||||
txFeePerByte = map.get("BTC");
|
||||
txFeePerVbyte = map.get("BTC");
|
||||
|
||||
if (txFeePerByte < minFeePerByte) {
|
||||
log.warn("The delivered fee per byte is smaller than the min. default fee of 5 sat/byte");
|
||||
txFeePerByte = minFeePerByte;
|
||||
if (txFeePerVbyte < minFeePerVByte) {
|
||||
log.warn("The delivered fee per vbyte is smaller than the min. default fee of 5 sat/vbyte");
|
||||
txFeePerVbyte = minFeePerVByte;
|
||||
}
|
||||
|
||||
feeUpdateCounter.set(feeUpdateCounter.get() + 1);
|
||||
log.info("BTC tx fee: txFeePerByte={}", txFeePerByte);
|
||||
log.info("BTC tx fee: txFeePerVbyte={}", txFeePerVbyte);
|
||||
if (resultHandler != null)
|
||||
resultHandler.run();
|
||||
});
|
||||
|
@ -180,12 +180,12 @@ public class FeeService {
|
|||
}
|
||||
}
|
||||
|
||||
public Coin getTxFee(int sizeInBytes) {
|
||||
return getTxFeePerByte().multiply(sizeInBytes);
|
||||
public Coin getTxFee(int vsizeInVbytes) {
|
||||
return getTxFeePerVbyte().multiply(vsizeInVbytes);
|
||||
}
|
||||
|
||||
public Coin getTxFeePerByte() {
|
||||
return Coin.valueOf(txFeePerByte);
|
||||
public Coin getTxFeePerVbyte() {
|
||||
return Coin.valueOf(txFeePerVbyte);
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty feeUpdateCounterProperty() {
|
||||
|
@ -195,7 +195,7 @@ public class FeeService {
|
|||
public String getFeeTextForDisplay() {
|
||||
// only show the fee rate if it has been initialized from the service (see feeUpdateCounter)
|
||||
if (feeUpdateCounter.get() > 0)
|
||||
return Res.get("mainView.footer.btcFeeRate", txFeePerByte);
|
||||
return Res.get("mainView.footer.btcFeeRate", txFeePerVbyte);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import bisq.core.trade.failed.FailedTradesManager;
|
|||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
|
@ -66,6 +67,7 @@ public class CorePersistedDataHost {
|
|||
persistedDataHosts.add(injector.getInstance(RefundDisputeListService.class));
|
||||
persistedDataHosts.add(injector.getInstance(P2PDataStorage.class));
|
||||
persistedDataHosts.add(injector.getInstance(PeerManager.class));
|
||||
persistedDataHosts.add(injector.getInstance(P2PService.class));
|
||||
|
||||
if (injector.getInstance(Config.class).daoActivated) {
|
||||
persistedDataHosts.add(injector.getInstance(BallotListService.class));
|
||||
|
|
|
@ -177,7 +177,7 @@ public abstract class SupportManager {
|
|||
requestPersistence();
|
||||
|
||||
if (decryptedMessageWithPubKey != null)
|
||||
p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey);
|
||||
p2PService.removeMailboxMsg(decryptedMessageWithPubKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,7 +314,7 @@ public abstract class SupportManager {
|
|||
log.debug("decryptedMessageWithPubKey.message " + networkEnvelope);
|
||||
if (networkEnvelope instanceof SupportMessage) {
|
||||
dispatchMessage((SupportMessage) networkEnvelope);
|
||||
p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey);
|
||||
p2PService.removeMailboxMsg(decryptedMessageWithPubKey);
|
||||
} else if (networkEnvelope instanceof AckMessage) {
|
||||
onAckMessage((AckMessage) networkEnvelope, decryptedMessageWithPubKey);
|
||||
}
|
||||
|
|
|
@ -395,6 +395,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
if (!storedDisputeOptional.isPresent()) {
|
||||
disputeList.add(dispute);
|
||||
trade.setDisputeState(getDisputeStateStartedByPeer());
|
||||
tradeManager.requestPersistence();
|
||||
errorMessage = null;
|
||||
} else {
|
||||
// valid case if both have opened a dispute and agent was not online.
|
||||
|
|
|
@ -204,9 +204,10 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
|||
trade.getDisputeState() == Trade.DisputeState.MEDIATION_STARTED_BY_PEER) {
|
||||
trade.getProcessModel().setBuyerPayoutAmountFromMediation(disputeResult.getBuyerPayoutAmount().value);
|
||||
trade.getProcessModel().setSellerPayoutAmountFromMediation(disputeResult.getSellerPayoutAmount().value);
|
||||
tradeManager.requestPersistence();
|
||||
|
||||
trade.setDisputeState(Trade.DisputeState.MEDIATION_CLOSED);
|
||||
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
} else {
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
|
||||
|
@ -243,6 +244,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
|||
DisputeProtocol tradeProtocol = (DisputeProtocol) tradeManager.getTradeProtocol(trade);
|
||||
|
||||
trade.setMediationResultState(MediationResultState.MEDIATION_RESULT_ACCEPTED);
|
||||
tradeManager.requestPersistence();
|
||||
|
||||
// If we have not got yet the peers signature we sign and send to the peer our signature.
|
||||
// Otherwise we sign and complete with the peers signature the payout tx.
|
||||
|
@ -265,5 +267,6 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
|||
|
||||
public void rejectMediationResult(Trade trade) {
|
||||
trade.setMediationResultState(MediationResultState.MEDIATION_RESULT_REJECTED);
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,6 +205,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
|||
if (trade.getDisputeState() == Trade.DisputeState.REFUND_REQUESTED ||
|
||||
trade.getDisputeState() == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) {
|
||||
trade.setDisputeState(Trade.DisputeState.REFUND_REQUEST_CLOSED);
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
} else {
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
|
||||
|
|
|
@ -164,5 +164,7 @@ public class TraderChatManager extends SupportManager {
|
|||
trade.getDate().getTime());
|
||||
chatMessage.setSystemMessage(true);
|
||||
trade.getChatMessages().add(chatMessage);
|
||||
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -700,10 +700,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
}
|
||||
}
|
||||
|
||||
public void appendErrorMessage(String msg) {
|
||||
errorMessage = errorMessage == null ? msg : errorMessage + "\n" + msg;
|
||||
}
|
||||
|
||||
public boolean mediationResultAppliedPenaltyToSeller() {
|
||||
// If mediated payout is same or more then normal payout we enable otherwise a penalty was applied
|
||||
// by mediators and we keep the confirm disabled to avoid that the seller can complete the trade
|
||||
|
@ -1099,6 +1095,9 @@ public abstract class Trade implements Tradable, Model {
|
|||
private void setConfirmedState() {
|
||||
// we only apply the state if we are not already further in the process
|
||||
if (!isDepositConfirmed()) {
|
||||
// As setState is called here from the trade itself we cannot trigger a requestPersistence call.
|
||||
// But as we get setupConfidenceListener called at startup anyway there is no issue if it would not be
|
||||
// persisted in case the shutdown routine did not persist the trade.
|
||||
setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -342,11 +342,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
private void initPersistedTrade(Trade trade) {
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
trade.updateDepositTxFromWallet();
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
private void initTradeAndProtocol(Trade trade, TradeProtocol tradeProtocol) {
|
||||
tradeProtocol.initialize(processModelServiceProvider, this, trade.getOffer());
|
||||
trade.initialize(processModelServiceProvider);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
public void requestPersistence() {
|
||||
|
@ -431,6 +433,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
|
||||
((TakerProtocol) tradeProtocol).onTakeOffer();
|
||||
tradeResultHandler.handleResult(trade);
|
||||
requestPersistence();
|
||||
}
|
||||
},
|
||||
errorMessageHandler);
|
||||
|
@ -544,10 +547,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
Date halfTradePeriodDate = trade.getHalfTradePeriodDate();
|
||||
if (maxTradePeriodDate != null && halfTradePeriodDate != null) {
|
||||
Date now = new Date();
|
||||
if (now.after(maxTradePeriodDate))
|
||||
if (now.after(maxTradePeriodDate)) {
|
||||
trade.setTradePeriodState(Trade.TradePeriodState.TRADE_PERIOD_OVER);
|
||||
else if (now.after(halfTradePeriodDate))
|
||||
requestPersistence();
|
||||
} else if (now.after(halfTradePeriodDate)) {
|
||||
trade.setTradePeriodState(Trade.TradePeriodState.SECOND_HALF);
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,6 +35,9 @@ import java.util.List;
|
|||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
//TODO with the redesign of mailbox messages that is not required anymore. We leave it for now as we want to minimize
|
||||
// changes for the 1.5.0 release but we should clean up afterwards...
|
||||
|
||||
/**
|
||||
* Util for removing pending mailbox messages in case the trade has been closed by the seller after confirming receipt
|
||||
* and a AckMessage as mailbox message will be sent by the buyer once they go online. In that case the seller's trade
|
||||
|
@ -73,8 +76,7 @@ public class CleanupMailboxMessages {
|
|||
}
|
||||
|
||||
private void cleanupMailboxMessages(List<Trade> trades) {
|
||||
p2PService.getMailboxItemsByUid().values()
|
||||
.stream().map(P2PService.MailboxItem::getDecryptedMessageWithPubKey)
|
||||
p2PService.getMailBoxMessages()
|
||||
.forEach(message -> handleDecryptedMessageWithPubKey(message, trades));
|
||||
}
|
||||
|
||||
|
@ -102,7 +104,7 @@ public class CleanupMailboxMessages {
|
|||
private void removeEntryFromMailbox(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) {
|
||||
log.info("We found a pending mailbox message ({}) for trade {}. As the trade is closed we remove the mailbox message.",
|
||||
decryptedMessageWithPubKey.getNetworkEnvelope().getClass().getSimpleName(), trade.getId());
|
||||
p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey);
|
||||
p2PService.removeMailboxMsg(decryptedMessageWithPubKey);
|
||||
}
|
||||
|
||||
private boolean isMyMessage(TradeMessage message, Trade trade) {
|
||||
|
|
|
@ -84,13 +84,13 @@ public class ClosedTradableManager implements PersistedDataHost {
|
|||
|
||||
public void add(Tradable tradable) {
|
||||
if (closedTradables.add(tradable)) {
|
||||
persistenceManager.requestPersistence();
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Tradable tradable) {
|
||||
if (closedTradables.remove(tradable)) {
|
||||
persistenceManager.requestPersistence();
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,4 +117,8 @@ public class ClosedTradableManager implements PersistedDataHost {
|
|||
return getClosedTrades().stream()
|
||||
.filter(Trade::isFundsLockedIn);
|
||||
}
|
||||
|
||||
private void requestPersistence() {
|
||||
persistenceManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,13 +95,13 @@ public class FailedTradesManager implements PersistedDataHost {
|
|||
|
||||
public void add(Trade trade) {
|
||||
if (failedTrades.add(trade)) {
|
||||
persistenceManager.requestPersistence();
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeTrade(Trade trade) {
|
||||
if (failedTrades.remove(trade)) {
|
||||
persistenceManager.requestPersistence();
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ public class FailedTradesManager implements PersistedDataHost {
|
|||
if (unFailTradeCallback.apply(trade)) {
|
||||
log.info("Unfailing trade {}", trade.getId());
|
||||
if (failedTrades.remove(trade)) {
|
||||
persistenceManager.requestPersistence();
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,4 +151,8 @@ public class FailedTradesManager implements PersistedDataHost {
|
|||
}
|
||||
return blockingTrades.toString();
|
||||
}
|
||||
|
||||
private void requestPersistence() {
|
||||
persistenceManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,19 @@ import lombok.Value;
|
|||
public final class DelayedPayoutTxSignatureRequest extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] delayedPayoutTx;
|
||||
private final byte[] delayedPayoutTxSellerSignature;
|
||||
|
||||
public DelayedPayoutTxSignatureRequest(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTx) {
|
||||
byte[] delayedPayoutTx,
|
||||
byte[] delayedPayoutTxSellerSignature) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
delayedPayoutTx);
|
||||
delayedPayoutTx,
|
||||
delayedPayoutTxSellerSignature);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -53,10 +56,12 @@ public final class DelayedPayoutTxSignatureRequest extends TradeMessage implemen
|
|||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTx) {
|
||||
byte[] delayedPayoutTx,
|
||||
byte[] delayedPayoutTxSellerSignature) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.delayedPayoutTx = delayedPayoutTx;
|
||||
this.delayedPayoutTxSellerSignature = delayedPayoutTxSellerSignature;
|
||||
}
|
||||
|
||||
|
||||
|
@ -67,16 +72,19 @@ public final class DelayedPayoutTxSignatureRequest extends TradeMessage implemen
|
|||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
|
||||
.setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx))
|
||||
.setDelayedPayoutTxSellerSignature(ByteString.copyFrom(delayedPayoutTxSellerSignature)))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DelayedPayoutTxSignatureRequest fromProto(protobuf.DelayedPayoutTxSignatureRequest proto, int messageVersion) {
|
||||
public static DelayedPayoutTxSignatureRequest fromProto(protobuf.DelayedPayoutTxSignatureRequest proto,
|
||||
int messageVersion) {
|
||||
return new DelayedPayoutTxSignatureRequest(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDelayedPayoutTx().toByteArray());
|
||||
proto.getDelayedPayoutTx().toByteArray(),
|
||||
proto.getDelayedPayoutTxSellerSignature().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,6 +92,7 @@ public final class DelayedPayoutTxSignatureRequest extends TradeMessage implemen
|
|||
return "DelayedPayoutTxSignatureRequest{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
|
||||
",\n delayedPayoutTxSellerSignature=" + Utilities.bytesAsHexString(delayedPayoutTxSellerSignature) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,17 +32,20 @@ import lombok.Value;
|
|||
@Value
|
||||
public final class DelayedPayoutTxSignatureResponse extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] delayedPayoutTxSignature;
|
||||
private final byte[] delayedPayoutTxBuyerSignature;
|
||||
private final byte[] depositTx;
|
||||
|
||||
public DelayedPayoutTxSignatureResponse(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTxSignature) {
|
||||
byte[] delayedPayoutTxBuyerSignature,
|
||||
byte[] depositTx) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
delayedPayoutTxSignature);
|
||||
delayedPayoutTxBuyerSignature,
|
||||
depositTx);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -53,10 +56,12 @@ public final class DelayedPayoutTxSignatureResponse extends TradeMessage impleme
|
|||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] delayedPayoutTxSignature) {
|
||||
byte[] delayedPayoutTxBuyerSignature,
|
||||
byte[] depositTx) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.delayedPayoutTxSignature = delayedPayoutTxSignature;
|
||||
this.delayedPayoutTxBuyerSignature = delayedPayoutTxBuyerSignature;
|
||||
this.depositTx = depositTx;
|
||||
}
|
||||
|
||||
|
||||
|
@ -67,24 +72,28 @@ public final class DelayedPayoutTxSignatureResponse extends TradeMessage impleme
|
|||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDelayedPayoutTxSignature(ByteString.copyFrom(delayedPayoutTxSignature))
|
||||
.setDelayedPayoutTxBuyerSignature(ByteString.copyFrom(delayedPayoutTxBuyerSignature))
|
||||
.setDepositTx(ByteString.copyFrom(depositTx))
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DelayedPayoutTxSignatureResponse fromProto(protobuf.DelayedPayoutTxSignatureResponse proto, int messageVersion) {
|
||||
public static DelayedPayoutTxSignatureResponse fromProto(protobuf.DelayedPayoutTxSignatureResponse proto,
|
||||
int messageVersion) {
|
||||
return new DelayedPayoutTxSignatureResponse(messageVersion,
|
||||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDelayedPayoutTxSignature().toByteArray());
|
||||
proto.getDelayedPayoutTxBuyerSignature().toByteArray(),
|
||||
proto.getDepositTx().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DelayedPayoutTxSignatureResponse{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n delayedPayoutTxSignature=" + Utilities.bytesAsHexString(delayedPayoutTxSignature) +
|
||||
",\n delayedPayoutTxBuyerSignature=" + Utilities.bytesAsHexString(delayedPayoutTxBuyerSignature) +
|
||||
",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,17 +34,17 @@ import lombok.Value;
|
|||
@Value
|
||||
public final class DepositTxMessage extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final byte[] depositTx;
|
||||
private final byte[] depositTxWithoutWitnesses;
|
||||
|
||||
public DepositTxMessage(String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] depositTx) {
|
||||
byte[] depositTxWithoutWitnesses) {
|
||||
this(Version.getP2PMessageVersion(),
|
||||
uid,
|
||||
tradeId,
|
||||
senderNodeAddress,
|
||||
depositTx);
|
||||
depositTxWithoutWitnesses);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -55,10 +55,10 @@ public final class DepositTxMessage extends TradeMessage implements DirectMessag
|
|||
String uid,
|
||||
String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
byte[] depositTx) {
|
||||
byte[] depositTxWithoutWitnesses) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.depositTx = depositTx;
|
||||
this.depositTxWithoutWitnesses = depositTxWithoutWitnesses;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,7 +68,7 @@ public final class DepositTxMessage extends TradeMessage implements DirectMessag
|
|||
.setUid(uid)
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDepositTx(ByteString.copyFrom(depositTx)))
|
||||
.setDepositTxWithoutWitnesses(ByteString.copyFrom(depositTxWithoutWitnesses)))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -77,14 +77,14 @@ public final class DepositTxMessage extends TradeMessage implements DirectMessag
|
|||
proto.getUid(),
|
||||
proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getDepositTx().toByteArray());
|
||||
proto.getDepositTxWithoutWitnesses().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DepositTxMessage{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
|
||||
",\n depositTxWithoutWitnesses=" + Utilities.bytesAsHexString(depositTxWithoutWitnesses) +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
|||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
|
||||
|
@ -103,6 +104,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
|||
MakerRemovesOpenOffer.class,
|
||||
BuyerVerifiesPreparedDelayedPayoutTx.class,
|
||||
BuyerSignsDelayedPayoutTx.class,
|
||||
BuyerFinalizesDelayedPayoutTx.class,
|
||||
BuyerSendsDelayedPayoutTxSignatureResponse.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
|
|
@ -29,6 +29,7 @@ import bisq.core.trade.messages.TradeMessage;
|
|||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
|
||||
|
@ -83,7 +84,10 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||
BuyerAsTakerCreatesDepositTxInputs.class,
|
||||
TakerSendInputsForDepositTxRequest.class)
|
||||
.withTimeout(30))
|
||||
.run(() -> processModel.setTempTradingPeerNodeAddress(trade.getTradingPeerNodeAddress()))
|
||||
.run(() -> {
|
||||
processModel.setTempTradingPeerNodeAddress(trade.getTradingPeerNodeAddress());
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
})
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
@ -116,6 +120,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||
BuyerProcessDelayedPayoutTxSignatureRequest.class,
|
||||
BuyerVerifiesPreparedDelayedPayoutTx.class,
|
||||
BuyerSignsDelayedPayoutTx.class,
|
||||
BuyerFinalizesDelayedPayoutTx.class,
|
||||
BuyerSendsDelayedPayoutTxSignatureResponse.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
|
|
@ -147,7 +147,10 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(event, errorMessage);
|
||||
})))
|
||||
.run(() -> trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED))
|
||||
.run(() -> {
|
||||
trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
})
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
|
|
@ -99,11 +99,13 @@ public class FluentProtocol {
|
|||
NodeAddress peer = condition.getPeer();
|
||||
if (peer != null) {
|
||||
tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer);
|
||||
tradeProtocol.processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
TradeMessage message = condition.getMessage();
|
||||
if (message != null) {
|
||||
tradeProtocol.processModel.setTradeMessage(message);
|
||||
tradeProtocol.processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
TradeTaskRunner taskRunner = setup.getTaskRunner(message, condition.getEvent());
|
||||
|
|
|
@ -294,6 +294,9 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||
|
||||
public void setPaymentStartedMessageState(MessageState paymentStartedMessageStateProperty) {
|
||||
this.paymentStartedMessageStateProperty.set(paymentStartedMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
void setDepositTxSentAckMessage(AckMessage ackMessage) {
|
||||
|
@ -305,6 +308,9 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||
|
||||
public void setDepositTxMessageState(MessageState messageState) {
|
||||
this.depositTxMessageStateProperty.set(messageState);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
void witnessDebugLog(Trade trade) {
|
||||
|
|
|
@ -35,6 +35,7 @@ import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
|
|||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
|
||||
|
@ -103,6 +104,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
|||
MakerRemovesOpenOffer.class,
|
||||
SellerAsMakerFinalizesDepositTx.class,
|
||||
SellerCreatesDelayedPayoutTx.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerSendDelayedPayoutTxSignatureRequest.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
|
|
@ -30,6 +30,7 @@ import bisq.core.trade.protocol.tasks.TradeTask;
|
|||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
|
||||
|
@ -98,6 +99,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
|||
TakerPublishFeeTx.class,
|
||||
SellerAsTakerSignsDepositTx.class,
|
||||
SellerCreatesDelayedPayoutTx.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerSendDelayedPayoutTxSignatureRequest.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
|
|
|
@ -33,7 +33,6 @@ import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
|
|||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
|
@ -77,7 +76,6 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(SellerProcessDelayedPayoutTxSignatureResponse.class,
|
||||
SellerSignsDelayedPayoutTx.class,
|
||||
SellerFinalizesDelayedPayoutTx.class,
|
||||
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishesDepositTx.class,
|
||||
|
@ -99,7 +97,12 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) {
|
||||
expect(phase(Trade.Phase.DEPOSIT_CONFIRMED)
|
||||
// We are more tolerant with expected phase and allow also DEPOSIT_PUBLISHED as it can be the case
|
||||
// that the wallet is still syncing and so the DEPOSIT_CONFIRMED state to yet triggered when we received
|
||||
// a mailbox message with CounterCurrencyTransferStartedMessage.
|
||||
// TODO A better fix would be to add a listener for the wallet sync state and process
|
||||
// the mailbox msg once wallet is ready and trade state set.
|
||||
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(message)
|
||||
.from(peer)
|
||||
.preCondition(trade.getPayoutTx() == null,
|
||||
|
@ -141,7 +144,10 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(event, errorMessage);
|
||||
})))
|
||||
.run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT))
|
||||
.run(() -> {
|
||||
trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
})
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ import bisq.network.p2p.DecryptedDirectMessageListener;
|
|||
import bisq.network.p2p.DecryptedMessageWithPubKey;
|
||||
import bisq.network.p2p.MailboxMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.SendMailboxMessageListener;
|
||||
import bisq.network.p2p.messaging.DecryptedMailboxListener;
|
||||
|
||||
|
@ -42,6 +41,8 @@ import bisq.common.taskrunner.Task;
|
|||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -77,10 +78,17 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
if (!trade.isWithdrawn()) {
|
||||
processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
||||
}
|
||||
processModel.getP2PService().addDecryptedMailboxListener(this);
|
||||
processModel.getP2PService().getMailboxItemsByUid().values()
|
||||
.stream().map(P2PService.MailboxItem::getDecryptedMessageWithPubKey)
|
||||
.forEach(this::handleDecryptedMessageWithPubKey);
|
||||
|
||||
// We delay a bit here as the trade gets updated from the wallet to update the trade
|
||||
// state (deposit confirmed) and that happens after our method is called.
|
||||
// TODO To fix that in a better way we would need to change the order of some routines
|
||||
// from the TradeManager, but as we are close to a release I dont want to risk a bigger
|
||||
// change and leave that for a later PR
|
||||
UserThread.runAfter(() -> {
|
||||
processModel.getP2PService().addDecryptedMailboxListener(this);
|
||||
processModel.getP2PService().getMailBoxMessages()
|
||||
.forEach(this::handleDecryptedMessageWithPubKey);
|
||||
}, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void onWithdrawCompleted() {
|
||||
|
@ -138,7 +146,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
// We only remove here if we have already completed the trade.
|
||||
// Otherwise removal is done after successfully applied the task runner.
|
||||
if (trade.isWithdrawn()) {
|
||||
processModel.getP2PService().removeEntryFromMailbox(decryptedMessageWithPubKey);
|
||||
processModel.getP2PService().removeMailboxMsg(decryptedMessageWithPubKey);
|
||||
log.info("Remove {} from the P2P network.", tradeMessage.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
@ -152,7 +160,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
onAckMessage((AckMessage) networkEnvelope, peer);
|
||||
}
|
||||
// In any case we remove the msg
|
||||
processModel.getP2PService().removeEntryFromMailbox(decryptedMessageWithPubKey);
|
||||
processModel.getP2PService().removeMailboxMsg(decryptedMessageWithPubKey);
|
||||
log.info("Remove {} from the P2P network.", networkEnvelope.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +173,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
PublicKey sigPubKey = processModel.getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
||||
// We reconstruct the DecryptedMessageWithPubKey from the message and the peers signature pubKey
|
||||
DecryptedMessageWithPubKey decryptedMessageWithPubKey = new DecryptedMessageWithPubKey(tradeMessage, sigPubKey);
|
||||
processModel.getP2PService().removeEntryFromMailbox(decryptedMessageWithPubKey);
|
||||
processModel.getP2PService().removeMailboxMsg(decryptedMessageWithPubKey);
|
||||
log.info("Remove {} from the P2P network.", tradeMessage.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
@ -299,6 +307,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
log.error("Timeout reached. TradeID={}, state={}, timeoutSec={}",
|
||||
trade.getId(), trade.stateProperty().get(), timeoutSec);
|
||||
trade.setErrorMessage("Timeout reached. Protocol did not complete in " + timeoutSec + " sec.");
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
cleanup();
|
||||
}, timeoutSec);
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@ public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
|
|||
Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
|
||||
WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet());
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -91,6 +91,7 @@ public abstract class SetupPayoutTxListener extends TradeTask {
|
|||
if (trade.getPayoutTx() == null) {
|
||||
Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash());
|
||||
trade.setPayoutTx(walletTx);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
BtcWalletService.printTx("payoutTx received from network", walletTx);
|
||||
setState();
|
||||
} else {
|
||||
|
|
|
@ -37,9 +37,18 @@ public abstract class TradeTask extends Task<Trade> {
|
|||
processModel = trade.getProcessModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void complete() {
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
trade.setErrorMessage(errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.failed();
|
||||
}
|
||||
|
||||
|
@ -47,6 +56,8 @@ public abstract class TradeTask extends Task<Trade> {
|
|||
protected void failed(String message) {
|
||||
appendToErrorMessage(message);
|
||||
trade.setErrorMessage(errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.failed();
|
||||
}
|
||||
|
||||
|
@ -55,6 +66,8 @@ public abstract class TradeTask extends Task<Trade> {
|
|||
t.printStackTrace();
|
||||
appendExceptionToErrorMessage(t);
|
||||
trade.setErrorMessage(errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.failed();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerFinalizesDelayedPayoutTx extends TradeTask {
|
||||
public BuyerFinalizesDelayedPayoutTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
BtcWalletService btcWalletService = processModel.getBtcWalletService();
|
||||
String id = processModel.getOffer().getId();
|
||||
Transaction preparedDepositTx = btcWalletService.getTxFromSerializedTx(processModel.getPreparedDepositTx());
|
||||
Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
|
||||
|
||||
byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
checkArgument(Arrays.equals(buyerMultiSigPubKey,
|
||||
btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
|
||||
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
|
||||
|
||||
byte[] buyerSignature = processModel.getDelayedPayoutTxSignature();
|
||||
byte[] sellerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature();
|
||||
|
||||
Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeUnconnectedDelayedPayoutTx(
|
||||
preparedDelayedPayoutTx,
|
||||
buyerMultiSigPubKey,
|
||||
sellerMultiSigPubKey,
|
||||
buyerSignature,
|
||||
sellerSignature,
|
||||
preparedDepositTx.getOutput(0).getValue());
|
||||
|
||||
trade.applyDelayedPayoutTxBytes(signedDelayedPayoutTx.bitcoinSerialize());
|
||||
log.info("DelayedPayoutTxBytes = {}", Utilities.bytesAsHexString(trade.getDelayedPayoutTxBytes()));
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask {
|
|||
byte[] delayedPayoutTxAsBytes = checkNotNull(request.getDelayedPayoutTx());
|
||||
Transaction preparedDelayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes);
|
||||
processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
|
||||
processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(request.getDelayedPayoutTxSellerSignature()));
|
||||
|
||||
// When we receive that message the taker has published the taker fee, so we apply it to the trade.
|
||||
// The takerFeeTx was sent in the first message. It should be part of DelayedPayoutTxSignatureRequest
|
||||
|
@ -54,6 +55,8 @@ public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask {
|
|||
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -26,12 +26,16 @@ import bisq.core.trade.protocol.tasks.TradeTask;
|
|||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
|
@ -59,9 +63,11 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
|
|||
|
||||
// To access tx confidence we need to add that tx into our wallet.
|
||||
byte[] delayedPayoutTxBytes = checkNotNull(message.getDelayedPayoutTx());
|
||||
checkArgument(Arrays.equals(delayedPayoutTxBytes, trade.getDelayedPayoutTxBytes()),
|
||||
"mismatch between delayedPayoutTx received from peer and our one." +
|
||||
"\n Expected: " + Utilities.bytesAsHexString(trade.getDelayedPayoutTxBytes()) +
|
||||
"\n Received: " + Utilities.bytesAsHexString(delayedPayoutTxBytes));
|
||||
trade.applyDelayedPayoutTxBytes(delayedPayoutTxBytes);
|
||||
BtcWalletService.printTx("delayedPayoutTx received from peer",
|
||||
checkNotNull(trade.getDelayedPayoutTx()));
|
||||
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
|
@ -73,6 +79,8 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
|
|||
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(),
|
||||
AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -73,6 +73,8 @@ public class BuyerProcessPayoutTxPublishedMessage extends TradeTask {
|
|||
processModel.getAccountAgeWitnessService().publishOwnSignedWitness(signedWitness);
|
||||
}
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -83,11 +83,15 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
cleanup();
|
||||
// Complete is called in base class
|
||||
}
|
||||
|
@ -104,6 +108,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
if (!trade.isPayoutPublished()) {
|
||||
tryToSendAgainLater();
|
||||
}
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
// We override the default behaviour for onFault and do not call appendToErrorMessage and failed
|
||||
|
@ -118,6 +123,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
if (!trade.isPayoutPublished()) {
|
||||
tryToSendAgainLater();
|
||||
}
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,6 +179,9 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
if (newValue == MessageState.ACKNOWLEDGED) {
|
||||
// We treat a ACK like BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
cleanup();
|
||||
complete();
|
||||
}
|
||||
|
|
|
@ -44,10 +44,15 @@ public class BuyerSendsDelayedPayoutTxSignatureResponse extends TradeTask {
|
|||
runInterceptHook();
|
||||
|
||||
byte[] delayedPayoutTxSignature = checkNotNull(processModel.getDelayedPayoutTxSignature());
|
||||
byte[] depositTxBytes = processModel.getDepositTx() != null
|
||||
? processModel.getDepositTx().bitcoinSerialize() // set in BuyerAsTakerSignsDepositTx task
|
||||
: processModel.getPreparedDepositTx(); // set in BuyerAsMakerCreatesAndSignsDepositTx task
|
||||
|
||||
DelayedPayoutTxSignatureResponse message = new DelayedPayoutTxSignatureResponse(UUID.randomUUID().toString(),
|
||||
processModel.getOfferId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
delayedPayoutTxSignature);
|
||||
delayedPayoutTxSignature,
|
||||
depositTxBytes);
|
||||
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
|
|
|
@ -100,6 +100,8 @@ public class BuyerSetupDepositTxListener extends TradeTask {
|
|||
// We don't want to trigger the tradeStateSubscription when setting the state, so we unsubscribe before
|
||||
unSubscribeAndRemoveListener();
|
||||
trade.setState(Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else {
|
||||
unSubscribeAndRemoveListener();
|
||||
}
|
||||
|
|
|
@ -45,5 +45,7 @@ public class BuyerSetupPayoutTxListener extends SetupPayoutTxListener {
|
|||
@Override
|
||||
protected void setState() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_PAYOUT_TX_IN_NETWORK);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,8 @@ public class BuyerSignPayoutTx extends TradeTask {
|
|||
sellerMultiSigPubKey);
|
||||
processModel.setPayoutTxSignature(payoutTxSignature);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -24,6 +24,7 @@ import bisq.core.trade.protocol.tasks.TradeTask;
|
|||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
|
||||
|
@ -46,7 +47,11 @@ public class BuyerSignsDelayedPayoutTx extends TradeTask {
|
|||
runInterceptHook();
|
||||
|
||||
Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
|
||||
|
||||
BtcWalletService btcWalletService = processModel.getBtcWalletService();
|
||||
NetworkParameters params = btcWalletService.getParams();
|
||||
Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx());
|
||||
|
||||
String id = processModel.getOffer().getId();
|
||||
|
||||
byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
|
@ -58,11 +63,14 @@ public class BuyerSignsDelayedPayoutTx extends TradeTask {
|
|||
byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
|
||||
byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(
|
||||
preparedDelayedPayoutTx,
|
||||
preparedDepositTx,
|
||||
myMultiSigKeyPair,
|
||||
buyerMultiSigPubKey,
|
||||
sellerMultiSigPubKey);
|
||||
processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -25,6 +25,8 @@ import bisq.common.taskrunner.TaskRunner;
|
|||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask {
|
||||
public BuyerVerifiesPreparedDelayedPayoutTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
|
@ -36,11 +38,22 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
var preparedDelayedPayoutTx = processModel.getPreparedDelayedPayoutTx();
|
||||
TradeDataValidation.validateDelayedPayoutTx(trade,
|
||||
processModel.getPreparedDelayedPayoutTx(),
|
||||
preparedDelayedPayoutTx,
|
||||
processModel.getDaoFacade(),
|
||||
processModel.getBtcWalletService());
|
||||
|
||||
// If the deposit tx is non-malleable, we already know its final ID, so should check that now
|
||||
// before sending any further data to the seller, to provide extra protection for the buyer.
|
||||
if (isDepositTxNonMalleable()) {
|
||||
var preparedDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(
|
||||
processModel.getPreparedDepositTx());
|
||||
TradeDataValidation.validatePayoutTxInput(preparedDepositTx, checkNotNull(preparedDelayedPayoutTx));
|
||||
} else {
|
||||
log.info("Deposit tx is malleable, so we skip preparedDelayedPayoutTx input validation.");
|
||||
}
|
||||
|
||||
complete();
|
||||
} catch (TradeDataValidation.ValidationException e) {
|
||||
failed(e.getMessage());
|
||||
|
@ -48,4 +61,12 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask {
|
|||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDepositTxNonMalleable() {
|
||||
var buyerInputs = checkNotNull(processModel.getRawTransactionInputs());
|
||||
var sellerInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs());
|
||||
|
||||
return buyerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH) &&
|
||||
sellerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,8 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask {
|
|||
.add(tradeAmount);
|
||||
|
||||
List<RawTransactionInput> takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
|
||||
checkArgument(takerRawTransactionInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH),
|
||||
"all takerRawTransactionInputs must be P2WH");
|
||||
long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
|
||||
@Nullable String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
|
||||
Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
|
@ -97,6 +99,8 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask {
|
|||
processModel.setPreparedDepositTx(result.depositTransaction);
|
||||
processModel.setRawTransactionInputs(result.rawMakerInputs);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -22,6 +22,8 @@ import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse
|
|||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
|
@ -32,6 +34,9 @@ public class BuyerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInput
|
|||
|
||||
@Override
|
||||
protected byte[] getPreparedDepositTx() {
|
||||
return processModel.getPreparedDepositTx();
|
||||
Transaction preparedDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(processModel.getPreparedDepositTx());
|
||||
// Remove witnesses from preparedDepositTx, so that the seller can still compute the final
|
||||
// tx id, but cannot publish it before providing the buyer with a signed delayed payout tx.
|
||||
return preparedDepositTx.bitcoinSerialize(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,8 @@ public class BuyerAsTakerCreatesDepositTxInputs extends TradeTask {
|
|||
processModel.setChangeOutputValue(result.changeOutputValue);
|
||||
processModel.setChangeOutputAddress(result.changeOutputAddress);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -41,10 +41,12 @@ public class BuyerAsTakerSendsDepositTxMessage extends TradeTask {
|
|||
try {
|
||||
runInterceptHook();
|
||||
if (processModel.getDepositTx() != null) {
|
||||
// Remove witnesses from the sent depositTx, so that the seller can still compute the final
|
||||
// tx id, but cannot publish it before providing the buyer with a signed delayed payout tx.
|
||||
DepositTxMessage message = new DepositTxMessage(UUID.randomUUID().toString(),
|
||||
processModel.getOfferId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getDepositTx().bitcoinSerialize());
|
||||
processModel.getDepositTx().bitcoinSerialize(false));
|
||||
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
|
@ -72,7 +74,7 @@ public class BuyerAsTakerSendsDepositTxMessage extends TradeTask {
|
|||
}
|
||||
);
|
||||
} else {
|
||||
log.error("processModel.getDepositTx() = " + processModel.getDepositTx());
|
||||
log.error("processModel.getDepositTx() = {}", processModel.getDepositTx());
|
||||
failed("DepositTx is null");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
|
|
|
@ -88,6 +88,8 @@ public class BuyerAsTakerSignsDepositTx extends TradeTask {
|
|||
sellerMultiSigPubKey);
|
||||
processModel.setDepositTx(depositTx);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -95,6 +95,8 @@ public class MakerCreateAndSignContract extends TradeTask {
|
|||
|
||||
processModel.setMyMultiSigPubKey(makerMultiSigPubKey);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -89,7 +89,7 @@ public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask {
|
|||
trade.getLockTime());
|
||||
|
||||
trade.setState(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
@ -103,6 +103,7 @@ public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask {
|
|||
log.info("{} arrived at peer {}. tradeId={}, uid={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
trade.setState(Trade.State.MAKER_SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
|
||||
|
@ -112,6 +113,7 @@ public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask {
|
|||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
|
||||
trade.setState(Trade.State.MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST);
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
failed(errorMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ public class MakerSetsLockTime extends TradeTask {
|
|||
log.info("lockTime={}, delay={}", lockTime, delay);
|
||||
trade.setLockTime(lockTime);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -45,5 +45,6 @@ public class BroadcastMediatedPayoutTx extends BroadcastPayoutTx {
|
|||
@Override
|
||||
protected void setState() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,8 @@ public class FinalizeMediatedPayoutTx extends TradeTask {
|
|||
|
||||
trade.setPayoutTx(transaction);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
walletService.swapTradeEntryToAvailableEntry(tradeId, AddressEntry.Context.MULTI_SIG);
|
||||
|
||||
complete();
|
||||
|
|
|
@ -51,6 +51,8 @@ public class ProcessMediatedPayoutSignatureMessage extends TradeTask {
|
|||
|
||||
trade.setMediationResultState(MediationResultState.RECEIVED_SIG_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -74,6 +74,9 @@ public class ProcessMediatedPayoutTxPublishedMessage extends TradeTask {
|
|||
} else {
|
||||
log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
|
||||
}
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -60,6 +60,7 @@ public class SendMediatedPayoutSignatureMessage extends TradeTask {
|
|||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_SENT);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
p2PService.sendEncryptedMailboxMessage(peersNodeAddress,
|
||||
peersPubKeyRing,
|
||||
message,
|
||||
|
@ -70,6 +71,7 @@ public class SendMediatedPayoutSignatureMessage extends TradeTask {
|
|||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
|
||||
|
@ -79,6 +81,7 @@ public class SendMediatedPayoutSignatureMessage extends TradeTask {
|
|||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
|
||||
|
@ -88,6 +91,7 @@ public class SendMediatedPayoutSignatureMessage extends TradeTask {
|
|||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_SEND_FAILED);
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
failed(errorMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,21 +54,25 @@ public class SendMediatedPayoutTxPublishedMessage extends SendMailboxMessageTask
|
|||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_SENT);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_SEND_FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -49,5 +49,6 @@ public class SetupMediatedPayoutTxListener extends SetupPayoutTxListener {
|
|||
if (trade.getPayoutTx() != null) {
|
||||
processModel.getTradeManager().closeDisputedTrade(trade.getId(), Trade.DisputeState.MEDIATION_CLOSED);
|
||||
}
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,8 @@ public class SignMediatedPayoutTx extends TradeTask {
|
|||
sellerMultiSigPubKey);
|
||||
processModel.setMediatedPayoutTxSignature(mediatedPayoutTxSignature);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -44,5 +44,6 @@ public class SellerBroadcastPayoutTx extends BroadcastPayoutTx {
|
|||
@Override
|
||||
protected void setState() {
|
||||
trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package bisq.core.trade.protocol.tasks.seller;
|
|||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.dao.governance.param.Param;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeDataValidation;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
@ -53,9 +54,15 @@ public class SellerCreatesDelayedPayoutTx extends TradeTask {
|
|||
donationAddressString,
|
||||
minerFee,
|
||||
lockTime);
|
||||
TradeDataValidation.validateDelayedPayoutTx(trade,
|
||||
preparedDelayedPayoutTx,
|
||||
processModel.getDaoFacade(),
|
||||
processModel.getBtcWalletService());
|
||||
|
||||
processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -68,6 +68,8 @@ public class SellerFinalizesDelayedPayoutTx extends TradeTask {
|
|||
trade.applyDelayedPayoutTx(signedDelayedPayoutTx);
|
||||
log.info("DelayedPayoutTxBytes = {}", Utilities.bytesAsHexString(trade.getDelayedPayoutTxBytes()));
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -61,6 +61,8 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas
|
|||
|
||||
trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -42,11 +42,18 @@ public class SellerProcessDelayedPayoutTxSignatureResponse extends TradeTask {
|
|||
checkNotNull(response);
|
||||
checkTradeId(processModel.getOfferId(), response);
|
||||
|
||||
processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxSignature()));
|
||||
processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxBuyerSignature()));
|
||||
|
||||
processModel.getTradeWalletService().sellerAddsBuyerWitnessesToDepositTx(
|
||||
processModel.getDepositTx(),
|
||||
processModel.getBtcWalletService().getTxFromSerializedTx(response.getDepositTx())
|
||||
);
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -54,6 +54,8 @@ public class SellerPublishesDepositTx extends TradeTask {
|
|||
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(processModel.getOffer().getId(),
|
||||
AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} else {
|
||||
log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
|
||||
|
|
|
@ -47,10 +47,13 @@ public class SellerSendDelayedPayoutTxSignatureRequest extends TradeTask {
|
|||
|
||||
Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx(),
|
||||
"processModel.getPreparedDelayedPayoutTx() must not be null");
|
||||
byte[] delayedPayoutTxSignature = checkNotNull(processModel.getDelayedPayoutTxSignature(),
|
||||
"processModel.getDelayedPayoutTxSignature() must not be null");
|
||||
DelayedPayoutTxSignatureRequest message = new DelayedPayoutTxSignatureRequest(UUID.randomUUID().toString(),
|
||||
processModel.getOfferId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
preparedDelayedPayoutTx.bitcoinSerialize());
|
||||
preparedDelayedPayoutTx.bitcoinSerialize(),
|
||||
delayedPayoutTxSignature);
|
||||
|
||||
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
|
|
|
@ -65,6 +65,7 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
|||
trade.setState(Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("Sent PayoutTxPublishedMessage: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,6 +73,7 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
|||
trade.setState(Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("PayoutTxPublishedMessage arrived: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,6 +81,7 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
|||
trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("PayoutTxPublishedMessage storedInMailbox: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -86,6 +89,7 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
|||
trade.setState(Trade.State.SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.error("PayoutTxPublishedMessage failed: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -76,11 +76,15 @@ public class SellerSendsDepositTxAndDelayedPayoutTxMessage extends SendMailboxMe
|
|||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
cleanup();
|
||||
// Complete is called in base class
|
||||
}
|
||||
|
@ -94,6 +98,8 @@ public class SellerSendsDepositTxAndDelayedPayoutTxMessage extends SendMailboxMe
|
|||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
// The DepositTxAndDelayedPayoutTxMessage is a mailbox message as earlier we use only the deposit tx which can
|
||||
// be also received from the network once published.
|
||||
// Now we send the delayed payout tx as well and with that this message is mandatory for continuing the protocol.
|
||||
|
@ -119,6 +125,8 @@ public class SellerSendsDepositTxAndDelayedPayoutTxMessage extends SendMailboxMe
|
|||
if (!trade.isDepositConfirmed()) {
|
||||
tryToSendAgainLater();
|
||||
}
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,6 +181,8 @@ public class SellerSendsDepositTxAndDelayedPayoutTxMessage extends SendMailboxMe
|
|||
if (newValue == MessageState.ACKNOWLEDGED) {
|
||||
// We treat a ACK like SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
cleanup();
|
||||
complete();
|
||||
}
|
||||
|
|
|
@ -100,6 +100,8 @@ public class SellerSignAndFinalizePayoutTx extends TradeTask {
|
|||
|
||||
trade.setPayoutTx(transaction);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.MULTI_SIG);
|
||||
|
||||
complete();
|
||||
|
|
|
@ -24,6 +24,7 @@ import bisq.core.trade.protocol.tasks.TradeTask;
|
|||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
|
||||
|
@ -47,6 +48,9 @@ public class SellerSignsDelayedPayoutTx extends TradeTask {
|
|||
|
||||
Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
|
||||
BtcWalletService btcWalletService = processModel.getBtcWalletService();
|
||||
NetworkParameters params = btcWalletService.getParams();
|
||||
Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx());
|
||||
|
||||
String id = processModel.getOffer().getId();
|
||||
|
||||
byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
|
@ -59,12 +63,15 @@ public class SellerSignsDelayedPayoutTx extends TradeTask {
|
|||
|
||||
byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(
|
||||
preparedDelayedPayoutTx,
|
||||
preparedDepositTx,
|
||||
myMultiSigKeyPair,
|
||||
buyerMultiSigPubKey,
|
||||
sellerMultiSigPubKey);
|
||||
|
||||
processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -78,6 +78,8 @@ public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask {
|
|||
.add(offer.getBuyerSecurityDeposit());
|
||||
|
||||
List<RawTransactionInput> takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
|
||||
checkArgument(takerRawTransactionInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH),
|
||||
"all takerRawTransactionInputs must be P2WH");
|
||||
long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
|
||||
String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
|
||||
Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
|
@ -103,6 +105,8 @@ public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask {
|
|||
processModel.setPreparedDepositTx(result.depositTransaction);
|
||||
processModel.setRawTransactionInputs(result.rawMakerInputs);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -48,6 +48,8 @@ public class SellerAsMakerFinalizesDepositTx extends TradeTask {
|
|||
|
||||
processModel.setDepositTx(myDepositTx);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -43,7 +43,7 @@ public class SellerAsMakerProcessDepositTxMessage extends TradeTask {
|
|||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkNotNull(message);
|
||||
|
||||
processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTx()));
|
||||
processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses()));
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
// When we receive that message the taker has published the taker fee, so we apply it to the trade.
|
||||
|
@ -51,6 +51,8 @@ public class SellerAsMakerProcessDepositTxMessage extends TradeTask {
|
|||
// but that cannot be changed due backward compatibility issues. It is a left over from the old trade protocol.
|
||||
trade.setTakerFeeTxId(processModel.getTakeOfferFeeTxId());
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -41,6 +41,10 @@ public class SellerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInpu
|
|||
// before we have received his signature for the delayed payout tx.
|
||||
input.setScriptSig(new Script(new byte[]{}));
|
||||
});
|
||||
return preparedDepositTx.bitcoinSerialize();
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// Make sure witnesses are removed as well before sending, to cover the segwit case.
|
||||
return preparedDepositTx.bitcoinSerialize(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ public class SellerAsTakerCreatesDepositTxInputs extends TradeTask {
|
|||
processModel.setChangeOutputValue(result.changeOutputValue);
|
||||
processModel.setChangeOutputAddress(result.changeOutputAddress);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue