diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000000..c6858a9c084
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,33 @@
+### Description
+
+
+
+#### Version
+
+
+
+### Steps to reproduce
+
+
+
+### Expected behaviour
+
+
+
+### Actual behaviour
+
+
+
+### Screenshots
+
+
+
+#### Device or machine
+
+
+
+
+
+#### Additional info
+
+
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 6e6eec11483..79ee123c2b2 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,6 +1,5 @@
-
\ No newline at end of file
diff --git a/assets/src/main/java/bisq/asset/coins/Animecoin.java b/assets/src/main/java/bisq/asset/coins/Animecoin.java
new file mode 100644
index 00000000000..7ddbe6db364
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/Animecoin.java
@@ -0,0 +1,36 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.Base58BitcoinAddressValidator;
+import bisq.asset.Coin;
+import bisq.asset.NetworkParametersAdapter;
+
+public class Animecoin extends Coin {
+ public Animecoin() {
+ super("Animecoin", "ANI", new Base58BitcoinAddressValidator(new AnimecoinMainNetParams()));
+ }
+
+ public static class AnimecoinMainNetParams extends NetworkParametersAdapter {
+ public AnimecoinMainNetParams() {
+ this.addressHeader = 23;
+ this.p2shHeader = 9;
+ this.acceptableAddressCodes = new int[]{this.addressHeader, this.p2shHeader};
+ }
+ }
+}
diff --git a/assets/src/main/java/bisq/asset/coins/CTSCoin.java b/assets/src/main/java/bisq/asset/coins/CTSCoin.java
new file mode 100644
index 00000000000..7db46edc262
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/CTSCoin.java
@@ -0,0 +1,36 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.Base58BitcoinAddressValidator;
+import bisq.asset.Coin;
+import bisq.asset.NetworkParametersAdapter;
+
+public class CTSCoin extends Coin {
+ public CTSCoin() {
+ super("CTSCoin", "CTSC", new Base58BitcoinAddressValidator(new CtscMainNetParams()));
+ }
+
+ public static class CtscMainNetParams extends NetworkParametersAdapter {
+ public CtscMainNetParams() {
+ this.addressHeader = 66;
+ this.p2shHeader = 16;
+ this.acceptableAddressCodes = new int[]{this.addressHeader, this.p2shHeader};
+ }
+ }
+}
diff --git a/assets/src/main/java/bisq/asset/coins/Ergo.java b/assets/src/main/java/bisq/asset/coins/Ergo.java
new file mode 100644
index 00000000000..fd9ae801cda
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/Ergo.java
@@ -0,0 +1,53 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AddressValidationResult;
+import bisq.asset.AddressValidator;
+import bisq.asset.Coin;
+
+import java.util.Arrays;
+
+import org.bitcoinj.core.Base58;
+import org.bitcoinj.core.AddressFormatException;
+
+public class Ergo extends Coin {
+
+ public Ergo() {
+ super("Ergo", "ERG", new ErgoAddressValidator());
+ }
+
+ public static class ErgoAddressValidator implements AddressValidator {
+
+ @Override
+ public AddressValidationResult validate(String address) {
+ try {
+ byte[] decoded = Base58.decode(address);
+ if (decoded.length < 4) {
+ return AddressValidationResult.invalidAddress("Input too short: " + decoded.length);
+ }
+ if (decoded[0] != 1 && decoded[0] != 2 && decoded[0] != 3) {
+ return AddressValidationResult.invalidAddress("Invalid prefix");
+ }
+ } catch (AddressFormatException e) {
+ return AddressValidationResult.invalidAddress(e);
+ }
+ return AddressValidationResult.validAddress();
+ }
+ }
+}
diff --git a/assets/src/main/java/bisq/asset/coins/Faircoin.java b/assets/src/main/java/bisq/asset/coins/Faircoin.java
new file mode 100644
index 00000000000..d94efa3a9a4
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/Faircoin.java
@@ -0,0 +1,39 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.Base58BitcoinAddressValidator;
+import bisq.asset.Coin;
+import bisq.asset.NetworkParametersAdapter;
+
+public class Faircoin extends Coin {
+
+ public Faircoin() {
+ super("Faircoin", "FAIR", new Base58BitcoinAddressValidator(new Faircoin.FaircoinParams()));
+ }
+
+ public static class FaircoinParams extends NetworkParametersAdapter {
+
+ public FaircoinParams() {
+ addressHeader = 95;
+ p2shHeader = 36;
+ acceptableAddressCodes = new int[]{addressHeader, p2shHeader};
+ }
+ }
+
+}
diff --git a/assets/src/main/java/bisq/asset/coins/Krypton.java b/assets/src/main/java/bisq/asset/coins/Krypton.java
new file mode 100644
index 00000000000..1e8e0770379
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/Krypton.java
@@ -0,0 +1,28 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.Coin;
+import bisq.asset.RegexAddressValidator;
+
+public class Krypton extends Coin {
+
+ public Krypton() {
+ super("Krypton", "ZOD", new RegexAddressValidator("^QQQ[1-9A-Za-z^OIl]{95}"));
+ }
+}
diff --git a/assets/src/main/java/bisq/asset/coins/Ndau.java b/assets/src/main/java/bisq/asset/coins/Ndau.java
new file mode 100644
index 00000000000..2cb6e8ac302
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/Ndau.java
@@ -0,0 +1,37 @@
+/*
+ * 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 .
+ */
+
+/*
+ * Copyright © 2019 Oneiro NA, Inc.
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.Coin;
+import bisq.asset.RegexAddressValidator;
+
+
+public class Ndau extends Coin {
+
+ public Ndau() {
+ // note: ndau addresses contain an internal checksum which was deemed too complicated to include here.
+ // this regex performs superficial validation, but there is a large space of addresses marked valid
+ // by this regex which are not in fact valid ndau addresses. For actual ndau address validation,
+ // use the Address class in github.com/oneiro-ndev/ndauj (java) or github.com/oneiro-ndev/ndaumath/pkg/address (go).
+ super("ndau", "XND", new RegexAddressValidator("nd[anexbm][abcdefghijkmnpqrstuvwxyz23456789]{45}"));
+ }
+}
diff --git a/assets/src/main/java/bisq/asset/coins/uPlexa.java b/assets/src/main/java/bisq/asset/coins/uPlexa.java
new file mode 100644
index 00000000000..fbaaf7d5344
--- /dev/null
+++ b/assets/src/main/java/bisq/asset/coins/uPlexa.java
@@ -0,0 +1,30 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AltCoinAccountDisclaimer;
+import bisq.asset.Coin;
+import bisq.asset.RegexAddressValidator;
+
+@AltCoinAccountDisclaimer("account.altcoin.popup.upx.msg")
+public class uPlexa extends Coin {
+
+ public uPlexa() {
+ super("uPlexa", "UPX", new RegexAddressValidator("^((UPX)[1-9A-Za-z^OIl]{95}|(UPi)[1-9A-Za-z^OIl]{106}|(UmV|UmW)[1-9A-Za-z^OIl]{94})$"));
+ }
+}
diff --git a/assets/src/main/resources/META-INF/services/bisq.asset.Asset b/assets/src/main/resources/META-INF/services/bisq.asset.Asset
index ed22d1cac15..2128b5b1fa0 100644
--- a/assets/src/main/resources/META-INF/services/bisq.asset.Asset
+++ b/assets/src/main/resources/META-INF/services/bisq.asset.Asset
@@ -6,6 +6,7 @@ bisq.asset.coins.Actinium
bisq.asset.coins.Adeptio
bisq.asset.coins.Aeon
bisq.asset.coins.Amitycoin
+bisq.asset.coins.Animecoin
bisq.asset.coins.Arqma
bisq.asset.coins.Askcoin
bisq.asset.coins.Australiacash
@@ -29,6 +30,7 @@ bisq.asset.coins.Counterparty
bisq.asset.coins.Credits
bisq.asset.coins.Croat
bisq.asset.coins.CRowdCLassic
+bisq.asset.coins.CTSCoin
bisq.asset.coins.DarkPay
bisq.asset.coins.Dash
bisq.asset.coins.Decred
@@ -40,8 +42,10 @@ bisq.asset.coins.Donu
bisq.asset.coins.Dragonglass
bisq.asset.coins.DSTRA
bisq.asset.coins.Emercoin
+bisq.asset.coins.Ergo
bisq.asset.coins.Ether
bisq.asset.coins.EtherClassic
+bisq.asset.coins.Faircoin
bisq.asset.coins.FourtyTwo
bisq.asset.coins.Fujicoin
bisq.asset.coins.Galilel
@@ -56,6 +60,7 @@ bisq.asset.coins.Iridium
bisq.asset.coins.Kekcoin
bisq.asset.coins.KnowYourDeveloper
bisq.asset.coins.Kore
+bisq.asset.coins.Krypton
bisq.asset.coins.Litecoin
bisq.asset.coins.LitecoinPlus
bisq.asset.coins.LitecoinZ
@@ -71,6 +76,7 @@ bisq.asset.coins.MoX
bisq.asset.coins.Myce
bisq.asset.coins.Namecoin
bisq.asset.coins.Navcoin
+bisq.asset.coins.Ndau
bisq.asset.coins.Noir
bisq.asset.coins.NoteBlockchain
bisq.asset.coins.ParsiCoin
@@ -99,6 +105,7 @@ bisq.asset.coins.TEO
bisq.asset.coins.TurtleCoin
bisq.asset.coins.UnitedCommunityCoin
bisq.asset.coins.Unobtanium
+bisq.asset.coins.uPlexa
bisq.asset.coins.VARIUS
bisq.asset.coins.Veil
bisq.asset.coins.Vertcoin
diff --git a/assets/src/test/java/bisq/asset/coins/AnimecoinTest.java b/assets/src/test/java/bisq/asset/coins/AnimecoinTest.java
new file mode 100644
index 00000000000..7538294578c
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/AnimecoinTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+import org.junit.Test;
+
+public class AnimecoinTest extends AbstractAssetTest {
+
+ public AnimecoinTest() {
+ super(new Animecoin());
+ }
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("Aa6TDuudiNh7DRzs11wEzZWiw9QBZY3Qw1");
+ assertValidAddress("AdsdUhnPsJwg5NvAuyxs4EsaE2GoSHohoq");
+ assertValidAddress("4s2peLxJJ2atz1tnAKpFshnVPKTmR312fr");
+
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("aa6TDuudiNh7DRzs11wEzZWiw9QBZY3Qw1");
+ assertInvalidAddress("3s2peLxJJ2atz1tnAKpFshnVPKTmR312fr");
+ assertInvalidAddress("ANNPzjj2ZYEhpyJ6p6sWeH1JXbkCSmNSd#");
+ }
+}
diff --git a/assets/src/test/java/bisq/asset/coins/CTSCoinTest.java b/assets/src/test/java/bisq/asset/coins/CTSCoinTest.java
new file mode 100644
index 00000000000..adbe76f7da7
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/CTSCoinTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+import org.junit.Test;
+
+public class CTSCoinTest extends AbstractAssetTest {
+
+ public CTSCoinTest() {
+ super(new CTSCoin());
+ }
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("Ti6S7JhtxKjSytZDmyMV4pVNVAPeiVsnpT");
+ assertValidAddress("TwzRDeNSPcJvquuGu7WxxH3RhXBR1VPYHZ");
+ assertValidAddress("TgYGQJd5TEzDRkyXt1tCvUnrbWBu38C8YK");
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("ti6S7JhtxKjSytZDmyMV4pVNVAPeiVsnpT");
+ assertInvalidAddress("2i6S7JhtxKjSytZDmyMV4pVNVAPeiVsnpT");
+ assertInvalidAddress("Ti6S7JhtxKjSytZDmyMV4pVNVAPeiVsnp#");
+ }
+}
diff --git a/assets/src/test/java/bisq/asset/coins/ErgoTest.java b/assets/src/test/java/bisq/asset/coins/ErgoTest.java
new file mode 100644
index 00000000000..213769cd81a
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/ErgoTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+import org.junit.Test;
+
+public class ErgoTest extends AbstractAssetTest {
+
+ public ErgoTest() {
+ super(new Ergo());
+ }
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("9fRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vA");
+ assertValidAddress("25qGdVWg2yyYho8uC1pLtc7KxFn4nEEAwD");
+ assertValidAddress("23NL9a8ngN28ovtLiKLgHexcdTKBbUMLhH");
+ assertValidAddress("7bwdkU5V8");
+ assertValidAddress("BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ");
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("9fRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vAaa");
+ assertInvalidAddress("25qGdVWg2yyYho8uC1pLtc7KxFn4nEEAwDaa");
+ assertInvalidAddress("23NL9a8ngN28ovtLiKLgHexcdTKBbUMLhHaa");
+ assertInvalidAddress("7bwdkU5V8aa");
+ assertInvalidAddress("BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ#");
+ }
+}
diff --git a/assets/src/test/java/bisq/asset/coins/FaircoinTest.java b/assets/src/test/java/bisq/asset/coins/FaircoinTest.java
new file mode 100644
index 00000000000..4f4b85186f8
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/FaircoinTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+public class FaircoinTest extends AbstractAssetTest {
+
+ public FaircoinTest() {
+ super(new Faircoin());
+ }
+
+ @Override
+ public void testValidAddresses() {
+ assertValidAddress("fLsJC1Njap5NxSArYr5wCJbKBbTQfWikY6");
+ assertValidAddress("FZHzHraqjty2Co7TinwcsBtPKoz5ANvgRd");
+ assertValidAddress("fHbXBBBjU1xxEVmWEtAEwXnoBDxxsxfvxg");
+ }
+
+ @Override
+ public void testInvalidAddresses() {
+ assertInvalidAddress("FLsJC1Njap5NxSArYr5wCJbKBbTQfWikY6");
+ assertInvalidAddress("fZHzHraqjty2Co7TinwcsBtPKoz5ANvgRd");
+ assertInvalidAddress("1HbXBBBjU1xxEVmWEtAEwXnoBDxxsxfvxg");
+ }
+}
diff --git a/assets/src/test/java/bisq/asset/coins/KryptonTest.java b/assets/src/test/java/bisq/asset/coins/KryptonTest.java
new file mode 100644
index 00000000000..1ac55127a79
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/KryptonTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+import org.junit.Test;
+
+public class KryptonTest extends AbstractAssetTest {
+
+ public KryptonTest() {
+ super(new Krypton());
+ }
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("QQQ1LgQ1m8vX5tGrBZ2miS7A54Fmj5Qbij4UXT8nD4aqF75b1cpAauxVkjYaefcztV62UrDT1K9WHDeQWu4vpVXU2wezpshvex");
+ assertValidAddress("QQQ1G56SKneSK1833tKjLH7E4ZgFwnqhqUb1HMHgYbnhaST56mukM1296jiYjTyTdMWnvH5FpWNAJWaQqwyPJHUR8qXRKBJy9o");
+ assertValidAddress("QQQ1Bg61uUZhsNaTmUSZNcFgX2bk9wnAoYg9DSYZidDMJt7wVyccvMy8J7zRBoV5iT1pbraFUDWPQWWdXGPPws2P2ZGe8UzsaJ");
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("QQQ1Bg61uUZhsNaTmUSZNcFgX2bk9wnAoYg9DSYZidDMJt7wVyccvMy8J7zRBoV5iT1pbraFUDWPQWWdXGPPws2P2ZGe8");
+ assertInvalidAddress("11QQQ1Bg61uUZhsNaTmUSZNcFgX2bk9wnAoYg9DSYZidDMJt7wVyccvMy8J7zRBoV5iT1pbraFUDWPQWWdXGPPws2P2ZGe8UzsaJ");
+ assertInvalidAddress("");
+ assertInvalidAddress("#RoUKWRwpsx1F");
+ assertInvalidAddress("YQQ1G56SKneSK1833tKjLH7E4ZgFwnqhqUb1HMHgYbnhaST56mukM1296jiYjTyTdMWnvH5FpWNAJWaQqwyPJHUR8qXRKBJy9o");
+ assertInvalidAddress("3jyRo3rcp9fjdfjdSGpx");
+ assertInvalidAddress("QQQ1G56SKneSK1833tKjLH7E4ZgFwnqhqUb1HMHgYbnhaST56mukM1296jiYjTyTdMWnvH5FpWNAJWaQqwyPJHUR8qXRKBJy9#");
+ assertInvalidAddress("ZOD1Bg61uUZhsNaTmUSZNcFgX2bk9wnAoYg9DSYZidDMJt7wVyccvMy8J7zRBoV5iT1pbraFUDWPQWWdXGPPws2P2ZGe8UzsaJ");
+ }
+}
\ No newline at end of file
diff --git a/assets/src/test/java/bisq/asset/coins/NdauTest.java b/assets/src/test/java/bisq/asset/coins/NdauTest.java
new file mode 100644
index 00000000000..5efa400319e
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/NdauTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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 .
+ */
+
+/*
+ * Copyright © 2019 Oneiro NA, Inc.
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+import org.junit.Test;
+
+public class NdauTest extends AbstractAssetTest {
+ public NdauTest() {super(new Ndau());}
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("ndaaacj4gbv5xgwikt6adcujqyvd37ksadj4mg9v3jqtbe9f");
+ assertValidAddress("ndnbeju3vmcxf9n96rb652eaeri79anqz47budnw8vwv3nyv");
+ assertValidAddress("ndeatpdkx5stu28n3v6pie96bma5k8pzbvbdpu8dchyn46nw");
+ assertValidAddress("ndxix97gyubjrkqbu4a5m3kpxyz4qhap3c3ui7359pzskwv4");
+ assertValidAddress("ndbjhkkcvj88beqcamr439z6d6icm5mjwth5r7vrgfbnxktr");
+ assertValidAddress("ndmpdkab97bi4ea73scjh6xpt8njjjhha4rarpr2zzzrv88u");
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("ndaaacj4gbv5xgwikt6adcujqyvd37ksadj4mg9v3jqtbe9");
+ assertInvalidAddress("ndnbeju3vmcxf9n96rb652eaeri79anqz47budnw8vwv3nyvw");
+ assertInvalidAddress("ndpatpdkx5stu28n3v6pie96bma5k8pzbvbdpu8dchyn46nw");
+ assertInvalidAddress("ndx1x97gyubjrkqbu4a5m3kpxyz4qhap3c3ui7359pzskwv4");
+ assertInvalidAddress("ndbjhklcvj88beqcamr439z6d6icm5mjwth5r7vrgfbnxktr");
+ assertInvalidAddress("ndmpdkab97bi4ea73scjh6xpt8njjjhhaArarpr2zzzrv88u");
+ }
+}
diff --git a/assets/src/test/java/bisq/asset/coins/uPlexaTest.java b/assets/src/test/java/bisq/asset/coins/uPlexaTest.java
new file mode 100644
index 00000000000..a4950f7ff3c
--- /dev/null
+++ b/assets/src/test/java/bisq/asset/coins/uPlexaTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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 .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+import org.junit.Test;
+
+public class uPlexaTest extends AbstractAssetTest {
+
+ public uPlexaTest() {
+ super(new uPlexa());
+ }
+
+ @Test
+ public void testValidAddresses() {
+ assertValidAddress("UPX1dz81hmfWc7AUhn16JATXJJgZeQZ4zLKA4tnHJHcdS5zoSaKQUoaGqDUQnTXecPL4mjJF1vkwRF3EEq5UJdSw8A84sXDjFP");
+ assertValidAddress("UPi1S1uqRRNSgC26PjasZP8FwTBRwnAEmBnx5mAYsbGqRvsU46aficYEA3FAT621EuPeChyKQumS7j6jpF74zW9tLJMve8kUJLP5zUgR5ts8W");
+ assertValidAddress("UmV7QTQs5Q47wMPggtuQSMTvuqNie1MRmbD4AG1xJXykZmxBG4P18p4CHqkV5sKDRXauXWbs76835PZoemQmPGJC1Dv2zdF43");
+ assertValidAddress("UmWh1MthnAiRP4GuN3DEQxPt6kgeAZfJLUuX1krtufAj2XvUJxDYnuYTAQzEp25V2W8BAJQkfXj8yFNUqQphxddN35nRLnZeE");
+ }
+
+ @Test
+ public void testInvalidAddresses() {
+ assertInvalidAddress("");
+ assertInvalidAddress("UPXLsinT9duNEtHGqAUicJKD2cmGiB9gB6sqHqWvV6suB4TtPSR8ynyh2vVVvNyDE6g7WEaBxCG8GD1KM2ffWPx7FLXgeJbNYrp");
+ assertInvalidAddress("UPXsjCoYrxag2pPoDDTB4cRriKCNn8WjhY99kqjYuNdTfE4MU2Yo1CPdpyK7PXpxDcAd5YDNerE6WCc4cVQvEbxLaHk4UcvbRp2");
+ assertInvalidAddress("UPXsinT9duNEtHGqAUicJKD2cmGiB9gB6sqHqWvV6suBx4TtPSR8ynyh2vVVvNyDE6g7W!!!xCG8GD1KM2ffWP7FLXgeJbNYrp2");
+ assertInvalidAddress("UmVSrJ7ES1IIIIIGHFm69SU6dTTKt8Vi6V7BoC3wsLccd1Y2CXgQkW7vHSe5uArGU9TjUC5RtvzhCycVDnPPbThTmZA8VqDzTP");
+ assertInvalidAddress("UmWrJ7ES1wGsikGHFm69SU6dTTKt8Vi6V7BoC3wsLcc1xY2CXgQkW7vHSe5uArGU9TjUC5RtvzhCycVDnPPbThTmZA8VqDzTPe");
+ assertInvalidAddress("UPi12rJ7ES1wGsikGHFm69SU6dTTKt8Vi6V7BoC36sqHqWvwsLcc1Y2CXgQkW7vHSe5uArGU9TjUC5RtvzhCycVDnPPbThTmZA8VqDzTPeM1");
+ assertInvalidAddress("UPisBB18NdcSywKDshsywbjc5uCi8ybSUtWgvM3LfzaYe93vd6DEu3PcSywKDshsywbjc5uCi8ybSUtWgvM3LfzaYe93d96NjjvBCYU2SZD2of");
+ }
+}
diff --git a/build.gradle b/build.gradle
index e63971d2755..3f395ea3b89 100644
--- a/build.gradle
+++ b/build.gradle
@@ -274,7 +274,7 @@ configure(project(':desktop')) {
apply plugin: 'witness'
apply from: '../gradle/witness/gradle-witness.gradle'
- version = '1.1.7-SNAPSHOT'
+ version = '1.2.1-SNAPSHOT'
mainClassName = 'bisq.desktop.app.BisqAppMain'
diff --git a/common/src/main/java/bisq/common/app/Capability.java b/common/src/main/java/bisq/common/app/Capability.java
index 8d18736f7b9..33040bbfa40 100644
--- a/common/src/main/java/bisq/common/app/Capability.java
+++ b/common/src/main/java/bisq/common/app/Capability.java
@@ -35,9 +35,10 @@ public enum Capability {
RECEIVE_BSQ_BLOCK, // Signaling that node which wants to receive BSQ blocks (DAO lite node)
@Deprecated DAO_STATE, // Not required anymore as no old clients out there not having that support
- //TODO can be set deprecated after v1.1.6 as we enforce update there
- BUNDLE_OF_ENVELOPES, // Supports bundling of messages if many messages are sent in short interval
+ @Deprecated BUNDLE_OF_ENVELOPES, // Supports bundling of messages if many messages are sent in short interval
SIGNED_ACCOUNT_AGE_WITNESS, // Supports the signed account age witness feature
- MEDIATION // Supports mediation feature
+ MEDIATION, // Supports mediation feature
+ REFUND_AGENT, // Supports refund agents
+ TRADE_STATISTICS_HASH_UPDATE // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data.
}
diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java
index c5275945fdb..2ef33d2e0b1 100644
--- a/common/src/main/java/bisq/common/app/Version.java
+++ b/common/src/main/java/bisq/common/app/Version.java
@@ -27,7 +27,7 @@ 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.1.7";
+ public static final String VERSION = "1.2.1";
public static int getMajorVersion(String version) {
return getSubVersion(version, 0);
@@ -73,7 +73,7 @@ private static int getSubVersion(String version, int index) {
// The version no. for the objects sent over the network. A change will break the serialization of old objects.
// If objects are used for both network and database the network version is applied.
// VERSION = 0.5.0 -> P2P_NETWORK_VERSION = 1
- @SuppressWarnings("ConstantConditions")
+ // With version 1.2.1 we change to version 2 (new trade protocol)
public static final int P2P_NETWORK_VERSION = 1;
// The version no. of the serialized data stored to disc. A change will break the serialization of old objects.
@@ -84,7 +84,8 @@ private static int getSubVersion(String version, int index) {
// 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.
// VERSION = 0.5.0 -> TRADE_PROTOCOL_VERSION = 1
- public static final int TRADE_PROTOCOL_VERSION = 1;
+ // Version 1.2.1 -> TRADE_PROTOCOL_VERSION = 2
+ public static final int TRADE_PROTOCOL_VERSION = 2;
private static int p2pMessageVersion;
public static final String BSQ_TX_VERSION = "1";
diff --git a/common/src/main/java/bisq/common/util/Utilities.java b/common/src/main/java/bisq/common/util/Utilities.java
index 11c72ac8a84..9d810853c73 100644
--- a/common/src/main/java/bisq/common/util/Utilities.java
+++ b/common/src/main/java/bisq/common/util/Utilities.java
@@ -23,7 +23,6 @@
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
-import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -50,17 +49,9 @@
import java.net.URI;
import java.net.URISyntaxException;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutput;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
@@ -87,9 +78,6 @@
@Slf4j
public class Utilities {
- private static long lastTimeStamp = System.currentTimeMillis();
- public static final String LB = System.getProperty("line.separator");
-
// TODO check out Jackson lib
public static String objectToJson(Object object) {
Gson gson = new GsonBuilder()
@@ -101,10 +89,6 @@ public static String objectToJson(Object object) {
return gson.toJson(object);
}
- public static ListeningExecutorService getListeningSingleThreadExecutor(String name) {
- return MoreExecutors.listeningDecorator(getSingleThreadExecutor(name));
- }
-
public static ListeningExecutorService getSingleThreadExecutor(String name) {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat(name)
@@ -279,10 +263,6 @@ && getDesktop().isSupported(Action.OPEN)) {
}
}
- public static String getTmpDir() {
- return System.getProperty("java.io.tmpdir");
- }
-
public static String getDownloadOfHomeDir() {
File file = new File(getSystemHomeDirectory() + "/Downloads");
if (file.exists())
@@ -291,13 +271,6 @@ public static String getDownloadOfHomeDir() {
return getSystemHomeDirectory();
}
- public static void printSystemLoad() {
- Runtime runtime = Runtime.getRuntime();
- long free = runtime.freeMemory() / 1024 / 1024;
- long total = runtime.totalMemory() / 1024 / 1024;
- long used = total - free;
- log.info("System load (no. threads/used memory (MB)): " + Thread.activeCount() + "/" + used);
- }
public static void copyToClipboard(String content) {
try {
@@ -313,99 +286,6 @@ public static void copyToClipboard(String content) {
}
}
- public static byte[] concatByteArrays(byte[]... arrays) {
- int totalLength = 0;
- for (byte[] array : arrays) {
- totalLength += array.length;
- }
-
- byte[] result = new byte[totalLength];
- int currentIndex = 0;
- for (byte[] array : arrays) {
- System.arraycopy(array, 0, result, currentIndex, array.length);
- currentIndex += array.length;
- }
- return result;
- }
-
- public static T jsonToObject(String jsonString, Class classOfT) {
- Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting().create();
- return gson.fromJson(jsonString, classOfT);
- }
-
- public static T deserialize(byte[] data) {
- ByteArrayInputStream bis = new ByteArrayInputStream(data);
- ObjectInput in = null;
- Object result = null;
- try {
- in = new ObjectInputStream(bis);
- result = in.readObject();
- if (!(result instanceof Serializable))
- throw new RuntimeException("Object not of type Serializable");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- bis.close();
- } catch (IOException ex) {
- // ignore close exception
- }
- try {
- if (in != null) {
- in.close();
- }
- } catch (IOException ex) {
- // ignore close exception
- }
- }
- //noinspection unchecked,ConstantConditions
- return (T) result;
- }
-
- public static byte[] serialize(Serializable object) {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutput out = null;
- byte[] result = null;
- try {
- out = new ObjectOutputStream(bos);
- out.writeObject(object);
- out.flush();
- result = bos.toByteArray().clone();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if (out != null) {
- out.close();
- }
- } catch (IOException ignore) {
- }
- try {
- bos.close();
- } catch (IOException ignore) {
- }
- }
- return result;
- }
-
- public static T cloneObject(Serializable object) {
- return deserialize(serialize(object));
- }
-
- @SuppressWarnings("SameParameterValue")
- private static void printElapsedTime(String msg) {
- if (!msg.isEmpty()) {
- msg += " / ";
- }
- long timeStamp = System.currentTimeMillis();
- log.debug(msg + "Elapsed: " + String.valueOf(timeStamp - lastTimeStamp));
- lastTimeStamp = timeStamp;
- }
-
- public static void printElapsedTime() {
- printElapsedTime("");
- }
-
public static void setThreadName(String name) {
Thread.currentThread().setName(name + "-" + new Random().nextInt(10000));
}
@@ -454,18 +334,6 @@ public static byte[] concatenateByteArrays(byte[] array1, byte[] array2) {
return ArrayUtils.addAll(array1, array2);
}
- public static byte[] concatenateByteArrays(byte[] array1, byte[] array2, byte[] array3) {
- return ArrayUtils.addAll(array1, ArrayUtils.addAll(array2, array3));
- }
-
- public static byte[] concatenateByteArrays(byte[] array1, byte[] array2, byte[] array3, byte[] array4) {
- return ArrayUtils.addAll(array1, ArrayUtils.addAll(array2, ArrayUtils.addAll(array3, array4)));
- }
-
- public static byte[] concatenateByteArrays(byte[] array1, byte[] array2, byte[] array3, byte[] array4, byte[] array5) {
- return ArrayUtils.addAll(array1, ArrayUtils.addAll(array2, ArrayUtils.addAll(array3, ArrayUtils.addAll(array4, array5))));
- }
-
public static Date getUTCDate(int year, int month, int dayOfMonth) {
GregorianCalendar calendar = new GregorianCalendar(year, month, dayOfMonth);
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -520,10 +388,6 @@ public static String toTruncatedString(Object message, int maxLength) {
return toTruncatedString(message, maxLength, true);
}
- public static String toTruncatedString(Object message, boolean removeLinebreaks) {
- return toTruncatedString(message, 200, removeLinebreaks);
- }
-
public static String toTruncatedString(Object message, int maxLength, boolean removeLinebreaks) {
if (message == null)
return "null";
@@ -579,10 +443,6 @@ public static String getShortId(String id, String sep) {
return id.substring(0, Math.min(8, id.length()));
}
- public static String collectionToCSV(Collection collection) {
- return collection.stream().map(Object::toString).collect(Collectors.joining(","));
- }
-
public static byte[] integerToByteArray(int intValue, int numBytes) {
byte[] bytes = new byte[numBytes];
for (int i = numBytes - 1; i >= 0; i--) {
diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto
index 63385e522dc..340be39a106 100644
--- a/common/src/main/proto/pb.proto
+++ b/common/src/main/proto/pb.proto
@@ -36,9 +36,9 @@ message NetworkEnvelope {
CloseConnectionMessage close_connection_message = 15;
PrefixedSealedAndSignedMessage prefixed_sealed_and_signed_message = 16;
- PayDepositRequest pay_deposit_request = 17;
- PublishDepositTxRequest publish_deposit_tx_request = 18;
- DepositTxPublishedMessage deposit_tx_published_message = 19;
+ InputsForDepositTxRequest inputs_for_deposit_tx_request = 17;
+ InputsForDepositTxResponse inputs_for_deposit_tx_response = 18;
+ DepositTxMessage deposit_tx_message = 19;
CounterCurrencyTransferStartedMessage counter_currency_transfer_started_message = 20;
PayoutTxPublishedMessage payout_tx_published_message = 21;
@@ -70,6 +70,11 @@ message NetworkEnvelope {
BundleOfEnvelopes bundle_of_envelopes = 43;
MediatedPayoutTxSignatureMessage mediated_payout_tx_signature_message = 44;
MediatedPayoutTxPublishedMessage mediated_payout_tx_published_message = 45;
+
+ DelayedPayoutTxSignatureRequest delayed_payout_tx_signature_request = 46;
+ DelayedPayoutTxSignatureResponse delayed_payout_tx_signature_response = 47;
+ DepositTxAndDelayedPayoutTxMessage deposit_tx_and_delayed_payout_tx_message = 48;
+ PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 49;
}
}
@@ -144,6 +149,7 @@ message OfferAvailabilityResponse {
string uid = 4;
NodeAddress arbitrator = 5;
NodeAddress mediator = 6;
+ NodeAddress refund_agent = 7;
}
message RefreshOfferMessage {
@@ -197,7 +203,7 @@ message PrefixedSealedAndSignedMessage {
// trade
-message PayDepositRequest {
+message InputsForDepositTxRequest {
string trade_id = 1;
NodeAddress sender_node_address = 2;
int64 trade_amount = 3;
@@ -221,9 +227,11 @@ message PayDepositRequest {
string uid = 21;
bytes account_age_witness_signature_of_offer_id = 22;
int64 current_date = 23;
+ repeated NodeAddress accepted_refund_agent_node_addresses = 24;
+ NodeAddress refund_agent_node_address = 25;
}
-message PublishDepositTxRequest {
+message InputsForDepositTxResponse {
string trade_id = 1;
PaymentAccountPayload maker_payment_account_payload = 2;
string maker_account_id = 3;
@@ -237,13 +245,42 @@ message PublishDepositTxRequest {
string uid = 11;
bytes account_age_witness_signature_of_prepared_deposit_tx = 12;
int64 current_date = 13;
+ int64 lock_time = 14;
}
-message DepositTxPublishedMessage {
- string trade_id = 1;
- bytes deposit_tx = 2;
+message DelayedPayoutTxSignatureRequest {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes delayed_payout_tx = 4;
+}
+
+message DelayedPayoutTxSignatureResponse {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes delayed_payout_tx_signature = 4;
+}
+
+message DepositTxAndDelayedPayoutTxMessage {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes deposit_tx = 4;
+ bytes delayed_payout_tx = 5;
+}
+
+message DepositTxMessage {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes deposit_tx = 4;
+}
+
+message PeerPublishedDelayedPayoutTxMessage {
+ string uid = 1;
+ string trade_id = 2;
NodeAddress sender_node_address = 3;
- string uid = 4;
}
message CounterCurrencyTransferStartedMessage {
@@ -279,8 +316,8 @@ message MediatedPayoutTxPublishedMessage {
message MediatedPayoutTxSignatureMessage {
string uid = 1;
- bytes tx_signature = 2;
string trade_id = 3;
+ bytes tx_signature = 2;
NodeAddress sender_node_address = 4;
}
@@ -290,6 +327,7 @@ enum SupportType {
ARBITRATION = 0;
MEDIATION = 1;
TRADE = 2;
+ REFUND = 3;
}
message OpenNewDisputeMessage {
@@ -431,7 +469,7 @@ message Peer {
message PubKeyRing {
bytes signature_pub_key_bytes = 1;
bytes encryption_pub_key_bytes = 2;
- reserved 3; // WAS: string pgp_pub_key_as_pem = 3;
+ reserved 3; // WAS: string pgp_pub_key_as_pem = 3;
}
message SealedAndSigned {
@@ -457,6 +495,7 @@ message StoragePayload {
MailboxStoragePayload mailbox_storage_payload = 6;
OfferPayload offer_payload = 7;
TempProposalPayload temp_proposal_payload = 8;
+ RefundAgent refund_agent = 9;
}
}
@@ -550,6 +589,18 @@ message Mediator {
map extra_data = 9;
}
+message RefundAgent {
+ NodeAddress node_address = 1;
+ repeated string language_codes = 2;
+ int64 registration_date = 3;
+ string registration_signature = 4;
+ bytes registration_pub_key = 5;
+ PubKeyRing pub_key_ring = 6;
+ string email_address = 7;
+ string info = 8;
+ map extra_data = 9;
+}
+
message Filter {
repeated string banned_node_address = 1;
repeated string banned_offer_ids = 2;
@@ -568,6 +619,7 @@ message Filter {
string disable_dao_below_version = 15;
string disable_trade_below_version = 16;
repeated string mediators = 17;
+ repeated string refundAgents = 18;
}
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
@@ -670,8 +722,14 @@ message AccountAgeWitness {
}
message SignedWitness {
- bool signed_by_arbitrator = 1;
- bytes witness_hash = 2;
+ enum VerificationMethod {
+ PB_ERROR = 0;
+ ARBITRATOR = 1;
+ TRADE = 2;
+ }
+
+ VerificationMethod verification_method = 1;
+ bytes account_age_witness_hash = 2;
bytes signature = 3;
bytes signer_pub_key = 4;
bytes witness_owner_pub_key = 5;
@@ -708,6 +766,9 @@ message Dispute {
bool is_closed = 21;
DisputeResult dispute_result = 22;
string dispute_payout_tx_id = 23;
+ SupportType support_type = 24;
+ string mediators_dispute_result = 25;
+ string delayed_payout_tx_id = 26;
}
message Attachment {
@@ -759,7 +820,7 @@ message Contract {
int64 trade_amount = 2;
int64 trade_price = 3;
string taker_fee_tx_id = 4;
- NodeAddress arbitrator_node_address = 5;
+ reserved 5; // WAS: arbitrator_node_address
bool is_buyer_maker_and_seller_taker = 6;
string maker_account_id = 7;
string taker_account_id = 8;
@@ -774,6 +835,8 @@ message Contract {
bytes maker_multi_sig_pub_key = 17;
bytes taker_multi_sig_pub_key = 18;
NodeAddress mediator_node_address = 19;
+ int64 lock_time = 20;
+ NodeAddress refund_agent_node_address = 21;
}
message RawTransactionInput {
@@ -793,6 +856,7 @@ enum AvailabilityResult {
NO_MEDIATORS = 7;
USER_IGNORED = 8;
MISSING_MANDATORY_CAPABILITY = 9;
+ NO_REFUND_AGENTS = 10;
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -1074,6 +1138,7 @@ message PersistableEnvelope {
UnconfirmedBsqChangeOutputList unconfirmed_bsq_change_output_list = 27;
SignedWitnessStore signed_witness_store = 28;
MediationDisputeList mediation_dispute_list = 29;
+ RefundDisputeList refund_dispute_list = 30;
}
}
@@ -1198,6 +1263,7 @@ message OpenOffer {
State state = 2;
NodeAddress arbitrator_node_address = 3;
NodeAddress mediator_node_address = 4;
+ NodeAddress refund_agent_node_address = 5;
}
message Tradable {
@@ -1220,13 +1286,13 @@ message Trade {
MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 5;
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 6;
TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST = 7;
- TAKER_PUBLISHED_DEPOSIT_TX = 8;
- TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 9;
- TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 10;
- TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 11;
- TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 12;
- MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 13;
- MAKER_SAW_DEPOSIT_TX_IN_NETWORK = 14;
+ SELLER_PUBLISHED_DEPOSIT_TX = 8;
+ SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 9;
+ SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 10;
+ SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 11;
+ SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 12;
+ BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 13;
+ BUYER_SAW_DEPOSIT_TX_IN_NETWORK = 14;
DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN = 15;
BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED = 16;
BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG = 17;
@@ -1266,6 +1332,9 @@ message Trade {
MEDIATION_REQUESTED = 5;
MEDIATION_STARTED_BY_PEER = 6;
MEDIATION_CLOSED = 7;
+ REFUND_REQUESTED = 8;
+ REFUND_REQUEST_STARTED_BY_PEER = 9;
+ REFUND_REQUEST_CLOSED = 10;
}
enum TradePeriodState {
@@ -1305,6 +1374,11 @@ message Trade {
string counter_currency_tx_id = 28;
repeated ChatMessage chat_message = 29;
MediationResultState mediation_result_state = 30;
+ int64 lock_time = 31;
+ bytes delayed_payout_tx_bytes = 32;
+ NodeAddress refund_agent_node_address = 33;
+ PubKeyRing refund_agent_pub_key_ring = 34;
+ RefundResultState refund_result_state = 35;
}
message BuyerAsMakerTrade {
@@ -1330,8 +1404,8 @@ message ProcessModel {
PubKeyRing pub_key_ring = 4;
string take_offer_fee_tx_id = 5;
bytes payout_tx_signature = 6;
- repeated NodeAddress taker_accepted_arbitrator_node_addresses = 7;
- repeated NodeAddress taker_accepted_mediator_node_addresses = 8;
+ reserved 7; // Not used anymore
+ reserved 8; // Not used anymore
bytes prepared_deposit_tx = 9;
repeated RawTransactionInput raw_transaction_inputs = 10;
int64 change_output_value = 11;
@@ -1376,6 +1450,10 @@ message MediationDisputeList {
repeated Dispute dispute = 1;
}
+message RefundDisputeList {
+ repeated Dispute dispute = 1;
+}
+
enum MediationResultState {
PB_ERROR_MEDIATION_RESULT = 0;
UNDEFINED_MEDIATION_RESULT = 1;
@@ -1395,6 +1473,12 @@ enum MediationResultState {
PAYOUT_TX_SEEN_IN_NETWORK = 15;
}
+//todo
+enum RefundResultState {
+ PB_ERROR_REFUND_RESULT = 0;
+ UNDEFINED_REFUND_RESULT = 1;
+}
+
///////////////////////////////////////////////////////////////////////////////////////////
// Preferences
///////////////////////////////////////////////////////////////////////////////////////////
@@ -1454,6 +1538,7 @@ message PreferencesPayload {
double buyer_security_deposit_as_percent_for_crypto = 52;
int32 block_notify_port = 53;
int32 css_theme = 54;
+ bool tac_accepted_v120 = 55;
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -1474,6 +1559,8 @@ message UserPayload {
Mediator registered_mediator = 11;
PriceAlertFilter price_alert_filter = 12;
repeated MarketAlertFilter market_alert_filters = 13;
+ repeated RefundAgent accepted_refund_agents = 14;
+ RefundAgent registered_refund_agent = 15;
}
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/common/src/test/java/bisq/common/util/UtilitiesTest.java b/common/src/test/java/bisq/common/util/UtilitiesTest.java
index 22dab34f1c4..7c315d70c20 100644
--- a/common/src/test/java/bisq/common/util/UtilitiesTest.java
+++ b/common/src/test/java/bisq/common/util/UtilitiesTest.java
@@ -17,8 +17,6 @@
package bisq.common.util;
-import java.util.Arrays;
-
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -26,14 +24,6 @@
public class UtilitiesTest {
- @Test
- public void testConcatenateByteArrays() {
- assertTrue(Arrays.equals(new byte[]{0x01, 0x02}, Utilities.concatenateByteArrays(new byte[]{0x01}, new byte[]{0x02})));
- assertTrue(Arrays.equals(new byte[]{0x01, 0x02, 0x03}, Utilities.concatenateByteArrays(new byte[]{0x01}, new byte[]{0x02}, new byte[]{0x03})));
- assertTrue(Arrays.equals(new byte[]{0x01, 0x02, 0x03, 0x04}, Utilities.concatenateByteArrays(new byte[]{0x01}, new byte[]{0x02}, new byte[]{0x03}, new byte[]{0x04})));
- assertTrue(Arrays.equals(new byte[]{0x01, 0x02, 0x03, 0x04, 0x05}, Utilities.concatenateByteArrays(new byte[]{0x01}, new byte[]{0x02}, new byte[]{0x03}, new byte[]{0x04}, new byte[]{0x05})));
- }
-
@Test
public void testToStringList() {
assertTrue(Utilities.commaSeparatedListToSet(null, false).isEmpty());
diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitness.java b/core/src/main/java/bisq/core/account/sign/SignedWitness.java
index 111bb195cbd..e5af940470c 100644
--- a/core/src/main/java/bisq/core/account/sign/SignedWitness.java
+++ b/core/src/main/java/bisq/core/account/sign/SignedWitness.java
@@ -26,6 +26,7 @@
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
import bisq.common.crypto.Hash;
+import bisq.common.proto.ProtoUtil;
import bisq.common.proto.persistable.PersistableEnvelope;
import bisq.common.util.Utilities;
@@ -46,10 +47,24 @@
@Value
public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope,
DateTolerantPayload, CapabilityRequiringPayload {
+
+ public enum VerificationMethod {
+ ARBITRATOR,
+ TRADE;
+
+ public static SignedWitness.VerificationMethod fromProto(protobuf.SignedWitness.VerificationMethod method) {
+ return ProtoUtil.enumFromProto(SignedWitness.VerificationMethod.class, method.name());
+ }
+
+ public static protobuf.SignedWitness.VerificationMethod toProtoMessage(SignedWitness.VerificationMethod method) {
+ return protobuf.SignedWitness.VerificationMethod.valueOf(method.name());
+ }
+ }
+
private static final long TOLERANCE = TimeUnit.DAYS.toMillis(1);
- private final boolean signedByArbitrator;
- private final byte[] witnessHash;
+ private final VerificationMethod verificationMethod;
+ private final byte[] accountAgeWitnessHash;
private final byte[] signature;
private final byte[] signerPubKey;
private final byte[] witnessOwnerPubKey;
@@ -58,15 +73,15 @@ public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPa
transient private final byte[] hash;
- public SignedWitness(boolean signedByArbitrator,
- byte[] witnessHash,
+ public SignedWitness(VerificationMethod verificationMethod,
+ byte[] accountAgeWitnessHash,
byte[] signature,
byte[] signerPubKey,
byte[] witnessOwnerPubKey,
long date,
long tradeAmount) {
- this.signedByArbitrator = signedByArbitrator;
- this.witnessHash = witnessHash.clone();
+ this.verificationMethod = verificationMethod;
+ this.accountAgeWitnessHash = accountAgeWitnessHash.clone();
this.signature = signature.clone();
this.signerPubKey = signerPubKey.clone();
this.witnessOwnerPubKey = witnessOwnerPubKey.clone();
@@ -80,7 +95,7 @@ public SignedWitness(boolean signedByArbitrator,
// same peer to add more security as if that one would be colluding it would be not detected anyway. The total
// number of signed trades with different peers is still available and can be considered more valuable data for
// security.
- byte[] data = Utilities.concatenateByteArrays(witnessHash, signature);
+ byte[] data = Utilities.concatenateByteArrays(accountAgeWitnessHash, signature);
data = Utilities.concatenateByteArrays(data, signerPubKey);
hash = Hash.getSha256Ripemd160hash(data);
}
@@ -93,8 +108,8 @@ public SignedWitness(boolean signedByArbitrator,
@Override
public protobuf.PersistableNetworkPayload toProtoMessage() {
final protobuf.SignedWitness.Builder builder = protobuf.SignedWitness.newBuilder()
- .setSignedByArbitrator(signedByArbitrator)
- .setWitnessHash(ByteString.copyFrom(witnessHash))
+ .setVerificationMethod(VerificationMethod.toProtoMessage(verificationMethod))
+ .setAccountAgeWitnessHash(ByteString.copyFrom(accountAgeWitnessHash))
.setSignature(ByteString.copyFrom(signature))
.setSignerPubKey(ByteString.copyFrom(signerPubKey))
.setWitnessOwnerPubKey(ByteString.copyFrom(witnessOwnerPubKey))
@@ -108,8 +123,9 @@ protobuf.SignedWitness toProtoSignedWitness() {
}
public static SignedWitness fromProto(protobuf.SignedWitness proto) {
- return new SignedWitness(proto.getSignedByArbitrator(),
- proto.getWitnessHash().toByteArray(),
+ return new SignedWitness(
+ SignedWitness.VerificationMethod.fromProto(proto.getVerificationMethod()),
+ proto.getAccountAgeWitnessHash().toByteArray(),
proto.getSignature().toByteArray(),
proto.getSignerPubKey().toByteArray(),
proto.getWitnessOwnerPubKey().toByteArray(),
@@ -124,7 +140,7 @@ public static SignedWitness fromProto(protobuf.SignedWitness proto) {
@Override
public boolean isDateInTolerance(Clock clock) {
- // We don't allow older or newer then 1 day.
+ // We don't allow older or newer than 1 day.
// Preventing forward dating is also important to protect against a sophisticated attack
return Math.abs(new Date().getTime() - date) <= TOLERANCE;
}
@@ -145,6 +161,9 @@ public byte[] getHash() {
return hash;
}
+ public boolean isSignedByArbitrator() {
+ return verificationMethod == VerificationMethod.ARBITRATOR;
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
@@ -157,8 +176,8 @@ P2PDataStorage.ByteArray getHashAsByteArray() {
@Override
public String toString() {
return "SignedWitness{" +
- ",\n signedByArbitrator=" + signedByArbitrator +
- ",\n witnessHash=" + Utilities.bytesAsHexString(witnessHash) +
+ ",\n verificationMethod=" + verificationMethod +
+ ",\n witnessHash=" + Utilities.bytesAsHexString(accountAgeWitnessHash) +
",\n signature=" + Utilities.bytesAsHexString(signature) +
",\n signerPubKey=" + Utilities.bytesAsHexString(signerPubKey) +
",\n witnessOwnerPubKey=" + Utilities.bytesAsHexString(witnessOwnerPubKey) +
diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
index ef87a6e35f1..e32061766df 100644
--- a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
+++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
@@ -18,23 +18,17 @@
package bisq.core.account.sign;
import bisq.core.account.witness.AccountAgeWitness;
-import bisq.core.account.witness.AccountAgeWitnessService;
-import bisq.core.payment.ChargeBackRisk;
-import bisq.core.payment.payload.PaymentAccountPayload;
-import bisq.core.payment.payload.PaymentMethod;
-import bisq.core.support.dispute.Dispute;
-import bisq.core.support.dispute.DisputeResult;
-import bisq.core.support.dispute.arbitration.ArbitrationManager;
-import bisq.core.support.dispute.arbitration.BuyerDataItem;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
+import bisq.core.user.User;
+import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.P2PDataStorage;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
+import bisq.common.UserThread;
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.KeyRing;
-import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.util.Utilities;
@@ -57,30 +51,24 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.Nullable;
-
@Slf4j
public class SignedWitnessService {
- public static final long CHARGEBACK_SAFETY_DAYS = 30;
+ public static final long SIGNER_AGE_DAYS = 30;
+ public static final long SIGNER_AGE = SIGNER_AGE_DAYS * ChronoUnit.DAYS.getDuration().toMillis();
private final KeyRing keyRing;
private final P2PService p2PService;
- private final AccountAgeWitnessService accountAgeWitnessService;
private final ArbitratorManager arbitratorManager;
- private final ArbitrationManager arbitrationManager;
- private final ChargeBackRisk chargeBackRisk;
+ private final User user;
private final Map signedWitnessMap = new HashMap<>();
-
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@@ -88,18 +76,14 @@ public class SignedWitnessService {
@Inject
public SignedWitnessService(KeyRing keyRing,
P2PService p2PService,
- AccountAgeWitnessService accountAgeWitnessService,
ArbitratorManager arbitratorManager,
SignedWitnessStorageService signedWitnessStorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService,
- ArbitrationManager arbitrationManager,
- ChargeBackRisk chargeBackRisk) {
+ User user) {
this.keyRing = keyRing;
this.p2PService = p2PService;
- this.accountAgeWitnessService = accountAgeWitnessService;
this.arbitratorManager = arbitratorManager;
- this.arbitrationManager = arbitrationManager;
- this.chargeBackRisk = chargeBackRisk;
+ this.user = user;
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
appendOnlyDataStoreService.addService(signedWitnessStorageService);
@@ -121,40 +105,72 @@ public void onAllServicesInitialized() {
if (e instanceof SignedWitness)
addToMap((SignedWitness) e);
});
+
+ if (p2PService.isBootstrapped()) {
+ onBootstrapComplete();
+ } else {
+ p2PService.addP2PServiceListener(new BootstrapListener() {
+ @Override
+ public void onUpdatedDataReceived() {
+ onBootstrapComplete();
+ }
+ });
+ }
}
+ private void onBootstrapComplete() {
+ if (user.getRegisteredArbitrator() != null) {
+ UserThread.runAfter(this::doRepublishAllSignedWitnesses, 60);
+ }
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
- public List getMyWitnessAgeList(PaymentAccountPayload myPaymentAccountPayload) {
- AccountAgeWitness accountAgeWitness = accountAgeWitnessService.getMyWitness(myPaymentAccountPayload);
- // We do not validate as it would not make sense to cheat one self...
+ /**
+ * List of dates as long when accountAgeWitness was signed
+ */
+ public List getVerifiedWitnessDateList(AccountAgeWitness accountAgeWitness) {
return getSignedWitnessSet(accountAgeWitness).stream()
+ .filter(this::verifySignature)
.map(SignedWitness::getDate)
.sorted()
.collect(Collectors.toList());
}
-
- public List getVerifiedWitnessAgeList(AccountAgeWitness accountAgeWitness) {
- return signedWitnessMap.values().stream()
- .filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
- .filter(this::verifySignature)
+ /**
+ * List of dates as long when accountAgeWitness was signed
+ * Not verifying that signatures are correct
+ */
+ public List getWitnessDateList(AccountAgeWitness accountAgeWitness) {
+ // We do not validate as it would not make sense to cheat one self...
+ return getSignedWitnessSet(accountAgeWitness).stream()
.map(SignedWitness::getDate)
.sorted()
.collect(Collectors.toList());
}
+ public boolean isSignedByArbitrator(AccountAgeWitness accountAgeWitness) {
+ return getSignedWitnessSet(accountAgeWitness).stream()
+ .map(SignedWitness::isSignedByArbitrator)
+ .findAny()
+ .orElse(false);
+ }
+
// Arbitrators sign with EC key
- public SignedWitness signAccountAgeWitness(Coin tradeAmount,
- AccountAgeWitness accountAgeWitness,
- ECKey key,
- PublicKey peersPubKey) {
+ public void signAccountAgeWitness(Coin tradeAmount,
+ AccountAgeWitness accountAgeWitness,
+ ECKey key,
+ PublicKey peersPubKey) {
+ if (isSignedAccountAgeWitness(accountAgeWitness)) {
+ log.warn("Arbitrator trying to sign already signed accountagewitness {}", accountAgeWitness.toString());
+ return;
+ }
+
String accountAgeWitnessHashAsHex = Utilities.encodeToHex(accountAgeWitness.getHash());
String signatureBase64 = key.signMessage(accountAgeWitnessHashAsHex);
- SignedWitness signedWitness = new SignedWitness(true,
+ SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.ARBITRATOR,
accountAgeWitness.getHash(),
signatureBase64.getBytes(Charsets.UTF_8),
key.getPubKey(),
@@ -162,15 +178,20 @@ public SignedWitness signAccountAgeWitness(Coin tradeAmount,
new Date().getTime(),
tradeAmount.value);
publishSignedWitness(signedWitness);
- return signedWitness;
+ log.info("Arbitrator signed witness {}", signedWitness.toString());
}
// Any peer can sign with DSA key
- public SignedWitness signAccountAgeWitness(Coin tradeAmount,
- AccountAgeWitness accountAgeWitness,
- PublicKey peersPubKey) throws CryptoException {
+ public void signAccountAgeWitness(Coin tradeAmount,
+ AccountAgeWitness accountAgeWitness,
+ PublicKey peersPubKey) throws CryptoException {
+ if (isSignedAccountAgeWitness(accountAgeWitness)) {
+ log.warn("Trader trying to sign already signed accountagewitness {}", accountAgeWitness.toString());
+ return;
+ }
+
byte[] signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), accountAgeWitness.getHash());
- SignedWitness signedWitness = new SignedWitness(false,
+ SignedWitness signedWitness = new SignedWitness(SignedWitness.VerificationMethod.TRADE,
accountAgeWitness.getHash(),
signature,
keyRing.getSignatureKeyPair().getPublic().getEncoded(),
@@ -178,7 +199,7 @@ public SignedWitness signAccountAgeWitness(Coin tradeAmount,
new Date().getTime(),
tradeAmount.value);
publishSignedWitness(signedWitness);
- return signedWitness;
+ log.info("Trader signed witness {}", signedWitness.toString());
}
public boolean verifySignature(SignedWitness signedWitness) {
@@ -191,7 +212,7 @@ public boolean verifySignature(SignedWitness signedWitness) {
private boolean verifySignatureWithECKey(SignedWitness signedWitness) {
try {
- String message = Utilities.encodeToHex(signedWitness.getWitnessHash());
+ String message = Utilities.encodeToHex(signedWitness.getAccountAgeWitnessHash());
String signatureBase64 = new String(signedWitness.getSignature(), Charsets.UTF_8);
ECKey key = ECKey.fromPublicOnly(signedWitness.getSignerPubKey());
if (arbitratorManager.isPublicKeyInList(Utilities.encodeToHex(key.getPubKey()))) {
@@ -211,7 +232,7 @@ private boolean verifySignatureWithECKey(SignedWitness signedWitness) {
private boolean verifySignatureWithDSAKey(SignedWitness signedWitness) {
try {
PublicKey signaturePubKey = Sig.getPublicKeyFromBytes(signedWitness.getSignerPubKey());
- Sig.verify(signaturePubKey, signedWitness.getWitnessHash(), signedWitness.getSignature());
+ Sig.verify(signaturePubKey, signedWitness.getAccountAgeWitnessHash(), signedWitness.getSignature());
return true;
} catch (CryptoException e) {
log.warn("verifySignature signedWitness failed. signedWitness={}", signedWitness);
@@ -220,9 +241,9 @@ private boolean verifySignatureWithDSAKey(SignedWitness signedWitness) {
}
}
- public Set getSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
+ private Set getSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
return signedWitnessMap.values().stream()
- .filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
+ .filter(e -> Arrays.equals(e.getAccountAgeWitnessHash(), accountAgeWitness.getHash()))
.collect(Collectors.toSet());
}
@@ -230,7 +251,7 @@ public Set getSignedWitnessSet(AccountAgeWitness accountAgeWitnes
public Set getArbitratorsSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
return signedWitnessMap.values().stream()
.filter(SignedWitness::isSignedByArbitrator)
- .filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
+ .filter(e -> Arrays.equals(e.getAccountAgeWitnessHash(), accountAgeWitness.getHash()))
.collect(Collectors.toSet());
}
@@ -238,31 +259,41 @@ public Set getArbitratorsSignedWitnessSet(AccountAgeWitness accou
public Set getTrustedPeerSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
return signedWitnessMap.values().stream()
.filter(e -> !e.isSignedByArbitrator())
- .filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
+ .filter(e -> Arrays.equals(e.getAccountAgeWitnessHash(), accountAgeWitness.getHash()))
.collect(Collectors.toSet());
}
// We go one level up by using the signer Key to lookup for SignedWitness objects which contain the signerKey as
// witnessOwnerPubKey
- public Set getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
- Stack excluded) {
+ private Set getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
+ Stack excluded) {
return signedWitnessMap.values().stream()
.filter(e -> Arrays.equals(e.getWitnessOwnerPubKey(), ownerPubKey))
.filter(e -> !excluded.contains(new P2PDataStorage.ByteArray(e.getSignerPubKey())))
.collect(Collectors.toSet());
}
+ public boolean isSignedAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
+ return isSignerAccountAgeWitness(accountAgeWitness, new Date().getTime() + SIGNER_AGE);
+ }
+
+ public boolean isSignerAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
+ return isSignerAccountAgeWitness(accountAgeWitness, new Date().getTime());
+ }
+
/**
- * Checks whether the accountAgeWitness has a valid signature from a peer/arbitrator.
- * @param accountAgeWitness
- * @return true if accountAgeWitness is valid, false otherwise.
+ * Checks whether the accountAgeWitness has a valid signature from a peer/arbitrator and is allowed to sign
+ * other accounts.
+ *
+ * @param accountAgeWitness accountAgeWitness
+ * @param time time of signing
+ * @return true if accountAgeWitness is allowed to sign at time, false otherwise.
*/
- public boolean isValidAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
+ private boolean isSignerAccountAgeWitness(AccountAgeWitness accountAgeWitness, long time) {
Stack excludedPubKeys = new Stack<>();
- long now = new Date().getTime();
Set signedWitnessSet = getSignedWitnessSet(accountAgeWitness);
for (SignedWitness signedWitness : signedWitnessSet) {
- if (isValidSignedWitnessInternal(signedWitness, now, excludedPubKeys)) {
+ if (isValidSignerWitnessInternal(signedWitness, time, excludedPubKeys)) {
return true;
}
}
@@ -272,12 +303,13 @@ public boolean isValidAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
/**
* Helper to isValidAccountAgeWitness(accountAgeWitness)
- * @param signedWitness the signedWitness to validate
+ *
+ * @param signedWitness the signedWitness to validate
* @param childSignedWitnessDateMillis the date the child SignedWitness was signed or current time if it is a leave.
- * @param excludedPubKeys stack to prevent recursive loops
+ * @param excludedPubKeys stack to prevent recursive loops
* @return true if signedWitness is valid, false otherwise.
*/
- private boolean isValidSignedWitnessInternal(SignedWitness signedWitness,
+ private boolean isValidSignerWitnessInternal(SignedWitness signedWitness,
long childSignedWitnessDateMillis,
Stack excludedPubKeys) {
if (!verifySignature(signedWitness)) {
@@ -299,7 +331,7 @@ private boolean isValidSignedWitnessInternal(SignedWitness signedWitness,
// Iterate over signedWitness signers
Set signerSignedWitnessSet = getSignedWitnessSetByOwnerPubKey(signedWitness.getSignerPubKey(), excludedPubKeys);
for (SignedWitness signerSignedWitness : signerSignedWitnessSet) {
- if (isValidSignedWitnessInternal(signerSignedWitness, signedWitness.getDate(), excludedPubKeys)) {
+ if (isValidSignerWitnessInternal(signerSignedWitness, signedWitness.getDate(), excludedPubKeys)) {
return true;
}
}
@@ -311,7 +343,8 @@ private boolean isValidSignedWitnessInternal(SignedWitness signedWitness,
}
private boolean verifyDate(SignedWitness signedWitness, long childSignedWitnessDateMillis) {
- long childSignedWitnessDateMinusChargebackPeriodMillis = Instant.ofEpochMilli(childSignedWitnessDateMillis).minus(CHARGEBACK_SAFETY_DAYS, ChronoUnit.DAYS).toEpochMilli();
+ long childSignedWitnessDateMinusChargebackPeriodMillis = Instant.ofEpochMilli(
+ childSignedWitnessDateMillis).minus(SIGNER_AGE, ChronoUnit.MILLIS).toEpochMilli();
long signedWitnessDateMillis = signedWitness.getDate();
return signedWitnessDateMillis <= childSignedWitnessDateMinusChargebackPeriodMillis;
}
@@ -322,49 +355,18 @@ private boolean verifyDate(SignedWitness signedWitness, long childSignedWitnessD
@VisibleForTesting
void addToMap(SignedWitness signedWitness) {
+ // TODO: Perhaps filter out all but one signedwitness per accountagewitness
signedWitnessMap.putIfAbsent(signedWitness.getHashAsByteArray(), signedWitness);
}
private void publishSignedWitness(SignedWitness signedWitness) {
if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) {
+ log.info("broadcast signed witness {}", signedWitness.toString());
p2PService.addPersistableNetworkPayload(signedWitness, false);
}
}
- // Arbitrator signing
- public List getBuyerPaymentAccounts(long safeDate, PaymentMethod paymentMethod) {
- return arbitrationManager.getDisputesAsObservableList().stream()
- .filter(dispute -> dispute.getContract().getPaymentMethodId().equals(paymentMethod.getId()))
- .filter(this::hasChargebackRisk)
- .filter(this::isBuyerWinner)
- .map(this::getBuyerData)
- .filter(Objects::nonNull)
- .filter(buyerDataItem -> buyerDataItem.getAccountAgeWitness().getDate() < safeDate)
- .distinct()
- .collect(Collectors.toList());
+ private void doRepublishAllSignedWitnesses() {
+ signedWitnessMap.forEach((e, signedWitness) -> p2PService.addPersistableNetworkPayload(signedWitness, true));
}
-
- private boolean hasChargebackRisk(Dispute dispute) {
- return chargeBackRisk.hasChargebackRisk(dispute.getContract().getPaymentMethodId(),
- dispute.getContract().getOfferPayload().getCurrencyCode());
- }
-
- private boolean isBuyerWinner(Dispute dispute) {
- return dispute.getDisputeResultProperty().get().getWinner() == DisputeResult.Winner.BUYER;
- }
-
- @Nullable
- private BuyerDataItem getBuyerData(Dispute dispute) {
- PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
- PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
- Optional optionalWitness = accountAgeWitnessService
- .findWitness(buyerPaymentAccountPaload, buyerPubKeyRing);
- return optionalWitness.map(witness -> new BuyerDataItem(
- buyerPaymentAccountPaload,
- witness,
- dispute.getContract().getTradeAmount(),
- dispute.getContract().getSellerPubKeyRing().getSignaturePubKey()))
- .orElse(null);
- }
-
}
diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeRestrictions.java b/core/src/main/java/bisq/core/account/witness/AccountAgeRestrictions.java
deleted file mode 100644
index 04c6ab2af43..00000000000
--- a/core/src/main/java/bisq/core/account/witness/AccountAgeRestrictions.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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 .
- */
-
-package bisq.core.account.witness;
-
-import bisq.core.offer.Offer;
-import bisq.core.offer.OfferPayload;
-import bisq.core.offer.OfferRestrictions;
-import bisq.core.payment.PaymentAccount;
-import bisq.core.payment.payload.PaymentMethod;
-import bisq.core.trade.Trade;
-
-import bisq.common.util.Utilities;
-
-import java.util.Date;
-import java.util.GregorianCalendar;
-
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class AccountAgeRestrictions {
- private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.MARCH, 1).getTime();
-
- public static boolean isMakersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Offer offer) {
- long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMakersAccountAge(offer, new Date());
- return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
- }
-
- public static boolean isTradePeersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Trade trade) {
- long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getTradingPeersAccountAge(trade);
- return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
- }
-
- public static boolean isMyAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount myPaymentAccount) {
- long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMyAccountAge(myPaymentAccount.getPaymentAccountPayload());
- return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
- }
-
- public static long getMyTradeLimitAtCreateOffer(AccountAgeWitnessService accountAgeWitnessService,
- PaymentAccount paymentAccount,
- String currencyCode,
- OfferPayload.Direction direction) {
- if (direction == OfferPayload.Direction.BUY &&
- PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
- AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, paymentAccount)) {
- return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
- } else {
- return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode);
- }
- }
-
- public static long getMyTradeLimitAtTakeOffer(AccountAgeWitnessService accountAgeWitnessService,
- PaymentAccount paymentAccount,
- Offer offer,
- String currencyCode,
- OfferPayload.Direction direction) {
- if (direction == OfferPayload.Direction.BUY &&
- PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
- AccountAgeRestrictions.isMakersAccountAgeImmature(accountAgeWitnessService, offer)) {
- // Taker is seller
- return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
- } else if (direction == OfferPayload.Direction.SELL &&
- PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
- AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, paymentAccount)) {
- // Taker is buyer
- return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
- } else {
- // Offers with no chargeback risk or mature buyer accounts
- return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode);
- }
- }
-}
diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
index 74f7d276d3e..37df8b9a634 100644
--- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
+++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
@@ -17,12 +17,22 @@
package bisq.core.account.witness;
+import bisq.core.account.sign.SignedWitnessService;
+import bisq.core.filter.FilterManager;
+import bisq.core.filter.PaymentAccountFilter;
import bisq.core.locale.CurrencyUtil;
+import bisq.core.locale.Res;
import bisq.core.offer.Offer;
+import bisq.core.offer.OfferPayload;
+import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.AssetAccount;
+import bisq.core.payment.ChargeBackRisk;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeResult;
+import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.user.User;
@@ -43,6 +53,7 @@
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.ECKey;
import javax.inject.Inject;
@@ -52,10 +63,14 @@
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
@@ -65,16 +80,40 @@
public class AccountAgeWitnessService {
private static final Date RELEASE = Utilities.getUTCDate(2017, GregorianCalendar.NOVEMBER, 11);
public static final Date FULL_ACTIVATION = Utilities.getUTCDate(2018, GregorianCalendar.FEBRUARY, 15);
+ private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.MARCH, 1).getTime();
public enum AccountAge {
+ UNVERIFIED,
LESS_ONE_MONTH,
ONE_TO_TWO_MONTHS,
TWO_MONTHS_OR_MORE
}
+ public enum SignState {
+ UNSIGNED(Res.get("offerbook.timeSinceSigning.notSigned")),
+ ARBITRATOR(Res.get("offerbook.timeSinceSigning.info.arbitrator")),
+ PEER_INITIAL(Res.get("offerbook.timeSinceSigning.info.peer")),
+ PEER_LIMIT_LIFTED(Res.get("offerbook.timeSinceSigning.info.peerLimitLifted")),
+ PEER_SIGNER(Res.get("offerbook.timeSinceSigning.info.signer"));
+
+ private String presentation;
+
+ SignState(String presentation) {
+ this.presentation = presentation;
+ }
+
+ public String getPresentation() {
+ return presentation;
+ }
+
+ }
+
private final KeyRing keyRing;
private final P2PService p2PService;
private final User user;
+ private final SignedWitnessService signedWitnessService;
+ private final ChargeBackRisk chargeBackRisk;
+ private final FilterManager filterManager;
private final Map accountAgeWitnessMap = new HashMap<>();
@@ -85,12 +124,20 @@ public enum AccountAge {
@Inject
- public AccountAgeWitnessService(KeyRing keyRing, P2PService p2PService, User user,
+ public AccountAgeWitnessService(KeyRing keyRing,
+ P2PService p2PService,
+ User user,
+ SignedWitnessService signedWitnessService,
+ ChargeBackRisk chargeBackRisk,
AccountAgeWitnessStorageService accountAgeWitnessStorageService,
- AppendOnlyDataStoreService appendOnlyDataStoreService) {
+ AppendOnlyDataStoreService appendOnlyDataStoreService,
+ FilterManager filterManager) {
this.keyRing = keyRing;
this.p2PService = p2PService;
this.user = user;
+ this.signedWitnessService = signedWitnessService;
+ this.chargeBackRisk = chargeBackRisk;
+ this.filterManager = filterManager;
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
appendOnlyDataStoreService.addService(accountAgeWitnessStorageService);
@@ -164,7 +211,8 @@ private AccountAgeWitness getNewWitness(PaymentAccountPayload paymentAccountPayl
return new AccountAgeWitness(hash, new Date().getTime());
}
- public Optional findWitness(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
+ private Optional findWitness(PaymentAccountPayload paymentAccountPayload,
+ PubKeyRing pubKeyRing) {
byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload);
byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt,
pubKeyRing.getSignaturePubKeyBytes()));
@@ -172,6 +220,19 @@ public Optional findWitness(PaymentAccountPayload paymentAcco
return getWitnessByHash(hash);
}
+ private Optional findWitness(Offer offer) {
+ final Optional accountAgeWitnessHash = offer.getAccountAgeWitnessHashAsHex();
+ return accountAgeWitnessHash.isPresent() ?
+ getWitnessByHashAsHex(accountAgeWitnessHash.get()) :
+ Optional.empty();
+ }
+
+ private Optional findTradePeerWitness(Trade trade) {
+ TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
+ return (tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) ?
+ Optional.empty() : findWitness(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing());
+ }
+
private Optional getWitnessByHash(byte[] hash) {
P2PDataStorage.ByteArray hashAsByteArray = new P2PDataStorage.ByteArray(hash);
@@ -186,19 +247,57 @@ private Optional getWitnessByHashAsHex(String hashAsHex) {
return getWitnessByHash(Utilities.decodeFromHex(hashAsHex));
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Witness age
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
public long getAccountAge(AccountAgeWitness accountAgeWitness, Date now) {
log.debug("getAccountAge now={}, accountAgeWitness.getDate()={}", now.getTime(), accountAgeWitness.getDate());
return now.getTime() - accountAgeWitness.getDate();
}
+ // Return -1 if no witness found
public long getAccountAge(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
return findWitness(paymentAccountPayload, pubKeyRing)
.map(accountAgeWitness -> getAccountAge(accountAgeWitness, new Date()))
.orElse(-1L);
}
- public AccountAge getAccountAgeCategory(long accountAge) {
- if (accountAge < TimeUnit.DAYS.toMillis(30)) {
+ public long getAccountAge(Offer offer) {
+ return findWitness(offer)
+ .map(accountAgeWitness -> getAccountAge(accountAgeWitness, new Date()))
+ .orElse(-1L);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Signed age
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ // Return -1 if not signed
+ public long getWitnessSignAge(AccountAgeWitness accountAgeWitness, Date now) {
+ List dates = signedWitnessService.getVerifiedWitnessDateList(accountAgeWitness);
+ if (dates.isEmpty()) {
+ return -1L;
+ } else {
+ return now.getTime() - dates.get(0);
+ }
+ }
+
+ // Return -1 if not signed
+ public long getWitnessSignAge(Offer offer, Date now) {
+ return findWitness(offer)
+ .map(witness -> getWitnessSignAge(witness, now))
+ .orElse(-1L);
+ }
+
+ public AccountAge getPeersAccountAgeCategory(long peersAccountAge) {
+ return getAccountAgeCategory(peersAccountAge);
+ }
+
+ private AccountAge getAccountAgeCategory(long accountAge) {
+ if (accountAge < 0) {
+ return AccountAge.UNVERIFIED;
+ } else if (accountAge < TimeUnit.DAYS.toMillis(30)) {
return AccountAge.LESS_ONE_MONTH;
} else if (accountAge < TimeUnit.DAYS.toMillis(60)) {
return AccountAge.ONE_TO_TWO_MONTHS;
@@ -207,41 +306,85 @@ public AccountAge getAccountAgeCategory(long accountAge) {
}
}
- private long getTradeLimit(Coin maxTradeLimit, String currencyCode, Optional accountAgeWitnessOptional, Date now) {
+ // Checks trade limit based on time since signing of AccountAgeWitness
+ private long getTradeLimit(Coin maxTradeLimit,
+ String currencyCode,
+ AccountAgeWitness accountAgeWitness,
+ AccountAge accountAgeCategory,
+ OfferPayload.Direction direction,
+ PaymentMethod paymentMethod) {
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
double factor;
-
- final long accountAge = getAccountAge((accountAgeWitnessOptional.get()), now);
- AccountAge accountAgeCategory = accountAgeWitnessOptional
- .map(accountAgeWitness -> getAccountAgeCategory(accountAge))
- .orElse(AccountAge.LESS_ONE_MONTH);
-
- switch (accountAgeCategory) {
- case TWO_MONTHS_OR_MORE:
- factor = 1;
- break;
- case ONE_TO_TWO_MONTHS:
- factor = 0.5;
- break;
- case LESS_ONE_MONTH:
- default:
- factor = 0.25;
- break;
+ boolean isRisky = PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode);
+ if (!isRisky || direction == OfferPayload.Direction.SELL) {
+ // Get age of witness rather than time since signing for non risky payment methods and for selling
+ accountAgeCategory = getAccountAgeCategory(getAccountAge(accountAgeWitness, new Date()));
+ }
+ long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
+ if (direction == OfferPayload.Direction.BUY && isRisky) {
+ // Used only for bying of BTC with risky payment methods
+ switch (accountAgeCategory) {
+ case TWO_MONTHS_OR_MORE:
+ factor = 1;
+ break;
+ case ONE_TO_TWO_MONTHS:
+ factor = 0.5;
+ break;
+ case LESS_ONE_MONTH:
+ case UNVERIFIED:
+ default:
+ factor = 0;
+ }
+ } else {
+ // Used by non risky payment methods and for selling BTC with risky methods
+ switch (accountAgeCategory) {
+ case TWO_MONTHS_OR_MORE:
+ factor = 1;
+ break;
+ case ONE_TO_TWO_MONTHS:
+ factor = 0.5;
+ break;
+ case LESS_ONE_MONTH:
+ case UNVERIFIED:
+ factor = 0.25;
+ break;
+ default:
+ factor = 0;
+ }
+ }
+ if (factor > 0) {
+ limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
}
- final long limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
- log.debug("accountAgeCategory={}, accountAge={}, limit={}, factor={}, accountAgeWitnessHash={}",
+ log.debug("accountAgeCategory={}, limit={}, factor={}, accountAgeWitnessHash={}",
accountAgeCategory,
- accountAge / TimeUnit.DAYS.toMillis(1) + " days",
Coin.valueOf(limit).toFriendlyString(),
factor,
- accountAgeWitnessOptional.map(accountAgeWitness -> Utilities.bytesAsHexString(accountAgeWitness.getHash())).orElse("accountAgeWitnessOptional not present"));
+ Utilities.bytesAsHexString(accountAgeWitness.getHash()));
return limit;
} else {
return maxTradeLimit.value;
}
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Trade limit exceptions
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private boolean isImmature(AccountAgeWitness accountAgeWitness) {
+ return accountAgeWitness.getDate() > SAFE_ACCOUNT_AGE_DATE;
+ }
+
+ public boolean myHasTradeLimitException(PaymentAccount myPaymentAccount) {
+ return hasTradeLimitException(getMyWitness(myPaymentAccount.getPaymentAccountPayload()));
+ }
+
+ // There are no trade limits on accounts that
+ // - are mature
+ // - were signed by an arbitrator
+ private boolean hasTradeLimitException(AccountAgeWitness accountAgeWitness) {
+ return !isImmature(accountAgeWitness) || signedWitnessService.isSignedByArbitrator(accountAgeWitness);
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// My witness
@@ -264,44 +407,27 @@ public long getMyAccountAge(PaymentAccountPayload paymentAccountPayload) {
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
}
- public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode) {
+ public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction
+ direction) {
if (paymentAccount == null)
return 0;
- Optional witnessOptional = Optional.of(getMyWitness(paymentAccount.getPaymentAccountPayload()));
- return getTradeLimit(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode),
- currencyCode,
- witnessOptional,
- new Date());
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Peers witness
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- // Return -1 if witness data is not found (old versions)
- public long getMakersAccountAge(Offer offer, Date peersCurrentDate) {
- final Optional accountAgeWitnessHash = offer.getAccountAgeWitnessHashAsHex();
- final Optional witnessByHashAsHex = accountAgeWitnessHash.isPresent() ?
- getWitnessByHashAsHex(accountAgeWitnessHash.get()) :
- Optional.empty();
- return witnessByHashAsHex
- .map(accountAgeWitness -> getAccountAge(accountAgeWitness, peersCurrentDate))
- .orElse(-1L);
- }
-
- public long getTradingPeersAccountAge(Trade trade) {
- TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
- if (tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) {
- // unexpected
- return -1;
+ AccountAgeWitness accountAgeWitness = getMyWitness(paymentAccount.getPaymentAccountPayload());
+ Coin maxTradeLimit = paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode);
+ if (hasTradeLimitException(accountAgeWitness)) {
+ return maxTradeLimit.value;
}
+ final long accountSignAge = getWitnessSignAge(accountAgeWitness, new Date());
+ AccountAge accountAgeCategory = getAccountAgeCategory(accountSignAge);
- return getAccountAge(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing());
+ return getTradeLimit(maxTradeLimit,
+ currencyCode,
+ accountAgeWitness,
+ accountAgeCategory,
+ direction,
+ paymentAccount.getPaymentMethod());
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Verification
///////////////////////////////////////////////////////////////////////////////////////////
@@ -343,7 +469,8 @@ public boolean verifyAccountAgeWitness(Trade trade,
return false;
// Check if the peers trade limit is not less than the trade amount
- if (!verifyPeersTradeLimit(trade, peersWitness, peersCurrentDate, errorMessageHandler)) {
+ if (!verifyPeersTradeLimit(trade.getOffer(), trade.getTradeAmount(), peersWitness, peersCurrentDate,
+ errorMessageHandler)) {
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
return false;
}
@@ -351,12 +478,22 @@ public boolean verifyAccountAgeWitness(Trade trade,
return verifySignature(peersPubKeyRing.getSignaturePubKey(), nonce, signature, errorMessageHandler);
}
+ public boolean verifyPeersTradeAmount(Offer offer,
+ Coin tradeAmount,
+ ErrorMessageHandler errorMessageHandler) {
+ checkNotNull(offer);
+ return findWitness(offer)
+ .map(witness -> verifyPeersTradeLimit(offer, tradeAmount, witness, new Date(), errorMessageHandler))
+ .orElse(false);
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Package scope verification subroutines
///////////////////////////////////////////////////////////////////////////////////////////
- boolean isDateAfterReleaseDate(long witnessDateAsLong, Date ageWitnessReleaseDate, ErrorMessageHandler errorMessageHandler) {
+ boolean isDateAfterReleaseDate(long witnessDateAsLong,
+ Date ageWitnessReleaseDate,
+ ErrorMessageHandler errorMessageHandler) {
// Release date minus 1 day as tolerance for not synced clocks
Date releaseDateWithTolerance = new Date(ageWitnessReleaseDate.getTime() - TimeUnit.DAYS.toMillis(1));
final Date witnessDate = new Date(witnessDateAsLong);
@@ -394,16 +531,23 @@ private boolean verifyWitnessHash(byte[] witnessHash,
return result;
}
- private boolean verifyPeersTradeLimit(Trade trade,
+ private boolean verifyPeersTradeLimit(Offer offer,
+ Coin tradeAmount,
AccountAgeWitness peersWitness,
Date peersCurrentDate,
ErrorMessageHandler errorMessageHandler) {
- Offer offer = trade.getOffer();
- Coin tradeAmount = checkNotNull(trade.getTradeAmount());
checkNotNull(offer);
final String currencyCode = offer.getCurrencyCode();
final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById(offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode);
- long peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, Optional.of(peersWitness), peersCurrentDate);
+ long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
+ if (!hasTradeLimitException(peersWitness)) {
+ final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
+ AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
+ OfferPayload.Direction direction = offer.isMyOffer(keyRing) ?
+ offer.getMirroredDirection() : offer.getDirection();
+ peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
+ accountAgeCategory, direction, offer.getPaymentMethod());
+ }
// Makers current trade limit cannot be smaller than that in the offer
boolean result = tradeAmount.value <= peersCurrentTradeLimit;
if (!result) {
@@ -436,4 +580,139 @@ boolean verifySignature(PublicKey peersPublicKey,
}
return result;
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Witness signing
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public void arbitratorSignAccountAgeWitness(Coin tradeAmount,
+ AccountAgeWitness accountAgeWitness,
+ ECKey key,
+ PublicKey peersPubKey) {
+ signedWitnessService.signAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey);
+ }
+
+ public void traderSignPeersAccountAgeWitness(Trade trade) {
+ AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
+ Coin tradeAmount = trade.getTradeAmount();
+ checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
+ PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
+ checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", trade.toString());
+ checkNotNull(tradeAmount, "Trade amount must not be null");
+ checkNotNull(peersPubKey, "Peers pub key must not be null");
+
+ try {
+ signedWitnessService.signAccountAgeWitness(tradeAmount, peersWitness, peersPubKey);
+ } catch (CryptoException e) {
+ log.warn("Trader failed to sign witness, exception {}", e.toString());
+ }
+ }
+
+ // Arbitrator signing
+ public List getTraderPaymentAccounts(long safeDate, PaymentMethod paymentMethod,
+ List disputes) {
+ return disputes.stream()
+ .filter(dispute -> dispute.getContract().getPaymentMethodId().equals(paymentMethod.getId()))
+ .filter(this::isNotFiltered)
+ .filter(this::hasChargebackRisk)
+ .filter(this::isBuyerWinner)
+ .flatMap(this::getTraderData)
+ .filter(Objects::nonNull)
+ .filter(traderDataItem ->
+ !signedWitnessService.isSignedAccountAgeWitness(traderDataItem.getAccountAgeWitness()))
+ .filter(traderDataItem -> traderDataItem.getAccountAgeWitness().getDate() < safeDate)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ private boolean isNotFiltered(Dispute dispute) {
+ boolean isFiltered = filterManager.isNodeAddressBanned(dispute.getContract().getBuyerNodeAddress()) ||
+ filterManager.isNodeAddressBanned(dispute.getContract().getSellerNodeAddress()) ||
+ filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
+ filterManager.isPaymentMethodBanned(
+ PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
+ filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getBuyerPaymentAccountPayload(),
+ new PaymentAccountFilter[1]) ||
+ filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getSellerPaymentAccountPayload(),
+ new PaymentAccountFilter[1]);
+ return !isFiltered;
+ }
+
+ private boolean hasChargebackRisk(Dispute dispute) {
+ return chargeBackRisk.hasChargebackRisk(dispute.getContract().getPaymentMethodId(),
+ dispute.getContract().getOfferPayload().getCurrencyCode());
+ }
+
+ private boolean isBuyerWinner(Dispute dispute) {
+ if (!dispute.isClosed() || dispute.getDisputeResultProperty() == null)
+ return false;
+ return dispute.getDisputeResultProperty().get().getWinner() == DisputeResult.Winner.BUYER;
+ }
+
+ private Stream getTraderData(Dispute dispute) {
+ Coin tradeAmount = dispute.getContract().getTradeAmount();
+
+ PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
+ PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing();
+
+ PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
+ PaymentAccountPayload sellerPaymentAccountPaload = dispute.getContract().getSellerPaymentAccountPayload();
+
+ TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing)
+ .map(witness -> new TraderDataItem(
+ buyerPaymentAccountPaload,
+ witness,
+ tradeAmount,
+ sellerPubKeyRing.getSignaturePubKey()))
+ .orElse(null);
+ TraderDataItem sellerData = findWitness(sellerPaymentAccountPaload, sellerPubKeyRing)
+ .map(witness -> new TraderDataItem(
+ sellerPaymentAccountPaload,
+ witness,
+ tradeAmount,
+ buyerPubKeyRing.getSignaturePubKey()))
+ .orElse(null);
+ return Stream.of(buyerData, sellerData);
+ }
+
+ public boolean hasSignedWitness(Offer offer) {
+ return findWitness(offer)
+ .map(signedWitnessService::isSignedAccountAgeWitness)
+ .orElse(false);
+ }
+
+ public boolean peerHasSignedWitness(Trade trade) {
+ return findTradePeerWitness(trade)
+ .map(signedWitnessService::isSignedAccountAgeWitness)
+ .orElse(false);
+ }
+
+ public boolean accountIsSigner(AccountAgeWitness accountAgeWitness) {
+ return signedWitnessService.isSignerAccountAgeWitness(accountAgeWitness);
+ }
+
+ public SignState getSignState(Offer offer) {
+ return findWitness(offer)
+ .map(this::getSignState)
+ .orElse(SignState.UNSIGNED);
+ }
+
+ public SignState getSignState(AccountAgeWitness accountAgeWitness) {
+ if (signedWitnessService.isSignedByArbitrator(accountAgeWitness)) {
+ return SignState.ARBITRATOR;
+ } else {
+ final long accountSignAge = getWitnessSignAge(accountAgeWitness, new Date());
+ switch (getAccountAgeCategory(accountSignAge)) {
+ case TWO_MONTHS_OR_MORE:
+ return SignState.PEER_SIGNER;
+ case ONE_TO_TWO_MONTHS:
+ return SignState.PEER_LIMIT_LIFTED;
+ case LESS_ONE_MONTH:
+ return SignState.PEER_INITIAL;
+ case UNVERIFIED:
+ default:
+ return SignState.UNSIGNED;
+ }
+ }
+ }
}
diff --git a/core/src/main/java/bisq/core/app/BisqEnvironment.java b/core/src/main/java/bisq/core/app/BisqEnvironment.java
index fdc211ad583..23d93760f54 100644
--- a/core/src/main/java/bisq/core/app/BisqEnvironment.java
+++ b/core/src/main/java/bisq/core/app/BisqEnvironment.java
@@ -94,8 +94,6 @@ public static void setDefaultAppName(String defaultAppName) {
public static final String BISQ_COMMANDLINE_PROPERTY_SOURCE_NAME = "bisqCommandLineProperties";
public static final String BISQ_APP_DIR_PROPERTY_SOURCE_NAME = "bisqAppDirProperties";
public static final String BISQ_DEFAULT_PROPERTY_SOURCE_NAME = "bisqDefaultProperties";
- private static final String BISQ_HOME_DIR_PROPERTY_SOURCE_NAME = "bisqHomeDirProperties";
- private static final String BISQ_CLASSPATH_PROPERTY_SOURCE_NAME = "bisqClasspathProperties";
private static String staticAppDataDir;
@@ -208,155 +206,67 @@ public BisqEnvironment(OptionSet options) {
@SuppressWarnings("ConstantConditions")
public BisqEnvironment(PropertySource commandLineProperties) {
//CommonOptionKeys
- logLevel = commandLineProperties.containsProperty(CommonOptionKeys.LOG_LEVEL_KEY) ?
- (String) commandLineProperties.getProperty(CommonOptionKeys.LOG_LEVEL_KEY) :
- LOG_LEVEL_DEFAULT;
+ logLevel = getProperty(commandLineProperties, CommonOptionKeys.LOG_LEVEL_KEY, LOG_LEVEL_DEFAULT);
+ useDevMode = getProperty(commandLineProperties, CommonOptionKeys.USE_DEV_MODE, "");
//AppOptionKeys
- userDataDir = commandLineProperties.containsProperty(AppOptionKeys.USER_DATA_DIR_KEY) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.USER_DATA_DIR_KEY) :
- DEFAULT_USER_DATA_DIR;
-
- appName = commandLineProperties.containsProperty(AppOptionKeys.APP_NAME_KEY) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.APP_NAME_KEY) :
- DEFAULT_APP_NAME;
-
- appDataDir = commandLineProperties.containsProperty(AppOptionKeys.APP_DATA_DIR_KEY) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.APP_DATA_DIR_KEY) :
- appDataDir(userDataDir, appName);
+ userDataDir = getProperty(commandLineProperties, AppOptionKeys.USER_DATA_DIR_KEY, DEFAULT_USER_DATA_DIR);
+ appName = getProperty(commandLineProperties, AppOptionKeys.APP_NAME_KEY, DEFAULT_APP_NAME);
+ appDataDir = getProperty(commandLineProperties, AppOptionKeys.APP_DATA_DIR_KEY, appDataDir(userDataDir, appName));
staticAppDataDir = appDataDir;
- desktopWithHttpApi = commandLineProperties.containsProperty(AppOptionKeys.DESKTOP_WITH_HTTP_API) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.DESKTOP_WITH_HTTP_API) :
- "false";
- desktopWithGrpcApi = commandLineProperties.containsProperty(AppOptionKeys.DESKTOP_WITH_GRPC_API) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.DESKTOP_WITH_GRPC_API) :
- "false";
- ignoreDevMsg = commandLineProperties.containsProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY) :
- "";
- useDevPrivilegeKeys = commandLineProperties.containsProperty(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) :
- "";
- referralId = commandLineProperties.containsProperty(AppOptionKeys.REFERRAL_ID) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.REFERRAL_ID) :
- "";
- useDevMode = commandLineProperties.containsProperty(CommonOptionKeys.USE_DEV_MODE) ?
- (String) commandLineProperties.getProperty(CommonOptionKeys.USE_DEV_MODE) :
- "";
- dumpStatistics = commandLineProperties.containsProperty(AppOptionKeys.DUMP_STATISTICS) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.DUMP_STATISTICS) :
- "";
- maxMemory = commandLineProperties.containsProperty(AppOptionKeys.MAX_MEMORY) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.MAX_MEMORY) :
- "";
- providers = commandLineProperties.containsProperty(AppOptionKeys.PROVIDERS) ?
- (String) commandLineProperties.getProperty(AppOptionKeys.PROVIDERS) :
- "";
+ desktopWithHttpApi = getProperty(commandLineProperties, AppOptionKeys.DESKTOP_WITH_HTTP_API, "false");
+ desktopWithGrpcApi = getProperty(commandLineProperties, AppOptionKeys.DESKTOP_WITH_GRPC_API, "false");
+ ignoreDevMsg = getProperty(commandLineProperties, AppOptionKeys.IGNORE_DEV_MSG_KEY, "");
+ useDevPrivilegeKeys = getProperty(commandLineProperties, AppOptionKeys.USE_DEV_PRIVILEGE_KEYS, "");
+ referralId = getProperty(commandLineProperties, AppOptionKeys.REFERRAL_ID, "");
+ dumpStatistics = getProperty(commandLineProperties, AppOptionKeys.DUMP_STATISTICS, "");
+ maxMemory = getProperty(commandLineProperties, AppOptionKeys.MAX_MEMORY, "");
+ providers = getProperty(commandLineProperties, AppOptionKeys.PROVIDERS, "");
//NetworkOptionKeys
- seedNodes = commandLineProperties.containsProperty(NetworkOptionKeys.SEED_NODES_KEY) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.SEED_NODES_KEY) :
- "";
-
- banList = commandLineProperties.containsProperty(NetworkOptionKeys.BAN_LIST) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.BAN_LIST) :
- "";
- socks5ProxyBtcAddress = commandLineProperties.containsProperty(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS) :
- "";
- socks5ProxyHttpAddress = commandLineProperties.containsProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS) :
- "";
- torRcFile = commandLineProperties.containsProperty(NetworkOptionKeys.TORRC_FILE) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.TORRC_FILE) :
- "";
- torRcOptions = commandLineProperties.containsProperty(NetworkOptionKeys.TORRC_OPTIONS) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.TORRC_OPTIONS) :
- "";
- externalTorControlPort = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT) :
- "";
- externalTorPassword = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD) :
- "";
- externalTorCookieFile = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE) :
- "";
+ seedNodes = getProperty(commandLineProperties, NetworkOptionKeys.SEED_NODES_KEY, "");
+ banList = getProperty(commandLineProperties, NetworkOptionKeys.BAN_LIST, "");
+ socks5ProxyBtcAddress = getProperty(commandLineProperties, NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS, "");
+ socks5ProxyHttpAddress = getProperty(commandLineProperties, NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS, "");
+ torRcFile = getProperty(commandLineProperties, NetworkOptionKeys.TORRC_FILE, "");
+ torRcOptions = getProperty(commandLineProperties, NetworkOptionKeys.TORRC_OPTIONS, "");
+ externalTorControlPort = getProperty(commandLineProperties, NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT, "");
+ externalTorPassword = getProperty(commandLineProperties, NetworkOptionKeys.EXTERNAL_TOR_PASSWORD, "");
+ externalTorCookieFile = getProperty(commandLineProperties, NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE, "");
externalTorUseSafeCookieAuthentication = commandLineProperties.containsProperty(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE);
torStreamIsolation = commandLineProperties.containsProperty(NetworkOptionKeys.TOR_STREAM_ISOLATION);
- msgThrottlePerSec = commandLineProperties.containsProperty(NetworkOptionKeys.MSG_THROTTLE_PER_SEC) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.MSG_THROTTLE_PER_SEC) :
- String.valueOf(ConnectionConfig.MSG_THROTTLE_PER_SEC);
- msgThrottlePer10Sec = commandLineProperties.containsProperty(NetworkOptionKeys.MSG_THROTTLE_PER_10_SEC) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.MSG_THROTTLE_PER_10_SEC) :
- String.valueOf(ConnectionConfig.MSG_THROTTLE_PER_10_SEC);
- sendMsgThrottleTrigger = commandLineProperties.containsProperty(NetworkOptionKeys.SEND_MSG_THROTTLE_TRIGGER) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.SEND_MSG_THROTTLE_TRIGGER) :
- String.valueOf(ConnectionConfig.SEND_MSG_THROTTLE_TRIGGER);
- sendMsgThrottleSleep = commandLineProperties.containsProperty(NetworkOptionKeys.SEND_MSG_THROTTLE_SLEEP) ?
- (String) commandLineProperties.getProperty(NetworkOptionKeys.SEND_MSG_THROTTLE_SLEEP) :
- String.valueOf(ConnectionConfig.SEND_MSG_THROTTLE_SLEEP);
-
- //RpcOptionKeys
- rpcUser = commandLineProperties.containsProperty(DaoOptionKeys.RPC_USER) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.RPC_USER) :
- "";
- rpcPassword = commandLineProperties.containsProperty(DaoOptionKeys.RPC_PASSWORD) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.RPC_PASSWORD) :
- "";
- rpcHost = commandLineProperties.containsProperty(DaoOptionKeys.RPC_HOST) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.RPC_HOST) :
- "";
- rpcPort = commandLineProperties.containsProperty(DaoOptionKeys.RPC_PORT) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.RPC_PORT) :
- "";
- rpcBlockNotificationPort = commandLineProperties.containsProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT) :
- "";
- rpcBlockNotificationHost = commandLineProperties.containsProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_HOST) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_HOST) :
- "";
- dumpBlockchainData = commandLineProperties.containsProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) :
- "";
- fullDaoNode = commandLineProperties.containsProperty(DaoOptionKeys.FULL_DAO_NODE) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.FULL_DAO_NODE) :
- "";
- genesisTxId = commandLineProperties.containsProperty(DaoOptionKeys.GENESIS_TX_ID) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.GENESIS_TX_ID) :
- "";
- genesisBlockHeight = commandLineProperties.containsProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) :
- "-1";
- genesisTotalSupply = commandLineProperties.containsProperty(DaoOptionKeys.GENESIS_TOTAL_SUPPLY) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.GENESIS_TOTAL_SUPPLY) :
- "-1";
- daoActivated = commandLineProperties.containsProperty(DaoOptionKeys.DAO_ACTIVATED) ?
- (String) commandLineProperties.getProperty(DaoOptionKeys.DAO_ACTIVATED) :
- "true";
-
- btcNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_NODES) ?
- (String) commandLineProperties.getProperty(BtcOptionKeys.BTC_NODES) :
- "";
-
- useTorForBtc = commandLineProperties.containsProperty(BtcOptionKeys.USE_TOR_FOR_BTC) ?
- (String) commandLineProperties.getProperty(BtcOptionKeys.USE_TOR_FOR_BTC) :
- "";
- userAgent = commandLineProperties.containsProperty(BtcOptionKeys.USER_AGENT) ?
- (String) commandLineProperties.getProperty(BtcOptionKeys.USER_AGENT) :
- "Bisq";
- useAllProvidedNodes = commandLineProperties.containsProperty(BtcOptionKeys.USE_ALL_PROVIDED_NODES) ?
- (String) commandLineProperties.getProperty(BtcOptionKeys.USE_ALL_PROVIDED_NODES) :
- "false";
- numConnectionForBtc = commandLineProperties.containsProperty(BtcOptionKeys.NUM_CONNECTIONS_FOR_BTC) ?
- (String) commandLineProperties.getProperty(BtcOptionKeys.NUM_CONNECTIONS_FOR_BTC) :
- "9";
- ignoreLocalBtcNode = commandLineProperties.containsProperty(BtcOptionKeys.IGNORE_LOCAL_BTC_NODE) ?
- (String) commandLineProperties.getProperty(BtcOptionKeys.IGNORE_LOCAL_BTC_NODE) :
- "false";
-
- MutablePropertySources propertySources = this.getPropertySources();
+
+ msgThrottlePerSec = getProperty(commandLineProperties, NetworkOptionKeys.MSG_THROTTLE_PER_SEC, String.valueOf(ConnectionConfig.MSG_THROTTLE_PER_SEC));
+ msgThrottlePer10Sec = getProperty(commandLineProperties, NetworkOptionKeys.MSG_THROTTLE_PER_10_SEC, String.valueOf(ConnectionConfig.MSG_THROTTLE_PER_10_SEC));
+ sendMsgThrottleTrigger = getProperty(commandLineProperties, NetworkOptionKeys.SEND_MSG_THROTTLE_TRIGGER, String.valueOf(ConnectionConfig.SEND_MSG_THROTTLE_TRIGGER));
+ sendMsgThrottleSleep = getProperty(commandLineProperties, NetworkOptionKeys.SEND_MSG_THROTTLE_SLEEP, String.valueOf(ConnectionConfig.SEND_MSG_THROTTLE_SLEEP));
+
+
+ //DaoOptionKeys
+ rpcUser = getProperty(commandLineProperties, DaoOptionKeys.RPC_USER, "");
+ rpcPassword = getProperty(commandLineProperties, DaoOptionKeys.RPC_PASSWORD, "");
+ rpcHost = getProperty(commandLineProperties, DaoOptionKeys.RPC_HOST, "");
+ rpcPort = getProperty(commandLineProperties, DaoOptionKeys.RPC_PORT, "");
+ rpcBlockNotificationPort = getProperty(commandLineProperties, DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT, "");
+ rpcBlockNotificationHost = getProperty(commandLineProperties, DaoOptionKeys.RPC_BLOCK_NOTIFICATION_HOST, "");
+ dumpBlockchainData = getProperty(commandLineProperties, DaoOptionKeys.DUMP_BLOCKCHAIN_DATA, "");
+ fullDaoNode = getProperty(commandLineProperties, DaoOptionKeys.FULL_DAO_NODE, "");
+ genesisTxId = getProperty(commandLineProperties, DaoOptionKeys.GENESIS_TX_ID, "");
+ genesisBlockHeight = getProperty(commandLineProperties, DaoOptionKeys.GENESIS_BLOCK_HEIGHT, "-1");
+ genesisTotalSupply = getProperty(commandLineProperties, DaoOptionKeys.GENESIS_TOTAL_SUPPLY, "-1");
+ daoActivated = getProperty(commandLineProperties, DaoOptionKeys.DAO_ACTIVATED, "true");
+
+ //BtcOptionKeys
+ btcNodes = getProperty(commandLineProperties, BtcOptionKeys.BTC_NODES, "");
+ useTorForBtc = getProperty(commandLineProperties, BtcOptionKeys.USE_TOR_FOR_BTC, "");
+ userAgent = getProperty(commandLineProperties, BtcOptionKeys.USER_AGENT, "Bisq");
+ useAllProvidedNodes = getProperty(commandLineProperties, BtcOptionKeys.USE_ALL_PROVIDED_NODES, "false");
+ numConnectionForBtc = getProperty(commandLineProperties, BtcOptionKeys.NUM_CONNECTIONS_FOR_BTC, "9");
+ ignoreLocalBtcNode = getProperty(commandLineProperties, BtcOptionKeys.IGNORE_LOCAL_BTC_NODE, "false");
+
+
+ MutablePropertySources propertySources = getPropertySources();
propertySources.addFirst(commandLineProperties);
try {
propertySources.addLast(getAppDirProperties());
@@ -460,12 +370,8 @@ PropertySource> getAppDirProperties() throws Exception {
return new ResourcePropertySource(BISQ_APP_DIR_PROPERTY_SOURCE_NAME, resource);
}
- private PropertySource> homeDirProperties() {
- return new PropertySource.StubPropertySource(BISQ_HOME_DIR_PROPERTY_SOURCE_NAME);
- }
-
- private PropertySource> classpathProperties() {
- return new PropertySource.StubPropertySource(BISQ_CLASSPATH_PROPERTY_SOURCE_NAME);
+ private String getProperty (PropertySource properties, String propertyKey, String defaultValue) {
+ return properties.containsProperty(propertyKey) ? (String) properties.getProperty(propertyKey) : defaultValue;
}
private PropertySource> defaultProperties() {
diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java
index f5935a54067..337dc8a885d 100644
--- a/core/src/main/java/bisq/core/app/BisqExecutable.java
+++ b/core/src/main/java/bisq/core/app/BisqExecutable.java
@@ -72,7 +72,7 @@
import static java.lang.String.format;
@Slf4j
-public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSetup.BisqSetupCompleteListener {
+public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSetup.BisqSetupListener {
private final String fullName;
private final String scriptName;
@@ -261,7 +261,7 @@ protected void onApplicationStarted() {
protected void startAppSetup() {
BisqSetup bisqSetup = injector.getInstance(BisqSetup.class);
- bisqSetup.addBisqSetupCompleteListener(this);
+ bisqSetup.addBisqSetupListener(this);
bisqSetup.start();
}
diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java
index f7ece906fb7..c77acd6d7c5 100644
--- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java
+++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java
@@ -43,7 +43,7 @@ public class BisqHeadlessApp implements HeadlessApp {
@Setter
private GracefulShutDownHandler gracefulShutDownHandler;
private boolean shutDownRequested;
- private BisqSetup bisqSetup;
+ protected BisqSetup bisqSetup;
private CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler;
private TradeManager tradeManager;
@@ -54,7 +54,7 @@ public BisqHeadlessApp() {
public void startApplication() {
try {
bisqSetup = injector.getInstance(BisqSetup.class);
- bisqSetup.addBisqSetupCompleteListener(this);
+ bisqSetup.addBisqSetupListener(this);
corruptedDatabaseFilesHandler = injector.getInstance(CorruptedDatabaseFilesHandler.class);
tradeManager = injector.getInstance(TradeManager.class);
diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java
index 18763753fcf..6f3222b0ef5 100644
--- a/core/src/main/java/bisq/core/app/BisqSetup.java
+++ b/core/src/main/java/bisq/core/app/BisqSetup.java
@@ -17,6 +17,7 @@
package bisq.core.app;
+import bisq.core.account.sign.SignedWitness;
import bisq.core.account.sign.SignedWitnessService;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.alert.Alert;
@@ -43,12 +44,15 @@
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.TradeLimits;
+import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.RefundManager;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.support.traderchat.TraderChatManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.statistics.AssetTradeActivityCheck;
@@ -61,6 +65,7 @@
import bisq.network.crypto.EncryptionService;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.peers.keepalive.messages.Ping;
+import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
import bisq.common.ClockWatcher;
import bisq.common.Timer;
@@ -100,6 +105,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@@ -116,7 +122,19 @@
@Slf4j
@Singleton
public class BisqSetup {
- public interface BisqSetupCompleteListener {
+ public interface BisqSetupListener {
+ default void onInitP2pNetwork() {
+ log.info("onInitP2pNetwork");
+ }
+
+ default void onInitWallet() {
+ log.info("onInitWallet");
+ }
+
+ default void onRequestWalletPassword() {
+ log.info("onRequestWalletPassword");
+ }
+
void onSetupComplete();
}
@@ -131,11 +149,13 @@ public interface BisqSetupCompleteListener {
private final PriceFeedService priceFeedService;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
+ private final RefundAgentManager refundAgentManager;
private final P2PService p2PService;
private final TradeManager tradeManager;
private final OpenOfferManager openOfferManager;
private final ArbitrationManager arbitrationManager;
private final MediationManager mediationManager;
+ private final RefundManager refundManager;
private final TraderChatManager traderChatManager;
private final Preferences preferences;
private final User user;
@@ -171,7 +191,8 @@ public interface BisqSetupCompleteListener {
private Consumer cryptoSetupFailedHandler, chainFileLockedExceptionHandler,
spvFileCorruptedHandler, lockedUpFundsHandler, daoErrorMessageHandler, daoWarnMessageHandler,
filterWarningHandler, displaySecurityRecommendationHandler, displayLocalhostHandler,
- wrongOSArchitectureHandler;
+ wrongOSArchitectureHandler, displaySignedByArbitratorHandler,
+ displaySignedByPeerHandler, displayPeerLimitLiftedHandler, displayPeerSignerHandler;
@Setter
@Nullable
private Consumer displayTorNetworkSettingsHandler;
@@ -201,7 +222,7 @@ public interface BisqSetupCompleteListener {
private boolean allBasicServicesInitialized;
@SuppressWarnings("FieldCanBeLocal")
private MonadicBinding p2pNetworkAndWalletInitialized;
- private List bisqSetupCompleteListeners = new ArrayList<>();
+ private List bisqSetupListeners = new ArrayList<>();
@Inject
public BisqSetup(P2PNetworkSetup p2PNetworkSetup,
@@ -213,11 +234,13 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup,
PriceFeedService priceFeedService,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
P2PService p2PService,
TradeManager tradeManager,
OpenOfferManager openOfferManager,
ArbitrationManager arbitrationManager,
MediationManager mediationManager,
+ RefundManager refundManager,
TraderChatManager traderChatManager,
Preferences preferences,
User user,
@@ -257,11 +280,13 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup,
this.priceFeedService = priceFeedService;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
this.p2PService = p2PService;
this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager;
this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
+ this.refundManager = refundManager;
this.traderChatManager = traderChatManager;
this.preferences = preferences;
this.user = user;
@@ -296,16 +321,13 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup,
// Setup
///////////////////////////////////////////////////////////////////////////////////////////
- public void addBisqSetupCompleteListener(BisqSetupCompleteListener listener) {
- bisqSetupCompleteListeners.add(listener);
+ public void addBisqSetupListener(BisqSetupListener listener) {
+ bisqSetupListeners.add(listener);
}
public void start() {
- if (log.isDebugEnabled()) {
- UserThread.runPeriodically(() -> {
- log.debug("1 second heartbeat");
- }, 1);
- }
+ UserThread.runPeriodically(() -> {
+ }, 1);
maybeReSyncSPVChain();
maybeShowTac();
}
@@ -328,12 +350,13 @@ private void step4() {
private void step5() {
initDomainServices();
- bisqSetupCompleteListeners.forEach(BisqSetupCompleteListener::onSetupComplete);
+ bisqSetupListeners.forEach(BisqSetupListener::onSetupComplete);
// We set that after calling the setupCompleteHandler to not trigger a popup from the dev dummy accounts
// in MainViewModel
maybeShowSecurityRecommendation();
maybeShowLocalhostRunningInfo();
+ maybeShowAccountSigningStateInfo();
}
@@ -428,10 +451,10 @@ private void maybeReSyncSPVChain() {
}
private void maybeShowTac() {
- if (!preferences.isTacAccepted() && !DevEnv.isDevMode()) {
+ if (!preferences.isTacAcceptedV120() && !DevEnv.isDevMode()) {
if (displayTacHandler != null)
displayTacHandler.accept(() -> {
- preferences.setTacAccepted(true);
+ preferences.setTacAcceptedV120(true);
step2();
});
} else {
@@ -524,6 +547,7 @@ else if (displayTorNetworkSettingsHandler != null)
}, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES);
+ bisqSetupListeners.forEach(BisqSetupListener::onInitP2pNetwork);
p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler);
// We only init wallet service here if not using Tor for bitcoinj.
@@ -550,7 +574,10 @@ else if (displayTorNetworkSettingsHandler != null)
}
private void initWallet() {
+ bisqSetupListeners.forEach(BisqSetupListener::onInitWallet);
Runnable walletPasswordHandler = () -> {
+ log.info("Wallet password required");
+ bisqSetupListeners.forEach(BisqSetupListener::onRequestWalletPassword);
if (p2pNetworkReady.get())
p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true);
@@ -561,6 +588,9 @@ private void initWallet() {
if (showFirstPopupIfResyncSPVRequestedHandler != null)
showFirstPopupIfResyncSPVRequestedHandler.run();
} else {
+ // TODO no guarantee here that the wallet is really fully initialized
+ // We would need a new walletInitializedButNotEncrypted state to track
+ // Usually init is fast and we have our wallet initialized at that state though.
walletInitialized.set(true);
}
});
@@ -574,9 +604,7 @@ private void initWallet() {
if (allBasicServicesInitialized)
checkForLockedUpFunds();
},
- () -> {
- walletInitialized.set(true);
- });
+ () -> walletInitialized.set(true));
}
@@ -618,6 +646,7 @@ private void initDomainServices() {
arbitrationManager.onAllServicesInitialized();
mediationManager.onAllServicesInitialized();
+ refundManager.onAllServicesInitialized();
traderChatManager.onAllServicesInitialized();
tradeManager.onAllServicesInitialized();
@@ -631,6 +660,7 @@ private void initDomainServices() {
arbitratorManager.onAllServicesInitialized();
mediatorManager.onAllServicesInitialized();
+ refundAgentManager.onAllServicesInitialized();
alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) ->
displayAlertIfPresent(newValue, false));
@@ -696,9 +726,7 @@ private void initDomainServices() {
voteResultService.getVoteResultExceptions().addListener((ListChangeListener) c -> {
c.next();
if (c.wasAdded() && voteResultExceptionHandler != null) {
- c.getAddedSubList().forEach(e -> {
- voteResultExceptionHandler.accept(e);
- });
+ c.getAddedSubList().forEach(e -> voteResultExceptionHandler.accept(e));
}
});
@@ -722,9 +750,62 @@ private void maybeShowSecurityRecommendation() {
}
private void maybeShowLocalhostRunningInfo() {
- String key = "bitcoinLocalhostNode";
- if (bisqEnvironment.isBitcoinLocalhostNodeRunning() && preferences.showAgain(key) &&
- displayLocalhostHandler != null)
- displayLocalhostHandler.accept(key);
+ maybeTriggerDisplayHandler("bitcoinLocalhostNode", displayLocalhostHandler, bisqEnvironment.isBitcoinLocalhostNodeRunning());
+ }
+
+ private void maybeShowAccountSigningStateInfo() {
+ String keySignedByArbitrator = "accountSignedByArbitrator";
+ String keySignedByPeer = "accountSignedByPeer";
+ String keyPeerLimitedLifted = "accountLimitLifted";
+ String keyPeerSigner = "accountPeerSigner";
+
+ // check signed witness on startup
+ checkSigningState(AccountAgeWitnessService.SignState.ARBITRATOR, keySignedByArbitrator, displaySignedByArbitratorHandler);
+ checkSigningState(AccountAgeWitnessService.SignState.PEER_INITIAL, keySignedByPeer, displaySignedByPeerHandler);
+ checkSigningState(AccountAgeWitnessService.SignState.PEER_LIMIT_LIFTED, keyPeerLimitedLifted, displayPeerLimitLiftedHandler);
+ checkSigningState(AccountAgeWitnessService.SignState.PEER_SIGNER, keyPeerSigner, displayPeerSignerHandler);
+
+ // check signed witness during runtime
+ p2PService.getP2PDataStorage().addAppendOnlyDataStoreListener(
+ payload -> {
+ maybeTriggerDisplayHandler(keySignedByArbitrator, displaySignedByArbitratorHandler,
+ isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.ARBITRATOR));
+ maybeTriggerDisplayHandler(keySignedByPeer, displaySignedByPeerHandler,
+ isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.PEER_INITIAL));
+ maybeTriggerDisplayHandler(keyPeerLimitedLifted, displayPeerLimitLiftedHandler,
+ isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.PEER_LIMIT_LIFTED));
+ maybeTriggerDisplayHandler(keyPeerSigner, displayPeerSignerHandler,
+ isSignedWitnessOfMineWithState(payload, AccountAgeWitnessService.SignState.PEER_SIGNER));
+ });
+ }
+
+ private void checkSigningState(AccountAgeWitnessService.SignState state,
+ String key, Consumer displayHandler) {
+ boolean signingStateFound = p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().stream()
+ .anyMatch(payload -> isSignedWitnessOfMineWithState(payload, state));
+
+ maybeTriggerDisplayHandler(key, displayHandler, signingStateFound);
+ }
+
+ private boolean isSignedWitnessOfMineWithState(PersistableNetworkPayload payload,
+ AccountAgeWitnessService.SignState state) {
+ if (payload instanceof SignedWitness && user.getPaymentAccounts() != null) {
+ // We know at this point that it is already added to the signed witness list
+ // Check if new signed witness is for one of my own accounts
+ return user.getPaymentAccounts().stream()
+ .filter(a -> PaymentMethod.hasChargebackRisk(a.getPaymentMethod(), a.getTradeCurrencies()))
+ .filter(a -> Arrays.equals(((SignedWitness) payload).getAccountAgeWitnessHash(),
+ accountAgeWitnessService.getMyWitness(a.getPaymentAccountPayload()).getHash()))
+ .anyMatch(a -> accountAgeWitnessService.getSignState(accountAgeWitnessService.getMyWitness(
+ a.getPaymentAccountPayload())).equals(state));
+ }
+ return false;
+ }
+
+ private void maybeTriggerDisplayHandler(String key, Consumer displayHandler, boolean signingStateFound) {
+ if (signingStateFound && preferences.showAgain(key) &&
+ displayHandler != null) {
+ displayHandler.accept(key);
+ }
}
}
diff --git a/core/src/main/java/bisq/core/app/HeadlessApp.java b/core/src/main/java/bisq/core/app/HeadlessApp.java
index 8d105a93398..3808b89b1a0 100644
--- a/core/src/main/java/bisq/core/app/HeadlessApp.java
+++ b/core/src/main/java/bisq/core/app/HeadlessApp.java
@@ -22,7 +22,7 @@
import com.google.inject.Injector;
-public interface HeadlessApp extends UncaughtExceptionHandler, BisqSetup.BisqSetupCompleteListener {
+public interface HeadlessApp extends UncaughtExceptionHandler, BisqSetup.BisqSetupListener {
void setGracefulShutDownHandler(GracefulShutDownHandler gracefulShutDownHandler);
void setInjector(Injector injector);
diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java
index 5959b7ee894..783440b1b76 100644
--- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java
+++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java
@@ -41,14 +41,18 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class ExecutableForAppWithP2p extends BisqExecutable implements UncaughtExceptionHandler {
- private static final long MAX_MEMORY_MB_DEFAULT = 500;
+ private static final long MAX_MEMORY_MB_DEFAULT = 1200;
private static final long CHECK_MEMORY_PERIOD_SEC = 300;
+ private static final long CHECK_SHUTDOWN_SEC = TimeUnit.HOURS.toSeconds(1);
+ private static final long SHUTDOWN_INTERVAL = TimeUnit.HOURS.toMillis(24);
private volatile boolean stopped;
+ private final long startTime = System.currentTimeMillis();
private static long maxMemory = MAX_MEMORY_MB_DEFAULT;
public ExecutableForAppWithP2p(String fullName, String scriptName, String version) {
@@ -120,6 +124,20 @@ protected void keepRunning() {
}
}
+ protected void startShutDownInterval(GracefulShutDownHandler gracefulShutDownHandler) {
+ UserThread.runPeriodically(() -> {
+ if (System.currentTimeMillis() - startTime > SHUTDOWN_INTERVAL) {
+ log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
+ "Shut down as node was running longer as {} hours" +
+ "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n",
+ SHUTDOWN_INTERVAL / 3600000);
+
+ shutDown(gracefulShutDownHandler);
+ }
+
+ }, CHECK_SHUTDOWN_SEC);
+ }
+
protected void checkMemory(BisqEnvironment environment, GracefulShutDownHandler gracefulShutDownHandler) {
String maxMemoryOption = environment.getProperty(AppOptionKeys.MAX_MEMORY);
if (maxMemoryOption != null && !maxMemoryOption.isEmpty()) {
@@ -153,16 +171,24 @@ protected void checkMemory(BisqEnvironment environment, GracefulShutDownHandler
long usedMemory = Profiler.getUsedMemoryInMB();
if (usedMemory > maxMemory) {
log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
- "We are over our memory limit ({}) and trigger a restart. usedMemory: {} MB. freeMemory: {} MB" +
+ "We are over our memory limit ({}) and trigger a shutdown. usedMemory: {} MB. freeMemory: {} MB" +
"\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n",
(int) maxMemory, usedMemory, Profiler.getFreeMemoryInMB());
- restart(environment, gracefulShutDownHandler);
+ shutDown(gracefulShutDownHandler);
}
}, 5);
}
}, CHECK_MEMORY_PERIOD_SEC);
}
+ protected void shutDown(GracefulShutDownHandler gracefulShutDownHandler) {
+ stopped = true;
+ gracefulShutDownHandler.gracefulShutDown(() -> {
+ log.info("Shutdown complete");
+ System.exit(1);
+ });
+ }
+
protected void restart(BisqEnvironment bisqEnvironment, GracefulShutDownHandler gracefulShutDownHandler) {
stopped = true;
gracefulShutDownHandler.gracefulShutDown(() -> {
@@ -180,5 +206,4 @@ protected void restart(BisqEnvironment bisqEnvironment, GracefulShutDownHandler
}
});
}
-
}
diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java
index 984211c451e..cf85da0b98d 100644
--- a/core/src/main/java/bisq/core/btc/Balances.java
+++ b/core/src/main/java/bisq/core/btc/Balances.java
@@ -22,6 +22,8 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
@@ -52,6 +54,7 @@ public class Balances {
private final OpenOfferManager openOfferManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
+ private final RefundManager refundManager;
@Getter
private final ObjectProperty availableBalance = new SimpleObjectProperty<>();
@@ -65,17 +68,20 @@ public Balances(TradeManager tradeManager,
BtcWalletService btcWalletService,
OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager,
- FailedTradesManager failedTradesManager) {
+ FailedTradesManager failedTradesManager,
+ RefundManager refundManager) {
this.tradeManager = tradeManager;
this.btcWalletService = btcWalletService;
this.openOfferManager = openOfferManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
+ this.refundManager = refundManager;
}
public void onAllServicesInitialized() {
openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalance());
tradeManager.getTradableList().addListener((ListChangeListener) change -> updateBalance());
+ refundManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> updateBalance());
btcWalletService.addBalanceListener(new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
diff --git a/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java b/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java
index c4468563299..8981f41426d 100644
--- a/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java
+++ b/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java
@@ -73,6 +73,6 @@ public boolean isRegtest() {
}
public long getDefaultMinFeePerByte() {
- return 1;
+ return 2;
}
}
diff --git a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java
index 9a2e1c76ba5..c0fbfb5b682 100644
--- a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java
+++ b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java
@@ -126,6 +126,25 @@ private Tuple2 getEstimatedFeeAndTxSize(boolean isTaker,
return new Tuple2<>(txFee, size);
}
+ public Tuple2 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;
+ try {
+ estimatedTxSize = getEstimatedTxSize(List.of(amount), estimatedTxSize, txFeePerByte, 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);
+ }
+
+ 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);
+
+ return new Tuple2<>(txFee, estimatedTxSize);
+ }
+
// We start with the initialEstimatedTxSize for a tx with 1 input (260) bytes 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
diff --git a/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java b/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java
index e7dbcba61be..a394892aba2 100644
--- a/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java
+++ b/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java
@@ -47,10 +47,6 @@ public enum BitcoinNodesOption {
public List getProvidedBtcNodes() {
return useProvidedBtcNodes() ?
Arrays.asList(
- // ManfredKarrer
- new BtcNode("btc1.0-2-1.net", "r3dsojfhwcm7x7p6.onion", "159.89.16.222", BtcNode.DEFAULT_PORT, "@manfredkarrer"),
- new BtcNode("btc2.0-2-1.net", "vlf5i3grro3wux24.onion", "165.227.34.56", BtcNode.DEFAULT_PORT, "@manfredkarrer"),
-
// emzy
new BtcNode("kirsche.emzy.de", "fz6nsij6jiyuwlsc.onion", "78.47.61.83", BtcNode.DEFAULT_PORT, "@emzy"),
new BtcNode("node2.emzy.de", "c6ac4jdfyeiakex2.onion", "62.75.210.81", BtcNode.DEFAULT_PORT, "@emzy"),
@@ -68,8 +64,8 @@ public List getProvidedBtcNodes() {
new BtcNode("btc1.sqrrm.net", "3r44ddzjitznyahw.onion", "185.25.48.184", BtcNode.DEFAULT_PORT, "@sqrrm"),
new BtcNode("btc2.sqrrm.net", "i3a5xtzfm4xwtybd.onion", "81.171.22.143", BtcNode.DEFAULT_PORT, "@sqrrm"),
- // KanoczTomas
- new BtcNode("btc.ispol.sk", "mbm6ffx6j5ygi2ck.onion", "193.58.196.212", BtcNode.DEFAULT_PORT, "@KanoczTomas"),
+ // KanoczTomas - temp disabled until he returns
+ // new BtcNode("btc.ispol.sk", "mbm6ffx6j5ygi2ck.onion", "193.58.196.212", BtcNode.DEFAULT_PORT, "@KanoczTomas"),
// Devin Bileck
new BtcNode("btc1.dnsalias.net", "lva54pnbq2nsmjyr.onion", "165.227.34.198", BtcNode.DEFAULT_PORT, "@devinbileck"),
diff --git a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
index 14dd75236ac..773fe88d72b 100644
--- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
+++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
@@ -122,6 +122,8 @@ public class WalletsSetup {
private final File walletDir;
private final int socks5DiscoverMode;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
+ private final IntegerProperty chainHeight = new SimpleIntegerProperty(0);
+ private final ObjectProperty blocksDownloadedFromPeer = new SimpleObjectProperty<>();
private final ObjectProperty> connectedPeers = new SimpleObjectProperty<>();
private final DownloadListener downloadListener = new DownloadListener();
private final List setupCompletedHandlers = new ArrayList<>();
@@ -200,6 +202,7 @@ protected void onSetupCompleted() {
super.onSetupCompleted();
final PeerGroup peerGroup = walletConfig.peerGroup();
+ final BlockChain chain = walletConfig.chain();
// We don't want to get our node white list polluted with nodes from AddressMessage calls.
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
@@ -215,6 +218,13 @@ protected void onSetupCompleted() {
numPeers.set(peerCount);
connectedPeers.set(peerGroup.getConnectedPeers());
});
+ peerGroup.addBlocksDownloadedEventListener((peer, block, filteredBlock, blocksLeft) -> {
+ blocksDownloadedFromPeer.set(peer);
+ });
+ chain.addNewBestBlockListener(block -> {
+ connectedPeers.set(peerGroup.getConnectedPeers());
+ chainHeight.set(block.getHeight());
+ });
// Map to user thread
UserThread.execute(() -> {
@@ -429,6 +439,14 @@ public ReadOnlyObjectProperty> connectedPeersProperty() {
return connectedPeers;
}
+ public ReadOnlyIntegerProperty chainHeightProperty() {
+ return chainHeight;
+ }
+
+ public ReadOnlyObjectProperty blocksDownloadedFromPeerProperty() {
+ return blocksDownloadedFromPeer;
+ }
+
public ReadOnlyDoubleProperty downloadPercentageProperty() {
return downloadListener.percentageProperty();
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java
index 300771460f4..915e00ca235 100644
--- a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java
@@ -493,6 +493,15 @@ public Transaction signTx(Transaction tx) throws WalletException, TransactionVer
}
}
+ for (TransactionOutput txo : tx.getOutputs()) {
+ Coin value = txo.getValue();
+ // OpReturn outputs have value 0
+ if (value.isPositive()) {
+ checkArgument(Restrictions.isAboveDust(txo.getValue()),
+ "An output value is below dust limit. Transaction=" + tx);
+ }
+ }
+
checkWalletConsistency(wallet);
verifyTransaction(tx);
printTx("BSQ wallet: Signed Tx", tx);
@@ -556,11 +565,11 @@ private Transaction getPreparedSendTx(String receiverAddress, Coin receiverAmoun
// Tx has as first output BSQ and an optional second BSQ change output.
// At that stage we do not have added the BTC inputs so there is no BTC change output here.
if (tx.getOutputs().size() == 2) {
- TransactionOutput bsqChangeOutput = tx.getOutputs().get(1);
- if (!Restrictions.isAboveDust(bsqChangeOutput.getValue())) {
- String msg = "BSQ change output is below dust limit. outputValue=" + bsqChangeOutput.getValue().toFriendlyString();
+ Coin bsqChangeOutputValue = tx.getOutputs().get(1).getValue();
+ if (!Restrictions.isAboveDust(bsqChangeOutputValue)) {
+ String msg = "BSQ change output is below dust limit. outputValue=" + bsqChangeOutputValue.value / 100 + " BSQ";
log.warn(msg);
- throw new BsqChangeBelowDustException(msg, bsqChangeOutput.getValue());
+ throw new BsqChangeBelowDustException(msg, bsqChangeOutputValue);
}
}
@@ -573,60 +582,128 @@ private Transaction getPreparedSendTx(String receiverAddress, Coin receiverAmoun
///////////////////////////////////////////////////////////////////////////////////////////
- // Burn fee tx
+ // Burn fee txs
///////////////////////////////////////////////////////////////////////////////////////////
+ public Transaction getPreparedTradeFeeTx(Coin fee) throws InsufficientBsqException {
+ daoKillSwitch.assertDaoIsNotDisabled();
+
+ Transaction tx = new Transaction(params);
+ addInputsAndChangeOutputForTx(tx, fee, bsqCoinSelector);
+ return tx;
+ }
+
// We create a tx with Bsq inputs for the fee and optional BSQ change output.
// As the fee amount will be missing in the output those BSQ fees are burned.
- public Transaction getPreparedProposalTx(Coin fee, boolean requireChangeOutput) throws InsufficientBsqException {
- return getPreparedBurnFeeTx(fee, requireChangeOutput);
+ public Transaction getPreparedProposalTx(Coin fee) throws InsufficientBsqException {
+ return getPreparedTxWithMandatoryBsqChangeOutput(fee);
}
- public Transaction getPreparedBurnFeeTx(Coin fee) throws InsufficientBsqException {
- return getPreparedBurnFeeTx(fee, false);
+ public Transaction getPreparedIssuanceTx(Coin fee) throws InsufficientBsqException {
+ return getPreparedTxWithMandatoryBsqChangeOutput(fee);
}
- private Transaction getPreparedBurnFeeTx(Coin fee, boolean requireChangeOutput) throws InsufficientBsqException {
+ public Transaction getPreparedProofOfBurnTx(Coin fee) throws InsufficientBsqException {
+ return getPreparedTxWithMandatoryBsqChangeOutput(fee);
+ }
+
+ public Transaction getPreparedBurnFeeTxForAssetListing(Coin fee) throws InsufficientBsqException {
+ return getPreparedTxWithMandatoryBsqChangeOutput(fee);
+ }
+
+ // We need to require one BSQ change output as we could otherwise not be able to distinguish between 2
+ // structurally same transactions where only the BSQ fee is different. In case of asset listing fee and proof of
+ // burn it is a user input, so it is not know to the parser, instead we derive the burned fee from the parser.
+
+ // In case of proposal fee we could derive it from the params.
+
+ // For issuance txs we also require a BSQ change output before the issuance output gets added. There was a
+ // minor bug with the old version that multiple inputs would have caused an exception in case there was no
+ // change output (e.g. inputs of 21 and 6 BSQ for BSQ fee of 21 BSQ would have caused that only 1 input was used
+ // and then caused an error as we enforced a change output. This new version handles such cases correctly.
+
+ // Examples for the structurally indistinguishable transactions:
+ // Case 1: 10 BSQ fee to burn
+ // In: 17 BSQ
+ // Out: BSQ change 7 BSQ -> valid BSQ
+ // Out: OpReturn
+ // Miner fee: 1000 sat (10 BSQ burned)
+
+ // Case 2: 17 BSQ fee to burn
+ // In: 17 BSQ
+ // Out: burned BSQ change 7 BSQ -> BTC (7 BSQ burned)
+ // Out: OpReturn
+ // Miner fee: 1000 sat (10 BSQ burned)
+
+ private Transaction getPreparedTxWithMandatoryBsqChangeOutput(Coin fee) throws InsufficientBsqException {
daoKillSwitch.assertDaoIsNotDisabled();
- final Transaction tx = new Transaction(params);
- addInputsAndChangeOutputForTx(tx, fee, bsqCoinSelector, requireChangeOutput);
- // printTx("getPreparedFeeTx", tx);
- return tx;
+
+ Transaction tx = new Transaction(params);
+ // We look for inputs covering out BSQ fee we want to pay.
+ CoinSelection coinSelection = bsqCoinSelector.select(fee, wallet.calculateAllSpendCandidates());
+ try {
+ Coin change = bsqCoinSelector.getChange(fee, coinSelection);
+ if (change.isZero() || Restrictions.isDust(change)) {
+ // If change is zero or below dust we increase required input amount to enforce a BSQ change output.
+ // All outputs after that are considered BTC and therefore would be burned BSQ if BSQ is left from what
+ // we use for miner fee.
+
+ Coin minDustThreshold = Coin.valueOf(preferences.getIgnoreDustThreshold());
+ Coin increasedRequiredInput = fee.add(minDustThreshold);
+ coinSelection = bsqCoinSelector.select(increasedRequiredInput, wallet.calculateAllSpendCandidates());
+ change = bsqCoinSelector.getChange(fee, coinSelection);
+
+ log.warn("We increased required input as change output was zero or dust: New change value={}", change);
+ String info = "Available BSQ balance=" + coinSelection.valueGathered.value / 100 + " BSQ. " +
+ "Intended fee to burn=" + fee.value / 100 + " BSQ. " +
+ "Please increase your balance to at least " + (coinSelection.valueGathered.value + minDustThreshold.value) / 100 + " BSQ.";
+ checkArgument(coinSelection.valueGathered.compareTo(fee) > 0,
+ "This transaction require a change output of at least " + minDustThreshold.value / 100 + " BSQ (dust limit). " +
+ info);
+
+ checkArgument(!Restrictions.isDust(change),
+ "This transaction would create a dust output of " + change.value / 100 + " BSQ. " +
+ "It requires a change output of at least " + minDustThreshold.value / 100 + " BSQ (dust limit). " +
+ info);
+ }
+
+ coinSelection.gathered.forEach(tx::addInput);
+ tx.addOutput(change, getChangeAddress());
+
+ return tx;
+
+ } catch (InsufficientMoneyException e) {
+ log.error("coinSelection.gathered={}", coinSelection.gathered);
+ throw new InsufficientBsqException(e.missing);
+ }
}
private void addInputsAndChangeOutputForTx(Transaction tx,
Coin fee,
- BsqCoinSelector bsqCoinSelector,
- boolean requireChangeOutput)
+ BsqCoinSelector bsqCoinSelector)
throws InsufficientBsqException {
Coin requiredInput;
// If our fee is less then dust limit we increase it so we are sure to not get any dust output.
- if (Restrictions.isDust(fee))
- requiredInput = Restrictions.getMinNonDustOutput().add(fee);
- else
+ if (Restrictions.isDust(fee)) {
+ requiredInput = fee.add(Restrictions.getMinNonDustOutput());
+ } else {
requiredInput = fee;
+ }
CoinSelection coinSelection = bsqCoinSelector.select(requiredInput, wallet.calculateAllSpendCandidates());
coinSelection.gathered.forEach(tx::addInput);
try {
- // TODO why is fee passed to getChange ???
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
- if (requireChangeOutput) {
- checkArgument(change.isPositive(),
- "This transaction requires a mandatory BSQ change output. " +
- "At least " + Restrictions.getMinNonDustOutput().add(fee).value / 100d + " " +
- "BSQ is needed for this transaction");
- }
-
if (change.isPositive()) {
checkArgument(Restrictions.isAboveDust(change),
"The change output of " + change.value / 100d + " BSQ is below the min. dust value of "
+ Restrictions.getMinNonDustOutput().value / 100d +
- ". At least " + Restrictions.getMinNonDustOutput().add(fee).value / 100d + " " +
- "BSQ is needed for this transaction");
+ ". At least " + Restrictions.getMinNonDustOutput().add(fee).value / 100d +
+ " BSQ is needed for this transaction");
tx.addOutput(change, getChangeAddress());
}
} catch (InsufficientMoneyException e) {
+ log.error(tx.toString());
throw new InsufficientBsqException(e.missing);
}
}
@@ -642,7 +719,7 @@ public Transaction getPreparedBlindVoteTx(Coin fee, Coin stake) throws Insuffici
daoKillSwitch.assertDaoIsNotDisabled();
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, stake, getUnusedAddress()));
- addInputsAndChangeOutputForTx(tx, fee.add(stake), bsqCoinSelector, false);
+ addInputsAndChangeOutputForTx(tx, fee.add(stake), bsqCoinSelector);
//printTx("getPreparedBlindVoteTx", tx);
return tx;
}
@@ -676,7 +753,7 @@ public Transaction getPreparedLockupTx(Coin lockupAmount) throws AddressFormatEx
Transaction tx = new Transaction(params);
checkArgument(Restrictions.isAboveDust(lockupAmount), "The amount is too low (dust limit).");
tx.addOutput(new TransactionOutput(params, tx, lockupAmount, getUnusedAddress()));
- addInputsAndChangeOutputForTx(tx, lockupAmount, bsqCoinSelector, false);
+ addInputsAndChangeOutputForTx(tx, lockupAmount, bsqCoinSelector);
printTx("prepareLockupTx", tx);
return tx;
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java
index 0e0ee6f0e9d..7e1ed9c9c74 100644
--- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java
@@ -408,13 +408,13 @@ public Transaction completePreparedSendBsqTx(Transaction preparedBsqTx, boolean
TransactionVerificationException, WalletException, InsufficientMoneyException {
// preparedBsqTx has following structure:
// inputs [1-n] BSQ inputs
- // outputs [0-1] BSQ receivers output
+ // outputs [1] BSQ receivers output
// outputs [0-1] BSQ change output
// We add BTC mining fee. Result tx looks like:
// inputs [1-n] BSQ inputs
// inputs [1-n] BTC inputs
- // outputs [0-1] BSQ receivers output
+ // outputs [1] BSQ receivers output
// outputs [0-1] BSQ change output
// outputs [0-1] BTC change output
// mining fee: BTC mining fee
@@ -426,7 +426,7 @@ public Transaction completePreparedBsqTx(Transaction preparedBsqTx, boolean useC
// preparedBsqTx has following structure:
// inputs [1-n] BSQ inputs
- // outputs [0-1] BSQ receivers output
+ // outputs [1] BSQ receivers output
// outputs [0-1] BSQ change output
// mining fee: optional burned BSQ fee (only if opReturnData != null)
@@ -1114,4 +1114,55 @@ private SendRequest getSendRequestForMultipleAddresses(Set fromAddresses
protected boolean isDustAttackUtxo(TransactionOutput output) {
return output.getValue().value < preferences.getIgnoreDustThreshold();
}
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Refund payoutTx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public Transaction createRefundPayoutTx(Coin buyerAmount,
+ Coin sellerAmount,
+ Coin fee,
+ String buyerAddressString,
+ String sellerAddressString)
+ throws AddressFormatException, InsufficientMoneyException, WalletException, TransactionVerificationException {
+ Transaction tx = new Transaction(params);
+ Preconditions.checkArgument(buyerAmount.add(sellerAmount).isPositive(),
+ "The sellerAmount + buyerAmount must be positive.");
+ // buyerAmount can be 0
+ if (buyerAmount.isPositive()) {
+ Preconditions.checkArgument(Restrictions.isAboveDust(buyerAmount),
+ "The buyerAmount is too low (dust limit).");
+
+ tx.addOutput(buyerAmount, Address.fromBase58(params, buyerAddressString));
+ }
+ // sellerAmount can be 0
+ if (sellerAmount.isPositive()) {
+ Preconditions.checkArgument(Restrictions.isAboveDust(sellerAmount),
+ "The sellerAmount is too low (dust limit).");
+
+ tx.addOutput(sellerAmount, Address.fromBase58(params, sellerAddressString));
+ }
+
+ SendRequest sendRequest = SendRequest.forTx(tx);
+ sendRequest.fee = fee;
+ sendRequest.feePerKb = Coin.ZERO;
+ sendRequest.ensureMinRequiredFee = false;
+ sendRequest.aesKey = aesKey;
+ sendRequest.shuffleOutputs = false;
+ sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
+ preferences.getIgnoreDustThreshold());
+ sendRequest.changeAddress = getFreshAddressEntry().getAddress();
+
+ checkNotNull(wallet);
+ wallet.completeTx(sendRequest);
+
+ Transaction resultTx = sendRequest.tx;
+ checkWalletConsistency(wallet);
+ verifyTransaction(resultTx);
+
+ WalletService.printTx("createRefundPayoutTx", resultTx);
+
+ return resultTx;
+ }
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
index ac18d14e10a..e0c57058548 100644
--- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
@@ -33,13 +33,11 @@
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
-import org.bitcoinj.core.Context;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
-import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.TransactionOutput;
@@ -72,46 +70,6 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-// TradeService handles all relevant transactions used in the trade process
-/*
- To maintain a consistent tx structure we use that structure:
- Always buyers in/outputs/keys first then sellers in/outputs/keys the arbitrators outputs/keys.
-
- Deposit tx:
- IN[0] buyer (mandatory) e.g. 0.1 BTC
- IN[...] optional additional buyer inputs (normally never used as we pay from trade fee tx and always have 1 output there)
- IN[...] seller (mandatory) e.g. 1.1001 BTC
- IN[...] optional additional seller inputs (normally never used as we pay from trade fee tx and always have 1 output there)
- OUT[0] Multisig output (include tx fee for payout tx) e.g. 1.2001
- OUT[1] OP_RETURN with hash of contract and 0 BTC amount
- OUT[...] optional buyer change (normally never used as we pay from trade fee tx and always have 1 output there)
- OUT[...] optional seller change (normally never used as we pay from trade fee tx and always have 1 output there)
- FEE tx fee 0.0001 BTC
-
- Payout tx:
- IN[0] Multisig output from deposit Tx (signed by buyer and trader)
- OUT[0] Buyer payout address
- OUT[1] Seller payout address
-
- We use 0 confirmation transactions to make the trade process practical from usability side.
- There is no risk for double spends as the deposit transaction would become invalid if any preceding transaction would have been double spent.
- If a preceding transaction in the chain will not make it into the same or earlier block as the deposit transaction the deposit transaction
- will be invalid as well.
- Though the deposit need 1 confirmation before the buyer starts the Fiat payment.
-
- We have that chain of transactions:
- 1. Deposit from external wallet to our trading wallet: Tx0 (0 conf)
- 2. Create offer (or take offer) fee payment from Tx0 output: tx1 (0 conf)
- 3. Deposit tx created with inputs from tx1 of both traders: Tx2 (here we wait for 1 conf)
-
- Fiat transaction will not start before we get at least 1 confirmation for the deposit tx, then we can proceed.
- 4. Payout tx with input from MS output and output to both traders: Tx3 (0 conf)
- 5. Withdrawal to external wallet from Tx3: Tx4 (0 conf)
-
- After the payout transaction we also don't have issues with 0 conf or if not both tx (payout, withdrawal) make it into a block.
- Worst case is to rebroadcast the transactions (TODO: is not implemented yet).
-
- */
public class TradeWalletService {
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
@@ -178,16 +136,7 @@ public Transaction createBtcTradingFeeTx(Address fundingAddress,
Coin txFee,
String feeReceiverAddresses,
boolean doBroadcast,
- @Nullable TxBroadcaster.Callback callback)
- throws InsufficientMoneyException, AddressFormatException {
- log.debug("fundingAddress {}", fundingAddress);
- log.debug("reservedForTradeAddress {}", reservedForTradeAddress);
- log.debug("changeAddress {}", changeAddress);
- log.info("reservedFundsForOffer {}", reservedFundsForOffer.toPlainString());
- log.debug("useSavingsWallet {}", useSavingsWallet);
- log.info("tradingFee {}", tradingFee.toPlainString());
- log.info("txFee {}", txFee.toPlainString());
- log.debug("feeReceiverAddresses {}", feeReceiverAddresses);
+ @Nullable TxBroadcaster.Callback callback) throws InsufficientMoneyException, AddressFormatException {
Transaction tradingFeeTx = new Transaction(params);
SendRequest sendRequest = null;
try {
@@ -220,17 +169,18 @@ public Transaction createBtcTradingFeeTx(Address fundingAddress,
wallet.completeTx(sendRequest);
WalletService.printTx("tradingFeeTx", tradingFeeTx);
- if (doBroadcast && callback != null)
+ if (doBroadcast && callback != null) {
broadcastTx(tradingFeeTx, callback);
+ }
return tradingFeeTx;
} catch (Throwable t) {
- if (wallet != null && sendRequest != null && sendRequest.coinSelector != null)
- log.warn("Balance = {}; CoinSelector = {}",
- wallet.getBalance(sendRequest.coinSelector),
- sendRequest.coinSelector);
+ if (wallet != null && sendRequest != null && sendRequest.coinSelector != null) {
+ log.warn("Balance = {}; CoinSelector = {}", wallet.getBalance(sendRequest.coinSelector), sendRequest.coinSelector);
+ }
- log.warn("createBtcTradingFeeTx failed: tradingFeeTx={}, txOutputs={}", tradingFeeTx.toString(), tradingFeeTx.getOutputs());
+ log.warn("createBtcTradingFeeTx failed: tradingFeeTx={}, txOutputs={}", tradingFeeTx.toString(),
+ tradingFeeTx.getOutputs());
throw t;
}
}
@@ -241,17 +191,8 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
Address changeAddress,
Coin reservedFundsForOffer,
boolean useSavingsWallet,
- Coin txFee) throws
- TransactionVerificationException, WalletException,
- InsufficientMoneyException, AddressFormatException {
-
- log.debug("preparedBsqTx {}", preparedBsqTx);
- log.debug("fundingAddress {}", fundingAddress);
- log.debug("changeAddress {}", changeAddress);
- log.debug("reservedFundsForOffer {}", reservedFundsForOffer.toPlainString());
- log.debug("useSavingsWallet {}", useSavingsWallet);
- log.debug("txFee {}", txFee.toPlainString());
-
+ Coin txFee)
+ throws TransactionVerificationException, WalletException, InsufficientMoneyException, AddressFormatException {
// preparedBsqTx has following structure:
// inputs [1-n] BSQ inputs
// outputs [0-1] BSQ change output
@@ -279,6 +220,10 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
// wait for 1 confirmation)
// In case of double spend we will detect later in the trade process and use a ban score to penalize bad behaviour (not impl. yet)
+ // If BSQ trade fee > reservedFundsForOffer we would create a BSQ output instead of a BTC output.
+ // As the min. reservedFundsForOffer is 0.001 BTC which is 1000 BSQ this is an unrealistic scenario and not
+ // handled atm (if BTC price is 1M USD and BSQ price is 0.1 USD, then fee would be 10% which still is unrealistic).
+
// WalletService.printTx("preparedBsqTx", preparedBsqTx);
SendRequest sendRequest = SendRequest.forTx(preparedBsqTx);
sendRequest.shuffleOutputs = false;
@@ -306,7 +251,8 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
// Sign all BTC inputs
for (int i = preparedBsqTxInputsSize; i < resultTx.getInputs().size(); i++) {
TransactionInput txIn = resultTx.getInputs().get(i);
- checkArgument(txIn.getConnectedOutput() != null && txIn.getConnectedOutput().isMine(wallet),
+ checkArgument(txIn.getConnectedOutput() != null &&
+ txIn.getConnectedOutput().isMine(wallet),
"txIn.getConnectedOutput() is not in our wallet. That must not happen.");
WalletService.signTransactionInput(wallet, aesKey, resultTx, txIn, i);
WalletService.checkScriptSig(resultTx, txIn, i);
@@ -321,9 +267,10 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Trade
+ // Deposit tx
///////////////////////////////////////////////////////////////////////////////////////////
+
// We construct the deposit transaction in the way that the buyer is always the first entry (inputs, outputs, MS keys) and then the seller.
// In the creation of the deposit tx the taker/maker roles are the determining roles instead of buyer/seller.
// In the payout tx is is the buyer/seller role. We keep the buyer/seller ordering over all transactions to not get confusion with ordering,
@@ -341,18 +288,10 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
* @return A data container holding the inputs, the output value and address
* @throws TransactionVerificationException
*/
- public InputsAndChangeOutput takerCreatesDepositsTxInputs(Transaction takeOfferFeeTx,
- Coin inputAmount,
- Coin txFee,
- Address takersAddress) throws
- TransactionVerificationException {
- if (log.isDebugEnabled()) {
- log.debug("takerCreatesDepositsTxInputs called");
- log.debug("inputAmount {}", inputAmount.toFriendlyString());
- log.debug("txFee {}", txFee.toFriendlyString());
- log.debug("takersAddress {}", takersAddress.toString());
- }
-
+ public InputsAndChangeOutput takerCreatesDepositTxInputs(Transaction takeOfferFeeTx,
+ Coin inputAmount,
+ Coin txFee)
+ throws TransactionVerificationException {
// We add the mining fee 2 times to the deposit tx:
// 1. Will be spent when publishing the deposit tx (paid by buyer)
// 2. Will be added to the MS amount, so when publishing the payout tx the fee is already there and the outputs are not changed by fee reduction
@@ -390,14 +329,13 @@ OUT[0] dummyOutputAmount (inputAmount - tx fee)
//WalletService.printTx("dummyTX", dummyTX);
- List rawTransactionInputList = dummyTX.getInputs().stream()
- .map(e -> {
- checkNotNull(e.getConnectedOutput(), "e.getConnectedOutput() must not be null");
- checkNotNull(e.getConnectedOutput().getParentTransaction(), "e.getConnectedOutput().getParentTransaction() must not be null");
- checkNotNull(e.getValue(), "e.getValue() must not be null");
- return getRawInputFromTransactionInput(e);
- })
- .collect(Collectors.toList());
+ List rawTransactionInputList = dummyTX.getInputs().stream().map(e -> {
+ checkNotNull(e.getConnectedOutput(), "e.getConnectedOutput() must not be null");
+ checkNotNull(e.getConnectedOutput().getParentTransaction(),
+ "e.getConnectedOutput().getParentTransaction() must not be null");
+ checkNotNull(e.getValue(), "e.getValue() must not be null");
+ return getRawInputFromTransactionInput(e);
+ }).collect(Collectors.toList());
// TODO changeOutputValue and changeOutputAddress is not used as taker spends exact amount from fee tx.
@@ -408,6 +346,54 @@ OUT[0] dummyOutputAmount (inputAmount - tx fee)
return new InputsAndChangeOutput(new ArrayList<>(rawTransactionInputList), 0, null);
}
+ public PreparedDepositTxAndMakerInputs sellerAsMakerCreatesDepositTx(byte[] contractHash,
+ Coin makerInputAmount,
+ Coin msOutputAmount,
+ List takerRawTransactionInputs,
+ long takerChangeOutputValue,
+ @Nullable String takerChangeAddressString,
+ Address makerAddress,
+ Address makerChangeAddress,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
+ return makerCreatesDepositTx(false,
+ contractHash,
+ makerInputAmount,
+ msOutputAmount,
+ takerRawTransactionInputs,
+ takerChangeOutputValue,
+ takerChangeAddressString,
+ makerAddress,
+ makerChangeAddress,
+ buyerPubKey,
+ sellerPubKey);
+ }
+
+ public PreparedDepositTxAndMakerInputs buyerAsMakerCreatesAndSignsDepositTx(byte[] contractHash,
+ Coin makerInputAmount,
+ Coin msOutputAmount,
+ List takerRawTransactionInputs,
+ long takerChangeOutputValue,
+ @Nullable String takerChangeAddressString,
+ Address makerAddress,
+ Address makerChangeAddress,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
+ return makerCreatesDepositTx(true,
+ contractHash,
+ makerInputAmount,
+ msOutputAmount,
+ takerRawTransactionInputs,
+ takerChangeOutputValue,
+ takerChangeAddressString,
+ makerAddress,
+ makerChangeAddress,
+ buyerPubKey,
+ sellerPubKey);
+ }
+
/**
* The maker creates the deposit transaction using the takers input(s) and optional output and signs his input(s).
*
@@ -422,38 +408,23 @@ OUT[0] dummyOutputAmount (inputAmount - tx fee)
* @param makerChangeAddress The maker's change address.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
* @return A data container holding the serialized transaction and the maker raw inputs
* @throws SigningException
* @throws TransactionVerificationException
* @throws WalletException
*/
- public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean makerIsBuyer,
- byte[] contractHash,
- Coin makerInputAmount,
- Coin msOutputAmount,
- List takerRawTransactionInputs,
- long takerChangeOutputValue,
- @Nullable String takerChangeAddressString,
- Address makerAddress,
- Address makerChangeAddress,
- byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ private PreparedDepositTxAndMakerInputs makerCreatesDepositTx(boolean makerIsBuyer,
+ byte[] contractHash,
+ Coin makerInputAmount,
+ Coin msOutputAmount,
+ List takerRawTransactionInputs,
+ long takerChangeOutputValue,
+ @Nullable String takerChangeAddressString,
+ Address makerAddress,
+ Address makerChangeAddress,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
- log.debug("makerCreatesAndSignsDepositTx called");
- log.debug("makerIsBuyer {}", makerIsBuyer);
- log.debug("makerInputAmount {}", makerInputAmount.toFriendlyString());
- log.debug("msOutputAmount {}", msOutputAmount.toFriendlyString());
- log.debug("takerRawInputs {}", takerRawTransactionInputs.toString());
- log.debug("takerChangeOutputValue {}", takerChangeOutputValue);
- log.debug("takerChangeAddressString {}", takerChangeAddressString);
- log.debug("makerAddress {}", makerAddress);
- log.debug("makerChangeAddress {}", makerChangeAddress);
- log.debug("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey));
- log.debug("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey));
- log.debug("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey));
-
checkArgument(!takerRawTransactionInputs.isEmpty());
// First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx.
@@ -461,7 +432,7 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
Transaction dummyTx = new Transaction(params);
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, new ECKey().toAddress(params));
dummyTx.addOutput(dummyOutput);
- addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress, Coin.ZERO);
+ addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress);
// Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions.
List makerInputs = dummyTx.getInputs();
TransactionOutput makerOutput = null;
@@ -470,8 +441,9 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
checkArgument(dummyTx.getOutputs().size() < 3, "dummyTx.getOutputs().size() >= 3");
// Only save change outputs, the dummy output is ignored (that's why we start with index 1)
- if (dummyTx.getOutputs().size() > 1)
+ if (dummyTx.getOutputs().size() > 1) {
makerOutput = dummyTx.getOutput(1);
+ }
// Now we construct the real deposit tx
Transaction preparedDepositTx = new Transaction(params);
@@ -505,10 +477,11 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
// Add MultiSig output
- Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
// Tx fee for deposit tx will be paid by buyer.
- TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount, p2SHMultiSigOutputScript.getProgram());
+ TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount,
+ p2SHMultiSigOutputScript.getProgram());
preparedDepositTx.addOutput(p2SHMultiSigOutput);
// We add the hash ot OP_RETURN with a 0 amount output
@@ -517,31 +490,35 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
preparedDepositTx.addOutput(contractHashOutput);
TransactionOutput takerTransactionOutput = null;
- if (takerChangeOutputValue > 0 && takerChangeAddressString != null)
+ if (takerChangeOutputValue > 0 && takerChangeAddressString != null) {
takerTransactionOutput = new TransactionOutput(params, preparedDepositTx, Coin.valueOf(takerChangeOutputValue),
Address.fromBase58(params, takerChangeAddressString));
+ }
if (makerIsBuyer) {
// Add optional buyer outputs
- if (makerOutput != null)
+ if (makerOutput != null) {
preparedDepositTx.addOutput(makerOutput);
+ }
// Add optional seller outputs
- if (takerTransactionOutput != null)
+ if (takerTransactionOutput != null) {
preparedDepositTx.addOutput(takerTransactionOutput);
+ }
} else {
// taker is buyer role
// Add optional seller outputs
- if (takerTransactionOutput != null)
+ if (takerTransactionOutput != null) {
preparedDepositTx.addOutput(takerTransactionOutput);
+ }
// Add optional buyer outputs
- if (makerOutput != null)
+ if (makerOutput != null) {
preparedDepositTx.addOutput(makerOutput);
+ }
}
- // Sign inputs
int start = makerIsBuyer ? 0 : takerRawTransactionInputs.size();
int end = makerIsBuyer ? makerInputs.size() : preparedDepositTx.getInputs().size();
for (int i = start; i < end; i++) {
@@ -550,8 +527,7 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
WalletService.checkScriptSig(preparedDepositTx, input, i);
}
- WalletService.printTx("prepared depositTx", preparedDepositTx);
-
+ WalletService.printTx("makerCreatesDepositTx", preparedDepositTx);
WalletService.verifyTransaction(preparedDepositTx);
return new PreparedDepositTxAndMakerInputs(makerRawTransactionInputs, preparedDepositTx.bitcoinSerialize());
@@ -567,40 +543,28 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
* @param sellerInputs The connected outputs for all inputs of the seller.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
- * @param callback Callback when transaction is broadcasted.
* @throws SigningException
* @throws TransactionVerificationException
* @throws WalletException
*/
- public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
- byte[] contractHash,
- byte[] makersDepositTxSerialized,
- List buyerInputs,
- List sellerInputs,
- byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey,
- TxBroadcaster.Callback callback) throws SigningException, TransactionVerificationException,
- WalletException {
+ public Transaction takerSignsDepositTx(boolean takerIsSeller,
+ byte[] contractHash,
+ byte[] makersDepositTxSerialized,
+ List buyerInputs,
+ List sellerInputs,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws SigningException, TransactionVerificationException, WalletException {
Transaction makersDepositTx = new Transaction(params, makersDepositTxSerialized);
- log.debug("signAndPublishDepositTx called");
- log.debug("takerIsSeller {}", takerIsSeller);
- log.debug("makersDepositTx {}", makersDepositTx.toString());
- log.debug("buyerConnectedOutputsForAllInputs {}", buyerInputs.toString());
- log.debug("sellerConnectedOutputsForAllInputs {}", sellerInputs.toString());
- log.debug("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.debug("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.debug("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
checkArgument(!buyerInputs.isEmpty());
checkArgument(!sellerInputs.isEmpty());
// Check if maker's Multisig script is identical to the takers
- Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
- if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript))
+ Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
+ if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript)) {
throw new TransactionVerificationException("Maker's p2SHMultiSigOutputScript does not match to takers p2SHMultiSigOutputScript");
+ }
// The outpoints are not available from the serialized makersDepositTx, so we cannot use that tx directly, but we use it to construct a new
// depositTx
@@ -609,22 +573,29 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
if (takerIsSeller) {
// Add buyer inputs and apply signature
// We grab the signature from the makersDepositTx and apply it to the new tx input
- for (int i = 0; i < buyerInputs.size(); i++)
- depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), buyerInputs.get(i)));
+ for (int i = 0; i < buyerInputs.size(); i++) {
+ TransactionInput transactionInput = makersDepositTx.getInputs().get(i);
+ depositTx.addInput(getTransactionInput(depositTx, getMakersScriptSigProgram(transactionInput), buyerInputs.get(i)));
+ }
// Add seller inputs
- for (RawTransactionInput rawTransactionInput : sellerInputs)
+ for (RawTransactionInput rawTransactionInput : sellerInputs) {
depositTx.addInput(getTransactionInput(depositTx, new byte[]{}, rawTransactionInput));
+ }
} else {
// taker is buyer
// Add buyer inputs and apply signature
- for (RawTransactionInput rawTransactionInput : buyerInputs)
+ for (RawTransactionInput rawTransactionInput : buyerInputs) {
depositTx.addInput(getTransactionInput(depositTx, new byte[]{}, rawTransactionInput));
+ }
// Add seller inputs
// We grab the signature from the makersDepositTx and apply it to the new tx input
- for (int i = buyerInputs.size(), k = 0; i < makersDepositTx.getInputs().size(); i++, k++)
- depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), sellerInputs.get(k)));
+ for (int i = buyerInputs.size(), k = 0; i < makersDepositTx.getInputs().size(); i++, k++) {
+ TransactionInput transactionInput = makersDepositTx.getInputs().get(i);
+ // We get the deposit tx unsigned if maker is seller
+ depositTx.addInput(getTransactionInput(depositTx, new byte[]{}, sellerInputs.get(k)));
+ }
}
// Check if OP_RETURN output with contract hash matches the one from the maker
@@ -633,12 +604,13 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
log.debug("contractHashOutput {}", contractHashOutput);
TransactionOutput makersContractHashOutput = makersDepositTx.getOutputs().get(1);
log.debug("makersContractHashOutput {}", makersContractHashOutput);
- if (!makersContractHashOutput.getScriptPubKey().equals(contractHashOutput.getScriptPubKey()))
+ if (!makersContractHashOutput.getScriptPubKey().equals(contractHashOutput.getScriptPubKey())) {
throw new TransactionVerificationException("Maker's transaction output for the contract hash is not matching takers version.");
+ }
// Add all outputs from makersDepositTx to depositTx
makersDepositTx.getOutputs().forEach(depositTx::addOutput);
- //WalletService.printTx("makersDepositTx", makersDepositTx);
+ WalletService.printTx("makersDepositTx", makersDepositTx);
// Sign inputs
int start = takerIsSeller ? buyerInputs.size() : 0;
@@ -649,17 +621,113 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
WalletService.checkScriptSig(depositTx, input, i);
}
- WalletService.printTx("depositTx", depositTx);
+ WalletService.printTx("takerSignsDepositTx", depositTx);
WalletService.verifyTransaction(depositTx);
WalletService.checkWalletConsistency(wallet);
- broadcastTx(depositTx, callback);
-
return depositTx;
}
+ public void sellerAsMakerFinalizesDepositTx(Transaction myDepositTx, Transaction takersDepositTx, int numTakersInputs)
+ throws TransactionVerificationException, AddressFormatException {
+
+ // We add takers signature from his inputs and add it to out tx which was already signed earlier.
+ for (int i = 0; i < numTakersInputs; i++) {
+ TransactionInput input = takersDepositTx.getInput(i);
+ Script scriptSig = input.getScriptSig();
+ myDepositTx.getInput(i).setScriptSig(scriptSig);
+ }
+
+ WalletService.printTx("sellerAsMakerFinalizesDepositTx", myDepositTx);
+ WalletService.verifyTransaction(myDepositTx);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Delayed payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public Transaction createDelayedUnsignedPayoutTx(Transaction depositTx,
+ String donationAddressString,
+ Coin minerFee,
+ long lockTime)
+ throws AddressFormatException, TransactionVerificationException {
+ TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
+ Transaction delayedPayoutTx = new Transaction(params);
+ delayedPayoutTx.addInput(p2SHMultiSigOutput);
+ applyLockTime(lockTime, delayedPayoutTx);
+ Coin outputAmount = depositTx.getOutputSum().subtract(minerFee);
+ delayedPayoutTx.addOutput(outputAmount, Address.fromBase58(params, donationAddressString));
+ WalletService.printTx("Unsigned delayedPayoutTx ToDonationAddress", delayedPayoutTx);
+ WalletService.verifyTransaction(delayedPayoutTx);
+ return delayedPayoutTx;
+ }
+
+ public byte[] signDelayedPayoutTx(Transaction delayedPayoutTx,
+ DeterministicKey myMultiSigKeyPair,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws AddressFormatException, TransactionVerificationException {
+
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
+ Sha256Hash sigHash = delayedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
+ checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
+ if (myMultiSigKeyPair.isEncrypted()) {
+ checkNotNull(aesKey);
+ }
+
+ ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
+ WalletService.printTx("delayedPayoutTx for sig creation", delayedPayoutTx);
+ WalletService.verifyTransaction(delayedPayoutTx);
+ return mySignature.encodeToDER();
+ }
+
+ public Transaction finalizeDelayedPayoutTx(Transaction delayedPayoutTx,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey,
+ byte[] buyerSignature,
+ byte[] sellerSignature)
+ throws AddressFormatException, TransactionVerificationException, WalletException {
+ 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);
+ WalletService.checkWalletConsistency(wallet);
+ WalletService.checkScriptSig(delayedPayoutTx, input, 0);
+ checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
+ input.verify(input.getConnectedOutput());
+ return delayedPayoutTx;
+ }
+
+ public boolean verifiesDepositTxAndDelayedPayoutTx(Transaction depositTx,
+ Transaction delayedPayoutTx) {
+ // todo add more checks
+ if (delayedPayoutTx.getLockTime() == 0) {
+ log.error("Time lock is not set");
+ return false;
+ }
+
+ if (delayedPayoutTx.getInputs().stream().noneMatch(e -> e.getSequenceNumber() == TransactionInput.NO_SEQUENCE - 1)) {
+ log.error("Sequence number must be 0xFFFFFFFE");
+ return false;
+ }
+
+ return true;
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Standard payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
/**
* Seller signs payout transaction, buyer has not signed yet.
*
@@ -671,7 +739,6 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
* @param multiSigKeyPair DeterministicKey for MultiSig from seller
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
* @return DER encoded canonical signature
* @throws AddressFormatException
* @throws TransactionVerificationException
@@ -683,38 +750,21 @@ public byte[] buyerSignsPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey multiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException {
- log.trace("sellerSignsPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey HEX=" + ECKey.fromPublicOnly(buyerPubKey).getPublicKeyAsHex());
- log.info("sellerPubKey HEX=" + ECKey.fromPublicOnly(sellerPubKey).getPublicKeyAsHex());
- log.info("arbitratorPubKey HEX=" + ECKey.fromPublicOnly(arbitratorPubKey).getPublicKeyAsHex());
- Transaction preparedPayoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction preparedPayoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount,
+ buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
- if (multiSigKeyPair.isEncrypted())
+ if (multiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
+ }
ECKey.ECDSASignature buyerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
WalletService.printTx("prepared payoutTx", preparedPayoutTx);
-
WalletService.verifyTransaction(preparedPayoutTx);
-
return buyerSignature.encodeToDER();
}
@@ -731,7 +781,6 @@ public byte[] buyerSignsPayoutTx(Transaction depositTx,
* @param multiSigKeyPair Buyer's keypair for MultiSig
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
* @return The payout transaction
* @throws AddressFormatException
* @throws TransactionVerificationException
@@ -745,49 +794,27 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey multiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
- log.trace("buyerSignsAndFinalizesPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerSignature r {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).r.toString());
- log.trace("buyerSignature s {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).s.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.info("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.info("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
- Transaction payoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction payoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
- if (multiSigKeyPair.isEncrypted())
+ if (multiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
-
+ }
ECKey.ECDSASignature sellerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature),
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 (arbitrator, seller, buyer)
- Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
-
+ Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig),
+ redeemScript);
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
-
WalletService.printTx("payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
WalletService.checkScriptSig(payoutTx, input, 0);
@@ -798,7 +825,7 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Mediation
+ // Mediated payoutTx
///////////////////////////////////////////////////////////////////////////////////////////
public byte[] signMediatedPayoutTx(Transaction depositTx,
@@ -808,38 +835,20 @@ public byte[] signMediatedPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey myMultiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException {
- log.trace("signMediatedPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.trace("buyerPubKey HEX=" + ECKey.fromPublicOnly(buyerPubKey).getPublicKeyAsHex());
- log.trace("sellerPubKey HEX=" + ECKey.fromPublicOnly(sellerPubKey).getPublicKeyAsHex());
- log.trace("arbitratorPubKey HEX=" + ECKey.fromPublicOnly(arbitratorPubKey).getPublicKeyAsHex());
- Transaction preparedPayoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction preparedPayoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
- if (myMultiSigKeyPair.isEncrypted())
+ if (myMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
+ }
ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
WalletService.printTx("prepared mediated payoutTx for sig creation", preparedPayoutTx);
-
WalletService.verifyTransaction(preparedPayoutTx);
-
return mySignature.encodeToDER();
}
@@ -852,47 +861,22 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey multiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
- log.trace("finalizeMediatedPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerSignature r {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).r.toString());
- log.trace("buyerSignature s {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).s.toString());
- log.trace("sellerSignature r {}", ECKey.ECDSASignature.decodeFromDER(sellerSignature).r.toString());
- log.trace("sellerSignature s {}", ECKey.ECDSASignature.decodeFromDER(sellerSignature).s.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.trace("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.trace("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.trace("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
- Transaction payoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction payoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
-
TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature),
Transaction.SigHash.ALL, false);
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 (arbitrator, seller, buyer)
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
-
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
-
WalletService.printTx("mediated payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
WalletService.checkScriptSig(payoutTx, input, 0);
@@ -903,7 +887,7 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Arbitration
+ // Arbitrated payoutTx
///////////////////////////////////////////////////////////////////////////////////////////
/**
@@ -933,39 +917,27 @@ public byte[] arbitratorSignsDisputedPayoutTx(byte[] depositTxSerialized,
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException {
Transaction depositTx = new Transaction(params, depositTxSerialized);
- log.trace("signDisputedPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerAddressString {}", buyerAddressString);
- log.trace("sellerAddressString {}", sellerAddressString);
- log.trace("arbitratorKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.info("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.info("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
// Our MS is index 0
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
Transaction preparedPayoutTx = new Transaction(params);
preparedPayoutTx.addInput(p2SHMultiSigOutput);
- if (buyerPayoutAmount.isGreaterThan(Coin.ZERO))
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
preparedPayoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
- if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
preparedPayoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
+ }
// take care of sorting!
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(arbitratorKeyPair, "arbitratorKeyPair must not be null");
- if (arbitratorKeyPair.isEncrypted())
+ if (arbitratorKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
+ }
ECKey.ECDSASignature arbitratorSignature = arbitratorKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
WalletService.verifyTransaction(preparedPayoutTx);
-
- //WalletService.printTx("preparedPayoutTx", preparedPayoutTx);
-
+ WalletService.printTx("preparedPayoutTx", preparedPayoutTx);
return arbitratorSignature.encodeToDER();
}
@@ -999,47 +971,33 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
Transaction depositTx = new Transaction(params, depositTxSerialized);
-
- log.trace("signAndFinalizeDisputedPayoutTx called");
- log.trace("depositTx {}", depositTx);
- log.trace("arbitratorSignature r {}", ECKey.ECDSASignature.decodeFromDER(arbitratorSignature).r.toString());
- log.trace("arbitratorSignature s {}", ECKey.ECDSASignature.decodeFromDER(arbitratorSignature).s.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerAddressString {}", buyerAddressString);
- log.trace("sellerAddressString {}", sellerAddressString);
- log.trace("tradersMultiSigKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.info("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.info("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
-
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
Transaction payoutTx = new Transaction(params);
payoutTx.addInput(p2SHMultiSigOutput);
- if (buyerPayoutAmount.isGreaterThan(Coin.ZERO))
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
- if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
+ }
// take care of sorting!
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(tradersMultiSigKeyPair, "tradersMultiSigKeyPair must not be null");
- if (tradersMultiSigKeyPair.isEncrypted())
+ if (tradersMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
+ }
ECKey.ECDSASignature tradersSignature = tradersMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
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);
+ Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(arbitratorTxSig, tradersTxSig),
+ redeemScript);
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
-
WalletService.printTx("disputed payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
WalletService.checkScriptSig(payoutTx, input, 0);
@@ -1049,49 +1007,38 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Emergency payoutTx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
// Emergency payout tool. Used only in cased when the payput from the arbitrator does not work because some data
// in the trade/dispute are messed up.
// We keep here arbitratorPayoutAmount just in case (requires cooperation from peer anyway)
- public Transaction emergencySignAndPublishPayoutTx(String depositTxHex,
- Coin buyerPayoutAmount,
- Coin sellerPayoutAmount,
- Coin arbitratorPayoutAmount,
- Coin txFee,
- String buyerAddressString,
- String sellerAddressString,
- String arbitratorAddressString,
- @Nullable String buyerPrivateKeyAsHex,
- @Nullable String sellerPrivateKeyAsHex,
- String arbitratorPrivateKeyAsHex,
- String buyerPubKeyAsHex,
- String sellerPubKeyAsHex,
- String arbitratorPubKeyAsHex,
- String P2SHMultiSigOutputScript,
- TxBroadcaster.Callback callback)
+ public Transaction emergencySignAndPublishPayoutTxFrom2of3MultiSig(String depositTxHex,
+ Coin buyerPayoutAmount,
+ Coin sellerPayoutAmount,
+ Coin arbitratorPayoutAmount,
+ Coin txFee,
+ String buyerAddressString,
+ String sellerAddressString,
+ String arbitratorAddressString,
+ @Nullable String buyerPrivateKeyAsHex,
+ @Nullable String sellerPrivateKeyAsHex,
+ String arbitratorPrivateKeyAsHex,
+ String buyerPubKeyAsHex,
+ String sellerPubKeyAsHex,
+ String arbitratorPubKeyAsHex,
+ TxBroadcaster.Callback callback)
throws AddressFormatException, TransactionVerificationException, WalletException {
- log.info("signAndPublishPayoutTx called");
- log.info("depositTxHex {}", depositTxHex);
- log.info("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.info("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.info("arbitratorPayoutAmount {}", arbitratorPayoutAmount.toFriendlyString());
- log.info("buyerAddressString {}", buyerAddressString);
- log.info("sellerAddressString {}", sellerAddressString);
- log.info("arbitratorAddressString {}", arbitratorAddressString);
- log.info("buyerPrivateKeyAsHex (not displayed for security reasons)");
- log.info("sellerPrivateKeyAsHex (not displayed for security reasons)");
- log.info("arbitratorPrivateKeyAsHex (not displayed for security reasons)");
- log.info("buyerPubKeyAsHex {}", buyerPubKeyAsHex);
- log.info("sellerPubKeyAsHex {}", sellerPubKeyAsHex);
- log.info("arbitratorPubKeyAsHex {}", arbitratorPubKeyAsHex);
- log.info("P2SHMultiSigOutputScript {}", P2SHMultiSigOutputScript);
-
- checkNotNull((buyerPrivateKeyAsHex != null || sellerPrivateKeyAsHex != null), "either buyerPrivateKeyAsHex or sellerPrivateKeyAsHex must not be null");
+ checkNotNull((buyerPrivateKeyAsHex != null || sellerPrivateKeyAsHex != null),
+ "either buyerPrivateKeyAsHex or sellerPrivateKeyAsHex must not be null");
byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey();
byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey();
- final byte[] arbitratorPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(arbitratorPubKeyAsHex)).getPubKey();
+ byte[] arbitratorPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(arbitratorPubKeyAsHex)).getPubKey();
- Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script p2SHMultiSigOutputScript = get2of3MultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(arbitratorPayoutAmount).add(txFee);
TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram());
@@ -1102,15 +1049,18 @@ public Transaction emergencySignAndPublishPayoutTx(String depositTxHex,
Sha256Hash spendTxHash = Sha256Hash.wrap(depositTxHex);
payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
- if (buyerPayoutAmount.isGreaterThan(Coin.ZERO))
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
- if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
- if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(arbitratorPayoutAmount, Address.fromBase58(params, arbitratorAddressString));
+ }
// take care of sorting!
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature tradersSignature;
@@ -1124,24 +1074,79 @@ public Transaction emergencySignAndPublishPayoutTx(String depositTxHex,
checkNotNull(sellerPrivateKey, "sellerPrivateKey must not be null");
tradersSignature = sellerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
}
- final ECKey key = ECKey.fromPrivate(Utils.HEX.decode(arbitratorPrivateKeyAsHex));
+ ECKey key = ECKey.fromPrivate(Utils.HEX.decode(arbitratorPrivateKeyAsHex));
checkNotNull(key, "key must not be null");
ECKey.ECDSASignature arbitratorSignature = key.sign(sigHash, aesKey).toCanonicalised();
-
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
TransactionSignature arbitratorTxSig = new TransactionSignature(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);
-
WalletService.printTx("payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
-
broadcastTx(payoutTx, callback, 20);
+ return payoutTx;
+ }
+
+ //todo add window tool for usage
+ public Transaction emergencySignAndPublishPayoutTxFrom2of2MultiSig(String depositTxHex,
+ Coin buyerPayoutAmount,
+ Coin sellerPayoutAmount,
+ Coin txFee,
+ String buyerAddressString,
+ String sellerAddressString,
+ String buyerPrivateKeyAsHex,
+ String sellerPrivateKeyAsHex,
+ String buyerPubKeyAsHex,
+ String sellerPubKeyAsHex,
+ 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);
+ Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(txFee);
+ TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram());
+ Transaction depositTx = new Transaction(params);
+ depositTx.addOutput(p2SHMultiSigOutput);
+
+ Transaction payoutTx = new Transaction(params);
+ Sha256Hash spendTxHash = Sha256Hash.wrap(depositTxHex);
+ payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
+
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
+ payoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
+ payoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
+ }
+
+ // take care of sorting!
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
+ Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
+
+ ECKey buyerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(buyerPrivateKeyAsHex));
+ checkNotNull(buyerPrivateKey, "key must not be null");
+ ECKey.ECDSASignature buyerECDSASignature = buyerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
+
+ ECKey sellerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(sellerPrivateKeyAsHex));
+ checkNotNull(sellerPrivateKey, "key must not be null");
+ ECKey.ECDSASignature sellerECDSASignature = sellerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
+
+ 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);
+ WalletService.printTx("payoutTx", payoutTx);
+ WalletService.verifyTransaction(payoutTx);
+ WalletService.checkWalletConsistency(wallet);
+ broadcastTx(payoutTx, callback, 20);
return payoutTx;
}
@@ -1165,36 +1170,6 @@ public void broadcastTx(Transaction tx, TxBroadcaster.Callback callback, int tim
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
- /**
- * @param transaction The transaction to be added to the wallet
- * @return The transaction we added to the wallet, which is different as the one we passed as argument!
- * @throws VerificationException
- */
- public Transaction addTxToWallet(Transaction transaction) throws VerificationException {
- // We need to recreate the transaction otherwise we get a null pointer...
- Transaction result = new Transaction(params, transaction.bitcoinSerialize());
- result.getConfidence(Context.get()).setSource(TransactionConfidence.Source.SELF);
-
- if (wallet != null)
- wallet.receivePending(result, null, true);
- return result;
- }
-
- /**
- * @param serializedTransaction The serialized transaction to be added to the wallet
- * @return The transaction we added to the wallet, which is different as the one we passed as argument!
- * @throws VerificationException
- */
- public Transaction addTxToWallet(byte[] serializedTransaction) throws VerificationException {
- // We need to recreate the tx otherwise we get a null pointer...
- Transaction transaction = new Transaction(params, serializedTransaction);
- transaction.getConfidence(Context.get()).setSource(TransactionConfidence.Source.NETWORK);
-
- if (wallet != null)
- wallet.receivePending(transaction, null, true);
- return transaction;
- }
-
/**
* @param txId The transaction ID of the transaction we want to lookup
* @return Returns local existing wallet transaction
@@ -1219,31 +1194,31 @@ public Transaction getClonedTransaction(Transaction tx) {
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
- @NotNull
private RawTransactionInput getRawInputFromTransactionInput(@NotNull TransactionInput input) {
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
- checkNotNull(input.getConnectedOutput().getParentTransaction(), "input.getConnectedOutput().getParentTransaction() must not be null");
+ checkNotNull(input.getConnectedOutput().getParentTransaction(),
+ "input.getConnectedOutput().getParentTransaction() must not be null");
checkNotNull(input.getValue(), "input.getValue() must not be null");
- return new RawTransactionInput(input.getOutpoint().getIndex(), input.getConnectedOutput().getParentTransaction().bitcoinSerialize(), input.getValue().value);
+ return new RawTransactionInput(input.getOutpoint().getIndex(),
+ input.getConnectedOutput().getParentTransaction().bitcoinSerialize(),
+ input.getValue().value);
}
- private byte[] getScriptProgram(Transaction makersDepositTx, int i) throws TransactionVerificationException {
- byte[] scriptProgram = makersDepositTx.getInputs().get(i).getScriptSig().getProgram();
- if (scriptProgram.length == 0)
+ 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;
}
- @NotNull
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, new TransactionOutPoint(params,
+ rawTransactionInput.index, new Transaction(params, rawTransactionInput.parentTransaction)),
Coin.valueOf(rawTransactionInput.value));
}
@@ -1257,7 +1232,7 @@ private TransactionInput getTransactionInput(Transaction depositTx,
// Furthermore the executed list is reversed to the provided.
// Best practice is to provide the list sorted by the least probable successful candidates first (arbitrator is first -> will be last in execution loop, so
// avoiding unneeded expensive ECKey.verify calls)
- private Script getMultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
+ private Script get2of3MultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
ECKey buyerKey = ECKey.fromPublicOnly(buyerPubKey);
ECKey sellerKey = ECKey.fromPublicOnly(sellerPubKey);
ECKey arbitratorKey = ECKey.fromPublicOnly(arbitratorPubKey);
@@ -1266,8 +1241,20 @@ private Script getMultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey,
return ScriptBuilder.createMultiSigOutputScript(2, keys);
}
- private Script getP2SHMultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
- return ScriptBuilder.createP2SHOutputScript(getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey));
+ private Script get2of2MultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey) {
+ ECKey buyerKey = ECKey.fromPublicOnly(buyerPubKey);
+ ECKey sellerKey = ECKey.fromPublicOnly(sellerPubKey);
+ // Take care of sorting! Need to reverse to the order we use normally (buyer, seller)
+ List keys = ImmutableList.of(sellerKey, buyerKey);
+ return ScriptBuilder.createMultiSigOutputScript(2, keys);
+ }
+
+ private Script get2of3MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
+ return ScriptBuilder.createP2SHOutputScript(get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey));
+ }
+
+ private Script get2of2MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey) {
+ return ScriptBuilder.createP2SHOutputScript(get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey));
}
private Transaction createPayoutTx(Transaction depositTx,
@@ -1288,9 +1275,11 @@ private void signInput(Transaction transaction, TransactionInput input, int inpu
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
checkNotNull(wallet);
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
- checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" + input.getOutpoint().toString());
- if (sigKey.isEncrypted())
+ checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
+ input.getOutpoint().toString());
+ if (sigKey.isEncrypted()) {
checkNotNull(aesKey);
+ }
Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature signature = sigKey.sign(hash, aesKey);
TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false);
@@ -1305,8 +1294,7 @@ private void signInput(Transaction transaction, TransactionInput input, int inpu
private void addAvailableInputsAndChangeOutputs(Transaction transaction,
Address address,
- Address changeAddress,
- Coin txFee) throws WalletException {
+ Address changeAddress) throws WalletException {
SendRequest sendRequest = null;
try {
// Lets let the framework do the work to find the right inputs
@@ -1314,7 +1302,7 @@ private void addAvailableInputsAndChangeOutputs(Transaction transaction,
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
// We use a fixed fee
- sendRequest.fee = txFee;
+ sendRequest.fee = Coin.ZERO;
sendRequest.feePerKb = Coin.ZERO;
sendRequest.ensureMinRequiredFee = false;
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation)
@@ -1327,10 +1315,18 @@ private void addAvailableInputsAndChangeOutputs(Transaction transaction,
checkNotNull(wallet, "wallet must not be null");
wallet.completeTx(sendRequest);
} catch (Throwable t) {
- if (sendRequest != null && sendRequest.tx != null)
- log.warn("addAvailableInputsAndChangeOutputs: sendRequest.tx={}, sendRequest.tx.getOutputs()={}", sendRequest.tx, sendRequest.tx.getOutputs());
+ if (sendRequest != null && sendRequest.tx != null) {
+ log.warn("addAvailableInputsAndChangeOutputs: sendRequest.tx={}, sendRequest.tx.getOutputs()={}",
+ sendRequest.tx, sendRequest.tx.getOutputs());
+ }
throw new WalletException(t);
}
}
+
+ private void applyLockTime(long lockTime, Transaction tx) {
+ checkArgument(!tx.getInputs().isEmpty(), "The tx must have inputs. tx={}", tx);
+ tx.getInputs().forEach(input -> input.setSequenceNumber(TransactionInput.NO_SEQUENCE - 1));
+ tx.setLockTime(lockTime);
+ }
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java b/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java
index 0576024a736..23d38dd13c1 100644
--- a/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java
+++ b/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java
@@ -104,12 +104,12 @@ public static void broadcastTx(Wallet wallet, PeerGroup peerGroup, Transaction t
}
// We decided the least risky scenario is to commit the tx to the wallet and broadcast it later.
- // If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have commited the tx to both bsq and btc
+ // If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have committed the tx to both bsq and btc
// wallets so the next line causes no effect.
// If it's a btc tx, the next line adds the tx to the wallet.
wallet.maybeCommitTx(tx);
- Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback() {
+ Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Transaction result) {
// We expect that there is still a timeout in our map, otherwise the timeout got triggered
@@ -119,7 +119,7 @@ public void onSuccess(@Nullable Transaction result) {
// before the caller is finished.
UserThread.execute(() -> callback.onSuccess(tx));
} else {
- log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout.", txId);
+ log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout. txId={}", txId);
}
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
index 62822c4a0ad..d04567d5f16 100644
--- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
@@ -34,6 +34,7 @@
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.Context;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
@@ -43,6 +44,7 @@
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
+import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypter;
@@ -102,6 +104,7 @@ public abstract class WalletService {
protected final CopyOnWriteArraySet addressConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet txConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet balanceListeners = new CopyOnWriteArraySet<>();
+ @Getter
protected Wallet wallet;
@Getter
protected KeyParameter aesKey;
@@ -223,7 +226,9 @@ public static void checkAllScriptSignaturesForTx(Transaction transaction) throws
}
}
- public static void checkScriptSig(Transaction transaction, TransactionInput input, int inputIndex) throws TransactionVerificationException {
+ public static void checkScriptSig(Transaction transaction,
+ TransactionInput input,
+ int inputIndex) throws TransactionVerificationException {
try {
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
@@ -245,7 +250,11 @@ public static void removeSignatures(Transaction transaction) {
// Sign tx
///////////////////////////////////////////////////////////////////////////////////////////
- public static void signTransactionInput(Wallet wallet, KeyParameter aesKey, Transaction tx, TransactionInput txIn, int index) {
+ public static void signTransactionInput(Wallet wallet,
+ KeyParameter aesKey,
+ Transaction tx,
+ TransactionInput txIn,
+ int index) {
KeyBag maybeDecryptingKeyBag = new DecryptingKeyBag(wallet, aesKey);
if (txIn.getConnectedOutput() != null) {
try {
@@ -475,7 +484,10 @@ public boolean isAddressUnused(Address address) {
// Empty complete Wallet
///////////////////////////////////////////////////////////////////////////////////////////
- public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
+ public void emptyWallet(String toAddress,
+ KeyParameter aesKey,
+ ResultHandler resultHandler,
+ ErrorMessageHandler errorMessageHandler)
throws InsufficientMoneyException, AddressFormatException {
SendRequest sendRequest = SendRequest.emptyWallet(Address.fromBase58(params, toAddress));
sendRequest.fee = Coin.ZERO;
@@ -675,6 +687,45 @@ public static String getAddressStringFromOutput(TransactionOutput output) {
}
+ /**
+ * @param serializedTransaction The serialized transaction to be added to the wallet
+ * @return The transaction we added to the wallet, which is different as the one we passed as argument!
+ * @throws VerificationException
+ */
+ public static Transaction maybeAddTxToWallet(byte[] serializedTransaction,
+ Wallet wallet,
+ TransactionConfidence.Source source) throws VerificationException {
+ Transaction tx = new Transaction(wallet.getParams(), serializedTransaction);
+ Transaction walletTransaction = wallet.getTransaction(tx.getHash());
+
+ if (walletTransaction == null) {
+ // We need to recreate the transaction otherwise we get a null pointer...
+ tx.getConfidence(Context.get()).setSource(source);
+ //wallet.maybeCommitTx(tx);
+ wallet.receivePending(tx, null, true);
+ return tx;
+ } else {
+ return walletTransaction;
+ }
+ }
+
+ public static Transaction maybeAddNetworkTxToWallet(byte[] serializedTransaction,
+ Wallet wallet) throws VerificationException {
+ return maybeAddTxToWallet(serializedTransaction, wallet, TransactionConfidence.Source.NETWORK);
+ }
+
+ public static Transaction maybeAddSelfTxToWallet(Transaction transaction,
+ Wallet wallet) throws VerificationException {
+ return maybeAddTxToWallet(transaction, wallet, TransactionConfidence.Source.SELF);
+ }
+
+ public static Transaction maybeAddTxToWallet(Transaction transaction,
+ Wallet wallet,
+ TransactionConfidence.Source source) throws VerificationException {
+ return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
+ }
+
+
///////////////////////////////////////////////////////////////////////////////////////////
// bisqWalletEventListener
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java
index 7b9390da6cd..c765697a704 100644
--- a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java
+++ b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java
@@ -322,11 +322,11 @@ public Transaction payFee(StatefulAsset statefulAsset, long listingFee) throws I
checkArgument(listingFee % 100 == 0, "Fee must be a multiple of 1 BSQ (100 satoshi).");
try {
// We create a prepared Bsq Tx for the listing fee.
- final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTx(Coin.valueOf(listingFee));
+ Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTxForAssetListing(Coin.valueOf(listingFee));
byte[] hash = AssetConsensus.getHash(statefulAsset);
byte[] opReturnData = AssetConsensus.getOpReturnData(hash);
// We add the BTC inputs for the miner fee.
- final Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
+ Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
// We sign the BSQ inputs of the final tx.
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
log.info("Asset listing fee tx: " + transaction);
diff --git a/core/src/main/java/bisq/core/dao/governance/param/Param.java b/core/src/main/java/bisq/core/dao/governance/param/Param.java
index 9d7fb1568ba..f17a0894b90 100644
--- a/core/src/main/java/bisq/core/dao/governance/param/Param.java
+++ b/core/src/main/java/bisq/core/dao/governance/param/Param.java
@@ -107,9 +107,9 @@ public enum Param {
// but can be also a burner address if we prefer to burn the BTC
@SuppressWarnings("SpellCheckingInspection")
RECIPIENT_BTC_ADDRESS(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7" : // mainnet
- BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ? "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7" : // mainnet
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ? "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7" : // daoBetaNet
BisqEnvironment.getBaseCurrencyNetwork().isTestnet() ? "2N4mVTpUZAnhm9phnxB7VrHB4aBhnWrcUrV" : // testnet
- "mquz1zFmhs7iy8qJTkhY7C9bhJ5S3g8Xim", // regtest or DAO testnet (regtest)
+ "2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w", // regtest or DAO testnet (regtest)
ParamType.ADDRESS),
// Fee for activating an asset or re-listing after deactivation due lack of trade activity. Fee per day of trial period without activity checks.
diff --git a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java
index e347656a597..cca3185a507 100644
--- a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java
+++ b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnService.java
@@ -134,11 +134,11 @@ public void onParseBlockCompleteAfterBatchProcessing(Block block) {
public Transaction burn(String preImageAsString, long amount) throws InsufficientMoneyException, TxException {
try {
// We create a prepared Bsq Tx for the burn amount
- final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTx(Coin.valueOf(amount));
+ Transaction preparedBurnFeeTx = bsqWalletService.getPreparedProofOfBurnTx(Coin.valueOf(amount));
byte[] hash = getHashFromPreImage(preImageAsString);
byte[] opReturnData = ProofOfBurnConsensus.getOpReturnData(hash);
// We add the BTC inputs for the miner fee.
- final Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
+ Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData);
// We sign the BSQ inputs of the final tx.
Transaction transaction = bsqWalletService.signTx(txWithBtcFee);
log.info("Proof of burn tx: " + transaction);
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java
index 4c3cb5f78f7..abb58e32c2d 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java
@@ -87,8 +87,9 @@ private Transaction createTransaction(R proposal) throws InsufficientMoneyExcept
try {
Coin fee = ProposalConsensus.getFee(daoStateService, daoStateService.getChainHeight());
// We create a prepared Bsq Tx for the proposal fee.
- boolean requireChangeOutput = proposal instanceof IssuanceProposal;
- Transaction preparedBurnFeeTx = bsqWalletService.getPreparedProposalTx(fee, requireChangeOutput);
+ Transaction preparedBurnFeeTx = proposal instanceof IssuanceProposal ?
+ bsqWalletService.getPreparedIssuanceTx(fee) :
+ bsqWalletService.getPreparedProposalTx(fee);
// payload does not have txId at that moment
byte[] hashOfPayload = ProposalConsensus.getHashOfPayload(proposal);
diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java
index e520e191015..0a9d09bf34d 100644
--- a/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java
+++ b/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java
@@ -17,7 +17,9 @@
package bisq.core.dao.node.parser;
+import bisq.core.app.BisqEnvironment;
import bisq.core.dao.governance.bond.BondConsensus;
+import bisq.core.dao.governance.param.Param;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.OpReturnType;
import bisq.core.dao.state.model.blockchain.TxOutput;
@@ -38,11 +40,57 @@
/**
* Checks if an output is a BSQ output and apply state change.
+ *
+ * With block 602500 (about 4 weeks after v1.2.0 release) we enforce a new rule which represents a
+ * hard fork. Not updated nodes would see an out of sync dao state hash if a relevant transaction would
+ * happen again.
+ * Further (highly unlikely) consequences could be:
+ * If the BSQ output would be sent to a BSQ address the old client would accept that even it is
+ * invalid according to the new rules. But sending such a output would require a manually crafted tx
+ * (not possible in the UI). Worst case a not updated user would buy invalid BSQ but that is not possible as we
+ * enforce update to 1.2.0 for trading a few days after release as that release introduced the new trade protocol
+ * and protection tool. Only of both both traders would have deactivated filter messages they could trade.
+ *
+ * Problem description:
+ * We did not apply the check to not allow BSQ outputs after we had detected a BTC output.
+ * The supported BSQ transactions did not support such cases anyway but we missed an edge case:
+ * A trade fee tx in case when the BTC input matches exactly the BTC output
+ * (or BTC change was <= the miner fee) and the BSQ fee was > the miner fee. Then we
+ * create a change output after the BTC output (using an address from the BTC wallet) and as
+ * available BSQ was >= as spent BSQ it was considered a valid BSQ output.
+ * There have been observed 5 such transactions where 4 got spent later to a BTC address and by that burned
+ * the pending BSQ (spending amount was higher than sending amount). One was still unspent.
+ * The BSQ was sitting in the BTC wallet so not even visible as BSQ to the user.
+ * If the user would have crafted a custom BSQ tx he could have avoided that the full trade fee was burned.
+ *
+ * Not an universal rule:
+ * We cannot enforce the rule that no BSQ output is permitted to all possible transactions because there can be cases
+ * where we need to permit this case.
+ * For instance in case we confiscate a lockupTx we have usually 2 BSQ outputs: The first one is the bond which
+ * should be confiscated and the second one is the BSQ change output.
+ * At confiscating we set the first to TxOutputType.BTC_OUTPUT but we do not want to confiscate
+ * the second BSQ change output as well. So we do not apply the rule that no BSQ is allowed once a BTC output is
+ * found. Theoretically other transactions could be confiscated as well and all BSQ tx which allow > 1 BSQ outputs
+ * would have the same issue as well if the first output gets confiscated.
+ * We also don't enforce the rule for irregular or invalid txs which are usually set and detected at the end of
+ * the tx parsing which is done in the TxParser. Blind vote and LockupTx with invalid OpReturn would be such cases
+ * where we don't want to invalidate the change output (See comments in TxParser).
+ *
+ * Most transactions created in Bisq (Proposal, blind vote and lockup,...) have only 1 or 2 BSQ
+ * outputs but we do not enforce a limit of max. 2 transactions in the parser.
+ * We leave for now that flexibility but it should not be considered as a rule. We might strengthen
+ * it any time if we find a reason for that (e.g. attack risk) and add checks that no more
+ * BSQ outputs are permitted for those txs.
+ * Some transactions like issuance, vote reveal and unlock have exactly 1 BSQ output and that rule
+ * is enforced.
*/
@Slf4j
-public class TxOutputParser {
- private final DaoStateService daoStateService;
+class TxOutputParser {
+ private static int ACTIVATE_HARD_FORK_1_HEIGHT_MAINNET = 605000;
+ private static int ACTIVATE_HARD_FORK_1_HEIGHT_TESTNET = 1583054;
+ private static int ACTIVATE_HARD_FORK_1_HEIGHT_REGTEST = 1;
+ private final DaoStateService daoStateService;
// Setters
@Getter
@Setter
@@ -66,10 +114,12 @@ public class TxOutputParser {
private Optional optionalVoteRevealUnlockStakeOutput = Optional.empty();
@Getter
private Optional optionalLockupOutput = Optional.empty();
+ private Optional optionalOpReturnIndex = Optional.empty();
// Private
private int lockTime;
private final List utxoCandidates = new ArrayList<>();
+ private boolean prohibitMoreBsqOutputs = false;
///////////////////////////////////////////////////////////////////////////////////////////
@@ -93,6 +143,8 @@ void processOpReturnOutput(TempTxOutput tempTxOutput) {
optionalOpReturnType = getMappedOpReturnType(txOutputType);
+ optionalOpReturnType.ifPresent(e -> optionalOpReturnIndex = Optional.of(tempTxOutput.getIndex()));
+
// If we have a LOCKUP opReturn output we save the lockTime to apply it later to the LOCKUP output.
// We keep that data in that other output as it makes parsing of the UNLOCK tx easier.
optionalOpReturnType.filter(opReturnType -> opReturnType == OpReturnType.LOCKUP)
@@ -100,14 +152,14 @@ void processOpReturnOutput(TempTxOutput tempTxOutput) {
}
void processTxOutput(TempTxOutput tempTxOutput) {
- if (!daoStateService.isConfiscatedOutput(tempTxOutput.getKey())) {
- // We don not expect here an opReturn output as we do not get called on the last output. Any opReturn at
- // another output index is invalid.
- if (tempTxOutput.isOpReturnOutput()) {
- tempTxOutput.setTxOutputType(TxOutputType.INVALID_OUTPUT);
- return;
- }
+ // We don not expect here an opReturn output as we do not get called on the last output. Any opReturn at
+ // another output index is invalid.
+ if (tempTxOutput.isOpReturnOutput()) {
+ tempTxOutput.setTxOutputType(TxOutputType.INVALID_OUTPUT);
+ return;
+ }
+ if (!daoStateService.isConfiscatedOutput(tempTxOutput.getKey())) {
long txOutputValue = tempTxOutput.getValue();
int index = tempTxOutput.getIndex();
if (isUnlockBondTx(tempTxOutput.getValue(), index)) {
@@ -118,8 +170,18 @@ void processTxOutput(TempTxOutput tempTxOutput) {
} else if (isBtcOutputOfBurnFeeTx(tempTxOutput)) {
// In case we have the opReturn for a burn fee tx all outputs after 1st output are considered BTC
handleBtcOutput(tempTxOutput, index);
+ } else if (isHardForkActivated(tempTxOutput) && isIssuanceCandidateTxOutput(tempTxOutput)) {
+ // After the hard fork activation we fix a bug with a transaction which would have interpreted the
+ // issuance output as BSQ if the availableInputValue was >= issuance amount.
+ // Such a tx was never created but as we don't know if it will happen before activation date we cannot
+ // enforce the bug fix which represents a rule change before the activation date.
+ handleIssuanceCandidateOutput(tempTxOutput);
} else if (availableInputValue > 0 && availableInputValue >= txOutputValue) {
- handleBsqOutput(tempTxOutput, index, txOutputValue);
+ if (isHardForkActivated(tempTxOutput) && prohibitMoreBsqOutputs) {
+ handleBtcOutput(tempTxOutput, index);
+ } else {
+ handleBsqOutput(tempTxOutput, index, txOutputValue);
+ }
} else {
handleBtcOutput(tempTxOutput, index);
}
@@ -127,6 +189,9 @@ void processTxOutput(TempTxOutput tempTxOutput) {
log.warn("TxOutput {} is confiscated ", tempTxOutput.getKey());
// We only burn that output
availableInputValue -= tempTxOutput.getValue();
+
+ // We must not set prohibitMoreBsqOutputs at confiscation transactions as optional
+ // BSQ change output (output 2) must not be confiscated.
tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
}
}
@@ -139,6 +204,7 @@ void commitUTXOCandidates() {
* This sets all outputs to BTC_OUTPUT and doesn't add any txOutputs to the unspentTxOutput map in daoStateService
*/
void invalidateUTXOCandidates() {
+ // We do not need to apply prohibitMoreBsqOutputs as all spendable outputs are set to BTC_OUTPUT anyway.
utxoCandidates.forEach(output -> output.setTxOutputType(TxOutputType.BTC_OUTPUT));
}
@@ -171,17 +237,108 @@ private void handleUnlockBondTx(TempTxOutput txOutput) {
utxoCandidates.add(txOutput);
bsqOutputFound = true;
+
+ // We do not permit more BSQ outputs after the unlock txo as we don't expect additional BSQ outputs.
+ prohibitMoreBsqOutputs = true;
}
private boolean isBtcOutputOfBurnFeeTx(TempTxOutput tempTxOutput) {
- // If we get a asset listing or proof of burn tx we have only 1 BSQ output and if the
- // burned amount is larger than the miner fee we might have a BTC output for receiving the burned funds.
- // If the burned funds are less than the miner fee a BTC input is used for miner fee and a BTC change output for
- // the remaining funds. In any case only the first output is BSQ all the others are BTC.
- return optionalOpReturnType.isPresent() &&
- (optionalOpReturnType.get() == OpReturnType.ASSET_LISTING_FEE ||
- optionalOpReturnType.get() == OpReturnType.PROOF_OF_BURN) &&
- tempTxOutput.getIndex() >= 1;
+ if (optionalOpReturnType.isPresent()) {
+ int index = tempTxOutput.getIndex();
+ switch (optionalOpReturnType.get()) {
+ case UNDEFINED:
+ break;
+ case PROPOSAL:
+ if (isHardForkActivated(tempTxOutput)) {
+ // We enforce a mandatory BSQ change output.
+ // We need that as similar to ASSET_LISTING_FEE and PROOF_OF_BURN
+ // we could not distinguish between 2 structurally same transactions otherwise (only way here
+ // would be to check the proposal fee as that is known from the params).
+ return index >= 1;
+ }
+ break;
+ case COMPENSATION_REQUEST:
+ break;
+ case REIMBURSEMENT_REQUEST:
+ break;
+ case BLIND_VOTE:
+ if (isHardForkActivated(tempTxOutput)) {
+ // After the hard fork activation we fix a bug with a transaction which would have interpreted the
+ // burned vote fee output as BSQ if the vote fee was >= miner fee.
+ // Such a tx was never created but as we don't know if it will happen before activation date we cannot
+ // enforce the bug fix which represents a rule change before the activation date.
+
+ // If it is the vote stake output we return false.
+ if (index == 0) {
+ return false;
+ }
+
+ // There must be a vote fee left
+ if (availableInputValue <= 0) {
+ return false;
+ }
+
+ // Burned BSQ output is last output before opReturn.
+ // We could have also a BSQ change output as last output before opReturn but that will
+ // be detected at blindVoteFee check.
+ // We always have the BSQ change before the burned BSQ output if both are present.
+ checkArgument(optionalOpReturnIndex.isPresent());
+ if (index != optionalOpReturnIndex.get() - 1) {
+ return false;
+ }
+
+ // Without checking the fee we would not be able to distinguish between 2 structurally same transactions, one
+ // where the output is burned BSQ and one where it is a BSQ change output.
+ long blindVoteFee = daoStateService.getParamValueAsCoin(Param.BLIND_VOTE_FEE, tempTxOutput.getBlockHeight()).value;
+ return availableInputValue == blindVoteFee;
+ }
+ case VOTE_REVEAL:
+ break;
+ case LOCKUP:
+ break;
+ case ASSET_LISTING_FEE:
+ case PROOF_OF_BURN:
+ // Asset listing fee and proof of burn tx are structurally the same.
+
+ // We need to require one BSQ change output as we could otherwise not be able to distinguish between 2
+ // structurally same transactions where only the BSQ fee is different. In case of asset listing fee and proof of
+ // burn it is a user input, so it is not know to the parser, instead we derive the burned fee from the parser.
+ // In case of proposal fee we could derive it from the params.
+
+ // Case 1: 10 BSQ fee to burn
+ // In: 17 BSQ
+ // Out: BSQ change 7 BSQ -> valid BSQ
+ // Out: OpReturn
+ // Miner fee: 1000 sat (10 BSQ burned)
+
+
+ // Case 2: 17 BSQ fee to burn
+ // In: 17 BSQ
+ // Out: burned BSQ change 7 BSQ -> BTC (7 BSQ burned)
+ // Out: OpReturn
+ // Miner fee: 1000 sat (10 BSQ burned)
+ return index >= 1;
+ }
+ }
+ return false;
+ }
+
+ private boolean isIssuanceCandidateTxOutput(TempTxOutput tempTxOutput) {
+ // If we have BSQ left as fee and we are at the second output we interpret it as a compensation request output.
+ return availableInputValue > 0 &&
+ tempTxOutput.getIndex() == 1 &&
+ optionalOpReturnType.isPresent() &&
+ (optionalOpReturnType.get() == OpReturnType.COMPENSATION_REQUEST ||
+ optionalOpReturnType.get() == OpReturnType.REIMBURSEMENT_REQUEST);
+ }
+
+ private void handleIssuanceCandidateOutput(TempTxOutput tempTxOutput) {
+ // We do not permit more BSQ outputs after the issuance candidate.
+ prohibitMoreBsqOutputs = true;
+
+ // We store the candidate but we don't apply the TxOutputType yet as we need to verify the fee after all
+ // outputs are parsed and check the phase. The TxParser will do that....
+ optionalIssuanceCandidate = Optional.of(tempTxOutput);
}
private void handleBsqOutput(TempTxOutput txOutput, int index, long txOutputValue) {
@@ -201,6 +358,9 @@ private void handleBsqOutput(TempTxOutput txOutput, int index, long txOutputValu
} else if (isFirstOutput && opReturnTypeCandidate == OpReturnType.VOTE_REVEAL) {
txOutputType = TxOutputType.VOTE_REVEAL_UNLOCK_STAKE_OUTPUT;
optionalVoteRevealUnlockStakeOutput = Optional.of(txOutput);
+
+ // We do not permit more BSQ outputs after the VOTE_REVEAL_UNLOCK_STAKE_OUTPUT.
+ prohibitMoreBsqOutputs = true;
} else if (isFirstOutput && opReturnTypeCandidate == OpReturnType.LOCKUP) {
txOutputType = TxOutputType.LOCKUP_OUTPUT;
@@ -219,20 +379,43 @@ private void handleBsqOutput(TempTxOutput txOutput, int index, long txOutputValu
}
private void handleBtcOutput(TempTxOutput txOutput, int index) {
- // If we have BSQ left as fee and we are at the second output it might be a compensation request output.
- // We store the candidate but we don't apply the TxOutputType yet as we need to verify the fee after all
- // outputs are parsed and check the phase. The TxParser will do that....
- if (availableInputValue > 0 &&
- index == 1 &&
- optionalOpReturnType.isPresent() &&
- (optionalOpReturnType.get() == OpReturnType.COMPENSATION_REQUEST ||
- optionalOpReturnType.get() == OpReturnType.REIMBURSEMENT_REQUEST)) {
- optionalIssuanceCandidate = Optional.of(txOutput);
- } else {
+ if (isHardForkActivated(txOutput)) {
txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
+
+ // For regular transactions we don't permit BSQ outputs after a BTC output was detected.
+ prohibitMoreBsqOutputs = true;
+ } else {
+ // If we have BSQ left as fee and we are at the second output it might be a compensation request output.
+ // We store the candidate but we don't apply the TxOutputType yet as we need to verify the fee after all
+ // outputs are parsed and check the phase. The TxParser will do that....
+ if (availableInputValue > 0 &&
+ index == 1 &&
+ optionalOpReturnType.isPresent() &&
+ (optionalOpReturnType.get() == OpReturnType.COMPENSATION_REQUEST ||
+ optionalOpReturnType.get() == OpReturnType.REIMBURSEMENT_REQUEST)) {
+ optionalIssuanceCandidate = Optional.of(txOutput);
+
+ // We do not permit more BSQ outputs after the issuance candidate.
+ prohibitMoreBsqOutputs = true;
+ } else {
+ txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
+
+ // For regular transactions we don't permit BSQ outputs after a BTC output was detected.
+ prohibitMoreBsqOutputs = true;
+ }
}
}
+ private boolean isHardForkActivated(TempTxOutput tempTxOutput) {
+ return tempTxOutput.getBlockHeight() >= getActivateHardFork1Height();
+ }
+
+ private int getActivateHardFork1Height() {
+ return BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? ACTIVATE_HARD_FORK_1_HEIGHT_MAINNET :
+ BisqEnvironment.getBaseCurrencyNetwork().isTestnet() ? ACTIVATE_HARD_FORK_1_HEIGHT_TESTNET :
+ ACTIVATE_HARD_FORK_1_HEIGHT_REGTEST;
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Static
diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java
index 42be945a632..1b518de77d1 100644
--- a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java
+++ b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java
@@ -217,19 +217,33 @@ private void applyTxTypeAndTxOutputType(int blockHeight, TempTx tempTx, long bsq
// We need to check if any tempTxOutput is available and if so and the OpReturn data is invalid we
// set the output to a BTC output. We must not use `if else` cases here!
if (opReturnType != OpReturnType.COMPENSATION_REQUEST && opReturnType != OpReturnType.REIMBURSEMENT_REQUEST) {
+ // We applied already the check to not permit further BSQ outputs after the issuanceCandidate in the
+ // txOutputParser so we don't need to do any additional check here when we change to BTC_OUTPUT.
txOutputParser.getOptionalIssuanceCandidate().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
}
if (opReturnType != OpReturnType.BLIND_VOTE) {
- txOutputParser.getOptionalBlindVoteLockStakeOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
+ txOutputParser.getOptionalBlindVoteLockStakeOutput().ifPresent(tempTxOutput -> {
+ // We cannot apply the rule to not allow BSQ outputs after a BTC output as the 2nd output is an
+ // optional BSQ change output and we don't want to burn that in case the opReturn is invalid.
+ tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
+ });
}
if (opReturnType != OpReturnType.VOTE_REVEAL) {
- txOutputParser.getOptionalVoteRevealUnlockStakeOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
+ txOutputParser.getOptionalVoteRevealUnlockStakeOutput().ifPresent(tempTxOutput -> {
+ // We do not apply the rule to not allow BSQ outputs after a BTC output here because we expect only
+ // one BSQ output anyway.
+ tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
+ });
}
if (opReturnType != OpReturnType.LOCKUP) {
- txOutputParser.getOptionalLockupOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
+ txOutputParser.getOptionalLockupOutput().ifPresent(tempTxOutput -> {
+ // We cannot apply the rule to not allow BSQ outputs after a BTC output as the 2nd output is an
+ // optional BSQ change output and we don't want to burn that in case the opReturn is invalid.
+ tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
+ });
}
}
@@ -259,12 +273,14 @@ private void processIssuance(int blockHeight, TempTx tempTx, long bsqFee) {
}
} else {
// This could be a valid compensation request that failed to be included in a block during the
- // correct phase due to no fault of the user. Better not burn the change as long as the BSQ inputs
+ // correct phase due to no fault of the user. We must not burn the change as long as the BSQ inputs
// cover the value of the outputs.
// We tolerate such an incorrect tx and do not burn the BSQ
tempTx.setTxType(TxType.IRREGULAR);
// Make sure the optionalIssuanceCandidate is set to BTC
+ // We applied already the check to not permit further BSQ outputs after the issuanceCandidate in the
+ // txOutputParser so we don't need to do any additional check here when we change to BTC_OUTPUT.
optionalIssuanceCandidate.ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
// Empty Optional case is a possible valid case where a random tx matches our opReturn rules but it is not a
// valid BSQ tx.
diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java
index 8d92fa17839..43d366991a1 100644
--- a/core/src/main/java/bisq/core/filter/Filter.java
+++ b/core/src/main/java/bisq/core/filter/Filter.java
@@ -98,6 +98,10 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Nullable
private final List mediators;
+ // added in v1.2.0
+ @Nullable
+ private final List refundAgents;
+
public Filter(List bannedOfferIds,
List bannedNodeAddress,
List bannedPaymentAccounts,
@@ -111,7 +115,8 @@ public Filter(List bannedOfferIds,
boolean disableDao,
@Nullable String disableDaoBelowVersion,
@Nullable String disableTradeBelowVersion,
- @Nullable List mediators) {
+ @Nullable List mediators,
+ @Nullable List refundAgents) {
this.bannedOfferIds = bannedOfferIds;
this.bannedNodeAddress = bannedNodeAddress;
this.bannedPaymentAccounts = bannedPaymentAccounts;
@@ -126,6 +131,7 @@ public Filter(List bannedOfferIds,
this.disableDaoBelowVersion = disableDaoBelowVersion;
this.disableTradeBelowVersion = disableTradeBelowVersion;
this.mediators = mediators;
+ this.refundAgents = refundAgents;
}
@@ -150,7 +156,8 @@ public Filter(List bannedOfferIds,
String signatureAsBase64,
byte[] ownerPubKeyBytes,
@Nullable Map extraDataMap,
- @Nullable List mediators) {
+ @Nullable List mediators,
+ @Nullable List refundAgents) {
this(bannedOfferIds,
bannedNodeAddress,
bannedPaymentAccounts,
@@ -164,7 +171,8 @@ public Filter(List bannedOfferIds,
disableDao,
disableDaoBelowVersion,
disableTradeBelowVersion,
- mediators);
+ mediators,
+ refundAgents);
this.signatureAsBase64 = signatureAsBase64;
this.ownerPubKeyBytes = ownerPubKeyBytes;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
@@ -198,6 +206,7 @@ public protobuf.StoragePayload toProtoMessage() {
Optional.ofNullable(disableTradeBelowVersion).ifPresent(builder::setDisableTradeBelowVersion);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
Optional.ofNullable(mediators).ifPresent(builder::addAllMediators);
+ Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents);
return protobuf.StoragePayload.newBuilder().setFilter(builder).build();
}
@@ -221,7 +230,8 @@ public static Filter fromProto(protobuf.Filter proto) {
proto.getSignatureAsBase64(),
proto.getOwnerPubKeyBytes().toByteArray(),
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
- CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()));
+ CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()),
+ CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()));
}
@@ -240,4 +250,26 @@ void setSigAndPubKey(String signatureAsBase64, PublicKey ownerPubKey) {
ownerPubKeyBytes = Sig.getPublicKeyBytes(this.ownerPubKey);
}
+
+ @Override
+ public String toString() {
+ return "Filter{" +
+ "\n bannedOfferIds=" + bannedOfferIds +
+ ",\n bannedNodeAddress=" + bannedNodeAddress +
+ ",\n bannedPaymentAccounts=" + bannedPaymentAccounts +
+ ",\n bannedCurrencies=" + bannedCurrencies +
+ ",\n bannedPaymentMethods=" + bannedPaymentMethods +
+ ",\n arbitrators=" + arbitrators +
+ ",\n seedNodes=" + seedNodes +
+ ",\n priceRelayNodes=" + priceRelayNodes +
+ ",\n preventPublicBtcNetwork=" + preventPublicBtcNetwork +
+ ",\n btcNodes=" + btcNodes +
+ ",\n extraDataMap=" + extraDataMap +
+ ",\n disableDao=" + disableDao +
+ ",\n disableDaoBelowVersion='" + disableDaoBelowVersion + '\'' +
+ ",\n disableTradeBelowVersion='" + disableTradeBelowVersion + '\'' +
+ ",\n mediators=" + mediators +
+ ",\n refundAgents=" + refundAgents +
+ "\n}";
+ }
}
diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java
index a29b43ce13c..5104929f8fc 100644
--- a/core/src/main/java/bisq/core/filter/FilterManager.java
+++ b/core/src/main/java/bisq/core/filter/FilterManager.java
@@ -297,7 +297,7 @@ private boolean verifySignature(Filter filter) {
ECKey.fromPublicOnly(HEX.decode(pubKeyAsHex)).verifyMessage(getHexFromData(filter), filter.getSignatureAsBase64());
return true;
} catch (SignatureException e) {
- log.warn("verifySignature failed");
+ log.warn("verifySignature failed. filter={}", filter);
return false;
}
}
diff --git a/core/src/main/java/bisq/core/offer/AvailabilityResult.java b/core/src/main/java/bisq/core/offer/AvailabilityResult.java
index 95895687037..2d3d749ff24 100644
--- a/core/src/main/java/bisq/core/offer/AvailabilityResult.java
+++ b/core/src/main/java/bisq/core/offer/AvailabilityResult.java
@@ -26,5 +26,6 @@ public enum AvailabilityResult {
NO_ARBITRATORS,
NO_MEDIATORS,
USER_IGNORED,
- MISSING_MANDATORY_CAPABILITY
+ MISSING_MANDATORY_CAPABILITY,
+ NO_REFUND_AGENTS
}
diff --git a/core/src/main/java/bisq/core/offer/OfferBookService.java b/core/src/main/java/bisq/core/offer/OfferBookService.java
index d99955d5327..5ebe152453c 100644
--- a/core/src/main/java/bisq/core/offer/OfferBookService.java
+++ b/core/src/main/java/bisq/core/offer/OfferBookService.java
@@ -28,7 +28,6 @@
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.common.UserThread;
-import bisq.common.app.Capability;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.common.storage.JsonFileManager;
@@ -93,10 +92,8 @@ public void onAdded(ProtectedStorageEntry data) {
if (data.getProtectedStoragePayload() instanceof OfferPayload) {
OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload);
- if (showOffer(offer)) {
- offer.setPriceFeedService(priceFeedService);
- listener.onAdded(offer);
- }
+ offer.setPriceFeedService(priceFeedService);
+ listener.onAdded(offer);
}
});
}
@@ -135,11 +132,6 @@ public void onRemoved(Offer offer) {
}
}
- private boolean showOffer(Offer offer) {
- return !OfferRestrictions.requiresUpdate() ||
- OfferRestrictions.hasOfferMandatoryCapability(offer, Capability.MEDIATION);
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// API
@@ -208,7 +200,6 @@ public List getOffers() {
offer.setPriceFeedService(priceFeedService);
return offer;
})
- .filter(this::showOffer)
.collect(Collectors.toList());
}
diff --git a/core/src/main/java/bisq/core/offer/OfferRestrictions.java b/core/src/main/java/bisq/core/offer/OfferRestrictions.java
index d7b64580bd5..856b7a6e206 100644
--- a/core/src/main/java/bisq/core/offer/OfferRestrictions.java
+++ b/core/src/main/java/bisq/core/offer/OfferRestrictions.java
@@ -17,9 +17,6 @@
package bisq.core.offer;
-import bisq.core.payment.payload.PaymentMethod;
-import bisq.core.trade.Trade;
-
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
import bisq.common.util.Utilities;
@@ -41,38 +38,6 @@ static boolean requiresUpdate() {
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
- public static boolean isOfferRisky(Offer offer) {
- return offer != null &&
- offer.isBuyOffer() &&
- PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
- isMinTradeAmountRisky(offer);
- }
-
- public static boolean isSellOfferRisky(Offer offer) {
- return offer != null &&
- PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
- isMinTradeAmountRisky(offer);
- }
-
- public static boolean isTradeRisky(Trade trade) {
- if (trade == null)
- return false;
-
- Offer offer = trade.getOffer();
- return offer != null &&
- PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
- trade.getTradeAmount() != null &&
- isAmountRisky(trade.getTradeAmount());
- }
-
- public static boolean isMinTradeAmountRisky(Offer offer) {
- return isAmountRisky(offer.getMinAmount());
- }
-
- private static boolean isAmountRisky(Coin amount) {
- return amount.isGreaterThan(TOLERATED_SMALL_TRADE_AMOUNT);
- }
-
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) {
Map extraDataMap = offer.getOfferPayload().getExtraDataMap();
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java
index bffe39c8890..a8c9406d682 100644
--- a/core/src/main/java/bisq/core/offer/OfferUtil.java
+++ b/core/src/main/java/bisq/core/offer/OfferUtil.java
@@ -32,7 +32,6 @@
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.user.Preferences;
-import bisq.core.util.BSFormatter;
import bisq.core.util.BsqFormatter;
import bisq.core.util.CoinUtil;
@@ -326,7 +325,8 @@ public static Optional getFeeInUserFiatCurrency(Coin makerFee, boolean i
public static Map getExtraDataMap(AccountAgeWitnessService accountAgeWitnessService,
ReferralIdService referralIdService,
PaymentAccount paymentAccount,
- String currencyCode) {
+ String currencyCode,
+ Preferences preferences) {
Map extraDataMap = new HashMap<>();
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
String myWitnessHashAsHex = accountAgeWitnessService.getMyWitnessHashAsHex(paymentAccount.getPaymentAccountPayload());
diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java
index 2193b30752c..ced8cce9834 100644
--- a/core/src/main/java/bisq/core/offer/OpenOffer.java
+++ b/core/src/main/java/bisq/core/offer/OpenOffer.java
@@ -65,6 +65,12 @@ public enum State {
@Nullable
private NodeAddress mediatorNodeAddress;
+ // Added v1.2.0
+ @Getter
+ @Setter
+ @Nullable
+ private NodeAddress refundAgentNodeAddress;
+
transient private Storage> storage;
public OpenOffer(Offer offer, Storage> storage) {
@@ -80,11 +86,13 @@ public OpenOffer(Offer offer, Storage> storage) {
private OpenOffer(Offer offer,
State state,
@Nullable NodeAddress arbitratorNodeAddress,
- @Nullable NodeAddress mediatorNodeAddress) {
+ @Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress) {
this.offer = offer;
this.state = state;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
if (this.state == State.RESERVED)
setState(State.AVAILABLE);
@@ -98,6 +106,7 @@ public protobuf.Tradable toProtoMessage() {
Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage()));
Optional.ofNullable(mediatorNodeAddress).ifPresent(nodeAddress -> builder.setMediatorNodeAddress(nodeAddress.toProtoMessage()));
+ Optional.ofNullable(refundAgentNodeAddress).ifPresent(nodeAddress -> builder.setRefundAgentNodeAddress(nodeAddress.toProtoMessage()));
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
}
@@ -106,7 +115,8 @@ public static Tradable fromProto(protobuf.OpenOffer proto) {
return new OpenOffer(Offer.fromProto(proto.getOffer()),
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
- proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null);
+ proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
}
@@ -175,6 +185,7 @@ public String toString() {
",\n state=" + state +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
"\n}";
}
}
diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
index 6754512c6e1..1212a7313e2 100644
--- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java
+++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
@@ -20,6 +20,7 @@
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.messages.OfferAvailabilityRequest;
@@ -29,6 +30,7 @@
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.TradableList;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.handlers.TransactionResultHandler;
@@ -51,6 +53,7 @@
import bisq.common.UserThread;
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
+import bisq.common.app.Version;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
import bisq.common.handlers.ErrorMessageHandler;
@@ -104,6 +107,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final TradeStatisticsManager tradeStatisticsManager;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
+ private final RefundAgentManager refundAgentManager;
+ private final DaoFacade daoFacade;
private final Storage> openOfferTradableListStorage;
private final Map offersToBeEdited = new HashMap<>();
private boolean stopped;
@@ -129,6 +134,8 @@ public OpenOfferManager(KeyRing keyRing,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
+ DaoFacade daoFacade,
Storage> storage) {
this.keyRing = keyRing;
this.user = user;
@@ -143,6 +150,8 @@ public OpenOfferManager(KeyRing keyRing,
this.tradeStatisticsManager = tradeStatisticsManager;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
+ this.daoFacade = daoFacade;
openOfferTradableListStorage = storage;
@@ -339,6 +348,7 @@ public void placeOffer(Offer offer,
offerBookService,
arbitratorManager,
tradeStatisticsManager,
+ daoFacade,
user);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model,
@@ -577,6 +587,7 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
AvailabilityResult availabilityResult;
NodeAddress arbitratorNodeAddress = null;
NodeAddress mediatorNodeAddress = null;
+ NodeAddress refundAgentNodeAddress = null;
if (openOfferOptional.isPresent()) {
OpenOffer openOffer = openOfferOptional.get();
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
@@ -584,41 +595,26 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
availabilityResult = AvailabilityResult.AVAILABLE;
- List acceptedArbitrators = user.getAcceptedArbitratorAddresses();
- if (acceptedArbitrators != null && !acceptedArbitrators.isEmpty()) {
- arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager).getNodeAddress();
- openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
-
- mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
- openOffer.setMediatorNodeAddress(mediatorNodeAddress);
- Capabilities supportedCapabilities = request.getSupportedCapabilities();
- if (!OfferRestrictions.requiresUpdate() ||
- (supportedCapabilities != null &&
- Capabilities.hasMandatoryCapability(supportedCapabilities, Capability.MEDIATION))) {
- try {
- // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
- // in trade price between the peers. Also here poor connectivity might cause market price API connection
- // losses and therefore an outdated market price.
- offer.checkTradePriceTolerance(request.getTakersTradePrice());
- } catch (TradePriceOutOfToleranceException e) {
- log.warn("Trade price check failed because takers price is outside out tolerance.");
- availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
- } catch (MarketPriceNotAvailableException e) {
- log.warn(e.getMessage());
- availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
- } catch (Throwable e) {
- log.warn("Trade price check failed. " + e.getMessage());
- availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
- }
- } else {
- log.warn("Taker has not mandatory capability MEDIATION");
- // Because an old peer has not AvailabilityResult.MISSING_MANDATORY_CAPABILITY and we
- // have not set the UNDEFINED fallback in AvailabilityResult the user will get a null value.
- availabilityResult = AvailabilityResult.MISSING_MANDATORY_CAPABILITY;
- }
- } else {
- log.warn("acceptedArbitrators is null or empty: acceptedArbitrators=" + acceptedArbitrators);
- availabilityResult = AvailabilityResult.NO_ARBITRATORS;
+ mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
+ openOffer.setMediatorNodeAddress(mediatorNodeAddress);
+
+ refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress();
+ openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress);
+
+ try {
+ // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
+ // in trade price between the peers. Also here poor connectivity might cause market price API connection
+ // losses and therefore an outdated market price.
+ offer.checkTradePriceTolerance(request.getTakersTradePrice());
+ } catch (TradePriceOutOfToleranceException e) {
+ log.warn("Trade price check failed because takers price is outside out tolerance.");
+ availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
+ } catch (MarketPriceNotAvailableException e) {
+ log.warn(e.getMessage());
+ availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
+ } catch (Throwable e) {
+ log.warn("Trade price check failed. " + e.getMessage());
+ availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
}
} else {
availabilityResult = AvailabilityResult.USER_IGNORED;
@@ -634,7 +630,8 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
availabilityResult,
arbitratorNodeAddress,
- mediatorNodeAddress);
+ mediatorNodeAddress,
+ refundAgentNodeAddress);
log.info("Send {} with offerId {} and uid {} to peer {}",
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
offerAvailabilityResponse.getUid(), peer);
@@ -715,14 +712,17 @@ public void onFault(String errorMessage) {
private void maybeUpdatePersistedOffers() {
// We need to clone to avoid ConcurrentModificationException
ArrayList openOffersClone = new ArrayList<>(openOffers.getList());
- openOffersClone.forEach(openOffer -> {
- Offer originalOffer = openOffer.getOffer();
+ openOffersClone.forEach(originalOpenOffer -> {
+ Offer originalOffer = originalOpenOffer.getOffer();
OfferPayload originalOfferPayload = originalOffer.getOfferPayload();
- // We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and want to rewrite a
- // persisted offer after the user has updated to 1.1.6 so their offer will be accepted by the network.
+ // We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and
+ // Capability.REFUND_AGENT in v1.2.0 and want to rewrite a
+ // persisted offer after the user has updated to 1.2.0 so their offer will be accepted by the network.
- if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION)) {
+ if (originalOfferPayload.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION ||
+ !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION) ||
+ !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.REFUND_AGENT)) {
// We rewrite our offer with the additional capabilities entry
Map originalExtraDataMap = originalOfferPayload.getExtraDataMap();
@@ -735,6 +735,9 @@ private void maybeUpdatePersistedOffers() {
// We overwrite any entry with our current capabilities
updatedExtraDataMap.put(OfferPayload.CAPABILITIES, Capabilities.app.toStringList());
+ // We update the trade protocol version
+ int protocolVersion = Version.TRADE_PROTOCOL_VERSION;
+
OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(),
originalOfferPayload.getDate(),
originalOfferPayload.getOwnerNodeAddress(),
@@ -772,17 +775,17 @@ private void maybeUpdatePersistedOffers() {
originalOfferPayload.isPrivateOffer(),
originalOfferPayload.getHashOfChallenge(),
updatedExtraDataMap,
- originalOfferPayload.getProtocolVersion());
+ protocolVersion);
- // Save states from original data to use the for updated
+ // Save states from original data to use for the updated
Offer.State originalOfferState = originalOffer.getState();
- OpenOffer.State originalOpenOfferState = openOffer.getState();
+ OpenOffer.State originalOpenOfferState = originalOpenOffer.getState();
// remove old offer
originalOffer.setState(Offer.State.REMOVED);
- openOffer.setState(OpenOffer.State.CANCELED);
- openOffer.setStorage(openOfferTradableListStorage);
- openOffers.remove(openOffer);
+ originalOpenOffer.setState(OpenOffer.State.CANCELED);
+ originalOpenOffer.setStorage(openOfferTradableListStorage);
+ openOffers.remove(originalOpenOffer);
// Create new Offer
Offer updatedOffer = new Offer(updatedPayload);
@@ -794,7 +797,7 @@ private void maybeUpdatePersistedOffers() {
updatedOpenOffer.setStorage(openOfferTradableListStorage);
openOffers.add(updatedOpenOffer);
- log.info("Converted offer to support new Capability.MEDIATION capability. id={}", originalOffer.getId());
+ log.info("Converted offer to support new Capability.MEDIATION and Capability.REFUND_AGENT capability. id={}", originalOffer.getId());
}
});
}
diff --git a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
index bce44e6cbc6..2639261d6f4 100644
--- a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
+++ b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
@@ -57,6 +57,13 @@ public static T getLeastUsedMediator(TradeStatisticsMan
TradeStatistics2.MEDIATOR_ADDRESS);
}
+ public static T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
+ DisputeAgentManager disputeAgentManager) {
+ return getLeastUsedDisputeAgent(tradeStatisticsManager,
+ disputeAgentManager,
+ TradeStatistics2.REFUND_AGENT_ADDRESS);
+ }
+
private static T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager disputeAgentManager,
String extraMapKey) {
diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java
index 2b55c18813a..aad97d33f42 100644
--- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java
+++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java
@@ -60,6 +60,12 @@ public class OfferAvailabilityModel implements Model {
@Getter
private NodeAddress selectedMediator;
+ // Added in v1.2.0
+ @Nullable
+ @Setter
+ @Getter
+ private NodeAddress selectedRefundAgent;
+
public OfferAvailabilityModel(Offer offer,
PubKeyRing pubKeyRing,
diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
index 1690ddc34ae..6862661b391 100644
--- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
+++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
@@ -57,12 +57,16 @@ protected void run() {
offer.setState(Offer.State.AVAILABLE);
model.setSelectedArbitrator(offerAvailabilityResponse.getArbitrator());
+
NodeAddress mediator = offerAvailabilityResponse.getMediator();
if (mediator == null) {
// We do not get a mediator from old clients so we need to handle the null case.
mediator = DisputeAgentSelection.getLeastUsedMediator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
}
model.setSelectedMediator(mediator);
+
+ model.setSelectedRefundAgent(offerAvailabilityResponse.getRefundAgent());
+
complete();
} catch (Throwable t) {
offer.setErrorMessage("An error occurred.\n" +
diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java
index f71713c7a01..41b5aa0fcc7 100644
--- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java
+++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java
@@ -49,17 +49,23 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable
private final NodeAddress mediator;
+ // Added v1.2.0
+ @Nullable
+ private final NodeAddress refundAgent;
+
public OfferAvailabilityResponse(String offerId,
AvailabilityResult availabilityResult,
NodeAddress arbitrator,
- NodeAddress mediator) {
+ NodeAddress mediator,
+ NodeAddress refundAgent) {
this(offerId,
availabilityResult,
Capabilities.app,
Version.getP2PMessageVersion(),
UUID.randomUUID().toString(),
arbitrator,
- mediator);
+ mediator,
+ refundAgent);
}
@@ -73,24 +79,27 @@ private OfferAvailabilityResponse(String offerId,
int messageVersion,
@Nullable String uid,
NodeAddress arbitrator,
- @Nullable NodeAddress mediator) {
+ @Nullable NodeAddress mediator,
+ @Nullable NodeAddress refundAgent) {
super(messageVersion, offerId, uid);
this.availabilityResult = availabilityResult;
this.supportedCapabilities = supportedCapabilities;
this.arbitrator = arbitrator;
this.mediator = mediator;
+ this.refundAgent = refundAgent;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder()
.setOfferId(offerId)
- .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()))
- .setArbitrator(arbitrator.toProtoMessage());
+ .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()));
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
Optional.ofNullable(mediator).ifPresent(e -> builder.setMediator(mediator.toProtoMessage()));
+ Optional.ofNullable(refundAgent).ifPresent(e -> builder.setRefundAgent(refundAgent.toProtoMessage()));
+ Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage()));
return getNetworkEnvelopeBuilder()
.setOfferAvailabilityResponse(builder)
@@ -103,7 +112,8 @@ public static OfferAvailabilityResponse fromProto(protobuf.OfferAvailabilityResp
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
messageVersion,
proto.getUid().isEmpty() ? null : proto.getUid(),
- NodeAddress.fromProto(proto.getArbitrator()),
- proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null);
+ proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null,
+ proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null,
+ proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null);
}
}
diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java
index e63e31ecdf2..294f7cd19ff 100644
--- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java
+++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java
@@ -20,6 +20,7 @@
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
@@ -48,6 +49,7 @@ public class PlaceOfferModel implements Model {
private final OfferBookService offerBookService;
private final ArbitratorManager arbitratorManager;
private final TradeStatisticsManager tradeStatisticsManager;
+ private final DaoFacade daoFacade;
private final User user;
// Mutable
@@ -65,6 +67,7 @@ public PlaceOfferModel(Offer offer,
OfferBookService offerBookService,
ArbitratorManager arbitratorManager,
TradeStatisticsManager tradeStatisticsManager,
+ DaoFacade daoFacade,
User user) {
this.offer = offer;
this.reservedFundsForOffer = reservedFundsForOffer;
@@ -75,6 +78,7 @@ public PlaceOfferModel(Offer offer,
this.offerBookService = offerBookService;
this.arbitratorManager = arbitratorManager;
this.tradeStatisticsManager = tradeStatisticsManager;
+ this.daoFacade = daoFacade;
this.user = user;
}
diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java
index f7e35d82a08..da2b60a0ace 100644
--- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java
+++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java
@@ -25,11 +25,10 @@
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletService;
import bisq.core.dao.exceptions.DaoDisabledException;
+import bisq.core.dao.governance.param.Param;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.offer.Offer;
-import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.placeoffer.PlaceOfferModel;
-import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.common.UserThread;
import bisq.common.taskrunner.Task;
@@ -45,7 +44,6 @@
public class CreateMakerFeeTx extends Task {
private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class);
- private Transaction tradeFeeTx = null;
@SuppressWarnings({"unused"})
public CreateMakerFeeTx(TaskRunner taskHandler, PlaceOfferModel model) {
@@ -62,17 +60,15 @@ protected void run() {
String id = offer.getId();
BtcWalletService walletService = model.getWalletService();
- Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(),
- model.getArbitratorManager());
-
Address fundingAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress();
Address reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
Address changeAddress = walletService.getFreshAddressEntry().getAddress();
- final TradeWalletService tradeWalletService = model.getTradeWalletService();
+ TradeWalletService tradeWalletService = model.getTradeWalletService();
+ String feeReceiver = model.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
if (offer.isCurrencyForMakerFeeBtc()) {
- tradeFeeTx = tradeWalletService.createBtcTradingFeeTx(
+ tradeWalletService.createBtcTradingFeeTx(
fundingAddress,
reservedForTradeAddress,
changeAddress,
@@ -80,7 +76,7 @@ protected void run() {
model.isUseSavingsWallet(),
offer.getMakerFee(),
offer.getTxFee(),
- arbitrator.getBtcAddress(),
+ feeReceiver,
true,
new TxBroadcaster.Callback() {
@Override
@@ -113,7 +109,7 @@ public void onFailure(TxBroadcastException exception) {
});
} else {
final BsqWalletService bsqWalletService = model.getBsqWalletService();
- Transaction preparedBurnFeeTx = model.getBsqWalletService().getPreparedBurnFeeTx(offer.getMakerFee());
+ Transaction preparedBurnFeeTx = model.getBsqWalletService().getPreparedTradeFeeTx(offer.getMakerFee());
Transaction txWithBsqFee = tradeWalletService.completeBsqTradingFeeTx(preparedBurnFeeTx,
fundingAddress,
reservedForTradeAddress,
diff --git a/core/src/main/java/bisq/core/payment/PaymentAccount.java b/core/src/main/java/bisq/core/payment/PaymentAccount.java
index 2a4b6bd354e..bec9f0a111d 100644
--- a/core/src/main/java/bisq/core/payment/PaymentAccount.java
+++ b/core/src/main/java/bisq/core/payment/PaymentAccount.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -148,6 +149,20 @@ public TradeCurrency getSingleTradeCurrency() {
return null;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PaymentAccount that = (PaymentAccount) o;
+ return creationDate == that.creationDate &&
+ id.equals(that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, creationDate);
+ }
+
public long getMaxTradePeriod() {
return paymentMethod.getMaxTradePeriod();
}
diff --git a/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java
index b4bba4f4e76..fbc4d491bf9 100644
--- a/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java
+++ b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java
@@ -17,11 +17,9 @@
package bisq.core.payment;
-import bisq.core.account.witness.AccountAgeRestrictions;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.locale.Country;
import bisq.core.offer.Offer;
-import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.payload.PaymentMethod;
import javafx.collections.FXCollections;
@@ -41,57 +39,8 @@
@Slf4j
public class PaymentAccountUtil {
- public static boolean isRiskyBuyOfferWithImmatureAccountAge(Offer offer, AccountAgeWitnessService accountAgeWitnessService) {
- return OfferRestrictions.isOfferRisky(offer) &&
- AccountAgeRestrictions.isMakersAccountAgeImmature(accountAgeWitnessService, offer);
- }
-
- public static boolean isSellOfferAndAllTakerPaymentAccountsForOfferImmature(Offer offer,
- Collection takerPaymentAccounts,
- AccountAgeWitnessService accountAgeWitnessService) {
- if (offer.isBuyOffer()) {
- return false;
- }
-
- if (!OfferRestrictions.isSellOfferRisky(offer)) {
- return false;
- }
-
- for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
- if (isTakerAccountForOfferMature(offer, takerPaymentAccount, accountAgeWitnessService))
- return false;
- }
- return true;
- }
-
- private static boolean isTakerAccountForOfferMature(Offer offer,
- PaymentAccount takerPaymentAccount,
- AccountAgeWitnessService accountAgeWitnessService) {
- return !PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) ||
- !OfferRestrictions.isMinTradeAmountRisky(offer) ||
- (isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount) &&
- !AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, takerPaymentAccount));
- }
-
- public static boolean hasMakerAnyMatureAccountForBuyOffer(Collection makerPaymentAccounts,
- AccountAgeWitnessService accountAgeWitnessService) {
- for (PaymentAccount makerPaymentAccount : makerPaymentAccounts) {
- if (hasMyMatureAccountForBuyOffer(makerPaymentAccount, accountAgeWitnessService))
- return true;
- }
- return false;
- }
-
- private static boolean hasMyMatureAccountForBuyOffer(PaymentAccount myPaymentAccount,
- AccountAgeWitnessService accountAgeWitnessService) {
- if (myPaymentAccount.selectedTradeCurrency == null)
- return false;
- return !PaymentMethod.hasChargebackRisk(myPaymentAccount.getPaymentMethod(),
- myPaymentAccount.selectedTradeCurrency.getCode()) ||
- !AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, myPaymentAccount);
- }
-
- public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer, Collection takerPaymentAccounts) {
+ public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer,
+ Collection takerPaymentAccounts) {
for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
if (isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount))
return true;
@@ -105,11 +54,21 @@ public static ObservableList getPossiblePaymentAccounts(Offer of
ObservableList result = FXCollections.observableArrayList();
result.addAll(paymentAccounts.stream()
.filter(paymentAccount -> isTakerPaymentAccountValidForOffer(offer, paymentAccount))
- .filter(paymentAccount -> offer.isBuyOffer() || isTakerAccountForOfferMature(offer, paymentAccount, accountAgeWitnessService))
+ .filter(paymentAccount -> isAmountValidForOffer(offer, paymentAccount, accountAgeWitnessService))
.collect(Collectors.toList()));
return result;
}
+ // Return true if paymentAccount can take this offer
+ public static boolean isAmountValidForOffer(Offer offer,
+ PaymentAccount paymentAccount,
+ AccountAgeWitnessService accountAgeWitnessService) {
+ boolean hasChargebackRisk = PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode());
+ boolean hasValidAccountAgeWitness = accountAgeWitnessService.getMyTradeLimit(paymentAccount,
+ offer.getCurrencyCode(), offer.getMirroredDirection()) >= offer.getAmount().value;
+ return !hasChargebackRisk || hasValidAccountAgeWitness;
+ }
+
// TODO might be used to show more details if we get payment methods updates with diff. limits
public static String getInfoForMismatchingPaymentMethodLimits(Offer offer, PaymentAccount paymentAccount) {
// dont translate atm as it is not used so far in the UI just for logs
diff --git a/core/src/main/java/bisq/core/payment/PaymentAccounts.java b/core/src/main/java/bisq/core/payment/PaymentAccounts.java
index e6fe3a32bb4..c27c883449d 100644
--- a/core/src/main/java/bisq/core/payment/PaymentAccounts.java
+++ b/core/src/main/java/bisq/core/payment/PaymentAccounts.java
@@ -37,17 +37,17 @@ class PaymentAccounts {
private static final Logger log = LoggerFactory.getLogger(PaymentAccounts.class);
private final Set accounts;
- private final AccountAgeWitnessService service;
+ private final AccountAgeWitnessService accountAgeWitnessService;
private final BiFunction validator;
- PaymentAccounts(Set accounts, AccountAgeWitnessService service) {
- this(accounts, service, PaymentAccountUtil::isTakerPaymentAccountValidForOffer);
+ PaymentAccounts(Set accounts, AccountAgeWitnessService accountAgeWitnessService) {
+ this(accounts, accountAgeWitnessService, PaymentAccountUtil::isTakerPaymentAccountValidForOffer);
}
- PaymentAccounts(Set accounts, AccountAgeWitnessService service,
+ PaymentAccounts(Set accounts, AccountAgeWitnessService accountAgeWitnessService,
BiFunction validator) {
this.accounts = accounts;
- this.service = service;
+ this.accountAgeWitnessService = accountAgeWitnessService;
this.validator = validator;
}
@@ -61,7 +61,7 @@ PaymentAccount getOldestPaymentAccountForOffer(Offer offer) {
}
private List sortValidAccounts(Offer offer) {
- Comparator comparator = this::compareByAge;
+ Comparator comparator = this::compareByTradeLimit;
return accounts.stream()
.filter(account -> validator.apply(offer, account))
.sorted(comparator.reversed())
@@ -78,7 +78,7 @@ private void logAccounts(List accounts) {
StringBuilder message = new StringBuilder("Valid accounts: \n");
for (PaymentAccount account : accounts) {
String accountName = account.getAccountName();
- String witnessHex = service.getMyWitnessHashAsHex(account.getPaymentAccountPayload());
+ String witnessHex = accountAgeWitnessService.getMyWitnessHashAsHex(account.getPaymentAccountPayload());
message.append("name = ")
.append(accountName)
@@ -91,15 +91,24 @@ private void logAccounts(List accounts) {
}
}
- private int compareByAge(PaymentAccount left, PaymentAccount right) {
- AccountAgeWitness leftWitness = service.getMyWitness(left.getPaymentAccountPayload());
- AccountAgeWitness rightWitness = service.getMyWitness(right.getPaymentAccountPayload());
+ // Accounts ranked by trade limit
+ private int compareByTradeLimit(PaymentAccount left, PaymentAccount right) {
+ // Mature accounts count as infinite sign age
+ if (accountAgeWitnessService.myHasTradeLimitException(left)) {
+ return !accountAgeWitnessService.myHasTradeLimitException(right) ? 1 : 0;
+ }
+ if (accountAgeWitnessService.myHasTradeLimitException(right)) {
+ return -1;
+ }
+
+ AccountAgeWitness leftWitness = accountAgeWitnessService.getMyWitness(left.getPaymentAccountPayload());
+ AccountAgeWitness rightWitness = accountAgeWitnessService.getMyWitness(right.getPaymentAccountPayload());
Date now = new Date();
- long leftAge = service.getAccountAge(leftWitness, now);
- long rightAge = service.getAccountAge(rightWitness, now);
+ long leftSignAge = accountAgeWitnessService.getWitnessSignAge(leftWitness, now);
+ long rightSignAge = accountAgeWitnessService.getWitnessSignAge(rightWitness, now);
- return Long.compare(leftAge, rightAge);
+ return Long.compare(leftSignAge, rightSignAge);
}
}
diff --git a/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java b/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java
index 983d0177e62..d39787702a7 100644
--- a/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java
+++ b/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java
@@ -19,6 +19,7 @@
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
+import bisq.core.locale.TradeCurrency;
import bisq.core.payment.TradeLimits;
import bisq.common.proto.persistable.PersistablePayload;
@@ -322,6 +323,15 @@ public boolean isAsset() {
return this.equals(BLOCK_CHAINS_INSTANT) || this.equals(BLOCK_CHAINS);
}
+ public static boolean hasChargebackRisk(PaymentMethod paymentMethod, List tradeCurrencies) {
+ return tradeCurrencies.stream()
+ .anyMatch(tradeCurrency -> hasChargebackRisk(paymentMethod, tradeCurrency.getCode()));
+ }
+
+ public static boolean hasChargebackRisk(PaymentMethod paymentMethod) {
+ return hasChargebackRisk(paymentMethod, CurrencyUtil.getMatureMarketCurrencies());
+ }
+
public static boolean hasChargebackRisk(PaymentMethod paymentMethod, String currencyCode) {
if (paymentMethod == null)
return false;
diff --git a/core/src/main/java/bisq/core/presentation/SupportTicketsPresentation.java b/core/src/main/java/bisq/core/presentation/SupportTicketsPresentation.java
index be98548140a..8e1764f635e 100644
--- a/core/src/main/java/bisq/core/presentation/SupportTicketsPresentation.java
+++ b/core/src/main/java/bisq/core/presentation/SupportTicketsPresentation.java
@@ -19,6 +19,7 @@
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
+import bisq.core.support.dispute.refund.RefundManager;
import javax.inject.Inject;
@@ -27,51 +28,39 @@
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
-import java.util.concurrent.atomic.AtomicInteger;
-
import lombok.Getter;
public class SupportTicketsPresentation {
- @Getter
- private final StringProperty numOpenArbitrationTickets = new SimpleStringProperty();
- @Getter
- private final BooleanProperty showOpenArbitrationTicketsNotification = new SimpleBooleanProperty();
-
- @Getter
- private final StringProperty numOpenMediationTickets = new SimpleStringProperty();
- @Getter
- private final BooleanProperty showOpenMediationTicketsNotification = new SimpleBooleanProperty();
-
@Getter
private final StringProperty numOpenSupportTickets = new SimpleStringProperty();
@Getter
private final BooleanProperty showOpenSupportTicketsNotification = new SimpleBooleanProperty();
+
@org.jetbrains.annotations.NotNull
private final ArbitrationManager arbitrationManager;
@org.jetbrains.annotations.NotNull
private final MediationManager mediationManager;
+ @org.jetbrains.annotations.NotNull
+ private final RefundManager refundManager;
@Inject
- public SupportTicketsPresentation(ArbitrationManager arbitrationManager, MediationManager mediationManager) {
+ public SupportTicketsPresentation(ArbitrationManager arbitrationManager,
+ MediationManager mediationManager,
+ RefundManager refundManager) {
this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
+ this.refundManager = refundManager;
arbitrationManager.getNumOpenDisputes().addListener((observable, oldValue, newValue) -> onChange());
mediationManager.getNumOpenDisputes().addListener((observable, oldValue, newValue) -> onChange());
+ refundManager.getNumOpenDisputes().addListener((observable, oldValue, newValue) -> onChange());
}
private void onChange() {
- AtomicInteger openArbitrationDisputes = new AtomicInteger(arbitrationManager.getNumOpenDisputes().get());
- int arbitrationTickets = openArbitrationDisputes.get();
- numOpenArbitrationTickets.set(String.valueOf(arbitrationTickets));
- showOpenArbitrationTicketsNotification.set(arbitrationTickets > 0);
-
- AtomicInteger openMediationDisputes = new AtomicInteger(mediationManager.getNumOpenDisputes().get());
- int mediationTickets = openMediationDisputes.get();
- numOpenMediationTickets.set(String.valueOf(mediationTickets));
- showOpenMediationTicketsNotification.set(mediationTickets > 0);
+ int supportTickets = arbitrationManager.getNumOpenDisputes().get() +
+ mediationManager.getNumOpenDisputes().get() +
+ refundManager.getNumOpenDisputes().get();
- int supportTickets = arbitrationTickets + mediationTickets;
numOpenSupportTickets.set(String.valueOf(supportTickets));
showOpenSupportTicketsNotification.set(supportTickets > 0);
}
diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
index 9d84408420b..91791a5ba39 100644
--- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
+++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
@@ -44,14 +44,19 @@
import bisq.core.support.dispute.messages.DisputeResultMessage;
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
+import bisq.core.support.dispute.refund.refundagent.RefundAgent;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
-import bisq.core.trade.messages.PayDepositRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
-import bisq.core.trade.messages.PublishDepositTxRequest;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.statistics.TradeStatistics;
import bisq.network.p2p.AckMessage;
@@ -89,7 +94,6 @@
@Slf4j
@Singleton
public class CoreNetworkProtoResolver extends CoreProtoResolver implements NetworkProtoResolver {
-
@Inject
public CoreNetworkProtoResolver() {
}
@@ -134,16 +138,28 @@ public NetworkEnvelope fromProto(protobuf.NetworkEnvelope proto) throws Protobuf
case PREFIXED_SEALED_AND_SIGNED_MESSAGE:
return PrefixedSealedAndSignedMessage.fromProto(proto.getPrefixedSealedAndSignedMessage(), messageVersion);
- case PAY_DEPOSIT_REQUEST:
- return PayDepositRequest.fromProto(proto.getPayDepositRequest(), this, messageVersion);
- case DEPOSIT_TX_PUBLISHED_MESSAGE:
- return DepositTxPublishedMessage.fromProto(proto.getDepositTxPublishedMessage(), messageVersion);
- case PUBLISH_DEPOSIT_TX_REQUEST:
- return PublishDepositTxRequest.fromProto(proto.getPublishDepositTxRequest(), this, messageVersion);
+ // trade protocol messages
+ case INPUTS_FOR_DEPOSIT_TX_REQUEST:
+ return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
+ case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
+ return InputsForDepositTxResponse.fromProto(proto.getInputsForDepositTxResponse(), this, messageVersion);
+ case DEPOSIT_TX_MESSAGE:
+ return DepositTxMessage.fromProto(proto.getDepositTxMessage(), messageVersion);
+ case DELAYED_PAYOUT_TX_SIGNATURE_REQUEST:
+ return DelayedPayoutTxSignatureRequest.fromProto(proto.getDelayedPayoutTxSignatureRequest(), messageVersion);
+ case DELAYED_PAYOUT_TX_SIGNATURE_RESPONSE:
+ return DelayedPayoutTxSignatureResponse.fromProto(proto.getDelayedPayoutTxSignatureResponse(), messageVersion);
+ case DEPOSIT_TX_AND_DELAYED_PAYOUT_TX_MESSAGE:
+ return DepositTxAndDelayedPayoutTxMessage.fromProto(proto.getDepositTxAndDelayedPayoutTxMessage(), messageVersion);
+
case COUNTER_CURRENCY_TRANSFER_STARTED_MESSAGE:
return CounterCurrencyTransferStartedMessage.fromProto(proto.getCounterCurrencyTransferStartedMessage(), messageVersion);
+
case PAYOUT_TX_PUBLISHED_MESSAGE:
return PayoutTxPublishedMessage.fromProto(proto.getPayoutTxPublishedMessage(), messageVersion);
+ case PEER_PUBLISHED_DELAYED_PAYOUT_TX_MESSAGE:
+ return PeerPublishedDelayedPayoutTxMessage.fromProto(proto.getPeerPublishedDelayedPayoutTxMessage(), messageVersion);
+
case MEDIATED_PAYOUT_TX_SIGNATURE_MESSAGE:
return MediatedPayoutTxSignatureMessage.fromProto(proto.getMediatedPayoutTxSignatureMessage(), messageVersion);
case MEDIATED_PAYOUT_TX_PUBLISHED_MESSAGE:
@@ -236,6 +252,8 @@ public NetworkPayload fromProto(protobuf.StoragePayload proto) {
return Arbitrator.fromProto(proto.getArbitrator());
case MEDIATOR:
return Mediator.fromProto(proto.getMediator());
+ case REFUND_AGENT:
+ return RefundAgent.fromProto(proto.getRefundAgent());
case FILTER:
return Filter.fromProto(proto.getFilter());
case TRADE_STATISTICS:
diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
index 796572b0c3f..321b236408f 100644
--- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
+++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
@@ -37,6 +37,7 @@
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.ArbitrationDisputeList;
import bisq.core.support.dispute.mediation.MediationDisputeList;
+import bisq.core.support.dispute.refund.RefundDisputeList;
import bisq.core.trade.TradableList;
import bisq.core.trade.statistics.TradeStatistics2Store;
import bisq.core.user.PreferencesPayload;
@@ -110,6 +111,10 @@ public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) {
return MediationDisputeList.fromProto(proto.getMediationDisputeList(),
this,
new Storage<>(storageDir, this, corruptedDatabaseFilesHandler));
+ case REFUND_DISPUTE_LIST:
+ return RefundDisputeList.fromProto(proto.getRefundDisputeList(),
+ this,
+ new Storage<>(storageDir, this, corruptedDatabaseFilesHandler));
case PREFERENCES_PAYLOAD:
return PreferencesPayload.fromProto(proto.getPreferencesPayload(), this);
case USER_PAYLOAD:
diff --git a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
index 757f1f09df7..680c984d27b 100644
--- a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
+++ b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
@@ -37,7 +37,9 @@ static void setSupportedCapabilities(BisqEnvironment bisqEnvironment) {
Capability.BLIND_VOTE,
Capability.DAO_STATE,
Capability.BUNDLE_OF_ENVELOPES,
- Capability.MEDIATION
+ Capability.MEDIATION,
+ Capability.SIGNED_ACCOUNT_AGE_WITNESS,
+ Capability.REFUND_AGENT
);
if (BisqEnvironment.isDaoActivated(bisqEnvironment)) {
diff --git a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
index 1a068e82a8a..f5a0e39c6b3 100644
--- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
+++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
@@ -29,6 +29,7 @@
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.dispute.arbitration.ArbitrationDisputeListService;
import bisq.core.support.dispute.mediation.MediationDisputeListService;
+import bisq.core.support.dispute.refund.RefundDisputeListService;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
@@ -63,6 +64,7 @@ public static List getPersistedDataHosts(Injector injector) {
persistedDataHosts.add(injector.getInstance(FailedTradesManager.class));
persistedDataHosts.add(injector.getInstance(ArbitrationDisputeListService.class));
persistedDataHosts.add(injector.getInstance(MediationDisputeListService.class));
+ persistedDataHosts.add(injector.getInstance(RefundDisputeListService.class));
persistedDataHosts.add(injector.getInstance(P2PService.class));
if (injector.getInstance(Key.get(Boolean.class, Names.named(DaoOptionKeys.DAO_ACTIVATED)))) {
diff --git a/core/src/main/java/bisq/core/support/SupportType.java b/core/src/main/java/bisq/core/support/SupportType.java
index 4d13c7848ec..cd10cc024ff 100644
--- a/core/src/main/java/bisq/core/support/SupportType.java
+++ b/core/src/main/java/bisq/core/support/SupportType.java
@@ -22,7 +22,8 @@
public enum SupportType {
ARBITRATION, // Need to be at index 0 to be the fall back for old clients
MEDIATION,
- TRADE;
+ TRADE,
+ REFUND;
public static SupportType fromProto(
protobuf.SupportType type) {
diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java
index 3348bbf963f..d5f45a7a185 100644
--- a/core/src/main/java/bisq/core/support/dispute/Dispute.java
+++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java
@@ -48,6 +48,7 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@@ -80,7 +81,7 @@ public final class Dispute implements NetworkPayload {
private final String makerContractSignature;
@Nullable
private final String takerContractSignature;
- private final PubKeyRing agentPubKeyRing; // arbitrator or mediator
+ private final PubKeyRing agentPubKeyRing; // dispute agent
private final boolean isSupportTicket;
private final ObservableList chatMessages = FXCollections.observableArrayList();
private BooleanProperty isClosedProperty = new SimpleBooleanProperty();
@@ -92,6 +93,16 @@ public final class Dispute implements NetworkPayload {
transient private Storage extends DisputeList> storage;
+ // Added v1.2.0
+ private SupportType supportType;
+ // Only used at refundAgent so that he knows how the mediator resolved the case
+ @Setter
+ @Nullable
+ private String mediatorsDisputeResult;
+ @Setter
+ @Nullable
+ private String delayedPayoutTxId;
+
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@@ -114,7 +125,8 @@ public Dispute(Storage extends DisputeList> storage,
@Nullable String makerContractSignature,
@Nullable String takerContractSignature,
PubKeyRing agentPubKeyRing,
- boolean isSupportTicket) {
+ boolean isSupportTicket,
+ SupportType supportType) {
this(tradeId,
traderId,
disputeOpenerIsBuyer,
@@ -131,7 +143,8 @@ public Dispute(Storage extends DisputeList> storage,
makerContractSignature,
takerContractSignature,
agentPubKeyRing,
- isSupportTicket);
+ isSupportTicket,
+ supportType);
this.storage = storage;
openingDate = new Date().getTime();
}
@@ -157,7 +170,8 @@ public Dispute(String tradeId,
@Nullable String makerContractSignature,
@Nullable String takerContractSignature,
PubKeyRing agentPubKeyRing,
- boolean isSupportTicket) {
+ boolean isSupportTicket,
+ SupportType supportType) {
this.tradeId = tradeId;
this.traderId = traderId;
this.disputeOpenerIsBuyer = disputeOpenerIsBuyer;
@@ -175,6 +189,7 @@ public Dispute(String tradeId,
this.takerContractSignature = takerContractSignature;
this.agentPubKeyRing = agentPubKeyRing;
this.isSupportTicket = isSupportTicket;
+ this.supportType = supportType;
id = tradeId + "_" + traderId;
}
@@ -210,11 +225,14 @@ public protobuf.Dispute toProtoMessage() {
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
Optional.ofNullable(disputeResultProperty.get()).ifPresent(result -> builder.setDisputeResult(disputeResultProperty.get().toProtoMessage()));
+ Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType)));
+ Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
+ Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId));
return builder.build();
}
public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver coreProtoResolver) {
- final Dispute dispute = new Dispute(proto.getTradeId(),
+ Dispute dispute = new Dispute(proto.getTradeId(),
proto.getTraderId(),
proto.getDisputeOpenerIsBuyer(),
proto.getDisputeOpenerIsMaker(),
@@ -230,7 +248,8 @@ public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr
ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()),
ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()),
PubKeyRing.fromProto(proto.getAgentPubKeyRing()),
- proto.getIsSupportTicket());
+ proto.getIsSupportTicket(),
+ SupportType.fromProto(proto.getSupportType()));
dispute.chatMessages.addAll(proto.getChatMessageList().stream()
.map(ChatMessage::fromPayloadProto)
@@ -241,6 +260,17 @@ public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr
if (proto.hasDisputeResult())
dispute.disputeResultProperty.set(DisputeResult.fromProto(proto.getDisputeResult()));
dispute.disputePayoutTxId = ProtoUtil.stringOrNullFromProto(proto.getDisputePayoutTxId());
+
+ String mediatorsDisputeResult = proto.getMediatorsDisputeResult();
+ if (!mediatorsDisputeResult.isEmpty()) {
+ dispute.setMediatorsDisputeResult(mediatorsDisputeResult);
+ }
+
+ String delayedPayoutTxId = proto.getDelayedPayoutTxId();
+ if (!delayedPayoutTxId.isEmpty()) {
+ dispute.setDelayedPayoutTxId(delayedPayoutTxId);
+ }
+
return dispute;
}
@@ -258,10 +288,6 @@ public void addAndPersistChatMessage(ChatMessage chatMessage) {
}
}
- public boolean isMediationDispute() {
- return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION;
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
@@ -293,6 +319,9 @@ public void setDisputePayoutTxId(String disputePayoutTxId) {
storage.queueUpForSave();
}
+ public void setSupportType(SupportType supportType) {
+ this.supportType = supportType;
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
@@ -322,34 +351,37 @@ public boolean isClosed() {
return isClosedProperty.get();
}
+
@Override
public String toString() {
return "Dispute{" +
- "tradeId='" + tradeId + '\'' +
- ", id='" + id + '\'' +
- ", traderId=" + traderId +
- ", disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
- ", disputeOpenerIsMaker=" + disputeOpenerIsMaker +
- ", openingDate=" + openingDate +
- ", traderPubKeyRing=" + traderPubKeyRing +
- ", tradeDate=" + tradeDate +
- ", contract=" + contract +
- ", contractHash=" + Utilities.bytesAsHexString(contractHash) +
- ", depositTxSerialized=" + Utilities.bytesAsHexString(depositTxSerialized) +
- ", payoutTxSerialized not displayed for privacy reasons..." +
- ", depositTxId='" + depositTxId + '\'' +
- ", payoutTxId='" + payoutTxId + '\'' +
- ", contractAsJson='" + contractAsJson + '\'' +
- ", makerContractSignature='" + makerContractSignature + '\'' +
- ", takerContractSignature='" + takerContractSignature + '\'' +
- ", agentPubKeyRing=" + agentPubKeyRing +
- ", isSupportTicket=" + isSupportTicket +
- ", chatMessages=" + chatMessages +
- ", isClosed=" + isClosedProperty.get() +
- ", disputeResult=" + disputeResultProperty.get() +
- ", disputePayoutTxId='" + disputePayoutTxId + '\'' +
- ", isClosedProperty=" + isClosedProperty +
- ", disputeResultProperty=" + disputeResultProperty +
- '}';
+ "\n tradeId='" + tradeId + '\'' +
+ ",\n id='" + id + '\'' +
+ ",\n traderId=" + traderId +
+ ",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
+ ",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker +
+ ",\n traderPubKeyRing=" + traderPubKeyRing +
+ ",\n tradeDate=" + tradeDate +
+ ",\n contract=" + contract +
+ ",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
+ ",\n depositTxSerialized=" + Utilities.bytesAsHexString(depositTxSerialized) +
+ ",\n payoutTxSerialized=" + Utilities.bytesAsHexString(payoutTxSerialized) +
+ ",\n depositTxId='" + depositTxId + '\'' +
+ ",\n payoutTxId='" + payoutTxId + '\'' +
+ ",\n contractAsJson='" + contractAsJson + '\'' +
+ ",\n makerContractSignature='" + makerContractSignature + '\'' +
+ ",\n takerContractSignature='" + takerContractSignature + '\'' +
+ ",\n agentPubKeyRing=" + agentPubKeyRing +
+ ",\n isSupportTicket=" + isSupportTicket +
+ ",\n chatMessages=" + chatMessages +
+ ",\n isClosedProperty=" + isClosedProperty +
+ ",\n disputeResultProperty=" + disputeResultProperty +
+ ",\n disputePayoutTxId='" + disputePayoutTxId + '\'' +
+ ",\n openingDate=" + openingDate +
+ ",\n storage=" + storage +
+ ",\n supportType=" + supportType +
+ ",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' +
+ ",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' +
+ "\n}";
}
}
diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
index 7049dbcc431..1edb7cbc3b7 100644
--- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
@@ -55,12 +55,14 @@
import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Nullable;
+
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public abstract class DisputeManager> extends SupportManager {
protected final TradeWalletService tradeWalletService;
- protected final BtcWalletService walletService;
+ protected final BtcWalletService btcWalletService;
protected final TradeManager tradeManager;
protected final ClosedTradableManager closedTradableManager;
protected final OpenOfferManager openOfferManager;
@@ -74,7 +76,7 @@ public abstract class DisputeManager findOwnDispute(String tradeId) {
// Message handler
///////////////////////////////////////////////////////////////////////////////////////////
- // arbitrator receives that from trader who opens dispute
+ // dispute agent receives that from trader who opens dispute
protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessage) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -246,13 +255,17 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
String errorMessage = null;
Dispute dispute = openNewDisputeMessage.getDispute();
+
+ // Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before
+ dispute.setSupportType(openNewDisputeMessage.getSupportType());
+
+ dispute.setStorage(disputeListService.getStorage());
Contract contractFromOpener = dispute.getContract();
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getSellerPubKeyRing() : contractFromOpener.getBuyerPubKeyRing();
if (isAgent(dispute)) {
if (!disputeList.contains(dispute)) {
Optional storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
- dispute.setStorage(disputeListService.getStorage());
disputeList.add(dispute);
errorMessage = sendPeerOpenedDisputeMessage(dispute, contractFromOpener, peersPubKeyRing);
} else {
@@ -270,15 +283,29 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
}
// We use the ChatMessage not the openNewDisputeMessage for the ACK
- ObservableList messages = openNewDisputeMessage.getDispute().getChatMessages();
+ ObservableList messages = dispute.getChatMessages();
if (!messages.isEmpty()) {
- ChatMessage msg = messages.get(0);
+ ChatMessage chatMessage = messages.get(0);
PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getBuyerPubKeyRing() : contractFromOpener.getSellerPubKeyRing();
- sendAckMessage(msg, sendersPubKeyRing, errorMessage == null, errorMessage);
+ sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage);
+ }
+
+ // In case of refundAgent we add a message with the mediatorsDisputeSummary. Only visible for refundAgent.
+ if (dispute.getMediatorsDisputeResult() != null) {
+ String mediatorsDisputeResult = Res.get("support.mediatorsDisputeSummary", dispute.getMediatorsDisputeResult());
+ ChatMessage mediatorsDisputeResultMessage = new ChatMessage(
+ getSupportType(),
+ dispute.getTradeId(),
+ pubKeyRing.hashCode(),
+ false,
+ mediatorsDisputeResult,
+ p2PService.getAddress());
+ mediatorsDisputeResultMessage.setSystemMessage(true);
+ dispute.addAndPersistChatMessage(mediatorsDisputeResultMessage);
}
}
- // not dispute requester receives that from arbitrator
+ // not dispute requester receives that from dispute agent
protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDisputeMessage) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -345,17 +372,19 @@ public void sendOpenNewDisputeMessage(Dispute dispute,
Optional storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent() || reOpen) {
- String disputeInfo = getDisputeInfo(dispute.isMediationDispute());
+ String disputeInfo = getDisputeInfo(dispute);
+ String disputeMessage = getDisputeIntroForDisputeCreator(disputeInfo);
String sysMsg = dispute.isSupportTicket() ?
Res.get("support.youOpenedTicket", disputeInfo, Version.VERSION)
- : Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
+ : disputeMessage;
+ String message = Res.get("support.systemMsg", sysMsg);
ChatMessage chatMessage = new ChatMessage(
getSupportType(),
dispute.getTradeId(),
pubKeyRing.hashCode(),
false,
- Res.get("support.systemMsg", sysMsg),
+ message,
p2PService.getAddress());
chatMessage.setSystemMessage(true);
dispute.addAndPersistChatMessage(chatMessage);
@@ -364,15 +393,22 @@ public void sendOpenNewDisputeMessage(Dispute dispute,
}
NodeAddress agentNodeAddress = getAgentNodeAddress(dispute);
+ if (agentNodeAddress == null) {
+ return;
+ }
+
OpenNewDisputeMessage openNewDisputeMessage = new OpenNewDisputeMessage(dispute,
p2PService.getAddress(),
UUID.randomUUID().toString(),
getSupportType());
- log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, " +
- "chatMessage.uid={}",
- openNewDisputeMessage.getClass().getSimpleName(), agentNodeAddress,
- openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(),
+
+ log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, chatMessage.uid={}",
+ openNewDisputeMessage.getClass().getSimpleName(),
+ agentNodeAddress,
+ openNewDisputeMessage.getTradeId(),
+ openNewDisputeMessage.getUid(),
chatMessage.getUid());
+
p2PService.sendEncryptedMailboxMessage(agentNodeAddress,
dispute.getAgentPubKeyRing(),
openNewDisputeMessage,
@@ -432,7 +468,7 @@ public void onFault(String errorMessage) {
}
}
- // arbitrator sends that to trading peer when he received openDispute request
+ // dispute agent sends that to trading peer when he received openDispute request
private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
Contract contractFromOpener,
PubKeyRing pubKeyRing) {
@@ -459,13 +495,17 @@ private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
disputeFromOpener.getMakerContractSignature(),
disputeFromOpener.getTakerContractSignature(),
disputeFromOpener.getAgentPubKeyRing(),
- disputeFromOpener.isSupportTicket());
+ disputeFromOpener.isSupportTicket(),
+ disputeFromOpener.getSupportType());
+ dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId());
+
Optional storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
- String disputeInfo = getDisputeInfo(dispute.isMediationDispute());
+ String disputeInfo = getDisputeInfo(dispute);
+ String disputeMessage = getDisputeIntroForPeer(disputeInfo);
String sysMsg = dispute.isSupportTicket() ?
- Res.get("support.peerOpenedTicket", disputeInfo)
- : Res.get("support.peerOpenedDispute", disputeInfo);
+ Res.get("support.peerOpenedTicket", disputeInfo, Version.VERSION)
+ : disputeMessage;
ChatMessage chatMessage = new ChatMessage(
getSupportType(),
dispute.getTradeId(),
@@ -485,11 +525,12 @@ private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
p2PService.getAddress(),
UUID.randomUUID().toString(),
getSupportType());
- log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
- "chatMessage.uid={}",
+
+ log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, chatMessage.uid={}",
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
chatMessage.getUid());
+
p2PService.sendEncryptedMailboxMessage(peersNodeAddress,
peersPubKeyRing,
peerOpenedDisputeMessage,
@@ -546,7 +587,7 @@ public void onFault(String errorMessage) {
}
}
- // arbitrator send result to trader
+ // dispute agent send result to trader
public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String text) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -690,12 +731,4 @@ public Optional findDispute(String tradeId) {
.filter(e -> e.getTradeId().equals(tradeId))
.findAny();
}
-
- private String getDisputeInfo(boolean isMediationDispute) {
- String role = isMediationDispute ? Res.get("shared.mediator").toLowerCase() :
- Res.get("shared.arbitrator2").toLowerCase();
- String link = isMediationDispute ? "https://docs.bisq.network/trading-rules.html#mediation" :
- "https://bisq.network/docs/exchange/arbitration-system";
- return Res.get("support.initialInfo", role, role, link);
- }
}
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java
index 5ce8f6b2c7b..13f51fecae7 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java
@@ -18,6 +18,7 @@
package bisq.core.support.dispute.arbitration;
import bisq.core.proto.CoreProtoResolver;
+import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeList;
@@ -33,6 +34,8 @@
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkArgument;
+
@Slf4j
@ToString
/*
@@ -67,6 +70,9 @@ private ArbitrationDisputeList(Storage storage, List checkArgument(dispute.getSupportType().equals(SupportType.ARBITRATION), "Support type has to be ARBITRATION"));
+
return protobuf.PersistableEnvelope.newBuilder().setArbitrationDisputeList(protobuf.ArbitrationDisputeList.newBuilder()
.addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list)))).build();
}
@@ -77,7 +83,11 @@ public static ArbitrationDisputeList fromProto(protobuf.ArbitrationDisputeList p
List list = proto.getDisputeList().stream()
.map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver))
.collect(Collectors.toList());
- list.forEach(e -> e.setStorage(storage));
+
+ list.forEach(e -> {
+ checkArgument(e.getSupportType().equals(SupportType.ARBITRATION), "Support type has to be ARBITRATION");
+ e.setStorage(storage);
+ });
return new ArbitrationDisputeList(storage, list);
}
}
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
index cbf859e2a24..233955cfbf2 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
@@ -24,6 +24,8 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
+import bisq.core.btc.wallet.WalletService;
+import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.SupportType;
@@ -49,6 +51,7 @@
import bisq.common.Timer;
import bisq.common.UserThread;
+import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import org.bitcoinj.core.AddressFormatException;
@@ -64,6 +67,8 @@
import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Nullable;
+
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
@@ -120,9 +125,10 @@ public void dispatchMessage(SupportMessage message) {
}
}
+ @Nullable
@Override
public NodeAddress getAgentNodeAddress(Dispute dispute) {
- return dispute.getContract().getArbitratorNodeAddress();
+ return null;
}
@Override
@@ -140,6 +146,22 @@ public void cleanupDisputes() {
disputeListService.cleanupDisputes(tradeId -> tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.DISPUTE_CLOSED));
}
+ @Override
+ protected String getDisputeInfo(Dispute dispute) {
+ String role = Res.get("shared.arbitrator").toLowerCase();
+ String link = "https://docs.bisq.network/trading-rules.html#legacy-arbitration"; //TODO needs to be created
+ return Res.get("support.initialInfo", role, role, link);
+ }
+
+ @Override
+ protected String getDisputeIntroForPeer(String disputeInfo) {
+ return Res.get("support.peerOpenedDispute", disputeInfo, Version.VERSION);
+ }
+
+ @Override
+ protected String getDisputeIntroForDisputeCreator(String disputeInfo) {
+ return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Message handler
@@ -152,7 +174,7 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
ChatMessage chatMessage = disputeResult.getChatMessage();
checkNotNull(chatMessage, "chatMessage must not be null");
if (Arrays.equals(disputeResult.getArbitratorPubKey(),
- walletService.getArbitratorAddressEntry().getPubKey())) {
+ btcWalletService.getArbitratorAddressEntry().getPubKey())) {
log.error("Arbitrator received disputeResultMessage. That must never happen.");
return;
}
@@ -230,7 +252,7 @@ else if (publisher == DisputeResult.Winner.SELLER)
if (payoutTx == null) {
if (dispute.getDepositTxSerialized() != null) {
byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
- DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
+ DeterministicKey multiSigKeyPair = btcWalletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
dispute.getDepositTxSerialized(),
disputeResult.getArbitratorSignature(),
@@ -243,7 +265,7 @@ else if (publisher == DisputeResult.Winner.SELLER)
contract.getSellerMultiSigPubKey(),
disputeResult.getArbitratorPubKey()
);
- Transaction committedDisputedPayoutTx = tradeWalletService.addTxToWallet(signedDisputedPayoutTx);
+ Transaction committedDisputedPayoutTx = WalletService.maybeAddSelfTxToWallet(signedDisputedPayoutTx, btcWalletService.getWallet());
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {
@@ -328,9 +350,11 @@ private void onDisputedPayoutTxMessage(PeerPublishedDisputePayoutTxMessage peerP
PubKeyRing peersPubKeyRing = isBuyer ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
cleanupRetryMap(uid);
- Transaction walletTx = tradeWalletService.addTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction());
- dispute.setDisputePayoutTxId(walletTx.getHashAsString());
- BtcWalletService.printTx("Disputed payoutTx received from peer", walletTx);
+
+ Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
+
+ dispute.setDisputePayoutTxId(committedDisputePayoutTx.getHashAsString());
+ BtcWalletService.printTx("Disputed payoutTx received from peer", committedDisputePayoutTx);
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/BuyerDataItem.java b/core/src/main/java/bisq/core/support/dispute/arbitration/TraderDataItem.java
similarity index 80%
rename from core/src/main/java/bisq/core/support/dispute/arbitration/BuyerDataItem.java
rename to core/src/main/java/bisq/core/support/dispute/arbitration/TraderDataItem.java
index 7962a4e074f..1ae4cc16462 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/BuyerDataItem.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/TraderDataItem.java
@@ -30,20 +30,20 @@
// TODO consider to move to signed witness domain
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
-public class BuyerDataItem {
+public class TraderDataItem {
private final PaymentAccountPayload paymentAccountPayload;
@EqualsAndHashCode.Include
private final AccountAgeWitness accountAgeWitness;
private final Coin tradeAmount;
- private final PublicKey sellerPubKey;
+ private final PublicKey peersPubKey;
- public BuyerDataItem(PaymentAccountPayload paymentAccountPayload,
- AccountAgeWitness accountAgeWitness,
- Coin tradeAmount,
- PublicKey sellerPubKey) {
+ public TraderDataItem(PaymentAccountPayload paymentAccountPayload,
+ AccountAgeWitness accountAgeWitness,
+ Coin tradeAmount,
+ PublicKey peersPubKey) {
this.paymentAccountPayload = paymentAccountPayload;
this.accountAgeWitness = accountAgeWitness;
this.tradeAmount = tradeAmount;
- this.sellerPubKey = sellerPubKey;
+ this.peersPubKey = peersPubKey;
}
}
diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
index 705a17ff5ef..ea6b12bd997 100644
--- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
@@ -20,6 +20,7 @@
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.SupportType;
@@ -43,22 +44,22 @@
import bisq.common.Timer;
import bisq.common.UserThread;
+import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.util.Date;
-import java.util.GregorianCalendar;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
+import javax.annotation.Nullable;
+
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -66,14 +67,6 @@
@Singleton
public final class MediationManager extends DisputeManager {
- // The date when mediation is activated
- private static final Date MEDIATION_ACTIVATED_DATE = Utilities.getUTCDate(2019, GregorianCalendar.SEPTEMBER, 26);
-
- public static boolean isMediationActivated() {
- return new Date().after(MEDIATION_ACTIVATED_DATE);
- }
-
-
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@@ -141,6 +134,22 @@ public void cleanupDisputes() {
});
}
+ @Override
+ protected String getDisputeInfo(Dispute dispute) {
+ String role = Res.get("shared.mediator").toLowerCase();
+ String link = "https://docs.bisq.network/trading-rules.html#mediation";
+ return Res.get("support.initialInfo", role, role, link);
+ }
+
+ @Override
+ protected String getDisputeIntroForPeer(String disputeInfo) {
+ return Res.get("support.peerOpenedDisputeForMediation", disputeInfo, Version.VERSION);
+ }
+
+ @Override
+ protected String getDisputeIntroForDisputeCreator(String disputeInfo) {
+ return Res.get("support.youOpenedDisputeForMediation", disputeInfo, Version.VERSION);
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Message handler
@@ -205,6 +214,7 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
// API
///////////////////////////////////////////////////////////////////////////////////////////
+ @Nullable
@Override
public NodeAddress getAgentNodeAddress(Dispute dispute) {
return dispute.getContract().getMediatorNodeAddress();
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java
new file mode 100644
index 00000000000..f344475560a
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java
@@ -0,0 +1,93 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.proto.CoreProtoResolver;
+import bisq.core.support.SupportType;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeList;
+
+import bisq.common.proto.ProtoUtil;
+import bisq.common.storage.Storage;
+
+import com.google.protobuf.Message;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Slf4j
+@ToString
+/*
+ * Holds a List of refund dispute objects.
+ *
+ * Calls to the List are delegated because this class intercepts the add/remove calls so changes
+ * can be saved to disc.
+ */
+public final class RefundDisputeList extends DisputeList {
+
+ RefundDisputeList(Storage storage) {
+ super(storage);
+ }
+
+ @Override
+ public void readPersisted() {
+ // We need to use DisputeList as file name to not lose existing disputes which are stored in the DisputeList file
+ RefundDisputeList persisted = storage.initAndGetPersisted(this, "RefundDisputeList", 50);
+ if (persisted != null) {
+ list.addAll(persisted.getList());
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private RefundDisputeList(Storage storage, List list) {
+ super(storage, list);
+ }
+
+ @Override
+ public Message toProtoMessage() {
+
+ list.forEach(dispute -> checkArgument(dispute.getSupportType().equals(SupportType.REFUND), "Support type has to be REFUND"));
+
+ return protobuf.PersistableEnvelope.newBuilder().setRefundDisputeList(protobuf.RefundDisputeList.newBuilder()
+ .addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list)))).build();
+ }
+
+ public static RefundDisputeList fromProto(protobuf.RefundDisputeList proto,
+ CoreProtoResolver coreProtoResolver,
+ Storage storage) {
+ List list = proto.getDisputeList().stream()
+ .map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver))
+ .collect(Collectors.toList());
+
+ list.forEach(e -> {
+ checkArgument(e.getSupportType().equals(SupportType.REFUND), "Support type has to be REFUND");
+ e.setStorage(storage);
+ });
+ return new RefundDisputeList(storage, list);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java
new file mode 100644
index 00000000000..afdac5c9f3b
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java
@@ -0,0 +1,48 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.support.dispute.DisputeListService;
+
+import bisq.common.storage.Storage;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public final class RefundDisputeListService extends DisputeListService {
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Inject
+ public RefundDisputeListService(Storage storage) {
+ super(storage);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Implement template methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected RefundDisputeList getConcreteDisputeList() {
+ return new RefundDisputeList(storage);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java
new file mode 100644
index 00000000000..139832a1c16
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java
@@ -0,0 +1,219 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.btc.setup.WalletsSetup;
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.locale.Res;
+import bisq.core.offer.OpenOffer;
+import bisq.core.offer.OpenOfferManager;
+import bisq.core.support.SupportType;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeManager;
+import bisq.core.support.dispute.DisputeResult;
+import bisq.core.support.dispute.messages.DisputeResultMessage;
+import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
+import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
+import bisq.core.support.messages.ChatMessage;
+import bisq.core.support.messages.SupportMessage;
+import bisq.core.trade.Trade;
+import bisq.core.trade.TradeManager;
+import bisq.core.trade.closed.ClosedTradableManager;
+
+import bisq.network.p2p.AckMessageSourceType;
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.P2PService;
+
+import bisq.common.Timer;
+import bisq.common.UserThread;
+import bisq.common.app.Version;
+import bisq.common.crypto.PubKeyRing;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.Optional;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+@Singleton
+public final class RefundManager extends DisputeManager {
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Inject
+ public RefundManager(P2PService p2PService,
+ TradeWalletService tradeWalletService,
+ BtcWalletService walletService,
+ WalletsSetup walletsSetup,
+ TradeManager tradeManager,
+ ClosedTradableManager closedTradableManager,
+ OpenOfferManager openOfferManager,
+ PubKeyRing pubKeyRing,
+ RefundDisputeListService refundDisputeListService) {
+ super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
+ openOfferManager, pubKeyRing, refundDisputeListService);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Implement template methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public SupportType getSupportType() {
+ return SupportType.REFUND;
+ }
+
+ @Override
+ public void dispatchMessage(SupportMessage message) {
+ if (canProcessMessage(message)) {
+ log.info("Received {} with tradeId {} and uid {}",
+ message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
+
+ if (message instanceof OpenNewDisputeMessage) {
+ onOpenNewDisputeMessage((OpenNewDisputeMessage) message);
+ } else if (message instanceof PeerOpenedDisputeMessage) {
+ onPeerOpenedDisputeMessage((PeerOpenedDisputeMessage) message);
+ } else if (message instanceof ChatMessage) {
+ onChatMessage((ChatMessage) message);
+ } else if (message instanceof DisputeResultMessage) {
+ onDisputeResultMessage((DisputeResultMessage) message);
+ } else {
+ log.warn("Unsupported message at dispatchMessage. message={}", message);
+ }
+ }
+ }
+
+ @Override
+ protected Trade.DisputeState getDisputeState_StartedByPeer() {
+ return Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER;
+ }
+
+ @Override
+ protected AckMessageSourceType getAckMessageSourceType() {
+ return AckMessageSourceType.REFUND_MESSAGE;
+ }
+
+ @Override
+ public void cleanupDisputes() {
+ disputeListService.cleanupDisputes(tradeId -> tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED));
+ }
+
+ @Override
+ protected String getDisputeInfo(Dispute dispute) {
+ String role = Res.get("shared.refundAgent").toLowerCase();
+ String link = "https://docs.bisq.network/trading-rules.html#arbitration";
+ return Res.get("support.initialInfo", role, role, link);
+ }
+
+ @Override
+ protected String getDisputeIntroForPeer(String disputeInfo) {
+ return Res.get("support.peerOpenedDispute", disputeInfo, Version.VERSION);
+ }
+
+ @Override
+ protected String getDisputeIntroForDisputeCreator(String disputeInfo) {
+ return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Message handler
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ // We get that message at both peers. The dispute object is in context of the trader
+ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
+ DisputeResult disputeResult = disputeResultMessage.getDisputeResult();
+ String tradeId = disputeResult.getTradeId();
+ ChatMessage chatMessage = disputeResult.getChatMessage();
+ checkNotNull(chatMessage, "chatMessage must not be null");
+ Optional disputeOptional = findDispute(disputeResult);
+ String uid = disputeResultMessage.getUid();
+ if (!disputeOptional.isPresent()) {
+ log.warn("We got a dispute result msg but we don't have a matching dispute. " +
+ "That might happen when we get the disputeResultMessage before the dispute was created. " +
+ "We try again after 2 sec. to apply the disputeResultMessage. TradeId = " + tradeId);
+ if (!delayMsgMap.containsKey(uid)) {
+ // We delay 2 sec. to be sure the comm. msg gets added first
+ Timer timer = UserThread.runAfter(() -> onDisputeResultMessage(disputeResultMessage), 2);
+ delayMsgMap.put(uid, timer);
+ } else {
+ log.warn("We got a dispute result msg after we already repeated to apply the message after a delay. " +
+ "That should never happen. TradeId = " + tradeId);
+ }
+ return;
+ }
+
+ Dispute dispute = disputeOptional.get();
+ cleanupRetryMap(uid);
+ if (!dispute.getChatMessages().contains(chatMessage)) {
+ dispute.addAndPersistChatMessage(chatMessage);
+ } else {
+ log.warn("We got a dispute mail msg what we have already stored. TradeId = " + chatMessage.getTradeId());
+ }
+ dispute.setIsClosed(true);
+
+ if (dispute.disputeResultProperty().get() != null) {
+ log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " +
+ "again because the first close did not succeed. TradeId = " + tradeId);
+ }
+
+ dispute.setDisputeResult(disputeResult);
+
+ Optional tradeOptional = tradeManager.getTradeById(tradeId);
+ if (tradeOptional.isPresent()) {
+ Trade trade = tradeOptional.get();
+ if (trade.getDisputeState() == Trade.DisputeState.REFUND_REQUESTED ||
+ trade.getDisputeState() == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) {
+ trade.setDisputeState(Trade.DisputeState.REFUND_REQUEST_CLOSED);
+ }
+ } else {
+ Optional openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
+ openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
+ }
+ sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
+
+ // set state after payout as we call swapTradeEntryToAvailableEntry
+ if (tradeManager.getTradeById(tradeId).isPresent()) {
+ tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED);
+ } else {
+ Optional openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
+ openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // API
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Nullable
+ @Override
+ public NodeAddress getAgentNodeAddress(Dispute dispute) {
+ return dispute.getContract().getRefundAgentNodeAddress();
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundResultState.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundResultState.java
new file mode 100644
index 00000000000..1664b733bc4
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundResultState.java
@@ -0,0 +1,33 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.common.proto.ProtoUtil;
+
+// todo
+public enum RefundResultState {
+ UNDEFINED_REFUND_RESULT;
+
+ public static RefundResultState fromProto(protobuf.RefundResultState refundResultState) {
+ return ProtoUtil.enumFromProto(RefundResultState.class, refundResultState.name());
+ }
+
+ public static protobuf.RefundResultState toProtoMessage(RefundResultState refundResultState) {
+ return protobuf.RefundResultState.valueOf(refundResultState.name());
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundSession.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundSession.java
new file mode 100644
index 00000000000..b5e9d7e5cc9
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundSession.java
@@ -0,0 +1,33 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeSession;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+@Slf4j
+public class RefundSession extends DisputeSession {
+
+ public RefundSession(@Nullable Dispute dispute, boolean isTrader) {
+ super(dispute, isTrader);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgent.java b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgent.java
new file mode 100644
index 00000000000..268506d06d5
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgent.java
@@ -0,0 +1,117 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund.refundagent;
+
+import bisq.core.support.dispute.agent.DisputeAgent;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
+
+import bisq.common.app.Capabilities;
+import bisq.common.app.Capability;
+import bisq.common.crypto.PubKeyRing;
+import bisq.common.proto.ProtoUtil;
+
+import com.google.protobuf.ByteString;
+
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+@EqualsAndHashCode(callSuper = true)
+@Slf4j
+@Getter
+public final class RefundAgent extends DisputeAgent implements CapabilityRequiringPayload {
+
+ public RefundAgent(NodeAddress nodeAddress,
+ PubKeyRing pubKeyRing,
+ List languageCodes,
+ long registrationDate,
+ byte[] registrationPubKey,
+ String registrationSignature,
+ @Nullable String emailAddress,
+ @Nullable String info,
+ @Nullable Map extraDataMap) {
+
+ super(nodeAddress,
+ pubKeyRing,
+ languageCodes,
+ registrationDate,
+ registrationPubKey,
+ registrationSignature,
+ emailAddress,
+ info,
+ extraDataMap);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public protobuf.StoragePayload toProtoMessage() {
+ protobuf.RefundAgent.Builder builder = protobuf.RefundAgent.newBuilder()
+ .setNodeAddress(nodeAddress.toProtoMessage())
+ .setPubKeyRing(pubKeyRing.toProtoMessage())
+ .addAllLanguageCodes(languageCodes)
+ .setRegistrationDate(registrationDate)
+ .setRegistrationPubKey(ByteString.copyFrom(registrationPubKey))
+ .setRegistrationSignature(registrationSignature);
+ Optional.ofNullable(emailAddress).ifPresent(builder::setEmailAddress);
+ Optional.ofNullable(info).ifPresent(builder::setInfo);
+ Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
+ return protobuf.StoragePayload.newBuilder().setRefundAgent(builder).build();
+ }
+
+ public static RefundAgent fromProto(protobuf.RefundAgent proto) {
+ return new RefundAgent(NodeAddress.fromProto(proto.getNodeAddress()),
+ PubKeyRing.fromProto(proto.getPubKeyRing()),
+ new ArrayList<>(proto.getLanguageCodesList()),
+ proto.getRegistrationDate(),
+ proto.getRegistrationPubKey().toByteArray(),
+ proto.getRegistrationSignature(),
+ ProtoUtil.stringOrNullFromProto(proto.getEmailAddress()),
+ ProtoUtil.stringOrNullFromProto(proto.getInfo()),
+ CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap());
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // API
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ @Override
+ public String toString() {
+ return "RefundAgent{} " + super.toString();
+ }
+
+ @Override
+ public Capabilities getRequiredCapabilities() {
+ return new Capabilities(Capability.REFUND_AGENT);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java
new file mode 100644
index 00000000000..d13560f3dfc
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java
@@ -0,0 +1,105 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund.refundagent;
+
+import bisq.core.app.AppOptionKeys;
+import bisq.core.filter.FilterManager;
+import bisq.core.support.dispute.agent.DisputeAgentManager;
+import bisq.core.user.User;
+
+import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
+
+import bisq.common.crypto.KeyRing;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Singleton
+public class RefundAgentManager extends DisputeAgentManager {
+
+ @Inject
+ public RefundAgentManager(KeyRing keyRing,
+ RefundAgentService refundAgentService,
+ User user,
+ FilterManager filterManager,
+ @Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
+ super(keyRing, refundAgentService, user, filterManager, useDevPrivilegeKeys);
+ }
+
+ @Override
+ protected List getPubKeyList() {
+ return List.of("02a25798e256b800d7ea71c31098ac9a47cb20892176afdfeb051f5ded382d44af",
+ "0360455d3cffe00ef73cc1284c84eedacc8c5c3374c43f4aac8ffb95f5130b9ef5",
+ "03b0513afbb531bc4551b379eba027feddd33c92b5990fd477b0fa6eff90a5b7db",
+ "03533fd75fda29c351298e50b8ea696656dcb8ce4e263d10618c6901a50450bf0e",
+ "028124436482aa4c61a4bc4097d60c80b09f4285413be3b023a37a0164cbd5d818",
+ "0384fcf883116d8e9469720ed7808cc4141f6dc6a5ed23d76dd48f2f5f255590d7",
+ "029bd318ecee4e212ff06a4396770d600d72e9e0c6532142a428bdb401491e9721",
+ "02e375b4b24d0a858953f7f94666667554d41f78000b9c8a301294223688b29011",
+ "0232c088ae7c070de89d2b6c8d485b34bf0e3b2a964a2c6622f39ca501260c23f7",
+ "033e047f74f2aa1ce41e8c85731f97ab83d448d65dc8518ab3df4474a5d53a3d19",
+ "02f52a8cf373c8cbddb318e523b7f111168bf753fdfb6f8aa81f88c950ede3a5ce",
+ "039784029922c54bcd0f0e7f14530f586053a5f4e596e86b3474cd7404657088ae",
+ "037969f9d5ab2cc609104c6e61323df55428f8f108c11aab7c7b5f953081d39304",
+ "031bd37475b8c5615ac46d6816e791c59d806d72a0bc6739ae94e5fe4545c7f8a6",
+ "021bb92c636feacf5b082313eb071a63dfcd26501a48b3cd248e35438e5afb7daf");
+
+
+ }
+
+ @Override
+ protected boolean isExpectedInstance(ProtectedStorageEntry data) {
+ return data.getProtectedStoragePayload() instanceof RefundAgent;
+ }
+
+ @Override
+ protected void addAcceptedDisputeAgentToUser(RefundAgent disputeAgent) {
+ user.addAcceptedRefundAgent(disputeAgent);
+ }
+
+ @Override
+ protected void removeAcceptedDisputeAgentFromUser(ProtectedStorageEntry data) {
+ user.removeAcceptedRefundAgent((RefundAgent) data.getProtectedStoragePayload());
+ }
+
+ @Override
+ protected List getAcceptedDisputeAgentsFromUser() {
+ return user.getAcceptedRefundAgents();
+ }
+
+ @Override
+ protected void clearAcceptedDisputeAgentsAtUser() {
+ user.clearAcceptedRefundAgents();
+ }
+
+ @Override
+ protected RefundAgent getRegisteredDisputeAgentFromUser() {
+ return user.getRegisteredRefundAgent();
+ }
+
+ @Override
+ protected void setRegisteredDisputeAgentAtUser(RefundAgent disputeAgent) {
+ user.setRegisteredRefundAgent(disputeAgent);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentService.java b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentService.java
new file mode 100644
index 00000000000..ab67223e984
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentService.java
@@ -0,0 +1,61 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund.refundagent;
+
+import bisq.core.filter.FilterManager;
+import bisq.core.support.dispute.agent.DisputeAgentService;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.P2PService;
+
+import com.google.inject.Singleton;
+
+import javax.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Singleton
+public class RefundAgentService extends DisputeAgentService {
+ @Inject
+ public RefundAgentService(P2PService p2PService, FilterManager filterManager) {
+ super(p2PService, filterManager);
+ }
+
+ @Override
+ protected Set getDisputeAgentSet(List bannedDisputeAgents) {
+ return p2PService.getDataMap().values().stream()
+ .filter(data -> data.getProtectedStoragePayload() instanceof RefundAgent)
+ .map(data -> (RefundAgent) data.getProtectedStoragePayload())
+ .filter(a -> bannedDisputeAgents == null ||
+ !bannedDisputeAgents.contains(a.getNodeAddress().getFullAddress()))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ protected List getDisputeAgentsFromFilter() {
+ return filterManager.getFilter() != null ? filterManager.getFilter().getRefundAgents() : new ArrayList<>();
+ }
+
+ public Map getRefundAgents() {
+ return super.getDisputeAgents();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
index 17eeca7a7a4..e6cecc1e4f9 100644
--- a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
@@ -20,7 +20,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.BuyerAsMakerProtocol;
import bisq.core.trade.protocol.MakerProtocol;
@@ -48,6 +48,7 @@ public BuyerAsMakerTrade(Offer offer,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -56,6 +57,7 @@ public BuyerAsMakerTrade(Offer offer,
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -84,6 +86,7 @@ public static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradePro
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService);
@@ -107,7 +110,7 @@ protected void createTradeProtocol() {
}
@Override
- public void handleTakeOfferRequest(TradeMessage message,
+ public void handleTakeOfferRequest(InputsForDepositTxRequest message,
NodeAddress taker,
ErrorMessageHandler errorMessageHandler) {
((MakerProtocol) tradeProtocol).handleTakeOfferRequest(message, taker, errorMessageHandler);
diff --git a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
index c4c2c1e7768..d9b5f86c290 100644
--- a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
@@ -51,6 +51,7 @@ public BuyerAsTakerTrade(Offer offer,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -62,6 +63,7 @@ public BuyerAsTakerTrade(Offer offer,
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -94,6 +96,7 @@ public static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradePro
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService),
proto,
diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/BuyerTrade.java
index 4c534b30f05..4112252e6d6 100644
--- a/core/src/main/java/bisq/core/trade/BuyerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerTrade.java
@@ -47,6 +47,7 @@ public abstract class BuyerTrade extends Trade {
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -58,6 +59,7 @@ public abstract class BuyerTrade extends Trade {
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -68,6 +70,7 @@ public abstract class BuyerTrade extends Trade {
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -76,6 +79,7 @@ public abstract class BuyerTrade extends Trade {
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
diff --git a/core/src/main/java/bisq/core/trade/Contract.java b/core/src/main/java/bisq/core/trade/Contract.java
index 6ec2e573949..65a8b00d9f7 100644
--- a/core/src/main/java/bisq/core/trade/Contract.java
+++ b/core/src/main/java/bisq/core/trade/Contract.java
@@ -55,7 +55,6 @@ public final class Contract implements NetworkPayload {
private final String takerFeeTxID;
private final NodeAddress buyerNodeAddress;
private final NodeAddress sellerNodeAddress;
- private final NodeAddress arbitratorNodeAddress;
private final NodeAddress mediatorNodeAddress;
private final boolean isBuyerMakerAndSellerTaker;
private final String makerAccountId;
@@ -73,13 +72,16 @@ public final class Contract implements NetworkPayload {
@JsonExclude
private final byte[] takerMultiSigPubKey;
+ // Added in v1.2.0
+ private long lockTime;
+ private final NodeAddress refundAgentNodeAddress;
+
public Contract(OfferPayload offerPayload,
long tradeAmount,
long tradePrice,
String takerFeeTxID,
NodeAddress buyerNodeAddress,
NodeAddress sellerNodeAddress,
- NodeAddress arbitratorNodeAddress,
NodeAddress mediatorNodeAddress,
boolean isBuyerMakerAndSellerTaker,
String makerAccountId,
@@ -91,14 +93,15 @@ public Contract(OfferPayload offerPayload,
String makerPayoutAddressString,
String takerPayoutAddressString,
byte[] makerMultiSigPubKey,
- byte[] takerMultiSigPubKey) {
+ byte[] takerMultiSigPubKey,
+ long lockTime,
+ NodeAddress refundAgentNodeAddress) {
this.offerPayload = offerPayload;
this.tradeAmount = tradeAmount;
this.tradePrice = tradePrice;
this.takerFeeTxID = takerFeeTxID;
this.buyerNodeAddress = buyerNodeAddress;
this.sellerNodeAddress = sellerNodeAddress;
- this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
this.makerAccountId = makerAccountId;
@@ -111,6 +114,8 @@ public Contract(OfferPayload offerPayload,
this.takerPayoutAddressString = takerPayoutAddressString;
this.makerMultiSigPubKey = makerMultiSigPubKey;
this.takerMultiSigPubKey = takerMultiSigPubKey;
+ this.lockTime = lockTime;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId();
String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId();
@@ -128,7 +133,6 @@ public Contract(OfferPayload offerPayload,
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- @Nullable
public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver coreProtoResolver) {
return new Contract(OfferPayload.fromProto(proto.getOfferPayload()),
proto.getTradeAmount(),
@@ -136,7 +140,6 @@ public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver core
proto.getTakerFeeTxId(),
NodeAddress.fromProto(proto.getBuyerNodeAddress()),
NodeAddress.fromProto(proto.getSellerNodeAddress()),
- NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
NodeAddress.fromProto(proto.getMediatorNodeAddress()),
proto.getIsBuyerMakerAndSellerTaker(),
proto.getMakerAccountId(),
@@ -148,7 +151,9 @@ public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver core
proto.getMakerPayoutAddressString(),
proto.getTakerPayoutAddressString(),
proto.getMakerMultiSigPubKey().toByteArray(),
- proto.getTakerMultiSigPubKey().toByteArray());
+ proto.getTakerMultiSigPubKey().toByteArray(),
+ proto.getLockTime(),
+ NodeAddress.fromProto(proto.getRefundAgentNodeAddress()));
}
@Override
@@ -160,7 +165,6 @@ public protobuf.Contract toProtoMessage() {
.setTakerFeeTxId(takerFeeTxID)
.setBuyerNodeAddress(buyerNodeAddress.toProtoMessage())
.setSellerNodeAddress(sellerNodeAddress.toProtoMessage())
- .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
.setMakerAccountId(makerAccountId)
@@ -173,6 +177,8 @@ public protobuf.Contract toProtoMessage() {
.setTakerPayoutAddressString(takerPayoutAddressString)
.setMakerMultiSigPubKey(ByteString.copyFrom(makerMultiSigPubKey))
.setTakerMultiSigPubKey(ByteString.copyFrom(takerMultiSigPubKey))
+ .setLockTime(lockTime)
+ .setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
.build();
}
@@ -289,8 +295,8 @@ public String toString() {
",\n takerFeeTxID='" + takerFeeTxID + '\'' +
",\n buyerNodeAddress=" + buyerNodeAddress +
",\n sellerNodeAddress=" + sellerNodeAddress +
- ",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
",\n makerAccountId='" + makerAccountId + '\'' +
",\n takerAccountId='" + takerAccountId + '\'' +
@@ -304,6 +310,7 @@ public String toString() {
",\n takerMultiSigPubKey=" + Utilities.bytesAsHexString(takerMultiSigPubKey) +
",\n buyerMultiSigPubKey=" + Utilities.bytesAsHexString(getBuyerMultiSigPubKey()) +
",\n sellerMultiSigPubKey=" + Utilities.bytesAsHexString(getSellerMultiSigPubKey()) +
+ ",\n lockTime=" + lockTime +
"\n}";
}
}
diff --git a/core/src/main/java/bisq/core/trade/MakerTrade.java b/core/src/main/java/bisq/core/trade/MakerTrade.java
index ffb5b672892..5a2fd7dd8d3 100644
--- a/core/src/main/java/bisq/core/trade/MakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/MakerTrade.java
@@ -17,12 +17,12 @@
package bisq.core.trade;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface MakerTrade {
- void handleTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler);
+ void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler);
}
diff --git a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
index 08fb2f46ca2..5e9b5883f9d 100644
--- a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
@@ -20,7 +20,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.MakerProtocol;
import bisq.core.trade.protocol.SellerAsMakerProtocol;
@@ -48,9 +48,18 @@ public SellerAsMakerTrade(Offer offer,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
- super(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, mediatorNodeAddress, storage, btcWalletService);
+ super(offer,
+ txFee,
+ takerFee,
+ isCurrencyForTakerFeeBtc,
+ arbitratorNodeAddress,
+ mediatorNodeAddress,
+ refundAgentNodeAddress,
+ storage,
+ btcWalletService);
}
@@ -78,6 +87,7 @@ public static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeP
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService);
@@ -101,7 +111,7 @@ protected void createTradeProtocol() {
}
@Override
- public void handleTakeOfferRequest(TradeMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
+ public void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
((MakerProtocol) tradeProtocol).handleTakeOfferRequest(message, taker, errorMessageHandler);
}
}
diff --git a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
index 0d4aeb6b847..79debd8137c 100644
--- a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
@@ -51,6 +51,7 @@ public SellerAsTakerTrade(Offer offer,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -62,6 +63,7 @@ public SellerAsTakerTrade(Offer offer,
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -94,6 +96,7 @@ public static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeP
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService),
proto,
diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/SellerTrade.java
index 629dc5adb52..68eed3a1e08 100644
--- a/core/src/main/java/bisq/core/trade/SellerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerTrade.java
@@ -46,6 +46,7 @@ public abstract class SellerTrade extends Trade {
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -57,6 +58,7 @@ public abstract class SellerTrade extends Trade {
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -67,6 +69,7 @@ public abstract class SellerTrade extends Trade {
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -75,6 +78,7 @@ public abstract class SellerTrade extends Trade {
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java
index a86ea93fa7b..7006fba886f 100644
--- a/core/src/main/java/bisq/core/trade/Trade.java
+++ b/core/src/main/java/bisq/core/trade/Trade.java
@@ -21,6 +21,7 @@
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.monetary.Price;
@@ -34,6 +35,8 @@
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.RefundResultState;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.TradeProtocol;
@@ -114,7 +117,7 @@ public enum State {
// maker perspective
MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
MAKER_SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
- MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
+ MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED), //todo remove
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
// taker perspective
@@ -122,21 +125,21 @@ public enum State {
// #################### Phase DEPOSIT_PAID
- TAKER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
+ SELLER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
// DEPOSIT_TX_PUBLISHED_MSG
- // taker perspective
- TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ // seller perspective
+ SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- // maker perspective
- MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ // buyer perspective
+ BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- // Alternatively the maker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
- MAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
+ // Alternatively the buyer could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
+ BUYER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
// #################### Phase DEPOSIT_CONFIRMED
@@ -221,7 +224,12 @@ public enum DisputeState {
// mediation
MEDIATION_REQUESTED,
MEDIATION_STARTED_BY_PEER,
- MEDIATION_CLOSED;
+ MEDIATION_CLOSED,
+
+ // refund
+ REFUND_REQUESTED,
+ REFUND_REQUEST_STARTED_BY_PEER,
+ REFUND_REQUEST_CLOSED;
public static Trade.DisputeState fromProto(protobuf.Trade.DisputeState disputeState) {
return ProtoUtil.enumFromProto(Trade.DisputeState.class, disputeState.name());
@@ -368,9 +376,14 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
@Getter
transient protected TradeProtocol tradeProtocol;
@Nullable
- transient private Transaction payoutTx;
- @Nullable
transient private Transaction depositTx;
+
+ // Added in v1.2.0
+ @Nullable
+ transient private Transaction delayedPayoutTx;
+
+ @Nullable
+ transient private Transaction payoutTx;
@Nullable
transient private Coin tradeAmount;
@@ -378,12 +391,33 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
transient private ObjectProperty tradeVolumeProperty;
final transient private Set decryptedMessageWithPubKeySet = new HashSet<>();
- //Added in v1.1.6
+ // Added in v1.1.6
@Getter
@Nullable
private MediationResultState mediationResultState = MediationResultState.UNDEFINED_MEDIATION_RESULT;
transient final private ObjectProperty mediationResultStateProperty = new SimpleObjectProperty<>(mediationResultState);
+ // Added in v1.2.0
+ @Getter
+ @Setter
+ private long lockTime;
+ @Nullable
+ @Getter
+ @Setter
+ private byte[] delayedPayoutTxBytes;
+ @Nullable
+ @Getter
+ @Setter
+ private NodeAddress refundAgentNodeAddress;
+ @Nullable
+ @Getter
+ @Setter
+ private PubKeyRing refundAgentPubKeyRing;
+ @Getter
+ @Nullable
+ private RefundResultState refundResultState = RefundResultState.UNDEFINED_REFUND_RESULT;
+ transient final private ObjectProperty refundResultStateProperty = new SimpleObjectProperty<>(refundResultState);
+
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
@@ -396,6 +430,7 @@ protected Trade(Offer offer,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
this.offer = offer;
@@ -406,6 +441,7 @@ protected Trade(Offer offer,
this.btcWalletService = btcWalletService;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
txFeeAsLong = txFee.value;
takerFeeAsLong = takerFee.value;
@@ -425,6 +461,7 @@ protected Trade(Offer offer,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
@@ -434,6 +471,7 @@ protected Trade(Offer offer,
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
this.tradePrice = tradePrice;
@@ -463,7 +501,8 @@ public Message toProtoMessage() {
.setTradePeriodState(Trade.TradePeriodState.toProtoMessage(tradePeriodState))
.addAllChatMessage(chatMessages.stream()
.map(msg -> msg.toProtoNetworkEnvelope().getChatMessage())
- .collect(Collectors.toList()));
+ .collect(Collectors.toList()))
+ .setLockTime(lockTime);
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId);
@@ -476,13 +515,17 @@ public Message toProtoMessage() {
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
Optional.ofNullable(mediatorNodeAddress).ifPresent(e -> builder.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage()));
+ Optional.ofNullable(refundAgentNodeAddress).ifPresent(e -> builder.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage()));
Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey)));
Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId);
Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage);
Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage()));
Optional.ofNullable(mediatorPubKeyRing).ifPresent(e -> builder.setMediatorPubKeyRing(mediatorPubKeyRing.toProtoMessage()));
+ Optional.ofNullable(refundAgentPubKeyRing).ifPresent(e -> builder.setRefundAgentPubKeyRing(refundAgentPubKeyRing.toProtoMessage()));
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
+ Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState)));
+ Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
return builder.build();
}
@@ -502,13 +545,18 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv
trade.setMakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()));
trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
trade.setMediatorNodeAddress(proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null);
+ trade.setRefundAgentNodeAddress(proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey()));
trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId()));
trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
trade.setArbitratorPubKeyRing(proto.hasArbitratorPubKeyRing() ? PubKeyRing.fromProto(proto.getArbitratorPubKeyRing()) : null);
trade.setMediatorPubKeyRing(proto.hasMediatorPubKeyRing() ? PubKeyRing.fromProto(proto.getMediatorPubKeyRing()) : null);
+ trade.setRefundAgentPubKeyRing(proto.hasRefundAgentPubKeyRing() ? PubKeyRing.fromProto(proto.getRefundAgentPubKeyRing()) : null);
trade.setCounterCurrencyTxId(proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId());
trade.setMediationResultState(MediationResultState.fromProto(proto.getMediationResultState()));
+ trade.setRefundResultState(RefundResultState.fromProto(proto.getRefundResultState()));
+ trade.setDelayedPayoutTxBytes(ProtoUtil.byteArrayOrNullFromProto(proto.getDelayedPayoutTxBytes()));
+ trade.setLockTime(proto.getLockTime());
trade.chatMessages.addAll(proto.getChatMessageList().stream()
.map(ChatMessage::fromPayloadProto)
@@ -531,6 +579,7 @@ public void init(P2PService p2PService,
BtcWalletService btcWalletService,
BsqWalletService bsqWalletService,
TradeWalletService tradeWalletService,
+ DaoFacade daoFacade,
TradeManager tradeManager,
OpenOfferManager openOfferManager,
ReferralIdService referralIdService,
@@ -540,6 +589,7 @@ public void init(P2PService p2PService,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
KeyRing keyRing,
boolean useSavingsWallet,
Coin fundsNeededForTrade) {
@@ -550,6 +600,7 @@ public void init(P2PService p2PService,
btcWalletService,
bsqWalletService,
tradeWalletService,
+ daoFacade,
referralIdService,
user,
filterManager,
@@ -557,6 +608,7 @@ public void init(P2PService p2PService,
tradeStatisticsManager,
arbitratorManager,
mediatorManager,
+ refundAgentManager,
keyRing,
useSavingsWallet,
fundsNeededForTrade);
@@ -572,6 +624,11 @@ public void init(P2PService p2PService,
persist();
});
+ refundAgentManager.getDisputeAgentByNodeAddress(refundAgentNodeAddress).ifPresent(refundAgent -> {
+ refundAgentPubKeyRing = refundAgent.getPubKeyRing();
+ persist();
+ });
+
createTradeProtocol();
// If we have already received a msg we apply it.
@@ -590,11 +647,10 @@ public void init(P2PService p2PService,
// The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet.
void updateDepositTxFromWallet() {
if (getDepositTx() != null)
- setDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getHash()));
+ applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getHash()));
}
- public void setDepositTx(Transaction tx) {
- log.debug("setDepositTx " + tx);
+ public void applyDepositTx(Transaction tx) {
this.depositTx = tx;
depositTxId = depositTx.getHashAsString();
setupConfidenceListener();
@@ -608,6 +664,25 @@ public Transaction getDepositTx() {
return depositTx;
}
+ public void applyDelayedPayoutTx(Transaction delayedPayoutTx) {
+ this.delayedPayoutTx = delayedPayoutTx;
+ this.delayedPayoutTxBytes = delayedPayoutTx.bitcoinSerialize();
+ persist();
+ }
+
+ public void applyDelayedPayoutTxBytes(byte[] delayedPayoutTxBytes) {
+ this.delayedPayoutTxBytes = delayedPayoutTxBytes;
+ persist();
+ }
+
+ @Nullable
+ public Transaction getDelayedPayoutTx() {
+ if (delayedPayoutTx == null) {
+ delayedPayoutTx = delayedPayoutTxBytes != null ? processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxBytes) : null;
+ }
+ return delayedPayoutTx;
+ }
+
// We don't need to persist the msg as if we dont apply it it will not be removed from the P2P network and we
// will received it again at next startup. Such might happen in edge cases when the user shuts down after we
// received the msb but before the init is called.
@@ -703,6 +778,14 @@ public void setMediationResultState(MediationResultState mediationResultState) {
persist();
}
+ public void setRefundResultState(RefundResultState refundResultState) {
+ boolean changed = this.refundResultState != refundResultState;
+ this.refundResultState = refundResultState;
+ refundResultStateProperty.set(refundResultState);
+ if (changed)
+ persist();
+ }
+
public void setTradePeriodState(TradePeriodState tradePeriodState) {
boolean changed = this.tradePeriodState != tradePeriodState;
@@ -821,7 +904,12 @@ public boolean isDepositPublished() {
}
public boolean isFundsLockedIn() {
- return isDepositPublished() && !isPayoutPublished() && disputeState != DisputeState.DISPUTE_CLOSED;
+ return isDepositPublished() &&
+ !isPayoutPublished() &&
+ disputeState != DisputeState.DISPUTE_CLOSED &&
+ disputeState != DisputeState.REFUND_REQUESTED &&
+ disputeState != DisputeState.REFUND_REQUEST_STARTED_BY_PEER &&
+ disputeState != DisputeState.REFUND_REQUEST_CLOSED;
}
public boolean isDepositConfirmed() {
@@ -832,7 +920,6 @@ public boolean isFiatSent() {
return getState().getPhase().ordinal() >= Phase.FIAT_SENT.ordinal();
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isFiatReceived() {
return getState().getPhase().ordinal() >= Phase.FIAT_RECEIVED.ordinal();
}
@@ -861,6 +948,10 @@ public ReadOnlyObjectProperty mediationResultStateProperty
return mediationResultStateProperty;
}
+ public ReadOnlyObjectProperty refundResultStateProperty() {
+ return refundResultStateProperty;
+ }
+
public ReadOnlyObjectProperty tradePeriodStateProperty() {
return tradePeriodStateProperty;
}
@@ -987,7 +1078,7 @@ public String toString() {
",\n isCurrencyForTakerFeeBtc=" + isCurrencyForTakerFeeBtc +
",\n txFeeAsLong=" + txFeeAsLong +
",\n takerFeeAsLong=" + takerFeeAsLong +
- ",\n takeOfferDate=" + getTakeOfferDate() +
+ ",\n takeOfferDate=" + takeOfferDate +
",\n processModel=" + processModel +
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
",\n depositTxId='" + depositTxId + '\'' +
@@ -1004,10 +1095,14 @@ public String toString() {
",\n takerContractSignature='" + takerContractSignature + '\'' +
",\n makerContractSignature='" + makerContractSignature + '\'' +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
- ",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
+ ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
+ ",\n mediatorNodeAddress=" + mediatorNodeAddress +
+ ",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
",\n errorMessage='" + errorMessage + '\'' +
+ ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
+ ",\n chatMessages=" + chatMessages +
",\n txFee=" + txFee +
",\n takerFee=" + takerFee +
",\n storage=" + storage +
@@ -1018,15 +1113,21 @@ public String toString() {
",\n tradePeriodStateProperty=" + tradePeriodStateProperty +
",\n errorMessageProperty=" + errorMessageProperty +
",\n tradeProtocol=" + tradeProtocol +
- ",\n payoutTx=" + payoutTx +
",\n depositTx=" + depositTx +
+ ",\n delayedPayoutTx=" + delayedPayoutTx +
+ ",\n payoutTx=" + payoutTx +
",\n tradeAmount=" + tradeAmount +
",\n tradeAmountProperty=" + tradeAmountProperty +
",\n tradeVolumeProperty=" + tradeVolumeProperty +
",\n decryptedMessageWithPubKeySet=" + decryptedMessageWithPubKeySet +
- ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
- ",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
- ",\n chatMessages=" + chatMessages +
+ ",\n mediationResultState=" + mediationResultState +
+ ",\n mediationResultStateProperty=" + mediationResultStateProperty +
+ ",\n lockTime=" + lockTime +
+ ",\n delayedPayoutTxBytes=" + Utilities.bytesAsHexString(delayedPayoutTxBytes) +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
+ ",\n refundAgentPubKeyRing=" + refundAgentPubKeyRing +
+ ",\n refundResultState=" + refundResultState +
+ ",\n refundResultStateProperty=" + refundResultStateProperty +
"\n}";
}
}
diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java
index d3e57398885..1faf2d9a8a9 100644
--- a/core/src/main/java/bisq/core/trade/TradeManager.java
+++ b/core/src/main/java/bisq/core/trade/TradeManager.java
@@ -19,10 +19,14 @@
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.exceptions.AddressEntryException;
+import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.TxBroadcaster;
+import bisq.core.btc.wallet.WalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
@@ -32,10 +36,12 @@
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.handlers.TradeResultHandler;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatisticsManager;
@@ -47,9 +53,9 @@
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
+import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.ClockWatcher;
-import bisq.common.UserThread;
import bisq.common.crypto.KeyRing;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.FaultHandler;
@@ -82,7 +88,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
+import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -116,6 +122,8 @@ public class TradeManager implements PersistedDataHost {
private final AccountAgeWitnessService accountAgeWitnessService;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
+ private final RefundAgentManager refundAgentManager;
+ private final DaoFacade daoFacade;
private final ClockWatcher clockWatcher;
private final Storage> tradableListStorage;
@@ -150,6 +158,8 @@ public TradeManager(User user,
AccountAgeWitnessService accountAgeWitnessService,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
+ DaoFacade daoFacade,
ClockWatcher clockWatcher,
Storage> storage) {
this.user = user;
@@ -168,6 +178,8 @@ public TradeManager(User user,
this.accountAgeWitnessService = accountAgeWitnessService;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
+ this.daoFacade = daoFacade;
this.clockWatcher = clockWatcher;
tradableListStorage = storage;
@@ -176,8 +188,8 @@ public TradeManager(User user,
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
// Handler for incoming initial network_messages from taker
- if (networkEnvelope instanceof PayDepositRequest) {
- handlePayDepositRequest((PayDepositRequest) networkEnvelope, peerNodeAddress);
+ if (networkEnvelope instanceof InputsForDepositTxRequest) {
+ handlePayDepositRequest((InputsForDepositTxRequest) networkEnvelope, peerNodeAddress);
}
});
@@ -274,11 +286,6 @@ private void initPendingTrades() {
cleanUpAddressEntries();
- // TODO remove once we support Taker side publishing at take offer process
- // We start later to have better connectivity to the network
- UserThread.runAfter(() -> tradeStatisticsManager.publishTradeStatistics(tradesForStatistics),
- 30, TimeUnit.SECONDS);
-
pendingTradesInitialized.set(true);
}
@@ -307,18 +314,18 @@ private void cleanUpAddressEntries() {
});
}
- private void handlePayDepositRequest(PayDepositRequest payDepositRequest, NodeAddress peer) {
+ private void handlePayDepositRequest(InputsForDepositTxRequest inputsForDepositTxRequest, NodeAddress peer) {
log.info("Received PayDepositRequest from {} with tradeId {} and uid {}",
- peer, payDepositRequest.getTradeId(), payDepositRequest.getUid());
+ peer, inputsForDepositTxRequest.getTradeId(), inputsForDepositTxRequest.getUid());
try {
- Validator.nonEmptyStringOf(payDepositRequest.getTradeId());
+ Validator.nonEmptyStringOf(inputsForDepositTxRequest.getTradeId());
} catch (Throwable t) {
- log.warn("Invalid requestDepositTxInputsMessage " + payDepositRequest.toString());
+ log.warn("Invalid requestDepositTxInputsMessage " + inputsForDepositTxRequest.toString());
return;
}
- Optional openOfferOptional = openOfferManager.getOpenOfferById(payDepositRequest.getTradeId());
+ Optional openOfferOptional = openOfferManager.getOpenOfferById(inputsForDepositTxRequest.getTradeId());
if (openOfferOptional.isPresent() && openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) {
OpenOffer openOffer = openOfferOptional.get();
Offer offer = openOffer.getOffer();
@@ -326,26 +333,28 @@ private void handlePayDepositRequest(PayDepositRequest payDepositRequest, NodeAd
Trade trade;
if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer,
- Coin.valueOf(payDepositRequest.getTxFee()),
- Coin.valueOf(payDepositRequest.getTakerFee()),
- payDepositRequest.isCurrencyForTakerFeeBtc(),
+ Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
+ Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
+ inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
openOffer.getMediatorNodeAddress(),
+ openOffer.getRefundAgentNodeAddress(),
tradableListStorage,
btcWalletService);
else
trade = new SellerAsMakerTrade(offer,
- Coin.valueOf(payDepositRequest.getTxFee()),
- Coin.valueOf(payDepositRequest.getTakerFee()),
- payDepositRequest.isCurrencyForTakerFeeBtc(),
+ Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
+ Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
+ inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
openOffer.getMediatorNodeAddress(),
+ openOffer.getRefundAgentNodeAddress(),
tradableListStorage,
btcWalletService);
initTrade(trade, trade.getProcessModel().isUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTradeAsLong());
tradableList.add(trade);
- ((MakerTrade) trade).handleTakeOfferRequest(payDepositRequest, peer, errorMessage -> {
+ ((MakerTrade) trade).handleTakeOfferRequest(inputsForDepositTxRequest, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
@@ -362,6 +371,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo
btcWalletService,
bsqWalletService,
tradeWalletService,
+ daoFacade,
this,
openOfferManager,
referralIdService,
@@ -371,6 +381,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo
tradeStatisticsManager,
arbitratorManager,
mediatorManager,
+ refundAgentManager,
keyRing,
useSavingsWallet,
fundsNeededForTrade);
@@ -453,6 +464,7 @@ private void createTrade(Coin amount,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
+ model.getSelectedRefundAgent(),
tradableListStorage,
btcWalletService);
else
@@ -465,6 +477,7 @@ private void createTrade(Coin amount,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
+ model.getSelectedRefundAgent(),
tradableListStorage,
btcWalletService);
@@ -567,6 +580,70 @@ public void closeDisputedTrade(String tradeId, Trade.DisputeState disputeState)
}
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Publish delayed payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public void publishDelayedPayoutTx(String tradeId,
+ ResultHandler resultHandler,
+ ErrorMessageHandler errorMessageHandler) {
+ getTradeById(tradeId).ifPresent(trade -> {
+ Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
+ if (delayedPayoutTx != null) {
+ // We have spent the funds from the deposit tx with the delayedPayoutTx
+ btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
+ // We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that
+
+ Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet());
+
+ tradeWalletService.broadcastTx(committedDelayedPayoutTx, new TxBroadcaster.Callback() {
+ @Override
+ public void onSuccess(Transaction transaction) {
+ log.info("publishDelayedPayoutTx onSuccess " + transaction);
+ NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress();
+ PeerPublishedDelayedPayoutTxMessage msg = new PeerPublishedDelayedPayoutTxMessage(UUID.randomUUID().toString(),
+ tradeId,
+ tradingPeerNodeAddress);
+ p2PService.sendEncryptedMailboxMessage(
+ tradingPeerNodeAddress,
+ trade.getProcessModel().getTradingPeer().getPubKeyRing(),
+ msg,
+ new SendMailboxMessageListener() {
+ @Override
+ public void onArrived() {
+ resultHandler.handleResult();
+ log.info("SendMailboxMessageListener onArrived tradeId={} at peer {}",
+ tradeId, tradingPeerNodeAddress);
+ }
+
+ @Override
+ public void onStoredInMailbox() {
+ resultHandler.handleResult();
+ log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {}",
+ tradeId, tradingPeerNodeAddress);
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("SendMailboxMessageListener onFault tradeId={} at peer {}",
+ tradeId, tradingPeerNodeAddress);
+ errorMessageHandler.handleErrorMessage(errorMessage);
+ }
+ }
+ );
+ }
+
+ @Override
+ public void onFailure(TxBroadcastException exception) {
+ log.error("publishDelayedPayoutTx onFailure", exception);
+ errorMessageHandler.handleErrorMessage(exception.toString());
+ }
+ });
+ }
+ });
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java
new file mode 100644
index 00000000000..0d1182001b4
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java
@@ -0,0 +1,89 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.DirectMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+import bisq.common.util.Utilities;
+
+import com.google.protobuf.ByteString;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class DelayedPayoutTxSignatureRequest extends TradeMessage implements DirectMessage {
+ private final NodeAddress senderNodeAddress;
+ private final byte[] delayedPayoutTx;
+
+ public DelayedPayoutTxSignatureRequest(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTx) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress,
+ delayedPayoutTx);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private DelayedPayoutTxSignatureRequest(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTx) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ this.delayedPayoutTx = delayedPayoutTx;
+ }
+
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ return getNetworkEnvelopeBuilder()
+ .setDelayedPayoutTxSignatureRequest(protobuf.DelayedPayoutTxSignatureRequest.newBuilder()
+ .setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
+ .setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
+ .build();
+ }
+
+ public static DelayedPayoutTxSignatureRequest fromProto(protobuf.DelayedPayoutTxSignatureRequest proto, int messageVersion) {
+ return new DelayedPayoutTxSignatureRequest(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDelayedPayoutTx().toByteArray());
+ }
+
+ @Override
+ public String toString() {
+ return "DelayedPayoutTxSignatureRequest{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java
new file mode 100644
index 00000000000..639d5edb72a
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java
@@ -0,0 +1,90 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.DirectMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+import bisq.common.util.Utilities;
+
+import com.google.protobuf.ByteString;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class DelayedPayoutTxSignatureResponse extends TradeMessage implements DirectMessage {
+ private final NodeAddress senderNodeAddress;
+ private final byte[] delayedPayoutTxSignature;
+
+ public DelayedPayoutTxSignatureResponse(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTxSignature) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress,
+ delayedPayoutTxSignature);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private DelayedPayoutTxSignatureResponse(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTxSignature) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ this.delayedPayoutTxSignature = delayedPayoutTxSignature;
+ }
+
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ return getNetworkEnvelopeBuilder()
+ .setDelayedPayoutTxSignatureResponse(protobuf.DelayedPayoutTxSignatureResponse.newBuilder()
+ .setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
+ .setDelayedPayoutTxSignature(ByteString.copyFrom(delayedPayoutTxSignature))
+ )
+ .build();
+ }
+
+ public static DelayedPayoutTxSignatureResponse fromProto(protobuf.DelayedPayoutTxSignatureResponse proto, int messageVersion) {
+ return new DelayedPayoutTxSignatureResponse(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDelayedPayoutTxSignature().toByteArray());
+ }
+
+ @Override
+ public String toString() {
+ return "DelayedPayoutTxSignatureResponse{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n delayedPayoutTxSignature=" + Utilities.bytesAsHexString(delayedPayoutTxSignature) +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java
new file mode 100644
index 00000000000..6d90bb1f2de
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java
@@ -0,0 +1,98 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+import bisq.common.util.Utilities;
+
+import com.google.protobuf.ByteString;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+// It is the last message in the take offer phase. We use MailboxMessage instead of DirectMessage to add more tolerance
+// in case of network issues and as the message does not trigger further protocol execution.
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class DepositTxAndDelayedPayoutTxMessage extends TradeMessage implements MailboxMessage {
+ private final NodeAddress senderNodeAddress;
+ private final byte[] depositTx;
+ private final byte[] delayedPayoutTx;
+
+ public DepositTxAndDelayedPayoutTxMessage(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx,
+ byte[] delayedPayoutTx) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress,
+ depositTx,
+ delayedPayoutTx);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private DepositTxAndDelayedPayoutTxMessage(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx,
+ byte[] delayedPayoutTx) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ this.depositTx = depositTx;
+ this.delayedPayoutTx = delayedPayoutTx;
+ }
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ return getNetworkEnvelopeBuilder()
+ .setDepositTxAndDelayedPayoutTxMessage(protobuf.DepositTxAndDelayedPayoutTxMessage.newBuilder()
+ .setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
+ .setDepositTx(ByteString.copyFrom(depositTx))
+ .setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
+ .build();
+ }
+
+ public static DepositTxAndDelayedPayoutTxMessage fromProto(protobuf.DepositTxAndDelayedPayoutTxMessage proto, int messageVersion) {
+ return new DepositTxAndDelayedPayoutTxMessage(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDepositTx().toByteArray(),
+ proto.getDelayedPayoutTx().toByteArray());
+ }
+
+ @Override
+ public String toString() {
+ return "DepositTxAndDelayedPayoutTxMessage{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
+ ",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
similarity index 58%
rename from core/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java
rename to core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
index e5fd07021e0..4350afd2266 100644
--- a/core/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
@@ -17,7 +17,7 @@
package bisq.core.trade.messages;
-import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
@@ -28,64 +28,63 @@
import lombok.EqualsAndHashCode;
import lombok.Value;
+// It is the last message in the take offer phase. We use MailboxMessage instead of DirectMessage to add more tolerance
+// in case of network issues and as the message does not trigger further protocol execution.
@EqualsAndHashCode(callSuper = true)
@Value
-public final class DepositTxPublishedMessage extends TradeMessage implements MailboxMessage {
- private final byte[] depositTx;
+public final class DepositTxMessage extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
+ private final byte[] depositTx;
- public DepositTxPublishedMessage(String tradeId,
- byte[] depositTx,
- NodeAddress senderNodeAddress,
- String uid) {
- this(tradeId,
- depositTx,
- senderNodeAddress,
+ public DepositTxMessage(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx) {
+ this(Version.getP2PMessageVersion(),
uid,
- Version.getP2PMessageVersion());
+ tradeId,
+ senderNodeAddress,
+ depositTx);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- private DepositTxPublishedMessage(String tradeId,
- byte[] depositTx,
- NodeAddress senderNodeAddress,
- String uid,
- int messageVersion) {
+ private DepositTxMessage(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx) {
super(messageVersion, tradeId, uid);
- this.depositTx = depositTx;
this.senderNodeAddress = senderNodeAddress;
+ this.depositTx = depositTx;
}
-
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
- .setDepositTxPublishedMessage(protobuf.DepositTxPublishedMessage.newBuilder()
+ .setDepositTxMessage(protobuf.DepositTxMessage.newBuilder()
+ .setUid(uid)
.setTradeId(tradeId)
- .setDepositTx(ByteString.copyFrom(depositTx))
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setUid(uid))
+ .setDepositTx(ByteString.copyFrom(depositTx)))
.build();
}
- public static DepositTxPublishedMessage fromProto(protobuf.DepositTxPublishedMessage proto, int messageVersion) {
- return new DepositTxPublishedMessage(proto.getTradeId(),
- proto.getDepositTx().toByteArray(),
- NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ public static DepositTxMessage fromProto(protobuf.DepositTxMessage proto, int messageVersion) {
+ return new DepositTxMessage(messageVersion,
proto.getUid(),
- messageVersion);
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDepositTx().toByteArray());
}
-
@Override
public String toString() {
- return "DepositTxPublishedMessage{" +
- "\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
- ",\n senderNodeAddress=" + senderNodeAddress +
- ",\n uid='" + uid + '\'' +
+ return "DepositTxMessage{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/PayDepositRequest.java b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java
similarity index 71%
rename from core/src/main/java/bisq/core/trade/messages/PayDepositRequest.java
rename to core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java
index 05447f26a65..57df03a1b24 100644
--- a/core/src/main/java/bisq/core/trade/messages/PayDepositRequest.java
+++ b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java
@@ -21,6 +21,7 @@
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
+import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
@@ -29,7 +30,6 @@
import com.google.protobuf.ByteString;
-import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -41,7 +41,7 @@
@EqualsAndHashCode(callSuper = true)
@Value
-public final class PayDepositRequest extends TradeMessage {
+public final class InputsForDepositTxRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final long tradeAmount;
private final long tradePrice;
@@ -60,38 +60,43 @@ public final class PayDepositRequest extends TradeMessage {
private final String takerFeeTxId;
private final List acceptedArbitratorNodeAddresses;
private final List acceptedMediatorNodeAddresses;
+ private final List acceptedRefundAgentNodeAddresses;
+ @Nullable
private final NodeAddress arbitratorNodeAddress;
private final NodeAddress mediatorNodeAddress;
+ private final NodeAddress refundAgentNodeAddress;
// added in v 0.6. can be null if we trade with an older peer
@Nullable
private final byte[] accountAgeWitnessSignatureOfOfferId;
private final long currentDate;
- public PayDepositRequest(String tradeId,
- NodeAddress senderNodeAddress,
- long tradeAmount,
- long tradePrice,
- long txFee,
- long takerFee,
- boolean isCurrencyForTakerFeeBtc,
- List rawTransactionInputs,
- long changeOutputValue,
- @Nullable String changeOutputAddress,
- byte[] takerMultiSigPubKey,
- String takerPayoutAddressString,
- PubKeyRing takerPubKeyRing,
- PaymentAccountPayload takerPaymentAccountPayload,
- String takerAccountId,
- String takerFeeTxId,
- List acceptedArbitratorNodeAddresses,
- List acceptedMediatorNodeAddresses,
- NodeAddress arbitratorNodeAddress,
- NodeAddress mediatorNodeAddress,
- String uid,
- int messageVersion,
- @Nullable byte[] accountAgeWitnessSignatureOfOfferId,
- long currentDate) {
+ public InputsForDepositTxRequest(String tradeId,
+ NodeAddress senderNodeAddress,
+ long tradeAmount,
+ long tradePrice,
+ long txFee,
+ long takerFee,
+ boolean isCurrencyForTakerFeeBtc,
+ List rawTransactionInputs,
+ long changeOutputValue,
+ @Nullable String changeOutputAddress,
+ byte[] takerMultiSigPubKey,
+ String takerPayoutAddressString,
+ PubKeyRing takerPubKeyRing,
+ PaymentAccountPayload takerPaymentAccountPayload,
+ String takerAccountId,
+ String takerFeeTxId,
+ List acceptedArbitratorNodeAddresses,
+ List acceptedMediatorNodeAddresses,
+ List acceptedRefundAgentNodeAddresses,
+ NodeAddress arbitratorNodeAddress,
+ NodeAddress mediatorNodeAddress,
+ NodeAddress refundAgentNodeAddress,
+ String uid,
+ int messageVersion,
+ @Nullable byte[] accountAgeWitnessSignatureOfOfferId,
+ long currentDate) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.tradeAmount = tradeAmount;
@@ -110,8 +115,10 @@ public PayDepositRequest(String tradeId,
this.takerFeeTxId = takerFeeTxId;
this.acceptedArbitratorNodeAddresses = acceptedArbitratorNodeAddresses;
this.acceptedMediatorNodeAddresses = acceptedMediatorNodeAddresses;
+ this.acceptedRefundAgentNodeAddresses = acceptedRefundAgentNodeAddresses;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
this.currentDate = currentDate;
}
@@ -123,7 +130,7 @@ public PayDepositRequest(String tradeId,
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
- protobuf.PayDepositRequest.Builder builder = protobuf.PayDepositRequest.newBuilder()
+ protobuf.InputsForDepositTxRequest.Builder builder = protobuf.InputsForDepositTxRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setTradeAmount(tradeAmount)
@@ -144,20 +151,23 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
.map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
.addAllAcceptedMediatorNodeAddresses(acceptedMediatorNodeAddresses.stream()
.map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
- .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
+ .addAllAcceptedRefundAgentNodeAddresses(acceptedRefundAgentNodeAddresses.stream()
+ .map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())
+ .setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
.setUid(uid);
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
+ Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
builder.setCurrentDate(currentDate);
- return getNetworkEnvelopeBuilder().setPayDepositRequest(builder).build();
+ return getNetworkEnvelopeBuilder().setInputsForDepositTxRequest(builder).build();
}
- public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
- CoreProtoResolver coreProtoResolver,
- int messageVersion) {
+ public static InputsForDepositTxRequest fromProto(protobuf.InputsForDepositTxRequest proto,
+ CoreProtoResolver coreProtoResolver,
+ int messageVersion) {
List rawTransactionInputs = proto.getRawTransactionInputsList().stream()
.map(rawTransactionInput -> new RawTransactionInput(rawTransactionInput.getIndex(),
rawTransactionInput.getParentTransaction().toByteArray(), rawTransactionInput.getValue()))
@@ -166,8 +176,10 @@ public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
.map(NodeAddress::fromProto).collect(Collectors.toList());
List acceptedMediatorNodeAddresses = proto.getAcceptedMediatorNodeAddressesList().stream()
.map(NodeAddress::fromProto).collect(Collectors.toList());
+ List acceptedRefundAgentNodeAddresses = proto.getAcceptedRefundAgentNodeAddressesList().stream()
+ .map(NodeAddress::fromProto).collect(Collectors.toList());
- return new PayDepositRequest(proto.getTradeId(),
+ return new InputsForDepositTxRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getTradeAmount(),
proto.getTradePrice(),
@@ -185,8 +197,10 @@ public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
proto.getTakerFeeTxId(),
acceptedArbitratorNodeAddresses,
acceptedMediatorNodeAddresses,
+ acceptedRefundAgentNodeAddresses,
NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
NodeAddress.fromProto(proto.getMediatorNodeAddress()),
+ NodeAddress.fromProto(proto.getRefundAgentNodeAddress()),
proto.getUid(),
messageVersion,
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
@@ -195,7 +209,7 @@ public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
@Override
public String toString() {
- return "PayDepositRequest{" +
+ return "InputsForDepositTxRequest{" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n tradeAmount=" + tradeAmount +
",\n tradePrice=" + tradePrice +
@@ -213,11 +227,12 @@ public String toString() {
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
",\n acceptedArbitratorNodeAddresses=" + acceptedArbitratorNodeAddresses +
",\n acceptedMediatorNodeAddresses=" + acceptedMediatorNodeAddresses +
+ ",\n acceptedRefundAgentNodeAddresses=" + acceptedRefundAgentNodeAddresses +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
- ",\n uid='" + uid + '\'' +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
- ",\n currentDate=" + new Date(currentDate) +
+ ",\n currentDate=" + currentDate +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java
similarity index 69%
rename from core/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java
rename to core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java
index 7e115979fbf..994be36e5bb 100644
--- a/core/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java
+++ b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java
@@ -21,7 +21,7 @@
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
-import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
@@ -40,12 +40,9 @@
import javax.annotation.Nullable;
-// We use a MailboxMessage here because the taker has paid already the trade fee and it could be that
-// we lost connection to him but we are complete on our side. So even if the peer is offline he can
-// continue later to complete the deposit tx.
@EqualsAndHashCode(callSuper = true)
@Value
-public final class PublishDepositTxRequest extends TradeMessage implements MailboxMessage {
+public final class InputsForDepositTxResponse extends TradeMessage implements DirectMessage {
private final PaymentAccountPayload makerPaymentAccountPayload;
private final String makerAccountId;
private final byte[] makerMultiSigPubKey;
@@ -60,20 +57,22 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
@Nullable
private final byte[] accountAgeWitnessSignatureOfPreparedDepositTx;
private final long currentDate;
-
- public PublishDepositTxRequest(String tradeId,
- PaymentAccountPayload makerPaymentAccountPayload,
- String makerAccountId,
- byte[] makerMultiSigPubKey,
- String makerContractAsJson,
- String makerContractSignature,
- String makerPayoutAddressString,
- byte[] preparedDepositTx,
- List makerInputs,
- NodeAddress senderNodeAddress,
- String uid,
- @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
- long currentDate) {
+ private final long lockTime;
+
+ public InputsForDepositTxResponse(String tradeId,
+ PaymentAccountPayload makerPaymentAccountPayload,
+ String makerAccountId,
+ byte[] makerMultiSigPubKey,
+ String makerContractAsJson,
+ String makerContractSignature,
+ String makerPayoutAddressString,
+ byte[] preparedDepositTx,
+ List makerInputs,
+ NodeAddress senderNodeAddress,
+ String uid,
+ @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
+ long currentDate,
+ long lockTime) {
this(tradeId,
makerPaymentAccountPayload,
makerAccountId,
@@ -87,7 +86,8 @@ public PublishDepositTxRequest(String tradeId,
uid,
Version.getP2PMessageVersion(),
accountAgeWitnessSignatureOfPreparedDepositTx,
- currentDate);
+ currentDate,
+ lockTime);
}
@@ -95,20 +95,21 @@ public PublishDepositTxRequest(String tradeId,
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- private PublishDepositTxRequest(String tradeId,
- PaymentAccountPayload makerPaymentAccountPayload,
- String makerAccountId,
- byte[] makerMultiSigPubKey,
- String makerContractAsJson,
- String makerContractSignature,
- String makerPayoutAddressString,
- byte[] preparedDepositTx,
- List makerInputs,
- NodeAddress senderNodeAddress,
- String uid,
- int messageVersion,
- @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
- long currentDate) {
+ private InputsForDepositTxResponse(String tradeId,
+ PaymentAccountPayload makerPaymentAccountPayload,
+ String makerAccountId,
+ byte[] makerMultiSigPubKey,
+ String makerContractAsJson,
+ String makerContractSignature,
+ String makerPayoutAddressString,
+ byte[] preparedDepositTx,
+ List makerInputs,
+ NodeAddress senderNodeAddress,
+ String uid,
+ int messageVersion,
+ @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
+ long currentDate,
+ long lockTime) {
super(messageVersion, tradeId, uid);
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
this.makerAccountId = makerAccountId;
@@ -121,11 +122,12 @@ private PublishDepositTxRequest(String tradeId,
this.senderNodeAddress = senderNodeAddress;
this.accountAgeWitnessSignatureOfPreparedDepositTx = accountAgeWitnessSignatureOfPreparedDepositTx;
this.currentDate = currentDate;
+ this.lockTime = lockTime;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
- final protobuf.PublishDepositTxRequest.Builder builder = protobuf.PublishDepositTxRequest.newBuilder()
+ final protobuf.InputsForDepositTxResponse.Builder builder = protobuf.InputsForDepositTxResponse.newBuilder()
.setTradeId(tradeId)
.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())
.setMakerAccountId(makerAccountId)
@@ -136,22 +138,23 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx))
.addAllMakerInputs(makerInputs.stream().map(RawTransactionInput::toProtoMessage).collect(Collectors.toList()))
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setUid(uid);
+ .setUid(uid)
+ .setLockTime(lockTime);
Optional.ofNullable(accountAgeWitnessSignatureOfPreparedDepositTx).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfPreparedDepositTx(ByteString.copyFrom(e)));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder()
- .setPublishDepositTxRequest(builder)
+ .setInputsForDepositTxResponse(builder)
.build();
}
- public static PublishDepositTxRequest fromProto(protobuf.PublishDepositTxRequest proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
+ public static InputsForDepositTxResponse fromProto(protobuf.InputsForDepositTxResponse proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
List makerInputs = proto.getMakerInputsList().stream()
.map(RawTransactionInput::fromProto)
.collect(Collectors.toList());
- return new PublishDepositTxRequest(proto.getTradeId(),
+ return new InputsForDepositTxResponse(proto.getTradeId(),
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
proto.getMakerAccountId(),
proto.getMakerMultiSigPubKey().toByteArray(),
@@ -164,13 +167,14 @@ public static PublishDepositTxRequest fromProto(protobuf.PublishDepositTxRequest
proto.getUid(),
messageVersion,
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfPreparedDepositTx()),
- proto.getCurrentDate());
+ proto.getCurrentDate(),
+ proto.getLockTime());
}
@Override
public String toString() {
- return "PublishDepositTxRequest{" +
+ return "InputsForDepositTxResponse{" +
"\n makerPaymentAccountPayload=" + makerPaymentAccountPayload +
",\n makerAccountId='" + makerAccountId + '\'' +
",\n makerMultiSigPubKey=" + Utilities.bytesAsHexString(makerMultiSigPubKey) +
@@ -183,6 +187,7 @@ public String toString() {
",\n uid='" + uid + '\'' +
",\n accountAgeWitnessSignatureOfPreparedDepositTx=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfPreparedDepositTx) +
",\n currentDate=" + new Date(currentDate) +
+ ",\n lockTime=" + lockTime +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java
new file mode 100644
index 00000000000..9447f9494f9
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java
@@ -0,0 +1,77 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class PeerPublishedDelayedPayoutTxMessage extends TradeMessage implements MailboxMessage {
+ private final NodeAddress senderNodeAddress;
+
+ public PeerPublishedDelayedPayoutTxMessage(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private PeerPublishedDelayedPayoutTxMessage(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ }
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ final protobuf.PeerPublishedDelayedPayoutTxMessage.Builder builder = protobuf.PeerPublishedDelayedPayoutTxMessage.newBuilder();
+ builder.setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage());
+ return getNetworkEnvelopeBuilder().setPeerPublishedDelayedPayoutTxMessage(builder).build();
+ }
+
+ public static PeerPublishedDelayedPayoutTxMessage fromProto(protobuf.PeerPublishedDelayedPayoutTxMessage proto, int messageVersion) {
+ return new PeerPublishedDelayedPayoutTxMessage(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()));
+ }
+
+ @Override
+ public String toString() {
+ return "PeerPublishedDelayedPayoutTxMessage{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/TradeMessage.java b/core/src/main/java/bisq/core/trade/messages/TradeMessage.java
index 5b386e2e1e5..e90cbb02657 100644
--- a/core/src/main/java/bisq/core/trade/messages/TradeMessage.java
+++ b/core/src/main/java/bisq/core/trade/messages/TradeMessage.java
@@ -17,7 +17,6 @@
package bisq.core.trade.messages;
-import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.UidMessage;
import bisq.common.proto.network.NetworkEnvelope;
@@ -29,7 +28,7 @@
@EqualsAndHashCode(callSuper = true)
@Getter
@ToString
-public abstract class TradeMessage extends NetworkEnvelope implements DirectMessage, UidMessage {
+public abstract class TradeMessage extends NetworkEnvelope implements UidMessage {
protected final String tradeId;
protected final String uid;
diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
index dd68fd16f01..236e6d47b09 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
@@ -19,38 +19,40 @@
import bisq.core.trade.BuyerAsMakerTrade;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx;
-import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
+import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
+import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.util.Validator;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
-import static com.google.common.base.Preconditions.checkArgument;
-
@Slf4j
public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol, MakerProtocol {
private final BuyerAsMakerTrade buyerAsMakerTrade;
@@ -68,10 +70,10 @@ public BuyerAsMakerProtocol(BuyerAsMakerTrade trade) {
Trade.Phase phase = trade.getState().getPhase();
if (phase == Trade.Phase.TAKER_FEE_PUBLISHED) {
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
- () -> handleTaskRunnerSuccess("MakerSetupDepositTxListener"),
+ () -> handleTaskRunnerSuccess("BuyerSetupDepositTxListener"),
this::handleTaskRunnerFault);
- taskRunner.addTasks(MakerSetupDepositTxListener.class);
+ taskRunner.addTasks(BuyerSetupDepositTxListener.class);
taskRunner.run();
} else if (trade.isFiatSent() && !trade.isPayoutPublished()) {
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
@@ -89,23 +91,13 @@ public BuyerAsMakerProtocol(BuyerAsMakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- MailboxMessage mailboxMessage = (MailboxMessage) networkEnvelope;
- NodeAddress peerNodeAddress = mailboxMessage.getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof DepositTxPublishedMessage)
- handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof PayoutTxPublishedMessage)
- handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
- else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
+ handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -115,11 +107,10 @@ else if (tradeMessage instanceof PayoutTxPublishedMessage)
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void handleTakeOfferRequest(TradeMessage tradeMessage,
+ public void handleTakeOfferRequest(InputsForDepositTxRequest tradeMessage,
NodeAddress peerNodeAddress,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), tradeMessage);
- checkArgument(tradeMessage instanceof PayDepositRequest);
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
@@ -130,15 +121,16 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
handleTaskRunnerFault(errorMessage);
});
taskRunner.addTasks(
- MakerProcessPayDepositRequest.class,
+ MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
MakerVerifyTakerAccount.class,
VerifyPeersAccountAgeWitness.class,
MakerVerifyTakerFeePayment.class,
+ MakerSetsLockTime.class,
MakerCreateAndSignContract.class,
BuyerAsMakerCreatesAndSignsDepositTx.class,
- MakerSetupDepositTxListener.class,
- MakerSendPublishDepositTxRequest.class
+ BuyerSetupDepositTxListener.class,
+ BuyerAsMakerSendsInputsForDepositTxResponse.class
);
// We don't use a timeout here because if the DepositTxPublishedMessage does not arrive we
// get the deposit tx set at MakerSetupDepositTxListener once it is seen in the bitcoin network
@@ -150,19 +142,35 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- private void handle(DepositTxPublishedMessage tradeMessage, NodeAddress peerNodeAddress) {
+ private void handle(DelayedPayoutTxSignatureRequest tradeMessage, NodeAddress peerNodeAddress) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade,
() -> {
- handleTaskRunnerSuccess(tradeMessage, "handle DepositTxPublishedMessage");
+ handleTaskRunnerSuccess(tradeMessage, "handle DelayedPayoutTxSignatureRequest");
},
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- MakerProcessDepositTxPublishedMessage.class,
- MakerVerifyTakerAccount.class,
- MakerVerifyTakerFeePayment.class,
+ BuyerProcessDelayedPayoutTxSignatureRequest.class,
+ BuyerSignsDelayedPayoutTx.class,
+ BuyerSendsDelayedPayoutTxSignatureResponse.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DepositTxAndDelayedPayoutTxMessage tradeMessage, NodeAddress peerNodeAddress) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade,
+ () -> {
+ handleTaskRunnerSuccess(tradeMessage, "handle DepositTxAndDelayedPayoutTxMessage");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+ taskRunner.addTasks(
+ BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
+ BuyerVerifiesDelayedPayoutTx.class,
PublishTradeStatistics.class
);
taskRunner.run();
@@ -191,7 +199,7 @@ public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandle
ApplyFilter.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
- BuyerAsMakerSignPayoutTx.class,
+ BuyerSignPayoutTx.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
BuyerSetupPayoutTxListener.class
);
@@ -232,8 +240,10 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof DepositTxPublishedMessage) {
- handle((DepositTxPublishedMessage) tradeMessage, sender);
+ if (tradeMessage instanceof DelayedPayoutTxSignatureRequest) {
+ handle((DelayedPayoutTxSignatureRequest) tradeMessage, sender);
+ } else if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
index 36b868cd90a..6047236501b 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
@@ -18,38 +18,47 @@
package bisq.core.trade.protocol;
+import bisq.core.offer.Offer;
import bisq.core.trade.BuyerAsTakerTrade;
import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
-import bisq.core.trade.messages.PublishDepositTxRequest;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
-import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs;
-import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignAndPublishDepositTx;
+import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
+import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerProcessPublishDepositTxRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerSendDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.taker.TakerSendPayDepositRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerAccount;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol, TakerProtocol {
private final BuyerAsTakerTrade buyerAsTakerTrade;
@@ -64,9 +73,18 @@ public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) {
this.buyerAsTakerTrade = trade;
- processModel.getTradingPeer().setPubKeyRing(trade.getOffer().getPubKeyRing());
+ Offer offer = checkNotNull(trade.getOffer());
+ processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
+
+ Trade.Phase phase = trade.getState().getPhase();
+ if (phase == Trade.Phase.TAKER_FEE_PUBLISHED) {
+ TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
+ () -> handleTaskRunnerSuccess("BuyerSetupDepositTxListener"),
+ this::handleTaskRunnerFault);
- if (trade.isFiatSent() && !trade.isPayoutPublished()) {
+ taskRunner.addTasks(BuyerSetupDepositTxListener.class);
+ taskRunner.run();
+ } else if (trade.isFiatSent() && !trade.isPayoutPublished()) {
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess("BuyerSetupPayoutTxListener"),
this::handleTaskRunnerFault);
@@ -82,22 +100,13 @@ public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- final NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest)
- handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof PayoutTxPublishedMessage) {
- handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
- } else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
+ handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -117,7 +126,7 @@ public void takeAvailableOffer() {
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
BuyerAsTakerCreatesDepositTxInputs.class,
- TakerSendPayDepositRequest.class
+ TakerSendInputsForDepositTxRequest.class
);
//TODO if peer does get an error he does not respond and all we get is the timeout now knowing why it failed.
@@ -131,7 +140,7 @@ public void takeAvailableOffer() {
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
+ private void handle(InputsForDepositTxResponse tradeMessage, NodeAddress sender) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -142,15 +151,50 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
},
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- TakerProcessPublishDepositTxRequest.class,
+ TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
VerifyPeersAccountAgeWitness.class,
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
- BuyerAsTakerSignAndPublishDepositTx.class,
- TakerSendDepositTxPublishedMessage.class,
+ BuyerAsTakerSignsDepositTx.class,
+ BuyerSetupDepositTxListener.class,
+ BuyerAsTakerSendsDepositTxMessage.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DelayedPayoutTxSignatureRequest tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
+ () -> {
+ handleTaskRunnerSuccess(tradeMessage, "handle DelayedPayoutTxSignatureRequest");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+ taskRunner.addTasks(
+ BuyerProcessDelayedPayoutTxSignatureRequest.class,
+ BuyerSignsDelayedPayoutTx.class,
+ BuyerSendsDelayedPayoutTxSignatureResponse.class
+ );
+ taskRunner.run();
+ }
+
+
+ private void handle(DepositTxAndDelayedPayoutTxMessage tradeMessage, NodeAddress peerNodeAddress) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
+ () -> {
+ handleTaskRunnerSuccess(tradeMessage, "handle DepositTxAndDelayedPayoutTxMessage");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+ taskRunner.addTasks(
+ BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
+ BuyerVerifiesDelayedPayoutTx.class,
PublishTradeStatistics.class
);
taskRunner.run();
@@ -180,7 +224,7 @@ public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandle
ApplyFilter.class,
TakerVerifyMakerAccount.class,
TakerVerifyMakerFeePayment.class,
- BuyerAsMakerSignPayoutTx.class,
+ BuyerSignPayoutTx.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
BuyerSetupPayoutTxListener.class
);
@@ -221,8 +265,12 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest) {
- handle((PublishDepositTxRequest) tradeMessage, sender);
+ if (tradeMessage instanceof InputsForDepositTxResponse) {
+ handle((InputsForDepositTxResponse) tradeMessage, sender);
+ } else if (tradeMessage instanceof DelayedPayoutTxSignatureRequest) {
+ handle((DelayedPayoutTxSignatureRequest) tradeMessage, sender);
+ } else if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java
index a95c78bfeb2..d460289e3fc 100644
--- a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java
@@ -18,12 +18,12 @@
package bisq.core.trade.protocol;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface MakerProtocol {
- void handleTakeOfferRequest(TradeMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
+ void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
index f2b180926aa..9e25fa1a0f8 100644
--- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
+++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
@@ -22,6 +22,7 @@
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.network.MessageState;
import bisq.core.offer.Offer;
@@ -31,6 +32,7 @@
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
@@ -69,6 +71,10 @@
import javax.annotation.Nullable;
+// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
+// persist them.
+//todo clean up older fields as well to make most transient
+
@Getter
@Slf4j
public class ProcessModel implements Model, PersistablePayload {
@@ -78,6 +84,7 @@ public class ProcessModel implements Model, PersistablePayload {
transient private BtcWalletService btcWalletService;
transient private BsqWalletService bsqWalletService;
transient private TradeWalletService tradeWalletService;
+ transient private DaoFacade daoFacade;
transient private Offer offer;
transient private User user;
transient private FilterManager filterManager;
@@ -85,6 +92,7 @@ public class ProcessModel implements Model, PersistablePayload {
transient private TradeStatisticsManager tradeStatisticsManager;
transient private ArbitratorManager arbitratorManager;
transient private MediatorManager mediatorManager;
+ transient private RefundAgentManager refundAgentManager;
transient private KeyRing keyRing;
transient private P2PService p2PService;
transient private ReferralIdService referralIdService;
@@ -96,32 +104,29 @@ public class ProcessModel implements Model, PersistablePayload {
@Setter
transient private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
-
- // Persistable Immutable (only set by PB)
+ // Added in v1.2.0
@Setter
- private TradingPeer tradingPeer = new TradingPeer();
+ @Nullable
+ transient private byte[] delayedPayoutTxSignature;
@Setter
+ @Nullable
+ transient private Transaction preparedDelayedPayoutTx;
+
+ // Persistable Immutable (private setter only used by PB method)
+ private TradingPeer tradingPeer = new TradingPeer();
private String offerId;
- @Setter
private String accountId;
- @Setter
private PubKeyRing pubKeyRing;
// Persistable Mutable
@Nullable
- @Setter
+ @Setter()
private String takeOfferFeeTxId;
@Nullable
@Setter
private byte[] payoutTxSignature;
@Nullable
@Setter
- private List takerAcceptedArbitratorNodeAddresses;
- @Nullable
- @Setter
- private List takerAcceptedMediatorNodeAddresses;
- @Nullable
- @Setter
private byte[] preparedDepositTx;
@Nullable
@Setter
@@ -182,14 +187,13 @@ public protobuf.ProcessModel toProtoMessage() {
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
- Optional.ofNullable(takerAcceptedArbitratorNodeAddresses).ifPresent(e -> builder.addAllTakerAcceptedArbitratorNodeAddresses(ProtoUtil.collectionToProto(takerAcceptedArbitratorNodeAddresses)));
- Optional.ofNullable(takerAcceptedMediatorNodeAddresses).ifPresent(e -> builder.addAllTakerAcceptedMediatorNodeAddresses(ProtoUtil.collectionToProto(takerAcceptedMediatorNodeAddresses)));
Optional.ofNullable(preparedDepositTx).ifPresent(e -> builder.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx)));
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs)));
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage()));
Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
+
return builder.build();
}
@@ -208,14 +212,6 @@ public static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResol
// nullable
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
- List takerAcceptedArbitratorNodeAddresses = proto.getTakerAcceptedArbitratorNodeAddressesList().isEmpty() ?
- null : proto.getTakerAcceptedArbitratorNodeAddressesList().stream()
- .map(NodeAddress::fromProto).collect(Collectors.toList());
- List takerAcceptedMediatorNodeAddresses = proto.getTakerAcceptedMediatorNodeAddressesList().isEmpty() ?
- null : proto.getTakerAcceptedMediatorNodeAddressesList().stream()
- .map(NodeAddress::fromProto).collect(Collectors.toList());
- processModel.setTakerAcceptedArbitratorNodeAddresses(takerAcceptedArbitratorNodeAddresses);
- processModel.setTakerAcceptedMediatorNodeAddresses(takerAcceptedMediatorNodeAddresses);
processModel.setPreparedDepositTx(ProtoUtil.byteArrayOrNullFromProto(proto.getPreparedDepositTx()));
List rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
null : proto.getRawTransactionInputsList().stream()
@@ -243,6 +239,7 @@ public void onAllServicesInitialized(Offer offer,
BtcWalletService walletService,
BsqWalletService bsqWalletService,
TradeWalletService tradeWalletService,
+ DaoFacade daoFacade,
ReferralIdService referralIdService,
User user,
FilterManager filterManager,
@@ -250,6 +247,7 @@ public void onAllServicesInitialized(Offer offer,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
KeyRing keyRing,
boolean useSavingsWallet,
Coin fundsNeededForTrade) {
@@ -259,6 +257,7 @@ public void onAllServicesInitialized(Offer offer,
this.btcWalletService = walletService;
this.bsqWalletService = bsqWalletService;
this.tradeWalletService = tradeWalletService;
+ this.daoFacade = daoFacade;
this.referralIdService = referralIdService;
this.user = user;
this.filterManager = filterManager;
@@ -266,6 +265,7 @@ public void onAllServicesInitialized(Offer offer,
this.tradeStatisticsManager = tradeStatisticsManager;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
this.keyRing = keyRing;
this.p2PService = p2PService;
this.useSavingsWallet = useSavingsWallet;
@@ -339,4 +339,20 @@ public void setPaymentStartedAckMessage(AckMessage ackMessage) {
public void setPaymentStartedMessageState(MessageState paymentStartedMessageStateProperty) {
this.paymentStartedMessageStateProperty.set(paymentStartedMessageStateProperty);
}
+
+ private void setTradingPeer(TradingPeer tradingPeer) {
+ this.tradingPeer = tradingPeer;
+ }
+
+ private void setOfferId(String offerId) {
+ this.offerId = offerId;
+ }
+
+ private void setAccountId(String accountId) {
+ this.accountId = accountId;
+ }
+
+ private void setPubKeyRing(PubKeyRing pubKeyRing) {
+ this.pubKeyRing = pubKeyRing;
+ }
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
index 60894b26336..f5090965295 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
@@ -21,38 +21,42 @@
import bisq.core.trade.SellerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
+import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
+import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
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.SellerVerifiesPeersAccountAge;
-import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesAndSignsDepositTx;
+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;
+import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.util.Validator;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
-import static com.google.common.base.Preconditions.checkArgument;
-
@Slf4j
public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtocol, MakerProtocol {
private final SellerAsMakerTrade sellerAsMakerTrade;
@@ -73,7 +77,6 @@ public SellerAsMakerProtocol(SellerAsMakerTrade trade) {
() -> handleTaskRunnerSuccess("MakerSetupDepositTxListener"),
this::handleTaskRunnerFault);
- taskRunner.addTasks(MakerSetupDepositTxListener.class);
taskRunner.run();
}
}
@@ -84,23 +87,11 @@ public SellerAsMakerProtocol(SellerAsMakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
-
- if (tradeMessage instanceof DepositTxPublishedMessage)
- handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
- handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
- else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
+ handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -110,11 +101,10 @@ else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void handleTakeOfferRequest(TradeMessage tradeMessage,
+ public void handleTakeOfferRequest(InputsForDepositTxRequest tradeMessage,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), tradeMessage);
- checkArgument(tradeMessage instanceof PayDepositRequest);
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -126,20 +116,17 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
});
taskRunner.addTasks(
- MakerProcessPayDepositRequest.class,
+ MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
MakerVerifyTakerAccount.class,
VerifyPeersAccountAgeWitness.class,
- SellerVerifiesPeersAccountAge.class,
MakerVerifyTakerFeePayment.class,
+ MakerSetsLockTime.class,
MakerCreateAndSignContract.class,
- SellerAsMakerCreatesAndSignsDepositTx.class,
- MakerSetupDepositTxListener.class,
- MakerSendPublishDepositTxRequest.class
+ SellerAsMakerCreatesUnsignedDepositTx.class,
+ SellerAsMakerSendsInputsForDepositTxResponse.class
);
- // We don't start a timeout because if we don't receive the peers DepositTxPublishedMessage we still
- // will get set the deposit tx in MakerSetupDepositTxListener once seen in the network
taskRunner.run();
}
@@ -148,7 +135,7 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- protected void handle(DepositTxPublishedMessage tradeMessage, NodeAddress sender) {
+ protected void handle(DepositTxMessage tradeMessage, NodeAddress sender) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -159,10 +146,32 @@ protected void handle(DepositTxPublishedMessage tradeMessage, NodeAddress sender
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- MakerProcessDepositTxPublishedMessage.class,
- PublishTradeStatistics.class,
- MakerVerifyTakerAccount.class,
- MakerVerifyTakerFeePayment.class
+ SellerAsMakerProcessDepositTxMessage.class,
+ SellerAsMakerFinalizesDepositTx.class,
+ SellerCreatesDelayedPayoutTx.class,
+ SellerSendDelayedPayoutTxSignatureRequest.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DelayedPayoutTxSignatureResponse tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsMakerTrade,
+ () -> {
+ stopTimeout();
+ handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+
+ taskRunner.addTasks(
+ SellerProcessDelayedPayoutTxSignatureResponse.class,
+ SellerSignsDelayedPayoutTx.class,
+ SellerFinalizesDelayedPayoutTx.class,
+ SellerPublishesDepositTx.class,
+ SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
+ PublishTradeStatistics.class
);
taskRunner.run();
}
@@ -255,8 +264,10 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof DepositTxPublishedMessage) {
- handle((DepositTxPublishedMessage) tradeMessage, sender);
+ if (tradeMessage instanceof DepositTxMessage) {
+ handle((DepositTxMessage) tradeMessage, sender);
+ } else if (tradeMessage instanceof DelayedPayoutTxSignatureResponse) {
+ handle((DelayedPayoutTxSignatureResponse) tradeMessage, sender);
} else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
handle((CounterCurrencyTransferStartedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
index d3071b1c8b6..fa4c4d3d0c5 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
@@ -18,39 +18,46 @@
package bisq.core.trade.protocol;
+import bisq.core.offer.Offer;
import bisq.core.trade.SellerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.PublishDepositTxRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
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.SellerVerifiesPeersAccountAge;
+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.SellerAsTakerSignAndPublishDepositTx;
+import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerProcessPublishDepositTxRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerSendDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.taker.TakerSendPayDepositRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerAccount;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtocol, TakerProtocol {
private final SellerAsTakerTrade sellerAsTakerTrade;
@@ -65,7 +72,8 @@ public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
this.sellerAsTakerTrade = trade;
- processModel.getTradingPeer().setPubKeyRing(trade.getOffer().getPubKeyRing());
+ Offer offer = checkNotNull(trade.getOffer());
+ processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
}
@@ -74,22 +82,11 @@ public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest)
- handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
- handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
- else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
+ handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -109,11 +106,9 @@ public void takeAvailableOffer() {
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
SellerAsTakerCreatesDepositTxInputs.class,
- TakerSendPayDepositRequest.class
+ TakerSendInputsForDepositTxRequest.class
);
- //TODO if peer does get an error he does not respond and all we get is the timeout now knowing why it failed.
- // We should add an error message the peer sends us in such cases.
startTimeout();
taskRunner.run();
}
@@ -123,7 +118,7 @@ public void takeAvailableOffer() {
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
+ private void handle(InputsForDepositTxResponse tradeMessage, NodeAddress sender) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -135,16 +130,37 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- TakerProcessPublishDepositTxRequest.class,
+ TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
VerifyPeersAccountAgeWitness.class,
- SellerVerifiesPeersAccountAge.class,
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
- SellerAsTakerSignAndPublishDepositTx.class,
- TakerSendDepositTxPublishedMessage.class,
+ SellerAsTakerSignsDepositTx.class,
+ SellerCreatesDelayedPayoutTx.class,
+ SellerSendDelayedPayoutTxSignatureRequest.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DelayedPayoutTxSignatureResponse tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsTakerTrade,
+ () -> {
+ stopTimeout();
+ handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+
+ taskRunner.addTasks(
+ SellerProcessDelayedPayoutTxSignatureResponse.class,
+ SellerSignsDelayedPayoutTx.class,
+ SellerFinalizesDelayedPayoutTx.class,
+ SellerPublishesDepositTx.class,
+ SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
PublishTradeStatistics.class
);
taskRunner.run();
@@ -238,8 +254,10 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest) {
- handle((PublishDepositTxRequest) tradeMessage, sender);
+ if (tradeMessage instanceof InputsForDepositTxResponse) {
+ handle((InputsForDepositTxResponse) tradeMessage, sender);
+ } else if (tradeMessage instanceof DelayedPayoutTxSignatureResponse) {
+ handle((DelayedPayoutTxSignatureResponse) tradeMessage, sender);
} else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
handle((CounterCurrencyTransferStartedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
index eee4da8371d..cc2a59c3187 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
@@ -21,11 +21,13 @@
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
+import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.FinalizeMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutSignatureMessage;
@@ -59,6 +61,7 @@
import javax.annotation.Nullable;
import static bisq.core.util.Validator.nonEmptyStringOf;
+import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public abstract class TradeProtocol {
@@ -80,13 +83,13 @@ public TradeProtocol(Trade trade) {
PublicKey signaturePubKey = decryptedMessageWithPubKey.getSignaturePubKey();
if (tradingPeerPubKeyRing != null && signaturePubKey.equals(tradingPeerPubKeyRing.getSignaturePubKey())) {
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
- log.trace("handleNewMessage: message = {} from {}", networkEnvelope.getClass().getSimpleName(), peersNodeAddress);
if (networkEnvelope instanceof TradeMessage) {
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
nonEmptyStringOf(tradeMessage.getTradeId());
- if (tradeMessage.getTradeId().equals(processModel.getOfferId()))
+ if (tradeMessage.getTradeId().equals(processModel.getOfferId())) {
doHandleDecryptedMessage(tradeMessage, peersNodeAddress);
+ }
} else if (networkEnvelope instanceof AckMessage) {
AckMessage ackMessage = (AckMessage) networkEnvelope;
if (ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE &&
@@ -110,7 +113,7 @@ public TradeProtocol(Trade trade) {
stateChangeListener = (observable, oldValue, newValue) -> {
if (newValue.getPhase() == Trade.Phase.TAKER_FEE_PUBLISHED && trade instanceof MakerTrade)
- processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());
+ processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
};
trade.stateProperty().addListener(stateChangeListener);
}
@@ -206,6 +209,26 @@ protected void handle(MediatedPayoutTxPublishedMessage tradeMessage, NodeAddress
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Peer has published the delayed payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private void handle(PeerPublishedDelayedPayoutTxMessage tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
+ () -> handleTaskRunnerSuccess(tradeMessage, "PeerPublishedDelayedPayoutTxMessage"),
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+
+ taskRunner.addTasks(
+ //todo
+ ProcessPeerPublishedDelayedPayoutTxMessage.class
+ );
+ taskRunner.run();
+ }
+
+
///////////////////////////////////////////////////////////////////////////////////////////
// Dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@@ -215,6 +238,8 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
handle((MediatedPayoutTxSignatureMessage) tradeMessage, sender);
} else if (tradeMessage instanceof MediatedPayoutTxPublishedMessage) {
handle((MediatedPayoutTxPublishedMessage) tradeMessage, sender);
+ } else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
+ handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, sender);
}
}
@@ -225,10 +250,6 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
public void completed() {
cleanup();
-
- // We only removed earlier the listener here, but then we migth have dangling trades after faults...
- // so lets remove it at cleanup
- //processModel.getP2PService().removeDecryptedDirectMessageListener(decryptedDirectMessageListener);
}
private void cleanup() {
@@ -241,28 +262,33 @@ private void cleanup() {
public void applyMailboxMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) {
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
- log.debug("applyMailboxMessage {}", networkEnvelope);
if (processModel.getTradingPeer().getPubKeyRing() != null &&
decryptedMessageWithPubKey.getSignaturePubKey().equals(processModel.getTradingPeer().getPubKeyRing().getSignaturePubKey())) {
processModel.setDecryptedMessageWithPubKey(decryptedMessageWithPubKey);
- doApplyMailboxMessage(networkEnvelope, trade);
- // This is just a quick fix for the missing handling of the mediation MailboxMessages.
- // With the new trade protocol that will be refactored further with using doApplyMailboxMessage...
if (networkEnvelope instanceof MailboxMessage && networkEnvelope instanceof TradeMessage) {
- NodeAddress sender = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof MediatedPayoutTxSignatureMessage) {
- handle((MediatedPayoutTxSignatureMessage) networkEnvelope, sender);
- } else if (networkEnvelope instanceof MediatedPayoutTxPublishedMessage) {
- handle((MediatedPayoutTxPublishedMessage) networkEnvelope, sender);
- }
+ this.trade = trade;
+ TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
+ NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
+ doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
}
} else {
log.error("SignaturePubKey in message does not match the SignaturePubKey we have stored to that trading peer.");
}
}
- protected abstract void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade);
+ protected void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
+ tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
+
+ if (tradeMessage instanceof MediatedPayoutTxSignatureMessage) {
+ handle((MediatedPayoutTxSignatureMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof MediatedPayoutTxPublishedMessage) {
+ handle((MediatedPayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
+ handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
+ }
+ }
protected void startTimeout() {
stopTimeout();
@@ -318,7 +344,7 @@ private void sendAckMessage(@Nullable TradeMessage tradeMessage, boolean result,
sourceUid = ((MailboxMessage) tradeMessage).getUid();
} else {
// For direct msg we don't have a mandatory uid so we need to cast to get it
- if (tradeMessage instanceof PayDepositRequest) {
+ if (tradeMessage instanceof InputsForDepositTxRequest) {
sourceUid = tradeMessage.getUid();
}
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
index 94398caf791..3a2af777055 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
@@ -38,10 +38,23 @@
import javax.annotation.Nullable;
+// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
+// persist them.
+//todo clean up older fields as well to make most transient
@Slf4j
@Getter
@Setter
public final class TradingPeer implements PersistablePayload {
+ // Transient/Mutable
+ // Added in v1.2.0
+ @Setter
+ @Nullable
+ transient private byte[] delayedPayoutTxSignature;
+ @Setter
+ @Nullable
+ transient private byte[] preparedDepositTx;
+
+ // Persistable mutable
@Nullable
private String accountId;
@Nullable
@@ -75,6 +88,7 @@ public final class TradingPeer implements PersistablePayload {
@Nullable
private byte[] mediatedPayoutTxSignature;
+
public TradingPeer() {
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java
new file mode 100644
index 00000000000..d0dc9300530
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java
@@ -0,0 +1,64 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks;
+
+import bisq.core.btc.wallet.WalletService;
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
+import bisq.core.util.Validator;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public ProcessPeerPublishedDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ PeerPublishedDelayedPayoutTxMessage message = (PeerPublishedDelayedPayoutTxMessage) processModel.getTradeMessage();
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ checkNotNull(message);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+ processModel.removeMailboxMessageAfterProcessing(trade);
+
+ // We add the tx to our wallet.
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet());
+
+ // todo trade.setState
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java b/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java
index 9a51cbb8bf4..ab083218d27 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java
@@ -51,17 +51,16 @@ protected void run() {
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
}
- NodeAddress arbitratorNodeAddress = trade.getArbitratorNodeAddress();
- if (arbitratorNodeAddress != null) {
-
+ NodeAddress mediatorNodeAddress = trade.getMediatorNodeAddress();
+ if (mediatorNodeAddress != null) {
// The first 4 chars are sufficient to identify an arbitrator.
// For testing with regtest/localhost we use the full address as its localhost and would result in
// same values for multiple arbitrators.
NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode();
String address = networkNode instanceof TorNetworkNode ?
- arbitratorNodeAddress.getFullAddress().substring(0, 4) :
- arbitratorNodeAddress.getFullAddress();
- extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address);
+ mediatorNodeAddress.getFullAddress().substring(0, 4) :
+ mediatorNodeAddress.getFullAddress();
+ extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address);
}
Offer offer = trade.getOffer();
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
index 61dbe0ff531..20b85539398 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
@@ -54,10 +54,10 @@ protected void run() {
runInterceptHook();
if (!trade.isPayoutPublished()) {
BtcWalletService walletService = processModel.getBtcWalletService();
- final String id = processModel.getOffer().getId();
+ String id = processModel.getOffer().getId();
Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress();
- final TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
+ TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
if (isInNetwork(confidence)) {
applyConfidence(confidence);
} else {
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java
new file mode 100644
index 00000000000..c9fe2e8b7ad
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.TradeTask;
+import bisq.core.util.Validator;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerProcessDelayedPayoutTxSignatureRequest(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ DelayedPayoutTxSignatureRequest message = (DelayedPayoutTxSignatureRequest) processModel.getTradeMessage();
+ checkNotNull(message);
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ byte[] delayedPayoutTxAsBytes = checkNotNull(message.getDelayedPayoutTx());
+ Transaction preparedDelayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes);
+ processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessDepositTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java
similarity index 64%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessDepositTxPublishedMessage.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java
index c000c49e3f5..509bfc7f163 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessDepositTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java
@@ -15,12 +15,13 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.maker;
+package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.WalletService;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.util.Validator;
@@ -34,9 +35,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class MakerProcessDepositTxPublishedMessage extends TradeTask {
+public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
@SuppressWarnings({"unused"})
- public MakerProcessDepositTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
+ public BuyerProcessDepositTxAndDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -44,18 +45,22 @@ public MakerProcessDepositTxPublishedMessage(TaskRunner taskHandler, Trade trade
protected void run() {
try {
runInterceptHook();
- log.debug("current trade state " + trade.getState());
- DepositTxPublishedMessage message = (DepositTxPublishedMessage) processModel.getTradeMessage();
- Validator.checkTradeId(processModel.getOfferId(), message);
+ DepositTxAndDelayedPayoutTxMessage message = (DepositTxAndDelayedPayoutTxMessage) processModel.getTradeMessage();
checkNotNull(message);
+ Validator.checkTradeId(processModel.getOfferId(), message);
checkArgument(message.getDepositTx() != null);
// To access tx confidence we need to add that tx into our wallet.
- Transaction txFromSerializedTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDepositTx());
+ Transaction depositTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDepositTx());
// update with full tx
- Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(txFromSerializedTx);
- trade.setDepositTx(walletTx);
- BtcWalletService.printTx("depositTx received from peer", walletTx);
+ Transaction committedDepositTx = WalletService.maybeAddSelfTxToWallet(depositTx, processModel.getBtcWalletService().getWallet());
+ trade.applyDepositTx(committedDepositTx);
+ BtcWalletService.printTx("depositTx received from peer", committedDepositTx);
+
+ // To access tx confidence we need to add that tx into our wallet.
+ byte[] delayedPayoutTxBytes = message.getDelayedPayoutTx();
+ trade.applyDelayedPayoutTxBytes(delayedPayoutTxBytes);
+ BtcWalletService.printTx("delayedPayoutTx received from peer", trade.getDelayedPayoutTx());
// update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
@@ -63,8 +68,8 @@ protected void run() {
processModel.removeMailboxMessageAfterProcessing(trade);
// If we got already the confirmation we don't want to apply an earlier state
- if (trade.getState() != Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK)
- trade.setState(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
+ if (trade.getState() != Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK)
+ trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.RESERVED_FOR_TRADE);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java
index 6fc5f2d18c7..245dd40aacb 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java
@@ -19,6 +19,7 @@
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.WalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
@@ -54,9 +55,9 @@ protected void run() {
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
if (trade.getPayoutTx() == null) {
- Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(message.getPayoutTx());
- trade.setPayoutTx(walletTx);
- BtcWalletService.printTx("payoutTx received from peer", walletTx);
+ Transaction committedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet());
+ trade.setPayoutTx(committedPayoutTx);
+ BtcWalletService.printTx("payoutTx received from peer", committedPayoutTx);
trade.setState(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java
new file mode 100644
index 00000000000..887c416a546
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java
@@ -0,0 +1,85 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.SendDirectMessageListener;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class BuyerSendsDelayedPayoutTxSignatureResponse extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerSendsDelayedPayoutTxSignatureResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ byte[] delayedPayoutTxSignature = checkNotNull(processModel.getDelayedPayoutTxSignature());
+ DelayedPayoutTxSignatureResponse message = new DelayedPayoutTxSignatureResponse(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
+ processModel.getMyNodeAddress(),
+ delayedPayoutTxSignature);
+
+ // todo trade.setState
+
+ NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
+ log.info("Send {} to peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ processModel.getP2PService().sendEncryptedDirectMessage(
+ peersNodeAddress,
+ processModel.getTradingPeer().getPubKeyRing(),
+ message,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ // todo trade.setState
+ complete();
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
+ // todo trade.setState
+ appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
+ failed(errorMessage);
+ }
+ }
+ );
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java
similarity index 94%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxListener.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java
index a858124c5a1..454761b4aec 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxListener.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java
@@ -15,7 +15,7 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.maker;
+package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.btc.listeners.AddressConfidenceListener;
import bisq.core.btc.model.AddressEntry;
@@ -39,13 +39,13 @@
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
-public class MakerSetupDepositTxListener extends TradeTask {
+public class BuyerSetupDepositTxListener extends TradeTask {
// Use instance fields to not get eaten up by the GC
private Subscription tradeStateSubscription;
private AddressConfidenceListener confidenceListener;
@SuppressWarnings({"unused"})
- public MakerSetupDepositTxListener(TaskRunner taskHandler, Trade trade) {
+ public BuyerSetupDepositTxListener(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -94,9 +94,9 @@ public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
private void applyConfidence(TransactionConfidence confidence) {
if (trade.getDepositTx() == null) {
Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash());
- trade.setDepositTx(walletTx);
+ trade.applyDepositTx(walletTx);
BtcWalletService.printTx("depositTx received from network", walletTx);
- trade.setState(Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK);
+ trade.setState(Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK);
} else {
log.info("We got the deposit tx already set from MakerProcessDepositTxPublishedMessage. tradeId={}, state={}", trade.getId(), trade.getState());
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSignPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java
similarity index 89%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSignPayoutTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java
index 4140ce364dd..b90a91ec22d 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSignPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java
@@ -15,7 +15,7 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.buyer_as_maker;
+package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
@@ -38,10 +38,10 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class BuyerAsMakerSignPayoutTx extends TradeTask {
+public class BuyerSignPayoutTx extends TradeTask {
@SuppressWarnings({"unused"})
- public BuyerAsMakerSignPayoutTx(TaskRunner taskHandler, Trade trade) {
+ public BuyerSignPayoutTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -69,7 +69,7 @@ protected void run() {
checkArgument(Arrays.equals(buyerMultiSigPubKey,
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
+ byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
byte[] payoutTxSignature = processModel.getTradeWalletService().buyerSignsPayoutTx(
trade.getDepositTx(),
@@ -79,8 +79,7 @@ protected void run() {
sellerPayoutAddressString,
buyerMultiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey());
+ sellerMultiSigPubKey);
processModel.setPayoutTxSignature(payoutTxSignature);
complete();
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java
new file mode 100644
index 00000000000..0d0bb585ae4
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java
@@ -0,0 +1,68 @@
+/*
+ * 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 .
+ */
+
+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 org.bitcoinj.core.Transaction;
+import org.bitcoinj.crypto.DeterministicKey;
+
+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 BuyerSignsDelayedPayoutTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerSignsDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
+ BtcWalletService btcWalletService = processModel.getBtcWalletService();
+ String id = processModel.getOffer().getId();
+
+ byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
+ DeterministicKey myMultiSigKeyPair = btcWalletService.getMultiSigKeyPair(id, buyerMultiSigPubKey);
+
+ 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[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(preparedDelayedPayoutTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey);
+ processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerVerifiesPeersAccountAge.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
similarity index 50%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerVerifiesPeersAccountAge.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
index 3f2637328a4..3ec30a67786 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerVerifiesPeersAccountAge.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
@@ -15,22 +15,23 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.seller;
+package bisq.core.trade.protocol.tasks.buyer;
-import bisq.core.account.witness.AccountAgeRestrictions;
-import bisq.core.offer.OfferRestrictions;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.common.taskrunner.TaskRunner;
+import org.bitcoinj.core.Transaction;
+
import lombok.extern.slf4j.Slf4j;
-@Slf4j
-public class SellerVerifiesPeersAccountAge extends TradeTask {
+import static com.google.common.base.Preconditions.checkNotNull;
+@Slf4j
+public class BuyerVerifiesDelayedPayoutTx extends TradeTask {
@SuppressWarnings({"unused"})
- public SellerVerifiesPeersAccountAge(TaskRunner taskHandler, Trade trade) {
+ public BuyerVerifiesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -39,19 +40,12 @@ protected void run() {
try {
runInterceptHook();
- boolean isTradeRisky = OfferRestrictions.isTradeRisky(trade);
- boolean isTradePeersAccountAgeImmature = AccountAgeRestrictions.isTradePeersAccountAgeImmature(
- processModel.getAccountAgeWitnessService(), trade);
- log.debug("SellerVerifiesPeersAccountAge isOfferRisky={} isTradePeersAccountAgeImmature={}",
- isTradeRisky, isTradePeersAccountAgeImmature);
- if (isTradeRisky &&
- isTradePeersAccountAgeImmature) {
- failed("Violation of security restrictions:\n" +
- " - The peer's account was created after March 1st 2019\n" +
- " - The trade amount is above 0.01 BTC\n" +
- " - The payment method for that offer is considered risky for bank chargebacks\n");
- } else {
+ Transaction depositTx = checkNotNull(trade.getDepositTx());
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ if (processModel.getTradeWalletService().verifiesDepositTxAndDelayedPayoutTx(depositTx, delayedPayoutTx)) {
complete();
+ } else {
+ failed("DelayedPayoutTx is not spending correctly depositTx");
}
} catch (Throwable t) {
failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java
index d49995560b2..f7d2e8c8176 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java
@@ -57,52 +57,40 @@ protected void run() {
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
TradingPeer tradingPeer = processModel.getTradingPeer();
- final Offer offer = trade.getOffer();
+ Offer offer = checkNotNull(trade.getOffer());
// params
- final boolean makerIsBuyer = true;
-
- final byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
log.debug("\n\n------------------------------------------------------------\n"
+ "Contract as json\n"
+ trade.getContractAsJson()
+ "\n------------------------------------------------------------\n");
- final Coin makerInputAmount = offer.getBuyerSecurityDeposit();
+ Coin makerInputAmount = offer.getBuyerSecurityDeposit();
Optional addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
AddressEntry makerMultiSigAddressEntry = addressEntryOptional.get();
makerMultiSigAddressEntry.setCoinLockedInMultiSig(makerInputAmount);
walletService.saveAddressEntryList();
- final Coin msOutputAmount = makerInputAmount
+ Coin msOutputAmount = makerInputAmount
.add(trade.getTxFee())
.add(offer.getSellerSecurityDeposit())
.add(trade.getTradeAmount());
- final List takerRawTransactionInputs = tradingPeer.getRawTransactionInputs();
-
- final long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
-
- final String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
-
- final Address makerAddress = walletService.getOrCreateAddressEntry(id,
- AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
-
- final Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
-
- final byte[] buyerPubKey = processModel.getMyMultiSigPubKey();
+ List takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
+ long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
+ String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
+ Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
+ Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
+ byte[] buyerPubKey = processModel.getMyMultiSigPubKey();
+ byte[] sellerPubKey = tradingPeer.getMultiSigPubKey();
checkArgument(Arrays.equals(buyerPubKey,
makerMultiSigAddressEntry.getPubKey()),
"buyerPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] sellerPubKey = tradingPeer.getMultiSigPubKey();
-
- final byte[] arbitratorBtcPubKey = trade.getArbitratorBtcPubKey();
-
- PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().makerCreatesAndSignsDepositTx(
- makerIsBuyer,
+ PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().buyerAsMakerCreatesAndSignsDepositTx(
contractHash,
makerInputAmount,
msOutputAmount,
@@ -112,8 +100,7 @@ protected void run() {
makerAddress,
makerChangeAddress,
buyerPubKey,
- sellerPubKey,
- arbitratorBtcPubKey);
+ sellerPubKey);
processModel.setPreparedDepositTx(result.depositTransaction);
processModel.setRawTransactionInputs(result.rawMakerInputs);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java
new file mode 100644
index 00000000000..029a8213dcd
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java
@@ -0,0 +1,38 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class BuyerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInputsForDepositTxResponse {
+ @SuppressWarnings({"unused"})
+ public BuyerAsMakerSendsInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected byte[] getPreparedDepositTx() {
+ return processModel.getPreparedDepositTx();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java
index 79b18574dd9..ad0efc70bf6 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java
@@ -17,19 +17,18 @@
package bisq.core.trade.protocol.tasks.buyer_as_taker;
-import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.InputsAndChangeOutput;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class BuyerAsTakerCreatesDepositTxInputs extends TradeTask {
@@ -47,15 +46,15 @@ protected void run() {
Coin bsqTakerFee = trade.isCurrencyForTakerFeeBtc() ? Coin.ZERO : trade.getTakerFee();
Coin txFee = trade.getTxFee();
- Coin takerInputAmount = trade.getOffer().getBuyerSecurityDeposit().add(txFee).add(txFee).subtract(bsqTakerFee);
- BtcWalletService walletService = processModel.getBtcWalletService();
- Address takersAddress = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(),
- AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
- InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(
+ Coin takerInputAmount = checkNotNull(trade.getOffer()).getBuyerSecurityDeposit()
+ .add(txFee)
+ .add(txFee)
+ .subtract(bsqTakerFee);
+ Coin fee = txFee.subtract(bsqTakerFee);
+ InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs(
processModel.getTakeOfferFeeTx(),
takerInputAmount,
- txFee.subtract(bsqTakerFee),
- takersAddress);
+ fee);
processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue);
processModel.setChangeOutputAddress(result.changeOutputAddress);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java
new file mode 100644
index 00000000000..f0391f91ebf
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java
@@ -0,0 +1,87 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer_as_taker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.SendDirectMessageListener;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class BuyerAsTakerSendsDepositTxMessage extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerAsTakerSendsDepositTxMessage(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ if (trade.getDepositTx() != null) {
+ DepositTxMessage message = new DepositTxMessage(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
+ processModel.getMyNodeAddress(),
+ trade.getDepositTx().bitcoinSerialize());
+
+ // todo trade.setState
+
+ NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
+ log.info("Send {} to peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ processModel.getP2PService().sendEncryptedDirectMessage(
+ peersNodeAddress,
+ processModel.getTradingPeer().getPubKeyRing(),
+ message,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ // todo trade.setState
+ complete();
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
+
+ // todo trade.setState
+ appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
+ failed();
+ }
+ }
+ );
+ } else {
+ log.error("trade.getDepositTx() = " + trade.getDepositTx());
+ failed("DepositTx is null");
+ }
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignAndPublishDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java
similarity index 55%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignAndPublishDepositTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java
index 125e347578c..f5cdcb37606 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignAndPublishDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java
@@ -17,11 +17,9 @@
package bisq.core.trade.protocol.tasks.buyer_as_taker;
-import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.wallet.BtcWalletService;
-import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.trade.protocol.tasks.TradeTask;
@@ -42,10 +40,10 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class BuyerAsTakerSignAndPublishDepositTx extends TradeTask {
+public class BuyerAsTakerSignsDepositTx extends TradeTask {
@SuppressWarnings({"unused"})
- public BuyerAsTakerSignAndPublishDepositTx(TaskRunner taskHandler, Trade trade) {
+ public BuyerAsTakerSignsDepositTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -60,7 +58,7 @@ protected void run() {
+ "\n------------------------------------------------------------\n");
- byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
List buyerInputs = checkNotNull(processModel.getRawTransactionInputs(), "buyerInputs must not be null");
BtcWalletService walletService = processModel.getBtcWalletService();
@@ -79,49 +77,19 @@ protected void run() {
checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()),
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
+ List sellerInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
+ byte[] sellerMultiSigPubKey = tradingPeer.getMultiSigPubKey();
+ Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx(
false,
contractHash,
processModel.getPreparedDepositTx(),
buyerInputs,
- tradingPeer.getRawTransactionInputs(),
+ sellerInputs,
buyerMultiSigPubKey,
- tradingPeer.getMultiSigPubKey(),
- trade.getArbitratorBtcPubKey(),
- new TxBroadcaster.Callback() {
- @Override
- public void onSuccess(Transaction transaction) {
- if (!completed) {
- // We set the depositTx before we change the state as the state change triggers code
- // which expected the tx to be available. That case will usually never happen as the
- // callback is called after the method call has returned but in some test scenarios
- // with regtest we run into such issues, thus fixing it to make it more stict seems
- // reasonable.
- trade.setDepositTx(transaction);
- log.trace("takerSignsAndPublishesDepositTx succeeded " + transaction);
- trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
- walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
-
- complete();
- } else {
- log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
- }
- }
-
- @Override
- public void onFailure(TxBroadcastException exception) {
- if (!completed) {
- failed(exception);
- } else {
- log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
- }
- }
- });
- if (trade.getDepositTx() == null) {
- // We set the deposit tx in case we get the onFailure called. We cannot set it in the onFailure
- // callback as the tx is returned by the method call where the callback is used as an argument.
- trade.setDepositTx(depositTx);
- }
+ sellerMultiSigPubKey);
+ trade.applyDepositTx(depositTx);
+
+ complete();
} catch (Throwable t) {
failed(t);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java
index 417a4be432c..fcd6ec6a797 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java
@@ -55,7 +55,7 @@ protected void run() {
TradingPeer taker = processModel.getTradingPeer();
PaymentAccountPayload makerPaymentAccountPayload = processModel.getPaymentAccountPayload(trade);
checkNotNull(makerPaymentAccountPayload, "makerPaymentAccountPayload must not be null");
- PaymentAccountPayload takerPaymentAccountPayload = taker.getPaymentAccountPayload();
+ PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(taker.getPaymentAccountPayload());
boolean isBuyerMakerAndSellerTaker = trade instanceof BuyerAsMakerTrade;
NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ?
@@ -79,7 +79,6 @@ protected void run() {
trade.getTakerFeeTxId(),
buyerNodeAddress,
sellerNodeAddress,
- trade.getArbitratorNodeAddress(),
trade.getMediatorNodeAddress(),
isBuyerMakerAndSellerTaker,
processModel.getAccountId(),
@@ -91,7 +90,9 @@ protected void run() {
takerAddressEntry.getAddressString(),
taker.getPayoutAddressString(),
makerMultiSigPubKey,
- taker.getMultiSigPubKey()
+ taker.getMultiSigPubKey(),
+ trade.getLockTime(),
+ trade.getRefundAgentNodeAddress()
);
String contractAsJson = Utilities.objectToJson(contract);
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessPayDepositRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java
similarity index 50%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessPayDepositRequest.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java
index 3a8088905ce..68fbbc51780 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessPayDepositRequest.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java
@@ -19,10 +19,9 @@
import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.offer.Offer;
-import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.user.User;
@@ -43,9 +42,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class MakerProcessPayDepositRequest extends TradeTask {
+public class MakerProcessesInputsForDepositTxRequest extends TradeTask {
@SuppressWarnings({"unused"})
- public MakerProcessPayDepositRequest(TaskRunner taskHandler, Trade trade) {
+ public MakerProcessesInputsForDepositTxRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -54,47 +53,33 @@ protected void run() {
try {
runInterceptHook();
log.debug("current trade state " + trade.getState());
- PayDepositRequest payDepositRequest = (PayDepositRequest) processModel.getTradeMessage();
- checkNotNull(payDepositRequest);
- checkTradeId(processModel.getOfferId(), payDepositRequest);
+ InputsForDepositTxRequest inputsForDepositTxRequest = (InputsForDepositTxRequest) processModel.getTradeMessage();
+ checkNotNull(inputsForDepositTxRequest);
+ checkTradeId(processModel.getOfferId(), inputsForDepositTxRequest);
final TradingPeer tradingPeer = processModel.getTradingPeer();
- tradingPeer.setPaymentAccountPayload(checkNotNull(payDepositRequest.getTakerPaymentAccountPayload()));
- tradingPeer.setRawTransactionInputs(checkNotNull(payDepositRequest.getRawTransactionInputs()));
- checkArgument(payDepositRequest.getRawTransactionInputs().size() > 0);
+ tradingPeer.setPaymentAccountPayload(checkNotNull(inputsForDepositTxRequest.getTakerPaymentAccountPayload()));
+ tradingPeer.setRawTransactionInputs(checkNotNull(inputsForDepositTxRequest.getRawTransactionInputs()));
+ checkArgument(inputsForDepositTxRequest.getRawTransactionInputs().size() > 0);
- tradingPeer.setChangeOutputValue(payDepositRequest.getChangeOutputValue());
- tradingPeer.setChangeOutputAddress(payDepositRequest.getChangeOutputAddress());
+ tradingPeer.setChangeOutputValue(inputsForDepositTxRequest.getChangeOutputValue());
+ tradingPeer.setChangeOutputAddress(inputsForDepositTxRequest.getChangeOutputAddress());
- tradingPeer.setMultiSigPubKey(checkNotNull(payDepositRequest.getTakerMultiSigPubKey()));
- tradingPeer.setPayoutAddressString(nonEmptyStringOf(payDepositRequest.getTakerPayoutAddressString()));
- tradingPeer.setPubKeyRing(checkNotNull(payDepositRequest.getTakerPubKeyRing()));
+ tradingPeer.setMultiSigPubKey(checkNotNull(inputsForDepositTxRequest.getTakerMultiSigPubKey()));
+ tradingPeer.setPayoutAddressString(nonEmptyStringOf(inputsForDepositTxRequest.getTakerPayoutAddressString()));
+ tradingPeer.setPubKeyRing(checkNotNull(inputsForDepositTxRequest.getTakerPubKeyRing()));
- tradingPeer.setAccountId(nonEmptyStringOf(payDepositRequest.getTakerAccountId()));
- trade.setTakerFeeTxId(nonEmptyStringOf(payDepositRequest.getTakerFeeTxId()));
- processModel.setTakerAcceptedArbitratorNodeAddresses(checkNotNull(payDepositRequest.getAcceptedArbitratorNodeAddresses()));
- processModel.setTakerAcceptedMediatorNodeAddresses(checkNotNull(payDepositRequest.getAcceptedMediatorNodeAddresses()));
- if (payDepositRequest.getAcceptedArbitratorNodeAddresses().isEmpty())
- failed("acceptedArbitratorNodeAddresses must not be empty");
+ tradingPeer.setAccountId(nonEmptyStringOf(inputsForDepositTxRequest.getTakerAccountId()));
+ trade.setTakerFeeTxId(nonEmptyStringOf(inputsForDepositTxRequest.getTakerFeeTxId()));
// Taker has to sign offerId (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed)
tradingPeer.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8));
- tradingPeer.setAccountAgeWitnessSignature(payDepositRequest.getAccountAgeWitnessSignatureOfOfferId());
- tradingPeer.setCurrentDate(payDepositRequest.getCurrentDate());
+ tradingPeer.setAccountAgeWitnessSignature(inputsForDepositTxRequest.getAccountAgeWitnessSignatureOfOfferId());
+ tradingPeer.setCurrentDate(inputsForDepositTxRequest.getCurrentDate());
User user = checkNotNull(processModel.getUser(), "User must not be null");
- NodeAddress arbitratorNodeAddress = checkNotNull(payDepositRequest.getArbitratorNodeAddress(),
- "payDepositRequest.getArbitratorNodeAddress() must not be null");
- trade.setArbitratorNodeAddress(arbitratorNodeAddress);
- Arbitrator arbitrator = checkNotNull(user.getAcceptedArbitratorByAddress(arbitratorNodeAddress),
- "user.getAcceptedArbitratorByAddress(arbitratorNodeAddress) must not be null");
- trade.setArbitratorBtcPubKey(checkNotNull(arbitrator.getBtcPubKey(),
- "arbitrator.getBtcPubKey() must not be null"));
- trade.setArbitratorPubKeyRing(checkNotNull(arbitrator.getPubKeyRing(),
- "arbitrator.getPubKeyRing() must not be null"));
-
- NodeAddress mediatorNodeAddress = checkNotNull(payDepositRequest.getMediatorNodeAddress(),
+ NodeAddress mediatorNodeAddress = checkNotNull(inputsForDepositTxRequest.getMediatorNodeAddress(),
"payDepositRequest.getMediatorNodeAddress() must not be null");
trade.setMediatorNodeAddress(mediatorNodeAddress);
Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(mediatorNodeAddress),
@@ -104,7 +89,7 @@ protected void run() {
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
try {
- long takersTradePrice = payDepositRequest.getTradePrice();
+ long takersTradePrice = inputsForDepositTxRequest.getTradePrice();
offer.checkTradePriceTolerance(takersTradePrice);
trade.setTradePrice(takersTradePrice);
} catch (TradePriceOutOfToleranceException e) {
@@ -113,15 +98,13 @@ protected void run() {
failed(e2);
}
- checkArgument(payDepositRequest.getTradeAmount() > 0);
- trade.setTradeAmount(Coin.valueOf(payDepositRequest.getTradeAmount()));
+ checkArgument(inputsForDepositTxRequest.getTradeAmount() > 0);
+ trade.setTradeAmount(Coin.valueOf(inputsForDepositTxRequest.getTradeAmount()));
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
trade.persist();
- processModel.removeMailboxMessageAfterProcessing(trade);
-
complete();
} catch (Throwable t) {
failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java
similarity index 79%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java
index 8bb029f140f..2cd81721ee7 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java
@@ -21,11 +21,11 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PublishDepositTxRequest;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.network.p2p.NodeAddress;
-import bisq.network.p2p.SendMailboxMessageListener;
+import bisq.network.p2p.SendDirectMessageListener;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
@@ -41,12 +41,14 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class MakerSendPublishDepositTxRequest extends TradeTask {
+public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask {
@SuppressWarnings({"unused"})
- public MakerSendPublishDepositTxRequest(TaskRunner taskHandler, Trade trade) {
+ public MakerSendsInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
+ protected abstract byte[] getPreparedDepositTx();
+
@Override
protected void run() {
try {
@@ -62,15 +64,16 @@ protected void run() {
addressEntryOptional.get().getPubKey()),
"makerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] preparedDepositTx = processModel.getPreparedDepositTx();
+ byte[] preparedDepositTx = getPreparedDepositTx();
// Maker has to use preparedDepositTx as nonce.
// He cannot manipulate the preparedDepositTx - so we avoid to have a challenge protocol for passing the nonce we want to get signed.
- final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null");
+ PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade),
+ "processModel.getPaymentAccountPayload(trade) must not be null");
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), preparedDepositTx);
- PublishDepositTxRequest message = new PublishDepositTxRequest(
+ InputsForDepositTxResponse message = new InputsForDepositTxResponse(
processModel.getOfferId(),
paymentAccountPayload,
processModel.getAccountId(),
@@ -83,18 +86,19 @@ protected void run() {
processModel.getMyNodeAddress(),
UUID.randomUUID().toString(),
sig,
- new Date().getTime());
+ new Date().getTime(),
+ trade.getLockTime());
trade.setState(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST);
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- processModel.getP2PService().sendEncryptedMailboxMessage(
+ processModel.getP2PService().sendEncryptedDirectMessage(
peersNodeAddress,
processModel.getTradingPeer().getPubKeyRing(),
message,
- new SendMailboxMessageListener() {
+ new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
@@ -103,14 +107,6 @@ public void onArrived() {
complete();
}
- @Override
- public void onStoredInMailbox() {
- log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
- message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- trade.setState(Trade.State.MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST);
- complete();
- }
-
@Override
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java
new file mode 100644
index 00000000000..18cb46a96c8
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java
@@ -0,0 +1,56 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.maker;
+
+import bisq.core.app.BisqEnvironment;
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.app.DevEnv;
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MakerSetsLockTime extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public MakerSetsLockTime(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ // 10 days for altcoins, 20 days for other payment methods
+ int delay = processModel.getOffer().getPaymentMethod().isAsset() ? 144 * 10 : 144 * 20;
+ if (BisqEnvironment.getBaseCurrencyNetwork().isRegtest()) {
+ delay = 5;
+ }
+
+ long lockTime = processModel.getBtcWalletService().getBestChainHeight() + delay;
+ log.info("lockTime={}, delay={}", lockTime, delay);
+ trade.setLockTime(lockTime);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java
index b88a39ff05e..96d8db5b4ec 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java
@@ -104,8 +104,7 @@ protected void run() {
sellerPayoutAddressString,
multiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey()
+ sellerMultiSigPubKey
);
trade.setPayoutTx(transaction);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java
index cb94c85d5a2..e6c60eb4a10 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java
@@ -19,6 +19,7 @@
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.WalletService;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
@@ -55,9 +56,9 @@ protected void run() {
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
if (trade.getPayoutTx() == null) {
- Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(message.getPayoutTx());
- trade.setPayoutTx(walletTx);
- BtcWalletService.printTx("payoutTx received from peer", walletTx);
+ Transaction committedMediatedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet());
+ trade.setPayoutTx(committedMediatedPayoutTx);
+ BtcWalletService.printTx("MediatedPayoutTx received from peer", committedMediatedPayoutTx);
trade.setMediationResultState(MediationResultState.RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java
index 59c27e83177..5a48240f7d4 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java
@@ -97,8 +97,7 @@ protected void run() {
sellerPayoutAddressString,
myMultiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey());
+ sellerMultiSigPubKey);
processModel.setMediatedPayoutTxSignature(mediatedPayoutTxSignature);
complete();
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java
new file mode 100644
index 00000000000..6bddc5d9d64
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java
@@ -0,0 +1,65 @@
+/*
+ * 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 .
+ */
+
+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.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerCreatesDelayedPayoutTx extends TradeTask {
+
+ @SuppressWarnings({"unused"})
+ public SellerCreatesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ String donationAddressString = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
+ Coin minerFee = trade.getTxFee();
+ TradeWalletService tradeWalletService = processModel.getTradeWalletService();
+ Transaction depositTx = checkNotNull(trade.getDepositTx());
+
+ long lockTime = trade.getLockTime();
+ Transaction preparedDelayedPayoutTx = tradeWalletService.createDelayedUnsignedPayoutTx(depositTx,
+ donationAddressString,
+ minerFee,
+ lockTime);
+
+ processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java
new file mode 100644
index 00000000000..c43dc9da4d5
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java
@@ -0,0 +1,74 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+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 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 SellerFinalizesDelayedPayoutTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerFinalizesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
+ BtcWalletService btcWalletService = processModel.getBtcWalletService();
+ String id = processModel.getOffer().getId();
+
+ byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
+ byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
+ checkArgument(Arrays.equals(sellerMultiSigPubKey,
+ btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
+ "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
+
+ byte[] buyerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature();
+ byte[] sellerSignature = processModel.getDelayedPayoutTxSignature();
+
+ Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeDelayedPayoutTx(preparedDelayedPayoutTx,
+ buyerMultiSigPubKey,
+ sellerMultiSigPubKey,
+ buyerSignature,
+ sellerSignature);
+
+ trade.applyDelayedPayoutTx(signedDelayedPayoutTx);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java
new file mode 100644
index 00000000000..e2e3d7cd37f
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java
@@ -0,0 +1,59 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.core.util.Validator.checkTradeId;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerProcessDelayedPayoutTxSignatureResponse extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerProcessDelayedPayoutTxSignatureResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ DelayedPayoutTxSignatureResponse delayedPayoutTxSignatureResponse = (DelayedPayoutTxSignatureResponse) processModel.getTradeMessage();
+ checkNotNull(delayedPayoutTxSignatureResponse);
+ checkTradeId(processModel.getOfferId(), delayedPayoutTxSignatureResponse);
+
+ byte[] delayedPayoutTxSignature = checkNotNull(delayedPayoutTxSignatureResponse.getDelayedPayoutTxSignature());
+ processModel.getTradingPeer().setDelayedPayoutTxSignature(delayedPayoutTxSignature);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+
+ // todo trade.setState
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java
new file mode 100644
index 00000000000..be9b3fc1bd3
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java
@@ -0,0 +1,76 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.btc.exceptions.TxBroadcastException;
+import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.wallet.TxBroadcaster;
+import bisq.core.trade.Contract;
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SellerPublishesDepositTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerPublishesDepositTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ processModel.getTradeWalletService().broadcastTx(trade.getDepositTx(),
+ new TxBroadcaster.Callback() {
+ @Override
+ public void onSuccess(Transaction transaction) {
+ if (!completed) {
+ trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX);
+
+ processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE);
+
+ complete();
+ } else {
+ log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
+ }
+ }
+
+ @Override
+ public void onFailure(TxBroadcastException exception) {
+ if (!completed) {
+ failed(exception);
+ } else {
+ log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
+ }
+ }
+ });
+ } catch (Throwable t) {
+ Contract contract = trade.getContract();
+ if (contract != null)
+ contract.printDiff(processModel.getTradingPeer().getContractAsJson());
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java
new file mode 100644
index 00000000000..187c274304b
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.SendDirectMessageListener;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerSendDelayedPayoutTxSignatureRequest extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerSendDelayedPayoutTxSignatureRequest(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx(),
+ "processModel.getPreparedDelayedPayoutTx() must not be null");
+ DelayedPayoutTxSignatureRequest message = new DelayedPayoutTxSignatureRequest(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
+ processModel.getMyNodeAddress(),
+ preparedDelayedPayoutTx.bitcoinSerialize());
+
+ // todo trade.setState
+
+ NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
+ log.info("Send {} to peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ processModel.getP2PService().sendEncryptedDirectMessage(
+ peersNodeAddress,
+ processModel.getTradingPeer().getPubKeyRing(),
+ message,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ // todo trade.setState
+ complete();
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
+ // todo trade.setState
+ appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
+ failed();
+ }
+ }
+ );
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java
similarity index 73%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java
index ce9034b98dc..077bca13636 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java
@@ -15,10 +15,10 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.taker;
+package bisq.core.trade.protocol.tasks.seller;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.network.p2p.NodeAddress;
@@ -26,14 +26,18 @@
import bisq.common.taskrunner.TaskRunner;
+import org.bitcoinj.core.Transaction;
+
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
-public class TakerSendDepositTxPublishedMessage extends TradeTask {
+public class SellerSendsDepositTxAndDelayedPayoutTxMessage extends TradeTask {
@SuppressWarnings({"unused"})
- public TakerSendDepositTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
+ public SellerSendsDepositTxAndDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -42,12 +46,14 @@ protected void run() {
try {
runInterceptHook();
if (trade.getDepositTx() != null) {
- final String id = processModel.getOfferId();
- DepositTxPublishedMessage message = new DepositTxPublishedMessage(processModel.getOfferId(),
- trade.getDepositTx().bitcoinSerialize(),
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ Transaction depositTx = checkNotNull(trade.getDepositTx());
+ DepositTxAndDelayedPayoutTxMessage message = new DepositTxAndDelayedPayoutTxMessage(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
processModel.getMyNodeAddress(),
- UUID.randomUUID().toString());
- trade.setState(Trade.State.TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG);
+ depositTx.bitcoinSerialize(),
+ delayedPayoutTx.bitcoinSerialize());
+ trade.setState(Trade.State.SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG);
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}",
@@ -61,7 +67,7 @@ protected void run() {
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- trade.setState(Trade.State.TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG);
+ trade.setState(Trade.State.SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG);
complete();
}
@@ -69,7 +75,8 @@ public void onArrived() {
public void onStoredInMailbox() {
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- trade.setState(Trade.State.TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG);
+
+ trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG);
complete();
}
@@ -77,7 +84,7 @@ public void onStoredInMailbox() {
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
- trade.setState(Trade.State.TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG);
+ trade.setState(Trade.State.SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
failed();
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java
index 80b3e4c6340..d6868db4238 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java
@@ -60,7 +60,7 @@ protected void run() {
final byte[] buyerSignature = tradingPeer.getSignature();
- Coin buyerPayoutAmount = offer.getBuyerSecurityDeposit().add(trade.getTradeAmount());
+ Coin buyerPayoutAmount = checkNotNull(offer.getBuyerSecurityDeposit()).add(trade.getTradeAmount());
Coin sellerPayoutAmount = offer.getSellerSecurityDeposit();
final String buyerPayoutAddressString = tradingPeer.getPayoutAddressString();
@@ -78,7 +78,7 @@ protected void run() {
DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(id, sellerMultiSigPubKey);
Transaction transaction = processModel.getTradeWalletService().sellerSignsAndFinalizesPayoutTx(
- trade.getDepositTx(),
+ checkNotNull(trade.getDepositTx()),
buyerSignature,
buyerPayoutAmount,
sellerPayoutAmount,
@@ -86,8 +86,7 @@ protected void run() {
sellerPayoutAddressString,
multiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey()
+ sellerMultiSigPubKey
);
trade.setPayoutTx(transaction);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java
new file mode 100644
index 00000000000..b8b8e155c2d
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java
@@ -0,0 +1,73 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+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 org.bitcoinj.core.Transaction;
+import org.bitcoinj.crypto.DeterministicKey;
+
+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 SellerSignsDelayedPayoutTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerSignsDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
+ BtcWalletService btcWalletService = processModel.getBtcWalletService();
+ String id = processModel.getOffer().getId();
+
+ byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
+ DeterministicKey myMultiSigKeyPair = btcWalletService.getMultiSigKeyPair(id, sellerMultiSigPubKey);
+
+ checkArgument(Arrays.equals(sellerMultiSigPubKey,
+ btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
+ "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
+ byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
+
+ byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(preparedDelayedPayoutTx,
+ myMultiSigKeyPair,
+ buyerMultiSigPubKey,
+ sellerMultiSigPubKey);
+
+ processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java
similarity index 72%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsDepositTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java
index 28f3005163d..3f7cbdbef02 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java
@@ -42,9 +42,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class SellerAsMakerCreatesAndSignsDepositTx extends TradeTask {
+public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask {
@SuppressWarnings({"unused"})
- public SellerAsMakerCreatesAndSignsDepositTx(TaskRunner taskHandler, Trade trade) {
+ public SellerAsMakerCreatesUnsignedDepositTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -57,44 +57,39 @@ protected void run() {
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
TradingPeer tradingPeer = processModel.getTradingPeer();
- final Offer offer = trade.getOffer();
+ Offer offer = checkNotNull(trade.getOffer());
// params
- final boolean makerIsBuyer = false;
-
- final byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
log.debug("\n\n------------------------------------------------------------\n"
+ "Contract as json\n"
+ trade.getContractAsJson()
+ "\n------------------------------------------------------------\n");
- final Coin makerInputAmount = offer.getSellerSecurityDeposit().add(trade.getTradeAmount());
+ Coin makerInputAmount = offer.getSellerSecurityDeposit().add(trade.getTradeAmount());
Optional addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
AddressEntry makerMultiSigAddressEntry = addressEntryOptional.get();
makerMultiSigAddressEntry.setCoinLockedInMultiSig(makerInputAmount);
walletService.saveAddressEntryList();
- final Coin msOutputAmount = makerInputAmount
+ Coin msOutputAmount = makerInputAmount
.add(trade.getTxFee())
.add(offer.getBuyerSecurityDeposit());
- final List takerRawTransactionInputs = tradingPeer.getRawTransactionInputs();
- final long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
- final String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
- final Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
- final Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
- final byte[] buyerPubKey = tradingPeer.getMultiSigPubKey();
- final byte[] sellerPubKey = processModel.getMyMultiSigPubKey();
+ List takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
+ long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
+ String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
+ Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
+ Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
+ byte[] buyerPubKey = tradingPeer.getMultiSigPubKey();
+ byte[] sellerPubKey = processModel.getMyMultiSigPubKey();
checkArgument(Arrays.equals(sellerPubKey,
makerMultiSigAddressEntry.getPubKey()),
"sellerPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] arbitratorBtcPubKey = trade.getArbitratorBtcPubKey();
-
- PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().makerCreatesAndSignsDepositTx(
- makerIsBuyer,
+ PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().sellerAsMakerCreatesDepositTx(
contractHash,
makerInputAmount,
msOutputAmount,
@@ -104,8 +99,7 @@ protected void run() {
makerAddress,
makerChangeAddress,
buyerPubKey,
- sellerPubKey,
- arbitratorBtcPubKey);
+ sellerPubKey);
processModel.setPreparedDepositTx(result.depositTransaction);
processModel.setRawTransactionInputs(result.rawMakerInputs);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java
new file mode 100644
index 00000000000..ba490a92f3d
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java
@@ -0,0 +1,57 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerAsMakerFinalizesDepositTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerAsMakerFinalizesDepositTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ byte[] takersRawPreparedDepositTx = checkNotNull(processModel.getTradingPeer().getPreparedDepositTx());
+ byte[] myRawPreparedDepositTx = checkNotNull(processModel.getPreparedDepositTx());
+ Transaction takersDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(takersRawPreparedDepositTx);
+ Transaction myDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(myRawPreparedDepositTx);
+ int numTakersInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs()).size();
+ processModel.getTradeWalletService().sellerAsMakerFinalizesDepositTx(myDepositTx, takersDepositTx, numTakersInputs);
+
+ trade.applyDepositTx(myDepositTx);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java
new file mode 100644
index 00000000000..b5962c989fa
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java
@@ -0,0 +1,56 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.protocol.tasks.TradeTask;
+import bisq.core.util.Validator;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerAsMakerProcessDepositTxMessage extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerAsMakerProcessDepositTxMessage(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ log.debug("current trade state " + trade.getState());
+ DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage();
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ checkNotNull(message);
+ checkNotNull(message.getDepositTx());
+
+ processModel.getTradingPeer().setPreparedDepositTx(message.getDepositTx());
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java
new file mode 100644
index 00000000000..f1fb5162809
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java
@@ -0,0 +1,47 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.script.Script;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SellerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInputsForDepositTxResponse {
+ @SuppressWarnings({"unused"})
+ public SellerAsMakerSendsInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected byte[] getPreparedDepositTx() {
+ Transaction preparedDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(processModel.getPreparedDepositTx());
+ preparedDepositTx.getInputs().forEach(input -> {
+ // Remove signature before sending to peer as we don't want to risk that buyer could publish deposit tx
+ // before we have received his signature for the delayed payout tx.
+ input.setScriptSig(new Script(new byte[]{}));
+ });
+ return preparedDepositTx.bitcoinSerialize();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java
index 6298250566e..9b4b88a0ba4 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java
@@ -17,19 +17,18 @@
package bisq.core.trade.protocol.tasks.seller_as_taker;
-import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.InputsAndChangeOutput;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class SellerAsTakerCreatesDepositTxInputs extends TradeTask {
@SuppressWarnings({"unused"})
@@ -43,17 +42,14 @@ protected void run() {
runInterceptHook();
if (trade.getTradeAmount() != null) {
Coin txFee = trade.getTxFee();
- Coin takerInputAmount = trade.getOffer().getSellerSecurityDeposit()
- .add(txFee).add(txFee).add(trade.getTradeAmount());
-
- BtcWalletService walletService = processModel.getBtcWalletService();
- Address takersAddress = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(),
- AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
- InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(
+ Coin takerInputAmount = checkNotNull(trade.getOffer()).getSellerSecurityDeposit()
+ .add(txFee)
+ .add(txFee)
+ .add(trade.getTradeAmount());
+ InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs(
processModel.getTakeOfferFeeTx(),
takerInputAmount,
- txFee,
- takersAddress);
+ txFee);
processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignAndPublishDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java
similarity index 56%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignAndPublishDepositTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java
index 9bce2944109..6a27e1221c8 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignAndPublishDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java
@@ -17,11 +17,9 @@
package bisq.core.trade.protocol.tasks.seller_as_taker;
-import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.wallet.BtcWalletService;
-import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
@@ -43,9 +41,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class SellerAsTakerSignAndPublishDepositTx extends TradeTask {
+public class SellerAsTakerSignsDepositTx extends TradeTask {
@SuppressWarnings({"unused"})
- public SellerAsTakerSignAndPublishDepositTx(TaskRunner taskHandler, Trade trade) {
+ public SellerAsTakerSignsDepositTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -58,7 +56,7 @@ protected void run() {
+ trade.getContractAsJson()
+ "\n------------------------------------------------------------\n");
- byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
List sellerInputs = checkNotNull(processModel.getRawTransactionInputs(), "sellerInputs must not be null");
@@ -80,51 +78,20 @@ protected void run() {
TradingPeer tradingPeer = processModel.getTradingPeer();
- Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
+ Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx(
true,
contractHash,
processModel.getPreparedDepositTx(),
- tradingPeer.getRawTransactionInputs(),
+ checkNotNull(tradingPeer.getRawTransactionInputs()),
sellerInputs,
tradingPeer.getMultiSigPubKey(),
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey(),
- new TxBroadcaster.Callback() {
- @Override
- public void onSuccess(Transaction transaction) {
- if (!completed) {
- // We set the depositTx before we change the state as the state change triggers code
- // which expected the tx to be available. That case will usually never happen as the
- // callback is called after the method call has returned but in some test scenarios
- // with regtest we run into such issues, thus fixing it to make it more stict seems
- // reasonable.
- trade.setDepositTx(transaction);
- log.trace("takerSignsAndPublishesDepositTx succeeded " + transaction);
- trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
- walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
-
- complete();
- } else {
- log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
- }
- }
-
- @Override
- public void onFailure(TxBroadcastException exception) {
- if (!completed) {
- failed(exception);
- } else {
- log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
- }
- }
- });
- if (trade.getDepositTx() == null) {
- // We set the deposit tx in case we get the onFailure called. We cannot set it in the onFailure
- // callback as the tx is returned by the method call where the callback is used as an argument.
- trade.setDepositTx(depositTx);
- }
+ sellerMultiSigPubKey);
+
+ trade.applyDepositTx(depositTx);
+
+ complete();
} catch (Throwable t) {
- final Contract contract = trade.getContract();
+ Contract contract = trade.getContract();
if (contract != null)
contract.printDiff(processModel.getTradingPeer().getContractAsJson());
failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java
index 1b9241f8408..1e9f1a561c8 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java
@@ -22,8 +22,7 @@
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.WalletService;
import bisq.core.dao.exceptions.DaoDisabledException;
-import bisq.core.offer.availability.DisputeAgentSelection;
-import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
+import bisq.core.dao.governance.param.Param;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
@@ -47,8 +46,6 @@ protected void run() {
try {
runInterceptHook();
- Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(processModel.getTradeStatisticsManager(),
- processModel.getArbitratorManager());
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
@@ -68,6 +65,7 @@ protected void run() {
Address changeAddress = changeAddressEntry.getAddress();
TradeWalletService tradeWalletService = processModel.getTradeWalletService();
Transaction transaction;
+ String feeReceiver = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
if (trade.isCurrencyForTakerFeeBtc()) {
transaction = tradeWalletService.createBtcTradingFeeTx(
fundingAddress,
@@ -77,11 +75,11 @@ protected void run() {
processModel.isUseSavingsWallet(),
trade.getTakerFee(),
trade.getTxFee(),
- arbitrator.getBtcAddress(),
+ feeReceiver,
false,
null);
} else {
- Transaction preparedBurnFeeTx = processModel.getBsqWalletService().getPreparedBurnFeeTx(trade.getTakerFee());
+ Transaction preparedBurnFeeTx = processModel.getBsqWalletService().getPreparedTradeFeeTx(trade.getTakerFee());
Transaction txWithBsqFee = tradeWalletService.completeBsqTradingFeeTx(preparedBurnFeeTx,
fundingAddress,
reservedForTradeAddress,
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessPublishDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessPublishDepositTxRequest.java
deleted file mode 100644
index 0160397d04e..00000000000
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessPublishDepositTxRequest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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 .
- */
-
-package bisq.core.trade.protocol.tasks.taker;
-
-import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PublishDepositTxRequest;
-import bisq.core.trade.protocol.TradingPeer;
-import bisq.core.trade.protocol.tasks.TradeTask;
-
-import bisq.common.taskrunner.TaskRunner;
-
-import lombok.extern.slf4j.Slf4j;
-
-import static bisq.core.util.Validator.checkTradeId;
-import static bisq.core.util.Validator.nonEmptyStringOf;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-@Slf4j
-public class TakerProcessPublishDepositTxRequest extends TradeTask {
- @SuppressWarnings({"unused"})
- public TakerProcessPublishDepositTxRequest(TaskRunner taskHandler, Trade trade) {
- super(taskHandler, trade);
- }
-
- @Override
- protected void run() {
- try {
- runInterceptHook();
- log.debug("current trade state " + trade.getState());
- PublishDepositTxRequest publishDepositTxRequest = (PublishDepositTxRequest) processModel.getTradeMessage();
- checkTradeId(processModel.getOfferId(), publishDepositTxRequest);
- checkNotNull(publishDepositTxRequest);
-
- final TradingPeer tradingPeer = processModel.getTradingPeer();
- tradingPeer.setPaymentAccountPayload(checkNotNull(publishDepositTxRequest.getMakerPaymentAccountPayload()));
- tradingPeer.setAccountId(nonEmptyStringOf(publishDepositTxRequest.getMakerAccountId()));
- tradingPeer.setMultiSigPubKey(checkNotNull(publishDepositTxRequest.getMakerMultiSigPubKey()));
- tradingPeer.setContractAsJson(nonEmptyStringOf(publishDepositTxRequest.getMakerContractAsJson()));
- tradingPeer.setContractSignature(nonEmptyStringOf(publishDepositTxRequest.getMakerContractSignature()));
- tradingPeer.setPayoutAddressString(nonEmptyStringOf(publishDepositTxRequest.getMakerPayoutAddressString()));
- tradingPeer.setRawTransactionInputs(checkNotNull(publishDepositTxRequest.getMakerInputs()));
- final byte[] preparedDepositTx = publishDepositTxRequest.getPreparedDepositTx();
- processModel.setPreparedDepositTx(checkNotNull(preparedDepositTx));
-
- // Maker has to sign preparedDepositTx. He cannot manipulate the preparedDepositTx - so we avoid to have a
- // challenge protocol for passing the nonce we want to get signed.
- tradingPeer.setAccountAgeWitnessNonce(publishDepositTxRequest.getPreparedDepositTx());
- tradingPeer.setAccountAgeWitnessSignature(publishDepositTxRequest.getAccountAgeWitnessSignatureOfPreparedDepositTx());
-
- tradingPeer.setCurrentDate(publishDepositTxRequest.getCurrentDate());
-
- checkArgument(publishDepositTxRequest.getMakerInputs().size() > 0);
-
- // update to the latest peer address of our peer if the message is correct
- trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
- trade.setState(Trade.State.TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST);
-
- complete();
- } catch (Throwable t) {
- failed(t);
- }
- }
-}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java
new file mode 100644
index 00000000000..ba1b80b5308
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java
@@ -0,0 +1,84 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.taker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
+import bisq.core.trade.protocol.TradingPeer;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.core.util.Validator.checkTradeId;
+import static bisq.core.util.Validator.nonEmptyStringOf;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class TakerProcessesInputsForDepositTxResponse extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public TakerProcessesInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ log.debug("current trade state " + trade.getState());
+ InputsForDepositTxResponse inputsForDepositTxResponse = (InputsForDepositTxResponse) processModel.getTradeMessage();
+ checkTradeId(processModel.getOfferId(), inputsForDepositTxResponse);
+ checkNotNull(inputsForDepositTxResponse);
+
+ TradingPeer tradingPeer = processModel.getTradingPeer();
+ tradingPeer.setPaymentAccountPayload(checkNotNull(inputsForDepositTxResponse.getMakerPaymentAccountPayload()));
+ tradingPeer.setAccountId(nonEmptyStringOf(inputsForDepositTxResponse.getMakerAccountId()));
+ tradingPeer.setMultiSigPubKey(checkNotNull(inputsForDepositTxResponse.getMakerMultiSigPubKey()));
+ tradingPeer.setContractAsJson(nonEmptyStringOf(inputsForDepositTxResponse.getMakerContractAsJson()));
+ tradingPeer.setContractSignature(nonEmptyStringOf(inputsForDepositTxResponse.getMakerContractSignature()));
+ tradingPeer.setPayoutAddressString(nonEmptyStringOf(inputsForDepositTxResponse.getMakerPayoutAddressString()));
+ tradingPeer.setRawTransactionInputs(checkNotNull(inputsForDepositTxResponse.getMakerInputs()));
+ byte[] preparedDepositTx = inputsForDepositTxResponse.getPreparedDepositTx();
+ processModel.setPreparedDepositTx(checkNotNull(preparedDepositTx));
+ long lockTime = inputsForDepositTxResponse.getLockTime();
+ //todo for dev testing deactivated
+ //checkArgument(lockTime >= processModel.getBtcWalletService().getBestChainHeight() + 144 * 20);
+ trade.setLockTime(lockTime);
+ log.info("lockTime={}, delay={}", lockTime, (processModel.getBtcWalletService().getBestChainHeight() - lockTime));
+
+ // Maker has to sign preparedDepositTx. He cannot manipulate the preparedDepositTx - so we avoid to have a
+ // challenge protocol for passing the nonce we want to get signed.
+ tradingPeer.setAccountAgeWitnessNonce(inputsForDepositTxResponse.getPreparedDepositTx());
+ tradingPeer.setAccountAgeWitnessSignature(inputsForDepositTxResponse.getAccountAgeWitnessSignatureOfPreparedDepositTx());
+
+ tradingPeer.setCurrentDate(inputsForDepositTxResponse.getCurrentDate());
+
+ checkArgument(inputsForDepositTxResponse.getMakerInputs().size() > 0);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+ trade.setState(Trade.State.TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java
similarity index 87%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java
index 58f8591d47d..f9d0651383a 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java
@@ -21,7 +21,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.user.User;
@@ -45,9 +45,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class TakerSendPayDepositRequest extends TradeTask {
+public class TakerSendInputsForDepositTxRequest extends TradeTask {
@SuppressWarnings({"unused"})
- public TakerSendPayDepositRequest(TaskRunner taskHandler, Trade trade) {
+ public TakerSendInputsForDepositTxRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -61,8 +61,10 @@ protected void run() {
checkNotNull(user, "User must not be null");
final List acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses();
final List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses();
- checkNotNull(acceptedArbitratorAddresses, "acceptedArbitratorAddresses must not be null");
+ final List acceptedRefundAgentAddresses = user.getAcceptedRefundAgentAddresses();
+ // We don't check for arbitrators as they should vanish soon
checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null");
+ // We also don't check for refund agents yet as we don't want to restict us too much. They are not mandatory.
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
@@ -85,7 +87,7 @@ protected void run() {
final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null");
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8));
- PayDepositRequest message = new PayDepositRequest(
+ InputsForDepositTxRequest message = new InputsForDepositTxRequest(
offerId,
processModel.getMyNodeAddress(),
trade.getTradeAmount().value,
@@ -102,10 +104,12 @@ protected void run() {
paymentAccountPayload,
processModel.getAccountId(),
trade.getTakerFeeTxId(),
- new ArrayList<>(acceptedArbitratorAddresses),
+ acceptedArbitratorAddresses == null ? new ArrayList<>() : new ArrayList<>(acceptedArbitratorAddresses),
new ArrayList<>(acceptedMediatorAddresses),
+ acceptedRefundAgentAddresses == null ? new ArrayList<>() : new ArrayList<>(acceptedRefundAgentAddresses),
trade.getArbitratorNodeAddress(),
trade.getMediatorNodeAddress(),
+ trade.getRefundAgentNodeAddress(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
sig,
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java
index 879a9ac6e9b..c8134b59130 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java
@@ -56,8 +56,8 @@ protected void run() {
checkNotNull(trade.getTakerFeeTxId(), "TakeOfferFeeTxId must not be null");
TradingPeer maker = processModel.getTradingPeer();
- PaymentAccountPayload makerPaymentAccountPayload = maker.getPaymentAccountPayload();
- PaymentAccountPayload takerPaymentAccountPayload = processModel.getPaymentAccountPayload(trade);
+ PaymentAccountPayload makerPaymentAccountPayload = checkNotNull(maker.getPaymentAccountPayload());
+ PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade));
boolean isBuyerMakerAndSellerTaker = trade instanceof SellerAsTakerTrade;
NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress();
@@ -85,7 +85,6 @@ protected void run() {
trade.getTakerFeeTxId(),
buyerNodeAddress,
sellerNodeAddress,
- trade.getArbitratorNodeAddress(),
trade.getMediatorNodeAddress(),
isBuyerMakerAndSellerTaker,
maker.getAccountId(),
@@ -97,7 +96,9 @@ protected void run() {
maker.getPayoutAddressString(),
takerPayoutAddressString,
maker.getMultiSigPubKey(),
- takerMultiSigPubKey
+ takerMultiSigPubKey,
+ trade.getLockTime(),
+ trade.getRefundAgentNodeAddress()
);
String contractAsJson = Utilities.objectToJson(contract);
log.trace("Contract as json:{}", contractAsJson);
diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
index 1210de64c86..feb0b6ed14d 100644
--- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
+++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
@@ -24,9 +24,12 @@
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferUtil;
+import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
import bisq.network.p2p.storage.payload.LazyProcessedPayload;
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
+import bisq.common.app.Capabilities;
+import bisq.common.app.Capability;
import bisq.common.crypto.Hash;
import bisq.common.proto.persistable.PersistableEnvelope;
import bisq.common.util.ExtraDataMapValidator;
@@ -60,9 +63,14 @@
@Slf4j
@Value
-public final class TradeStatistics2 implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope {
+public final class TradeStatistics2 implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope, CapabilityRequiringPayload {
+
+ //We don't support arbitrators anymore so this entry will be only for pre v1.2. trades
+ @Deprecated
public static final String ARBITRATOR_ADDRESS = "arbAddr";
+
public static final String MEDIATOR_ADDRESS = "medAddr";
+ public static final String REFUND_AGENT_ADDRESS = "refAddr";
private final OfferPayload.Direction direction;
private final String baseCurrency;
@@ -81,7 +89,8 @@ public final class TradeStatistics2 implements LazyProcessedPayload, Persistable
private final long tradeDate;
private final String depositTxId;
- // hash get set in constructor from json of all the other data fields (with hash = null).
+ // Hash get set in constructor from json of all the other data fields (with hash = null).
+ @JsonExclude
private final byte[] hash;
// PB field signature_pub_key_bytes not used anymore from v0.6 on
@@ -89,6 +98,7 @@ public final class TradeStatistics2 implements LazyProcessedPayload, Persistable
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
// field in a class would break that hash and therefore break the storage mechanism.
@Nullable
+ @JsonExclude
private Map extraDataMap;
public TradeStatistics2(OfferPayload offerPayload,
@@ -151,12 +161,14 @@ public TradeStatistics2(OfferPayload.Direction direction,
this.depositTxId = depositTxId;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
- if (hash == null)
- // We create hash from all fields excluding hash itself. We use json as simple data serialisation.
- // tradeDate is different for both peers so we ignore it for hash.
- this.hash = Hash.getSha256Ripemd160hash(Utilities.objectToJson(this).getBytes(Charsets.UTF_8));
- else
- this.hash = hash;
+ this.hash = hash == null ? createHash() : hash;
+ }
+
+ public byte[] createHash() {
+ // We create hash from all fields excluding hash itself. We use json as simple data serialisation.
+ // TradeDate is different for both peers so we ignore it for hash. ExtraDataMap is ignored as well as at
+ // software updates we might have different entries which would cause a different hash.
+ return Hash.getSha256Ripemd160hash(Utilities.objectToJson(this).getBytes(Charsets.UTF_8));
}
@Override
@@ -202,7 +214,7 @@ public static TradeStatistics2 fromProto(protobuf.TradeStatistics2 proto) {
proto.getTradeAmount(),
proto.getTradeDate(),
proto.getDepositTxId(),
- proto.getHash().toByteArray(),
+ null, // We want to clean up the hashes with the changed hash method in v.1.2.0 so we don't use the value from the proto
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap());
}
@@ -222,6 +234,16 @@ public boolean verifyHashSize() {
return hash.length == 20;
}
+ // With v1.2.0 we changed the way how the hash is created. To not create too heavy load for seed nodes from
+ // requests from old nodes we use the TRADE_STATISTICS_HASH_UPDATE capability to send trade statistics only to new
+ // nodes. As trade statistics are only used for informational purpose it will not have any critical issue for the
+ // old nodes beside that they don't see the latest trades. We added TRADE_STATISTICS_HASH_UPDATE in v1.2.2 to fix a
+ // problem of not handling the hashes correctly.
+ @Override
+ public Capabilities getRequiredCapabilities() {
+ return new Capabilities(Capability.TRADE_STATISTICS_HASH_UPDATE);
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
@@ -258,7 +280,7 @@ public boolean isValid() {
// Since the trade wasn't executed it's better to filter it out to avoid it having an undue influence on the
// BSQ trade stats.
boolean excludedFailedTrade = offerId.equals("6E5KOI6O-3a06a037-6f03-4bfa-98c2-59f49f73466a-112");
- return tradeAmount > 0 && tradePrice > 0 && !excludedFailedTrade;
+ return tradeAmount > 0 && tradePrice > 0 && !excludedFailedTrade && !depositTxId.isEmpty();
}
@Override
diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java
index cb684ec9353..60673e07320 100644
--- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java
+++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java
@@ -33,8 +33,6 @@
import lombok.extern.slf4j.Slf4j;
-import static com.google.common.base.Preconditions.checkArgument;
-
@Slf4j
public class TradeStatistics2StorageService extends MapStoreService {
private static final String FILE_NAME = "TradeStatistics2Store";
@@ -83,8 +81,5 @@ protected TradeStatistics2Store createStore() {
@Override
protected void readStore() {
super.readStore();
- checkArgument(store instanceof TradeStatistics2Store,
- "Store is not instance of TradeStatistics2Store. That can happen if the ProtoBuffer " +
- "file got changed. We clear the data store and recreated it again.");
}
}
diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java
index 3f40cd660bd..e66c91b3e9b 100644
--- a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java
+++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java
@@ -21,15 +21,11 @@
import bisq.core.locale.CurrencyTuple;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
-import bisq.core.offer.Offer;
-import bisq.core.offer.OfferPayload;
import bisq.core.provider.price.PriceFeedService;
-import bisq.core.trade.Trade;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
-import bisq.common.UserThread;
import bisq.common.storage.JsonFileManager;
import bisq.common.storage.Storage;
import bisq.common.util.Utilities;
@@ -43,24 +39,19 @@
import java.io.File;
import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
-import static com.google.common.base.Preconditions.checkNotNull;
-
@Slf4j
public class TradeStatisticsManager {
private final JsonFileManager jsonFileManager;
private final P2PService p2PService;
private final PriceFeedService priceFeedService;
- private final ReferralIdService referralIdService;
+ private final TradeStatistics2StorageService tradeStatistics2StorageService;
private final boolean dumpStatistics;
private final ObservableSet observableTradeStatisticsSet = FXCollections.observableSet();
@@ -69,12 +60,11 @@ public TradeStatisticsManager(P2PService p2PService,
PriceFeedService priceFeedService,
TradeStatistics2StorageService tradeStatistics2StorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService,
- ReferralIdService referralIdService,
@Named(Storage.STORAGE_DIR) File storageDir,
@Named(AppOptionKeys.DUMP_STATISTICS) boolean dumpStatistics) {
this.p2PService = p2PService;
this.priceFeedService = priceFeedService;
- this.referralIdService = referralIdService;
+ this.tradeStatistics2StorageService = tradeStatistics2StorageService;
this.dumpStatistics = dumpStatistics;
jsonFileManager = new JsonFileManager(storageDir);
@@ -82,99 +72,56 @@ public TradeStatisticsManager(P2PService p2PService,
}
public void onAllServicesInitialized() {
- if (dumpStatistics) {
- ArrayList fiatCurrencyList = CurrencyUtil.getAllSortedFiatCurrencies().stream()
- .map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8))
- .collect(Collectors.toCollection(ArrayList::new));
- jsonFileManager.writeToDisc(Utilities.objectToJson(fiatCurrencyList), "fiat_currency_list");
-
- ArrayList cryptoCurrencyList = CurrencyUtil.getAllSortedCryptoCurrencies().stream()
- .map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8))
- .collect(Collectors.toCollection(ArrayList::new));
- cryptoCurrencyList.add(0, new CurrencyTuple(Res.getBaseCurrencyCode(), Res.getBaseCurrencyName(), 8));
- jsonFileManager.writeToDisc(Utilities.objectToJson(cryptoCurrencyList), "crypto_currency_list");
- }
-
p2PService.getP2PDataStorage().addAppendOnlyDataStoreListener(payload -> {
if (payload instanceof TradeStatistics2)
- addToMap((TradeStatistics2) payload, true);
+ addToSet((TradeStatistics2) payload);
});
- Map map = new HashMap<>();
- p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().stream()
+ Set collect = tradeStatistics2StorageService.getMap().values().stream()
.filter(e -> e instanceof TradeStatistics2)
.map(e -> (TradeStatistics2) e)
.filter(TradeStatistics2::isValid)
- .forEach(e -> addToMap(e, map));
- observableTradeStatisticsSet.addAll(map.values());
+ .collect(Collectors.toSet());
+ observableTradeStatisticsSet.addAll(collect);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
dump();
}
- public void publishTradeStatistics(List trades) {
- for (int i = 0; i < trades.size(); i++) {
- Trade trade = trades.get(i);
-
- Map extraDataMap = null;
- if (referralIdService.getOptionalReferralId().isPresent()) {
- extraDataMap = new HashMap<>();
- extraDataMap.put(OfferPayload.REFERRAL_ID, referralIdService.getOptionalReferralId().get());
- }
- Offer offer = trade.getOffer();
- checkNotNull(offer, "offer must not ne null");
- checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not ne null");
- TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
- trade.getTradePrice(),
- trade.getTradeAmount(),
- trade.getDate(),
- (trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
- extraDataMap);
- addToMap(tradeStatistics, true);
-
- // We only republish trades from last 10 days
- if ((new Date().getTime() - trade.getDate().getTime()) < TimeUnit.DAYS.toMillis(10)) {
- long delay = 5000;
- long minDelay = (i + 1) * delay;
- long maxDelay = (i + 2) * delay;
- UserThread.runAfterRandomDelay(() -> {
- p2PService.addPersistableNetworkPayload(tradeStatistics, true);
- }, minDelay, maxDelay, TimeUnit.MILLISECONDS);
- }
- }
- }
-
public ObservableSet getObservableTradeStatisticsSet() {
return observableTradeStatisticsSet;
}
- private void addToMap(TradeStatistics2 tradeStatistics, boolean storeLocally) {
+ private void addToSet(TradeStatistics2 tradeStatistics) {
if (!observableTradeStatisticsSet.contains(tradeStatistics)) {
-
- if (observableTradeStatisticsSet.stream()
- .anyMatch(e -> (e.getOfferId().equals(tradeStatistics.getOfferId()))))
+ if (observableTradeStatisticsSet.stream().anyMatch(e -> e.getOfferId().equals(tradeStatistics.getOfferId()))) {
return;
+ }
- if (!tradeStatistics.isValid())
+ if (!tradeStatistics.isValid()) {
return;
+ }
observableTradeStatisticsSet.add(tradeStatistics);
- if (storeLocally) {
- priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
- dump();
- }
+ priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
+ dump();
}
}
- private void addToMap(TradeStatistics2 tradeStatistics, Map map) {
- TradeStatistics2 prevValue = map.putIfAbsent(tradeStatistics.getOfferId(), tradeStatistics);
- if (prevValue != null)
- log.trace("We have already an item with the same offer ID. That might happen if both the maker and the taker published the tradeStatistics");
- }
-
private void dump() {
if (dumpStatistics) {
+ ArrayList fiatCurrencyList = CurrencyUtil.getAllSortedFiatCurrencies().stream()
+ .map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8))
+ .collect(Collectors.toCollection(ArrayList::new));
+ jsonFileManager.writeToDisc(Utilities.objectToJson(fiatCurrencyList), "fiat_currency_list");
+
+ ArrayList cryptoCurrencyList = CurrencyUtil.getAllSortedCryptoCurrencies().stream()
+ .map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8))
+ .collect(Collectors.toCollection(ArrayList::new));
+ cryptoCurrencyList.add(0, new CurrencyTuple(Res.getBaseCurrencyCode(), Res.getBaseCurrencyName(), 8));
+ jsonFileManager.writeToDisc(Utilities.objectToJson(cryptoCurrencyList), "crypto_currency_list");
+
// We store the statistics as json so it is easy for further processing (e.g. for web based services)
// TODO This is just a quick solution for storing to one file.
// 1 statistic entry has 500 bytes as json.
diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java
index 4d575029d24..d022eb9dae9 100644
--- a/core/src/main/java/bisq/core/user/Preferences.java
+++ b/core/src/main/java/bisq/core/user/Preferences.java
@@ -83,6 +83,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
private static final ArrayList BTC_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList(
new BlockChainExplorer("Blockstream.info", "https://blockstream.info/tx/", "https://blockstream.info/address/"),
new BlockChainExplorer("Blockstream.info Tor V3", "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/tx/", "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/address/"),
+ new BlockChainExplorer("Blockstream.info + Mempool.space", "https://mempool.space/tx/", "https://blockstream.info/address/"),
new BlockChainExplorer("OXT", "https://oxt.me/transaction/", "https://oxt.me/address/"),
new BlockChainExplorer("Bitaps", "https://bitaps.com/", "https://bitaps.com/"),
new BlockChainExplorer("Blockcypher", "https://live.blockcypher.com/btc/tx/", "https://live.blockcypher.com/btc/address/"),
@@ -394,6 +395,11 @@ public void setTacAccepted(boolean tacAccepted) {
persist();
}
+ public void setTacAcceptedV120(boolean tacAccepted) {
+ prefPayload.setTacAcceptedV120(tacAccepted);
+ persist();
+ }
+
private void persist() {
if (initialReadDone)
storage.queueUpForSave(prefPayload);
@@ -951,5 +957,7 @@ private interface ExcludesDelegateMethods {
String getRpcPw();
int getBlockNotifyPort();
+
+ void setTacAcceptedV120(boolean tacAccepted);
}
}
diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java
index 998378b61da..1cddc321787 100644
--- a/core/src/main/java/bisq/core/user/PreferencesPayload.java
+++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java
@@ -125,6 +125,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private int ignoreDustThreshold = 600;
private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent(new CryptoCurrencyAccount());
private int blockNotifyPort;
+ private boolean tacAcceptedV120;
///////////////////////////////////////////////////////////////////////////////////////////
@@ -185,7 +186,8 @@ public Message toProtoMessage() {
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
.setIgnoreDustThreshold(ignoreDustThreshold)
.setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto)
- .setBlockNotifyPort(blockNotifyPort);
+ .setBlockNotifyPort(blockNotifyPort)
+ .setTacAcceptedV120(tacAcceptedV120);
Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode);
@@ -271,7 +273,8 @@ public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, C
proto.getBuyerSecurityDepositAsPercent(),
proto.getIgnoreDustThreshold(),
proto.getBuyerSecurityDepositAsPercentForCrypto(),
- proto.getBlockNotifyPort());
+ proto.getBlockNotifyPort(),
+ proto.getTacAcceptedV120());
}
}
diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java
index d197a802c98..75891395d22 100644
--- a/core/src/main/java/bisq/core/user/User.java
+++ b/core/src/main/java/bisq/core/user/User.java
@@ -26,6 +26,7 @@
import bisq.core.payment.PaymentAccount;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.mediator.Mediator;
+import bisq.core.support.dispute.refund.refundagent.RefundAgent;
import bisq.network.p2p.NodeAddress;
@@ -44,7 +45,6 @@
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -126,13 +126,6 @@ public void persist() {
// API
///////////////////////////////////////////////////////////////////////////////////////////
- /* public Optional getPaymentAccountForCurrency(TradeCurrency tradeCurrency) {
- return getPaymentAccounts().stream()
- .flatMap(e -> e.getTradeCurrencies().stream())
- .filter(e -> e.equals(tradeCurrency))
- .findFirst();
- }*/
-
@Nullable
public Arbitrator getAcceptedArbitratorByAddress(NodeAddress nodeAddress) {
final List acceptedArbitrators = userPayload.getAcceptedArbitrators();
@@ -159,6 +152,19 @@ public Mediator getAcceptedMediatorByAddress(NodeAddress nodeAddress) {
}
}
+ @Nullable
+ public RefundAgent getAcceptedRefundAgentByAddress(NodeAddress nodeAddress) {
+ final List acceptedRefundAgents = userPayload.getAcceptedRefundAgents();
+ if (acceptedRefundAgents != null) {
+ Optional refundAgentOptional = acceptedRefundAgents.stream()
+ .filter(e -> e.getNodeAddress().equals(nodeAddress))
+ .findFirst();
+ return refundAgentOptional.orElse(null);
+ } else {
+ return null;
+ }
+ }
+
@Nullable
public PaymentAccount findFirstPaymentAccountWithCurrency(TradeCurrency tradeCurrency) {
if (userPayload.getPaymentAccounts() != null) {
@@ -174,21 +180,6 @@ public PaymentAccount findFirstPaymentAccountWithCurrency(TradeCurrency tradeCur
}
}
- public boolean hasMatchingLanguage(Arbitrator arbitrator) {
- final List codes = userPayload.getAcceptedLanguageLocaleCodes();
- if (arbitrator != null && codes != null) {
- for (String acceptedCode : codes) {
- for (String itemCode : arbitrator.getLanguageCodes()) {
- if (acceptedCode.equals(itemCode))
- return true;
- }
- }
- return false;
- } else {
- return false;
- }
- }
-
public boolean hasPaymentAccountForCurrency(TradeCurrency tradeCurrency) {
return findFirstPaymentAccountWithCurrency(tradeCurrency) != null;
}
@@ -222,10 +213,10 @@ public void removePaymentAccount(PaymentAccount paymentAccount) {
persist();
}
- public boolean addAcceptedLanguageLocale(String localeCode) {
- final List codes = userPayload.getAcceptedLanguageLocaleCodes();
- if (codes != null && !codes.contains(localeCode)) {
- boolean changed = codes.add(localeCode);
+ public boolean addAcceptedArbitrator(Arbitrator arbitrator) {
+ final List arbitrators = userPayload.getAcceptedArbitrators();
+ if (arbitrators != null && !arbitrators.contains(arbitrator) && !isMyOwnRegisteredArbitrator(arbitrator)) {
+ boolean changed = arbitrators.add(arbitrator);
if (changed)
persist();
return changed;
@@ -234,23 +225,18 @@ public boolean addAcceptedLanguageLocale(String localeCode) {
}
}
- public boolean removeAcceptedLanguageLocale(String languageLocaleCode) {
- boolean changed = userPayload.getAcceptedLanguageLocaleCodes() != null &&
- userPayload.getAcceptedLanguageLocaleCodes().remove(languageLocaleCode);
- if (changed)
- persist();
- return changed;
- }
-
- public boolean addAcceptedArbitrator(Arbitrator arbitrator) {
- final List arbitrators = userPayload.getAcceptedArbitrators();
- if (arbitrators != null && !arbitrators.contains(arbitrator) && !isMyOwnRegisteredArbitrator(arbitrator)) {
- boolean changed = arbitrators.add(arbitrator);
+ public void removeAcceptedArbitrator(Arbitrator arbitrator) {
+ if (userPayload.getAcceptedArbitrators() != null) {
+ boolean changed = userPayload.getAcceptedArbitrators().remove(arbitrator);
if (changed)
persist();
- return changed;
- } else {
- return false;
+ }
+ }
+
+ public void clearAcceptedArbitrators() {
+ if (userPayload.getAcceptedArbitrators() != null) {
+ userPayload.getAcceptedArbitrators().clear();
+ persist();
}
}
@@ -266,33 +252,44 @@ public boolean addAcceptedMediator(Mediator mediator) {
}
}
-
- public void removeAcceptedArbitrator(Arbitrator arbitrator) {
- if (userPayload.getAcceptedArbitrators() != null) {
- boolean changed = userPayload.getAcceptedArbitrators().remove(arbitrator);
+ public void removeAcceptedMediator(Mediator mediator) {
+ if (userPayload.getAcceptedMediators() != null) {
+ boolean changed = userPayload.getAcceptedMediators().remove(mediator);
if (changed)
persist();
}
}
- public void clearAcceptedArbitrators() {
- if (userPayload.getAcceptedArbitrators() != null) {
- userPayload.getAcceptedArbitrators().clear();
+ public void clearAcceptedMediators() {
+ if (userPayload.getAcceptedMediators() != null) {
+ userPayload.getAcceptedMediators().clear();
persist();
}
}
- public void removeAcceptedMediator(Mediator mediator) {
- if (userPayload.getAcceptedMediators() != null) {
- boolean changed = userPayload.getAcceptedMediators().remove(mediator);
+ public boolean addAcceptedRefundAgent(RefundAgent refundAgent) {
+ final List refundAgents = userPayload.getAcceptedRefundAgents();
+ if (refundAgents != null && !refundAgents.contains(refundAgent) && !isMyOwnRegisteredRefundAgent(refundAgent)) {
+ boolean changed = refundAgents.add(refundAgent);
if (changed)
persist();
+ return changed;
+ } else {
+ return false;
}
}
- public void clearAcceptedMediators() {
- if (userPayload.getAcceptedMediators() != null) {
- userPayload.getAcceptedMediators().clear();
+ public void removeAcceptedRefundAgent(RefundAgent refundAgent) {
+ if (userPayload.getAcceptedRefundAgents() != null) {
+ boolean changed = userPayload.getAcceptedRefundAgents().remove(refundAgent);
+ if (changed)
+ persist();
+ }
+ }
+
+ public void clearAcceptedRefundAgents() {
+ if (userPayload.getAcceptedRefundAgents() != null) {
+ userPayload.getAcceptedRefundAgents().clear();
persist();
}
}
@@ -317,6 +314,11 @@ public void setRegisteredMediator(@Nullable Mediator mediator) {
persist();
}
+ public void setRegisteredRefundAgent(@Nullable RefundAgent refundAgent) {
+ userPayload.setRegisteredRefundAgent(refundAgent);
+ persist();
+ }
+
public void setDevelopersFilter(@Nullable Filter developersFilter) {
userPayload.setDevelopersFilter(developersFilter);
persist();
@@ -385,6 +387,12 @@ public ObservableSet getPaymentAccountsAsObservable() {
return paymentAccountsAsObservable;
}
+
+ /**
+ * If this user is an arbitrator it returns the registered arbitrator.
+ *
+ * @return The arbitrator registered for this user
+ */
@Nullable
public Arbitrator getRegisteredArbitrator() {
return userPayload.getRegisteredArbitrator();
@@ -395,20 +403,41 @@ public Mediator getRegisteredMediator() {
return userPayload.getRegisteredMediator();
}
+ @Nullable
+ public RefundAgent getRegisteredRefundAgent() {
+ return userPayload.getRegisteredRefundAgent();
+ }
+
+
//TODO
@Nullable
public List getAcceptedArbitrators() {
return userPayload.getAcceptedArbitrators();
}
+ @Nullable
+ public List getAcceptedMediators() {
+ return userPayload.getAcceptedMediators();
+ }
+
+ @Nullable
+ public List getAcceptedRefundAgents() {
+ return userPayload.getAcceptedRefundAgents();
+ }
+
@Nullable
public List getAcceptedArbitratorAddresses() {
return userPayload.getAcceptedArbitrators() != null ? userPayload.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList()) : null;
}
@Nullable
- public List getAcceptedMediators() {
- return userPayload.getAcceptedMediators();
+ public List