mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 06:55:08 +01:00
Merge branch 'master' into tau3-refactoring/core-seed-nodes-repository
This commit is contained in:
commit
cef131151c
279 changed files with 8663 additions and 4477 deletions
|
@ -4,8 +4,8 @@
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>parent</artifactId>
|
<artifactId>parent</artifactId>
|
||||||
<groupId>io.bisq</groupId>
|
<groupId>io.bisq.exchange</groupId>
|
||||||
<version>0.6.5</version>
|
<version>-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.bisq</groupId>
|
<groupId>io.bisq.exchange</groupId>
|
||||||
<artifactId>consensus</artifactId>
|
<artifactId>consensus</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
|
@ -4,21 +4,26 @@ public class DevEnv {
|
||||||
// Was used for P2P network stress test to adjust several setting for the tests (e.g. use lower btc fees for offers,..)
|
// Was used for P2P network stress test to adjust several setting for the tests (e.g. use lower btc fees for offers,..)
|
||||||
public static final boolean STRESS_TEST_MODE = false;
|
public static final boolean STRESS_TEST_MODE = false;
|
||||||
|
|
||||||
// If that is true all the privileged features which requires a private key to enable it are overridden by a dev ey pair.
|
|
||||||
// The UI got set the private dev key so the developer does not need to do anything and can test those features.
|
// The UI got set the private dev key so the developer does not need to do anything and can test those features.
|
||||||
// Features: Arbitration registration (alt+R at account), Alert/Update (alt+m), private message to a
|
// Features: Arbitration registration (alt+R at account), Alert/Update (alt+m), private message to a
|
||||||
// peer (click user icon and alt+r), filter/block offers by various data like offer ID (cmd + f).
|
// peer (click user icon and alt+r), filter/block offers by various data like offer ID (cmd + f).
|
||||||
// The user can set a program argument to ignore all of those privileged network_messages. They are intended for
|
// The user can set a program argument to ignore all of those privileged network_messages. They are intended for
|
||||||
// emergency cases only (beside update message and arbitrator registration).
|
// emergency cases only (beside update message and arbitrator registration).
|
||||||
public static final boolean USE_DEV_PRIVILEGE_KEYS = false;
|
|
||||||
public static final String DEV_PRIVILEGE_PUB_KEY = "027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee";
|
public static final String DEV_PRIVILEGE_PUB_KEY = "027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee";
|
||||||
public static final String DEV_PRIVILEGE_PRIV_KEY = "6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a";
|
public static final String DEV_PRIVILEGE_PRIV_KEY = "6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a";
|
||||||
|
|
||||||
|
|
||||||
// If set to true we ignore several UI behavior like confirmation popups as well dummy accounts are created and
|
// If set to true we ignore several UI behavior like confirmation popups as well dummy accounts are created and
|
||||||
// offers are filled with default values. Intended to make dev testing faster.
|
// offers are filled with default values. Intended to make dev testing faster.
|
||||||
@SuppressWarnings("PointlessBooleanExpression")
|
@SuppressWarnings("PointlessBooleanExpression")
|
||||||
public static final boolean DEV_MODE = STRESS_TEST_MODE || false;
|
private static boolean devMode = false;
|
||||||
|
|
||||||
|
public static boolean isDevMode() {
|
||||||
|
return devMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDevMode(boolean devMode) {
|
||||||
|
DevEnv.devMode = devMode;
|
||||||
|
}
|
||||||
|
|
||||||
public static final boolean DAO_PHASE2_ACTIVATED = false;
|
public static final boolean DAO_PHASE2_ACTIVATED = false;
|
||||||
public static final boolean DAO_TRADING_ACTIVATED = false;
|
public static final boolean DAO_TRADING_ACTIVATED = false;
|
||||||
|
|
|
@ -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
|
// 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
|
// Therefore all sub versions start again with 1
|
||||||
// We use semantic versioning with major, minor and patch
|
// We use semantic versioning with major, minor and patch
|
||||||
public static final String VERSION = "0.6.5";
|
public static final String VERSION = "0.6.7";
|
||||||
|
|
||||||
public static int getMajorVersion(String version) {
|
public static int getMajorVersion(String version) {
|
||||||
return getSubVersion(version, 0);
|
return getSubVersion(version, 0);
|
||||||
|
@ -120,4 +120,5 @@ public class Version {
|
||||||
|
|
||||||
public static final byte COMPENSATION_REQUEST_VERSION = (byte) 0x01;
|
public static final byte COMPENSATION_REQUEST_VERSION = (byte) 0x01;
|
||||||
public static final byte VOTING_VERSION = (byte) 0x01;
|
public static final byte VOTING_VERSION = (byte) 0x01;
|
||||||
|
public static final byte VOTING_RELEASE_VERSION = (byte) 0x01;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,21 +96,18 @@ public class CurrencyUtil {
|
||||||
result.add(new CryptoCurrency("BCH", "Bitcoin Cash"));
|
result.add(new CryptoCurrency("BCH", "Bitcoin Cash"));
|
||||||
result.add(new CryptoCurrency("BCHC", "Bitcoin Clashic"));
|
result.add(new CryptoCurrency("BCHC", "Bitcoin Clashic"));
|
||||||
result.add(new CryptoCurrency("BTG", "Bitcoin Gold"));
|
result.add(new CryptoCurrency("BTG", "Bitcoin Gold"));
|
||||||
result.add(new CryptoCurrency("DARX", "BitDaric"));
|
|
||||||
result.add(new CryptoCurrency("BURST", "Burstcoin"));
|
result.add(new CryptoCurrency("BURST", "Burstcoin"));
|
||||||
result.add(new CryptoCurrency("GBYTE", "Byte"));
|
result.add(new CryptoCurrency("GBYTE", "Byte"));
|
||||||
result.add(new CryptoCurrency("CAGE", "Cagecoin"));
|
result.add(new CryptoCurrency("CAGE", "Cagecoin"));
|
||||||
result.add(new CryptoCurrency("XCP", "Counterparty"));
|
result.add(new CryptoCurrency("XCP", "Counterparty"));
|
||||||
result.add(new CryptoCurrency("CREA", "Creativecoin"));
|
result.add(new CryptoCurrency("CREA", "Creativecoin"));
|
||||||
result.add(new CryptoCurrency("XCN", "Cryptonite"));
|
result.add(new CryptoCurrency("XCN", "Cryptonite"));
|
||||||
result.add(new CryptoCurrency("DAI", "Dai Stablecoin", true));
|
|
||||||
result.add(new CryptoCurrency("DNET", "DarkNet"));
|
result.add(new CryptoCurrency("DNET", "DarkNet"));
|
||||||
if (!baseCurrencyCode.equals("DASH"))
|
if (!baseCurrencyCode.equals("DASH"))
|
||||||
result.add(new CryptoCurrency("DASH", "Dash"));
|
result.add(new CryptoCurrency("DASH", "Dash"));
|
||||||
result.add(new CryptoCurrency("DCT", "DECENT"));
|
result.add(new CryptoCurrency("DCT", "DECENT"));
|
||||||
result.add(new CryptoCurrency("DCR", "Decred"));
|
result.add(new CryptoCurrency("DCR", "Decred"));
|
||||||
result.add(new CryptoCurrency("ONION", "DeepOnion"));
|
result.add(new CryptoCurrency("ONION", "DeepOnion"));
|
||||||
if (!baseCurrencyCode.equals("DOGE"))
|
|
||||||
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
|
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
|
||||||
result.add(new CryptoCurrency("DMC", "DynamicCoin"));
|
result.add(new CryptoCurrency("DMC", "DynamicCoin"));
|
||||||
result.add(new CryptoCurrency("ELLA", "Ellaism"));
|
result.add(new CryptoCurrency("ELLA", "Ellaism"));
|
||||||
|
@ -133,7 +130,6 @@ public class CurrencyUtil {
|
||||||
result.add(new CryptoCurrency("NMC", "Namecoin"));
|
result.add(new CryptoCurrency("NMC", "Namecoin"));
|
||||||
result.add(new CryptoCurrency("NBT", "NuBits"));
|
result.add(new CryptoCurrency("NBT", "NuBits"));
|
||||||
result.add(new CryptoCurrency("NXT", "Nxt"));
|
result.add(new CryptoCurrency("NXT", "Nxt"));
|
||||||
result.add(new CryptoCurrency("ODN", "Obsidian"));
|
|
||||||
result.add(new CryptoCurrency("888", "OctoCoin"));
|
result.add(new CryptoCurrency("888", "OctoCoin"));
|
||||||
result.add(new CryptoCurrency("PART", "Particl"));
|
result.add(new CryptoCurrency("PART", "Particl"));
|
||||||
result.add(new CryptoCurrency("PASC", "Pascal Coin", true));
|
result.add(new CryptoCurrency("PASC", "Pascal Coin", true));
|
||||||
|
@ -149,7 +145,7 @@ public class CurrencyUtil {
|
||||||
result.add(new CryptoCurrency("SIB", "Sibcoin"));
|
result.add(new CryptoCurrency("SIB", "Sibcoin"));
|
||||||
result.add(new CryptoCurrency("XSPEC", "Spectrecoin"));
|
result.add(new CryptoCurrency("XSPEC", "Spectrecoin"));
|
||||||
result.add(new CryptoCurrency("STEEM", "STEEM"));
|
result.add(new CryptoCurrency("STEEM", "STEEM"));
|
||||||
result.add(new CryptoCurrency("STL", "Stellite"));
|
|
||||||
result.add(new CryptoCurrency("TRC", "Terracoin"));
|
result.add(new CryptoCurrency("TRC", "Terracoin"));
|
||||||
result.add(new CryptoCurrency("MVT", "The Movement", true));
|
result.add(new CryptoCurrency("MVT", "The Movement", true));
|
||||||
|
|
||||||
|
@ -157,10 +153,23 @@ public class CurrencyUtil {
|
||||||
result.add(new CryptoCurrency("CRED", "Verify", true));
|
result.add(new CryptoCurrency("CRED", "Verify", true));
|
||||||
result.add(new CryptoCurrency("WAC", "WACoins"));
|
result.add(new CryptoCurrency("WAC", "WACoins"));
|
||||||
result.add(new CryptoCurrency("WILD", "WILD Token", true));
|
result.add(new CryptoCurrency("WILD", "WILD Token", true));
|
||||||
result.add(new CryptoCurrency("YTN", "Yenten"));
|
|
||||||
result.add(new CryptoCurrency("XZC", "Zcoin"));
|
result.add(new CryptoCurrency("XZC", "Zcoin"));
|
||||||
result.add(new CryptoCurrency("ZEC", "Zcash"));
|
result.add(new CryptoCurrency("ZEC", "Zcash"));
|
||||||
result.add(new CryptoCurrency("ZEN", "ZenCash"));
|
result.add(new CryptoCurrency("ZEN", "ZenCash"));
|
||||||
|
|
||||||
|
// Added 0.6.6
|
||||||
|
result.add(new CryptoCurrency("STL", "Stellite"));
|
||||||
|
result.add(new CryptoCurrency("DAI", "Dai Stablecoin", true));
|
||||||
|
result.add(new CryptoCurrency("YTN", "Yenten"));
|
||||||
|
result.add(new CryptoCurrency("DARX", "BitDaric"));
|
||||||
|
result.add(new CryptoCurrency("ODN", "Obsidian"));
|
||||||
|
result.add(new CryptoCurrency("CDT", "Cassubian Detk"));
|
||||||
|
result.add(new CryptoCurrency("DGM", "DigiMoney"));
|
||||||
|
result.add(new CryptoCurrency("SCS", "SpeedCash"));
|
||||||
|
result.add(new CryptoCurrency("SOS", "SOS Coin", true));
|
||||||
|
result.add(new CryptoCurrency("ACH", "AchieveCoin"));
|
||||||
|
result.add(new CryptoCurrency("VDN", "vDinar"));
|
||||||
|
|
||||||
result.sort(TradeCurrency::compareTo);
|
result.sort(TradeCurrency::compareTo);
|
||||||
|
|
||||||
// Util for printing all altcoins for adding to FAQ page
|
// Util for printing all altcoins for adding to FAQ page
|
||||||
|
@ -185,8 +194,7 @@ public class CurrencyUtil {
|
||||||
if (!baseCurrencyCode.equals("DASH"))
|
if (!baseCurrencyCode.equals("DASH"))
|
||||||
result.add(new CryptoCurrency("DASH", "Dash"));
|
result.add(new CryptoCurrency("DASH", "Dash"));
|
||||||
result.add(new CryptoCurrency("DCR", "Decred"));
|
result.add(new CryptoCurrency("DCR", "Decred"));
|
||||||
if (!baseCurrencyCode.equals("DOGE"))
|
result.add(new CryptoCurrency("ONION", "DeepOnion"));
|
||||||
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
|
|
||||||
result.add(new CryptoCurrency("ETH", "Ether"));
|
result.add(new CryptoCurrency("ETH", "Ether"));
|
||||||
result.add(new CryptoCurrency("ETC", "Ether Classic"));
|
result.add(new CryptoCurrency("ETC", "Ether Classic"));
|
||||||
result.add(new CryptoCurrency("GRC", "Gridcoin"));
|
result.add(new CryptoCurrency("GRC", "Gridcoin"));
|
||||||
|
@ -239,7 +247,72 @@ public class CurrencyUtil {
|
||||||
new FiatCurrency("HKD"),
|
new FiatCurrency("HKD"),
|
||||||
new FiatCurrency("CNY")
|
new FiatCurrency("CNY")
|
||||||
));
|
));
|
||||||
currencies.sort(TradeCurrency::compareTo);
|
currencies.sort(Comparator.comparing(TradeCurrency::getCode));
|
||||||
|
return currencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://support.uphold.com/hc/en-us/articles/202473803-Supported-currencies
|
||||||
|
public static List<TradeCurrency> getAllUpholdCurrencies() {
|
||||||
|
ArrayList<TradeCurrency> currencies = new ArrayList<>(Arrays.asList(
|
||||||
|
new FiatCurrency("USD"),
|
||||||
|
new FiatCurrency("EUR"),
|
||||||
|
new FiatCurrency("GBP"),
|
||||||
|
new FiatCurrency("CNY"),
|
||||||
|
new FiatCurrency("JPY"),
|
||||||
|
new FiatCurrency("CHF"),
|
||||||
|
new FiatCurrency("INR"),
|
||||||
|
new FiatCurrency("MXN"),
|
||||||
|
new FiatCurrency("AUD"),
|
||||||
|
new FiatCurrency("CAD"),
|
||||||
|
new FiatCurrency("HKD"),
|
||||||
|
new FiatCurrency("NZD"),
|
||||||
|
new FiatCurrency("SGD"),
|
||||||
|
new FiatCurrency("KES"),
|
||||||
|
new FiatCurrency("ILS"),
|
||||||
|
new FiatCurrency("DKK"),
|
||||||
|
new FiatCurrency("NOK"),
|
||||||
|
new FiatCurrency("SEK"),
|
||||||
|
new FiatCurrency("PLN"),
|
||||||
|
new FiatCurrency("ARS"),
|
||||||
|
new FiatCurrency("BRL"),
|
||||||
|
new FiatCurrency("AED"),
|
||||||
|
new FiatCurrency("PHP")
|
||||||
|
));
|
||||||
|
|
||||||
|
currencies.sort(Comparator.comparing(TradeCurrency::getCode));
|
||||||
|
return currencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://www.revolut.com/pa/faq#can-i-hold-multiple-currencies
|
||||||
|
public static List<TradeCurrency> getAllRevolutCurrencies() {
|
||||||
|
ArrayList<TradeCurrency> currencies = new ArrayList<>(Arrays.asList(
|
||||||
|
new FiatCurrency("USD"),
|
||||||
|
new FiatCurrency("GBP"),
|
||||||
|
new FiatCurrency("EUR"),
|
||||||
|
new FiatCurrency("PLN"),
|
||||||
|
new FiatCurrency("CHF"),
|
||||||
|
new FiatCurrency("DKK"),
|
||||||
|
new FiatCurrency("NOK"),
|
||||||
|
new FiatCurrency("SEK"),
|
||||||
|
new FiatCurrency("RON"),
|
||||||
|
new FiatCurrency("SGD"),
|
||||||
|
new FiatCurrency("HKD"),
|
||||||
|
new FiatCurrency("AUD"),
|
||||||
|
new FiatCurrency("NZD"),
|
||||||
|
new FiatCurrency("TRY"),
|
||||||
|
new FiatCurrency("ILS"),
|
||||||
|
new FiatCurrency("AED"),
|
||||||
|
new FiatCurrency("CAD"),
|
||||||
|
new FiatCurrency("HUF"),
|
||||||
|
new FiatCurrency("INR"),
|
||||||
|
new FiatCurrency("JPY"),
|
||||||
|
new FiatCurrency("MAD"),
|
||||||
|
new FiatCurrency("QAR"),
|
||||||
|
new FiatCurrency("THB"),
|
||||||
|
new FiatCurrency("ZAR")
|
||||||
|
));
|
||||||
|
|
||||||
|
currencies.sort(Comparator.comparing(TradeCurrency::getCode));
|
||||||
return currencies;
|
return currencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,8 @@ public class Res {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
GlobalSettings.localeProperty().addListener((observable, oldValue, newValue) -> {
|
GlobalSettings.localeProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if ("en".equalsIgnoreCase(newValue.getLanguage()))
|
||||||
|
newValue = Locale.ROOT;
|
||||||
resourceBundle = ResourceBundle.getBundle("i18n.displayStrings", newValue, new UTF8Control());
|
resourceBundle = ResourceBundle.getBundle("i18n.displayStrings", newValue, new UTF8Control());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -101,7 +103,7 @@ public class Res {
|
||||||
.replace("bitcoin", baseCurrencyNameLowerCase);
|
.replace("bitcoin", baseCurrencyNameLowerCase);
|
||||||
} catch (MissingResourceException e) {
|
} catch (MissingResourceException e) {
|
||||||
log.warn("Missing resource for key: " + key);
|
log.warn("Missing resource for key: " + key);
|
||||||
if (DevEnv.DEV_MODE)
|
if (DevEnv.isDevMode())
|
||||||
throw new RuntimeException("Missing resource for key: " + key);
|
throw new RuntimeException("Missing resource for key: " + key);
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
|
|
|
@ -8,8 +8,6 @@ package io.bisq.protobuffer;
|
||||||
option java_package = "io.bisq.generated.protobuffer";
|
option java_package = "io.bisq.generated.protobuffer";
|
||||||
option java_outer_classname = "PB";
|
option java_outer_classname = "PB";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Network messages
|
// Network messages
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -60,7 +58,6 @@ message NetworkEnvelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Implementations of NetworkEnvelope
|
// Implementations of NetworkEnvelope
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -87,7 +84,6 @@ message GetUpdatedDataRequest {
|
||||||
repeated bytes excluded_keys = 3;
|
repeated bytes excluded_keys = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// peers
|
// peers
|
||||||
|
|
||||||
message GetPeersRequest {
|
message GetPeersRequest {
|
||||||
|
@ -112,7 +108,6 @@ message Pong {
|
||||||
int32 request_nonce = 1;
|
int32 request_nonce = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// offer
|
// offer
|
||||||
|
|
||||||
message OfferAvailabilityRequest {
|
message OfferAvailabilityRequest {
|
||||||
|
@ -135,7 +130,6 @@ message RefreshOfferMessage {
|
||||||
int32 sequence_number = 4;
|
int32 sequence_number = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// storage
|
// storage
|
||||||
|
|
||||||
message AddDataMessage {
|
message AddDataMessage {
|
||||||
|
@ -154,7 +148,6 @@ message AddPersistableNetworkPayloadMessage {
|
||||||
PersistableNetworkPayload payload = 1;
|
PersistableNetworkPayload payload = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
|
|
||||||
message CloseConnectionMessage {
|
message CloseConnectionMessage {
|
||||||
|
@ -168,7 +161,6 @@ message PrefixedSealedAndSignedMessage {
|
||||||
string uid = 4;
|
string uid = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// trade
|
// trade
|
||||||
|
|
||||||
message PayDepositRequest {
|
message PayDepositRequest {
|
||||||
|
@ -292,7 +284,6 @@ message PrivateNotificationMessage {
|
||||||
PrivateNotificationPayload private_notification_payload = 3;
|
PrivateNotificationPayload private_notification_payload = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// DAO
|
// DAO
|
||||||
|
|
||||||
message GetBsqBlocksRequest {
|
message GetBsqBlocksRequest {
|
||||||
|
@ -309,7 +300,6 @@ message NewBsqBlockBroadcastMessage {
|
||||||
BsqBlock bsq_block = 1;
|
BsqBlock bsq_block = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Payload
|
// Payload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -339,7 +329,6 @@ message SealedAndSigned {
|
||||||
bytes sig_public_key_bytes = 4;
|
bytes sig_public_key_bytes = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// storage
|
// storage
|
||||||
|
|
||||||
message StoragePayload {
|
message StoragePayload {
|
||||||
|
@ -374,7 +363,6 @@ message ProtectedStorageEntry {
|
||||||
int64 creation_time_stamp = 5;
|
int64 creation_time_stamp = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// mailbox
|
// mailbox
|
||||||
|
|
||||||
message StorageEntryWrapper {
|
message StorageEntryWrapper {
|
||||||
|
@ -394,7 +382,6 @@ message DataAndSeqNrPair {
|
||||||
int32 sequence_number = 2;
|
int32 sequence_number = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
|
|
||||||
message PrivateNotificationPayload {
|
message PrivateNotificationPayload {
|
||||||
|
@ -409,7 +396,6 @@ message PaymentAccountFilter {
|
||||||
string value = 3;
|
string value = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// DAO
|
// DAO
|
||||||
|
|
||||||
enum ScriptType {
|
enum ScriptType {
|
||||||
|
@ -441,7 +427,7 @@ message TxInput {
|
||||||
message SpentInfo {
|
message SpentInfo {
|
||||||
int64 block_height = 1;
|
int64 block_height = 1;
|
||||||
string tx_id = 2;
|
string tx_id = 2;
|
||||||
int32 input_index= 3;
|
int32 input_index = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TxOutputType {
|
enum TxOutputType {
|
||||||
|
@ -449,10 +435,10 @@ enum TxOutputType {
|
||||||
UNDEFINED = 1;
|
UNDEFINED = 1;
|
||||||
BSQ_OUTPUT = 2;
|
BSQ_OUTPUT = 2;
|
||||||
BTC_OUTPUT = 3;
|
BTC_OUTPUT = 3;
|
||||||
OP_RETURN_OUTPUT = 4;
|
COMPENSATION_REQUEST_OP_RETURN_OUTPUT = 4;
|
||||||
COMPENSATION_REQUEST_OP_RETURN_OUTPUT = 5;
|
COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT = 5;
|
||||||
COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT = 6;
|
VOTE_OP_RETURN_OUTPUT = 6;
|
||||||
VOTING_OP_RETURN_OUTPUT = 7;
|
VOTE_REVEAL_OP_RETURN_OUTPUT = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TxOutput {
|
message TxOutput {
|
||||||
|
@ -462,21 +448,13 @@ message TxOutput {
|
||||||
PubKeyScript pub_key_script = 4;
|
PubKeyScript pub_key_script = 4;
|
||||||
string address = 5;
|
string address = 5;
|
||||||
bytes op_return_data = 6;
|
bytes op_return_data = 6;
|
||||||
int32 block_height= 7;
|
int32 block_height = 7;
|
||||||
bool is_unspent = 8;
|
bool is_unspent = 8;
|
||||||
bool is_verified = 9;
|
bool is_verified = 9;
|
||||||
TxOutputType tx_output_type = 10;
|
TxOutputType tx_output_type = 10;
|
||||||
SpentInfo spent_info = 11;
|
SpentInfo spent_info = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TxVo {
|
|
||||||
string tx_version = 1;
|
|
||||||
string id = 2;
|
|
||||||
int32 block_height= 3;
|
|
||||||
string block_hash = 4;
|
|
||||||
int64 time = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TxType {
|
enum TxType {
|
||||||
PB_ERROR_TX_TYPE = 0;
|
PB_ERROR_TX_TYPE = 0;
|
||||||
UNDEFINED_TX_TYPE = 1;
|
UNDEFINED_TX_TYPE = 1;
|
||||||
|
@ -487,17 +465,22 @@ enum TxType {
|
||||||
PAY_TRADE_FEE = 6;
|
PAY_TRADE_FEE = 6;
|
||||||
COMPENSATION_REQUEST = 7;
|
COMPENSATION_REQUEST = 7;
|
||||||
VOTE = 8;
|
VOTE = 8;
|
||||||
ISSUANCE = 9;
|
VOTE_REVEAL = 9;
|
||||||
LOCK_UP = 10;
|
ISSUANCE = 10;
|
||||||
UN_LOCK = 11;
|
LOCK_UP = 11;
|
||||||
|
UN_LOCK = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Tx {
|
message Tx {
|
||||||
TxVo tx_vo = 1;
|
string tx_version = 1;
|
||||||
repeated TxInput inputs = 2;
|
string id = 2;
|
||||||
repeated TxOutput outputs = 3;
|
int32 block_height = 3;
|
||||||
int64 burnt_fee = 4;
|
string block_hash = 4;
|
||||||
TxType tx_type = 5;
|
int64 time = 5;
|
||||||
|
repeated TxInput inputs = 6;
|
||||||
|
repeated TxOutput outputs = 7;
|
||||||
|
int64 burnt_fee = 8;
|
||||||
|
TxType tx_type = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BsqBlock {
|
message BsqBlock {
|
||||||
|
@ -512,8 +495,6 @@ message TxIdIndexTuple {
|
||||||
int32 index = 2;
|
int32 index = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Storage payload
|
// Storage payload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -641,7 +622,7 @@ message OfferPayload {
|
||||||
string maker_payment_account_id = 16;
|
string maker_payment_account_id = 16;
|
||||||
string offer_fee_payment_tx_id = 17;
|
string offer_fee_payment_tx_id = 17;
|
||||||
string country_code = 18;
|
string country_code = 18;
|
||||||
repeated string accepted_country_codes =19;
|
repeated string accepted_country_codes = 19;
|
||||||
string bank_id = 20;
|
string bank_id = 20;
|
||||||
repeated string accepted_bank_ids = 21;
|
repeated string accepted_bank_ids = 21;
|
||||||
string version_nr = 22;
|
string version_nr = 22;
|
||||||
|
@ -668,7 +649,6 @@ message AccountAgeWitness {
|
||||||
int64 date = 2;
|
int64 date = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Dispute payload
|
// Dispute payload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -740,7 +720,6 @@ message DisputeResult {
|
||||||
bool is_loser_publisher = 15;
|
bool is_loser_publisher = 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Trade payload
|
// Trade payload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -785,7 +764,6 @@ enum AvailabilityResult {
|
||||||
USER_IGNORED = 8;
|
USER_IGNORED = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// PaymentAccount payload
|
// PaymentAccount payload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -807,6 +785,12 @@ message PaymentAccountPayload {
|
||||||
PerfectMoneyAccountPayload perfect_money_account_payload = 12;
|
PerfectMoneyAccountPayload perfect_money_account_payload = 12;
|
||||||
SwishAccountPayload swish_account_payload = 13;
|
SwishAccountPayload swish_account_payload = 13;
|
||||||
USPostalMoneyOrderAccountPayload u_s_postal_money_order_account_payload = 14;
|
USPostalMoneyOrderAccountPayload u_s_postal_money_order_account_payload = 14;
|
||||||
|
UpholdAccountPayload uphold_account_payload = 16;
|
||||||
|
CashAppAccountPayload cash_app_account_payload = 17;
|
||||||
|
MoneyBeamAccountPayload money_beam_account_payload = 18;
|
||||||
|
VenmoAccountPayload venmo_account_payload = 19;
|
||||||
|
PopmoneyAccountPayload popmoney_account_payload = 20;
|
||||||
|
RevolutAccountPayload revolut_account_payload = 21;
|
||||||
}
|
}
|
||||||
map<string, string> exclude_from_json_data = 15;
|
map<string, string> exclude_from_json_data = 15;
|
||||||
}
|
}
|
||||||
|
@ -917,6 +901,32 @@ message OKPayAccountPayload {
|
||||||
string account_nr = 1;
|
string account_nr = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UpholdAccountPayload {
|
||||||
|
string account_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CashAppAccountPayload {
|
||||||
|
string cash_tag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MoneyBeamAccountPayload {
|
||||||
|
string account_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VenmoAccountPayload {
|
||||||
|
string venmo_user_name = 1;
|
||||||
|
string holder_name = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PopmoneyAccountPayload {
|
||||||
|
string account_id = 1;
|
||||||
|
string holder_name = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RevolutAccountPayload {
|
||||||
|
string account_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message PerfectMoneyAccountPayload {
|
message PerfectMoneyAccountPayload {
|
||||||
string account_nr = 1;
|
string account_nr = 1;
|
||||||
}
|
}
|
||||||
|
@ -931,7 +941,6 @@ message USPostalMoneyOrderAccountPayload {
|
||||||
string holder_name = 2;
|
string holder_name = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// PersistableEnvelope
|
// PersistableEnvelope
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -962,7 +971,6 @@ message PersistableEnvelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Collections
|
// Collections
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1031,7 +1039,6 @@ message CompensationRequestList {
|
||||||
repeated CompensationRequest compensation_request = 1;
|
repeated CompensationRequest compensation_request = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Offer/Trade
|
// Offer/Trade
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1066,6 +1073,7 @@ message OpenOffer {
|
||||||
RESERVED = 2;
|
RESERVED = 2;
|
||||||
CLOSED = 3;
|
CLOSED = 3;
|
||||||
CANCELED = 4;
|
CANCELED = 4;
|
||||||
|
DEACTIVATED = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
Offer offer = 1;
|
Offer offer = 1;
|
||||||
|
@ -1076,7 +1084,7 @@ message Tradable {
|
||||||
oneof message {
|
oneof message {
|
||||||
OpenOffer open_offer = 1;
|
OpenOffer open_offer = 1;
|
||||||
BuyerAsMakerTrade buyer_as_maker_trade = 2;
|
BuyerAsMakerTrade buyer_as_maker_trade = 2;
|
||||||
BuyerAsTakerTrade buyer_as_taker_trade= 3;
|
BuyerAsTakerTrade buyer_as_taker_trade = 3;
|
||||||
SellerAsMakerTrade seller_as_maker_trade = 4;
|
SellerAsMakerTrade seller_as_maker_trade = 4;
|
||||||
SellerAsTakerTrade seller_as_taker_trade = 5;
|
SellerAsTakerTrade seller_as_taker_trade = 5;
|
||||||
}
|
}
|
||||||
|
@ -1230,8 +1238,6 @@ message AccountAgeWitnessMap {
|
||||||
map<string, AccountAgeWitness> account_age_witness_map = 1;
|
map<string, AccountAgeWitness> account_age_witness_map = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Dispute
|
// Dispute
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1240,7 +1246,6 @@ message DisputeList {
|
||||||
repeated Dispute dispute = 1;
|
repeated Dispute dispute = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Preferences
|
// Preferences
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1286,7 +1291,6 @@ message PreferencesPayload {
|
||||||
int32 bitcoin_nodes_option_ordinal = 38;
|
int32 bitcoin_nodes_option_ordinal = 38;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// UserPayload
|
// UserPayload
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1305,7 +1309,6 @@ message UserPayload {
|
||||||
Mediator registered_mediator = 11;
|
Mediator registered_mediator = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// DAO
|
// DAO
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1391,8 +1394,6 @@ message CompensationRequestVoteItemCollection {
|
||||||
repeated CompensationRequestVoteItem compensation_request_vote_item = 1;
|
repeated CompensationRequestVoteItem compensation_request_vote_item = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Misc
|
// Misc
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1453,8 +1454,6 @@ message Region {
|
||||||
string name = 2;
|
string name = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Mock
|
// Mock
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -49,8 +49,8 @@ shared.sell=sell
|
||||||
shared.buying=buying
|
shared.buying=buying
|
||||||
shared.selling=selling
|
shared.selling=selling
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offer=offer
|
shared.oneOffer=offer
|
||||||
shared.offers=offers
|
shared.multipleOffers=offers
|
||||||
shared.Offer=Offer
|
shared.Offer=Offer
|
||||||
shared.openOffers=open offers
|
shared.openOffers=open offers
|
||||||
shared.trade=trade
|
shared.trade=trade
|
||||||
|
@ -84,6 +84,8 @@ shared.bankName=Bank name
|
||||||
shared.acceptedBanks=Accepted banks
|
shared.acceptedBanks=Accepted banks
|
||||||
shared.amountMinMax=Amount (min - max)
|
shared.amountMinMax=Amount (min - max)
|
||||||
shared.remove=Remove
|
shared.remove=Remove
|
||||||
|
shared.deactivate=Deactivate
|
||||||
|
shared.activate=Activate
|
||||||
shared.goTo=Go to {0}
|
shared.goTo=Go to {0}
|
||||||
shared.BTCMinMax=BTC (min - max)
|
shared.BTCMinMax=BTC (min - max)
|
||||||
shared.removeOffer=Remove offer
|
shared.removeOffer=Remove offer
|
||||||
|
@ -98,6 +100,8 @@ shared.fundFromSavingsWalletButton=Transfer funds from Bisq wallet
|
||||||
shared.fundFromExternalWalletButton=Open your external wallet for funding
|
shared.fundFromExternalWalletButton=Open your external wallet for funding
|
||||||
shared.openDefaultWalletFailed=Opening a default bitcoin wallet application has failed. Perhaps you don't have one installed?
|
shared.openDefaultWalletFailed=Opening a default bitcoin wallet application has failed. Perhaps you don't have one installed?
|
||||||
shared.distanceInPercent=Distance in % from market price
|
shared.distanceInPercent=Distance in % from market price
|
||||||
|
shared.belowInPercent=Below % from market price
|
||||||
|
shared.aboveInPercent=Above % from market price
|
||||||
shared.enterPercentageValue=Enter % value
|
shared.enterPercentageValue=Enter % value
|
||||||
shared.OR=OR
|
shared.OR=OR
|
||||||
shared.notEnoughFunds=You don''t have enough funds in your Bisq wallet.\nYou need {0} but you have only {1} in your Bisq wallet.\n\nPlease fund that trade from an external Bitcoin wallet or fund your Bisq wallet at \"Funds/Receive funds\".
|
shared.notEnoughFunds=You don''t have enough funds in your Bisq wallet.\nYou need {0} but you have only {1} in your Bisq wallet.\n\nPlease fund that trade from an external Bitcoin wallet or fund your Bisq wallet at \"Funds/Receive funds\".
|
||||||
|
@ -262,10 +266,10 @@ market.tabs.trades=Trades
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Offer book for {0}
|
market.offerBook.chart.title=Offer book for {0}
|
||||||
market.offerBook.leftButtonAltcoin=I want to buy {0} (sell {1})
|
market.offerBook.buyAltcoin=Buy {0} (sell {1})
|
||||||
market.offerBook.rightButtonAltcoin=I want to sell {0} (buy {1})
|
market.offerBook.sellAltcoin=Sell {0} (buy {1})
|
||||||
market.offerBook.leftButtonFiat=I want to buy {0} with {1}
|
market.offerBook.buyWithFiat=Buy {0}
|
||||||
market.offerBook.rightButtonFiat=I want to sell {0} for {1}
|
market.offerBook.sellWithFiat=Sell {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Sell {0} to
|
market.offerBook.sellOffersHeaderLabel=Sell {0} to
|
||||||
market.offerBook.buyOffersHeaderLabel=Buy {0} from
|
market.offerBook.buyOffersHeaderLabel=Buy {0} from
|
||||||
market.offerBook.buy=I want to buy bitcoin
|
market.offerBook.buy=I want to buy bitcoin
|
||||||
|
@ -316,6 +320,8 @@ offerbook.yesCreateOffer=Yes, create offer
|
||||||
offerbook.setupNewAccount=Set up a new trading account
|
offerbook.setupNewAccount=Set up a new trading account
|
||||||
offerbook.removeOffer.success=Remove offer was successful.
|
offerbook.removeOffer.success=Remove offer was successful.
|
||||||
offerbook.removeOffer.failed=Remove offer failed:\n{0}
|
offerbook.removeOffer.failed=Remove offer failed:\n{0}
|
||||||
|
offerbook.deactivateOffer.failed=Deactivating of offer failed:\n{0}
|
||||||
|
offerbook.activateOffer.failed=Publishing of offer failed:\n{0}
|
||||||
offerbook.withdrawFundsHint=You can withdraw the funds you paid in from the {0} screen.
|
offerbook.withdrawFundsHint=You can withdraw the funds you paid in from the {0} screen.
|
||||||
|
|
||||||
offerbook.warning.noTradingAccountForCurrency.headline=No trading account for selected currency
|
offerbook.warning.noTradingAccountForCurrency.headline=No trading account for selected currency
|
||||||
|
@ -474,7 +480,7 @@ portfolio.pending.step2_buyer.refTextWarn=DO NOT use any additional notice in th
|
||||||
portfolio.pending.step2_buyer.accountDetails=Here are the trade account details of the BTC seller:\n
|
portfolio.pending.step2_buyer.accountDetails=Here are the trade account details of the BTC seller:\n
|
||||||
portfolio.pending.step2_buyer.tradeId=Please don't forget to add the trade ID
|
portfolio.pending.step2_buyer.tradeId=Please don't forget to add the trade ID
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
portfolio.pending.step2_buyer.assign= as \"reason for payment\" so the receiver can assign your payment to this trade.\n\n
|
portfolio.pending.step2_buyer.assign=as \"reason for payment\" so the receiver can assign your payment to this trade.\n\n
|
||||||
portfolio.pending.step2_buyer.fees=If your bank charges fees you have to cover those fees.
|
portfolio.pending.step2_buyer.fees=If your bank charges fees you have to cover those fees.
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
portfolio.pending.step2_buyer.altcoin=Please transfer from your external {0} wallet\n{1} to the BTC seller.\n\n
|
portfolio.pending.step2_buyer.altcoin=Please transfer from your external {0} wallet\n{1} to the BTC seller.\n\n
|
||||||
|
@ -591,7 +597,7 @@ portfolio.pending.tradeInformation=Trade information
|
||||||
portfolio.pending.remainingTime=Remaining time
|
portfolio.pending.remainingTime=Remaining time
|
||||||
portfolio.pending.remainingTimeDetail={0} (until {1})
|
portfolio.pending.remainingTimeDetail={0} (until {1})
|
||||||
portfolio.pending.tradePeriodInfo=After the first blockchain confirmation, the trade period starts. Based on the payment method used, a different maximum allowed trade period is applied.
|
portfolio.pending.tradePeriodInfo=After the first blockchain confirmation, the trade period starts. Based on the payment method used, a different maximum allowed trade period is applied.
|
||||||
portfolio.pending.tradePeriodWarning=If the period is exceeded both trades can open a dispute.
|
portfolio.pending.tradePeriodWarning=If the period is exceeded both traders can open a dispute.
|
||||||
portfolio.pending.tradeNotCompleted=Trade not completed in time (until {0})
|
portfolio.pending.tradeNotCompleted=Trade not completed in time (until {0})
|
||||||
portfolio.pending.tradeProcess=Trade process
|
portfolio.pending.tradeProcess=Trade process
|
||||||
portfolio.pending.openAgainDispute.msg=If you are not sure that the message to the arbitrator arrived (e.g. if you did not got a response after 1 day) feel free to open a dispute again.
|
portfolio.pending.openAgainDispute.msg=If you are not sure that the message to the arbitrator arrived (e.g. if you did not got a response after 1 day) feel free to open a dispute again.
|
||||||
|
@ -653,7 +659,7 @@ funds.withdrawal.noFundsAvailable=No funds are available for withdrawal
|
||||||
funds.withdrawal.confirmWithdrawalRequest=Confirm withdrawal request
|
funds.withdrawal.confirmWithdrawalRequest=Confirm withdrawal request
|
||||||
funds.withdrawal.withdrawMultipleAddresses=Withdraw from multiple addresses ({0})
|
funds.withdrawal.withdrawMultipleAddresses=Withdraw from multiple addresses ({0})
|
||||||
funds.withdrawal.withdrawMultipleAddresses.tooltip=Withdraw from multiple addresses:\n{0}
|
funds.withdrawal.withdrawMultipleAddresses.tooltip=Withdraw from multiple addresses:\n{0}
|
||||||
funds.withdrawal.notEnoughFunds=You don't have enough fund in your wallet.
|
funds.withdrawal.notEnoughFunds=You don't have enough funds in your wallet.
|
||||||
funds.withdrawal.selectAddress=Select a source address from the table
|
funds.withdrawal.selectAddress=Select a source address from the table
|
||||||
funds.withdrawal.setAmount=Set the amount to withdraw
|
funds.withdrawal.setAmount=Set the amount to withdraw
|
||||||
funds.withdrawal.fillDestAddress=Fill in your destination address
|
funds.withdrawal.fillDestAddress=Fill in your destination address
|
||||||
|
@ -765,10 +771,10 @@ setting.preferences.general=General preferences
|
||||||
setting.preferences.explorer=Bitcoin block explorer:
|
setting.preferences.explorer=Bitcoin block explorer:
|
||||||
setting.preferences.deviation=Max. deviation from market price:
|
setting.preferences.deviation=Max. deviation from market price:
|
||||||
setting.preferences.autoSelectArbitrators=Auto select arbitrators:
|
setting.preferences.autoSelectArbitrators=Auto select arbitrators:
|
||||||
setting.preferences.deviationToLarge=Values higher than 30 % are not allowed.
|
setting.preferences.deviationToLarge=Values higher than {0}% are not allowed.
|
||||||
setting.preferences.txFee=Withdrawal transaction fee (satoshi/byte):
|
setting.preferences.txFee=Withdrawal transaction fee (satoshi/byte):
|
||||||
setting.preferences.useCustomValue=Use custom value
|
setting.preferences.useCustomValue=Use custom value
|
||||||
setting.preferences.txFeeMin=Transaction fee must be at least 5 satoshi/byte
|
setting.preferences.txFeeMin=Transaction fee must be at least {0} satoshi/byte
|
||||||
setting.preferences.txFeeTooLarge=Your input is above any reasonable value (>5000 satoshi/byte). Transaction fee is usually in the range of 50-400 satoshi/byte.
|
setting.preferences.txFeeTooLarge=Your input is above any reasonable value (>5000 satoshi/byte). Transaction fee is usually in the range of 50-400 satoshi/byte.
|
||||||
setting.preferences.ignorePeers=Ignore peers with onion address (comma sep.):
|
setting.preferences.ignorePeers=Ignore peers with onion address (comma sep.):
|
||||||
setting.preferences.currenciesInList=Currencies in market price feed list
|
setting.preferences.currenciesInList=Currencies in market price feed list
|
||||||
|
@ -1010,9 +1016,9 @@ dao.phase.short.BREAK2=
|
||||||
dao.phase.short.VOTE_CONFIRMATION=Result
|
dao.phase.short.VOTE_CONFIRMATION=Result
|
||||||
dao.phase.short.BREAK3=
|
dao.phase.short.BREAK3=
|
||||||
|
|
||||||
|
dao.compensation.selectedRequest=Selected compensation request
|
||||||
|
|
||||||
dao.compensation.active.header=Active compensation request
|
dao.compensation.active.header=Active compensation request
|
||||||
dao.compensation.active.selectedRequest=Selected compensation request
|
|
||||||
dao.compensation.active.notOpenAnymore=This compensation request is not open anymore for funding. Please wait until the next funding period starts.
|
dao.compensation.active.notOpenAnymore=This compensation request is not open anymore for funding. Please wait until the next funding period starts.
|
||||||
dao.compensation.active.remove=Remove compensation request
|
dao.compensation.active.remove=Remove compensation request
|
||||||
dao.compensation.active.remove.failed=Could not remove compensation request.
|
dao.compensation.active.remove.failed=Could not remove compensation request.
|
||||||
|
@ -1020,6 +1026,8 @@ dao.compensation.active.vote=Vote on compensation request
|
||||||
dao.compensation.active.fund=Fund compensation request
|
dao.compensation.active.fund=Fund compensation request
|
||||||
dao.compensation.active.successfullyFunded=Compensation request successfully funded.
|
dao.compensation.active.successfullyFunded=Compensation request successfully funded.
|
||||||
|
|
||||||
|
dao.compensation.past.header=Past compensation request
|
||||||
|
|
||||||
dao.compensation.create.createNew=Create new compensation request
|
dao.compensation.create.createNew=Create new compensation request
|
||||||
dao.compensation.create.create.button=Create compensation request
|
dao.compensation.create.create.button=Create compensation request
|
||||||
dao.compensation.create.confirm=Confirm compensation request
|
dao.compensation.create.confirm=Confirm compensation request
|
||||||
|
@ -1032,6 +1040,7 @@ dao.compensation.display.link=Link to detail info:
|
||||||
dao.compensation.display.link.prompt=Provide a link to an issue at \"https://github.com/bisq-network/compensation/issues/\" or the Bisq Forum.
|
dao.compensation.display.link.prompt=Provide a link to an issue at \"https://github.com/bisq-network/compensation/issues/\" or the Bisq Forum.
|
||||||
dao.compensation.display.requestedBsq=Requested amount in BSQ:
|
dao.compensation.display.requestedBsq=Requested amount in BSQ:
|
||||||
dao.compensation.display.bsqAddress=BSQ address:
|
dao.compensation.display.bsqAddress=BSQ address:
|
||||||
|
dao.compensation.display.txId=BTC Transaction ID:
|
||||||
|
|
||||||
dao.voting.item.title=Compensation request
|
dao.voting.item.title=Compensation request
|
||||||
dao.voting.item.stage.title=Compensation request with ID: {0}
|
dao.voting.item.stage.title=Compensation request with ID: {0}
|
||||||
|
@ -1102,7 +1111,6 @@ dao.compensation.create.confirm.info=Requested amount: {0}\n\
|
||||||
dao.compensation.create.missingFunds=You don''t have sufficient funds for creating the compensation request.\n\
|
dao.compensation.create.missingFunds=You don''t have sufficient funds for creating the compensation request.\n\
|
||||||
Missing: {0}
|
Missing: {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Windows
|
# Windows
|
||||||
####################################################################
|
####################################################################
|
||||||
|
@ -1560,14 +1568,6 @@ LTC_MAINNET=Litecoin Mainnet
|
||||||
LTC_TESTNET=Litecoin Testnet
|
LTC_TESTNET=Litecoin Testnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Litecoin Regtest
|
LTC_REGTEST=Litecoin Regtest
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Dogecoin Mainnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Dogecoin Testnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Dogecoin Regtest
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=DASH Mainnet
|
DASH_MAINNET=DASH Mainnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1629,6 +1629,7 @@ seed.restore.error=An error occurred when restoring the wallets with seed words.
|
||||||
# Payment methods
|
# Payment methods
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
|
payment.account=Account
|
||||||
payment.account.no=Account no.:
|
payment.account.no=Account no.:
|
||||||
payment.account.name=Account name:
|
payment.account.name=Account name:
|
||||||
payment.account.owner=Account owner full name
|
payment.account.owner=Account owner full name
|
||||||
|
@ -1648,9 +1649,6 @@ payment.restore.default=No, restore default currency
|
||||||
payment.email=Email:
|
payment.email=Email:
|
||||||
payment.country=Country:
|
payment.country=Country:
|
||||||
payment.extras=Extra requirements:
|
payment.extras=Extra requirements:
|
||||||
payment.us.info=Bank transfer with WIRE or ACH is not supported for the US because WIRE is too expensive and ACH has a high chargeback risk.\n\n\
|
|
||||||
Please use one of the available payment methods for the US like: \
|
|
||||||
\"Zelle (ClearXchange)\", \"Chase QuickPay\", \"US Postal Money Order\" or \"Cash Deposit\".
|
|
||||||
payment.email.mobile=Email or mobile no.:
|
payment.email.mobile=Email or mobile no.:
|
||||||
payment.altcoin.address=Altcoin address:
|
payment.altcoin.address=Altcoin address:
|
||||||
payment.altcoin=Altcoin:
|
payment.altcoin=Altcoin:
|
||||||
|
@ -1658,7 +1656,13 @@ payment.select.altcoin=Select or search altcoin
|
||||||
payment.secret=Secret question:
|
payment.secret=Secret question:
|
||||||
payment.answer=Answer:
|
payment.answer=Answer:
|
||||||
payment.wallet=Wallet ID:
|
payment.wallet=Wallet ID:
|
||||||
payment.supported.okpay=Supported currencies:
|
payment.uphold.accountId=Username or email or phone no.:
|
||||||
|
payment.cashApp.cashTag=$Cashtag:
|
||||||
|
payment.moneyBeam.accountId=Email or phone no.:
|
||||||
|
payment.venmo.venmoUserName=Venmo username:
|
||||||
|
payment.popmoney.accountId=Email or phone no.:
|
||||||
|
payment.revolut.accountId=Email or phone no.:
|
||||||
|
payment.supportedCurrencies=Supported currencies:
|
||||||
payment.limitations=Limitations:
|
payment.limitations=Limitations:
|
||||||
payment.salt=Salt for account age verification:
|
payment.salt=Salt for account age verification:
|
||||||
payment.error.noHexSalt=The salt need to be in HEX format.\n\
|
payment.error.noHexSalt=The salt need to be in HEX format.\n\
|
||||||
|
@ -1703,6 +1707,7 @@ before starting a trade or creating an offer.\n\n\
|
||||||
\t● FirstBank Person to Person Transfers\n\
|
\t● FirstBank Person to Person Transfers\n\
|
||||||
\t● Frost Send Money\n\
|
\t● Frost Send Money\n\
|
||||||
\t● U.S. Bank Send Money\n\
|
\t● U.S. Bank Send Money\n\
|
||||||
|
\t● TD Bank\n\
|
||||||
\t● Wells Fargo SurePay\n\n\
|
\t● Wells Fargo SurePay\n\n\
|
||||||
3. You need to be sure to not exceed the daily or monthly Zelle (ClearXchange) transfer limits.\n\n\
|
3. You need to be sure to not exceed the daily or monthly Zelle (ClearXchange) transfer limits.\n\n\
|
||||||
Please use Zelle (ClearXchange) only if you fulfill all those requirements, \
|
Please use Zelle (ClearXchange) only if you fulfill all those requirements, \
|
||||||
|
@ -1715,6 +1720,20 @@ to get in contact with the {1} buyer by using the provided email address or mobi
|
||||||
|
|
||||||
payment.westernUnion.info=When using Western Union the BTC buyer has to send the MTCN (tracking number) and a photo of the receipt by email to the BTC seller. \
|
payment.westernUnion.info=When using Western Union the BTC buyer has to send the MTCN (tracking number) and a photo of the receipt by email to the BTC seller. \
|
||||||
The receipt must clearly show the seller''s full name, city, country and the amount. The buyer will get displayed the seller''s email in the trade process.
|
The receipt must clearly show the seller''s full name, city, country and the amount. The buyer will get displayed the seller''s email in the trade process.
|
||||||
|
payment.limits.info=Please be aware that all bank transfers carry a certain amount of chargeback risk.\n\
|
||||||
|
\n\
|
||||||
|
To mitigate this risk, Bisq sets per-trade limits based on two factors:\n\
|
||||||
|
\n\
|
||||||
|
1. The estimated level of chargeback risk for the payment method used\n\
|
||||||
|
2. The age of your account for that payment method\n\
|
||||||
|
\n\
|
||||||
|
The account you are creating now is new and its age is zero. As your account grows in age over a two-month period, your per-trade limits will grow along with it:\n\
|
||||||
|
\n\
|
||||||
|
● During the 1st month, your per-trade limit will be {0}\n\
|
||||||
|
● During the 2nd month, your per-trade limit will be {1}\n\
|
||||||
|
● After the 2nd month, your per-trade limit will be {2}\n\
|
||||||
|
\n\
|
||||||
|
Please note that there are no limits on the total number of times you can trade.
|
||||||
|
|
||||||
# We use constants from the code so we do not use our normal naming convention
|
# We use constants from the code so we do not use our normal naming convention
|
||||||
# dynamic values are not recognized by IntelliJ
|
# dynamic values are not recognized by IntelliJ
|
||||||
|
@ -1741,6 +1760,12 @@ WESTERN_UNION_SHORT=Western Union
|
||||||
|
|
||||||
# Do not translate brand names
|
# Do not translate brand names
|
||||||
OK_PAY=OKPay
|
OK_PAY=OKPay
|
||||||
|
UPHOLD=Uphold
|
||||||
|
CASH_APP=Cash App
|
||||||
|
MONEY_BEAM=MoneyBeam (N26)
|
||||||
|
VENMO=Venmo
|
||||||
|
POPMONEY=Popmoney
|
||||||
|
REVOLUT=Revolut
|
||||||
PERFECT_MONEY=Perfect Money
|
PERFECT_MONEY=Perfect Money
|
||||||
ALI_PAY=AliPay
|
ALI_PAY=AliPay
|
||||||
SEPA=SEPA
|
SEPA=SEPA
|
||||||
|
@ -1755,6 +1780,18 @@ BLOCK_CHAINS=Altcoins
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
OK_PAY_SHORT=OKPay
|
OK_PAY_SHORT=OKPay
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
UPHOLD_SHORT=Uphold
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
CASH_APP_SHORT=Cash App
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
MONEY_BEAM_SHORT=MoneyBeam (N26)
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
VENMO_SHORT=Venmo
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
POPMONEY_SHORT=Popmoney
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
REVOLUT_SHORT=Revolut
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
PERFECT_MONEY_SHORT=Perfect Money
|
PERFECT_MONEY_SHORT=Perfect Money
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
ALI_PAY_SHORT=AliPay
|
ALI_PAY_SHORT=AliPay
|
||||||
|
@ -1802,6 +1839,7 @@ validation.integerOnly=Please enter integer numbers only.
|
||||||
validation.inputError=Your input caused an error:\n{0}
|
validation.inputError=Your input caused an error:\n{0}
|
||||||
validation.bsq.insufficientBalance=Amount exceeds the available balance of {0}.
|
validation.bsq.insufficientBalance=Amount exceeds the available balance of {0}.
|
||||||
validation.btc.exceedsMaxTradeLimit=Amount larger than your trade limit of {0} is not allowed.
|
validation.btc.exceedsMaxTradeLimit=Amount larger than your trade limit of {0} is not allowed.
|
||||||
|
validation.bsq.amountBelowMinAmount=Min. amount is {0}
|
||||||
|
|
||||||
#new
|
#new
|
||||||
validation.invalidInput=Invalid input: {0}
|
validation.invalidInput=Invalid input: {0}
|
||||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=verkaufen
|
||||||
shared.buying=kaufe
|
shared.buying=kaufe
|
||||||
shared.selling=verkaufe
|
shared.selling=verkaufe
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offers=Angebote
|
shared.oneOffer=Angebot
|
||||||
|
shared.multipleOffers=Angebote
|
||||||
shared.Offer=Angebot
|
shared.Offer=Angebot
|
||||||
shared.openOffers=offene Angebote
|
shared.openOffers=offene Angebote
|
||||||
shared.trades=Händel
|
shared.trades=Händel
|
||||||
|
@ -96,6 +97,8 @@ shared.fundFromSavingsWalletButton=Gelder aus Bisq-Wallet überweisen
|
||||||
shared.fundFromExternalWalletButton=Ihre externe Wallet zum Finanzieren öffnen
|
shared.fundFromExternalWalletButton=Ihre externe Wallet zum Finanzieren öffnen
|
||||||
shared.openDefaultWalletFailed=Das Öffnen einer Bitcoin-Wallet-Standardanwendung schlug fehl. Haben Sie möglicherweise keine installiert?
|
shared.openDefaultWalletFailed=Das Öffnen einer Bitcoin-Wallet-Standardanwendung schlug fehl. Haben Sie möglicherweise keine installiert?
|
||||||
shared.distanceInPercent=Abstand vom Marktpreis in %
|
shared.distanceInPercent=Abstand vom Marktpreis in %
|
||||||
|
shared.belowInPercent=Unter dem Marktpreis in %
|
||||||
|
shared.aboveInPercent=Über dem Marktpreis in %
|
||||||
shared.enterPercentageValue=%-Wert eingeben
|
shared.enterPercentageValue=%-Wert eingeben
|
||||||
shared.OR=ODER
|
shared.OR=ODER
|
||||||
shared.notEnoughFunds=Sie haben nicht genug Gelder in Ihrer Bisq-Wallet.\nSie benötigen {0}, haben aber nur {1} in Ihrer Bisq-Wallet.\n\nFinanzieren Sie diesen Handel bitte von einer externen Bitcoin-Wallet oder finanzieren Sie Ihre Bisq-Wallet unter \"Gelder/Gelder erhalten\".
|
shared.notEnoughFunds=Sie haben nicht genug Gelder in Ihrer Bisq-Wallet.\nSie benötigen {0}, haben aber nur {1} in Ihrer Bisq-Wallet.\n\nFinanzieren Sie diesen Handel bitte von einer externen Bitcoin-Wallet oder finanzieren Sie Ihre Bisq-Wallet unter \"Gelder/Gelder erhalten\".
|
||||||
|
@ -166,6 +169,7 @@ shared.viewContractAsJson=Vertrag im JSON-Format ansehen
|
||||||
shared.contract.title=Vertrag für den Handel mit der ID: {0}
|
shared.contract.title=Vertrag für den Handel mit der ID: {0}
|
||||||
shared.paymentDetails=Zahlungsdetails des BTC-{0}:
|
shared.paymentDetails=Zahlungsdetails des BTC-{0}:
|
||||||
shared.securityDeposit=Kaution
|
shared.securityDeposit=Kaution
|
||||||
|
shared.yourSecurityDeposit=Deine Kaution
|
||||||
shared.contract=Vertrag
|
shared.contract=Vertrag
|
||||||
shared.messageArrived=Nachricht angekommen.
|
shared.messageArrived=Nachricht angekommen.
|
||||||
shared.messageStoredInMailbox=Nachricht in Postfach gespeichert.
|
shared.messageStoredInMailbox=Nachricht in Postfach gespeichert.
|
||||||
|
@ -254,10 +258,10 @@ market.tabs.trades=Händel
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Angebotsbuch für {0}
|
market.offerBook.chart.title=Angebotsbuch für {0}
|
||||||
market.offerBook.leftButtonAltcoin=Ich möchte {0} kaufen ({1} verkaufen)
|
market.offerBook.buyAltcoin={0} kaufen ({1} verkaufen)
|
||||||
market.offerBook.rightButtonAltcoin=Ich möchte {0} verkaufen ({1} kaufen)
|
market.offerBook.sellAltcoin={0} verkaufen ({1} kaufen)
|
||||||
market.offerBook.leftButtonFiat=Ich möchte {0} mit {1} kaufen
|
market.offerBook.buyWithFiat={0} kaufen
|
||||||
market.offerBook.rightButtonFiat=Ich möchte {0} für {1} verkaufen
|
market.offerBook.sellWithFiat={0} verkaufen
|
||||||
market.offerBook.sellOffersHeaderLabel=Verkaufe {0} an
|
market.offerBook.sellOffersHeaderLabel=Verkaufe {0} an
|
||||||
market.offerBook.buyOffersHeaderLabel=Kaufe {0} von
|
market.offerBook.buyOffersHeaderLabel=Kaufe {0} von
|
||||||
market.offerBook.buy=Ich möchte Bitcoins kaufen
|
market.offerBook.buy=Ich möchte Bitcoins kaufen
|
||||||
|
@ -874,7 +878,7 @@ dao.compensation.menuItem.activeRequests=Aktive Anfragen
|
||||||
dao.compensation.menuItem.pastRequests=Erledigte Anfragen
|
dao.compensation.menuItem.pastRequests=Erledigte Anfragen
|
||||||
|
|
||||||
dao.compensation.active.header=Aktive Entschädigungsanfrage
|
dao.compensation.active.header=Aktive Entschädigungsanfrage
|
||||||
dao.compensation.active.selectedRequest=Ausgewählte Entschädigungsanfrage
|
dao.compensation.selectedRequest=Ausgewählte Entschädigungsanfrage
|
||||||
dao.compensation.active.notOpenAnymore=Diese Entschädigung ist nicht zur Finanzierung verfügbar. Bitte warten Sie bis die nächste Finanzierungsperiode beginnt.
|
dao.compensation.active.notOpenAnymore=Diese Entschädigung ist nicht zur Finanzierung verfügbar. Bitte warten Sie bis die nächste Finanzierungsperiode beginnt.
|
||||||
dao.compensation.active.vote=Für Entschädigung stimmen
|
dao.compensation.active.vote=Für Entschädigung stimmen
|
||||||
dao.compensation.active.fund=Entschädigungsanfrage finanzieren
|
dao.compensation.active.fund=Entschädigungsanfrage finanzieren
|
||||||
|
@ -1324,13 +1328,6 @@ LTC_TESTNET=Litecoin-Testnetzwerk
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Litecoin-Regtest
|
LTC_REGTEST=Litecoin-Regtest
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Dogecoin-Hauptnetzwerk
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Dogecoin-Testnetzwerk
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Dogecoin-Regtest
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=DASH-Hauptnetz
|
DASH_MAINNET=DASH-Hauptnetz
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1399,7 +1396,6 @@ payment.restore.default=Nein, Standardwährung wiederherstellen
|
||||||
payment.email=E-Mail:
|
payment.email=E-Mail:
|
||||||
payment.country=Land:
|
payment.country=Land:
|
||||||
payment.extras=Besondere Anforderungen:
|
payment.extras=Besondere Anforderungen:
|
||||||
payment.us.info=Überweisungen mit WIRE oder ACH werden in den USA nicht unterstützt, da WIRE zu teuer ist und ACH ein hohes Rückbuchungsrisikio beinhaltet.\n\nBitte nutzen Sie stattdessen die Zahlungsmethoden \"Zelle (ClearXchange)\", \"Chase QuickPay\", \"US Postal Money Order\" oder \"Cash Deposit\".
|
|
||||||
payment.email.mobile=E-Mail oder Telefonnummer:
|
payment.email.mobile=E-Mail oder Telefonnummer:
|
||||||
payment.altcoin.address=Altcoin-Adresse:
|
payment.altcoin.address=Altcoin-Adresse:
|
||||||
payment.altcoin=Altcoin:
|
payment.altcoin=Altcoin:
|
||||||
|
@ -1407,7 +1403,7 @@ payment.select.altcoin=Altcoin wählen oder suchen
|
||||||
payment.secret=Geheimfrage:
|
payment.secret=Geheimfrage:
|
||||||
payment.answer=Antwort:
|
payment.answer=Antwort:
|
||||||
payment.wallet=Wallet-ID:
|
payment.wallet=Wallet-ID:
|
||||||
payment.supported.okpay=Unterstützte Währungen:
|
payment.supportedCurrencies=Unterstützte Währungen:
|
||||||
payment.limitations=Einschränkungen:
|
payment.limitations=Einschränkungen:
|
||||||
payment.accept.euro=Händel aus diesen Euroländern akzeptieren:
|
payment.accept.euro=Händel aus diesen Euroländern akzeptieren:
|
||||||
payment.accept.nonEuro=Händel aus diesen Nicht-Euroländern akzeptieren:
|
payment.accept.nonEuro=Händel aus diesen Nicht-Euroländern akzeptieren:
|
||||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=πώληση
|
||||||
shared.buying=αγοράζοντας
|
shared.buying=αγοράζοντας
|
||||||
shared.selling=πουλώντας
|
shared.selling=πουλώντας
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offers=προσφορές
|
shared.oneOffer=προσφορά
|
||||||
|
shared.multipleOffers=προσφορές
|
||||||
shared.Offer=Προσφορά
|
shared.Offer=Προσφορά
|
||||||
shared.openOffers=ανοιχτές προσφορές
|
shared.openOffers=ανοιχτές προσφορές
|
||||||
shared.trades=συναλλαγές
|
shared.trades=συναλλαγές
|
||||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Συναλλαγές
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Καθολικό προσφορών για {0}
|
market.offerBook.chart.title=Καθολικό προσφορών για {0}
|
||||||
market.offerBook.leftButtonAltcoin=Θέλω να αγοράσω {0} (πώληση {1})
|
market.offerBook.buyAltcoin=Θέλω να αγοράσω {0} (πώληση {1})
|
||||||
market.offerBook.rightButtonAltcoin=Θέλω να πουλήσω {0} (αγορά {1})
|
market.offerBook.sellAltcoin=Θέλω να πουλήσω {0} (αγορά {1})
|
||||||
market.offerBook.leftButtonFiat=Θέλω να αγοράσω {0} με {1}
|
market.offerBook.buyWithFiat=Αγορά {0}
|
||||||
market.offerBook.rightButtonFiat=Θέλω να πουλήσω {0} με {1}
|
market.offerBook.sellWithFiat=Πώληση {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Πουλήστε το {0} στο
|
market.offerBook.sellOffersHeaderLabel=Πουλήστε το {0} στο
|
||||||
market.offerBook.buyOffersHeaderLabel=Αγοράστε {0} από
|
market.offerBook.buyOffersHeaderLabel=Αγοράστε {0} από
|
||||||
market.offerBook.buy=Θέλω να αγοράσω bitcoin
|
market.offerBook.buy=Θέλω να αγοράσω bitcoin
|
||||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Ενεργά αιτήματα
|
||||||
dao.compensation.menuItem.pastRequests=Προηγούμενα αιτήματα
|
dao.compensation.menuItem.pastRequests=Προηγούμενα αιτήματα
|
||||||
|
|
||||||
dao.compensation.active.header=Ενεργό αίτημα αποζημίωσης
|
dao.compensation.active.header=Ενεργό αίτημα αποζημίωσης
|
||||||
dao.compensation.active.selectedRequest=Επιλεγμένο αίτημα αποζημίωσης
|
dao.compensation.selectedRequest=Επιλεγμένο αίτημα αποζημίωσης
|
||||||
dao.compensation.active.notOpenAnymore=Το αίτημα αποζημίωσης δεν είναι πλέον ανοιχτό προς χρηματοδότηση. Περίμενε μέχρι την έναρξη της επόμενης περιόδου χρηματοδότησης.
|
dao.compensation.active.notOpenAnymore=Το αίτημα αποζημίωσης δεν είναι πλέον ανοιχτό προς χρηματοδότηση. Περίμενε μέχρι την έναρξη της επόμενης περιόδου χρηματοδότησης.
|
||||||
dao.compensation.active.vote=Ψήφισμα περί αιτήματος αποζημίωσης
|
dao.compensation.active.vote=Ψήφισμα περί αιτήματος αποζημίωσης
|
||||||
dao.compensation.active.fund=Χρηματοδότηση αιτήματος αποζημίωσης
|
dao.compensation.active.fund=Χρηματοδότηση αιτήματος αποζημίωσης
|
||||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Litecoin Testnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Litecoin Regtest
|
LTC_REGTEST=Litecoin Regtest
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Dogecoin Mainnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Dogecoin Testnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Dogecoin Regtest
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=DASH Mainnet
|
DASH_MAINNET=DASH Mainnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Όχι, επανάφερε το προεπιλεγμέ
|
||||||
payment.email=Email:
|
payment.email=Email:
|
||||||
payment.country=Χώρα:
|
payment.country=Χώρα:
|
||||||
payment.extras=Επιπλέον προαπαιτήσεις:
|
payment.extras=Επιπλέον προαπαιτήσεις:
|
||||||
payment.us.info=Bank transfer with WIRE or ACH is not supported for the US because WIRE is too expensive and ACH has a high chargeback risk.\n\nPlease use one of the available payment methods for the US like: \"Zelle (ClearXchange)\", \"Chase QuickPay\", \"US Postal Money Order\" or \"Cash Deposit\".
|
|
||||||
payment.email.mobile=Email ή αριθμός κινητού:
|
payment.email.mobile=Email ή αριθμός κινητού:
|
||||||
payment.altcoin.address=Διεύθυνση altcoin:
|
payment.altcoin.address=Διεύθυνση altcoin:
|
||||||
payment.altcoin=Altcoin:
|
payment.altcoin=Altcoin:
|
||||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Διάλεξε ή αναζήτησε altcoin:
|
||||||
payment.secret=Ερώτηση ασφάλειας:
|
payment.secret=Ερώτηση ασφάλειας:
|
||||||
payment.answer=Απάντηση:
|
payment.answer=Απάντηση:
|
||||||
payment.wallet=Ταυτότητα πορτοφολιού:
|
payment.wallet=Ταυτότητα πορτοφολιού:
|
||||||
payment.supported.okpay= Υποστηριζόμενα νομίσματα:
|
payment.supportedCurrencies=Υποστηριζόμενα νομίσματα:
|
||||||
payment.limitations=Περιορισμοί:
|
payment.limitations=Περιορισμοί:
|
||||||
payment.accept.euro=Αποδοχή συναλλαγών από τις ακόλουθες χώρες Ευρώ:
|
payment.accept.euro=Αποδοχή συναλλαγών από τις ακόλουθες χώρες Ευρώ:
|
||||||
payment.accept.nonEuro=Αποδοχή συναλλαγών από τις ακόλουθες χώρες εκτός Ευρώ:
|
payment.accept.nonEuro=Αποδοχή συναλλαγών από τις ακόλουθες χώρες εκτός Ευρώ:
|
||||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=vender
|
||||||
shared.buying=Comprando
|
shared.buying=Comprando
|
||||||
shared.selling=Vendiendo
|
shared.selling=Vendiendo
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offers=ofertas
|
shared.oneOffer=oferta
|
||||||
|
shared.multipleOffers=ofertas
|
||||||
shared.Offer=Oferta
|
shared.Offer=Oferta
|
||||||
shared.openOffers=ofertas abiertas
|
shared.openOffers=ofertas abiertas
|
||||||
shared.trades=Intercambios
|
shared.trades=Intercambios
|
||||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Intercambios
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Libro de ofertas para {0}
|
market.offerBook.chart.title=Libro de ofertas para {0}
|
||||||
market.offerBook.leftButtonAltcoin=Quiero comprar {0} (vender {1})
|
market.offerBook.buyAltcoin=Quiero comprar {0} (vender {1})
|
||||||
market.offerBook.rightButtonAltcoin=Quiero vender {0} (comprar {1})
|
market.offerBook.sellAltcoin=Quiero vender {0} (comprar {1})
|
||||||
market.offerBook.leftButtonFiat=Quiero comprar {0} con {1}
|
market.offerBook.buyWithFiat=Comprar {0}
|
||||||
market.offerBook.rightButtonFiat=Quiero vender {0} por {1}
|
market.offerBook.sellWithFiat=Vender {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Vender {0} a
|
market.offerBook.sellOffersHeaderLabel=Vender {0} a
|
||||||
market.offerBook.buyOffersHeaderLabel=Compre {0} desde
|
market.offerBook.buyOffersHeaderLabel=Compre {0} desde
|
||||||
market.offerBook.buy=Quiero comprar bitcoin
|
market.offerBook.buy=Quiero comprar bitcoin
|
||||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Activar solicitudes
|
||||||
dao.compensation.menuItem.pastRequests=Solicitudes pasadas
|
dao.compensation.menuItem.pastRequests=Solicitudes pasadas
|
||||||
|
|
||||||
dao.compensation.active.header=Activar solicitud de compensación
|
dao.compensation.active.header=Activar solicitud de compensación
|
||||||
dao.compensation.active.selectedRequest=Solicitud de compensación seleccionada
|
dao.compensation.selectedRequest=Solicitud de compensación seleccionada
|
||||||
dao.compensation.active.notOpenAnymore=Esta solicitud de compensación ya no está abierta para añadir fondos. Por favor, espere a que comience el siguiente periodo de financiación.
|
dao.compensation.active.notOpenAnymore=Esta solicitud de compensación ya no está abierta para añadir fondos. Por favor, espere a que comience el siguiente periodo de financiación.
|
||||||
dao.compensation.active.vote=Votar solicitud de compensación
|
dao.compensation.active.vote=Votar solicitud de compensación
|
||||||
dao.compensation.active.fund=Financiar solicitud de compensación
|
dao.compensation.active.fund=Financiar solicitud de compensación
|
||||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Testnet Litecoin
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Regtest Litecoin
|
LTC_REGTEST=Regtest Litecoin
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Red principal Dogecoin
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Testnet Dogecoin
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Regtest Dogecoin
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=DASH Mainnet
|
DASH_MAINNET=DASH Mainnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=No, restaurar moneda por defecto.
|
||||||
payment.email=Email:
|
payment.email=Email:
|
||||||
payment.country=País:
|
payment.country=País:
|
||||||
payment.extras=Requisitos extra:
|
payment.extras=Requisitos extra:
|
||||||
payment.us.info=Bank transfer with WIRE or ACH is not supported for the US because WIRE is too expensive and ACH has a high chargeback risk.\n\nPlease use one of the available payment methods for the US like: \"Zelle (ClearXchange)\", \"Chase QuickPay\", \"US Postal Money Order\" or \"Cash Deposit\".
|
|
||||||
payment.email.mobile=Email o número de móvil:
|
payment.email.mobile=Email o número de móvil:
|
||||||
payment.altcoin.address=Dirección de altcoin:
|
payment.altcoin.address=Dirección de altcoin:
|
||||||
payment.altcoin=Altcoin:
|
payment.altcoin=Altcoin:
|
||||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Seleccione o busque altcoin
|
||||||
payment.secret=Pregunta secreta:
|
payment.secret=Pregunta secreta:
|
||||||
payment.answer=Respuesta:
|
payment.answer=Respuesta:
|
||||||
payment.wallet=ID de cartera:
|
payment.wallet=ID de cartera:
|
||||||
payment.supported.okpay=Monedas soportadas:
|
payment.supportedCurrencies=Monedas soportadas:
|
||||||
payment.limitations=Límitaciones:
|
payment.limitations=Límitaciones:
|
||||||
payment.accept.euro=Aceptar intercambios desde estos países Euro.
|
payment.accept.euro=Aceptar intercambios desde estos países Euro.
|
||||||
payment.accept.nonEuro=Aceptar intercambios desde estos países no-Euro:
|
payment.accept.nonEuro=Aceptar intercambios desde estos países no-Euro:
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -49,7 +49,8 @@ shared.sell=vender
|
||||||
shared.buying=comprando
|
shared.buying=comprando
|
||||||
shared.selling=vendendo
|
shared.selling=vendendo
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offers=ofertas
|
shared.oneOffer=oferta
|
||||||
|
shared.multipleOffers=ofertas
|
||||||
shared.Offer=Oferta
|
shared.Offer=Oferta
|
||||||
shared.openOffers=abrir ofertas
|
shared.openOffers=abrir ofertas
|
||||||
shared.trades=negociações
|
shared.trades=negociações
|
||||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Negociações
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Livro de ofertas para {0}
|
market.offerBook.chart.title=Livro de ofertas para {0}
|
||||||
market.offerBook.leftButtonAltcoin=Eu quero comprar {0} (vender {1})
|
market.offerBook.buyAltcoin=Eu quero comprar {0} (vender {1})
|
||||||
market.offerBook.rightButtonAltcoin=Eu quero vender {0} (comprar {1})
|
market.offerBook.sellAltcoin=Eu quero vender {0} (comprar {1})
|
||||||
market.offerBook.leftButtonFiat=Eu quero comprar {0} com {1}
|
market.offerBook.buyWithFiat=Compre {0}
|
||||||
market.offerBook.rightButtonFiat=Eu quero vender {0} por {1}
|
market.offerBook.sellWithFiat=Vender {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Vender {0} para
|
market.offerBook.sellOffersHeaderLabel=Vender {0} para
|
||||||
market.offerBook.buyOffersHeaderLabel=Compre {0} de
|
market.offerBook.buyOffersHeaderLabel=Compre {0} de
|
||||||
market.offerBook.buy=Eu quero comprar bitcoin
|
market.offerBook.buy=Eu quero comprar bitcoin
|
||||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Solicitações ativas
|
||||||
dao.compensation.menuItem.pastRequests=Solicitações anteriores
|
dao.compensation.menuItem.pastRequests=Solicitações anteriores
|
||||||
|
|
||||||
dao.compensation.active.header=Pedido de compensação ativo
|
dao.compensation.active.header=Pedido de compensação ativo
|
||||||
dao.compensation.active.selectedRequest=Pedido de compensação selecionado
|
dao.compensation.selectedRequest=Pedido de compensação selecionado
|
||||||
dao.compensation.active.notOpenAnymore=Este pedido de compensação não está mais aberto para financiamento. Favor aguardar até o próximo período de financiamento iniciar.
|
dao.compensation.active.notOpenAnymore=Este pedido de compensação não está mais aberto para financiamento. Favor aguardar até o próximo período de financiamento iniciar.
|
||||||
dao.compensation.active.vote=Votar em pedido de compensação
|
dao.compensation.active.vote=Votar em pedido de compensação
|
||||||
dao.compensation.active.fund=Financiar pedido de compensação
|
dao.compensation.active.fund=Financiar pedido de compensação
|
||||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Litecoin Testnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Litecoin Regtest
|
LTC_REGTEST=Litecoin Regtest
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Dogecoin Mainnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Dogecoin Testnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Dogecoin Regtest
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=DASH Mainnet
|
DASH_MAINNET=DASH Mainnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Não, resturar para a moeda padrão
|
||||||
payment.email=Email:
|
payment.email=Email:
|
||||||
payment.country=País:
|
payment.country=País:
|
||||||
payment.extras=Requerimentos adicionais:
|
payment.extras=Requerimentos adicionais:
|
||||||
payment.us.info=Bank transfer with WIRE or ACH is not supported for the US because WIRE is too expensive and ACH has a high chargeback risk.\n\nPlease use one of the available payment methods for the US like: \"Zelle (ClearXchange)\", \"Chase QuickPay\", \"US Postal Money Order\" or \"Cash Deposit\".
|
|
||||||
payment.email.mobile=Email ou celular:
|
payment.email.mobile=Email ou celular:
|
||||||
payment.altcoin.address=Endereço altcoin:
|
payment.altcoin.address=Endereço altcoin:
|
||||||
payment.altcoin=Altcoin:
|
payment.altcoin=Altcoin:
|
||||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Selecionar ou buscar altcoin
|
||||||
payment.secret=Pergunta secreta:
|
payment.secret=Pergunta secreta:
|
||||||
payment.answer=Resposta:
|
payment.answer=Resposta:
|
||||||
payment.wallet=ID da carteira:
|
payment.wallet=ID da carteira:
|
||||||
payment.supported.okpay=Moedas suportadas:
|
payment.supportedCurrencies=Moedas suportadas:
|
||||||
payment.limitations=Limitações:
|
payment.limitations=Limitações:
|
||||||
payment.accept.euro=Aceitar negociações destes países do Euro:
|
payment.accept.euro=Aceitar negociações destes países do Euro:
|
||||||
payment.accept.nonEuro=Aceitar negociações desses países fora do Euro:
|
payment.accept.nonEuro=Aceitar negociações desses países fora do Euro:
|
||||||
|
|
|
@ -49,8 +49,8 @@ shared.sell=vinde
|
||||||
shared.buying=cumpărând
|
shared.buying=cumpărând
|
||||||
shared.selling=vânzând
|
shared.selling=vânzând
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offer=oferă
|
shared.oneOffer=ofertă
|
||||||
shared.offers=oferte
|
shared.multipleOffers=oferte
|
||||||
shared.Offer=Ofertă
|
shared.Offer=Ofertă
|
||||||
shared.openOffers=oferte deschise
|
shared.openOffers=oferte deschise
|
||||||
shared.trade=tranzacționează
|
shared.trade=tranzacționează
|
||||||
|
@ -189,6 +189,7 @@ shared.takerTxFee=Acceptant: {0}
|
||||||
shared.securityDepositBox.description=Depozitul de securitate pentru BTC {0}
|
shared.securityDepositBox.description=Depozitul de securitate pentru BTC {0}
|
||||||
shared.iConfirm=Confirm
|
shared.iConfirm=Confirm
|
||||||
shared.tradingFeeInBsqInfo=echivalentul al {0} folosit drept comision de minare
|
shared.tradingFeeInBsqInfo=echivalentul al {0} folosit drept comision de minare
|
||||||
|
shared.paidWithBsq=plătit cu BSQ
|
||||||
shared.availableBsqBalance=Sold disponibil BSQ:
|
shared.availableBsqBalance=Sold disponibil BSQ:
|
||||||
shared.unverifiedBsqBalance=Sold BSQ neverificat:
|
shared.unverifiedBsqBalance=Sold BSQ neverificat:
|
||||||
shared.totalBsqBalance=Soldul total BSQ
|
shared.totalBsqBalance=Soldul total BSQ
|
||||||
|
@ -260,12 +261,12 @@ market.tabs.trades=Tranzacții
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Cartea de oferte pentru {0}
|
market.offerBook.chart.title=Cartea de oferte pentru {0}
|
||||||
market.offerBook.leftButtonAltcoin=Doresc să cumpăr {0} (vând {1})
|
market.offerBook.buyAltcoin=Cumpărare {0} (vânzare {1})
|
||||||
market.offerBook.rightButtonAltcoin=Doresc să vând {0} (cumpăr {1})
|
market.offerBook.sellAltcoin=Vânzare {0} (cumpărare {1})
|
||||||
market.offerBook.leftButtonFiat=Doresc să cumpăr {0} cu {1}
|
market.offerBook.buyWithFiat=Cumpără {0}
|
||||||
market.offerBook.rightButtonFiat=Doresc să vând {0} contra {1}
|
market.offerBook.sellWithFiat=Vinde {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Vindem {0} la
|
market.offerBook.sellOffersHeaderLabel=Vinde {0} către
|
||||||
market.offerBook.buyOffersHeaderLabel=Cumpărați {0} de la
|
market.offerBook.buyOffersHeaderLabel=Cumpără {0} de la
|
||||||
market.offerBook.buy=Doresc să cumpăr bitcoin
|
market.offerBook.buy=Doresc să cumpăr bitcoin
|
||||||
market.offerBook.sell=Doresc să vând bitcoin
|
market.offerBook.sell=Doresc să vând bitcoin
|
||||||
|
|
||||||
|
@ -348,6 +349,7 @@ createOffer.fundsBox.offerFee=Comisionul tranzacției:
|
||||||
createOffer.fundsBox.networkFee=Comision de minare:
|
createOffer.fundsBox.networkFee=Comision de minare:
|
||||||
createOffer.fundsBox.placeOfferSpinnerInfo=Publicare ofertei în desfășurare ...
|
createOffer.fundsBox.placeOfferSpinnerInfo=Publicare ofertei în desfășurare ...
|
||||||
createOffer.fundsBox.paymentLabel=Tranzacția Bisq cu codul {0}
|
createOffer.fundsBox.paymentLabel=Tranzacția Bisq cu codul {0}
|
||||||
|
createOffer.fundsBox.fundsStructure=({0} cauțiune, {1} comision tranzacție, {2} comision minier)
|
||||||
createOffer.success.headline=Oferta ta a fost publicată
|
createOffer.success.headline=Oferta ta a fost publicată
|
||||||
createOffer.success.info=Îți poți gestiona ofertele deschise la \"Portofoliu/Tranzacții deschise\".
|
createOffer.success.info=Îți poți gestiona ofertele deschise la \"Portofoliu/Tranzacții deschise\".
|
||||||
|
|
||||||
|
@ -401,6 +403,7 @@ takeOffer.fundsBox.offerFee=Comisionul tranzacției:
|
||||||
takeOffer.fundsBox.networkFee=Totalul comisioanelor de minare:
|
takeOffer.fundsBox.networkFee=Totalul comisioanelor de minare:
|
||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Acceptă oferta în curs ...
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Acceptă oferta în curs ...
|
||||||
takeOffer.fundsBox.paymentLabel=Tranzacția Bisq cu codul {0}
|
takeOffer.fundsBox.paymentLabel=Tranzacția Bisq cu codul {0}
|
||||||
|
takeOffer.fundsBox.fundsStructure=({0} cauțiune, {1} comision tranzacție, {2} comision minier)
|
||||||
takeOffer.success.headline=Ai acceptat cu succes o ofertă.
|
takeOffer.success.headline=Ai acceptat cu succes o ofertă.
|
||||||
takeOffer.success.info=Puteți vedea starea tranzacției tale la \"Portofoliu/Tranzacții deschise\".
|
takeOffer.success.info=Puteți vedea starea tranzacției tale la \"Portofoliu/Tranzacții deschise\".
|
||||||
takeOffer.error.message=A apărut o eroare la preluarea ofertei.\n\n{0}
|
takeOffer.error.message=A apărut o eroare la preluarea ofertei.\n\n{0}
|
||||||
|
@ -452,7 +455,7 @@ portfolio.pending.step5.completed=Finalizat
|
||||||
|
|
||||||
portfolio.pending.step1.info=Tranzacția de depozitare a fost publicată.\n{0} trebuie să aștepte cel puțin o confirmare de pe blockchain înainte de a începe plata.
|
portfolio.pending.step1.info=Tranzacția de depozitare a fost publicată.\n{0} trebuie să aștepte cel puțin o confirmare de pe blockchain înainte de a începe plata.
|
||||||
portfolio.pending.step1.warn=Tranzacția de depozitare încă nu a ajuns confirmată.\nAcest lucru se poate întâmpla în rare cazuri când comisionul de finanțare din portofelul extern al unui comerciant este prea mic
|
portfolio.pending.step1.warn=Tranzacția de depozitare încă nu a ajuns confirmată.\nAcest lucru se poate întâmpla în rare cazuri când comisionul de finanțare din portofelul extern al unui comerciant este prea mic
|
||||||
portfolio.pending.step1.openForDispute=Tranzacția de depozitare încă nu a fost confirmată.\nAcest lucru se poate întâmpla în rare cazuri când comisionul de finanțare al unui comerciant din portofelul său extern a fost prea mic.\nPerioada de timp pentru tranzacționare a expirat.\n\nTe rugăm să contactezi arbitrul în vederea deschiderii unei dispute.
|
portfolio.pending.step1.openForDispute=Tranzacția de depozitare încă nu a fost confirmată.\nAcest lucru se poate întâmpla în rare cazuri când comisionul de finanțare al unui comerciant din portofelul său extern a fost prea mic.\nPerioada de timp pentru tranzacționare a expirat.\n\nPoți aștepta mai mult sau poți contacta arbitrul în vederea deschiderii unei dispute.
|
||||||
|
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
portfolio.pending.step2.confReached=Tranzacția ta a obținut cel puțin o confirmare pe blockchain.\n(Poți aștepta mai multe confirmări dacă dorești - 6 confirmări sunt considerate foarte sigure.)\n\n
|
portfolio.pending.step2.confReached=Tranzacția ta a obținut cel puțin o confirmare pe blockchain.\n(Poți aștepta mai multe confirmări dacă dorești - 6 confirmări sunt considerate foarte sigure.)\n\n
|
||||||
|
@ -495,14 +498,14 @@ portfolio.pending.step2_buyer.confirmStart.yes=Da, am inițiat plata
|
||||||
portfolio.pending.step2_seller.waitPayment.headline=Așteaptă plata
|
portfolio.pending.step2_seller.waitPayment.headline=Așteaptă plata
|
||||||
portfolio.pending.step2_seller.waitPayment.msg=Tranzacția de depozitare are cel puțin o confirmare pe blockchain.\nTrebuie să aștepți până când cumpărătorul de BTC inițiază plata {0}.
|
portfolio.pending.step2_seller.waitPayment.msg=Tranzacția de depozitare are cel puțin o confirmare pe blockchain.\nTrebuie să aștepți până când cumpărătorul de BTC inițiază plata {0}.
|
||||||
portfolio.pending.step2_seller.warn=Cumpărătorul de BTC încă nu a efectuat plata {0}.\nTrebuie să aștepți până când acesta inițiază plata.\nDacă tranzacția nu a fost finalizată la {1}, arbitrul o va investiga.
|
portfolio.pending.step2_seller.warn=Cumpărătorul de BTC încă nu a efectuat plata {0}.\nTrebuie să aștepți până când acesta inițiază plata.\nDacă tranzacția nu a fost finalizată la {1}, arbitrul o va investiga.
|
||||||
portfolio.pending.step2_seller.openForDispute=Cumpărătorul de BTC nu și-a efectual plata!\nPerioada maximă permisă pentru tranzacționare a expirat.\nTe rugăm să contactezi arbitrul pentru deschiderea unei dispute.
|
portfolio.pending.step2_seller.openForDispute=Vânzătorul de BTC înca nu a inițiat plata!\nPerioada maximă de timp pentru tranzacționare a expirat.\nPoți aștepta mai mult astfel acordând mai mult timp partenerului de tranzacționare sau poți contacta arbitrul în vederea deschiderii unei dispute.
|
||||||
|
|
||||||
portfolio.pending.step3_buyer.wait.headline=Așteptă confirmarea plății vânzătorului de BTC
|
portfolio.pending.step3_buyer.wait.headline=Așteptă confirmarea plății vânzătorului de BTC
|
||||||
portfolio.pending.step3_buyer.wait.info=În așteptarea confirmării vânzătorului de BTC privind primirea plății {0}.
|
portfolio.pending.step3_buyer.wait.info=În așteptarea confirmării vânzătorului de BTC privind primirea plății {0}.
|
||||||
portfolio.pending.step3_buyer.warn.part1a=pe blockchain-ul {0}
|
portfolio.pending.step3_buyer.warn.part1a=pe blockchain-ul {0}
|
||||||
portfolio.pending.step3_buyer.warn.part1b=la furnizorul tău de plăți (ex: bancă)
|
portfolio.pending.step3_buyer.warn.part1b=la furnizorul tău de plăți (ex: bancă)
|
||||||
portfolio.pending.step3_buyer.warn.part2=Vânzătorul de BTC încă nu ți-a confirmat plata!\nVerifică {0} dacă plata a fost executată cu succes.\nDacă vânzătorul de BTC nu confirmă primirea plății până la data de {1} tranzacția va fi investigată de arbitru.
|
portfolio.pending.step3_buyer.warn.part2=Vânzătorul de BTC încă nu ți-a confirmat plata!\nVerifică {0} dacă plata a fost executată cu succes.\nDacă vânzătorul de BTC nu confirmă primirea plății până la data de {1} tranzacția va fi investigată de arbitru.
|
||||||
portfolio.pending.step3_buyer.openForDispute=Vânzătorul de BTC înca nu ți-a confirmat plata!\nPerioada maximă de timp pentru tranzacționare a expirat.\nTe rugăm să contactezi arbitrul în vederea deschiderii unei dispute.
|
portfolio.pending.step3_buyer.openForDispute=Vânzătorul de BTC înca nu ți-a confirmat plata!\nPerioada maximă de timp pentru tranzacționare a expirat.\nPoți aștepta mai mult astfel acordând mai mult timp partenerului de tranzacționare sau poți contacta arbitrul în vederea deschiderii unei dispute.
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
portfolio.pending.step3_seller.part=Partenerul tău de tranzacționare a confirmat că a inițiat plata {0}.\n\n
|
portfolio.pending.step3_seller.part=Partenerul tău de tranzacționare a confirmat că a inițiat plata {0}.\n\n
|
||||||
portfolio.pending.step3_seller.altcoin={0}Te rugăm să verifici în exploratorul tău preferat de {1} blockchain dacă tranzacția către adresa ta de primire\n{2}\nare deja suficiente confirmări de blockchain.\nValoarea plății trebuie să fie {3}\n\nÎți poți copia și insera adresa de {4} din ecranul principal după închiderea ferestrei de pop-up.
|
portfolio.pending.step3_seller.altcoin={0}Te rugăm să verifici în exploratorul tău preferat de {1} blockchain dacă tranzacția către adresa ta de primire\n{2}\nare deja suficiente confirmări de blockchain.\nValoarea plății trebuie să fie {3}\n\nÎți poți copia și insera adresa de {4} din ecranul principal după închiderea ferestrei de pop-up.
|
||||||
|
@ -526,7 +529,7 @@ portfolio.pending.step3_seller.buyerStartedPayment.fiat=Verifică-ți contul de
|
||||||
portfolio.pending.step3_seller.warn.part1a=pe blockchain-ul {0}
|
portfolio.pending.step3_seller.warn.part1a=pe blockchain-ul {0}
|
||||||
portfolio.pending.step3_seller.warn.part1b=la furnizorul tău de plăți (ex: bancă)
|
portfolio.pending.step3_seller.warn.part1b=la furnizorul tău de plăți (ex: bancă)
|
||||||
portfolio.pending.step3_seller.warn.part2=Încă nu ai confirmat primirea plății!\nVerifică {0} dacă ai primit plata.\nDacă nu confirmi primirea plății până la {1}, tranzacția va fi investigată de arbitru.
|
portfolio.pending.step3_seller.warn.part2=Încă nu ai confirmat primirea plății!\nVerifică {0} dacă ai primit plata.\nDacă nu confirmi primirea plății până la {1}, tranzacția va fi investigată de arbitru.
|
||||||
portfolio.pending.step3_seller.openForDispute=Nu ai confirmat primirea plății!\nPerioada maximă de timp pentru tranzacționare a expirat.\nTe rugăm să contactezi arbitrul în vederea deschiderii unei dispute.
|
portfolio.pending.step3_seller.openForDispute=Nu ai confirmat primirea plății!\nPerioada maximă de timp pentru tranzacționare a expirat.\nTe rugăm să confirmi sau să contactezi arbitrul în vederea deschiderii unei dispute.
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
portfolio.pending.step3_seller.onPaymentReceived.part1=Ai primit plata {0} de la partenerul tău de tranzacționare?\n\n
|
portfolio.pending.step3_seller.onPaymentReceived.part1=Ai primit plata {0} de la partenerul tău de tranzacționare?\n\n
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
|
@ -561,6 +564,10 @@ portfolio.pending.step5_seller.received=Ai primit:
|
||||||
portfolio.pending.role=Rolul meu
|
portfolio.pending.role=Rolul meu
|
||||||
portfolio.pending.tradeInformation=Informații tranzacții
|
portfolio.pending.tradeInformation=Informații tranzacții
|
||||||
portfolio.pending.remainingTime=Timp rămas
|
portfolio.pending.remainingTime=Timp rămas
|
||||||
|
portfolio.pending.remainingTimeDetail={0} (până {1})
|
||||||
|
portfolio.pending.tradePeriodInfo=După prima confirmare de blockchain, începe perioada de tranzacționare. În baza metodei de plată folosite, diferite perioade maxime de tranzacționare permise vor fi aplicate.
|
||||||
|
portfolio.pending.tradePeriodWarning=Dacă perioada este depășită, ambii utilizatori pot deschide o dispută.
|
||||||
|
portfolio.pending.tradeNotCompleted=Tranzacție nefinalizată la timp (până la {0})
|
||||||
portfolio.pending.tradeProcess=Proces de tranzacționare
|
portfolio.pending.tradeProcess=Proces de tranzacționare
|
||||||
portfolio.pending.openAgainDispute.msg=Dacă nu ești sigur că mesajul către arbitru a sosit (de ex. dacă nu ai primit un răspuns după 1 zi), nu ezita să deschizi din nou o dispută.
|
portfolio.pending.openAgainDispute.msg=Dacă nu ești sigur că mesajul către arbitru a sosit (de ex. dacă nu ai primit un răspuns după 1 zi), nu ezita să deschizi din nou o dispută.
|
||||||
portfolio.pending.openAgainDispute.button=Deschide disputa din nou
|
portfolio.pending.openAgainDispute.button=Deschide disputa din nou
|
||||||
|
@ -758,7 +765,7 @@ settings.net.roundTripTimeColumn=Tur-retur
|
||||||
settings.net.sentBytesColumn=Trimis
|
settings.net.sentBytesColumn=Trimis
|
||||||
settings.net.receivedBytesColumn=Recepționat
|
settings.net.receivedBytesColumn=Recepționat
|
||||||
settings.net.peerTypeColumn=Tip partener
|
settings.net.peerTypeColumn=Tip partener
|
||||||
settings.net.openTorSettingsButton=Deschide setările rețelei Tor
|
settings.net.openTorSettingsButton=Deschide setări Tor
|
||||||
|
|
||||||
settings.net.needRestart=Trebuie să repornești aplicația pentru a aplica modificările.\nDorești să faci asta acum?
|
settings.net.needRestart=Trebuie să repornești aplicația pentru a aplica modificările.\nDorești să faci asta acum?
|
||||||
settings.net.notKnownYet=Încă necunoscut...
|
settings.net.notKnownYet=Încă necunoscut...
|
||||||
|
@ -771,11 +778,10 @@ settings.net.inbound=Intrare
|
||||||
settings.net.outbound=ieșire
|
settings.net.outbound=ieșire
|
||||||
settings.net.reSyncSPVChainLabel=Resincronizează lanțul SPV
|
settings.net.reSyncSPVChainLabel=Resincronizează lanțul SPV
|
||||||
settings.net.reSyncSPVChainButton=Șterge fișierul SPV și resincronizează
|
settings.net.reSyncSPVChainButton=Șterge fișierul SPV și resincronizează
|
||||||
settings.net.reSyncSPVSuccess=Fișierul lanțului SPV a fost șters. Acum trebuie să repornești aplicația.\n\nDupă repornire, poate dura puțin timp până se resincronizează cu rețeaua și vei vedea toate tranzacțiile numai după finalizarea resincronizării.\n\nTe rugăm să repornești din nou după finalizarea resincronizării deoarece uneori pot apărea neconcordanțe ce duc la afișarea incorectă a soldului.
|
settings.net.reSyncSPVSuccess=Fișierul lanțului SPV va fi șters la următoarea pornire. Acum trebuie să repornești aplicația.\n\nDupă repornire, poate dura puțin timp până se resincronizează cu rețeaua și vei vedea toate tranzacțiile numai după finalizarea resincronizării.\n\nTe rugăm să repornești din nou după finalizarea resincronizării deoarece uneori pot apărea neconcordanțe ce duc la afișarea incorectă a soldului.
|
||||||
settings.net.reSyncSPVAfterRestart=Fișierul lanțului SPV a fost șters. Te rugăm să ai răbdare, resincronizarea cu rețeaua poate dura ceva timp.
|
settings.net.reSyncSPVAfterRestart=Fișierul lanțului SPV a fost șters. Te rugăm să ai răbdare, resincronizarea cu rețeaua poate dura ceva timp.
|
||||||
settings.net.reSyncSPVAfterRestartCompleted=Resincronizarea s-a încheiat. Te rog repornește aplicația.
|
settings.net.reSyncSPVAfterRestartCompleted=Resincronizarea s-a încheiat. Te rog repornește aplicația.
|
||||||
|
settings.net.reSyncSPVFailed=Nu s-a putut șterge fișierul lanțului SPV.\nEroare: {0}
|
||||||
settings.net.reSyncSPVFailed=Fișierul lanțului SPV nu a putut fi șters
|
|
||||||
setting.about.aboutBisq=Despre Bisq
|
setting.about.aboutBisq=Despre Bisq
|
||||||
setting.about.about=Bisq este un proiect open source și o rețea descentralizată de utilizatori care doresc să schimbe Bitcoin cu monedele naționale sau cu cripto-valutele alternative într-un mod care protejează intimitatea interlocutorilor. Află mai multe despre Bisq pe pagina noastră web a proiectului.
|
setting.about.about=Bisq este un proiect open source și o rețea descentralizată de utilizatori care doresc să schimbe Bitcoin cu monedele naționale sau cu cripto-valutele alternative într-un mod care protejează intimitatea interlocutorilor. Află mai multe despre Bisq pe pagina noastră web a proiectului.
|
||||||
setting.about.web=Pagina web Bisq
|
setting.about.web=Pagina web Bisq
|
||||||
|
@ -841,7 +847,7 @@ account.altcoin.popup.wallet.confirm=Înțeleg și confirm că știu ce portofel
|
||||||
account.altcoin.popup.xmr.msg=Dacă dorești să tranzacționezi XMR pe Bisq, asigură-te că ai înțeles și îndeplinești următoarele condiții:\n\nPentru a trimite XMR, trebuie să utilizezi portofelul oficial Monero GUI sau portofelul simplu Monero cu steagul store-tx-info activat (implicit în noile versiuni).\nAsigură-te că poți accesa cheia tx (folosește comanda get_tx_key în simplewallet) din motiv ce aceasta va fi necesară în cazul unei dispute care să permită arbitrului verificarea transferului de XMR cu ajutorul instrumentului checktx XMR (http://xmr.llcoins.net/checktx.html).\nÎn cazul exploratorilor normali ai blocurilor, transferul nu este verificabil.\n\nÎn cazul unei dispute va trebui să pui la dispoziția arbitrului următoarele date:\n- Cheia privată Tx\n- Hashul tranzacției\n- Adresa publică a destinatarului\n\nDacă nu poți furniza datele de mai sus sau dacă ai folosit un portofel incompatibil, va rezulta pierderea cazului disputei. Expeditorul XMR este responsabil pentru a face verificabil transferul XMR către arbitru în cazul unei dispute.\n\nNu este necesar un cod de plată, ci doar adresa publică normală.\n\nDacă nu ești sigur de acest proces vizitează forumul Monero (https://forum.getmonero.org) pentru mai multe informații.
|
account.altcoin.popup.xmr.msg=Dacă dorești să tranzacționezi XMR pe Bisq, asigură-te că ai înțeles și îndeplinești următoarele condiții:\n\nPentru a trimite XMR, trebuie să utilizezi portofelul oficial Monero GUI sau portofelul simplu Monero cu steagul store-tx-info activat (implicit în noile versiuni).\nAsigură-te că poți accesa cheia tx (folosește comanda get_tx_key în simplewallet) din motiv ce aceasta va fi necesară în cazul unei dispute care să permită arbitrului verificarea transferului de XMR cu ajutorul instrumentului checktx XMR (http://xmr.llcoins.net/checktx.html).\nÎn cazul exploratorilor normali ai blocurilor, transferul nu este verificabil.\n\nÎn cazul unei dispute va trebui să pui la dispoziția arbitrului următoarele date:\n- Cheia privată Tx\n- Hashul tranzacției\n- Adresa publică a destinatarului\n\nDacă nu poți furniza datele de mai sus sau dacă ai folosit un portofel incompatibil, va rezulta pierderea cazului disputei. Expeditorul XMR este responsabil pentru a face verificabil transferul XMR către arbitru în cazul unei dispute.\n\nNu este necesar un cod de plată, ci doar adresa publică normală.\n\nDacă nu ești sigur de acest proces vizitează forumul Monero (https://forum.getmonero.org) pentru mai multe informații.
|
||||||
account.altcoin.popup.ZEC.msg=Atunci când folosești {0}, poți utiliza doar adresele transparente (începând cu t) nu adresele z (private), deoarece arbitrul nu va putea verifica tranzacția cu adrese z.
|
account.altcoin.popup.ZEC.msg=Atunci când folosești {0}, poți utiliza doar adresele transparente (începând cu t) nu adresele z (private), deoarece arbitrul nu va putea verifica tranzacția cu adrese z.
|
||||||
account.altcoin.popup.XZC.msg=Când folosești {0}, poți folosi numai adresele transparente (ce pot fi urmărite), nu adresele indetectabile, deoarece arbitrul nu va putea verifica tranzacția executată cu adrese ce nu pot fi citite de către un explorator de bloc.
|
account.altcoin.popup.XZC.msg=Când folosești {0}, poți folosi numai adresele transparente (ce pot fi urmărite), nu adresele indetectabile, deoarece arbitrul nu va putea verifica tranzacția executată cu adrese ce nu pot fi citite de către un explorator de bloc.
|
||||||
account.altcoin.popup.bch=Bitcoin Cash și Bitcoin Classic suferă de protecție împotriva reluării. Dacă folosești aceste monede, asigură-te că ai luat măsurile necesare de precauție și înțelegi toate implicațiile. Poți suferi pierderi prin trimiterea unei monezi și neintenționat să trimiți aceeași monedă pe celălalt blockchain. Deoarece acele "monede airdrop" au aceeași istorie cu blockchainul Bitcoin există și riscuri de securitate cât și un risc considerabil în pierderea confidențialității.\n\nTe invităm să citești mai multe despre acest subiect pe forumul Bisq: https://forum.bisq.io/t/airdrop-coins-information-thread-bch-btg-bchc
|
account.altcoin.popup.bch=Bitcoin Cash și Bitcoin Clashic suferă de protecție împotriva reluării. Dacă folosești aceste monede, asigură-te că ai luat măsurile necesare de precauție și înțelegi toate implicațiile. Poți suferi pierderi prin trimiterea unei monezi și neintenționat să trimiți aceeași monedă pe celălalt blockchain. Deoarece acele "monede airdrop" au aceeași istorie cu blockchainul Bitcoin există și riscuri de securitate cât și un risc considerabil în pierderea confidențialității.\n\nTe invităm să citești mai multe despre acest subiect pe forumul Bisq: https://forum.bisq.io/t/airdrop-coins-information-thread-bch-btg-bchc
|
||||||
account.altcoin.popup.btg=Deoarece Bitcoin Gold împărtășește același istoric ca și blocukchainul Bitcoin, acesta vine atașat cu anumite riscuri de securitate și un risc considerabil crescut privind pierderea confidențialității. Dacă folosești Bitcoin Gold asigură-te că ai luat măsurile de precauție necesare și înțelegi toate implicațiile.\n\nTe invităm să citești mai multe despre acest subiect pe forumul Bisq: https://forum.bisq.io/t/airdrop-coins-information-thread-bch-btg-bchc
|
account.altcoin.popup.btg=Deoarece Bitcoin Gold împărtășește același istoric ca și blocukchainul Bitcoin, acesta vine atașat cu anumite riscuri de securitate și un risc considerabil crescut privind pierderea confidențialității. Dacă folosești Bitcoin Gold asigură-te că ai luat măsurile de precauție necesare și înțelegi toate implicațiile.\n\nTe invităm să citești mai multe despre acest subiect pe forumul Bisq: https://forum.bisq.io/t/airdrop-coins-information-thread-bch-btg-bchc
|
||||||
|
|
||||||
account.fiat.yourFiatAccounts=Conturile tale de valută\nnațională:
|
account.fiat.yourFiatAccounts=Conturile tale de valută\nnațională:
|
||||||
|
@ -909,7 +915,7 @@ dao.phase.short.BREAK3=
|
||||||
|
|
||||||
|
|
||||||
dao.compensation.active.header=Solicitare de despăgubire activă
|
dao.compensation.active.header=Solicitare de despăgubire activă
|
||||||
dao.compensation.active.selectedRequest=Solicitare de despăgubire selectată
|
dao.compensation.selectedRequest=Solicitare de despăgubire selectată
|
||||||
dao.compensation.active.notOpenAnymore=Această solicitare de despăgubire nu mai este deschisă spre finanțare. Așteaptă până începe următoarea perioadă de finanțare.
|
dao.compensation.active.notOpenAnymore=Această solicitare de despăgubire nu mai este deschisă spre finanțare. Așteaptă până începe următoarea perioadă de finanțare.
|
||||||
dao.compensation.active.remove=Înlătură solicitarea de despăgubire
|
dao.compensation.active.remove=Înlătură solicitarea de despăgubire
|
||||||
dao.compensation.active.remove.failed=Solicitarea de despăgubire nu a putut fi înlăturată.
|
dao.compensation.active.remove.failed=Solicitarea de despăgubire nu a putut fi înlăturată.
|
||||||
|
@ -1155,7 +1161,13 @@ torNetworkSettingWindow.enterBridge=Intordu unul sau mai multe releuri-punți (u
|
||||||
torNetworkSettingWindow.enterBridgePrompt=tastează adresa:portul
|
torNetworkSettingWindow.enterBridgePrompt=tastează adresa:portul
|
||||||
torNetworkSettingWindow.restartInfo=Trebuie să repornești pentru a aplica schimbările
|
torNetworkSettingWindow.restartInfo=Trebuie să repornești pentru a aplica schimbările
|
||||||
torNetworkSettingWindow.openTorWebPage=Deschide pagina web al proiectului Tor
|
torNetworkSettingWindow.openTorWebPage=Deschide pagina web al proiectului Tor
|
||||||
torNetworkSettingWindow.info=Dacă Tor este blocat de către furnizorul tău de internet sau chiar țara ta, poți încerca folosirea de punți Tor.\nVizitează pagina web Tor la: https://bridges.torproject.org/bridges pentru a învăța mai multe despre punți și transporturi conectabile.
|
torNetworkSettingWindow.deleteFiles.header=Probleme de conexiune?
|
||||||
|
torNetworkSettingWindow.deleteFiles.info=Dacă ai probleme de conectare repetate la pornire, ștergerea fișierelor Tor expirate ar putea ajuta. Pentru asta apasă pe butonul de mai jos iar apoi repornește.
|
||||||
|
torNetworkSettingWindow.deleteFiles.button=Șterge fișierele Tor expirate și închide
|
||||||
|
torNetworkSettingWindow.deleteFiles.progress=Tor este în curs de oprire
|
||||||
|
torNetworkSettingWindow.deleteFiles.success=Fișierele Tor expirate au fost șterse cu succes. Reporniți vă rog.
|
||||||
|
torNetworkSettingWindow.bridges.header=Tor este blocat?
|
||||||
|
torNetworkSettingWindow.bridges.info=Dacă Tor este blocat de către furnizorul tău de internet sau chiar țara ta, poți încerca folosirea de punți Tor.\nVizitează pagina web Tor la: https://bridges.torproject.org/bridges pentru a învăța mai multe despre punți și transporturi conectabile.
|
||||||
|
|
||||||
feeOptionWindow.headline=Alege valuta pentru plata comisionului tranzacției
|
feeOptionWindow.headline=Alege valuta pentru plata comisionului tranzacției
|
||||||
feeOptionWindow.info=Poți alege să plătești comisionul de tranzacționare în BSQ sau în BTC. Dacă alegi BSQ te vei bucura de un comision comercial mai redus.
|
feeOptionWindow.info=Poți alege să plătești comisionul de tranzacționare în BSQ sau în BTC. Dacă alegi BSQ te vei bucura de un comision comercial mai redus.
|
||||||
|
@ -1188,6 +1200,7 @@ popup.error.createTx=Eroare în crearea tranzacției: {0}
|
||||||
popup.error.takeOfferRequestFailed=A intervenit o eroare când cineva a încercat să îți preia una din oferte:\n{0}
|
popup.error.takeOfferRequestFailed=A intervenit o eroare când cineva a încercat să îți preia una din oferte:\n{0}
|
||||||
|
|
||||||
error.spvFileCorrupted=A intervenit o eroare la citirea fișierului de blockchain SPV.\nPoate fi din cauză că fișierul blockchainului SPV este corupt.\n\nMesaj de eroare: {0}\n\nDorești să îl ștergi și să pornești o resincronizare?
|
error.spvFileCorrupted=A intervenit o eroare la citirea fișierului de blockchain SPV.\nPoate fi din cauză că fișierul blockchainului SPV este corupt.\n\nMesaj de eroare: {0}\n\nDorești să îl ștergi și să pornești o resincronizare?
|
||||||
|
error.deleteAddressEntryListFailed=Nu s-a putut șterge fișierul AddressEntryList.\nEroare: {0}
|
||||||
|
|
||||||
popup.warning.walletNotInitialized=Portofelul încă nu a fost inițializat
|
popup.warning.walletNotInitialized=Portofelul încă nu a fost inițializat
|
||||||
popup.warning.wrongVersion=Probabil ai versiunea Bisq nepotrivită pentru acest calculator.\nArhitectura calculatorului tău este: {0}.\nBinarul de Bisq instalat este: {1}.\nTe rugăm să închizi și să reinstalezi versiunea corectă ({2}).
|
popup.warning.wrongVersion=Probabil ai versiunea Bisq nepotrivită pentru acest calculator.\nArhitectura calculatorului tău este: {0}.\nBinarul de Bisq instalat este: {1}.\nTe rugăm să închizi și să reinstalezi versiunea corectă ({2}).
|
||||||
|
@ -1223,7 +1236,7 @@ popup.warning.lockedUpFunds=Ai fonduri blocate dintr-o tranzacție nereușită.\
|
||||||
popup.warning.offerWithoutAccountAgeWitness=Oferta ta cu codul de ofertare {0} a fost creată cu o versiune mai veche de Bisq care nu suporta verificarea vechimii contului.\nDupă 1 februarie 2018 astfel de oferte nu mai sunt valabile.\n\nTe rugăm elimină oferta și creează una nouă.
|
popup.warning.offerWithoutAccountAgeWitness=Oferta ta cu codul de ofertare {0} a fost creată cu o versiune mai veche de Bisq care nu suporta verificarea vechimii contului.\nDupă 1 februarie 2018 astfel de oferte nu mai sunt valabile.\n\nTe rugăm elimină oferta și creează una nouă.
|
||||||
popup.warning.offerWithoutAccountAgeWitness.confirm=Înlătură oferta
|
popup.warning.offerWithoutAccountAgeWitness.confirm=Înlătură oferta
|
||||||
popup.warning.nodeBanned=Unul dintre nodurile {0} a fost interzise. Te rugăm să repornești aplicația pentru a te asigura că nu te conectezi la nodul interzis.
|
popup.warning.nodeBanned=Unul dintre nodurile {0} a fost interzise. Te rugăm să repornești aplicația pentru a te asigura că nu te conectezi la nodul interzis.
|
||||||
popup.warning.priceRelay=reluare preț
|
popup.warning.priceRelay=preț releu
|
||||||
popup.warning.seed=nucleu
|
popup.warning.seed=nucleu
|
||||||
|
|
||||||
popup.info.securityDepositInfo=Pentru a asigura că ambii comercianți respectă protocolul de tranzacționare, ambii trebuie să plătească un depozit de securitate.\n\nDepozitul va rămâne în portofelul tău local de tranzacționare până când oferta ajunge acceptată de un alt comerciant.\nAceasta va fi returnată după ce tranzacția a fost finalizată cu succes.\n\nReține că trebuie să păstrezi aplicația deschisă dacă ai o ofertă deschisă. Atunci când un alt comerciant dorește să îți accepte oferta, este necesar ca aplicația ta să fie online pentru executarea protocolului de tranzacționare.\nAsigură-te că ai modul în așteptare dezactivat, deoarece acesta ar deconecta rețeaua (starea în așteptare a monitorului nu reprezintă o problemă).
|
popup.info.securityDepositInfo=Pentru a asigura că ambii comercianți respectă protocolul de tranzacționare, ambii trebuie să plătească un depozit de securitate.\n\nDepozitul va rămâne în portofelul tău local de tranzacționare până când oferta ajunge acceptată de un alt comerciant.\nAceasta va fi returnată după ce tranzacția a fost finalizată cu succes.\n\nReține că trebuie să păstrezi aplicația deschisă dacă ai o ofertă deschisă. Atunci când un alt comerciant dorește să îți accepte oferta, este necesar ca aplicația ta să fie online pentru executarea protocolului de tranzacționare.\nAsigură-te că ai modul în așteptare dezactivat, deoarece acesta ar deconecta rețeaua (starea în așteptare a monitorului nu reprezintă o problemă).
|
||||||
|
@ -1349,6 +1362,7 @@ navigation.arbitratorSelection=\"Selecție arbitrii\"
|
||||||
navigation.funds.availableForWithdrawal=\"Fonduri/Trimite fonduri\"
|
navigation.funds.availableForWithdrawal=\"Fonduri/Trimite fonduri\"
|
||||||
navigation.portfolio.myOpenOffers=\"Portofoliu/Ofertele mele deschise\"
|
navigation.portfolio.myOpenOffers=\"Portofoliu/Ofertele mele deschise\"
|
||||||
navigation.portfolio.pending=\"Portofoliu/Tranzacții deschise\"
|
navigation.portfolio.pending=\"Portofoliu/Tranzacții deschise\"
|
||||||
|
navigation.portfolio.closedTrades=\"Portofoliu/Istoric\"
|
||||||
navigation.funds.depositFunds=\"Fonduri/Încasare fonduri\"
|
navigation.funds.depositFunds=\"Fonduri/Încasare fonduri\"
|
||||||
navigation.settings.preferences=\"Setări/Preferințe\"
|
navigation.settings.preferences=\"Setări/Preferințe\"
|
||||||
navigation.funds.transactions=\"Fonduri/Tranzacții\"
|
navigation.funds.transactions=\"Fonduri/Tranzacții\"
|
||||||
|
@ -1391,13 +1405,6 @@ LTC_TESTNET=Litecoin Testnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Litecoin Regtest
|
LTC_REGTEST=Litecoin Regtest
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Dogecoin Mainnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Dogecoin Testnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Dogecoin Regtest
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=Rețeaua principală DASH
|
DASH_MAINNET=Rețeaua principală DASH
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1470,7 +1477,6 @@ payment.restore.default=Nu, restaurează valuta prestabilită
|
||||||
payment.email=E-mail:
|
payment.email=E-mail:
|
||||||
payment.country=Țara:
|
payment.country=Țara:
|
||||||
payment.extras=Cerințe extra:
|
payment.extras=Cerințe extra:
|
||||||
payment.us.info=Transferul bancar cu WIRE sau ACH nu sunt acceptate pentru SUA, deoarece WIRE este prea scump și ACH are un risc prea mare de rambursare.\n\nTe rugăm să folosești mai degrabă metode de plată disponibile în SUA precum: \"Zelle (ClearXchange)\", \"Chase QuickPay\", \"Ordin de plată poștală SUA\" sau \"Depozit Numerar\".
|
|
||||||
payment.email.mobile=E-mail sau nr. mobil:
|
payment.email.mobile=E-mail sau nr. mobil:
|
||||||
payment.altcoin.address=Adresă altcoin:
|
payment.altcoin.address=Adresă altcoin:
|
||||||
payment.altcoin=Altcoin:
|
payment.altcoin=Altcoin:
|
||||||
|
@ -1478,7 +1484,7 @@ payment.select.altcoin=Selectează sau caută altcoin
|
||||||
payment.secret=Întrebarea secretă:
|
payment.secret=Întrebarea secretă:
|
||||||
payment.answer=Răspuns:
|
payment.answer=Răspuns:
|
||||||
payment.wallet=Cod portofel:
|
payment.wallet=Cod portofel:
|
||||||
payment.supported.okpay=Valute acceptate:
|
payment.supportedCurrencies=Valute acceptate:
|
||||||
payment.limitations=Limite:
|
payment.limitations=Limite:
|
||||||
payment.salt=Sare pentru verificarea vechimii contului:
|
payment.salt=Sare pentru verificarea vechimii contului:
|
||||||
payment.error.noHexSalt=Sarea trebuie să fie în format HEX.\nSe recomandă editarea câmpul de sare dacă dorești să transferi sarea dintr-un cont vechi pentru a păstra vechimea contului. Vechimea contului este verificată folosind sarea contului și datele de identificare ale acestuia (de exemplu codul IBAN).
|
payment.error.noHexSalt=Sarea trebuie să fie în format HEX.\nSe recomandă editarea câmpul de sare dacă dorești să transferi sarea dintr-un cont vechi pentru a păstra vechimea contului. Vechimea contului este verificată folosind sarea contului și datele de identificare ale acestuia (de exemplu codul IBAN).
|
||||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=продать
|
||||||
shared.buying=покупка
|
shared.buying=покупка
|
||||||
shared.selling=продажа
|
shared.selling=продажа
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offers=предложения
|
shared.oneOffer=предложение
|
||||||
|
shared.multipleOffers=предложения
|
||||||
shared.Offer=Предложение
|
shared.Offer=Предложение
|
||||||
shared.openOffers=открыть предложения
|
shared.openOffers=открыть предложения
|
||||||
shared.trades=сделок
|
shared.trades=сделок
|
||||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Сделки
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Книга предложений на {0}
|
market.offerBook.chart.title=Книга предложений на {0}
|
||||||
market.offerBook.leftButtonAltcoin=Я хочу купить {0} (продать {1})
|
market.offerBook.buyAltcoin=Я хочу купить {0} (продать {1})
|
||||||
market.offerBook.rightButtonAltcoin=Я хочу продать {0} (купить {1})
|
market.offerBook.sellAltcoin=Я хочу продать {0} (купить {1})
|
||||||
market.offerBook.leftButtonFiat=Я хочу купить {0} за {1}
|
market.offerBook.buyWithFiat=Купить {0}
|
||||||
market.offerBook.rightButtonFiat=Я хочу продать {0} за {1}
|
market.offerBook.sellWithFiat=Продать {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Продать {0} до
|
market.offerBook.sellOffersHeaderLabel=Продать {0} до
|
||||||
market.offerBook.buyOffersHeaderLabel=Купите {0} из
|
market.offerBook.buyOffersHeaderLabel=Купите {0} из
|
||||||
market.offerBook.buy=Я хочу купить Биткоин
|
market.offerBook.buy=Я хочу купить Биткоин
|
||||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Текущие запросы
|
||||||
dao.compensation.menuItem.pastRequests=Прошлые запросы
|
dao.compensation.menuItem.pastRequests=Прошлые запросы
|
||||||
|
|
||||||
dao.compensation.active.header=Запрос активной компенсации
|
dao.compensation.active.header=Запрос активной компенсации
|
||||||
dao.compensation.active.selectedRequest=Запрос выбранной компенсации
|
dao.compensation.selectedRequest=Запрос выбранной компенсации
|
||||||
dao.compensation.active.notOpenAnymore=Этот запрос компенсации более не открыт для оплаты. Пожалуйста подождите до начала следующего периода оплаты.
|
dao.compensation.active.notOpenAnymore=Этот запрос компенсации более не открыт для оплаты. Пожалуйста подождите до начала следующего периода оплаты.
|
||||||
dao.compensation.active.vote=Голосование по запросу компенсации
|
dao.compensation.active.vote=Голосование по запросу компенсации
|
||||||
dao.compensation.active.fund=Найти запрос компенсации
|
dao.compensation.active.fund=Найти запрос компенсации
|
||||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Litecoin Тест-сеть
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Litecoin Регистр.-тест
|
LTC_REGTEST=Litecoin Регистр.-тест
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Dogecoin основная сеть
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Dogecoin тест-сеть
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Dogecoin Регистр. -тест
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=DASH Mainnet
|
DASH_MAINNET=DASH Mainnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Нет, восстановить валюту по ум
|
||||||
payment.email=Электронная почта:
|
payment.email=Электронная почта:
|
||||||
payment.country=Страна:
|
payment.country=Страна:
|
||||||
payment.extras=Дополнительные требования:
|
payment.extras=Дополнительные требования:
|
||||||
payment.us.info=Bank transfer with WIRE or ACH is not supported for the US because WIRE is too expensive and ACH has a high chargeback risk.\n\nPlease use one of the available payment methods for the US like: \"Zelle (ClearXchange)\", \"Chase QuickPay\", \"US Postal Money Order\" or \"Cash Deposit\".
|
|
||||||
payment.email.mobile=Электронная почта или номер моб. тел.:
|
payment.email.mobile=Электронная почта или номер моб. тел.:
|
||||||
payment.altcoin.address=Альткоин-адрес:
|
payment.altcoin.address=Альткоин-адрес:
|
||||||
payment.altcoin=Альткоин:
|
payment.altcoin=Альткоин:
|
||||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Выбор или поиск альткоин
|
||||||
payment.secret=Секретный вопрос:
|
payment.secret=Секретный вопрос:
|
||||||
payment.answer=Ответ:
|
payment.answer=Ответ:
|
||||||
payment.wallet=Идентификатор кошелька:
|
payment.wallet=Идентификатор кошелька:
|
||||||
payment.supported.okpay=Поддерживаемые валюты:
|
payment.supportedCurrencies=Поддерживаемые валюты:
|
||||||
payment.limitations=Ограничения:
|
payment.limitations=Ограничения:
|
||||||
payment.accept.euro=Принять сделки от этих стран Евро:
|
payment.accept.euro=Принять сделки от этих стран Евро:
|
||||||
payment.accept.nonEuro=Примите сделки от этих не- Евро стран:
|
payment.accept.nonEuro=Примите сделки от этих не- Евро стран:
|
||||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=prodaj
|
||||||
shared.buying=kupujem
|
shared.buying=kupujem
|
||||||
shared.selling=prodajem
|
shared.selling=prodajem
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offers=ponude
|
shared.oneOffer=ponuda
|
||||||
|
shared.multipleOffers=ponude
|
||||||
shared.Offer=Ponuda
|
shared.Offer=Ponuda
|
||||||
shared.openOffers=otvorene ponude
|
shared.openOffers=otvorene ponude
|
||||||
shared.trades=trgovine
|
shared.trades=trgovine
|
||||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Trgovine
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title=Knjiga ponuda za {0}
|
market.offerBook.chart.title=Knjiga ponuda za {0}
|
||||||
market.offerBook.leftButtonAltcoin=Želim da kupim {0} (prodaja {1})
|
market.offerBook.buyAltcoin=Želim da kupim {0} (prodaja {1})
|
||||||
market.offerBook.rightButtonAltcoin=Želim da prodam {0} (kupovina {1})
|
market.offerBook.sellAltcoin=Želim da prodam {0} (kupovina {1})
|
||||||
market.offerBook.leftButtonFiat=Želim da kupim {0} sa {1}
|
market.offerBook.buyWithFiat=Kupi {0}
|
||||||
market.offerBook.rightButtonFiat=Želim da prodam {0} za {1}
|
market.offerBook.sellWithFiat=Prodaj {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Prodaj {0} u
|
market.offerBook.sellOffersHeaderLabel=Prodaj {0} u
|
||||||
market.offerBook.buyOffersHeaderLabel=Kupite {0} iz
|
market.offerBook.buyOffersHeaderLabel=Kupite {0} iz
|
||||||
market.offerBook.buy=Želim da kupim bitkoin
|
market.offerBook.buy=Želim da kupim bitkoin
|
||||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Aktivni zahtevi
|
||||||
dao.compensation.menuItem.pastRequests=Raniji zahtevi
|
dao.compensation.menuItem.pastRequests=Raniji zahtevi
|
||||||
|
|
||||||
dao.compensation.active.header=Aktivni zahtevi za nadoknadu
|
dao.compensation.active.header=Aktivni zahtevi za nadoknadu
|
||||||
dao.compensation.active.selectedRequest=Izabrani zahtevi za nadoknadu
|
dao.compensation.selectedRequest=Izabrani zahtevi za nadoknadu
|
||||||
dao.compensation.active.notOpenAnymore=Ovaj zahtev za nadoknadu nije više otvoren za finansiranje. Molimo sačekajte do početka sledećeg perioda finansiranja.
|
dao.compensation.active.notOpenAnymore=Ovaj zahtev za nadoknadu nije više otvoren za finansiranje. Molimo sačekajte do početka sledećeg perioda finansiranja.
|
||||||
dao.compensation.active.vote=Glasaj na zahtev za nadoknadu
|
dao.compensation.active.vote=Glasaj na zahtev za nadoknadu
|
||||||
dao.compensation.active.fund=Finansiraj zahtev za nadoknadu
|
dao.compensation.active.fund=Finansiraj zahtev za nadoknadu
|
||||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Lajtkoin Testnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=Lajtkoin Regtest
|
LTC_REGTEST=Lajtkoin Regtest
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=Dogekoin Mejnnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=Dogekoin Testnet
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=Dogekoin Regtest
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=DASH Mainnet
|
DASH_MAINNET=DASH Mainnet
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Ne, vrati podrazumevanu valutu
|
||||||
payment.email=Email:
|
payment.email=Email:
|
||||||
payment.country=Država:
|
payment.country=Država:
|
||||||
payment.extras=Dodatni zahtevi:
|
payment.extras=Dodatni zahtevi:
|
||||||
payment.us.info=Bank transfer with WIRE or ACH is not supported for the US because WIRE is too expensive and ACH has a high chargeback risk.\n\nPlease use one of the available payment methods for the US like: \"Zelle (ClearXchange)\", \"Chase QuickPay\", \"US Postal Money Order\" or \"Cash Deposit\".
|
|
||||||
payment.email.mobile=Email ili br. mobilnog:
|
payment.email.mobile=Email ili br. mobilnog:
|
||||||
payment.altcoin.address=Altkoin adresa:
|
payment.altcoin.address=Altkoin adresa:
|
||||||
payment.altcoin=Altkoin:
|
payment.altcoin=Altkoin:
|
||||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Izaberi ili traži altkoin
|
||||||
payment.secret=Tajno pitanje:
|
payment.secret=Tajno pitanje:
|
||||||
payment.answer=Odgovor:
|
payment.answer=Odgovor:
|
||||||
payment.wallet=ID novčanika:
|
payment.wallet=ID novčanika:
|
||||||
payment.supported.okpay=Podržane valute:
|
payment.supportedCurrencies=Podržane valute:
|
||||||
payment.limitations=Ograničenja:
|
payment.limitations=Ograničenja:
|
||||||
payment.accept.euro=Prihvati trgovine od ovih Euro država:
|
payment.accept.euro=Prihvati trgovine od ovih Euro država:
|
||||||
payment.accept.nonEuro=Prihvati trgovine od ovih ne-Euro država:
|
payment.accept.nonEuro=Prihvati trgovine od ovih ne-Euro država:
|
||||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=卖
|
||||||
shared.buying=买入
|
shared.buying=买入
|
||||||
shared.selling=卖出
|
shared.selling=卖出
|
||||||
shared.P2P=P2P
|
shared.P2P=P2P
|
||||||
shared.offers=委托
|
shared.oneOffer=委托
|
||||||
|
shared.multipleOffers=委托
|
||||||
shared.Offer=委托
|
shared.Offer=委托
|
||||||
shared.openOffers=发布中委托
|
shared.openOffers=发布中委托
|
||||||
shared.trades=交易
|
shared.trades=交易
|
||||||
|
@ -254,10 +255,10 @@ market.tabs.trades=行情图
|
||||||
|
|
||||||
# OfferBookChartView
|
# OfferBookChartView
|
||||||
market.offerBook.chart.title={0} 的委托列表
|
market.offerBook.chart.title={0} 的委托列表
|
||||||
market.offerBook.leftButtonAltcoin=我想要买入 {0} (卖出 {1})
|
market.offerBook.buyAltcoin=我想要买入 {0} (卖出 {1})
|
||||||
market.offerBook.rightButtonAltcoin=我想要卖出 {0} (买入 {1})
|
market.offerBook.sellAltcoin=我想要卖出 {0} (买入 {1})
|
||||||
market.offerBook.leftButtonFiat=我想要用 {1} 买入 {0}
|
market.offerBook.buyWithFiat=购买{0}
|
||||||
market.offerBook.rightButtonFiat=我想要用 {1} 卖出 {0}
|
market.offerBook.sellWithFiat=出售 {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=出售 {0} 到
|
market.offerBook.sellOffersHeaderLabel=出售 {0} 到
|
||||||
market.offerBook.buyOffersHeaderLabel=从中购买 {0}
|
market.offerBook.buyOffersHeaderLabel=从中购买 {0}
|
||||||
market.offerBook.buy=我想要买入比特币
|
market.offerBook.buy=我想要买入比特币
|
||||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=活动要求
|
||||||
dao.compensation.menuItem.pastRequests=过期要求
|
dao.compensation.menuItem.pastRequests=过期要求
|
||||||
|
|
||||||
dao.compensation.active.header=活动赔偿要求
|
dao.compensation.active.header=活动赔偿要求
|
||||||
dao.compensation.active.selectedRequest=选定赔偿要求
|
dao.compensation.selectedRequest=选定赔偿要求
|
||||||
dao.compensation.active.notOpenAnymore=这笔补偿申请不再对资金开放。 请等到下一个资助期开始。
|
dao.compensation.active.notOpenAnymore=这笔补偿申请不再对资金开放。 请等到下一个资助期开始。
|
||||||
dao.compensation.active.vote=在赔偿要求上投票
|
dao.compensation.active.vote=在赔偿要求上投票
|
||||||
dao.compensation.active.fund=为赔偿要求充值
|
dao.compensation.active.fund=为赔偿要求充值
|
||||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=莱特币测试网络
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
LTC_REGTEST=莱特币回归测试
|
LTC_REGTEST=莱特币回归测试
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_MAINNET=狗狗币主干网络
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_TESTNET=狗狗币测试网络
|
|
||||||
# suppress inspection "UnusedProperty"
|
|
||||||
DOGE_REGTEST=狗狗币回归测试
|
|
||||||
|
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
DASH_MAINNET=达世币主干网络
|
DASH_MAINNET=达世币主干网络
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=不,恢复默认值
|
||||||
payment.email=电子邮箱:
|
payment.email=电子邮箱:
|
||||||
payment.country=国家:
|
payment.country=国家:
|
||||||
payment.extras=额外要求:
|
payment.extras=额外要求:
|
||||||
payment.us.info=不支持美国银行使用WIRE或ACH方式的转账. 因为WIRE方式太昂贵而ACH方式有很高的撤销风险.\n请使用\"ClearXchange\", \"US Postal Money Order\" 或 \"Cash/ATM Deposit\" 支付方式.
|
|
||||||
payment.email.mobile=电子邮箱或手机号码:
|
payment.email.mobile=电子邮箱或手机号码:
|
||||||
payment.altcoin.address=数字货币地址:
|
payment.altcoin.address=数字货币地址:
|
||||||
payment.altcoin=数字货币:
|
payment.altcoin=数字货币:
|
||||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=选择或搜索数字货币
|
||||||
payment.secret=密保问题:
|
payment.secret=密保问题:
|
||||||
payment.answer=答案:
|
payment.answer=答案:
|
||||||
payment.wallet=钱包ID:
|
payment.wallet=钱包ID:
|
||||||
payment.supported.okpay=支持的货币:
|
payment.supportedCurrencies=支持的货币:
|
||||||
payment.limitations=限制条件:
|
payment.limitations=限制条件:
|
||||||
payment.accept.euro=接受来自这些欧元国家的交易:
|
payment.accept.euro=接受来自这些欧元国家的交易:
|
||||||
payment.accept.nonEuro=接受来自这些非欧元国家的交易:
|
payment.accept.nonEuro=接受来自这些非欧元国家的交易:
|
||||||
|
|
|
@ -45,7 +45,6 @@ payment.email=Email:
|
||||||
payment.country=Country:
|
payment.country=Country:
|
||||||
payment.owner.email=Account holder email:
|
payment.owner.email=Account holder email:
|
||||||
payment.extras=Extra requirements:
|
payment.extras=Extra requirements:
|
||||||
payment.us.info=Bank transfer with WIRE or ACH is not supported for the US because WIRE is too expensive and ACH has a high chargeback risk.\n\nPlease use payment methods \"Zelle (ClearXchange)\", \"US Postal Money Order\" or \"Cash Deposit\" instead.
|
|
||||||
payment.email.mobile=Email or mobile no.:
|
payment.email.mobile=Email or mobile no.:
|
||||||
payment.altcoin.address=Altcoin address:
|
payment.altcoin.address=Altcoin address:
|
||||||
payment.address=address:
|
payment.address=address:
|
||||||
|
@ -253,7 +252,8 @@ shared.sellingCurrency=Vendre {0} (Acheter des bitcoins)
|
||||||
shared.buying=acheter
|
shared.buying=acheter
|
||||||
shared.selling=vendre
|
shared.selling=vendre
|
||||||
shared.P2P=De pair à pair
|
shared.P2P=De pair à pair
|
||||||
shared.offers=Offres
|
shared.oneOffer=offre
|
||||||
|
shared.multipleOffers=offres
|
||||||
shared.Offer=Offre
|
shared.Offer=Offre
|
||||||
shared.openOffers=Ouvrir les offres
|
shared.openOffers=Ouvrir les offres
|
||||||
shared.openTrades=Opérations ouvertes
|
shared.openTrades=Opérations ouvertes
|
||||||
|
@ -430,10 +430,10 @@ market.tabs.offerBook=Livre d'offre
|
||||||
market.tabs.spread=Distribution
|
market.tabs.spread=Distribution
|
||||||
market.tabs.trades=Échanges
|
market.tabs.trades=Échanges
|
||||||
market.offerBook.chart.title=Livre d'offre pour {0}
|
market.offerBook.chart.title=Livre d'offre pour {0}
|
||||||
market.offerBook.leftButtonAltcoin=Je veux acheter {0} (vendre {1})
|
market.offerBook.buyAltcoin=Je veux acheter {0} (vendre {1})
|
||||||
market.offerBook.rightButtonAltcoin=Je veux vendre {0} (buy {1})
|
market.offerBook.sellAltcoin=Je veux vendre {0} (buy {1})
|
||||||
market.offerBook.leftButtonFiat=Je veux acheter {0} avec {1}
|
market.offerBook.buyWithFiat=Acheter {0}
|
||||||
market.offerBook.rightButtonFiat=Je veux vendre {0} pour {1}
|
market.offerBook.sellWithFiat=Vendre {0}
|
||||||
market.offerBook.sellOffersHeaderLabel=Vendre {0} à
|
market.offerBook.sellOffersHeaderLabel=Vendre {0} à
|
||||||
market.offerBook.buyOffersHeaderLabel=Acheter {0} à partir de
|
market.offerBook.buyOffersHeaderLabel=Acheter {0} à partir de
|
||||||
market.offerBook.buy=Je veux acheter du bitcoin
|
market.offerBook.buy=Je veux acheter du bitcoin
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>parent</artifactId>
|
<artifactId>parent</artifactId>
|
||||||
<groupId>io.bisq</groupId>
|
<groupId>io.bisq.exchange</groupId>
|
||||||
<version>0.6.5</version>
|
<version>-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
10
core/pom.xml
10
core/pom.xml
|
@ -5,25 +5,25 @@
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>parent</artifactId>
|
<artifactId>parent</artifactId>
|
||||||
<groupId>io.bisq</groupId>
|
<groupId>io.bisq.exchange</groupId>
|
||||||
<version>0.6.5</version>
|
<version>-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>core</artifactId>
|
<artifactId>core</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.bisq</groupId>
|
<groupId>io.bisq.exchange</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.bisq</groupId>
|
<groupId>io.bisq.exchange</groupId>
|
||||||
<artifactId>consensus</artifactId>
|
<artifactId>consensus</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.bisq</groupId>
|
<groupId>io.bisq.exchange</groupId>
|
||||||
<artifactId>network</artifactId>
|
<artifactId>network</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
|
@ -49,10 +49,7 @@ public class AlertManager {
|
||||||
private final ObjectProperty<Alert> alertMessageProperty = new SimpleObjectProperty<>();
|
private final ObjectProperty<Alert> alertMessageProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
// Pub key for developer global alert message
|
// Pub key for developer global alert message
|
||||||
@SuppressWarnings("ConstantConditions")
|
private final String pubKeyAsHex;
|
||||||
private static final String pubKeyAsHex = DevEnv.USE_DEV_PRIVILEGE_KEYS ?
|
|
||||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
|
||||||
"036d8a1dfcb406886037d2381da006358722823e1940acc2598c844bbc0fd1026f";
|
|
||||||
private ECKey alertSigningKey;
|
private ECKey alertSigningKey;
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,7 +58,11 @@ public class AlertManager {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(AppOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
|
public AlertManager(P2PService p2PService,
|
||||||
|
KeyRing keyRing,
|
||||||
|
User user,
|
||||||
|
@Named(AppOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg,
|
||||||
|
@Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
@ -88,6 +89,9 @@ public class AlertManager {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
pubKeyAsHex = useDevPrivilegeKeys ?
|
||||||
|
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||||
|
"036d8a1dfcb406886037d2381da006358722823e1940acc2598c844bbc0fd1026f";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,10 +50,7 @@ public class PrivateNotificationManager {
|
||||||
private final ObjectProperty<PrivateNotificationPayload> privateNotificationMessageProperty = new SimpleObjectProperty<>();
|
private final ObjectProperty<PrivateNotificationPayload> privateNotificationMessageProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
// Pub key for developer global privateNotification message
|
// Pub key for developer global privateNotification message
|
||||||
@SuppressWarnings("ConstantConditions")
|
private final String pubKeyAsHex;
|
||||||
private static final String pubKeyAsHex = DevEnv.USE_DEV_PRIVILEGE_KEYS ?
|
|
||||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
|
||||||
"02ba7c5de295adfe57b60029f3637a2c6b1d0e969a8aaefb9e0ddc3a7963f26925";
|
|
||||||
|
|
||||||
private ECKey privateNotificationSigningKey;
|
private ECKey privateNotificationSigningKey;
|
||||||
private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
|
private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
|
||||||
|
@ -64,7 +61,10 @@ public class PrivateNotificationManager {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(AppOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
|
public PrivateNotificationManager(P2PService p2PService,
|
||||||
|
KeyRing keyRing,
|
||||||
|
@Named(AppOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg,
|
||||||
|
@Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
|
|
||||||
|
@ -72,6 +72,9 @@ public class PrivateNotificationManager {
|
||||||
this.p2PService.addDecryptedDirectMessageListener(this::handleMessage);
|
this.p2PService.addDecryptedDirectMessageListener(this::handleMessage);
|
||||||
this.p2PService.addDecryptedMailboxListener(this::handleMessage);
|
this.p2PService.addDecryptedMailboxListener(this::handleMessage);
|
||||||
}
|
}
|
||||||
|
pubKeyAsHex = useDevPrivilegeKeys ?
|
||||||
|
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||||
|
"02ba7c5de295adfe57b60029f3637a2c6b1d0e969a8aaefb9e0ddc3a7963f26925";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress senderNodeAddress) {
|
private void handleMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress senderNodeAddress) {
|
||||||
|
|
|
@ -8,4 +8,6 @@ public class AppOptionKeys {
|
||||||
public static final String MAX_MEMORY = "maxMemory";
|
public static final String MAX_MEMORY = "maxMemory";
|
||||||
public static final String DUMP_STATISTICS = "dumpStatistics";
|
public static final String DUMP_STATISTICS = "dumpStatistics";
|
||||||
public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg";
|
public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg";
|
||||||
|
public static final String USE_DEV_PRIVILEGE_KEYS = "useDevPrivilegeKeys";
|
||||||
|
public static final String USE_DEV_MODE = "useDevMode";
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class AppSetupWithP2P extends AppSetup {
|
||||||
protected final FilterManager filterManager;
|
protected final FilterManager filterManager;
|
||||||
protected BooleanProperty p2pNetWorkReady;
|
protected BooleanProperty p2pNetWorkReady;
|
||||||
protected final TradeStatisticsManager tradeStatisticsManager;
|
protected final TradeStatisticsManager tradeStatisticsManager;
|
||||||
|
protected ArrayList<PersistedDataHost> persistedDataHosts;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AppSetupWithP2P(EncryptionService encryptionService,
|
public AppSetupWithP2P(EncryptionService encryptionService,
|
||||||
|
@ -50,16 +51,16 @@ public class AppSetupWithP2P extends AppSetup {
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
AccountAgeWitnessService accountAgeWitnessService,
|
AccountAgeWitnessService accountAgeWitnessService,
|
||||||
FilterManager filterManager) {
|
FilterManager filterManager) {
|
||||||
super(encryptionService,keyRing);
|
super(encryptionService, keyRing);
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
|
this.persistedDataHosts = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initPersistedDataHosts() {
|
public void initPersistedDataHosts() {
|
||||||
ArrayList<PersistedDataHost> persistedDataHosts = new ArrayList<>();
|
|
||||||
persistedDataHosts.add(p2PService);
|
persistedDataHosts.add(p2PService);
|
||||||
|
|
||||||
// we apply at startup the reading of persisted data but don't want to get it triggered in the constructor
|
// we apply at startup the reading of persisted data but don't want to get it triggered in the constructor
|
||||||
|
|
|
@ -19,6 +19,7 @@ package io.bisq.core.app;
|
||||||
|
|
||||||
import io.bisq.common.crypto.KeyRing;
|
import io.bisq.common.crypto.KeyRing;
|
||||||
import io.bisq.core.dao.DaoManager;
|
import io.bisq.core.dao.DaoManager;
|
||||||
|
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||||
import io.bisq.core.filter.FilterManager;
|
import io.bisq.core.filter.FilterManager;
|
||||||
import io.bisq.core.payment.AccountAgeWitnessService;
|
import io.bisq.core.payment.AccountAgeWitnessService;
|
||||||
import io.bisq.core.trade.statistics.TradeStatisticsManager;
|
import io.bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
|
@ -39,7 +40,8 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
AccountAgeWitnessService accountAgeWitnessService,
|
AccountAgeWitnessService accountAgeWitnessService,
|
||||||
FilterManager filterManager,
|
FilterManager filterManager,
|
||||||
DaoManager daoManager) {
|
DaoManager daoManager,
|
||||||
|
CompensationRequestManager compensationRequestManager) {
|
||||||
super(encryptionService,
|
super(encryptionService,
|
||||||
keyRing,
|
keyRing,
|
||||||
p2PService,
|
p2PService,
|
||||||
|
@ -47,6 +49,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||||
accountAgeWitnessService,
|
accountAgeWitnessService,
|
||||||
filterManager);
|
filterManager);
|
||||||
this.daoManager = daoManager;
|
this.daoManager = daoManager;
|
||||||
|
this.persistedDataHosts.add(compensationRequestManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -177,10 +177,10 @@ public class BisqEnvironment extends StandardEnvironment {
|
||||||
@Getter
|
@Getter
|
||||||
protected List<String> bannedSeedNodes, bannedBtcNodes, bannedPriceRelayNodes;
|
protected List<String> bannedSeedNodes, bannedBtcNodes, bannedPriceRelayNodes;
|
||||||
|
|
||||||
protected final String btcNodes, seedNodes, ignoreDevMsg, useTorForBtc, rpcUser, rpcPassword,
|
protected final String btcNodes, seedNodes, ignoreDevMsg, useDevPrivilegeKeys, useDevMode, useTorForBtc, rpcUser, rpcPassword,
|
||||||
rpcPort, rpcBlockNotificationPort, dumpBlockchainData, fullDaoNode,
|
rpcPort, rpcBlockNotificationPort, dumpBlockchainData, fullDaoNode,
|
||||||
myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress,
|
myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress,
|
||||||
socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, regTestBsqGenesisTxId;
|
socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, genesisTxId, genesisBlockHeight;
|
||||||
|
|
||||||
|
|
||||||
public BisqEnvironment(OptionSet options) {
|
public BisqEnvironment(OptionSet options) {
|
||||||
|
@ -212,6 +212,12 @@ public class BisqEnvironment extends StandardEnvironment {
|
||||||
ignoreDevMsg = commandLineProperties.containsProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY) ?
|
ignoreDevMsg = commandLineProperties.containsProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY) ?
|
||||||
(String) commandLineProperties.getProperty(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) :
|
||||||
|
"";
|
||||||
|
useDevMode = commandLineProperties.containsProperty(AppOptionKeys.USE_DEV_MODE) ?
|
||||||
|
(String) commandLineProperties.getProperty(AppOptionKeys.USE_DEV_MODE) :
|
||||||
|
"";
|
||||||
dumpStatistics = commandLineProperties.containsProperty(AppOptionKeys.DUMP_STATISTICS) ?
|
dumpStatistics = commandLineProperties.containsProperty(AppOptionKeys.DUMP_STATISTICS) ?
|
||||||
(String) commandLineProperties.getProperty(AppOptionKeys.DUMP_STATISTICS) :
|
(String) commandLineProperties.getProperty(AppOptionKeys.DUMP_STATISTICS) :
|
||||||
"";
|
"";
|
||||||
|
@ -259,8 +265,11 @@ public class BisqEnvironment extends StandardEnvironment {
|
||||||
fullDaoNode = commandLineProperties.containsProperty(DaoOptionKeys.FULL_DAO_NODE) ?
|
fullDaoNode = commandLineProperties.containsProperty(DaoOptionKeys.FULL_DAO_NODE) ?
|
||||||
(String) commandLineProperties.getProperty(DaoOptionKeys.FULL_DAO_NODE) :
|
(String) commandLineProperties.getProperty(DaoOptionKeys.FULL_DAO_NODE) :
|
||||||
"";
|
"";
|
||||||
regTestBsqGenesisTxId = commandLineProperties.containsProperty(DaoOptionKeys.REG_TEST_GENESIS_TX_ID) ?
|
genesisTxId = commandLineProperties.containsProperty(DaoOptionKeys.GENESIS_TX_ID) ?
|
||||||
(String) commandLineProperties.getProperty(DaoOptionKeys.REG_TEST_GENESIS_TX_ID) :
|
(String) commandLineProperties.getProperty(DaoOptionKeys.GENESIS_TX_ID) :
|
||||||
|
"";
|
||||||
|
genesisBlockHeight = commandLineProperties.containsProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) ?
|
||||||
|
(String) commandLineProperties.getProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) :
|
||||||
"";
|
"";
|
||||||
|
|
||||||
btcNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_NODES) ?
|
btcNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_NODES) ?
|
||||||
|
@ -403,6 +412,8 @@ public class BisqEnvironment extends StandardEnvironment {
|
||||||
|
|
||||||
setProperty(AppOptionKeys.APP_DATA_DIR_KEY, appDataDir);
|
setProperty(AppOptionKeys.APP_DATA_DIR_KEY, appDataDir);
|
||||||
setProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
|
setProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
|
||||||
|
setProperty(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS, useDevPrivilegeKeys);
|
||||||
|
setProperty(AppOptionKeys.USE_DEV_MODE, useDevMode);
|
||||||
setProperty(AppOptionKeys.DUMP_STATISTICS, dumpStatistics);
|
setProperty(AppOptionKeys.DUMP_STATISTICS, dumpStatistics);
|
||||||
setProperty(AppOptionKeys.APP_NAME_KEY, appName);
|
setProperty(AppOptionKeys.APP_NAME_KEY, appName);
|
||||||
setProperty(AppOptionKeys.MAX_MEMORY, maxMemory);
|
setProperty(AppOptionKeys.MAX_MEMORY, maxMemory);
|
||||||
|
@ -415,7 +426,8 @@ public class BisqEnvironment extends StandardEnvironment {
|
||||||
setProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT, rpcBlockNotificationPort);
|
setProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT, rpcBlockNotificationPort);
|
||||||
setProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA, dumpBlockchainData);
|
setProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA, dumpBlockchainData);
|
||||||
setProperty(DaoOptionKeys.FULL_DAO_NODE, fullDaoNode);
|
setProperty(DaoOptionKeys.FULL_DAO_NODE, fullDaoNode);
|
||||||
setProperty(DaoOptionKeys.REG_TEST_GENESIS_TX_ID, regTestBsqGenesisTxId);
|
setProperty(DaoOptionKeys.GENESIS_TX_ID, genesisTxId);
|
||||||
|
setProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT, genesisBlockHeight);
|
||||||
|
|
||||||
setProperty(BtcOptionKeys.BTC_NODES, btcNodes);
|
setProperty(BtcOptionKeys.BTC_NODES, btcNodes);
|
||||||
setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);
|
setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);
|
||||||
|
|
|
@ -124,6 +124,15 @@ public abstract class BisqExecutable {
|
||||||
"(Global alert, Version update alert, Filters for offers, nodes or trading account data)", false))
|
"(Global alert, Version update alert, Filters for offers, nodes or trading account data)", false))
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(boolean.class);
|
.ofType(boolean.class);
|
||||||
|
parser.accepts(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS,
|
||||||
|
description("If that is true all the privileged features which requires a private key to enable it are overridden by a dev key pair " +
|
||||||
|
"(This is for developers only!)", false))
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(boolean.class);
|
||||||
|
parser.accepts(AppOptionKeys.USE_DEV_MODE,
|
||||||
|
description("Enables dev mode which is used for convenience for developer testing", false))
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(boolean.class);
|
||||||
parser.accepts(AppOptionKeys.DUMP_STATISTICS,
|
parser.accepts(AppOptionKeys.DUMP_STATISTICS,
|
||||||
description("If set to true the trade statistics are stored as json file in the data dir.", false))
|
description("If set to true the trade statistics are stored as json file in the data dir.", false))
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
|
@ -188,10 +197,12 @@ public abstract class BisqExecutable {
|
||||||
"set as well.", false))
|
"set as well.", false))
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(boolean.class);
|
.ofType(boolean.class);
|
||||||
parser.accepts(DaoOptionKeys.REG_TEST_GENESIS_TX_ID,
|
parser.accepts(DaoOptionKeys.GENESIS_TX_ID,
|
||||||
description("Reg test BSQ genesis transaction id when not using the hard coded one", ""))
|
description("Genesis transaction ID when not using the hard coded one", ""))
|
||||||
|
.withRequiredArg();
|
||||||
|
parser.accepts(DaoOptionKeys.GENESIS_BLOCK_HEIGHT,
|
||||||
|
description("Genesis transaction block height when not using the hard coded one", ""))
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BisqEnvironment getBisqEnvironment(OptionSet options) {
|
public static BisqEnvironment getBisqEnvironment(OptionSet options) {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package io.bisq.core.arbitration;
|
package io.bisq.core.arbitration;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.name.Named;
|
||||||
import io.bisq.common.Timer;
|
import io.bisq.common.Timer;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
import io.bisq.common.app.DevEnv;
|
import io.bisq.common.app.DevEnv;
|
||||||
|
@ -25,6 +26,7 @@ import io.bisq.common.crypto.KeyRing;
|
||||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||||
import io.bisq.common.handlers.ResultHandler;
|
import io.bisq.common.handlers.ResultHandler;
|
||||||
import io.bisq.common.util.Utilities;
|
import io.bisq.common.util.Utilities;
|
||||||
|
import io.bisq.core.app.AppOptionKeys;
|
||||||
import io.bisq.core.filter.FilterManager;
|
import io.bisq.core.filter.FilterManager;
|
||||||
import io.bisq.core.user.Preferences;
|
import io.bisq.core.user.Preferences;
|
||||||
import io.bisq.core.user.User;
|
import io.bisq.core.user.User;
|
||||||
|
@ -63,26 +65,7 @@ public class ArbitratorManager {
|
||||||
private static final long RETRY_REPUBLISH_SEC = 5;
|
private static final long RETRY_REPUBLISH_SEC = 5;
|
||||||
private static final long REPEATED_REPUBLISH_AT_STARTUP_SEC = 60;
|
private static final long REPEATED_REPUBLISH_AT_STARTUP_SEC = 60;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
private final List<String> publicKeys;
|
||||||
private static final List<String> publicKeys = DevEnv.USE_DEV_PRIVILEGE_KEYS ?
|
|
||||||
new ArrayList<>(Collections.singletonList(DevEnv.DEV_PRIVILEGE_PUB_KEY)) :
|
|
||||||
new ArrayList<>(Arrays.asList(
|
|
||||||
"0365c6af94681dbee69de1851f98d4684063bf5c2d64b1c73ed5d90434f375a054",
|
|
||||||
"031c502a60f9dbdb5ae5e438a79819e4e1f417211dd537ac12c9bc23246534c4bd",
|
|
||||||
"02c1e5a242387b6d5319ce27246cea6edaaf51c3550591b528d2578a4753c56c2c",
|
|
||||||
"025c319faf7067d9299590dd6c97fe7e56cd4dac61205ccee1cd1fc390142390a2",
|
|
||||||
"038f6e24c2bfe5d51d0a290f20a9a657c270b94ef2b9c12cd15ca3725fa798fc55",
|
|
||||||
"0255256ff7fb615278c4544a9bbd3f5298b903b8a011cd7889be19b6b1c45cbefe",
|
|
||||||
"024a3a37289f08c910fbd925ebc72b946f33feaeff451a4738ee82037b4cda2e95",
|
|
||||||
"02a88b75e9f0f8afba1467ab26799dcc38fd7a6468fb2795444b425eb43e2c10bd",
|
|
||||||
"02349a51512c1c04c67118386f4d27d768c5195a83247c150a4b722d161722ba81",
|
|
||||||
"03f718a2e0dc672c7cdec0113e72c3322efc70412bb95870750d25c32cd98de17d",
|
|
||||||
"028ff47ee2c56e66313928975c58fa4f1b19a0f81f3a96c4e9c9c3c6768075509e",
|
|
||||||
"02b517c0cbc3a49548f448ddf004ed695c5a1c52ec110be1bfd65fa0ca0761c94b",
|
|
||||||
"03df837a3a0f3d858e82f3356b71d1285327f101f7c10b404abed2abc1c94e7169",
|
|
||||||
"0203a90fb2ab698e524a5286f317a183a84327b8f8c3f7fa4a98fec9e1cefd6b72",
|
|
||||||
"023c99cc073b851c892d8c43329ca3beb5d2213ee87111af49884e3ce66cbd5ba5"
|
|
||||||
));
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Instance fields
|
// Instance fields
|
||||||
|
@ -103,12 +86,36 @@ public class ArbitratorManager {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ArbitratorManager(KeyRing keyRing, ArbitratorService arbitratorService, User user, Preferences preferences, FilterManager filterManager) {
|
public ArbitratorManager(KeyRing keyRing,
|
||||||
|
ArbitratorService arbitratorService,
|
||||||
|
User user,
|
||||||
|
Preferences preferences,
|
||||||
|
FilterManager filterManager,
|
||||||
|
@Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.arbitratorService = arbitratorService;
|
this.arbitratorService = arbitratorService;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
|
publicKeys = useDevPrivilegeKeys ?
|
||||||
|
Collections.unmodifiableList(Collections.singletonList(DevEnv.DEV_PRIVILEGE_PUB_KEY)) :
|
||||||
|
Collections.unmodifiableList(Arrays.asList(
|
||||||
|
"0365c6af94681dbee69de1851f98d4684063bf5c2d64b1c73ed5d90434f375a054",
|
||||||
|
"031c502a60f9dbdb5ae5e438a79819e4e1f417211dd537ac12c9bc23246534c4bd",
|
||||||
|
"02c1e5a242387b6d5319ce27246cea6edaaf51c3550591b528d2578a4753c56c2c",
|
||||||
|
"025c319faf7067d9299590dd6c97fe7e56cd4dac61205ccee1cd1fc390142390a2",
|
||||||
|
"038f6e24c2bfe5d51d0a290f20a9a657c270b94ef2b9c12cd15ca3725fa798fc55",
|
||||||
|
"0255256ff7fb615278c4544a9bbd3f5298b903b8a011cd7889be19b6b1c45cbefe",
|
||||||
|
"024a3a37289f08c910fbd925ebc72b946f33feaeff451a4738ee82037b4cda2e95",
|
||||||
|
"02a88b75e9f0f8afba1467ab26799dcc38fd7a6468fb2795444b425eb43e2c10bd",
|
||||||
|
"02349a51512c1c04c67118386f4d27d768c5195a83247c150a4b722d161722ba81",
|
||||||
|
"03f718a2e0dc672c7cdec0113e72c3322efc70412bb95870750d25c32cd98de17d",
|
||||||
|
"028ff47ee2c56e66313928975c58fa4f1b19a0f81f3a96c4e9c9c3c6768075509e",
|
||||||
|
"02b517c0cbc3a49548f448ddf004ed695c5a1c52ec110be1bfd65fa0ca0761c94b",
|
||||||
|
"03df837a3a0f3d858e82f3356b71d1285327f101f7c10b404abed2abc1c94e7169",
|
||||||
|
"0203a90fb2ab698e524a5286f317a183a84327b8f8c3f7fa4a98fec9e1cefd6b72",
|
||||||
|
"023c99cc073b851c892d8c43329ca3beb5d2213ee87111af49884e3ce66cbd5ba5"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutDown() {
|
public void shutDown() {
|
||||||
|
|
|
@ -20,7 +20,6 @@ package io.bisq.core.btc;
|
||||||
import io.bisq.core.app.BisqEnvironment;
|
import io.bisq.core.app.BisqEnvironment;
|
||||||
import io.bisq.core.provider.fee.FeeService;
|
import io.bisq.core.provider.fee.FeeService;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
import org.bitcoinj.params.MainNetParams;
|
import org.bitcoinj.params.MainNetParams;
|
||||||
import org.bitcoinj.params.RegTestParams;
|
import org.bitcoinj.params.RegTestParams;
|
||||||
|
@ -36,10 +35,6 @@ public enum BaseCurrencyNetwork {
|
||||||
LTC_TESTNET(LitecoinTestNet3Params.get(), "LTC", "TESTNET", "Litecoin"),
|
LTC_TESTNET(LitecoinTestNet3Params.get(), "LTC", "TESTNET", "Litecoin"),
|
||||||
LTC_REGTEST(LitecoinRegTestParams.get(), "LTC", "REGTEST", "Litecoin"),
|
LTC_REGTEST(LitecoinRegTestParams.get(), "LTC", "REGTEST", "Litecoin"),
|
||||||
|
|
||||||
DOGE_MAINNET(DogecoinMainNetParams.get(), "DOGE", "MAINNET", "Dogecoin"),
|
|
||||||
DOGE_TESTNET(DogecoinTestNet3Params.get(), "DOGE", "TESTNET", "Dogecoin"),
|
|
||||||
DOGE_REGTEST(DogecoinRegTestParams.get(), "DOGE", "REGTEST", "Dogecoin"),
|
|
||||||
|
|
||||||
DASH_MAINNET(DashMainNetParams.get(), "DASH", "MAINNET", "Dash"),
|
DASH_MAINNET(DashMainNetParams.get(), "DASH", "MAINNET", "Dash"),
|
||||||
DASH_TESTNET(DashTestNet3Params.get(), "DASH", "TESTNET", "Dash"),
|
DASH_TESTNET(DashTestNet3Params.get(), "DASH", "TESTNET", "Dash"),
|
||||||
DASH_REGTEST(DashRegTestParams.get(), "DASH", "REGTEST", "Dash");
|
DASH_REGTEST(DashRegTestParams.get(), "DASH", "REGTEST", "Dash");
|
||||||
|
@ -80,21 +75,14 @@ public enum BaseCurrencyNetwork {
|
||||||
return "DASH".equals(currencyCode);
|
return "DASH".equals(currencyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDoge() {
|
public long getDefaultMinFeePerByte() {
|
||||||
return "DOGE".equals(currencyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Coin getDefaultMinFee() {
|
|
||||||
switch (BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()) {
|
switch (BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()) {
|
||||||
case "BTC":
|
case "BTC":
|
||||||
return FeeService.BTC_REFERENCE_DEFAULT_MIN_TX_FEE;
|
return FeeService.BTC_REFERENCE_DEFAULT_MIN_TX_FEE_PER_KB.divide(1000).value;
|
||||||
case "LTC":
|
case "LTC":
|
||||||
return FeeService.LTC_REFERENCE_DEFAULT_MIN_TX_FEE;
|
return FeeService.LTC_REFERENCE_DEFAULT_MIN_TX_FEE.value;
|
||||||
case "DOGE":
|
|
||||||
return FeeService.DOGE_REFERENCE_DEFAULT_MIN_TX_FEE;
|
|
||||||
case "DASH":
|
case "DASH":
|
||||||
return FeeService.DASH_REFERENCE_DEFAULT_MIN_TX_FEE;
|
return FeeService.DASH_REFERENCE_DEFAULT_MIN_TX_FEE.value;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Unsupported code at getDefaultMinFee: " + BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode());
|
throw new RuntimeException("Unsupported code at getDefaultMinFee: " + BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode());
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public enum RegTestHost {
|
||||||
LOCALHOST,
|
LOCALHOST,
|
||||||
REG_TEST_SERVER; // 188.226.179.109
|
REG_TEST_SERVER; // 188.226.179.109
|
||||||
|
|
||||||
public static final RegTestHost DEFAULT = NONE;
|
public static final RegTestHost DEFAULT = LOCALHOST;
|
||||||
public static final String SERVER_IP = "188.226.179.109";
|
public static final String SERVER_IP = "188.226.179.109";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,9 +54,6 @@ public class Restrictions {
|
||||||
case "LTC":
|
case "LTC":
|
||||||
MIN_TRADE_AMOUNT = Coin.valueOf(100_000); // 0.04 EUR @ 40 EUR/LTC
|
MIN_TRADE_AMOUNT = Coin.valueOf(100_000); // 0.04 EUR @ 40 EUR/LTC
|
||||||
break;
|
break;
|
||||||
case "DOGE":
|
|
||||||
MIN_TRADE_AMOUNT = Coin.valueOf(1_000_000_000L); // 0.03 EUR at DOGE price 0.003 EUR;
|
|
||||||
break;
|
|
||||||
case "DASH":
|
case "DASH":
|
||||||
MIN_TRADE_AMOUNT = Coin.valueOf(20_000L); // 0.03 EUR at @ 150 EUR/DASH;
|
MIN_TRADE_AMOUNT = Coin.valueOf(20_000L); // 0.03 EUR at @ 150 EUR/DASH;
|
||||||
break;
|
break;
|
||||||
|
@ -74,9 +71,6 @@ public class Restrictions {
|
||||||
case "LTC":
|
case "LTC":
|
||||||
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(1_200_000_000); // 500 EUR @ 40 EUR/LTC
|
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(1_200_000_000); // 500 EUR @ 40 EUR/LTC
|
||||||
break;
|
break;
|
||||||
case "DOGE":
|
|
||||||
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(20_000_000_000_000L); // 500 EUR @ 0.0025 EUR/DOGE;
|
|
||||||
break;
|
|
||||||
case "DASH":
|
case "DASH":
|
||||||
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(300_000_000L); // 450 EUR @ 150 EUR/DASH;
|
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(300_000_000L); // 450 EUR @ 150 EUR/DASH;
|
||||||
break;
|
break;
|
||||||
|
@ -94,9 +88,6 @@ public class Restrictions {
|
||||||
case "LTC":
|
case "LTC":
|
||||||
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(6_000_000); // 2.4 EUR @ 40 EUR/LTC
|
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(6_000_000); // 2.4 EUR @ 40 EUR/LTC
|
||||||
break;
|
break;
|
||||||
case "DOGE":
|
|
||||||
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(100_000_000_000L); // 2.5 EUR @ 0.0025 EUR/DOGE;
|
|
||||||
break;
|
|
||||||
case "DASH":
|
case "DASH":
|
||||||
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(1_500_000L); // 2.5 EUR @ 150 EUR/DASH;
|
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(1_500_000L); // 2.5 EUR @ 150 EUR/DASH;
|
||||||
break;
|
break;
|
||||||
|
@ -113,9 +104,6 @@ public class Restrictions {
|
||||||
case "LTC":
|
case "LTC":
|
||||||
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(200_000_000); // 75 EUR @ 40 EUR/LTC
|
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(200_000_000); // 75 EUR @ 40 EUR/LTC
|
||||||
break;
|
break;
|
||||||
case "DOGE":
|
|
||||||
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(3_000_000_000_000L); // 75 EUR @ 0.0025 EUR/DOGE;
|
|
||||||
break;
|
|
||||||
case "DASH":
|
case "DASH":
|
||||||
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(50_000_000L); // 75 EUR @ 150 EUR/DASH;
|
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(50_000_000L); // 75 EUR @ 150 EUR/DASH;
|
||||||
break;
|
break;
|
||||||
|
@ -132,9 +120,6 @@ public class Restrictions {
|
||||||
case "LTC":
|
case "LTC":
|
||||||
SELLER_SECURITY_DEPOSIT = Coin.valueOf(60_000_000); // 25 EUR @ 40 EUR/LTC
|
SELLER_SECURITY_DEPOSIT = Coin.valueOf(60_000_000); // 25 EUR @ 40 EUR/LTC
|
||||||
break;
|
break;
|
||||||
case "DOGE":
|
|
||||||
SELLER_SECURITY_DEPOSIT = Coin.valueOf(1_000_000_000_000L); // 25 EUR @ 0.0025 EUR/DOGE;
|
|
||||||
break;
|
|
||||||
case "DASH":
|
case "DASH":
|
||||||
SELLER_SECURITY_DEPOSIT = Coin.valueOf(15_000_000L); // 25 EUR @ 150 EUR/DASH;
|
SELLER_SECURITY_DEPOSIT = Coin.valueOf(15_000_000L); // 25 EUR @ 150 EUR/DASH;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package io.bisq.core.btc.wallet;
|
package io.bisq.core.btc.wallet;
|
||||||
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@ import io.bisq.core.app.BisqEnvironment;
|
||||||
import io.bisq.core.btc.Restrictions;
|
import io.bisq.core.btc.Restrictions;
|
||||||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import io.bisq.core.btc.exceptions.WalletException;
|
import io.bisq.core.btc.exceptions.WalletException;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
||||||
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
import io.bisq.core.provider.fee.FeeService;
|
import io.bisq.core.provider.fee.FeeService;
|
||||||
|
@ -396,7 +396,7 @@ public class BsqWalletService extends WalletService implements BsqBlockChainList
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Transaction getPreparedBurnFeeTx(Coin fee) throws
|
public Transaction getPreparedBurnFeeTx(Coin fee) throws
|
||||||
InsufficientBsqException, ChangeBelowDustException {
|
InsufficientBsqException {
|
||||||
Transaction tx = new Transaction(params);
|
Transaction tx = new Transaction(params);
|
||||||
|
|
||||||
// We might have no output if inputs match fee.
|
// We might have no output if inputs match fee.
|
||||||
|
@ -405,7 +405,7 @@ public class BsqWalletService extends WalletService implements BsqBlockChainList
|
||||||
|
|
||||||
// TODO check dust output
|
// TODO check dust output
|
||||||
CoinSelection coinSelection = bsqCoinSelector.select(fee, wallet.calculateAllSpendCandidates());
|
CoinSelection coinSelection = bsqCoinSelector.select(fee, wallet.calculateAllSpendCandidates());
|
||||||
coinSelection.gathered.stream().forEach(tx::addInput);
|
coinSelection.gathered.forEach(tx::addInput);
|
||||||
try {
|
try {
|
||||||
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
|
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
|
||||||
if (change.isPositive())
|
if (change.isPositive())
|
||||||
|
|
|
@ -22,7 +22,6 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||||
import io.bisq.core.app.BisqEnvironment;
|
|
||||||
import io.bisq.core.btc.*;
|
import io.bisq.core.btc.*;
|
||||||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import io.bisq.core.btc.exceptions.WalletException;
|
import io.bisq.core.btc.exceptions.WalletException;
|
||||||
|
@ -137,20 +136,20 @@ public class BtcWalletService extends WalletService {
|
||||||
// preparedCompensationRequestTx has following structure:
|
// preparedCompensationRequestTx has following structure:
|
||||||
// inputs [1-n] BSQ inputs for request fee
|
// inputs [1-n] BSQ inputs for request fee
|
||||||
// inputs [1-n] BTC inputs for BSQ issuance and miner fee
|
// inputs [1-n] BTC inputs for BSQ issuance and miner fee
|
||||||
// outputs [0-1] BSQ request fee change output
|
// outputs [0-1] BSQ request fee change output (>= 2730 Satoshi)
|
||||||
// outputs [1] BSQ issuance output
|
// outputs [1] Potentially BSQ issuance output (>= 2730 Satoshi)
|
||||||
// outputs [0-1] BTC change output from issuance and miner fee inputs
|
// outputs [0-1] BTC change output from issuance and miner fee inputs (>= 2730 Satoshi)
|
||||||
// outputs [0-1] OP_RETURN with opReturnData
|
// outputs [0-1] OP_RETURN with opReturnData and amount 0
|
||||||
// mining fee: BTC mining fee + burned BSQ fee
|
// mining fee: BTC mining fee + burned BSQ fee
|
||||||
|
|
||||||
Transaction preparedTx = new Transaction(params);
|
Transaction preparedTx = new Transaction(params);
|
||||||
// Copy inputs from BSQ fee tx
|
// Copy inputs from BSQ fee tx
|
||||||
feeTx.getInputs().stream().forEach(preparedTx::addInput);
|
feeTx.getInputs().forEach(preparedTx::addInput);
|
||||||
int indexOfBtcFirstInput = feeTx.getInputs().size();
|
int indexOfBtcFirstInput = feeTx.getInputs().size();
|
||||||
|
|
||||||
// Need to be first because issuance is not guaranteed to be valid and would otherwise burn change output!
|
// Need to be first because issuance is not guaranteed to be valid and would otherwise burn change output!
|
||||||
// BSQ change outputs from BSQ fee inputs.
|
// BSQ change outputs from BSQ fee inputs.
|
||||||
feeTx.getOutputs().stream().forEach(preparedTx::addOutput);
|
feeTx.getOutputs().forEach(preparedTx::addOutput);
|
||||||
|
|
||||||
// BSQ issuance output
|
// BSQ issuance output
|
||||||
preparedTx.addOutput(issuanceAmount, issuanceAddress);
|
preparedTx.addOutput(issuanceAmount, issuanceAddress);
|
||||||
|
@ -293,6 +292,7 @@ public class BtcWalletService extends WalletService {
|
||||||
int numInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input
|
int numInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input
|
||||||
Transaction resultTx = null;
|
Transaction resultTx = null;
|
||||||
boolean isFeeOutsideTolerance;
|
boolean isFeeOutsideTolerance;
|
||||||
|
boolean opReturnIsOnlyOutput;
|
||||||
do {
|
do {
|
||||||
counter++;
|
counter++;
|
||||||
if (counter >= 10) {
|
if (counter >= 10) {
|
||||||
|
@ -331,7 +331,8 @@ public class BtcWalletService extends WalletService {
|
||||||
// We might have the rare case that both inputs matched the required fees, so both did not require
|
// We might have the rare case that both inputs matched the required fees, so both did not require
|
||||||
// a change output.
|
// a change output.
|
||||||
// In such cases we need to add artificially a change output (OP_RETURN is not allowed as only output)
|
// In such cases we need to add artificially a change output (OP_RETURN is not allowed as only output)
|
||||||
forcedChangeValue = resultTx.getOutputs().size() == 0 ? Restrictions.getMinNonDustOutput() : Coin.ZERO;
|
opReturnIsOnlyOutput = resultTx.getOutputs().size() == 0;
|
||||||
|
forcedChangeValue = opReturnIsOnlyOutput ? Restrictions.getMinNonDustOutput() : Coin.ZERO;
|
||||||
|
|
||||||
// add OP_RETURN output
|
// add OP_RETURN output
|
||||||
if (opReturnData != null)
|
if (opReturnData != null)
|
||||||
|
@ -343,7 +344,9 @@ public class BtcWalletService extends WalletService {
|
||||||
// calculated fee must be inside of a tolerance range with tx fee
|
// calculated fee must be inside of a tolerance range with tx fee
|
||||||
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
||||||
}
|
}
|
||||||
while (forcedChangeValue.isPositive() || isFeeOutsideTolerance);
|
while (opReturnIsOnlyOutput ||
|
||||||
|
isFeeOutsideTolerance ||
|
||||||
|
resultTx.getFee().value < txFeePerByte.multiply(resultTx.bitcoinSerialize().length).value);
|
||||||
|
|
||||||
// Sign all BTC inputs
|
// Sign all BTC inputs
|
||||||
for (int i = preparedBsqTxInputs.size(); i < resultTx.getInputs().size(); i++) {
|
for (int i = preparedBsqTxInputs.size(); i < resultTx.getInputs().size(); i++) {
|
||||||
|
@ -603,10 +606,6 @@ public class BtcWalletService extends WalletService {
|
||||||
do {
|
do {
|
||||||
counter++;
|
counter++;
|
||||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||||
final Coin defaultMinFee = BisqEnvironment.getBaseCurrencyNetwork().getDefaultMinFee();
|
|
||||||
if (fee.compareTo(defaultMinFee) < 0)
|
|
||||||
fee = defaultMinFee;
|
|
||||||
|
|
||||||
newTransaction.clearOutputs();
|
newTransaction.clearOutputs();
|
||||||
newTransaction.addOutput(amount.subtract(fee), toAddress);
|
newTransaction.addOutput(amount.subtract(fee), toAddress);
|
||||||
|
|
||||||
|
@ -621,9 +620,10 @@ public class BtcWalletService extends WalletService {
|
||||||
tx = sendRequest.tx;
|
tx = sendRequest.tx;
|
||||||
txSize = tx.bitcoinSerialize().length;
|
txSize = tx.bitcoinSerialize().length;
|
||||||
printTx("FeeEstimationTransaction", tx);
|
printTx("FeeEstimationTransaction", tx);
|
||||||
sendRequest.tx.getOutputs().stream().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString()));
|
sendRequest.tx.getOutputs().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString()));
|
||||||
}
|
}
|
||||||
while (counter < 10 && Math.abs(tx.getFee().value - txFeeForWithdrawalPerByte.multiply(txSize).value) > 1000);
|
while (feeEstimationNotSatisfied(counter, tx));
|
||||||
|
|
||||||
if (counter == 10)
|
if (counter == 10)
|
||||||
log.error("Could not calculate the fee. Tx=" + tx);
|
log.error("Could not calculate the fee. Tx=" + tx);
|
||||||
|
|
||||||
|
@ -722,17 +722,13 @@ public class BtcWalletService extends WalletService {
|
||||||
do {
|
do {
|
||||||
counter++;
|
counter++;
|
||||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||||
final Coin defaultMinFee = BisqEnvironment.getBaseCurrencyNetwork().getDefaultMinFee();
|
|
||||||
if (fee.compareTo(defaultMinFee) < 0)
|
|
||||||
fee = defaultMinFee;
|
|
||||||
|
|
||||||
SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, aesKey, context);
|
SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, aesKey, context);
|
||||||
wallet.completeTx(sendRequest);
|
wallet.completeTx(sendRequest);
|
||||||
tx = sendRequest.tx;
|
tx = sendRequest.tx;
|
||||||
txSize = tx.bitcoinSerialize().length;
|
txSize = tx.bitcoinSerialize().length;
|
||||||
printTx("FeeEstimationTransaction", tx);
|
printTx("FeeEstimationTransaction", tx);
|
||||||
}
|
}
|
||||||
while (counter < 10 && Math.abs(tx.getFee().value - txFeeForWithdrawalPerByte.multiply(txSize).value) > 1000);
|
while (feeEstimationNotSatisfied(counter, tx));
|
||||||
if (counter == 10)
|
if (counter == 10)
|
||||||
log.error("Could not calculate the fee. Tx=" + tx);
|
log.error("Could not calculate the fee. Tx=" + tx);
|
||||||
|
|
||||||
|
@ -773,9 +769,6 @@ public class BtcWalletService extends WalletService {
|
||||||
do {
|
do {
|
||||||
counter++;
|
counter++;
|
||||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||||
final Coin defaultMinFee = BisqEnvironment.getBaseCurrencyNetwork().getDefaultMinFee();
|
|
||||||
if (fee.compareTo(defaultMinFee) < 0)
|
|
||||||
fee = defaultMinFee;
|
|
||||||
// We use a dummy address for the output
|
// We use a dummy address for the output
|
||||||
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddressString(), amount, fee, null, aesKey);
|
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddressString(), amount, fee, null, aesKey);
|
||||||
wallet.completeTx(sendRequest);
|
wallet.completeTx(sendRequest);
|
||||||
|
@ -783,7 +776,7 @@ public class BtcWalletService extends WalletService {
|
||||||
txSize = tx.bitcoinSerialize().length;
|
txSize = tx.bitcoinSerialize().length;
|
||||||
printTx("FeeEstimationTransactionForMultipleAddresses", tx);
|
printTx("FeeEstimationTransactionForMultipleAddresses", tx);
|
||||||
}
|
}
|
||||||
while (counter < 10 && Math.abs(tx.getFee().value - txFeeForWithdrawalPerByte.multiply(txSize).value) > 1000);
|
while (feeEstimationNotSatisfied(counter, tx));
|
||||||
if (counter == 10)
|
if (counter == 10)
|
||||||
log.error("Could not calculate the fee. Tx=" + tx);
|
log.error("Could not calculate the fee. Tx=" + tx);
|
||||||
|
|
||||||
|
@ -795,6 +788,14 @@ public class BtcWalletService extends WalletService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean feeEstimationNotSatisfied(int counter, Transaction tx) {
|
||||||
|
long targetFee = getTxFeeForWithdrawalPerByte().multiply(tx.bitcoinSerialize().length).value;
|
||||||
|
return counter < 10 &&
|
||||||
|
(tx.getFee().value < targetFee ||
|
||||||
|
tx.getFee().value - targetFee > 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Withdrawal Send
|
// Withdrawal Send
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -873,7 +874,7 @@ public class BtcWalletService extends WalletService {
|
||||||
"The amount is too low (dust limit).");
|
"The amount is too low (dust limit).");
|
||||||
|
|
||||||
final Coin netValue = amount.subtract(fee);
|
final Coin netValue = amount.subtract(fee);
|
||||||
if(netValue.isNegative())
|
if (netValue.isNegative())
|
||||||
throw new InsufficientMoneyException(netValue.multiply(-1), "The mining fee for that transaction exceed the available amount.");
|
throw new InsufficientMoneyException(netValue.multiply(-1), "The mining fee for that transaction exceed the available amount.");
|
||||||
|
|
||||||
tx.addOutput(netValue, Address.fromBase58(params, toAddress));
|
tx.addOutput(netValue, Address.fromBase58(params, toAddress));
|
||||||
|
|
|
@ -427,7 +427,7 @@ public abstract class WalletService {
|
||||||
return outputs;
|
return outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
Coin getTxFeeForWithdrawalPerByte() {
|
public Coin getTxFeeForWithdrawalPerByte() {
|
||||||
Coin fee = (preferences.isUseCustomWithdrawalTxFee()) ?
|
Coin fee = (preferences.isUseCustomWithdrawalTxFee()) ?
|
||||||
Coin.valueOf(preferences.getWithdrawalTxFeeInBytes()) :
|
Coin.valueOf(preferences.getWithdrawalTxFeeInBytes()) :
|
||||||
feeService.getTxFeePerByte();
|
feeService.getTxFeePerByte();
|
||||||
|
|
|
@ -196,9 +196,18 @@ public class WalletsSetup {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (params == RegTestParams.get()) {
|
if (params == RegTestParams.get()) {
|
||||||
configPeerNodesForRegTest();
|
walletConfig.setMinBroadcastConnections(1);
|
||||||
|
if (regTestHost == RegTestHost.LOCALHOST) {
|
||||||
|
walletConfig.setPeerNodesForLocalHost();
|
||||||
|
} else if (regTestHost == RegTestHost.REG_TEST_SERVER) {
|
||||||
|
walletConfig.setMinBroadcastConnections(1);
|
||||||
|
configPeerNodesForRegTestServer();
|
||||||
|
} else {
|
||||||
|
configPeerNodes(socks5Proxy);
|
||||||
|
}
|
||||||
} else if (bisqEnvironment.isBitcoinLocalhostNodeRunning()) {
|
} else if (bisqEnvironment.isBitcoinLocalhostNodeRunning()) {
|
||||||
configPeerNodesForLocalHostBitcoinNode();
|
walletConfig.setMinBroadcastConnections(1);
|
||||||
|
walletConfig.setPeerNodesForLocalHost();
|
||||||
} else {
|
} else {
|
||||||
configPeerNodes(socks5Proxy);
|
configPeerNodes(socks5Proxy);
|
||||||
}
|
}
|
||||||
|
@ -268,9 +277,7 @@ public class WalletsSetup {
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configPeerNodesForRegTest() {
|
private void configPeerNodesForRegTestServer() {
|
||||||
walletConfig.setMinBroadcastConnections(1);
|
|
||||||
if (regTestHost == RegTestHost.REG_TEST_SERVER) {
|
|
||||||
try {
|
try {
|
||||||
walletConfig.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
|
walletConfig.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
|
@ -278,14 +285,6 @@ public class WalletsSetup {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else if (regTestHost == RegTestHost.LOCALHOST) {
|
|
||||||
walletConfig.setPeerNodesForLocalHost();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configPeerNodesForLocalHostBitcoinNode() {
|
|
||||||
walletConfig.setMinBroadcastConnections(1);
|
|
||||||
walletConfig.setPeerNodesForLocalHost();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configPeerNodes(@Nullable Socks5Proxy proxy) {
|
private void configPeerNodes(@Nullable Socks5Proxy proxy) {
|
||||||
|
|
|
@ -20,9 +20,9 @@ package io.bisq.core.dao;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||||
import io.bisq.core.app.BisqEnvironment;
|
import io.bisq.core.app.BisqEnvironment;
|
||||||
import io.bisq.core.dao.blockchain.BsqNode;
|
import io.bisq.core.dao.node.BsqNode;
|
||||||
import io.bisq.core.dao.blockchain.BsqNodeProvider;
|
import io.bisq.core.dao.node.BsqNodeProvider;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||||
import io.bisq.core.dao.vote.VotingManager;
|
import io.bisq.core.dao.vote.VotingManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,4 +58,11 @@ public class DaoManager {
|
||||||
bsqNode.onAllServicesInitialized(errorMessageHandler);
|
bsqNode.onAllServicesInitialized(errorMessageHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
daoPeriodService.shutDown();
|
||||||
|
voteManager.shutDown();
|
||||||
|
compensationRequestManager.shutDown();
|
||||||
|
bsqNode.shutDown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,23 @@
|
||||||
package io.bisq.core.dao;
|
package io.bisq.core.dao;
|
||||||
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
import io.bisq.common.app.AppModule;
|
import io.bisq.common.app.AppModule;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
||||||
import io.bisq.core.dao.blockchain.BsqFullNode;
|
|
||||||
import io.bisq.core.dao.blockchain.BsqLiteNode;
|
|
||||||
import io.bisq.core.dao.blockchain.BsqNodeProvider;
|
|
||||||
import io.bisq.core.dao.blockchain.json.JsonBlockChainExporter;
|
import io.bisq.core.dao.blockchain.json.JsonBlockChainExporter;
|
||||||
import io.bisq.core.dao.blockchain.parse.*;
|
import io.bisq.core.dao.node.BsqNodeProvider;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
import io.bisq.core.dao.node.consensus.*;
|
||||||
|
import io.bisq.core.dao.node.full.FullNode;
|
||||||
|
import io.bisq.core.dao.node.full.FullNodeExecutor;
|
||||||
|
import io.bisq.core.dao.node.full.FullNodeParser;
|
||||||
|
import io.bisq.core.dao.node.full.network.FullNodeNetworkManager;
|
||||||
|
import io.bisq.core.dao.node.full.rpc.RpcService;
|
||||||
|
import io.bisq.core.dao.node.lite.LiteNode;
|
||||||
|
import io.bisq.core.dao.node.lite.LiteNodeExecutor;
|
||||||
|
import io.bisq.core.dao.node.lite.LiteNodeParser;
|
||||||
|
import io.bisq.core.dao.node.lite.network.LiteNodeNetworkManager;
|
||||||
|
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||||
import io.bisq.core.dao.vote.VotingDefaultValues;
|
import io.bisq.core.dao.vote.VotingDefaultValues;
|
||||||
import io.bisq.core.dao.vote.VotingManager;
|
import io.bisq.core.dao.vote.VotingManager;
|
||||||
import io.bisq.core.dao.vote.VotingService;
|
import io.bisq.core.dao.vote.VotingService;
|
||||||
|
@ -43,16 +52,26 @@ public class DaoModule extends AppModule {
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(DaoManager.class).in(Singleton.class);
|
bind(DaoManager.class).in(Singleton.class);
|
||||||
|
|
||||||
bind(BsqLiteNode.class).in(Singleton.class);
|
bind(LiteNodeNetworkManager.class).in(Singleton.class);
|
||||||
bind(BsqFullNode.class).in(Singleton.class);
|
bind(FullNodeNetworkManager.class).in(Singleton.class);
|
||||||
|
|
||||||
|
bind(RpcService.class).in(Singleton.class);
|
||||||
|
bind(FullNodeExecutor.class).in(Singleton.class);
|
||||||
|
bind(LiteNodeExecutor.class).in(Singleton.class);
|
||||||
|
bind(LiteNodeParser.class).in(Singleton.class);
|
||||||
|
bind(FullNodeParser.class).in(Singleton.class);
|
||||||
|
bind(LiteNode.class).in(Singleton.class);
|
||||||
|
bind(FullNode.class).in(Singleton.class);
|
||||||
bind(BsqNodeProvider.class).in(Singleton.class);
|
bind(BsqNodeProvider.class).in(Singleton.class);
|
||||||
bind(BsqBlockChain.class).in(Singleton.class);
|
bind(BsqBlockChain.class).in(Singleton.class);
|
||||||
bind(BsqFullNodeExecutor.class).in(Singleton.class);
|
|
||||||
bind(BsqLiteNodeExecutor.class).in(Singleton.class);
|
|
||||||
bind(BsqBlockChainChangeDispatcher.class).in(Singleton.class);
|
bind(BsqBlockChainChangeDispatcher.class).in(Singleton.class);
|
||||||
bind(BsqParser.class).in(Singleton.class);
|
|
||||||
bind(RpcService.class).in(Singleton.class);
|
|
||||||
|
|
||||||
|
bind(GenesisTxVerification.class).in(Singleton.class);
|
||||||
|
bind(BsqTxVerification.class).in(Singleton.class);
|
||||||
|
bind(TxInputsVerification.class).in(Singleton.class);
|
||||||
|
bind(TxInputVerification.class).in(Singleton.class);
|
||||||
|
bind(TxOutputsVerification.class).in(Singleton.class);
|
||||||
|
bind(TxOutputVerification.class).in(Singleton.class);
|
||||||
bind(OpReturnVerification.class).in(Singleton.class);
|
bind(OpReturnVerification.class).in(Singleton.class);
|
||||||
bind(CompensationRequestVerification.class).in(Singleton.class);
|
bind(CompensationRequestVerification.class).in(Singleton.class);
|
||||||
bind(VotingVerification.class).in(Singleton.class);
|
bind(VotingVerification.class).in(Singleton.class);
|
||||||
|
@ -75,8 +94,12 @@ public class DaoModule extends AppModule {
|
||||||
.to(environment.getRequiredProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA));
|
.to(environment.getRequiredProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA));
|
||||||
bindConstant().annotatedWith(named(DaoOptionKeys.FULL_DAO_NODE))
|
bindConstant().annotatedWith(named(DaoOptionKeys.FULL_DAO_NODE))
|
||||||
.to(environment.getRequiredProperty(DaoOptionKeys.FULL_DAO_NODE));
|
.to(environment.getRequiredProperty(DaoOptionKeys.FULL_DAO_NODE));
|
||||||
bindConstant().annotatedWith(named(DaoOptionKeys.REG_TEST_GENESIS_TX_ID))
|
|
||||||
.to(environment.getRequiredProperty(DaoOptionKeys.REG_TEST_GENESIS_TX_ID));
|
String genesisTxId = environment.getProperty(DaoOptionKeys.GENESIS_TX_ID, String.class, BsqBlockChain.BTC_GENESIS_TX_ID);
|
||||||
|
bind(String.class).annotatedWith(Names.named(DaoOptionKeys.GENESIS_TX_ID)).toInstance(genesisTxId);
|
||||||
|
|
||||||
|
Integer genesisBlockHeight = environment.getProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT, Integer.class, BsqBlockChain.BTC_GENESIS_BLOCK_HEIGHT);
|
||||||
|
bind(Integer.class).annotatedWith(Names.named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT)).toInstance(genesisBlockHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package io.bisq.core.dao;
|
package io.bisq.core.dao;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides program argument options used in the DAO domain.
|
||||||
|
*/
|
||||||
public class DaoOptionKeys {
|
public class DaoOptionKeys {
|
||||||
public static final String RPC_USER = "rpcUser";
|
public static final String RPC_USER = "rpcUser";
|
||||||
public static final String RPC_PASSWORD = "rpcPassword";
|
public static final String RPC_PASSWORD = "rpcPassword";
|
||||||
|
@ -8,5 +11,6 @@ public class DaoOptionKeys {
|
||||||
|
|
||||||
public static final String DUMP_BLOCKCHAIN_DATA = "dumpBlockchainData";
|
public static final String DUMP_BLOCKCHAIN_DATA = "dumpBlockchainData";
|
||||||
public static final String FULL_DAO_NODE = "fullDaoNode";
|
public static final String FULL_DAO_NODE = "fullDaoNode";
|
||||||
public static final String REG_TEST_GENESIS_TX_ID = "regtestBsqGenesisTxId";
|
public static final String GENESIS_TX_ID = "genesisTxId";
|
||||||
|
public static final String GENESIS_BLOCK_HEIGHT = "genesisBlockHeight";
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,8 @@ import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.bisq.common.app.DevEnv;
|
import io.bisq.common.app.DevEnv;
|
||||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
|
||||||
import io.bisq.core.dao.compensation.CompensationRequestPayload;
|
|
||||||
import io.bisq.core.dao.vote.VotingDefaultValues;
|
import io.bisq.core.dao.vote.VotingDefaultValues;
|
||||||
import io.bisq.core.dao.vote.VotingService;
|
import io.bisq.core.dao.vote.VotingService;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
@ -32,6 +30,8 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide information about the phase and cycle of the request/voting cycle.
|
* Provide information about the phase and cycle of the request/voting cycle.
|
||||||
* A cycle is the sequence of distinct phases. The first cycle and phase starts with the genesis block height.
|
* A cycle is the sequence of distinct phases. The first cycle and phase starts with the genesis block height.
|
||||||
|
@ -84,6 +84,7 @@ public class DaoPeriodService {
|
||||||
private BsqBlockChain bsqBlockChain;
|
private BsqBlockChain bsqBlockChain;
|
||||||
private final VotingDefaultValues votingDefaultValues;
|
private final VotingDefaultValues votingDefaultValues;
|
||||||
private final VotingService votingService;
|
private final VotingService votingService;
|
||||||
|
private final int genesisBlockHeight;
|
||||||
@Getter
|
@Getter
|
||||||
private ObjectProperty<Phase> phaseProperty = new SimpleObjectProperty<>(Phase.UNDEFINED);
|
private ObjectProperty<Phase> phaseProperty = new SimpleObjectProperty<>(Phase.UNDEFINED);
|
||||||
private int chainHeight;
|
private int chainHeight;
|
||||||
|
@ -97,13 +98,13 @@ public class DaoPeriodService {
|
||||||
public DaoPeriodService(BtcWalletService btcWalletService,
|
public DaoPeriodService(BtcWalletService btcWalletService,
|
||||||
BsqBlockChain bsqBlockChain,
|
BsqBlockChain bsqBlockChain,
|
||||||
VotingDefaultValues votingDefaultValues,
|
VotingDefaultValues votingDefaultValues,
|
||||||
VotingService votingService) {
|
VotingService votingService,
|
||||||
|
@Named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) int genesisBlockHeight) {
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.bsqBlockChain = bsqBlockChain;
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
this.votingDefaultValues = votingDefaultValues;
|
this.votingDefaultValues = votingDefaultValues;
|
||||||
this.votingService = votingService;
|
this.votingService = votingService;
|
||||||
|
this.genesisBlockHeight = genesisBlockHeight;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,6 +112,10 @@ public class DaoPeriodService {
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
btcWalletService.getChainHeightProperty().addListener((observable, oldValue, newValue) -> {
|
btcWalletService.getChainHeightProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
onChainHeightChanged((int) newValue);
|
onChainHeightChanged((int) newValue);
|
||||||
|
@ -118,54 +123,54 @@ public class DaoPeriodService {
|
||||||
onChainHeightChanged(btcWalletService.getChainHeightProperty().get());
|
onChainHeightChanged(btcWalletService.getChainHeightProperty().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInPhase(CompensationRequestPayload compensationRequestPayload, Phase phase) {
|
public boolean isTxInPhase(String txId, Phase phase) {
|
||||||
Tx tx = bsqBlockChain.getTxMap().get(compensationRequestPayload.getTxId());
|
Tx tx = bsqBlockChain.getTxMap().get(txId);
|
||||||
return tx != null && isTxHeightInPhase(tx.getBlockHeight(),
|
return tx != null && isTxInPhase(tx.getBlockHeight(),
|
||||||
chainHeight,
|
chainHeight,
|
||||||
BsqBlockChain.getGenesisHeight(),
|
genesisBlockHeight,
|
||||||
phase.getDurationInBlocks(),
|
phase.getDurationInBlocks(),
|
||||||
getNumBlocksOfCycle());
|
getNumBlocksOfCycle());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInCurrentCycle(CompensationRequest compensationRequest) {
|
public boolean isTxInCurrentCycle(String txId) {
|
||||||
Tx tx = bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId());
|
Tx tx = bsqBlockChain.getTxMap().get(txId);
|
||||||
return tx != null && isInCurrentCycle(tx.getBlockHeight(),
|
return tx != null && isTxInCurrentCycle(tx.getBlockHeight(),
|
||||||
chainHeight,
|
chainHeight,
|
||||||
BsqBlockChain.getGenesisHeight(),
|
genesisBlockHeight,
|
||||||
getNumBlocksOfCycle());
|
getNumBlocksOfCycle());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInPastCycle(CompensationRequest compensationRequest) {
|
public boolean isTxInPastCycle(String txId) {
|
||||||
Tx tx = bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId());
|
Tx tx = bsqBlockChain.getTxMap().get(txId);
|
||||||
return tx != null && isInPastCycle(tx.getBlockHeight(),
|
return tx != null && isTxInPastCycle(tx.getBlockHeight(),
|
||||||
chainHeight,
|
chainHeight,
|
||||||
BsqBlockChain.getGenesisHeight(),
|
genesisBlockHeight,
|
||||||
getNumBlocksOfCycle());
|
getNumBlocksOfCycle());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNumOfStartedCycles(int chainHeight) {
|
public int getNumOfStartedCycles(int chainHeight) {
|
||||||
return getNumOfStartedCycles(chainHeight,
|
return getNumOfStartedCycles(chainHeight,
|
||||||
BsqBlockChain.getGenesisHeight(),
|
genesisBlockHeight,
|
||||||
getNumBlocksOfCycle());
|
getNumBlocksOfCycle());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not used yet be leave it
|
// Not used yet be leave it
|
||||||
public int getNumOfCompletedCycles(int chainHeight) {
|
public int getNumOfCompletedCycles(int chainHeight) {
|
||||||
return getNumOfCompletedCycles(chainHeight,
|
return getNumOfCompletedCycles(chainHeight,
|
||||||
BsqBlockChain.getGenesisHeight(),
|
genesisBlockHeight,
|
||||||
getNumBlocksOfCycle());
|
getNumBlocksOfCycle());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAbsoluteStartBlockOfPhase(int chainHeight, Phase phase) {
|
public int getAbsoluteStartBlockOfPhase(int chainHeight, Phase phase) {
|
||||||
return getAbsoluteStartBlockOfPhase(chainHeight,
|
return getAbsoluteStartBlockOfPhase(chainHeight,
|
||||||
BsqBlockChain.getGenesisHeight(),
|
genesisBlockHeight,
|
||||||
phase,
|
phase,
|
||||||
getNumBlocksOfCycle());
|
getNumBlocksOfCycle());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAbsoluteEndBlockOfPhase(int chainHeight, Phase phase) {
|
public int getAbsoluteEndBlockOfPhase(int chainHeight, Phase phase) {
|
||||||
return getAbsoluteEndBlockOfPhase(chainHeight,
|
return getAbsoluteEndBlockOfPhase(chainHeight,
|
||||||
BsqBlockChain.getGenesisHeight(),
|
genesisBlockHeight,
|
||||||
phase,
|
phase,
|
||||||
getNumBlocksOfCycle());
|
getNumBlocksOfCycle());
|
||||||
}
|
}
|
||||||
|
@ -185,7 +190,7 @@ public class DaoPeriodService {
|
||||||
|
|
||||||
private void onChainHeightChanged(int chainHeight) {
|
private void onChainHeightChanged(int chainHeight) {
|
||||||
this.chainHeight = chainHeight;
|
this.chainHeight = chainHeight;
|
||||||
final int relativeBlocksInCycle = getRelativeBlocksInCycle(BsqBlockChain.getGenesisHeight(), this.chainHeight, getNumBlocksOfCycle());
|
final int relativeBlocksInCycle = getRelativeBlocksInCycle(genesisBlockHeight, this.chainHeight, getNumBlocksOfCycle());
|
||||||
phaseProperty.set(calculatePhase(relativeBlocksInCycle));
|
phaseProperty.set(calculatePhase(relativeBlocksInCycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +231,7 @@ public class DaoPeriodService {
|
||||||
return Phase.BREAK3;
|
return Phase.BREAK3;
|
||||||
else {
|
else {
|
||||||
log.error("blocksInNewPhase is not covered by phase checks. blocksInNewPhase={}", blocksInNewPhase);
|
log.error("blocksInNewPhase is not covered by phase checks. blocksInNewPhase={}", blocksInNewPhase);
|
||||||
if (DevEnv.DEV_MODE)
|
if (DevEnv.isDevMode())
|
||||||
throw new RuntimeException("blocksInNewPhase is not covered by phase checks. blocksInNewPhase=" + blocksInNewPhase);
|
throw new RuntimeException("blocksInNewPhase is not covered by phase checks. blocksInNewPhase=" + blocksInNewPhase);
|
||||||
else
|
else
|
||||||
return Phase.UNDEFINED;
|
return Phase.UNDEFINED;
|
||||||
|
@ -234,7 +239,7 @@ public class DaoPeriodService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
boolean isTxHeightInPhase(int txHeight, int chainHeight, int genesisHeight, int requestPhaseInBlocks, int numBlocksOfCycle) {
|
boolean isTxInPhase(int txHeight, int chainHeight, int genesisHeight, int requestPhaseInBlocks, int numBlocksOfCycle) {
|
||||||
if (txHeight >= genesisHeight && chainHeight >= genesisHeight && chainHeight >= txHeight) {
|
if (txHeight >= genesisHeight && chainHeight >= genesisHeight && chainHeight >= txHeight) {
|
||||||
int numBlocksOfTxHeightSinceGenesis = txHeight - genesisHeight;
|
int numBlocksOfTxHeightSinceGenesis = txHeight - genesisHeight;
|
||||||
int numBlocksOfChainHeightSinceGenesis = chainHeight - genesisHeight;
|
int numBlocksOfChainHeightSinceGenesis = chainHeight - genesisHeight;
|
||||||
|
@ -247,7 +252,7 @@ public class DaoPeriodService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
boolean isInCurrentCycle(int txHeight, int chainHeight, int genesisHeight, int numBlocksOfCycle) {
|
boolean isTxInCurrentCycle(int txHeight, int chainHeight, int genesisHeight, int numBlocksOfCycle) {
|
||||||
final int numOfCompletedCycles = getNumOfCompletedCycles(chainHeight, genesisHeight, numBlocksOfCycle);
|
final int numOfCompletedCycles = getNumOfCompletedCycles(chainHeight, genesisHeight, numBlocksOfCycle);
|
||||||
final int blockAtCycleStart = genesisHeight + numOfCompletedCycles * numBlocksOfCycle;
|
final int blockAtCycleStart = genesisHeight + numOfCompletedCycles * numBlocksOfCycle;
|
||||||
final int blockAtCycleEnd = blockAtCycleStart + numBlocksOfCycle - 1;
|
final int blockAtCycleEnd = blockAtCycleStart + numBlocksOfCycle - 1;
|
||||||
|
@ -258,7 +263,7 @@ public class DaoPeriodService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
boolean isInPastCycle(int txHeight, int chainHeight, int genesisHeight, int numBlocksOfCycle) {
|
boolean isTxInPastCycle(int txHeight, int chainHeight, int genesisHeight, int numBlocksOfCycle) {
|
||||||
final int numOfCompletedCycles = getNumOfCompletedCycles(chainHeight, genesisHeight, numBlocksOfCycle);
|
final int numOfCompletedCycles = getNumOfCompletedCycles(chainHeight, genesisHeight, numBlocksOfCycle);
|
||||||
final int blockAtCycleStart = genesisHeight + numOfCompletedCycles * numBlocksOfCycle;
|
final int blockAtCycleStart = genesisHeight + numOfCompletedCycles * numBlocksOfCycle;
|
||||||
return txHeight <= chainHeight &&
|
return txHeight <= chainHeight &&
|
||||||
|
|
29
core/src/main/java/io/bisq/core/dao/OpReturnTypes.java
Normal file
29
core/src/main/java/io/bisq/core/dao/OpReturnTypes.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides byte constants for distinguishing the type of a DAO transaction used in the OP_RETURN data.
|
||||||
|
*/
|
||||||
|
public class OpReturnTypes {
|
||||||
|
public static final byte COMPENSATION_REQUEST = (byte) 0x01;
|
||||||
|
public static final byte VOTE = (byte) 0x02;
|
||||||
|
public static final byte VOTE_RELEASE = (byte) 0x03;
|
||||||
|
public static final byte LOCK_UP = (byte) 0x04;
|
||||||
|
public static final byte UNLOCK = (byte) 0x05;
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.blockchain;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
@ -24,10 +24,13 @@ import io.bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||||
import io.bisq.common.storage.Storage;
|
import io.bisq.common.storage.Storage;
|
||||||
import io.bisq.common.util.FunctionalReadWriteLock;
|
import io.bisq.common.util.FunctionalReadWriteLock;
|
||||||
import io.bisq.common.util.Tuple2;
|
import io.bisq.common.util.Tuple2;
|
||||||
import io.bisq.core.app.BisqEnvironment;
|
|
||||||
import io.bisq.core.dao.DaoOptionKeys;
|
import io.bisq.core.dao.DaoOptionKeys;
|
||||||
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
||||||
import io.bisq.core.dao.blockchain.vo.*;
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.util.TxIdIndexTuple;
|
||||||
import io.bisq.generated.protobuffer.PB;
|
import io.bisq.generated.protobuffer.PB;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -65,7 +68,7 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
|
|
||||||
private static final int SNAPSHOT_GRID = 100; // set high to deactivate
|
private static final int SNAPSHOT_GRID = 100; // set high to deactivate
|
||||||
private static final int ISSUANCE_MATURITY = 144 * 30; // 30 days
|
private static final int ISSUANCE_MATURITY = 144 * 30; // 30 days
|
||||||
public static final Coin GENESIS_TOTAL_SUPPLY = Coin.COIN.multiply(25);
|
private static final Coin GENESIS_TOTAL_SUPPLY = Coin.parseCoin("2.5");
|
||||||
|
|
||||||
//mainnet
|
//mainnet
|
||||||
// this tx has a lot of outputs
|
// this tx has a lot of outputs
|
||||||
|
@ -75,33 +78,8 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
// block 376078 has 2843 recursions and caused once a StackOverflowError, a second run worked. Took 1,2 sec.
|
// block 376078 has 2843 recursions and caused once a StackOverflowError, a second run worked. Took 1,2 sec.
|
||||||
|
|
||||||
// BTC MAIN NET
|
// BTC MAIN NET
|
||||||
private static final String BTC_GENESIS_TX_ID = "e5c8313c4144d219b5f6b2dacf1d36f2d43a9039bb2fcd1bd57f8352a9c9809a";
|
public static final String BTC_GENESIS_TX_ID = "e5c8313c4144d219b5f6b2dacf1d36f2d43a9039bb2fcd1bd57f8352a9c9809a";
|
||||||
private static final int BTC_GENESIS_BLOCK_HEIGHT = 477865; // 2017-07-28
|
public static final int BTC_GENESIS_BLOCK_HEIGHT = 477865; // 2017-07-28
|
||||||
|
|
||||||
// TEST NET
|
|
||||||
// Phase 0 initial genesis tx 6.10.2017: 2f194230e23459a9211322c4b1c182cf3f367086e8059aca2f8f44e20dac527a
|
|
||||||
// private static final String BTC_TEST_NET_GENESIS_TX_ID = "2f194230e23459a9211322c4b1c182cf3f367086e8059aca2f8f44e20dac527a";
|
|
||||||
// private static final int BTC_TEST_NET_GENESIS_BLOCK_HEIGHT = 1209140;
|
|
||||||
|
|
||||||
// Rebased genesis tx 9th november 2017
|
|
||||||
private static final String BTC_TEST_NET_GENESIS_TX_ID = "f8b65c65624bd822f92480c39959f8ae4a6f94a9841c1625464ec6353cfba1d9";
|
|
||||||
private static final int BTC_TEST_NET_GENESIS_BLOCK_HEIGHT = 1227630;
|
|
||||||
|
|
||||||
// REG TEST
|
|
||||||
private static final String BTC_REG_TEST_GENESIS_TX_ID = "5116d4f9107ce2b6bacacf750037f1b51fa302a9c96fe20c0d68b35728182a38";
|
|
||||||
private static final int BTC_REG_TEST_GENESIS_BLOCK_HEIGHT = 200;
|
|
||||||
|
|
||||||
public static int getGenesisHeight() {
|
|
||||||
switch (BisqEnvironment.getBaseCurrencyNetwork()) {
|
|
||||||
case BTC_TESTNET:
|
|
||||||
return BTC_TEST_NET_GENESIS_BLOCK_HEIGHT;
|
|
||||||
case BTC_REGTEST:
|
|
||||||
return BTC_REG_TEST_GENESIS_BLOCK_HEIGHT;
|
|
||||||
case BTC_MAINNET:
|
|
||||||
default:
|
|
||||||
return BTC_GENESIS_BLOCK_HEIGHT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -139,31 +117,19 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
@Inject
|
@Inject
|
||||||
public BsqBlockChain(PersistenceProtoResolver persistenceProtoResolver,
|
public BsqBlockChain(PersistenceProtoResolver persistenceProtoResolver,
|
||||||
@Named(Storage.STORAGE_DIR) File storageDir,
|
@Named(Storage.STORAGE_DIR) File storageDir,
|
||||||
@Named(DaoOptionKeys.REG_TEST_GENESIS_TX_ID) String manualGenesisTxId) {
|
@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
|
||||||
|
@Named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) int genesisBlockHeight) {
|
||||||
|
this.genesisTxId = genesisTxId;
|
||||||
|
this.genesisBlockHeight = genesisBlockHeight;
|
||||||
|
|
||||||
|
storage = new Storage<>(storageDir, persistenceProtoResolver);
|
||||||
|
|
||||||
bsqBlocks = new LinkedList<>();
|
bsqBlocks = new LinkedList<>();
|
||||||
txMap = new HashMap<>();
|
txMap = new HashMap<>();
|
||||||
unspentTxOutputsMap = new HashMap<>();
|
unspentTxOutputsMap = new HashMap<>();
|
||||||
compensationRequestFees = new HashSet<>();
|
compensationRequestFees = new HashSet<>();
|
||||||
votingFees = new HashSet<>();
|
votingFees = new HashSet<>();
|
||||||
|
|
||||||
storage = new Storage<>(storageDir, persistenceProtoResolver);
|
|
||||||
|
|
||||||
switch (BisqEnvironment.getBaseCurrencyNetwork()) {
|
|
||||||
case BTC_TESTNET:
|
|
||||||
genesisTxId = BTC_TEST_NET_GENESIS_TX_ID;
|
|
||||||
genesisBlockHeight = BTC_TEST_NET_GENESIS_BLOCK_HEIGHT;
|
|
||||||
break;
|
|
||||||
case BTC_REGTEST:
|
|
||||||
genesisTxId = manualGenesisTxId.isEmpty() ? BTC_REG_TEST_GENESIS_TX_ID : manualGenesisTxId;
|
|
||||||
genesisBlockHeight = BTC_REG_TEST_GENESIS_BLOCK_HEIGHT;
|
|
||||||
break;
|
|
||||||
case BTC_MAINNET:
|
|
||||||
default:
|
|
||||||
genesisTxId = BTC_GENESIS_TX_ID;
|
|
||||||
genesisBlockHeight = BTC_GENESIS_BLOCK_HEIGHT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock = new FunctionalReadWriteLock(true);
|
lock = new FunctionalReadWriteLock(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,14 +251,14 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
// Package scope write access
|
// Package scope write access
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void addBlock(BsqBlock block) throws BlockNotConnectingException {
|
public void addBlock(BsqBlock block) throws BlockNotConnectingException {
|
||||||
try {
|
try {
|
||||||
lock.write2(() -> {
|
lock.write2(() -> {
|
||||||
if (!bsqBlocks.contains(block)) {
|
if (!bsqBlocks.contains(block)) {
|
||||||
if (bsqBlocks.isEmpty() || (bsqBlocks.getLast().getHash().equals(block.getPreviousBlockHash()) &&
|
if (bsqBlocks.isEmpty() || (bsqBlocks.getLast().getHash().equals(block.getPreviousBlockHash()) &&
|
||||||
bsqBlocks.getLast().getHeight() + 1 == block.getHeight())) {
|
bsqBlocks.getLast().getHeight() + 1 == block.getHeight())) {
|
||||||
bsqBlocks.add(block);
|
bsqBlocks.add(block);
|
||||||
block.getTxs().stream().forEach(BsqBlockChain.this::addTxToMap);
|
block.getTxs().forEach(BsqBlockChain.this::addTxToMap);
|
||||||
chainHeadHeight = block.getHeight();
|
chainHeadHeight = block.getHeight();
|
||||||
maybeMakeSnapshot();
|
maybeMakeSnapshot();
|
||||||
printDetails();
|
printDetails();
|
||||||
|
@ -316,22 +282,22 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addTxToMap(Tx tx) {
|
public void addTxToMap(Tx tx) {
|
||||||
lock.write(() -> txMap.put(tx.getId(), tx));
|
lock.write(() -> txMap.put(tx.getId(), tx));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addUnspentTxOutput(TxOutput txOutput) {
|
public void addUnspentTxOutput(TxOutput txOutput) {
|
||||||
lock.write(() -> {
|
lock.write(() -> {
|
||||||
checkArgument(txOutput.isVerified(), "txOutput must be verified at addUnspentTxOutput");
|
checkArgument(txOutput.isVerified(), "txOutput must be verified at addUnspentTxOutput");
|
||||||
unspentTxOutputsMap.put(txOutput.getTxIdIndexTuple(), txOutput);
|
unspentTxOutputsMap.put(txOutput.getTxIdIndexTuple(), txOutput);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeUnspentTxOutput(TxOutput txOutput) {
|
public void removeUnspentTxOutput(TxOutput txOutput) {
|
||||||
lock.write(() -> unspentTxOutputsMap.remove(txOutput.getTxIdIndexTuple()));
|
lock.write(() -> unspentTxOutputsMap.remove(txOutput.getTxIdIndexTuple()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setGenesisTx(Tx tx) {
|
public void setGenesisTx(Tx tx) {
|
||||||
lock.write(() -> genesisTx = tx);
|
lock.write(() -> genesisTx = tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +318,7 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
return getClone(this);
|
return getClone(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BsqBlockChain getClone(BsqBlockChain bsqBlockChain) {
|
private BsqBlockChain getClone(BsqBlockChain bsqBlockChain) {
|
||||||
return lock.read(() -> (BsqBlockChain) BsqBlockChain.fromProto(bsqBlockChain.getBsqBlockChainBuilder().build()));
|
return lock.read(() -> (BsqBlockChain) BsqBlockChain.fromProto(bsqBlockChain.getBsqBlockChainBuilder().build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +326,7 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
return lock.read(() -> bsqBlocks.contains(bsqBlock));
|
return lock.read(() -> bsqBlocks.contains(bsqBlock));
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<TxOutput> getUnspentTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
private Optional<TxOutput> getUnspentTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
||||||
return lock.read(() -> unspentTxOutputsMap.entrySet().stream()
|
return lock.read(() -> unspentTxOutputsMap.entrySet().stream()
|
||||||
.filter(e -> e.getKey().equals(txIdIndexTuple))
|
.filter(e -> e.getKey().equals(txIdIndexTuple))
|
||||||
.map(Map.Entry::getValue).findAny());
|
.map(Map.Entry::getValue).findAny());
|
||||||
|
@ -387,7 +353,7 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
if (tx != null)
|
if (tx != null)
|
||||||
return Optional.of(tx);
|
return Optional.of(tx);
|
||||||
else
|
else
|
||||||
return Optional.<Tx>empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getChainHeadHeight() {
|
public int getChainHeadHeight() {
|
||||||
|
@ -398,13 +364,13 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
return lock.read(() -> txMap);
|
return lock.read(() -> txMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BsqBlock> getResettedBlocksFrom(int fromBlockHeight) {
|
public List<BsqBlock> getResetBlocksFrom(int fromBlockHeight) {
|
||||||
return lock.read(() -> {
|
return lock.read(() -> {
|
||||||
BsqBlockChain clone = getClone();
|
BsqBlockChain clone = getClone();
|
||||||
List<BsqBlock> filtered = clone.bsqBlocks.stream()
|
List<BsqBlock> filtered = clone.bsqBlocks.stream()
|
||||||
.filter(block -> block.getHeight() >= fromBlockHeight)
|
.filter(block -> block.getHeight() >= fromBlockHeight)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
filtered.stream().forEach(BsqBlock::reset);
|
filtered.forEach(BsqBlock::reset);
|
||||||
return filtered;
|
return filtered;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -438,16 +404,16 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
// Package scope read access
|
// Package scope read access
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
Optional<TxOutput> getSpendableTxOutput(String txId, int index) {
|
public Optional<TxOutput> getSpendableTxOutput(String txId, int index) {
|
||||||
return lock.read(() -> getSpendableTxOutput(new TxIdIndexTuple(txId, index)));
|
return lock.read(() -> getSpendableTxOutput(new TxIdIndexTuple(txId, index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
public Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
||||||
return lock.read(() -> getUnspentTxOutput(txIdIndexTuple)
|
return lock.read(() -> getUnspentTxOutput(txIdIndexTuple)
|
||||||
.filter(this::isTxOutputMature));
|
.filter(this::isTxOutputMature));
|
||||||
}
|
}
|
||||||
|
|
||||||
long getCreateCompensationRequestFee(int blockHeight) {
|
public long getCreateCompensationRequestFee(int blockHeight) {
|
||||||
return lock.read(() -> {
|
return lock.read(() -> {
|
||||||
long fee = -1;
|
long fee = -1;
|
||||||
for (Tuple2<Long, Integer> feeAtHeight : compensationRequestFees) {
|
for (Tuple2<Long, Integer> feeAtHeight : compensationRequestFees) {
|
||||||
|
@ -460,46 +426,44 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO not impl yet
|
//TODO not impl yet
|
||||||
boolean isCompensationRequestPeriodValid(int blockHeight) {
|
public boolean isCompensationRequestPeriodValid(int blockHeight) {
|
||||||
return lock.read(() -> true);
|
return lock.read(() -> true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long getVotingFee(int blockHeight) {
|
public long getVotingFee(int blockHeight) {
|
||||||
return lock.read(() -> {
|
return lock.read(() -> {
|
||||||
long fee = -1;
|
long fee = -1;
|
||||||
for (Tuple2<Long, Integer> feeAtHeight : votingFees) {
|
for (Tuple2<Long, Integer> feeAtHeight : votingFees) {
|
||||||
if (feeAtHeight.second <= blockHeight)
|
if (feeAtHeight.second <= blockHeight)
|
||||||
fee = feeAtHeight.first;
|
fee = feeAtHeight.first;
|
||||||
}
|
}
|
||||||
checkArgument(fee > -1, "compensationRequestFees must be set");
|
checkArgument(fee > -1, "votingFee must be set");
|
||||||
return fee;
|
return fee;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO not impl yet
|
//TODO not impl yet
|
||||||
boolean isVotingPeriodValid(int blockHeight) {
|
public boolean isVotingPeriodValid(int blockHeight) {
|
||||||
return lock.read(() -> true);
|
return lock.read(() -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean existsCompensationRequestBtcAddress(String btcAddress) {
|
public boolean existsCompensationRequestBtcAddress(String btcAddress) {
|
||||||
return lock.read(() -> getAllTxOutputs().stream()
|
return lock.read(() -> getAllTxOutputs().stream()
|
||||||
.filter(txOutput -> txOutput.isCompensationRequestBtcOutput() &&
|
.anyMatch(txOutput -> txOutput.isCompensationRequestBtcOutput() &&
|
||||||
txOutput.getAddress().equals(btcAddress))
|
btcAddress.equals(txOutput.getAddress())));
|
||||||
.findAny()
|
|
||||||
.isPresent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<TxOutput> findSponsoringBtcOutputsWithSameBtcAddress(String btcAddress) {
|
public Set<TxOutput> findSponsoringBtcOutputsWithSameBtcAddress(String btcAddress) {
|
||||||
return lock.read(() -> getAllTxOutputs().stream()
|
return lock.read(() -> getAllTxOutputs().stream()
|
||||||
.filter(txOutput -> txOutput.isSponsoringBtcOutput() &&
|
.filter(txOutput -> txOutput.isSponsoringBtcOutput() &&
|
||||||
txOutput.getAddress().equals(btcAddress))
|
btcAddress.equals(txOutput.getAddress()))
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
//TODO
|
||||||
// for genesis we dont need it and for issuance we need more implemented first
|
// for genesis we don't need it and for issuance we need more implemented first
|
||||||
boolean isTxOutputMature(TxOutput spendingTxOutput) {
|
private boolean isTxOutputMature(TxOutput spendingTxOutput) {
|
||||||
return lock.read(() -> true);
|
return lock.read(() -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,12 +491,12 @@ public class BsqBlockChain implements PersistableEnvelope {
|
||||||
final BsqBlockChain cloned = getClone(snapshotCandidate);
|
final BsqBlockChain cloned = getClone(snapshotCandidate);
|
||||||
checkNotNull(storage, "storage must not be null");
|
checkNotNull(storage, "storage must not be null");
|
||||||
storage.queueUpForSave(cloned);
|
storage.queueUpForSave(cloned);
|
||||||
// dont access cloned anymore with methods as locks are transient!
|
// don't access cloned anymore with methods as locks are transient!
|
||||||
log.info("Saved snapshotCandidate to Disc at height " + cloned.chainHeadHeight);
|
log.info("Saved snapshotCandidate to Disc at height " + cloned.chainHeadHeight);
|
||||||
}
|
}
|
||||||
// Now we clone and keep it in memory for the next trigger
|
// Now we clone and keep it in memory for the next trigger
|
||||||
snapshotCandidate = getClone(this);
|
snapshotCandidate = getClone(this);
|
||||||
// dont access cloned anymore with methods as locks are transient!
|
// don't access cloned anymore with methods as locks are transient!
|
||||||
log.debug("Cloned new snapshotCandidate at height " + snapshotCandidate.chainHeadHeight);
|
log.debug("Cloned new snapshotCandidate at height " + snapshotCandidate.chainHeadHeight);
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -25,6 +25,8 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* Passing the BsqNode directly to the classes interested in onBsqBlockChainChanged events cause Guice dependency issues,
|
* Passing the BsqNode directly to the classes interested in onBsqBlockChainChanged events cause Guice dependency issues,
|
||||||
* so we use that object to isolate that concern.
|
* so we use that object to isolate that concern.
|
||||||
|
*
|
||||||
|
* TODO check if refactorings has solved the dependency problems.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BsqBlockChainChangeDispatcher implements BsqBlockChainListener {
|
public class BsqBlockChainChangeDispatcher implements BsqBlockChainListener {
|
||||||
|
|
|
@ -1,160 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import io.bisq.common.UserThread;
|
|
||||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
|
||||||
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
|
||||||
import io.bisq.core.dao.blockchain.p2p.RequestManager;
|
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksResponse;
|
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.NewBsqBlockBroadcastMessage;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqLiteNodeExecutor;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqParser;
|
|
||||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
|
||||||
import io.bisq.core.provider.fee.FeeService;
|
|
||||||
import io.bisq.network.p2p.P2PService;
|
|
||||||
import io.bisq.network.p2p.network.Connection;
|
|
||||||
import io.bisq.network.p2p.seed.SeedNodeRepository;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
// We are in UserThread context. We get callbacks from threaded classes which are already mapped to the UserThread.
|
|
||||||
@Slf4j
|
|
||||||
public class BsqLiteNode extends BsqNode {
|
|
||||||
private final BsqLiteNodeExecutor bsqLiteNodeExecutor;
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Constructor
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
@Inject
|
|
||||||
public BsqLiteNode(P2PService p2PService,
|
|
||||||
BsqParser bsqParser,
|
|
||||||
BsqLiteNodeExecutor bsqLiteNodeExecutor,
|
|
||||||
BsqBlockChain bsqBlockChain,
|
|
||||||
FeeService feeService,
|
|
||||||
SeedNodeRepository seedNodeRepository) {
|
|
||||||
super(p2PService,
|
|
||||||
bsqParser,
|
|
||||||
bsqBlockChain,
|
|
||||||
feeService,
|
|
||||||
seedNodeRepository);
|
|
||||||
this.bsqLiteNodeExecutor = bsqLiteNodeExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Public methods
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
|
|
||||||
super.onAllServicesInitialized(errorMessageHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onP2PNetworkReady() {
|
|
||||||
super.onP2PNetworkReady();
|
|
||||||
|
|
||||||
requestManager = new RequestManager(p2PService.getNetworkNode(),
|
|
||||||
p2PService.getPeerManager(),
|
|
||||||
p2PService.getBroadcaster(),
|
|
||||||
seedNodeRepository.getSeedNodeAddresses(),
|
|
||||||
bsqBlockChain,
|
|
||||||
new RequestManager.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onBlockReceived(GetBsqBlocksResponse getBsqBlocksResponse) {
|
|
||||||
List<BsqBlock> bsqBlockList = new ArrayList<>(getBsqBlocksResponse.getBsqBlocks());
|
|
||||||
log.info("received msg with {} items", bsqBlockList.size());
|
|
||||||
if (bsqBlockList.size() > 0)
|
|
||||||
log.info("block height of last item: {}", bsqBlockList.get(bsqBlockList.size() - 1).getHeight());
|
|
||||||
// Be safe and reset all mutable data in case the provider would not have done it
|
|
||||||
bsqBlockList.stream().forEach(BsqBlock::reset);
|
|
||||||
bsqLiteNodeExecutor.parseBsqBlocksForLiteNode(bsqBlockList,
|
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId,
|
|
||||||
BsqLiteNode.this::onNewBsqBlock,
|
|
||||||
() -> onParseBlockchainComplete(genesisBlockHeight, genesisTxId), throwable -> {
|
|
||||||
if (throwable instanceof BlockNotConnectingException) {
|
|
||||||
startReOrgFromLastSnapshot();
|
|
||||||
} else {
|
|
||||||
log.error(throwable.toString());
|
|
||||||
throwable.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewBsqBlockBroadcastMessage(NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage) {
|
|
||||||
BsqBlock bsqBlock = newBsqBlockBroadcastMessage.getBsqBlock();
|
|
||||||
// Be safe and reset all mutable data in case the provider would not have done it
|
|
||||||
bsqBlock.reset();
|
|
||||||
log.info("received broadcastNewBsqBlock bsqBlock {}", bsqBlock.getHeight());
|
|
||||||
if (!bsqBlockChain.containsBlock(bsqBlock)) {
|
|
||||||
bsqLiteNodeExecutor.parseBsqBlockForLiteNode(bsqBlock,
|
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId,
|
|
||||||
() -> onNewBsqBlock(bsqBlock), throwable -> {
|
|
||||||
if (throwable instanceof BlockNotConnectingException) {
|
|
||||||
startReOrgFromLastSnapshot();
|
|
||||||
} else {
|
|
||||||
log.error(throwable.toString());
|
|
||||||
throwable.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNoSeedNodeAvailable() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage, @Nullable Connection connection) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// delay a bit to not stress too much at startup
|
|
||||||
UserThread.runAfter(this::startParseBlocks, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void parseBlocksWithChainHeadHeight(int startBlockHeight, int genesisBlockHeight, String genesisTxId) {
|
|
||||||
parseBlocks(startBlockHeight, genesisBlockHeight, genesisTxId, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void parseBlocks(int startBlockHeight, int genesisBlockHeight, String genesisTxId, Integer chainHeadHeight) {
|
|
||||||
requestManager.requestBlocks(startBlockHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onParseBlockchainComplete(int genesisBlockHeight, String genesisTxId) {
|
|
||||||
parseBlockchainComplete = true;
|
|
||||||
bsqBlockChainListeners.stream().forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,7 @@ import io.bisq.common.storage.JsonFileManager;
|
||||||
import io.bisq.common.storage.Storage;
|
import io.bisq.common.storage.Storage;
|
||||||
import io.bisq.common.util.Utilities;
|
import io.bisq.common.util.Utilities;
|
||||||
import io.bisq.core.dao.DaoOptionKeys;
|
import io.bisq.core.dao.DaoOptionKeys;
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxType;
|
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
|
|
|
@ -1,398 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.neemre.btcdcli4j.core.domain.Block;
|
|
||||||
import io.bisq.common.app.DevEnv;
|
|
||||||
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
|
||||||
import io.bisq.core.dao.blockchain.exceptions.BsqBlockchainException;
|
|
||||||
import io.bisq.core.dao.blockchain.vo.*;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses blocks and transactions for finding BSQ relevant transactions.
|
|
||||||
* <p/>
|
|
||||||
* We are in threaded context. Don't mix up with UserThread.
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Immutable
|
|
||||||
public class BsqParser {
|
|
||||||
private final BsqBlockChain bsqBlockChain;
|
|
||||||
private final OpReturnVerification opReturnVerification;
|
|
||||||
private final IssuanceVerification issuanceVerification;
|
|
||||||
private final RpcService rpcService;
|
|
||||||
|
|
||||||
// Maybe we want to request fee at some point, leave it for now and disable it
|
|
||||||
private boolean requestFee = false;
|
|
||||||
private final Map<Integer, Long> feesByBlock = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
@Inject
|
|
||||||
public BsqParser(RpcService rpcService,
|
|
||||||
BsqBlockChain bsqBlockChain,
|
|
||||||
OpReturnVerification opReturnVerification,
|
|
||||||
IssuanceVerification issuanceVerification) {
|
|
||||||
this.rpcService = rpcService;
|
|
||||||
this.bsqBlockChain = bsqBlockChain;
|
|
||||||
this.opReturnVerification = opReturnVerification;
|
|
||||||
this.issuanceVerification = issuanceVerification;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Parsing with data delivered with BsqBlock list
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// TODO check with similar code at BsqLiteNodeExecutor
|
|
||||||
void parseBsqBlocks(List<BsqBlock> bsqBlocks,
|
|
||||||
int genesisBlockHeight,
|
|
||||||
String genesisTxId,
|
|
||||||
Consumer<BsqBlock> newBlockHandler)
|
|
||||||
throws BlockNotConnectingException {
|
|
||||||
for (BsqBlock bsqBlock : bsqBlocks) {
|
|
||||||
parseBsqBlock(bsqBlock,
|
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId);
|
|
||||||
bsqBlockChain.addBlock(bsqBlock);
|
|
||||||
newBlockHandler.accept(bsqBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseBsqBlock(BsqBlock bsqBlock,
|
|
||||||
int genesisBlockHeight,
|
|
||||||
String genesisTxId) {
|
|
||||||
int blockHeight = bsqBlock.getHeight();
|
|
||||||
log.info("Parse block at height={} ", blockHeight);
|
|
||||||
List<Tx> txList = new ArrayList<>(bsqBlock.getTxs());
|
|
||||||
List<Tx> bsqTxsInBlock = new ArrayList<>();
|
|
||||||
bsqBlock.getTxs().stream()
|
|
||||||
.forEach(tx -> checkForGenesisTx(genesisBlockHeight, genesisTxId, blockHeight, bsqTxsInBlock, tx));
|
|
||||||
recursiveFindBsqTxs(bsqTxsInBlock, txList, blockHeight, 0, 5300);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Parsing with data requested from bsqBlockchainService
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
void parseBlocks(int startBlockHeight,
|
|
||||||
int chainHeadHeight,
|
|
||||||
int genesisBlockHeight,
|
|
||||||
String genesisTxId,
|
|
||||||
Consumer<BsqBlock> newBlockHandler)
|
|
||||||
throws BsqBlockchainException, BlockNotConnectingException {
|
|
||||||
try {
|
|
||||||
for (int blockHeight = startBlockHeight; blockHeight <= chainHeadHeight; blockHeight++) {
|
|
||||||
long startTs = System.currentTimeMillis();
|
|
||||||
Block btcdBlock = rpcService.requestBlock(blockHeight);
|
|
||||||
|
|
||||||
List<Tx> bsqTxsInBlock = findBsqTxsInBlock(btcdBlock,
|
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId);
|
|
||||||
final BsqBlock bsqBlock = new BsqBlock(btcdBlock.getHeight(),
|
|
||||||
btcdBlock.getHash(),
|
|
||||||
btcdBlock.getPreviousBlockHash(),
|
|
||||||
ImmutableList.copyOf(bsqTxsInBlock));
|
|
||||||
|
|
||||||
bsqBlockChain.addBlock(bsqBlock);
|
|
||||||
newBlockHandler.accept(bsqBlock);
|
|
||||||
log.info("parseBlock took {} ms at blockHeight {}; bsqTxsInBlock.size={}",
|
|
||||||
System.currentTimeMillis() - startTs, blockHeight, bsqTxsInBlock.size());
|
|
||||||
}
|
|
||||||
} catch (BlockNotConnectingException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
log.error(t.toString());
|
|
||||||
t.printStackTrace();
|
|
||||||
throw new BsqBlockchainException(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Tx> findBsqTxsInBlock(Block btcdBlock,
|
|
||||||
int genesisBlockHeight,
|
|
||||||
String genesisTxId)
|
|
||||||
throws BsqBlockchainException {
|
|
||||||
|
|
||||||
int blockHeight = btcdBlock.getHeight();
|
|
||||||
log.debug("Parse block at height={} ", blockHeight);
|
|
||||||
|
|
||||||
// check if the new block is the same chain we have built on.
|
|
||||||
List<Tx> txList = new ArrayList<>();
|
|
||||||
// We use a list as we want to maintain sorting of tx intra-block dependency
|
|
||||||
List<Tx> bsqTxsInBlock = new ArrayList<>();
|
|
||||||
// We add all transactions to the block
|
|
||||||
long startTs = System.currentTimeMillis();
|
|
||||||
for (String txId : btcdBlock.getTx()) {
|
|
||||||
if (requestFee)
|
|
||||||
rpcService.requestFees(txId, blockHeight, feesByBlock);
|
|
||||||
|
|
||||||
final Tx tx = rpcService.requestTx(txId, blockHeight);
|
|
||||||
txList.add(tx);
|
|
||||||
checkForGenesisTx(genesisBlockHeight, genesisTxId, blockHeight, bsqTxsInBlock, tx);
|
|
||||||
}
|
|
||||||
log.info("Requesting {} transactions took {} ms",
|
|
||||||
btcdBlock.getTx().size(), System.currentTimeMillis() - startTs);
|
|
||||||
// Worst case is that all txs in a block are depending on another, so only one get resolved at each iteration.
|
|
||||||
// Min tx size is 189 bytes (normally about 240 bytes), 1 MB can contain max. about 5300 txs (usually 2000).
|
|
||||||
// Realistically we don't expect more then a few recursive calls.
|
|
||||||
// There are some blocks with testing such dependency chains like block 130768 where at each iteration only
|
|
||||||
// one get resolved.
|
|
||||||
// Lately there is a patter with 24 iterations observed
|
|
||||||
recursiveFindBsqTxs(bsqTxsInBlock, txList, blockHeight, 0, 5300);
|
|
||||||
|
|
||||||
return bsqTxsInBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Parse when requested from new block arrived handler (rpc)
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
BsqBlock parseBlock(Block btcdBlock, int genesisBlockHeight, String genesisTxId)
|
|
||||||
throws BsqBlockchainException, BlockNotConnectingException {
|
|
||||||
List<Tx> bsqTxsInBlock = findBsqTxsInBlock(btcdBlock,
|
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId);
|
|
||||||
final BsqBlock bsqBlock = new BsqBlock(btcdBlock.getHeight(),
|
|
||||||
btcdBlock.getHash(),
|
|
||||||
btcdBlock.getPreviousBlockHash(),
|
|
||||||
ImmutableList.copyOf(bsqTxsInBlock));
|
|
||||||
bsqBlockChain.addBlock(bsqBlock);
|
|
||||||
return bsqBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Generic
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private void checkForGenesisTx(int genesisBlockHeight,
|
|
||||||
String genesisTxId,
|
|
||||||
int blockHeight,
|
|
||||||
List<Tx> bsqTxsInBlock,
|
|
||||||
Tx tx) {
|
|
||||||
if (tx.getId().equals(genesisTxId) && blockHeight == genesisBlockHeight) {
|
|
||||||
tx.getOutputs().stream().forEach(txOutput -> {
|
|
||||||
txOutput.setUnspent(true);
|
|
||||||
txOutput.setVerified(true);
|
|
||||||
bsqBlockChain.addUnspentTxOutput(txOutput);
|
|
||||||
});
|
|
||||||
tx.setTxType(TxType.GENESIS);
|
|
||||||
|
|
||||||
bsqBlockChain.setGenesisTx(tx);
|
|
||||||
bsqBlockChain.addTxToMap(tx);
|
|
||||||
bsqTxsInBlock.add(tx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance-wise the recursion does not hurt (e.g. 5-20 ms).
|
|
||||||
// The RPC requestTransaction is the bottleneck.
|
|
||||||
private void recursiveFindBsqTxs(List<Tx> bsqTxsInBlock,
|
|
||||||
List<Tx> transactions,
|
|
||||||
int blockHeight,
|
|
||||||
int recursionCounter,
|
|
||||||
int maxRecursions) {
|
|
||||||
// The set of txIds of txs which are used for inputs of another tx in same block
|
|
||||||
Set<String> intraBlockSpendingTxIdSet = getIntraBlockSpendingTxIdSet(transactions);
|
|
||||||
|
|
||||||
List<Tx> txsWithoutInputsFromSameBlock = new ArrayList<>();
|
|
||||||
List<Tx> txsWithInputsFromSameBlock = new ArrayList<>();
|
|
||||||
|
|
||||||
// First we find the txs which have no intra-block inputs
|
|
||||||
outerLoop:
|
|
||||||
for (Tx tx : transactions) {
|
|
||||||
for (TxInput input : tx.getInputs()) {
|
|
||||||
if (intraBlockSpendingTxIdSet.contains(input.getTxId())) {
|
|
||||||
// We have an input from one of the intra-block-transactions, so we cannot process that tx now.
|
|
||||||
// We add the tx for later parsing to the txsWithInputsFromSameBlock and move to the next tx.
|
|
||||||
txsWithInputsFromSameBlock.add(tx);
|
|
||||||
continue outerLoop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we have not found any tx input pointing to anther tx in the same block we add it to our
|
|
||||||
// txsWithoutInputsFromSameBlock.
|
|
||||||
txsWithoutInputsFromSameBlock.add(tx);
|
|
||||||
}
|
|
||||||
checkArgument(txsWithInputsFromSameBlock.size() + txsWithoutInputsFromSameBlock.size() == transactions.size(),
|
|
||||||
"txsWithInputsFromSameBlock.size + txsWithoutInputsFromSameBlock.size != transactions.size");
|
|
||||||
|
|
||||||
// Usual values is up to 25
|
|
||||||
// There are some blocks where it seems devs have tested graphs of many depending txs, but even
|
|
||||||
// those dont exceed 200 recursions and are mostly old blocks from 2012 when fees have been low ;-).
|
|
||||||
// TODO check strategy btc core uses (sorting the dependency graph would be an optimisation)
|
|
||||||
// Seems btc core delivers tx list sorted by dependency graph. -> TODO verify and test
|
|
||||||
if (recursionCounter > 1000) {
|
|
||||||
log.warn("Unusual high recursive calls at resolveConnectedTxs. recursionCounter=" + recursionCounter);
|
|
||||||
log.warn("blockHeight=" + blockHeight);
|
|
||||||
log.warn("txsWithoutInputsFromSameBlock.size " + txsWithoutInputsFromSameBlock.size());
|
|
||||||
log.warn("txsWithInputsFromSameBlock.size " + txsWithInputsFromSameBlock.size());
|
|
||||||
// log.warn("txsWithInputsFromSameBlock " + txsWithInputsFromSameBlock.stream().map(e->e.getId()).collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// we check if we have any valid BSQ from that tx set
|
|
||||||
bsqTxsInBlock.addAll(txsWithoutInputsFromSameBlock.stream()
|
|
||||||
.filter(tx -> isBsqTx(blockHeight, tx))
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
|
|
||||||
log.debug("Parsing of all txsWithoutInputsFromSameBlock is done.");
|
|
||||||
|
|
||||||
// we check if we have any valid BSQ utxo from that tx set
|
|
||||||
// We might have InputsFromSameBlock which are BTC only but not BSQ, so we cannot
|
|
||||||
// optimize here and need to iterate further.
|
|
||||||
if (!txsWithInputsFromSameBlock.isEmpty()) {
|
|
||||||
if (recursionCounter < maxRecursions) {
|
|
||||||
recursiveFindBsqTxs(bsqTxsInBlock, txsWithInputsFromSameBlock, blockHeight,
|
|
||||||
++recursionCounter, maxRecursions);
|
|
||||||
} else {
|
|
||||||
final String msg = "We exceeded our max. recursions for resolveConnectedTxs.\n" +
|
|
||||||
"txsWithInputsFromSameBlock=" + txsWithInputsFromSameBlock.toString() + "\n" +
|
|
||||||
"txsWithoutInputsFromSameBlock=" + txsWithoutInputsFromSameBlock;
|
|
||||||
log.warn(msg);
|
|
||||||
if (DevEnv.DEV_MODE)
|
|
||||||
throw new RuntimeException(msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug("We have no more txsWithInputsFromSameBlock.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
boolean isBsqTx(int blockHeight, Tx tx) {
|
|
||||||
return bsqBlockChain.<Boolean>callFunctionWithWriteLock(() -> doIsBsqTx(blockHeight, tx));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not thread safe wrt bsqBlockChain
|
|
||||||
// Check if any of the inputs are BSQ inputs and update BsqBlockChain state accordingly
|
|
||||||
private boolean doIsBsqTx(int blockHeight, Tx tx) {
|
|
||||||
boolean isBsqTx = false;
|
|
||||||
long availableBsqFromInputs = 0;
|
|
||||||
// For each input in tx
|
|
||||||
for (int inputIndex = 0; inputIndex < tx.getInputs().size(); inputIndex++) {
|
|
||||||
availableBsqFromInputs += getBsqFromInput(blockHeight, tx, inputIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have an input with BSQ we iterate the outputs
|
|
||||||
if (availableBsqFromInputs > 0) {
|
|
||||||
bsqBlockChain.addTxToMap(tx);
|
|
||||||
isBsqTx = true;
|
|
||||||
|
|
||||||
// We use order of output index. An output is a BSQ utxo as long there is enough input value
|
|
||||||
final List<TxOutput> outputs = tx.getOutputs();
|
|
||||||
TxOutput compRequestIssuanceOutputCandidate = null;
|
|
||||||
TxOutput bsqOutput = null;
|
|
||||||
for (int index = 0; index < outputs.size(); index++) {
|
|
||||||
TxOutput txOutput = outputs.get(index);
|
|
||||||
final long txOutputValue = txOutput.getValue();
|
|
||||||
|
|
||||||
// We do not check for pubKeyScript.scriptType.NULL_DATA because that is only set if dumpBlockchainData is true
|
|
||||||
if (txOutput.getOpReturnData() == null) {
|
|
||||||
if (availableBsqFromInputs >= txOutputValue && txOutputValue != 0) {
|
|
||||||
// We are spending available tokens
|
|
||||||
markOutputAsBsq(txOutput, tx);
|
|
||||||
availableBsqFromInputs -= txOutputValue;
|
|
||||||
bsqOutput = txOutput;
|
|
||||||
if (availableBsqFromInputs == 0)
|
|
||||||
log.debug("We don't have anymore BSQ to spend");
|
|
||||||
} else if (availableBsqFromInputs > 0 && compRequestIssuanceOutputCandidate == null) {
|
|
||||||
// availableBsq must be > 0 as we expect a bsqFee for an compRequestIssuanceOutput
|
|
||||||
// We store the btc output as it might be the issuance output from a compensation request which might become BSQ after voting.
|
|
||||||
compRequestIssuanceOutputCandidate = txOutput;
|
|
||||||
// As we have not verified the OP_RETURN yet we set it temporary to BTC_OUTPUT
|
|
||||||
txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
|
||||||
|
|
||||||
// The other outputs cannot be BSQ outputs so we ignore them.
|
|
||||||
// We set the index directly to the last output as that might be an OP_RETURN with DAO data
|
|
||||||
//TODO remove because its premature optimisation....
|
|
||||||
// index = Math.max(index, outputs.size() - 2);
|
|
||||||
} else {
|
|
||||||
log.debug("We got another BTC output. We ignore it.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// availableBsq is used as bsqFee paid to miners (burnt) if OP-RETURN is used
|
|
||||||
opReturnVerification.processDaoOpReturnData(tx, index, availableBsqFromInputs, blockHeight, compRequestIssuanceOutputCandidate, bsqOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableBsqFromInputs > 0) {
|
|
||||||
log.debug("BSQ have been left which was not spent. Burned BSQ amount={}, tx={}",
|
|
||||||
availableBsqFromInputs,
|
|
||||||
tx.toString());
|
|
||||||
tx.setBurntFee(availableBsqFromInputs);
|
|
||||||
if (tx.getTxType() == null)
|
|
||||||
tx.setTxType(TxType.PAY_TRADE_FEE);
|
|
||||||
}
|
|
||||||
} else if (issuanceVerification.maybeProcessData(tx)) {
|
|
||||||
// We don't have any BSQ input, so we test if it is a sponsor/issuance tx
|
|
||||||
log.debug("We got a issuance tx and process the data");
|
|
||||||
}
|
|
||||||
|
|
||||||
return isBsqTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not thread safe wrt bsqBlockChain
|
|
||||||
private long getBsqFromInput(int blockHeight, Tx tx, int inputIndex) {
|
|
||||||
long bsqFromInput = 0;
|
|
||||||
TxInput input = tx.getInputs().get(inputIndex);
|
|
||||||
// TODO check if Tuple indexes of inputs outputs are not messed up...
|
|
||||||
// Get spendable BSQ output for txidindextuple... (get output used as input in tx if it's spendable BSQ)
|
|
||||||
Optional<TxOutput> spendableTxOutput = bsqBlockChain.getSpendableTxOutput(input.getTxIdIndexTuple());
|
|
||||||
if (spendableTxOutput.isPresent()) {
|
|
||||||
// The output is BSQ, set it as spent, update bsqBlockChain and add to available BSQ for this tx
|
|
||||||
final TxOutput spentTxOutput = spendableTxOutput.get();
|
|
||||||
spentTxOutput.setUnspent(false);
|
|
||||||
bsqBlockChain.removeUnspentTxOutput(spentTxOutput);
|
|
||||||
spentTxOutput.setSpentInfo(new SpentInfo(blockHeight, tx.getId(), inputIndex));
|
|
||||||
input.setConnectedTxOutput(spentTxOutput);
|
|
||||||
bsqFromInput = spentTxOutput.getValue();
|
|
||||||
}
|
|
||||||
return bsqFromInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not thread safe wrt bsqBlockChain
|
|
||||||
private void markOutputAsBsq(TxOutput txOutput, Tx tx) {
|
|
||||||
// We are spending available tokens
|
|
||||||
txOutput.setVerified(true);
|
|
||||||
txOutput.setUnspent(true);
|
|
||||||
txOutput.setTxOutputType(TxOutputType.BSQ_OUTPUT);
|
|
||||||
tx.setTxType(TxType.TRANSFER_BSQ);
|
|
||||||
bsqBlockChain.addUnspentTxOutput(txOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getIntraBlockSpendingTxIdSet(List<Tx> txs) {
|
|
||||||
Set<String> txIdSet = txs.stream().map(Tx::getId).collect(Collectors.toSet());
|
|
||||||
Set<String> intraBlockSpendingTxIdSet = new HashSet<>();
|
|
||||||
txs.stream()
|
|
||||||
.forEach(tx -> tx.getInputs().stream()
|
|
||||||
.filter(input -> txIdSet.contains(input.getTxId()))
|
|
||||||
.forEach(input -> intraBlockSpendingTxIdSet.add(input.getTxId())));
|
|
||||||
return intraBlockSpendingTxIdSet;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.vo;
|
package io.bisq.core.dao.blockchain.vo;
|
||||||
|
|
||||||
|
import io.bisq.common.app.Version;
|
||||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||||
import io.bisq.generated.protobuffer.PB;
|
import io.bisq.generated.protobuffer.PB;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -30,15 +31,30 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class Tx implements PersistablePayload {
|
public class Tx implements PersistablePayload {
|
||||||
private final TxVo txVo;
|
private final String txVersion;
|
||||||
|
private final String id;
|
||||||
|
private final int blockHeight;
|
||||||
|
private final String blockHash;
|
||||||
|
private final long time;
|
||||||
private final List<TxInput> inputs;
|
private final List<TxInput> inputs;
|
||||||
private final List<TxOutput> outputs;
|
private final List<TxOutput> outputs;
|
||||||
|
|
||||||
private long burntFee;
|
private long burntFee;
|
||||||
private TxType txType;
|
private TxType txType;
|
||||||
|
|
||||||
public Tx(TxVo txVo, List<TxInput> inputs, List<TxOutput> outputs) {
|
public Tx(String id, int blockHeight,
|
||||||
this(txVo, inputs, outputs, 0, null);
|
String blockHash,
|
||||||
|
long time,
|
||||||
|
List<TxInput> inputs,
|
||||||
|
List<TxOutput> outputs) {
|
||||||
|
this(Version.BSQ_TX_VERSION,
|
||||||
|
id,
|
||||||
|
blockHeight,
|
||||||
|
blockHash,
|
||||||
|
time,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
0,
|
||||||
|
TxType.UNDEFINED_TX_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,8 +62,20 @@ public class Tx implements PersistablePayload {
|
||||||
// PROTO BUFFER
|
// PROTO BUFFER
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private Tx(TxVo txVo, List<TxInput> inputs, List<TxOutput> outputs, long burntFee, @Nullable TxType txType) {
|
private Tx(String txVersion,
|
||||||
this.txVo = txVo;
|
String id,
|
||||||
|
int blockHeight,
|
||||||
|
String blockHash,
|
||||||
|
long time,
|
||||||
|
List<TxInput> inputs,
|
||||||
|
List<TxOutput> outputs,
|
||||||
|
long burntFee,
|
||||||
|
@Nullable TxType txType) {
|
||||||
|
this.txVersion = txVersion;
|
||||||
|
this.id = id;
|
||||||
|
this.blockHeight = blockHeight;
|
||||||
|
this.blockHash = blockHash;
|
||||||
|
this.time = time;
|
||||||
this.inputs = inputs;
|
this.inputs = inputs;
|
||||||
this.outputs = outputs;
|
this.outputs = outputs;
|
||||||
this.burntFee = burntFee;
|
this.burntFee = burntFee;
|
||||||
|
@ -56,7 +84,11 @@ public class Tx implements PersistablePayload {
|
||||||
|
|
||||||
public PB.Tx toProtoMessage() {
|
public PB.Tx toProtoMessage() {
|
||||||
final PB.Tx.Builder builder = PB.Tx.newBuilder()
|
final PB.Tx.Builder builder = PB.Tx.newBuilder()
|
||||||
.setTxVo(txVo.toProtoMessage())
|
.setTxVersion(txVersion)
|
||||||
|
.setId(id)
|
||||||
|
.setBlockHeight(blockHeight)
|
||||||
|
.setBlockHash(blockHash)
|
||||||
|
.setTime(time)
|
||||||
.addAllInputs(inputs.stream()
|
.addAllInputs(inputs.stream()
|
||||||
.map(TxInput::toProtoMessage)
|
.map(TxInput::toProtoMessage)
|
||||||
.collect(Collectors.toList()))
|
.collect(Collectors.toList()))
|
||||||
|
@ -71,7 +103,11 @@ public class Tx implements PersistablePayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Tx fromProto(PB.Tx proto) {
|
public static Tx fromProto(PB.Tx proto) {
|
||||||
return new Tx(TxVo.fromProto(proto.getTxVo()),
|
return new Tx(proto.getTxVersion(),
|
||||||
|
proto.getId(),
|
||||||
|
proto.getBlockHeight(),
|
||||||
|
proto.getBlockHash(),
|
||||||
|
proto.getTime(),
|
||||||
proto.getInputsList().isEmpty() ?
|
proto.getInputsList().isEmpty() ?
|
||||||
new ArrayList<>() :
|
new ArrayList<>() :
|
||||||
proto.getInputsList().stream()
|
proto.getInputsList().stream()
|
||||||
|
@ -92,53 +128,28 @@ public class Tx implements PersistablePayload {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Optional<TxOutput> getTxOutput(int index) {
|
public Optional<TxOutput> getTxOutput(int index) {
|
||||||
return outputs.size() > index ? Optional.of(outputs.get(index)) : Optional.<TxOutput>empty();
|
return outputs.size() > index ? Optional.of(outputs.get(index)) : Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
burntFee = 0;
|
burntFee = 0;
|
||||||
txType = TxType.UNDEFINED_TX_TYPE;
|
txType = TxType.UNDEFINED_TX_TYPE;
|
||||||
inputs.stream().forEach(TxInput::reset);
|
inputs.forEach(TxInput::reset);
|
||||||
outputs.stream().forEach(TxOutput::reset);
|
outputs.forEach(TxOutput::reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Tx{" +
|
return "Tx{" +
|
||||||
"\ntxVersion='" + getTxVersion() + '\'' +
|
"\ntxVersion='" + txVersion + '\'' +
|
||||||
",\nid='" + getId() + '\'' +
|
",\nid='" + id + '\'' +
|
||||||
",\nblockHeight=" + getBlockHeight() +
|
",\nblockHeight=" + blockHeight +
|
||||||
",\nblockHash=" + getBlockHash() +
|
",\nblockHash=" + blockHash +
|
||||||
",\ntime=" + new Date(getTime()) +
|
",\ntime=" + new Date(time) +
|
||||||
",\ninputs=" + inputs +
|
",\ninputs=" + inputs +
|
||||||
",\noutputs=" + outputs +
|
",\noutputs=" + outputs +
|
||||||
",\nburntFee=" + burntFee +
|
",\nburntFee=" + burntFee +
|
||||||
",\ntxType=" + txType +
|
",\ntxType=" + txType +
|
||||||
"}\n";
|
"}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Delegates
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public String getTxVersion() {
|
|
||||||
return txVo.getTxVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return txVo.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBlockHeight() {
|
|
||||||
return txVo.getBlockHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBlockHash() {
|
|
||||||
return txVo.getBlockHash();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return txVo.getTime();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package io.bisq.core.dao.blockchain.vo;
|
package io.bisq.core.dao.blockchain.vo;
|
||||||
|
|
||||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.util.TxIdIndexTuple;
|
||||||
import io.bisq.generated.protobuffer.PB;
|
import io.bisq.generated.protobuffer.PB;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.google.protobuf.ByteString;
|
||||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||||
import io.bisq.common.util.JsonExclude;
|
import io.bisq.common.util.JsonExclude;
|
||||||
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
|
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.util.TxIdIndexTuple;
|
||||||
import io.bisq.generated.protobuffer.PB;
|
import io.bisq.generated.protobuffer.PB;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
|
@ -24,10 +24,10 @@ public enum TxOutputType {
|
||||||
UNDEFINED,
|
UNDEFINED,
|
||||||
BSQ_OUTPUT,
|
BSQ_OUTPUT,
|
||||||
BTC_OUTPUT,
|
BTC_OUTPUT,
|
||||||
OP_RETURN_OUTPUT,
|
|
||||||
COMPENSATION_REQUEST_OP_RETURN_OUTPUT,
|
COMPENSATION_REQUEST_OP_RETURN_OUTPUT,
|
||||||
COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT,
|
COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT,
|
||||||
VOTING_OP_RETURN_OUTPUT;
|
VOTE_OP_RETURN_OUTPUT,
|
||||||
|
VOTE_REVEAL_OP_RETURN_OUTPUT;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -29,6 +29,7 @@ public enum TxType {
|
||||||
PAY_TRADE_FEE,
|
PAY_TRADE_FEE,
|
||||||
COMPENSATION_REQUEST,
|
COMPENSATION_REQUEST,
|
||||||
VOTE,
|
VOTE,
|
||||||
|
VOTE_REVEAL,
|
||||||
ISSUANCE,
|
ISSUANCE,
|
||||||
LOCK_UP,
|
LOCK_UP,
|
||||||
UN_LOCK;
|
UN_LOCK;
|
||||||
|
|
|
@ -1,67 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.vo;
|
|
||||||
|
|
||||||
import io.bisq.common.app.Version;
|
|
||||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
|
||||||
import io.bisq.generated.protobuffer.PB;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
public class TxVo implements PersistablePayload {
|
|
||||||
private final String txVersion;
|
|
||||||
private final String id;
|
|
||||||
private final int blockHeight;
|
|
||||||
private final String blockHash;
|
|
||||||
private final long time;
|
|
||||||
|
|
||||||
public TxVo(String id, int blockHeight, String blockHash, long time) {
|
|
||||||
this(Version.BSQ_TX_VERSION, id, blockHeight, blockHash, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// PROTO BUFFER
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private TxVo(String txVersion, String id, int blockHeight, String blockHash, long time) {
|
|
||||||
this.txVersion = txVersion;
|
|
||||||
this.id = id;
|
|
||||||
this.blockHeight = blockHeight;
|
|
||||||
this.blockHash = blockHash;
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TxVo fromProto(PB.TxVo proto) {
|
|
||||||
return new TxVo(proto.getTxVersion(),
|
|
||||||
proto.getId(),
|
|
||||||
proto.getBlockHeight(),
|
|
||||||
proto.getBlockHash(),
|
|
||||||
proto.getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
public PB.TxVo toProtoMessage() {
|
|
||||||
return PB.TxVo.newBuilder()
|
|
||||||
.setTxVersion(txVersion)
|
|
||||||
.setId(id)
|
|
||||||
.setBlockHeight(blockHeight)
|
|
||||||
.setBlockHash(blockHash)
|
|
||||||
.setTime(time)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.vo;
|
package io.bisq.core.dao.blockchain.vo.util;
|
||||||
|
|
||||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||||
import io.bisq.generated.protobuffer.PB;
|
import io.bisq.generated.protobuffer.PB;
|
|
@ -15,45 +15,37 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain;
|
package io.bisq.core.dao.node;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||||
import io.bisq.core.dao.blockchain.p2p.RequestManager;
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqParser;
|
|
||||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
|
||||||
import io.bisq.core.provider.fee.FeeService;
|
import io.bisq.core.provider.fee.FeeService;
|
||||||
import io.bisq.network.p2p.P2PService;
|
import io.bisq.network.p2p.P2PService;
|
||||||
import io.bisq.network.p2p.P2PServiceListener;
|
import io.bisq.network.p2p.P2PServiceListener;
|
||||||
import io.bisq.network.p2p.seed.SeedNodeRepository;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// We are in UserThread context. We get callbacks from threaded classes which are already mapped to the UserThread.
|
/**
|
||||||
|
* Base class for the lite and full node.
|
||||||
|
* <p>
|
||||||
|
* We are in UserThread context. We get callbacks from threaded classes which are already mapped to the UserThread.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class BsqNode {
|
public abstract class BsqNode {
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Class fields
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
protected final P2PService p2PService;
|
protected final P2PService p2PService;
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
protected final BsqParser bsqParser;
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
protected final BsqBlockChain bsqBlockChain;
|
protected final BsqBlockChain bsqBlockChain;
|
||||||
protected final SeedNodeRepository seedNodeRepository;
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
protected final List<BsqBlockChainListener> bsqBlockChainListeners = new ArrayList<>();
|
protected final List<BsqBlockChainListener> bsqBlockChainListeners = new ArrayList<>();
|
||||||
protected final String genesisTxId;
|
private final String genesisTxId;
|
||||||
protected final int genesisBlockHeight;
|
private final int genesisBlockHeight;
|
||||||
protected RequestManager requestManager;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
protected boolean parseBlockchainComplete;
|
protected boolean parseBlockchainComplete;
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
@ -66,15 +58,11 @@ public abstract class BsqNode {
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@Inject
|
@Inject
|
||||||
public BsqNode(P2PService p2PService,
|
public BsqNode(P2PService p2PService,
|
||||||
BsqParser bsqParser,
|
|
||||||
BsqBlockChain bsqBlockChain,
|
BsqBlockChain bsqBlockChain,
|
||||||
FeeService feeService,
|
FeeService feeService) {
|
||||||
SeedNodeRepository seedNodeRepository) {
|
|
||||||
|
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.bsqParser = bsqParser;
|
|
||||||
this.bsqBlockChain = bsqBlockChain;
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
this.seedNodeRepository = seedNodeRepository;
|
|
||||||
|
|
||||||
genesisTxId = bsqBlockChain.getGenesisTxId();
|
genesisTxId = bsqBlockChain.getGenesisTxId();
|
||||||
genesisBlockHeight = bsqBlockChain.getGenesisBlockHeight();
|
genesisBlockHeight = bsqBlockChain.getGenesisBlockHeight();
|
||||||
|
@ -90,7 +78,25 @@ public abstract class BsqNode {
|
||||||
// Public methods
|
// Public methods
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
|
public abstract void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler);
|
||||||
|
|
||||||
|
public void addBsqBlockChainListener(BsqBlockChainListener bsqBlockChainListener) {
|
||||||
|
bsqBlockChainListeners.add(bsqBlockChainListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeBsqBlockChainListener(BsqBlockChainListener bsqBlockChainListener) {
|
||||||
|
bsqBlockChainListeners.remove(bsqBlockChainListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void shutDown();
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Protected
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected void onInitialized() {
|
||||||
applySnapshot();
|
applySnapshot();
|
||||||
log.info("onAllServicesInitialized");
|
log.info("onAllServicesInitialized");
|
||||||
if (p2PService.isBootstrapped()) {
|
if (p2PService.isBootstrapped()) {
|
||||||
|
@ -137,19 +143,14 @@ public abstract class BsqNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applySnapshot() {
|
|
||||||
bsqBlockChain.applySnapshot();
|
|
||||||
bsqBlockChainListeners.stream().forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
protected void onP2PNetworkReady() {
|
protected void onP2PNetworkReady() {
|
||||||
p2pNetworkReady = true;
|
p2pNetworkReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
protected void startParseBlocks() {
|
protected int getStartBlockHeight() {
|
||||||
int startBlockHeight = Math.max(genesisBlockHeight, bsqBlockChain.getChainHeadHeight() + 1);
|
final int startBlockHeight = Math.max(genesisBlockHeight, bsqBlockChain.getChainHeadHeight() + 1);
|
||||||
log.info("Start parse blocks:\n" +
|
log.info("Start parse blocks:\n" +
|
||||||
" Start block height={}\n" +
|
" Start block height={}\n" +
|
||||||
" Genesis txId={}\n" +
|
" Genesis txId={}\n" +
|
||||||
|
@ -160,22 +161,13 @@ public abstract class BsqNode {
|
||||||
genesisBlockHeight,
|
genesisBlockHeight,
|
||||||
bsqBlockChain.getChainHeadHeight());
|
bsqBlockChain.getChainHeadHeight());
|
||||||
|
|
||||||
parseBlocksWithChainHeadHeight(startBlockHeight,
|
return startBlockHeight;
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected void parseBlocksWithChainHeadHeight(int startBlockHeight, int genesisBlockHeight, String genesisTxId);
|
abstract protected void startParseBlocks();
|
||||||
|
|
||||||
abstract protected void parseBlocks(int startBlockHeight, int genesisBlockHeight, String genesisTxId, Integer chainHeadHeight);
|
protected void notifyListenersOnNewBlock() {
|
||||||
|
bsqBlockChainListeners.forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
||||||
abstract protected void onParseBlockchainComplete(int genesisBlockHeight, String genesisTxId);
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
protected void onNewBsqBlock(BsqBlock bsqBlock) {
|
|
||||||
//TODO called at each block at startup parsing. cause a lot of cpu waste at listeners...
|
|
||||||
// -> make more fine grained callbacks so UI only listens on final results when parsing is complete
|
|
||||||
bsqBlockChainListeners.stream().forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
@ -184,11 +176,13 @@ public abstract class BsqNode {
|
||||||
startParseBlocks();
|
startParseBlocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBsqBlockChainListener(BsqBlockChainListener bsqBlockChainListener) {
|
|
||||||
bsqBlockChainListeners.add(bsqBlockChainListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeBsqBlockChainListener(BsqBlockChainListener bsqBlockChainListener) {
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
bsqBlockChainListeners.remove(bsqBlockChainListener);
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void applySnapshot() {
|
||||||
|
bsqBlockChain.applySnapshot();
|
||||||
|
bsqBlockChainListeners.forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,17 +15,20 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain;
|
package io.bisq.core.dao.node;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.bisq.core.dao.DaoOptionKeys;
|
import io.bisq.core.dao.DaoOptionKeys;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
||||||
|
import io.bisq.core.dao.node.full.FullNode;
|
||||||
|
import io.bisq.core.dao.node.lite.LiteNode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic wiring of blockchain related services and event listeners
|
* Returns a FullNode or LiteNode based on the DaoOptionKeys.FULL_DAO_NODE option.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BsqNodeProvider {
|
public class BsqNodeProvider {
|
||||||
|
@ -33,8 +36,8 @@ public class BsqNodeProvider {
|
||||||
private final BsqNode bsqNode;
|
private final BsqNode bsqNode;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public BsqNodeProvider(BsqLiteNode bsqLiteNode,
|
public BsqNodeProvider(LiteNode bsqLiteNode,
|
||||||
BsqFullNode bsqFullNode,
|
FullNode bsqFullNode,
|
||||||
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
|
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
|
||||||
@Named(DaoOptionKeys.FULL_DAO_NODE) boolean fullDaoNode) {
|
@Named(DaoOptionKeys.FULL_DAO_NODE) boolean fullDaoNode) {
|
||||||
bsqNode = fullDaoNode ? bsqFullNode : bsqLiteNode;
|
bsqNode = fullDaoNode ? bsqFullNode : bsqLiteNode;
|
158
core/src/main/java/io/bisq/core/dao/node/BsqParser.java
Normal file
158
core/src/main/java/io/bisq/core/dao/node/BsqParser.java
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node;
|
||||||
|
|
||||||
|
import io.bisq.common.app.DevEnv;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxInput;
|
||||||
|
import io.bisq.core.dao.node.consensus.BsqTxVerification;
|
||||||
|
import io.bisq.core.dao.node.consensus.GenesisTxVerification;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for lite node parser and full node parser. Iterates blocks to find BSQ relevant transactions.
|
||||||
|
*
|
||||||
|
* We are in threaded context. Don't mix up with UserThread.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Immutable
|
||||||
|
public abstract class BsqParser {
|
||||||
|
protected final BsqBlockChain bsqBlockChain;
|
||||||
|
private final GenesisTxVerification genesisTxVerification;
|
||||||
|
private final BsqTxVerification bsqTxVerification;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
@Inject
|
||||||
|
public BsqParser(BsqBlockChain bsqBlockChain,
|
||||||
|
GenesisTxVerification genesisTxVerification,
|
||||||
|
BsqTxVerification bsqTxVerification) {
|
||||||
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
|
this.genesisTxVerification = genesisTxVerification;
|
||||||
|
this.bsqTxVerification = bsqTxVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Protected
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
protected void checkForGenesisTx(int blockHeight,
|
||||||
|
List<Tx> bsqTxsInBlock,
|
||||||
|
Tx tx) {
|
||||||
|
if (genesisTxVerification.isGenesisTx(tx, blockHeight)) {
|
||||||
|
genesisTxVerification.applyStateChange(tx);
|
||||||
|
bsqTxsInBlock.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance-wise the recursion does not hurt (e.g. 5-20 ms).
|
||||||
|
// The RPC requestTransaction is the bottleneck.
|
||||||
|
protected void recursiveFindBsqTxs(List<Tx> bsqTxsInBlock,
|
||||||
|
List<Tx> transactions,
|
||||||
|
int blockHeight,
|
||||||
|
int recursionCounter,
|
||||||
|
int maxRecursions) {
|
||||||
|
// The set of txIds of txs which are used for inputs of another tx in same block
|
||||||
|
Set<String> intraBlockSpendingTxIdSet = getIntraBlockSpendingTxIdSet(transactions);
|
||||||
|
|
||||||
|
List<Tx> txsWithoutInputsFromSameBlock = new ArrayList<>();
|
||||||
|
List<Tx> txsWithInputsFromSameBlock = new ArrayList<>();
|
||||||
|
|
||||||
|
// First we find the txs which have no intra-block inputs
|
||||||
|
outerLoop:
|
||||||
|
for (Tx tx : transactions) {
|
||||||
|
for (TxInput input : tx.getInputs()) {
|
||||||
|
if (intraBlockSpendingTxIdSet.contains(input.getTxId())) {
|
||||||
|
// We have an input from one of the intra-block-transactions, so we cannot process that tx now.
|
||||||
|
// We add the tx for later parsing to the txsWithInputsFromSameBlock and move to the next tx.
|
||||||
|
txsWithInputsFromSameBlock.add(tx);
|
||||||
|
continue outerLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we have not found any tx input pointing to anther tx in the same block we add it to our
|
||||||
|
// txsWithoutInputsFromSameBlock.
|
||||||
|
txsWithoutInputsFromSameBlock.add(tx);
|
||||||
|
}
|
||||||
|
checkArgument(txsWithInputsFromSameBlock.size() + txsWithoutInputsFromSameBlock.size() == transactions.size(),
|
||||||
|
"txsWithInputsFromSameBlock.size + txsWithoutInputsFromSameBlock.size != transactions.size");
|
||||||
|
|
||||||
|
// Usual values is up to 25
|
||||||
|
// There are some blocks where it seems developers have tested graphs of many depending txs, but even
|
||||||
|
// those don't exceed 200 recursions and are mostly old blocks from 2012 when fees have been low ;-).
|
||||||
|
// TODO check strategy btc core uses (sorting the dependency graph would be an optimisation)
|
||||||
|
// Seems btc core delivers tx list sorted by dependency graph. -> TODO verify and test
|
||||||
|
if (recursionCounter > 1000) {
|
||||||
|
log.warn("Unusual high recursive calls at resolveConnectedTxs. recursionCounter=" + recursionCounter);
|
||||||
|
log.warn("blockHeight=" + blockHeight);
|
||||||
|
log.warn("txsWithoutInputsFromSameBlock.size " + txsWithoutInputsFromSameBlock.size());
|
||||||
|
log.warn("txsWithInputsFromSameBlock.size " + txsWithInputsFromSameBlock.size());
|
||||||
|
// log.warn("txsWithInputsFromSameBlock " + txsWithInputsFromSameBlock.stream().map(e->e.getId()).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we check if we have any valid BSQ from that tx set
|
||||||
|
bsqTxsInBlock.addAll(txsWithoutInputsFromSameBlock.stream()
|
||||||
|
.filter(tx -> bsqTxVerification.isBsqTx(blockHeight, tx))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
log.debug("Parsing of all txsWithoutInputsFromSameBlock is done.");
|
||||||
|
|
||||||
|
// we check if we have any valid BSQ utxo from that tx set
|
||||||
|
// We might have InputsFromSameBlock which are BTC only but not BSQ, so we cannot
|
||||||
|
// optimize here and need to iterate further.
|
||||||
|
if (!txsWithInputsFromSameBlock.isEmpty()) {
|
||||||
|
if (recursionCounter < maxRecursions) {
|
||||||
|
recursiveFindBsqTxs(bsqTxsInBlock, txsWithInputsFromSameBlock, blockHeight,
|
||||||
|
++recursionCounter, maxRecursions);
|
||||||
|
} else {
|
||||||
|
final String msg = "We exceeded our max. recursions for resolveConnectedTxs.\n" +
|
||||||
|
"txsWithInputsFromSameBlock=" + txsWithInputsFromSameBlock.toString() + "\n" +
|
||||||
|
"txsWithoutInputsFromSameBlock=" + txsWithoutInputsFromSameBlock;
|
||||||
|
log.warn(msg);
|
||||||
|
if (DevEnv.isDevMode())
|
||||||
|
throw new RuntimeException(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("We have no more txsWithInputsFromSameBlock.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> getIntraBlockSpendingTxIdSet(List<Tx> txs) {
|
||||||
|
Set<String> txIdSet = txs.stream().map(Tx::getId).collect(Collectors.toSet());
|
||||||
|
Set<String> intraBlockSpendingTxIdSet = new HashSet<>();
|
||||||
|
txs.forEach(tx -> tx.getInputs().stream()
|
||||||
|
.filter(input -> txIdSet.contains(input.getTxId()))
|
||||||
|
.forEach(input -> intraBlockSpendingTxIdSet.add(input.getTxId())));
|
||||||
|
return intraBlockSpendingTxIdSet;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if a given transaction is a BSQ transaction.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class BsqTxVerification {
|
||||||
|
|
||||||
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
private final TxInputsVerification txInputsVerification;
|
||||||
|
private final TxOutputsVerification txOutputsVerification;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BsqTxVerification(BsqBlockChain bsqBlockChain,
|
||||||
|
TxInputsVerification txInputsVerification,
|
||||||
|
TxOutputsVerification txOutputsVerification) {
|
||||||
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
|
this.txInputsVerification = txInputsVerification;
|
||||||
|
this.txOutputsVerification = txOutputsVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBsqTx(int blockHeight, Tx tx) {
|
||||||
|
return bsqBlockChain.<Boolean>callFunctionWithWriteLock(() -> execute(blockHeight, tx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not thread safe wrt bsqBlockChain
|
||||||
|
// Check if any of the inputs are BSQ inputs and update BsqBlockChain state accordingly
|
||||||
|
private boolean execute(int blockHeight, Tx tx) {
|
||||||
|
BsqInputBalance bsqInputBalance = txInputsVerification.getBsqInputBalance(tx, blockHeight);
|
||||||
|
|
||||||
|
final boolean bsqInputBalancePositive = bsqInputBalance.isPositive();
|
||||||
|
if (bsqInputBalancePositive) {
|
||||||
|
txInputsVerification.applyStateChange(tx);
|
||||||
|
txOutputsVerification.iterate(tx, blockHeight, bsqInputBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lets check if we have left over BSQ (burned fees)
|
||||||
|
if (bsqInputBalance.isPositive()) {
|
||||||
|
log.debug("BSQ have been left which was not spent. Burned BSQ amount={}, tx={}", bsqInputBalance.getValue(), tx.toString());
|
||||||
|
tx.setBurntFee(bsqInputBalance.getValue());
|
||||||
|
|
||||||
|
// Fees are used for all OP_RETURN transactions and for PAY_TRADE_FEE.
|
||||||
|
// The TxType for a TRANSFER_BSQ will get overwritten if the tx has an OP_RETURN.
|
||||||
|
// If it was not overwritten (still is TRANSFER_BSQ) we change the TxType to PAY_TRADE_FEE.
|
||||||
|
if (tx.getTxType().equals(TxType.TRANSFER_BSQ))
|
||||||
|
tx.setTxType(TxType.PAY_TRADE_FEE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any tx with BSQ input is a BSQ tx (except genesis tx but that not handled in that class).
|
||||||
|
return bsqInputBalancePositive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
static class BsqInputBalance {
|
||||||
|
// Remaining BSQ from inputs
|
||||||
|
private long value = 0;
|
||||||
|
|
||||||
|
BsqInputBalance() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(long value) {
|
||||||
|
this.value += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subtract(long value) {
|
||||||
|
this.value -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPositive() {
|
||||||
|
return value > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isZero() {
|
||||||
|
return value == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,9 +15,10 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
import io.bisq.common.app.Version;
|
import io.bisq.common.app.Version;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
||||||
|
@ -25,6 +26,9 @@ import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if OP_RETURN data matches rules for a compensation request tx and applies state change.
|
||||||
|
*/
|
||||||
public class CompensationRequestVerification {
|
public class CompensationRequestVerification {
|
||||||
private final BsqBlockChain bsqBlockChain;
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
|
||||||
|
@ -33,17 +37,17 @@ public class CompensationRequestVerification {
|
||||||
this.bsqBlockChain = bsqBlockChain;
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean processOpReturnData(Tx tx, byte[] opReturnData, TxOutput opReturnTxOutput, long bsqFee, int blockHeight, TxOutput btcTxOutput) {
|
public boolean verify(byte[] opReturnData, long bsqFee, int blockHeight, TxOutputsVerification.MutableState mutableState) {
|
||||||
if (btcTxOutput != null &&
|
return mutableState.getCompRequestIssuanceOutputCandidate() != null &&
|
||||||
opReturnData.length > 1 &&
|
opReturnData.length == 22 &&
|
||||||
Version.COMPENSATION_REQUEST_VERSION == opReturnData[1] &&
|
Version.COMPENSATION_REQUEST_VERSION == opReturnData[1] &&
|
||||||
bsqFee == bsqBlockChain.getCreateCompensationRequestFee(blockHeight) &&
|
bsqFee == bsqBlockChain.getCreateCompensationRequestFee(blockHeight) &&
|
||||||
bsqBlockChain.isCompensationRequestPeriodValid(blockHeight)) {
|
bsqBlockChain.isCompensationRequestPeriodValid(blockHeight);
|
||||||
opReturnTxOutput.setTxOutputType(TxOutputType.COMPENSATION_REQUEST_OP_RETURN_OUTPUT);
|
|
||||||
btcTxOutput.setTxOutputType(TxOutputType.COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT);
|
|
||||||
tx.setTxType(TxType.COMPENSATION_REQUEST);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
public void applyStateChange(Tx tx, TxOutput opReturnTxOutput, TxOutputsVerification.MutableState mutableState) {
|
||||||
|
opReturnTxOutput.setTxOutputType(TxOutputType.COMPENSATION_REQUEST_OP_RETURN_OUTPUT);
|
||||||
|
mutableState.getCompRequestIssuanceOutputCandidate().setTxOutputType(TxOutputType.COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT);
|
||||||
|
tx.setTxType(TxType.COMPENSATION_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.DaoOptionKeys;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if a given transaction is a BSQ genesis transaction.
|
||||||
|
*/
|
||||||
|
public class GenesisTxVerification {
|
||||||
|
|
||||||
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
private final String genesisTxId;
|
||||||
|
private final int genesisBlockHeight;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GenesisTxVerification(BsqBlockChain bsqBlockChain,
|
||||||
|
@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
|
||||||
|
@Named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) int genesisBlockHeight) {
|
||||||
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
|
this.genesisTxId = genesisTxId;
|
||||||
|
this.genesisBlockHeight = genesisBlockHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGenesisTx(Tx tx, int blockHeight) {
|
||||||
|
return tx.getId().equals(genesisTxId) && blockHeight == genesisBlockHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyStateChange(Tx tx) {
|
||||||
|
tx.getOutputs().forEach(txOutput -> {
|
||||||
|
txOutput.setUnspent(true);
|
||||||
|
txOutput.setVerified(true);
|
||||||
|
bsqBlockChain.addUnspentTxOutput(txOutput);
|
||||||
|
});
|
||||||
|
tx.setTxType(TxType.GENESIS);
|
||||||
|
|
||||||
|
bsqBlockChain.setGenesisTx(tx);
|
||||||
|
bsqBlockChain.addTxToMap(tx);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,13 +15,14 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxType;
|
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
import io.bisq.core.dao.request.compensation.CompensationRequest;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -32,13 +33,13 @@ import java.util.Set;
|
||||||
//TODO outdated, ignore
|
//TODO outdated, ignore
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class IssuanceVerification {
|
public class IssuanceVerification {
|
||||||
public static final long MIN_BSQ_ISSUANCE_AMOUNT = 1000;
|
private static final long MIN_BSQ_ISSUANCE_AMOUNT = 1000;
|
||||||
public static final long MAX_BSQ_ISSUANCE_AMOUNT = 10_000_000;
|
private static final long MAX_BSQ_ISSUANCE_AMOUNT = 10_000_000;
|
||||||
|
|
||||||
private final BsqBlockChain bsqBlockChain;
|
private final BsqBlockChain bsqBlockChain;
|
||||||
private final PeriodVerification periodVerification;
|
private final PeriodVerification periodVerification;
|
||||||
private final VotingVerification votingVerification;
|
private final VotingVerification votingVerification;
|
||||||
private CompensationRequestManager compensationRequestManager;
|
private final CompensationRequestManager compensationRequestManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public IssuanceVerification(BsqBlockChain bsqBlockChain,
|
public IssuanceVerification(BsqBlockChain bsqBlockChain,
|
||||||
|
@ -51,7 +52,7 @@ public class IssuanceVerification {
|
||||||
this.compensationRequestManager = compensationRequestManager;
|
this.compensationRequestManager = compensationRequestManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean maybeProcessData(Tx tx) {
|
public boolean maybeProcessData(Tx tx) {
|
||||||
List<TxOutput> outputs = tx.getOutputs();
|
List<TxOutput> outputs = tx.getOutputs();
|
||||||
if (outputs.size() >= 2) {
|
if (outputs.size() >= 2) {
|
||||||
TxOutput bsqTxOutput = outputs.get(0);
|
TxOutput bsqTxOutput = outputs.get(0);
|
||||||
|
@ -72,7 +73,7 @@ public class IssuanceVerification {
|
||||||
for (TxOutput txOutput : issuanceTxs) {
|
for (TxOutput txOutput : issuanceTxs) {
|
||||||
if (txOutput.getBlockHeight() < height ||
|
if (txOutput.getBlockHeight() < height ||
|
||||||
(txOutput.getBlockHeight() == height &&
|
(txOutput.getBlockHeight() == height &&
|
||||||
txOutput.getId().compareTo(btcTxOutput.getId()) == 1)) {
|
txOutput.getId().compareTo(btcTxOutput.getId()) > 0)) {
|
||||||
alreadyFundedBtc += txOutput.getValue();
|
alreadyFundedBtc += txOutput.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,18 +15,21 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
import io.bisq.core.dao.DaoConstants;
|
import io.bisq.core.dao.OpReturnTypes;
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bitcoinj.core.Utils;
|
import org.bitcoinj.core.Utils;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if a given transaction is a BSQ OP_RETURN transaction.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class OpReturnVerification {
|
public class OpReturnVerification {
|
||||||
private final CompensationRequestVerification compensationRequestVerification;
|
private final CompensationRequestVerification compensationRequestVerification;
|
||||||
|
@ -39,35 +42,39 @@ public class OpReturnVerification {
|
||||||
this.votingVerification = votingVerification;
|
this.votingVerification = votingVerification;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME bsqOutput can be null in case there is no BSQ change output at comp requests tx
|
public void process(TxOutput txOutput, Tx tx, int index, long bsqFee, int blockHeight, TxOutputsVerification.MutableState mutableState) {
|
||||||
boolean processDaoOpReturnData(Tx tx, int index, long bsqFee,
|
|
||||||
int blockHeight, TxOutput btcOutput, TxOutput bsqOutput) {
|
|
||||||
List<TxOutput> txOutputs = tx.getOutputs();
|
|
||||||
TxOutput txOutput = txOutputs.get(index);
|
|
||||||
final long txOutputValue = txOutput.getValue();
|
final long txOutputValue = txOutput.getValue();
|
||||||
// If we get an OP_RETURN it has to be the last output and the txOutputValue has to be 0 as well there have be at least one bsqOutput
|
// A BSQ OP_RETURN has to be the last output, the txOutputValue has to be 0 as well as there have to be a BSQ fee.
|
||||||
if (txOutputValue == 0 && index == txOutputs.size() - 1 && bsqOutput != null && bsqFee > 0) {
|
if (txOutputValue == 0 && index == tx.getOutputs().size() - 1 && bsqFee > 0) {
|
||||||
byte[] opReturnData = txOutput.getOpReturnData();
|
byte[] opReturnData = txOutput.getOpReturnData();
|
||||||
// We expect at least the type byte
|
checkArgument(opReturnData != null, "opReturnData must not be null");
|
||||||
if (opReturnData != null && opReturnData.length > 1) {
|
// All BSQ OP_RETURN txs have at least a type byte
|
||||||
txOutput.setTxOutputType(TxOutputType.OP_RETURN_OUTPUT);
|
if (opReturnData.length >= 1) {
|
||||||
|
// Check with the type byte which kind of OP_RETURN we have.
|
||||||
switch (opReturnData[0]) {
|
switch (opReturnData[0]) {
|
||||||
case DaoConstants.OP_RETURN_TYPE_COMPENSATION_REQUEST:
|
case OpReturnTypes.COMPENSATION_REQUEST:
|
||||||
return compensationRequestVerification.processOpReturnData(tx, opReturnData, txOutput,
|
if (compensationRequestVerification.verify(opReturnData, bsqFee, blockHeight, mutableState)) {
|
||||||
bsqFee, blockHeight, btcOutput);
|
compensationRequestVerification.applyStateChange(tx, txOutput, mutableState);
|
||||||
case DaoConstants.OP_RETURN_TYPE_VOTE:
|
}
|
||||||
return votingVerification.processOpReturnData(tx, opReturnData, txOutput, bsqFee, blockHeight, bsqOutput);
|
case OpReturnTypes.VOTE:
|
||||||
|
// TODO
|
||||||
|
case OpReturnTypes.VOTE_RELEASE:
|
||||||
|
// TODO
|
||||||
|
case OpReturnTypes.LOCK_UP:
|
||||||
|
// TODO
|
||||||
|
case OpReturnTypes.UNLOCK:
|
||||||
|
// TODO
|
||||||
default:
|
default:
|
||||||
log.warn("OP_RETURN version of the BSQ tx ={} does not match expected version bytes. opReturnData={}",
|
log.warn("OP_RETURN version of the BSQ tx ={} does not match expected version bytes. opReturnData={}",
|
||||||
tx.getId(), Utils.HEX.encode(opReturnData));
|
tx.getId(), Utils.HEX.encode(opReturnData));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("opReturnData is null or has no content. opReturnData={}", opReturnData != null ? Utils.HEX.encode(opReturnData) : "null");
|
log.warn("opReturnData is null or has no content. opReturnData={}", Utils.HEX.encode(opReturnData));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("opReturnData is not matching DAO rules.");
|
log.warn("opReturnData is not matching DAO rules txId={} outValue={} index={} #outputs={} bsqFee={}",
|
||||||
|
tx.getId(), txOutputValue, index, tx.getOutputs().size(), bsqFee);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,9 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@ -28,7 +30,7 @@ public class PeriodVerification {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
boolean isInSponsorPeriod(int blockHeight) {
|
public boolean isInSponsorPeriod(int blockHeight) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.SpentInfo;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxInput;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide spendable TxOutput and apply state change.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class TxInputVerification {
|
||||||
|
|
||||||
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TxInputVerification(BsqBlockChain bsqBlockChain) {
|
||||||
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<TxOutput> getOptionalSpendableTxOutput(TxInput input) {
|
||||||
|
// TODO check if Tuple indexes of inputs outputs are not messed up...
|
||||||
|
// Get spendable BSQ output for txIdIndexTuple... (get output used as input in tx if it's spendable BSQ)
|
||||||
|
return bsqBlockChain.getSpendableTxOutput(input.getTxIdIndexTuple());
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyStateChange(TxInput input, TxOutput spendableTxOutput, int blockHeight, Tx tx, int inputIndex) {
|
||||||
|
// The output is BSQ, set it as spent, update bsqBlockChain and add to available BSQ for this tx
|
||||||
|
spendableTxOutput.setUnspent(false);
|
||||||
|
bsqBlockChain.removeUnspentTxOutput(spendableTxOutput);
|
||||||
|
spendableTxOutput.setSpentInfo(new SpentInfo(blockHeight, tx.getId(), inputIndex));
|
||||||
|
input.setConnectedTxOutput(spendableTxOutput);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxInput;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the available BSQ balance from all inputs and apply state change.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TxInputsVerification {
|
||||||
|
|
||||||
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
private final TxInputVerification txInputVerification;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TxInputsVerification(BsqBlockChain bsqBlockChain, TxInputVerification txInputVerification) {
|
||||||
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
|
this.txInputVerification = txInputVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
BsqTxVerification.BsqInputBalance getBsqInputBalance(Tx tx, int blockHeight) {
|
||||||
|
BsqTxVerification.BsqInputBalance bsqInputBalance = new BsqTxVerification.BsqInputBalance();
|
||||||
|
for (int inputIndex = 0; inputIndex < tx.getInputs().size(); inputIndex++) {
|
||||||
|
TxInput input = tx.getInputs().get(inputIndex);
|
||||||
|
final Optional<TxOutput> optionalSpendableTxOutput = txInputVerification.getOptionalSpendableTxOutput(input);
|
||||||
|
if (optionalSpendableTxOutput.isPresent()) {
|
||||||
|
bsqInputBalance.add(optionalSpendableTxOutput.get().getValue());
|
||||||
|
txInputVerification.applyStateChange(input, optionalSpendableTxOutput.get(), blockHeight, tx, inputIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bsqInputBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyStateChange(Tx tx) {
|
||||||
|
bsqBlockChain.addTxToMap(tx);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an output is a BSQ output and apply state change.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class TxOutputVerification {
|
||||||
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
private final OpReturnVerification opReturnVerification;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TxOutputVerification(BsqBlockChain bsqBlockChain,
|
||||||
|
OpReturnVerification opReturnVerification) {
|
||||||
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
|
this.opReturnVerification = opReturnVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
void verify(Tx tx,
|
||||||
|
TxOutput txOutput,
|
||||||
|
int index,
|
||||||
|
int blockHeight,
|
||||||
|
BsqTxVerification.BsqInputBalance bsqInputBalance,
|
||||||
|
TxOutputsVerification.MutableState mutableState) {
|
||||||
|
|
||||||
|
final long txOutputValue = txOutput.getValue();
|
||||||
|
final long bsqInputBalanceValue = bsqInputBalance.getValue();
|
||||||
|
if (bsqInputBalance.isPositive()) {
|
||||||
|
// We do not check for pubKeyScript.scriptType.NULL_DATA because that is only set if dumpBlockchainData is true
|
||||||
|
if (txOutput.getOpReturnData() == null) {
|
||||||
|
if (bsqInputBalanceValue >= txOutputValue) {
|
||||||
|
// We have enough BSQ in the inputs to fund that output. Update the input balance.
|
||||||
|
bsqInputBalance.subtract(txOutputValue);
|
||||||
|
|
||||||
|
// We apply the state change for the output.
|
||||||
|
applyStateChangeForBsqOutput(txOutput);
|
||||||
|
|
||||||
|
// We don't know for sure the tx type before we are finished with the iterations. It might get changed in
|
||||||
|
// the OP_RETURN verification or after iteration if we have left over remaining BSQ which gets
|
||||||
|
// burned. That would mark a PAY_TRADE_FEE. A normal TRANSFER_BSQ tx will not have a fee and no
|
||||||
|
// OP_RETURN, so we set that and if not overwritten later it will stay.
|
||||||
|
tx.setTxType(TxType.TRANSFER_BSQ);
|
||||||
|
|
||||||
|
// We store the output as BSQ output for use in further iterations.
|
||||||
|
mutableState.setBsqOutput(txOutput);
|
||||||
|
} else if (bsqInputBalance.isPositive() && mutableState.getCompRequestIssuanceOutputCandidate() == null) {
|
||||||
|
// We don't know yes if the tx is a compensation request tx as that will be detected in the last
|
||||||
|
// output which is a OP_RETURN output. We store that output for later use at the OP_RETURN
|
||||||
|
// verification.
|
||||||
|
mutableState.setCompRequestIssuanceOutputCandidate(txOutput);
|
||||||
|
log.debug("BTC output of BSQ issuance output from a compensation request tx");
|
||||||
|
|
||||||
|
// As we have not verified the OP_RETURN yet we set it temporary to BTC_OUTPUT so we cover the case
|
||||||
|
// if it was normal tx with a non BSQ OP_RETURN output.
|
||||||
|
applyStateChangeForBtcOutput(txOutput);
|
||||||
|
} else {
|
||||||
|
applyStateChangeForBtcOutput(txOutput);
|
||||||
|
log.debug("We got a BTC output.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We got a OP_RETURN output.
|
||||||
|
opReturnVerification.process(txOutput, tx, index, bsqInputBalanceValue, blockHeight, mutableState);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.debug("We don't have any BSQ available anymore.");
|
||||||
|
checkArgument(bsqInputBalance.isZero(), "bsqInputBalanceValue must not be negative");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyStateChangeForBsqOutput(TxOutput txOutput) {
|
||||||
|
txOutput.setVerified(true);
|
||||||
|
txOutput.setUnspent(true);
|
||||||
|
txOutput.setTxOutputType(TxOutputType.BSQ_OUTPUT);
|
||||||
|
bsqBlockChain.addUnspentTxOutput(txOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyStateChangeForBtcOutput(TxOutput txOutput) {
|
||||||
|
txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates all outputs and applies the verification for BSQ outputs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class TxOutputsVerification {
|
||||||
|
|
||||||
|
private final TxOutputVerification txOutputVerification;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TxOutputsVerification(TxOutputVerification txOutputVerification) {
|
||||||
|
this.txOutputVerification = txOutputVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
void iterate(Tx tx, int blockHeight, BsqTxVerification.BsqInputBalance bsqInputBalance) {
|
||||||
|
// We use order of output index. An output is a BSQ utxo as long there is enough input value
|
||||||
|
final List<TxOutput> outputs = tx.getOutputs();
|
||||||
|
MutableState mutableState = new MutableState();
|
||||||
|
for (int index = 0; index < outputs.size(); index++) {
|
||||||
|
txOutputVerification.verify(tx, outputs.get(index), index, blockHeight, bsqInputBalance, mutableState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
static class MutableState {
|
||||||
|
private TxOutput compRequestIssuanceOutputCandidate;
|
||||||
|
private TxOutput bsqOutput;
|
||||||
|
|
||||||
|
MutableState() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,19 +15,21 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.consensus;
|
||||||
|
|
||||||
import io.bisq.common.app.Version;
|
import io.bisq.common.app.Version;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxType;
|
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
import io.bisq.core.dao.request.compensation.CompensationRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
//TODO outdated, ignore
|
//TODO outdated, ignore
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class VotingVerification {
|
public class VotingVerification {
|
||||||
private final BsqBlockChain bsqBlockChain;
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
@ -40,15 +42,15 @@ public class VotingVerification {
|
||||||
this.periodVerification = periodVerification;
|
this.periodVerification = periodVerification;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isCompensationRequestAccepted(CompensationRequest compensationRequest) {
|
public boolean isCompensationRequestAccepted(CompensationRequest compensationRequest) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isConversionRateValid(int blockHeight, long btcAmount, long bsqAmount) {
|
public boolean isConversionRateValid(int blockHeight, long btcAmount, long bsqAmount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean processOpReturnData(Tx tx, byte[] opReturnData, TxOutput txOutput, long bsqFee, int blockHeight, TxOutput bsqOutput) {
|
public boolean isOpReturn(Tx tx, byte[] opReturnData, TxOutput txOutput, long bsqFee, int blockHeight, TxOutput bsqOutput) {
|
||||||
if (Version.VOTING_VERSION == opReturnData[1] && opReturnData.length > 22) {
|
if (Version.VOTING_VERSION == opReturnData[1] && opReturnData.length > 22) {
|
||||||
final int sizeOfCompRequestsVotes = (int) opReturnData[22];
|
final int sizeOfCompRequestsVotes = (int) opReturnData[22];
|
||||||
if (bsqOutput != null &&
|
if (bsqOutput != null &&
|
||||||
|
@ -57,7 +59,7 @@ public class VotingVerification {
|
||||||
opReturnData.length >= 23 + sizeOfCompRequestsVotes * 2 &&
|
opReturnData.length >= 23 + sizeOfCompRequestsVotes * 2 &&
|
||||||
bsqFee == bsqBlockChain.getVotingFee(blockHeight) &&
|
bsqFee == bsqBlockChain.getVotingFee(blockHeight) &&
|
||||||
bsqBlockChain.isVotingPeriodValid(blockHeight)) {
|
bsqBlockChain.isVotingPeriodValid(blockHeight)) {
|
||||||
txOutput.setTxOutputType(TxOutputType.VOTING_OP_RETURN_OUTPUT);
|
txOutput.setTxOutputType(TxOutputType.VOTE_OP_RETURN_OUTPUT);
|
||||||
tx.setTxType(TxType.VOTE);
|
tx.setTxType(TxType.VOTE);
|
||||||
// TODO use bsqOutput as weight
|
// TODO use bsqOutput as weight
|
||||||
return true;
|
return true;
|
|
@ -15,32 +15,31 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain;
|
package io.bisq.core.dao.node.full;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
||||||
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
||||||
import io.bisq.core.dao.blockchain.json.JsonBlockChainExporter;
|
import io.bisq.core.dao.blockchain.json.JsonBlockChainExporter;
|
||||||
import io.bisq.core.dao.blockchain.p2p.RequestManager;
|
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksResponse;
|
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.NewBsqBlockBroadcastMessage;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqFullNodeExecutor;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqParser;
|
|
||||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.node.BsqNode;
|
||||||
|
import io.bisq.core.dao.node.full.network.FullNodeNetworkManager;
|
||||||
import io.bisq.core.provider.fee.FeeService;
|
import io.bisq.core.provider.fee.FeeService;
|
||||||
import io.bisq.network.p2p.P2PService;
|
import io.bisq.network.p2p.P2PService;
|
||||||
import io.bisq.network.p2p.network.Connection;
|
|
||||||
import io.bisq.network.p2p.seed.SeedNodeRepository;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
// We are in UserThread context. We get callbacks from threaded classes which are already mapped to the UserThread.
|
/**
|
||||||
|
* Main class for a full node which have Bitcoin Core with rpc running and does the blockchain lookup itself.
|
||||||
|
* It also provides the BSQ transactions to lite nodes on request and broadcasts new BSQ blocks.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BsqFullNode extends BsqNode {
|
public class FullNode extends BsqNode {
|
||||||
|
|
||||||
private final BsqFullNodeExecutor bsqFullNodeExecutor;
|
private final FullNodeExecutor bsqFullNodeExecutor;
|
||||||
|
private final FullNodeNetworkManager fullNodeNetworkManager;
|
||||||
private final JsonBlockChainExporter jsonBlockChainExporter;
|
private final JsonBlockChainExporter jsonBlockChainExporter;
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,20 +49,18 @@ public class BsqFullNode extends BsqNode {
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@Inject
|
@Inject
|
||||||
public BsqFullNode(P2PService p2PService,
|
public FullNode(P2PService p2PService,
|
||||||
BsqParser bsqParser,
|
FullNodeExecutor bsqFullNodeExecutor,
|
||||||
BsqFullNodeExecutor bsqFullNodeExecutor,
|
|
||||||
BsqBlockChain bsqBlockChain,
|
BsqBlockChain bsqBlockChain,
|
||||||
JsonBlockChainExporter jsonBlockChainExporter,
|
JsonBlockChainExporter jsonBlockChainExporter,
|
||||||
FeeService feeService,
|
FeeService feeService,
|
||||||
SeedNodeRepository seedNodeRepository) {
|
FullNodeNetworkManager fullNodeNetworkManager) {
|
||||||
super(p2PService,
|
super(p2PService,
|
||||||
bsqParser,
|
|
||||||
bsqBlockChain,
|
bsqBlockChain,
|
||||||
feeService,
|
feeService);
|
||||||
seedNodeRepository);
|
|
||||||
this.bsqFullNodeExecutor = bsqFullNodeExecutor;
|
this.bsqFullNodeExecutor = bsqFullNodeExecutor;
|
||||||
this.jsonBlockChainExporter = jsonBlockChainExporter;
|
this.jsonBlockChainExporter = jsonBlockChainExporter;
|
||||||
|
this.fullNodeNetworkManager = fullNodeNetworkManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,123 +68,55 @@ public class BsqFullNode extends BsqNode {
|
||||||
// Public methods
|
// Public methods
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
|
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
|
||||||
// bsqFullNodeExecutor.setup need to return with result handler before
|
|
||||||
// super.onAllServicesInitialized(errorMessageHandler) is called
|
|
||||||
// bsqFullNodeExecutor.setup is and async call.
|
|
||||||
bsqFullNodeExecutor.setup(() -> {
|
bsqFullNodeExecutor.setup(() -> {
|
||||||
super.onAllServicesInitialized(errorMessageHandler);
|
super.onInitialized();
|
||||||
startParseBlocks();
|
startParseBlocks();
|
||||||
},
|
},
|
||||||
throwable -> {
|
throwable -> {
|
||||||
log.error(throwable.toString());
|
log.error(throwable.toString());
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
|
errorMessageHandler.handleErrorMessage("Initializing BsqFullNode failed: Error=" + throwable.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void shutDown() {
|
||||||
protected void parseBlocksWithChainHeadHeight(int startBlockHeight, int genesisBlockHeight, String genesisTxId) {
|
jsonBlockChainExporter.shutDown();
|
||||||
log.info("parseBlocksWithChainHeadHeight startBlockHeight={}", startBlockHeight);
|
fullNodeNetworkManager.shutDown();
|
||||||
bsqFullNodeExecutor.requestChainHeadHeight(chainHeadHeight -> parseBlocks(startBlockHeight, genesisBlockHeight, genesisTxId, chainHeadHeight),
|
|
||||||
throwable -> {
|
|
||||||
log.error(throwable.toString());
|
|
||||||
throwable.printStackTrace();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Protected
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void parseBlocks(int startBlockHeight, int genesisBlockHeight, String genesisTxId, Integer chainHeadHeight) {
|
protected void startParseBlocks() {
|
||||||
log.info("parseBlocks with from={} with chainHeadHeight={}", startBlockHeight, chainHeadHeight);
|
requestChainHeadHeightAndParseBlocks(getStartBlockHeight());
|
||||||
if (chainHeadHeight != startBlockHeight) {
|
|
||||||
if (startBlockHeight <= chainHeadHeight) {
|
|
||||||
bsqFullNodeExecutor.parseBlocks(startBlockHeight,
|
|
||||||
chainHeadHeight,
|
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId,
|
|
||||||
this::onNewBsqBlock,
|
|
||||||
() -> {
|
|
||||||
// we are done but it might be that new blocks have arrived in the meantime,
|
|
||||||
// so we try again with startBlockHeight set to current chainHeadHeight
|
|
||||||
// We also set up the listener in the else main branch where we check
|
|
||||||
// if we at chainTip, so do nto include here another check as it would
|
|
||||||
// not trigger the listener registration.
|
|
||||||
parseBlocksWithChainHeadHeight(chainHeadHeight,
|
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId);
|
|
||||||
}, throwable -> {
|
|
||||||
if (throwable instanceof BlockNotConnectingException) {
|
|
||||||
startReOrgFromLastSnapshot();
|
|
||||||
} else {
|
|
||||||
log.error(throwable.toString());
|
|
||||||
throwable.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.warn("We are trying to start with a block which is above the chain height of bitcoin core. We need probably wait longer until bitcoin core has fully synced. We try again after a delay of 1 min.");
|
|
||||||
UserThread.runAfter(() -> parseBlocksWithChainHeadHeight(startBlockHeight, genesisBlockHeight, genesisTxId), 60);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We dont have received new blocks in the meantime so we are completed and we register our handler
|
|
||||||
onParseBlockchainComplete(genesisBlockHeight, genesisTxId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onP2PNetworkReady() {
|
protected void onP2PNetworkReady() {
|
||||||
super.onP2PNetworkReady();
|
super.onP2PNetworkReady();
|
||||||
|
|
||||||
if (requestManager == null && p2pNetworkReady) {
|
if (parseBlockchainComplete)
|
||||||
createRequestBlocksManager();
|
|
||||||
addBlockHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void onParseBlockchainComplete(int genesisBlockHeight, String genesisTxId) {
|
|
||||||
log.info("onParseBlockchainComplete");
|
|
||||||
parseBlockchainComplete = true;
|
|
||||||
|
|
||||||
if (requestManager == null && p2pNetworkReady) {
|
|
||||||
createRequestBlocksManager();
|
|
||||||
addBlockHandler();
|
addBlockHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
bsqBlockChainListeners.stream().forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
private void onNewBsqBlock(BsqBlock bsqBlock) {
|
||||||
|
notifyListenersOnNewBlock();
|
||||||
|
jsonBlockChainExporter.maybeExport();
|
||||||
|
if (parseBlockchainComplete && p2pNetworkReady)
|
||||||
|
fullNodeNetworkManager.publishNewBlock(bsqBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRequestBlocksManager() {
|
|
||||||
requestManager = new RequestManager(p2PService.getNetworkNode(),
|
|
||||||
p2PService.getPeerManager(),
|
|
||||||
p2PService.getBroadcaster(),
|
|
||||||
seedNodeRepository.getSeedNodeAddresses(),
|
|
||||||
bsqBlockChain,
|
|
||||||
new RequestManager.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onBlockReceived(GetBsqBlocksResponse getBsqBlocksResponse) {
|
|
||||||
|
|
||||||
}
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
@Override
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
public void onNewBsqBlockBroadcastMessage(NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNoSeedNodeAvailable() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage, @Nullable Connection connection) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addBlockHandler() {
|
private void addBlockHandler() {
|
||||||
// We register our handler for new blocks
|
|
||||||
bsqFullNodeExecutor.addBlockHandler(btcdBlock -> bsqFullNodeExecutor.parseBtcdBlock(btcdBlock,
|
bsqFullNodeExecutor.addBlockHandler(btcdBlock -> bsqFullNodeExecutor.parseBtcdBlock(btcdBlock,
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId,
|
|
||||||
this::onNewBsqBlock,
|
this::onNewBsqBlock,
|
||||||
throwable -> {
|
throwable -> {
|
||||||
if (throwable instanceof BlockNotConnectingException) {
|
if (throwable instanceof BlockNotConnectingException) {
|
||||||
|
@ -199,11 +128,55 @@ public class BsqFullNode extends BsqNode {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void requestChainHeadHeightAndParseBlocks(int startBlockHeight) {
|
||||||
protected void onNewBsqBlock(BsqBlock bsqBlock) {
|
log.info("parseBlocks startBlockHeight={}", startBlockHeight);
|
||||||
super.onNewBsqBlock(bsqBlock);
|
bsqFullNodeExecutor.requestChainHeadHeight(chainHeadHeight -> parseBlocksOnHeadHeight(startBlockHeight, chainHeadHeight),
|
||||||
jsonBlockChainExporter.maybeExport();
|
throwable -> {
|
||||||
if (parseBlockchainComplete && p2pNetworkReady && requestManager != null)
|
log.error(throwable.toString());
|
||||||
requestManager.publishNewBlock(bsqBlock);
|
throwable.printStackTrace();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseBlocksOnHeadHeight(int startBlockHeight, Integer chainHeadHeight) {
|
||||||
|
log.info("parseBlocks with from={} with chainHeadHeight={}", startBlockHeight, chainHeadHeight);
|
||||||
|
if (chainHeadHeight != startBlockHeight) {
|
||||||
|
if (startBlockHeight <= chainHeadHeight) {
|
||||||
|
bsqFullNodeExecutor.parseBlocks(startBlockHeight,
|
||||||
|
chainHeadHeight,
|
||||||
|
this::onNewBsqBlock,
|
||||||
|
() -> {
|
||||||
|
// We are done but it might be that new blocks have arrived in the meantime,
|
||||||
|
// so we try again with startBlockHeight set to current chainHeadHeight
|
||||||
|
// We also set up the listener in the else main branch where we check
|
||||||
|
// if we are at chainTip, so do not include here another check as it would
|
||||||
|
// not trigger the listener registration.
|
||||||
|
requestChainHeadHeightAndParseBlocks(chainHeadHeight);
|
||||||
|
}, throwable -> {
|
||||||
|
if (throwable instanceof BlockNotConnectingException) {
|
||||||
|
startReOrgFromLastSnapshot();
|
||||||
|
} else {
|
||||||
|
log.error(throwable.toString());
|
||||||
|
throwable.printStackTrace();
|
||||||
|
//TODO write error to an errorProperty
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.warn("We are trying to start with a block which is above the chain height of bitcoin core. We need probably wait longer until bitcoin core has fully synced. We try again after a delay of 1 min.");
|
||||||
|
UserThread.runAfter(() -> requestChainHeadHeightAndParseBlocks(startBlockHeight), 60);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We don't have received new blocks in the meantime so we are completed and we register our handler
|
||||||
|
onParseBlockchainComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onParseBlockchainComplete() {
|
||||||
|
log.info("onParseBlockchainComplete");
|
||||||
|
parseBlockchainComplete = true;
|
||||||
|
|
||||||
|
if (p2pNetworkReady)
|
||||||
|
addBlockHandler();
|
||||||
|
|
||||||
|
bsqBlockChainListeners.forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.full;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
@ -26,23 +26,26 @@ import io.bisq.common.UserThread;
|
||||||
import io.bisq.common.handlers.ResultHandler;
|
import io.bisq.common.handlers.ResultHandler;
|
||||||
import io.bisq.common.util.Utilities;
|
import io.bisq.common.util.Utilities;
|
||||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.node.full.rpc.RpcService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
// Used for non blocking access to BTC blockchain data and parsing. Encapsulate thread context, so caller
|
/**
|
||||||
// gets always called on UserThread
|
* Processes tasks in custom threads. Results are mapped back to user thread so client don't need to deal with threading.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BsqFullNodeExecutor {
|
public class FullNodeExecutor {
|
||||||
|
|
||||||
private final BsqParser bsqParser;
|
private final FullNodeParser fullNodeParser;
|
||||||
private final RpcService rpcService;
|
private final RpcService rpcService;
|
||||||
|
|
||||||
private final ListeningExecutorService parseBlocksExecutor = Utilities.getListeningExecutorService("ParseBlocks", 1, 1, 60);
|
|
||||||
private final ListeningExecutorService getChainHeightExecutor = Utilities.getListeningExecutorService("GetChainHeight", 1, 1, 60);
|
|
||||||
private final ListeningExecutorService setupExecutor = Utilities.getListeningExecutorService("RpcServiceSetup", 1, 1, 5);
|
private final ListeningExecutorService setupExecutor = Utilities.getListeningExecutorService("RpcServiceSetup", 1, 1, 5);
|
||||||
|
private final ListeningExecutorService getChainHeightExecutor = Utilities.getListeningExecutorService("GetChainHeight", 1, 1, 60);
|
||||||
|
private final ListeningExecutorService parseBlockExecutor = Utilities.getListeningExecutorService("ParseBlock", 1, 1, 60);
|
||||||
|
private final ListeningExecutorService parseBlocksExecutor = Utilities.getListeningExecutorService("ParseBlocks", 1, 1, 60);
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -51,12 +54,17 @@ public class BsqFullNodeExecutor {
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@Inject
|
@Inject
|
||||||
public BsqFullNodeExecutor(RpcService rpcService, BsqParser bsqParser) {
|
public FullNodeExecutor(RpcService rpcService, FullNodeParser fullNodeParser) {
|
||||||
this.rpcService = rpcService;
|
this.rpcService = rpcService;
|
||||||
this.bsqParser = bsqParser;
|
this.fullNodeParser = fullNodeParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setup(ResultHandler resultHandler, Consumer<Throwable> errorHandler) {
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Package private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void setup(ResultHandler resultHandler, Consumer<Throwable> errorHandler) {
|
||||||
ListenableFuture<Void> future = setupExecutor.submit(() -> {
|
ListenableFuture<Void> future = setupExecutor.submit(() -> {
|
||||||
rpcService.setup();
|
rpcService.setup();
|
||||||
return null;
|
return null;
|
||||||
|
@ -73,7 +81,7 @@ public class BsqFullNodeExecutor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestChainHeadHeight(Consumer<Integer> resultHandler, Consumer<Throwable> errorHandler) {
|
void requestChainHeadHeight(Consumer<Integer> resultHandler, Consumer<Throwable> errorHandler) {
|
||||||
ListenableFuture<Integer> future = getChainHeightExecutor.submit(rpcService::requestChainHeadHeight);
|
ListenableFuture<Integer> future = getChainHeightExecutor.submit(rpcService::requestChainHeadHeight);
|
||||||
|
|
||||||
Futures.addCallback(future, new FutureCallback<Integer>() {
|
Futures.addCallback(future, new FutureCallback<Integer>() {
|
||||||
|
@ -87,19 +95,15 @@ public class BsqFullNodeExecutor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseBlocks(int startBlockHeight,
|
void parseBlocks(int startBlockHeight,
|
||||||
int chainHeadHeight,
|
int chainHeadHeight,
|
||||||
int genesisBlockHeight,
|
|
||||||
String genesisTxId,
|
|
||||||
Consumer<BsqBlock> newBlockHandler,
|
Consumer<BsqBlock> newBlockHandler,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
Consumer<Throwable> errorHandler) {
|
Consumer<Throwable> errorHandler) {
|
||||||
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
||||||
long startTs = System.currentTimeMillis();
|
long startTs = System.currentTimeMillis();
|
||||||
bsqParser.parseBlocks(startBlockHeight,
|
fullNodeParser.parseBlocks(startBlockHeight,
|
||||||
chainHeadHeight,
|
chainHeadHeight,
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId,
|
|
||||||
newBsqBlock -> UserThread.execute(() -> newBlockHandler.accept(newBsqBlock)));
|
newBsqBlock -> UserThread.execute(() -> newBlockHandler.accept(newBsqBlock)));
|
||||||
log.info("parseBlocks took {} ms for {} blocks", System.currentTimeMillis() - startTs, chainHeadHeight - startBlockHeight);
|
log.info("parseBlocks took {} ms for {} blocks", System.currentTimeMillis() - startTs, chainHeadHeight - startBlockHeight);
|
||||||
return null;
|
return null;
|
||||||
|
@ -118,14 +122,10 @@ public class BsqFullNodeExecutor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseBtcdBlock(Block btcdBlock,
|
void parseBtcdBlock(Block btcdBlock,
|
||||||
int genesisBlockHeight,
|
|
||||||
String genesisTxId,
|
|
||||||
Consumer<BsqBlock> resultHandler,
|
Consumer<BsqBlock> resultHandler,
|
||||||
Consumer<Throwable> errorHandler) {
|
Consumer<Throwable> errorHandler) {
|
||||||
ListenableFuture<BsqBlock> future = parseBlocksExecutor.submit(() -> bsqParser.parseBlock(btcdBlock,
|
ListenableFuture<BsqBlock> future = parseBlockExecutor.submit(() -> fullNodeParser.parseBlock(btcdBlock));
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId));
|
|
||||||
|
|
||||||
Futures.addCallback(future, new FutureCallback<BsqBlock>() {
|
Futures.addCallback(future, new FutureCallback<BsqBlock>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,7 +140,7 @@ public class BsqFullNodeExecutor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBlockHandler(Consumer<Block> blockHandler) {
|
void addBlockHandler(Consumer<Block> blockHandler) {
|
||||||
rpcService.registerBlockHandler(blockHandler);
|
rpcService.registerBlockHandler(blockHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.full;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.neemre.btcdcli4j.core.domain.Block;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
||||||
|
import io.bisq.core.dao.blockchain.exceptions.BsqBlockchainException;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.node.BsqParser;
|
||||||
|
import io.bisq.core.dao.node.consensus.BsqTxVerification;
|
||||||
|
import io.bisq.core.dao.node.consensus.GenesisTxVerification;
|
||||||
|
import io.bisq.core.dao.node.full.rpc.RpcService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for full nodes. Request blockchain data via rpc from Bitcoin Core and iterates blocks to find BSQ relevant transactions.
|
||||||
|
* <p>
|
||||||
|
* We are in threaded context. Don't mix up with UserThread.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class FullNodeParser extends BsqParser {
|
||||||
|
|
||||||
|
private final RpcService rpcService;
|
||||||
|
// Maybe we want to request fee at some point, leave it for now and disable it
|
||||||
|
private final boolean requestFee = false;
|
||||||
|
private final Map<Integer, Long> feesByBlock = new HashMap<>();
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FullNodeParser(RpcService rpcService,
|
||||||
|
BsqBlockChain bsqBlockChain,
|
||||||
|
GenesisTxVerification genesisTxVerification,
|
||||||
|
BsqTxVerification bsqTxVerification) {
|
||||||
|
super(bsqBlockChain, genesisTxVerification, bsqTxVerification);
|
||||||
|
this.rpcService = rpcService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Package private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void parseBlocks(int startBlockHeight,
|
||||||
|
int chainHeadHeight,
|
||||||
|
Consumer<BsqBlock> newBlockHandler) throws BsqBlockchainException, BlockNotConnectingException {
|
||||||
|
try {
|
||||||
|
for (int blockHeight = startBlockHeight; blockHeight <= chainHeadHeight; blockHeight++) {
|
||||||
|
Block btcdBlock = rpcService.requestBlock(blockHeight);
|
||||||
|
final BsqBlock bsqBlock = parseBlock(btcdBlock);
|
||||||
|
newBlockHandler.accept(bsqBlock);
|
||||||
|
}
|
||||||
|
} catch (BlockNotConnectingException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error(t.toString());
|
||||||
|
t.printStackTrace();
|
||||||
|
throw new BsqBlockchainException(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BsqBlock parseBlock(Block btcdBlock) throws BsqBlockchainException, BlockNotConnectingException {
|
||||||
|
long startTs = System.currentTimeMillis();
|
||||||
|
List<Tx> bsqTxsInBlock = findBsqTxsInBlock(btcdBlock);
|
||||||
|
final BsqBlock bsqBlock = new BsqBlock(btcdBlock.getHeight(),
|
||||||
|
btcdBlock.getHash(),
|
||||||
|
btcdBlock.getPreviousBlockHash(),
|
||||||
|
ImmutableList.copyOf(bsqTxsInBlock));
|
||||||
|
bsqBlockChain.addBlock(bsqBlock);
|
||||||
|
log.info("parseBlock took {} ms at blockHeight {}; bsqTxsInBlock.size={}",
|
||||||
|
System.currentTimeMillis() - startTs, bsqBlock.getHeight(), bsqTxsInBlock.size());
|
||||||
|
return bsqBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private List<Tx> findBsqTxsInBlock(Block btcdBlock) throws BsqBlockchainException {
|
||||||
|
int blockHeight = btcdBlock.getHeight();
|
||||||
|
log.debug("Parse block at height={} ", blockHeight);
|
||||||
|
|
||||||
|
// Check if the new block is the same chain we have built on.
|
||||||
|
List<Tx> txList = new ArrayList<>();
|
||||||
|
// We use a list as we want to maintain sorting of tx intra-block dependency
|
||||||
|
List<Tx> bsqTxsInBlock = new ArrayList<>();
|
||||||
|
// We add all transactions to the block
|
||||||
|
long startTs = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// We don't user foreach because scope for exception would not be in method body...
|
||||||
|
for (String txId : btcdBlock.getTx()) {
|
||||||
|
|
||||||
|
// TODO if we use requestFee move code to later point once we found our bsq txs, so we only request it for bsq txs
|
||||||
|
if (requestFee)
|
||||||
|
rpcService.requestFees(txId, blockHeight, feesByBlock);
|
||||||
|
|
||||||
|
final Tx tx = rpcService.requestTx(txId, blockHeight);
|
||||||
|
txList.add(tx);
|
||||||
|
checkForGenesisTx(blockHeight, bsqTxsInBlock, tx);
|
||||||
|
}
|
||||||
|
log.info("Requesting {} transactions took {} ms",
|
||||||
|
btcdBlock.getTx().size(), System.currentTimeMillis() - startTs);
|
||||||
|
// Worst case is that all txs in a block are depending on another, so only one get resolved at each iteration.
|
||||||
|
// Min tx size is 189 bytes (normally about 240 bytes), 1 MB can contain max. about 5300 txs (usually 2000).
|
||||||
|
// Realistically we don't expect more then a few recursive calls.
|
||||||
|
// There are some blocks with testing such dependency chains like block 130768 where at each iteration only
|
||||||
|
// one get resolved.
|
||||||
|
// Lately there is a patter with 24 iterations observed
|
||||||
|
recursiveFindBsqTxs(bsqTxsInBlock, txList, blockHeight, 0, 5300);
|
||||||
|
|
||||||
|
return bsqTxsInBlock;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
package io.bisq.core.dao.node.full.network;
|
||||||
|
|
||||||
|
import io.bisq.common.UserThread;
|
||||||
|
import io.bisq.common.app.Log;
|
||||||
|
import io.bisq.common.proto.network.NetworkEnvelope;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.node.messages.GetBsqBlocksRequest;
|
||||||
|
import io.bisq.core.dao.node.messages.NewBsqBlockBroadcastMessage;
|
||||||
|
import io.bisq.network.p2p.network.Connection;
|
||||||
|
import io.bisq.network.p2p.network.MessageListener;
|
||||||
|
import io.bisq.network.p2p.network.NetworkNode;
|
||||||
|
import io.bisq.network.p2p.peers.Broadcaster;
|
||||||
|
import io.bisq.network.p2p.peers.PeerManager;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for handling requests for BSQ blocks from lite nodes and for broadcasting new blocks to the P2P network.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class FullNodeNetworkManager implements MessageListener, PeerManager.Listener {
|
||||||
|
|
||||||
|
private static final long CLEANUP_TIMER = 120;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Class fields
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private final NetworkNode networkNode;
|
||||||
|
private final PeerManager peerManager;
|
||||||
|
private final Broadcaster broadcaster;
|
||||||
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
|
||||||
|
// Key is connection UID
|
||||||
|
private final Map<String, GetBsqBlocksRequestHandler> getBlocksRequestHandlers = new HashMap<>();
|
||||||
|
private boolean stopped;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FullNodeNetworkManager(NetworkNode networkNode,
|
||||||
|
PeerManager peerManager,
|
||||||
|
Broadcaster broadcaster,
|
||||||
|
BsqBlockChain bsqBlockChain) {
|
||||||
|
this.networkNode = networkNode;
|
||||||
|
this.peerManager = peerManager;
|
||||||
|
this.broadcaster = broadcaster;
|
||||||
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
|
// seedNodeAddresses can be empty (in case there is only 1 seed node, the seed node starting up has no other seed nodes)
|
||||||
|
|
||||||
|
networkNode.addMessageListener(this);
|
||||||
|
peerManager.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// API
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public void shutDown() {
|
||||||
|
Log.traceCall();
|
||||||
|
stopped = true;
|
||||||
|
networkNode.removeMessageListener(this);
|
||||||
|
peerManager.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publishNewBlock(BsqBlock bsqBlock) {
|
||||||
|
log.info("Publish new block at height={} and block hash={}", bsqBlock.getHeight(), bsqBlock.getHash());
|
||||||
|
final NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage = new NewBsqBlockBroadcastMessage(bsqBlock);
|
||||||
|
broadcaster.broadcast(newBsqBlockBroadcastMessage, networkNode.getNodeAddress(), null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PeerManager.Listener implementation
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllConnectionsLost() {
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewConnectionAfterAllConnectionsLost() {
|
||||||
|
stopped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAwakeFromStandby() {
|
||||||
|
stopped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// MessageListener implementation
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
||||||
|
if (networkEnvelop instanceof GetBsqBlocksRequest) {
|
||||||
|
// We received a GetBsqBlocksRequest from a liteNode
|
||||||
|
Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection);
|
||||||
|
if (!stopped) {
|
||||||
|
final String uid = connection.getUid();
|
||||||
|
if (!getBlocksRequestHandlers.containsKey(uid)) {
|
||||||
|
GetBsqBlocksRequestHandler requestHandler = new GetBsqBlocksRequestHandler(networkNode,
|
||||||
|
bsqBlockChain,
|
||||||
|
new GetBsqBlocksRequestHandler.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
getBlocksRequestHandlers.remove(uid);
|
||||||
|
log.trace("requestDataHandshake completed.\n\tConnection={}", connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFault(String errorMessage, @Nullable Connection connection) {
|
||||||
|
getBlocksRequestHandlers.remove(uid);
|
||||||
|
if (!stopped) {
|
||||||
|
log.trace("GetDataRequestHandler failed.\n\tConnection={}\n\t" +
|
||||||
|
"ErrorMessage={}", connection, errorMessage);
|
||||||
|
peerManager.handleConnectionFault(connection);
|
||||||
|
} else {
|
||||||
|
log.warn("We have stopped already. We ignore that getDataRequestHandler.handle.onFault call.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getBlocksRequestHandlers.put(uid, requestHandler);
|
||||||
|
requestHandler.onGetBsqBlocksRequest((GetBsqBlocksRequest) networkEnvelop, connection);
|
||||||
|
} else {
|
||||||
|
log.warn("We have already a GetDataRequestHandler for that connection started. " +
|
||||||
|
"We start a cleanup timer if the handler has not closed by itself in between 2 minutes.");
|
||||||
|
|
||||||
|
UserThread.runAfter(() -> {
|
||||||
|
if (getBlocksRequestHandlers.containsKey(uid)) {
|
||||||
|
GetBsqBlocksRequestHandler handler = getBlocksRequestHandlers.get(uid);
|
||||||
|
handler.stop();
|
||||||
|
getBlocksRequestHandlers.remove(uid);
|
||||||
|
}
|
||||||
|
}, CLEANUP_TIMER);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("We have stopped already. We ignore that onMessage call.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.bisq.core.dao.blockchain.p2p;
|
package io.bisq.core.dao.node.full.network;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
@ -6,10 +6,10 @@ import com.google.common.util.concurrent.SettableFuture;
|
||||||
import io.bisq.common.Timer;
|
import io.bisq.common.Timer;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
import io.bisq.common.app.Log;
|
import io.bisq.common.app.Log;
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksRequest;
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksResponse;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
|
||||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.node.messages.GetBsqBlocksRequest;
|
||||||
|
import io.bisq.core.dao.node.messages.GetBsqBlocksResponse;
|
||||||
import io.bisq.network.p2p.network.CloseConnectionReason;
|
import io.bisq.network.p2p.network.CloseConnectionReason;
|
||||||
import io.bisq.network.p2p.network.Connection;
|
import io.bisq.network.p2p.network.Connection;
|
||||||
import io.bisq.network.p2p.network.NetworkNode;
|
import io.bisq.network.p2p.network.NetworkNode;
|
||||||
|
@ -18,8 +18,12 @@ import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts a GetBsqBlocksRequest from a lite nodes and send back a corresponding GetBsqBlocksResponse.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GetBlocksRequestHandler {
|
class GetBsqBlocksRequestHandler {
|
||||||
private static final long TIMEOUT = 120;
|
private static final long TIMEOUT = 120;
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,14 +46,14 @@ public class GetBlocksRequestHandler {
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
private Timer timeoutTimer;
|
private Timer timeoutTimer;
|
||||||
private boolean stopped;
|
private boolean stopped;
|
||||||
private BsqBlockChain bsqBlockChain;
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public GetBlocksRequestHandler(NetworkNode networkNode, BsqBlockChain bsqBlockChain, Listener listener) {
|
public GetBsqBlocksRequestHandler(NetworkNode networkNode, BsqBlockChain bsqBlockChain, Listener listener) {
|
||||||
this.networkNode = networkNode;
|
this.networkNode = networkNode;
|
||||||
this.bsqBlockChain = bsqBlockChain;
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
@ -60,9 +64,9 @@ public class GetBlocksRequestHandler {
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void handle(GetBsqBlocksRequest getBsqBlocksRequest, final Connection connection) {
|
public void onGetBsqBlocksRequest(GetBsqBlocksRequest getBsqBlocksRequest, final Connection connection) {
|
||||||
Log.traceCall(getBsqBlocksRequest + "\n\tconnection=" + connection);
|
Log.traceCall(getBsqBlocksRequest + "\n\tconnection=" + connection);
|
||||||
List<BsqBlock> bsqBlocks = bsqBlockChain.getResettedBlocksFrom(getBsqBlocksRequest.getFromBlockHeight());
|
List<BsqBlock> bsqBlocks = bsqBlockChain.getResetBlocksFrom(getBsqBlocksRequest.getFromBlockHeight());
|
||||||
final GetBsqBlocksResponse bsqBlocksResponse = new GetBsqBlocksResponse(bsqBlocks, getBsqBlocksRequest.getNonce());
|
final GetBsqBlocksResponse bsqBlocksResponse = new GetBsqBlocksResponse(bsqBlocks, getBsqBlocksRequest.getNonce());
|
||||||
log.debug("bsqBlocksResponse " + bsqBlocksResponse.getRequestNonce());
|
log.debug("bsqBlocksResponse " + bsqBlocksResponse.getRequestNonce());
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.full.rpc;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
@ -36,7 +36,6 @@ import io.bisq.core.dao.blockchain.exceptions.BsqBlockchainException;
|
||||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxInput;
|
import io.bisq.core.dao.blockchain.vo.TxInput;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||||
import io.bisq.core.dao.blockchain.vo.TxVo;
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
|
@ -53,8 +52,11 @@ import java.util.Properties;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
// Blocking access to Bitcoin Core via RPC requests
|
/**
|
||||||
// See the rpc.md file in the doc directory for more info about the setup.
|
* Request blockchain data vai via RPC from Bitcoin Core.
|
||||||
|
* Running in custom threads.
|
||||||
|
* See the rpc.md file in the doc directory for more info about the setup.
|
||||||
|
*/
|
||||||
public class RpcService {
|
public class RpcService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(RpcService.class);
|
private static final Logger log = LoggerFactory.getLogger(RpcService.class);
|
||||||
|
|
||||||
|
@ -86,7 +88,7 @@ public class RpcService {
|
||||||
this.dumpBlockchainData = dumpBlockchainData;
|
this.dumpBlockchainData = dumpBlockchainData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() throws BsqBlockchainException {
|
public void setup() throws BsqBlockchainException {
|
||||||
try {
|
try {
|
||||||
long startTs = System.currentTimeMillis();
|
long startTs = System.currentTimeMillis();
|
||||||
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
|
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
|
||||||
|
@ -120,7 +122,7 @@ public class RpcService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerBlockHandler(Consumer<Block> blockHandler) {
|
public void registerBlockHandler(Consumer<Block> blockHandler) {
|
||||||
daemon.addBlockListener(new BlockListener() {
|
daemon.addBlockListener(new BlockListener() {
|
||||||
@Override
|
@Override
|
||||||
public void blockDetected(Block block) {
|
public void blockDetected(Block block) {
|
||||||
|
@ -138,16 +140,16 @@ public class RpcService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int requestChainHeadHeight() throws BitcoindException, CommunicationException {
|
public int requestChainHeadHeight() throws BitcoindException, CommunicationException {
|
||||||
return client.getBlockCount();
|
return client.getBlockCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
Block requestBlock(int blockHeight) throws BitcoindException, CommunicationException {
|
public Block requestBlock(int blockHeight) throws BitcoindException, CommunicationException {
|
||||||
final String blockHash = client.getBlockHash(blockHeight);
|
final String blockHash = client.getBlockHash(blockHeight);
|
||||||
return client.getBlock(blockHash);
|
return client.getBlock(blockHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
void requestFees(String txId, int blockHeight, Map<Integer, Long> feesByBlock) throws BsqBlockchainException {
|
public void requestFees(String txId, int blockHeight, Map<Integer, Long> feesByBlock) throws BsqBlockchainException {
|
||||||
try {
|
try {
|
||||||
Transaction transaction = requestTx(txId);
|
Transaction transaction = requestTx(txId);
|
||||||
final BigDecimal fee = transaction.getFee();
|
final BigDecimal fee = transaction.getFee();
|
||||||
|
@ -159,7 +161,7 @@ public class RpcService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tx requestTx(String txId, int blockHeight) throws BsqBlockchainException {
|
public Tx requestTx(String txId, int blockHeight) throws BsqBlockchainException {
|
||||||
try {
|
try {
|
||||||
RawTransaction rawTransaction = requestRawTransaction(txId);
|
RawTransaction rawTransaction = requestRawTransaction(txId);
|
||||||
// rawTransaction.getTime() is in seconds but we keep it in ms internally
|
// rawTransaction.getTime() is in seconds but we keep it in ms internally
|
||||||
|
@ -192,7 +194,7 @@ public class RpcService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We dont support raw MS which are the only case where scriptPubKey.getAddresses()>1
|
// We don't support raw MS which are the only case where scriptPubKey.getAddresses()>1
|
||||||
String address = scriptPubKey.getAddresses() != null &&
|
String address = scriptPubKey.getAddresses() != null &&
|
||||||
scriptPubKey.getAddresses().size() == 1 ? scriptPubKey.getAddresses().get(0) : null;
|
scriptPubKey.getAddresses().size() == 1 ? scriptPubKey.getAddresses().get(0) : null;
|
||||||
final PubKeyScript pubKeyScript = dumpBlockchainData ? new PubKeyScript(scriptPubKey) : null;
|
final PubKeyScript pubKeyScript = dumpBlockchainData ? new PubKeyScript(scriptPubKey) : null;
|
||||||
|
@ -207,11 +209,10 @@ public class RpcService {
|
||||||
)
|
)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
final TxVo txVo = new TxVo(txId,
|
return new Tx(txId,
|
||||||
blockHeight,
|
blockHeight,
|
||||||
rawTransaction.getBlockHash(),
|
rawTransaction.getBlockHash(),
|
||||||
time);
|
time,
|
||||||
return new Tx(txVo,
|
|
||||||
ImmutableList.copyOf(txInputs),
|
ImmutableList.copyOf(txInputs),
|
||||||
ImmutableList.copyOf(txOutputs));
|
ImmutableList.copyOf(txOutputs));
|
||||||
} catch (BitcoindException | CommunicationException e) {
|
} catch (BitcoindException | CommunicationException e) {
|
||||||
|
@ -220,11 +221,11 @@ public class RpcService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RawTransaction requestRawTransaction(String txId) throws BitcoindException, CommunicationException {
|
private RawTransaction requestRawTransaction(String txId) throws BitcoindException, CommunicationException {
|
||||||
return (RawTransaction) client.getRawTransaction(txId, 1);
|
return (RawTransaction) client.getRawTransaction(txId, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction requestTx(String txId) throws BitcoindException, CommunicationException {
|
private Transaction requestTx(String txId) throws BitcoindException, CommunicationException {
|
||||||
return client.getTransaction(txId);
|
return client.getTransaction(txId);
|
||||||
}
|
}
|
||||||
}
|
}
|
170
core/src/main/java/io/bisq/core/dao/node/lite/LiteNode.java
Normal file
170
core/src/main/java/io/bisq/core/dao/node/lite/LiteNode.java
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.lite;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import io.bisq.common.UserThread;
|
||||||
|
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
||||||
|
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.node.BsqNode;
|
||||||
|
import io.bisq.core.dao.node.lite.network.LiteNodeNetworkManager;
|
||||||
|
import io.bisq.core.dao.node.messages.GetBsqBlocksResponse;
|
||||||
|
import io.bisq.core.dao.node.messages.NewBsqBlockBroadcastMessage;
|
||||||
|
import io.bisq.core.provider.fee.FeeService;
|
||||||
|
import io.bisq.network.p2p.P2PService;
|
||||||
|
import io.bisq.network.p2p.network.Connection;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class for lite nodes which receive the BSQ transactions from a full node (e.g. seed nodes).
|
||||||
|
*
|
||||||
|
* Verification of BSQ transactions is done also by the lite node.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class LiteNode extends BsqNode {
|
||||||
|
private final LiteNodeExecutor bsqLiteNodeExecutor;
|
||||||
|
private final LiteNodeNetworkManager liteNodeNetworkManager;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
@Inject
|
||||||
|
public LiteNode(P2PService p2PService,
|
||||||
|
LiteNodeExecutor bsqLiteNodeExecutor,
|
||||||
|
BsqBlockChain bsqBlockChain,
|
||||||
|
FeeService feeService,
|
||||||
|
LiteNodeNetworkManager liteNodeNetworkManager) {
|
||||||
|
super(p2PService,
|
||||||
|
bsqBlockChain,
|
||||||
|
feeService);
|
||||||
|
this.bsqLiteNodeExecutor = bsqLiteNodeExecutor;
|
||||||
|
this.liteNodeNetworkManager = liteNodeNetworkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Public methods
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
|
||||||
|
super.onInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
liteNodeNetworkManager.shutDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Protected
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onP2PNetworkReady() {
|
||||||
|
super.onP2PNetworkReady();
|
||||||
|
|
||||||
|
liteNodeNetworkManager.addListener(new LiteNodeNetworkManager.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onRequestedBlocksReceived(GetBsqBlocksResponse getBsqBlocksResponse) {
|
||||||
|
LiteNode.this.onRequestedBlocksReceived(new ArrayList<>(getBsqBlocksResponse.getBsqBlocks()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewBlockReceived(NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage) {
|
||||||
|
LiteNode.this.onNewBlockReceived(newBsqBlockBroadcastMessage.getBsqBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNoSeedNodeAvailable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFault(String errorMessage, @Nullable Connection connection) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// delay a bit to not stress too much at startup
|
||||||
|
UserThread.runAfter(this::startParseBlocks, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First we request the blocks from a full node
|
||||||
|
@Override
|
||||||
|
protected void startParseBlocks() {
|
||||||
|
liteNodeNetworkManager.requestBlocks(getStartBlockHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// We received the missing blocks
|
||||||
|
private void onRequestedBlocksReceived(List<BsqBlock> bsqBlockList) {
|
||||||
|
log.info("onRequestedBlocksReceived: blocks with {} items", bsqBlockList.size());
|
||||||
|
if (bsqBlockList.size() > 0)
|
||||||
|
log.info("block height of last item: {}", bsqBlockList.get(bsqBlockList.size() - 1).getHeight());
|
||||||
|
// We reset all mutable data in case the provider would not have done it.
|
||||||
|
bsqBlockList.forEach(BsqBlock::reset);
|
||||||
|
bsqLiteNodeExecutor.parseBlocks(bsqBlockList,
|
||||||
|
block -> notifyListenersOnNewBlock(),
|
||||||
|
this::onParseBlockchainComplete,
|
||||||
|
getErrorHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We received a new block
|
||||||
|
private void onNewBlockReceived(BsqBlock bsqBlock) {
|
||||||
|
// We reset all mutable data in case the provider would not have done it.
|
||||||
|
bsqBlock.reset();
|
||||||
|
log.info("onNewBlockReceived: bsqBlock={}", bsqBlock.getHeight());
|
||||||
|
if (!bsqBlockChain.containsBlock(bsqBlock)) {
|
||||||
|
bsqLiteNodeExecutor.parseBlock(bsqBlock,
|
||||||
|
this::notifyListenersOnNewBlock,
|
||||||
|
getErrorHandler());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onParseBlockchainComplete() {
|
||||||
|
parseBlockchainComplete = true;
|
||||||
|
bsqBlockChainListeners.forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Consumer<Throwable> getErrorHandler() {
|
||||||
|
return throwable -> {
|
||||||
|
if (throwable instanceof BlockNotConnectingException) {
|
||||||
|
startReOrgFromLastSnapshot();
|
||||||
|
} else {
|
||||||
|
log.error(throwable.toString());
|
||||||
|
throwable.printStackTrace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.blockchain.parse;
|
package io.bisq.core.dao.node.lite;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
import io.bisq.common.handlers.ResultHandler;
|
import io.bisq.common.handlers.ResultHandler;
|
||||||
import io.bisq.common.util.Utilities;
|
import io.bisq.common.util.Utilities;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
@ -32,14 +33,17 @@ import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
// Used for non blocking parsing. Encapsulate thread context, so caller gets always called on UserThread
|
/**
|
||||||
|
* Processes tasks in custom threads. Results are mapped back to user thread so client don't need to deal with threading.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BsqLiteNodeExecutor {
|
public class LiteNodeExecutor {
|
||||||
|
|
||||||
private final BsqParser bsqParser;
|
private final LiteNodeParser liteNodeParser;
|
||||||
private final BsqBlockChain bsqBlockChain;
|
private final BsqBlockChain bsqBlockChain;
|
||||||
|
|
||||||
private final ListeningExecutorService parseBlocksExecutor = Utilities.getListeningExecutorService("ParseBlocks", 1, 1, 60);
|
private final ListeningExecutorService parseBlocksExecutor = Utilities.getListeningExecutorService("ParseBlocks", 1, 1, 60);
|
||||||
|
private final ListeningExecutorService parseBlockExecutor = Utilities.getListeningExecutorService("ParseBlock", 1, 1, 60);
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -48,22 +52,23 @@ public class BsqLiteNodeExecutor {
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@Inject
|
@Inject
|
||||||
public BsqLiteNodeExecutor(BsqParser bsqParser, BsqBlockChain bsqBlockChain) {
|
public LiteNodeExecutor(LiteNodeParser liteNodeParser, BsqBlockChain bsqBlockChain) {
|
||||||
this.bsqParser = bsqParser;
|
this.liteNodeParser = liteNodeParser;
|
||||||
this.bsqBlockChain = bsqBlockChain;
|
this.bsqBlockChain = bsqBlockChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseBsqBlocksForLiteNode(List<BsqBlock> bsqBlockList,
|
|
||||||
int genesisBlockHeight,
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
String genesisTxId,
|
// Package private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void parseBlocks(List<BsqBlock> bsqBlockList,
|
||||||
Consumer<BsqBlock> newBlockHandler,
|
Consumer<BsqBlock> newBlockHandler,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
Consumer<Throwable> errorHandler) {
|
Consumer<Throwable> errorHandler) {
|
||||||
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
||||||
long startTs = System.currentTimeMillis();
|
long startTs = System.currentTimeMillis();
|
||||||
bsqParser.parseBsqBlocks(bsqBlockList,
|
liteNodeParser.parseBsqBlocks(bsqBlockList,
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId,
|
|
||||||
newBsqBlock -> UserThread.execute(() -> newBlockHandler.accept(newBsqBlock)));
|
newBsqBlock -> UserThread.execute(() -> newBlockHandler.accept(newBsqBlock)));
|
||||||
log.info("parseBlocks took {} ms for {} blocks", System.currentTimeMillis() - startTs, bsqBlockList.size());
|
log.info("parseBlocks took {} ms for {} blocks", System.currentTimeMillis() - startTs, bsqBlockList.size());
|
||||||
return null;
|
return null;
|
||||||
|
@ -82,17 +87,12 @@ public class BsqLiteNodeExecutor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check why it's not handled in the parser
|
void parseBlock(BsqBlock bsqBlock,
|
||||||
public void parseBsqBlockForLiteNode(BsqBlock bsqBlock,
|
|
||||||
int genesisBlockHeight,
|
|
||||||
String genesisTxId,
|
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
Consumer<Throwable> errorHandler) {
|
Consumer<Throwable> errorHandler) {
|
||||||
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
ListenableFuture<Void> future = parseBlockExecutor.submit(() -> {
|
||||||
long startTs = System.currentTimeMillis();
|
long startTs = System.currentTimeMillis();
|
||||||
bsqParser.parseBsqBlock(bsqBlock,
|
liteNodeParser.parseBsqBlock(bsqBlock);
|
||||||
genesisBlockHeight,
|
|
||||||
genesisTxId);
|
|
||||||
log.info("parseBlocks took {} ms", System.currentTimeMillis() - startTs);
|
log.info("parseBlocks took {} ms", System.currentTimeMillis() - startTs);
|
||||||
bsqBlockChain.addBlock(bsqBlock);
|
bsqBlockChain.addBlock(bsqBlock);
|
||||||
return null;
|
return null;
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.node.lite;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
|
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||||
|
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||||
|
import io.bisq.core.dao.node.BsqParser;
|
||||||
|
import io.bisq.core.dao.node.consensus.BsqTxVerification;
|
||||||
|
import io.bisq.core.dao.node.consensus.GenesisTxVerification;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for lite nodes. Iterates blocks to find BSQ relevant transactions.
|
||||||
|
* <p>
|
||||||
|
* We are in threaded context. Don't mix up with UserThread.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class LiteNodeParser extends BsqParser {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LiteNodeParser(BsqBlockChain bsqBlockChain,
|
||||||
|
GenesisTxVerification genesisTxVerification,
|
||||||
|
BsqTxVerification bsqTxVerification) {
|
||||||
|
super(bsqBlockChain, genesisTxVerification, bsqTxVerification);
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseBsqBlocks(List<BsqBlock> bsqBlocks,
|
||||||
|
Consumer<BsqBlock> newBlockHandler)
|
||||||
|
throws BlockNotConnectingException {
|
||||||
|
for (BsqBlock bsqBlock : bsqBlocks) {
|
||||||
|
parseBsqBlock(bsqBlock);
|
||||||
|
bsqBlockChain.addBlock(bsqBlock);
|
||||||
|
newBlockHandler.accept(bsqBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseBsqBlock(BsqBlock bsqBlock) {
|
||||||
|
int blockHeight = bsqBlock.getHeight();
|
||||||
|
log.info("Parse block at height={} ", blockHeight);
|
||||||
|
List<Tx> txList = new ArrayList<>(bsqBlock.getTxs());
|
||||||
|
List<Tx> bsqTxsInBlock = new ArrayList<>();
|
||||||
|
bsqBlock.getTxs().forEach(tx -> checkForGenesisTx(blockHeight, bsqTxsInBlock, tx));
|
||||||
|
recursiveFindBsqTxs(bsqTxsInBlock, txList, blockHeight, 0, 5300);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.bisq.core.dao.blockchain.p2p;
|
package io.bisq.core.dao.node.lite.network;
|
||||||
|
|
||||||
import io.bisq.common.Timer;
|
import io.bisq.common.Timer;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
|
@ -6,23 +6,24 @@ import io.bisq.common.app.DevEnv;
|
||||||
import io.bisq.common.app.Log;
|
import io.bisq.common.app.Log;
|
||||||
import io.bisq.common.proto.network.NetworkEnvelope;
|
import io.bisq.common.proto.network.NetworkEnvelope;
|
||||||
import io.bisq.common.util.Tuple2;
|
import io.bisq.common.util.Tuple2;
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksRequest;
|
import io.bisq.core.dao.node.messages.GetBsqBlocksResponse;
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksResponse;
|
import io.bisq.core.dao.node.messages.NewBsqBlockBroadcastMessage;
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.NewBsqBlockBroadcastMessage;
|
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
|
||||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
|
||||||
import io.bisq.network.p2p.NodeAddress;
|
import io.bisq.network.p2p.NodeAddress;
|
||||||
import io.bisq.network.p2p.network.*;
|
import io.bisq.network.p2p.network.*;
|
||||||
import io.bisq.network.p2p.peers.Broadcaster;
|
|
||||||
import io.bisq.network.p2p.peers.PeerManager;
|
import io.bisq.network.p2p.peers.PeerManager;
|
||||||
|
import io.bisq.network.p2p.seed.SeedNodeRepository;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for requesting BSQ blocks from a full node and for listening to new blocks broadcasted by full nodes.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RequestManager implements MessageListener, ConnectionListener, PeerManager.Listener {
|
public class LiteNodeNetworkManager implements MessageListener, ConnectionListener, PeerManager.Listener {
|
||||||
|
|
||||||
private static final long RETRY_DELAY_SEC = 10;
|
private static final long RETRY_DELAY_SEC = 10;
|
||||||
private static final long CLEANUP_TIMER = 120;
|
private static final long CLEANUP_TIMER = 120;
|
||||||
|
@ -40,9 +41,9 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
void onNoSeedNodeAvailable();
|
void onNoSeedNodeAvailable();
|
||||||
|
|
||||||
void onBlockReceived(GetBsqBlocksResponse getBsqBlocksResponse);
|
void onRequestedBlocksReceived(GetBsqBlocksResponse getBsqBlocksResponse);
|
||||||
|
|
||||||
void onNewBsqBlockBroadcastMessage(NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage);
|
void onNewBlockReceived(NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage);
|
||||||
|
|
||||||
void onFault(String errorMessage, @Nullable Connection connection);
|
void onFault(String errorMessage, @Nullable Connection connection);
|
||||||
}
|
}
|
||||||
|
@ -54,13 +55,12 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
|
|
||||||
private final NetworkNode networkNode;
|
private final NetworkNode networkNode;
|
||||||
private final PeerManager peerManager;
|
private final PeerManager peerManager;
|
||||||
private final Broadcaster broadcaster;
|
|
||||||
private final BsqBlockChain bsqBlockChain;
|
|
||||||
private final Collection<NodeAddress> seedNodeAddresses;
|
private final Collection<NodeAddress> seedNodeAddresses;
|
||||||
private final Listener listener;
|
|
||||||
|
|
||||||
|
private final List<Listener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
// Key is tuple of seedNode address and requested blockHeight
|
||||||
private final Map<Tuple2<NodeAddress, Integer>, RequestBlocksHandler> requestBlocksHandlerMap = new HashMap<>();
|
private final Map<Tuple2<NodeAddress, Integer>, RequestBlocksHandler> requestBlocksHandlerMap = new HashMap<>();
|
||||||
private final Map<String, GetBlocksRequestHandler> getBlocksRequestHandlers = new HashMap<>();
|
|
||||||
private Timer retryTimer;
|
private Timer retryTimer;
|
||||||
private boolean stopped;
|
private boolean stopped;
|
||||||
|
|
||||||
|
@ -69,25 +69,25 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public RequestManager(NetworkNode networkNode,
|
@Inject
|
||||||
|
public LiteNodeNetworkManager(NetworkNode networkNode,
|
||||||
PeerManager peerManager,
|
PeerManager peerManager,
|
||||||
Broadcaster broadcaster,
|
SeedNodeRepository seedNodesRepository) {
|
||||||
Set<NodeAddress> seedNodeAddresses,
|
|
||||||
BsqBlockChain bsqBlockChain,
|
|
||||||
Listener listener) {
|
|
||||||
this.networkNode = networkNode;
|
this.networkNode = networkNode;
|
||||||
this.peerManager = peerManager;
|
this.peerManager = peerManager;
|
||||||
this.broadcaster = broadcaster;
|
|
||||||
this.bsqBlockChain = bsqBlockChain;
|
|
||||||
// seedNodeAddresses can be empty (in case there is only 1 seed node, the seed node starting up has no other seed nodes)
|
// seedNodeAddresses can be empty (in case there is only 1 seed node, the seed node starting up has no other seed nodes)
|
||||||
this.seedNodeAddresses = new HashSet<>(seedNodeAddresses);
|
this.seedNodeAddresses = new HashSet<>(seedNodesRepository.getSeedNodeAddresses());
|
||||||
this.listener = listener;
|
|
||||||
|
|
||||||
networkNode.addMessageListener(this);
|
networkNode.addMessageListener(this);
|
||||||
networkNode.addConnectionListener(this);
|
networkNode.addConnectionListener(this);
|
||||||
peerManager.addListener(this);
|
peerManager.addListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// API
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
public void shutDown() {
|
public void shutDown() {
|
||||||
Log.traceCall();
|
Log.traceCall();
|
||||||
stopped = true;
|
stopped = true;
|
||||||
|
@ -98,31 +98,24 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
closeAllHandlers();
|
closeAllHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addListener(Listener listener) {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
listeners.add(listener);
|
||||||
// API
|
}
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public void requestBlocks(int startBlockHeight) {
|
public void requestBlocks(int startBlockHeight) {
|
||||||
Log.traceCall();
|
Log.traceCall();
|
||||||
lastRequestedBlockHeight = startBlockHeight;
|
lastRequestedBlockHeight = startBlockHeight;
|
||||||
Optional<Connection> seedNodeAddressOptional = networkNode.getConfirmedConnections().stream()
|
Optional<Connection> connectionToSeedNodeOptional = networkNode.getConfirmedConnections().stream()
|
||||||
.filter(peerManager::isSeedNode)
|
.filter(peerManager::isSeedNode)
|
||||||
.findAny();
|
.findAny();
|
||||||
if (seedNodeAddressOptional.isPresent() &&
|
if (connectionToSeedNodeOptional.isPresent() &&
|
||||||
seedNodeAddressOptional.get().getPeersNodeAddressOptional().isPresent()) {
|
connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().isPresent()) {
|
||||||
requestBlocks(seedNodeAddressOptional.get().getPeersNodeAddressOptional().get(), startBlockHeight);
|
requestBlocks(connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().get(), startBlockHeight);
|
||||||
} else {
|
} else {
|
||||||
tryWithNewSeedNode(startBlockHeight);
|
tryWithNewSeedNode(startBlockHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publishNewBlock(BsqBlock bsqBlock) {
|
|
||||||
log.info("Publish new block at height={} and block hash={}", bsqBlock.getHeight(), bsqBlock.getHash());
|
|
||||||
final NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage = new NewBsqBlockBroadcastMessage(bsqBlock);
|
|
||||||
broadcaster.broadcast(newBsqBlockBroadcastMessage, networkNode.getNodeAddress(), null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// ConnectionListener implementation
|
// ConnectionListener implementation
|
||||||
|
@ -139,13 +132,10 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
closeHandler(connection);
|
closeHandler(connection);
|
||||||
|
|
||||||
if (peerManager.isNodeBanned(closeConnectionReason, connection)) {
|
if (peerManager.isNodeBanned(closeConnectionReason, connection)) {
|
||||||
final NodeAddress nodeAddress = connection.getPeersNodeAddressOptional().get();
|
connection.getPeersNodeAddressOptional().ifPresent(nodeAddress -> {
|
||||||
seedNodeAddresses.remove(nodeAddress);
|
seedNodeAddresses.remove(nodeAddress);
|
||||||
requestBlocksHandlerMap.entrySet().stream()
|
removeFromRequestBlocksHandlerMap(nodeAddress);
|
||||||
.filter(e -> e.getKey().first.equals(nodeAddress))
|
});
|
||||||
.findAny()
|
|
||||||
.map(Map.Entry::getValue)
|
|
||||||
.ifPresent(requestBlocksHandlerMap::remove);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,54 +182,8 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
||||||
if (networkEnvelop instanceof GetBsqBlocksRequest) {
|
if (networkEnvelop instanceof NewBsqBlockBroadcastMessage) {
|
||||||
Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection);
|
listeners.forEach(listener -> listener.onNewBlockReceived((NewBsqBlockBroadcastMessage) networkEnvelop));
|
||||||
if (!stopped) {
|
|
||||||
if (peerManager.isSeedNode(connection))
|
|
||||||
connection.setPeerType(Connection.PeerType.SEED_NODE);
|
|
||||||
|
|
||||||
final String uid = connection.getUid();
|
|
||||||
if (!getBlocksRequestHandlers.containsKey(uid)) {
|
|
||||||
GetBlocksRequestHandler getDataRequestHandler = new GetBlocksRequestHandler(networkNode,
|
|
||||||
bsqBlockChain,
|
|
||||||
new GetBlocksRequestHandler.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onComplete() {
|
|
||||||
getBlocksRequestHandlers.remove(uid);
|
|
||||||
log.trace("requestDataHandshake completed.\n\tConnection={}", connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage, @Nullable Connection connection) {
|
|
||||||
getBlocksRequestHandlers.remove(uid);
|
|
||||||
if (!stopped) {
|
|
||||||
log.trace("GetDataRequestHandler failed.\n\tConnection={}\n\t" +
|
|
||||||
"ErrorMessage={}", connection, errorMessage);
|
|
||||||
peerManager.handleConnectionFault(connection);
|
|
||||||
} else {
|
|
||||||
log.warn("We have stopped already. We ignore that getDataRequestHandler.handle.onFault call.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
getBlocksRequestHandlers.put(uid, getDataRequestHandler);
|
|
||||||
getDataRequestHandler.handle((GetBsqBlocksRequest) networkEnvelop, connection);
|
|
||||||
} else {
|
|
||||||
log.warn("We have already a GetDataRequestHandler for that connection started. " +
|
|
||||||
"We start a cleanup timer if the handler has not closed by itself in between 2 minutes.");
|
|
||||||
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
if (getBlocksRequestHandlers.containsKey(uid)) {
|
|
||||||
GetBlocksRequestHandler handler = getBlocksRequestHandlers.get(uid);
|
|
||||||
handler.stop();
|
|
||||||
getBlocksRequestHandlers.remove(uid);
|
|
||||||
}
|
|
||||||
}, CLEANUP_TIMER);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn("We have stopped already. We ignore that onMessage call.");
|
|
||||||
}
|
|
||||||
} else if (networkEnvelop instanceof NewBsqBlockBroadcastMessage) {
|
|
||||||
listener.onNewBsqBlockBroadcastMessage((NewBsqBlockBroadcastMessage) networkEnvelop);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +196,10 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
final Tuple2<NodeAddress, Integer> key = new Tuple2<>(peersNodeAddress, startBlockHeight);
|
final Tuple2<NodeAddress, Integer> key = new Tuple2<>(peersNodeAddress, startBlockHeight);
|
||||||
if (!requestBlocksHandlerMap.containsKey(key)) {
|
if (!requestBlocksHandlerMap.containsKey(key)) {
|
||||||
if (startBlockHeight >= lastReceivedBlockHeight) {
|
if (startBlockHeight >= lastReceivedBlockHeight) {
|
||||||
RequestBlocksHandler requestBlocksHandler = new RequestBlocksHandler(networkNode, peerManager,
|
RequestBlocksHandler requestBlocksHandler = new RequestBlocksHandler(networkNode,
|
||||||
|
peerManager,
|
||||||
|
peersNodeAddress,
|
||||||
|
startBlockHeight,
|
||||||
new RequestBlocksHandler.Listener() {
|
new RequestBlocksHandler.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(GetBsqBlocksResponse getBsqBlocksResponse) {
|
public void onComplete(GetBsqBlocksResponse getBsqBlocksResponse) {
|
||||||
|
@ -265,7 +212,7 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
// we only notify if our request was latest
|
// we only notify if our request was latest
|
||||||
if (startBlockHeight >= lastReceivedBlockHeight) {
|
if (startBlockHeight >= lastReceivedBlockHeight) {
|
||||||
lastReceivedBlockHeight = startBlockHeight;
|
lastReceivedBlockHeight = startBlockHeight;
|
||||||
listener.onBlockReceived(getBsqBlocksResponse);
|
listeners.forEach(listener -> listener.onRequestedBlocksReceived(getBsqBlocksResponse));
|
||||||
} else {
|
} else {
|
||||||
log.warn("We got a response which is already obsolete because we receive a " +
|
log.warn("We got a response which is already obsolete because we receive a " +
|
||||||
"response from a request with a higher block height. " +
|
"response from a request with a higher block height. " +
|
||||||
|
@ -281,19 +228,19 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
peerManager.handleConnectionFault(peersNodeAddress);
|
peerManager.handleConnectionFault(peersNodeAddress);
|
||||||
requestBlocksHandlerMap.remove(key);
|
requestBlocksHandlerMap.remove(key);
|
||||||
|
|
||||||
listener.onFault(errorMessage, connection);
|
listeners.forEach(listener -> listener.onFault(errorMessage, connection));
|
||||||
|
|
||||||
tryWithNewSeedNode(startBlockHeight);
|
tryWithNewSeedNode(startBlockHeight);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
requestBlocksHandlerMap.put(key, requestBlocksHandler);
|
requestBlocksHandlerMap.put(key, requestBlocksHandler);
|
||||||
requestBlocksHandler.requestBlocks(peersNodeAddress, startBlockHeight);
|
requestBlocksHandler.requestBlocks();
|
||||||
} else {
|
} else {
|
||||||
//TODO check with re-orgs
|
//TODO check with re-orgs
|
||||||
// FIXME when a lot of blocks are created we get caught here. Seems to be a threading issue...
|
// FIXME when a lot of blocks are created we get caught here. Seems to be a threading issue...
|
||||||
log.warn("startBlockHeight must not be smaller than lastReceivedBlockHeight. That should never happen." +
|
log.warn("startBlockHeight must not be smaller than lastReceivedBlockHeight. That should never happen." +
|
||||||
"startBlockHeight={},lastReceivedBlockHeight={}", startBlockHeight, lastReceivedBlockHeight);
|
"startBlockHeight={},lastReceivedBlockHeight={}", startBlockHeight, lastReceivedBlockHeight);
|
||||||
if (DevEnv.DEV_MODE)
|
if (DevEnv.isDevMode())
|
||||||
throw new RuntimeException("startBlockHeight must be larger than lastReceivedBlockHeight. startBlockHeight=" +
|
throw new RuntimeException("startBlockHeight must be larger than lastReceivedBlockHeight. startBlockHeight=" +
|
||||||
startBlockHeight + " / lastReceivedBlockHeight=" + lastReceivedBlockHeight);
|
startBlockHeight + " / lastReceivedBlockHeight=" + lastReceivedBlockHeight);
|
||||||
}
|
}
|
||||||
|
@ -302,10 +249,10 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
"We start a cleanup timer if the handler has not closed by itself in between 2 minutes.");
|
"We start a cleanup timer if the handler has not closed by itself in between 2 minutes.");
|
||||||
|
|
||||||
UserThread.runAfter(() -> {
|
UserThread.runAfter(() -> {
|
||||||
if (requestBlocksHandlerMap.containsKey(peersNodeAddress)) {
|
if (requestBlocksHandlerMap.containsKey(key)) {
|
||||||
RequestBlocksHandler handler = requestBlocksHandlerMap.get(peersNodeAddress);
|
RequestBlocksHandler handler = requestBlocksHandlerMap.get(key);
|
||||||
handler.stop();
|
handler.stop();
|
||||||
requestBlocksHandlerMap.remove(peersNodeAddress);
|
requestBlocksHandlerMap.remove(key);
|
||||||
}
|
}
|
||||||
}, CLEANUP_TIMER);
|
}, CLEANUP_TIMER);
|
||||||
}
|
}
|
||||||
|
@ -342,13 +289,13 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
requestBlocks(nextCandidate, startBlockHeight);
|
requestBlocks(nextCandidate, startBlockHeight);
|
||||||
} else {
|
} else {
|
||||||
log.warn("No more seed nodes available we could try.");
|
log.warn("No more seed nodes available we could try.");
|
||||||
listener.onNoSeedNodeAvailable();
|
listeners.forEach(Listener::onNoSeedNodeAvailable);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RETRY_DELAY_SEC);
|
RETRY_DELAY_SEC);
|
||||||
} else {
|
} else {
|
||||||
log.warn("We tried {} times but could not connect to a seed node.", retryCounter);
|
log.warn("We tried {} times but could not connect to a seed node.", retryCounter);
|
||||||
listener.onNoSeedNodeAvailable();
|
listeners.forEach(Listener::onNoSeedNodeAvailable);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("We have a retry timer already running.");
|
log.warn("We have a retry timer already running.");
|
||||||
|
@ -366,17 +313,27 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
||||||
Optional<NodeAddress> peersNodeAddressOptional = connection.getPeersNodeAddressOptional();
|
Optional<NodeAddress> peersNodeAddressOptional = connection.getPeersNodeAddressOptional();
|
||||||
if (peersNodeAddressOptional.isPresent()) {
|
if (peersNodeAddressOptional.isPresent()) {
|
||||||
NodeAddress nodeAddress = peersNodeAddressOptional.get();
|
NodeAddress nodeAddress = peersNodeAddressOptional.get();
|
||||||
if (requestBlocksHandlerMap.containsKey(nodeAddress)) {
|
removeFromRequestBlocksHandlerMap(nodeAddress);
|
||||||
requestBlocksHandlerMap.get(nodeAddress).cancel();
|
|
||||||
requestBlocksHandlerMap.remove(nodeAddress);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.trace("closeHandler: nodeAddress not set in connection " + connection);
|
log.trace("closeHandler: nodeAddress not set in connection " + connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFromRequestBlocksHandlerMap(NodeAddress nodeAddress) {
|
||||||
|
requestBlocksHandlerMap.entrySet().stream()
|
||||||
|
.filter(e -> e.getKey().first.equals(nodeAddress))
|
||||||
|
.findAny()
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.ifPresent(handler -> {
|
||||||
|
final Tuple2<NodeAddress, Integer> key = new Tuple2<>(handler.getNodeAddress(), handler.getStartBlockHeight());
|
||||||
|
requestBlocksHandlerMap.get(key).cancel();
|
||||||
|
requestBlocksHandlerMap.remove(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void closeAllHandlers() {
|
private void closeAllHandlers() {
|
||||||
requestBlocksHandlerMap.values().stream().forEach(RequestBlocksHandler::cancel);
|
requestBlocksHandlerMap.values().forEach(RequestBlocksHandler::cancel);
|
||||||
requestBlocksHandlerMap.clear();
|
requestBlocksHandlerMap.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.bisq.core.dao.blockchain.p2p;
|
package io.bisq.core.dao.node.lite.network;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
@ -7,14 +7,15 @@ import io.bisq.common.Timer;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
import io.bisq.common.app.Log;
|
import io.bisq.common.app.Log;
|
||||||
import io.bisq.common.proto.network.NetworkEnvelope;
|
import io.bisq.common.proto.network.NetworkEnvelope;
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksRequest;
|
import io.bisq.core.dao.node.messages.GetBsqBlocksRequest;
|
||||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksResponse;
|
import io.bisq.core.dao.node.messages.GetBsqBlocksResponse;
|
||||||
import io.bisq.network.p2p.NodeAddress;
|
import io.bisq.network.p2p.NodeAddress;
|
||||||
import io.bisq.network.p2p.network.CloseConnectionReason;
|
import io.bisq.network.p2p.network.CloseConnectionReason;
|
||||||
import io.bisq.network.p2p.network.Connection;
|
import io.bisq.network.p2p.network.Connection;
|
||||||
import io.bisq.network.p2p.network.MessageListener;
|
import io.bisq.network.p2p.network.MessageListener;
|
||||||
import io.bisq.network.p2p.network.NetworkNode;
|
import io.bisq.network.p2p.network.NetworkNode;
|
||||||
import io.bisq.network.p2p.peers.PeerManager;
|
import io.bisq.network.p2p.peers.PeerManager;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
@ -23,6 +24,9 @@ import java.util.Random;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a GetBsqBlocksRequest to a full node and listens on corresponding GetBsqBlocksResponse from the full node.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RequestBlocksHandler implements MessageListener {
|
public class RequestBlocksHandler implements MessageListener {
|
||||||
private static final long TIMEOUT = 120;
|
private static final long TIMEOUT = 120;
|
||||||
|
@ -46,12 +50,14 @@ public class RequestBlocksHandler implements MessageListener {
|
||||||
|
|
||||||
private final NetworkNode networkNode;
|
private final NetworkNode networkNode;
|
||||||
private final PeerManager peerManager;
|
private final PeerManager peerManager;
|
||||||
|
@Getter
|
||||||
|
private final NodeAddress nodeAddress;
|
||||||
|
@Getter
|
||||||
|
private final int startBlockHeight;
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
private Timer timeoutTimer;
|
private Timer timeoutTimer;
|
||||||
private final int nonce = new Random().nextInt();
|
private final int nonce = new Random().nextInt();
|
||||||
private boolean stopped;
|
private boolean stopped;
|
||||||
private Connection connection;
|
|
||||||
private NodeAddress peersNodeAddress;
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -60,9 +66,13 @@ public class RequestBlocksHandler implements MessageListener {
|
||||||
|
|
||||||
public RequestBlocksHandler(NetworkNode networkNode,
|
public RequestBlocksHandler(NetworkNode networkNode,
|
||||||
PeerManager peerManager,
|
PeerManager peerManager,
|
||||||
|
NodeAddress nodeAddress,
|
||||||
|
int startBlockHeight,
|
||||||
Listener listener) {
|
Listener listener) {
|
||||||
this.networkNode = networkNode;
|
this.networkNode = networkNode;
|
||||||
this.peerManager = peerManager;
|
this.peerManager = peerManager;
|
||||||
|
this.nodeAddress = nodeAddress;
|
||||||
|
this.startBlockHeight = startBlockHeight;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +85,7 @@ public class RequestBlocksHandler implements MessageListener {
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void requestBlocks(NodeAddress nodeAddress, int startBlockHeight) {
|
public void requestBlocks() {
|
||||||
Log.traceCall("nodeAddress=" + nodeAddress);
|
|
||||||
this.peersNodeAddress = nodeAddress;
|
|
||||||
|
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
GetBsqBlocksRequest getBsqBlocksRequest = new GetBsqBlocksRequest(startBlockHeight, nonce);
|
GetBsqBlocksRequest getBsqBlocksRequest = new GetBsqBlocksRequest(startBlockHeight, nonce);
|
||||||
log.debug("getBsqBlocksRequest " + getBsqBlocksRequest);
|
log.debug("getBsqBlocksRequest " + getBsqBlocksRequest);
|
||||||
|
@ -86,9 +93,9 @@ public class RequestBlocksHandler implements MessageListener {
|
||||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
String errorMessage = "A timeout occurred at sending getBsqBlocksRequest:" + getBsqBlocksRequest +
|
String errorMessage = "A timeout occurred at sending getBsqBlocksRequest:" + getBsqBlocksRequest +
|
||||||
" on peersNodeAddress:" + peersNodeAddress;
|
" on peersNodeAddress:" + nodeAddress;
|
||||||
log.debug(errorMessage + " / RequestDataHandler=" + RequestBlocksHandler.this);
|
log.debug(errorMessage + " / RequestDataHandler=" + RequestBlocksHandler.this);
|
||||||
handleFault(errorMessage, peersNodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
|
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
|
||||||
} else {
|
} else {
|
||||||
log.trace("We have stopped already. We ignore that timeoutTimer.run call. " +
|
log.trace("We have stopped already. We ignore that timeoutTimer.run call. " +
|
||||||
"Might be caused by an previous networkNode.sendMessage.onFailure.");
|
"Might be caused by an previous networkNode.sendMessage.onFailure.");
|
||||||
|
@ -97,15 +104,14 @@ public class RequestBlocksHandler implements MessageListener {
|
||||||
TIMEOUT);
|
TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("We send a {} to peer {}. ", getBsqBlocksRequest.getClass().getSimpleName(), peersNodeAddress);
|
log.debug("We send a {} to peer {}. ", getBsqBlocksRequest.getClass().getSimpleName(), nodeAddress);
|
||||||
networkNode.addMessageListener(this);
|
networkNode.addMessageListener(this);
|
||||||
SettableFuture<Connection> future = networkNode.sendMessage(peersNodeAddress, getBsqBlocksRequest);
|
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getBsqBlocksRequest);
|
||||||
Futures.addCallback(future, new FutureCallback<Connection>() {
|
Futures.addCallback(future, new FutureCallback<Connection>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Connection connection) {
|
public void onSuccess(Connection connection) {
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
RequestBlocksHandler.this.connection = connection;
|
log.trace("Send " + getBsqBlocksRequest + " to " + nodeAddress + " succeeded.");
|
||||||
log.trace("Send " + getBsqBlocksRequest + " to " + peersNodeAddress + " succeeded.");
|
|
||||||
} else {
|
} else {
|
||||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call." +
|
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call." +
|
||||||
"Might be caused by an previous timeout.");
|
"Might be caused by an previous timeout.");
|
||||||
|
@ -115,12 +121,12 @@ public class RequestBlocksHandler implements MessageListener {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NotNull Throwable throwable) {
|
public void onFailure(@NotNull Throwable throwable) {
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
String errorMessage = "Sending getBsqBlocksRequest to " + peersNodeAddress +
|
String errorMessage = "Sending getBsqBlocksRequest to " + nodeAddress +
|
||||||
" failed. That is expected if the peer is offline.\n\t" +
|
" failed. That is expected if the peer is offline.\n\t" +
|
||||||
"getBsqBlocksRequest=" + getBsqBlocksRequest + "." +
|
"getBsqBlocksRequest=" + getBsqBlocksRequest + "." +
|
||||||
"\n\tException=" + throwable.getMessage();
|
"\n\tException=" + throwable.getMessage();
|
||||||
log.error(errorMessage);
|
log.error(errorMessage);
|
||||||
handleFault(errorMessage, peersNodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
|
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
|
||||||
} else {
|
} else {
|
||||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call. " +
|
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call. " +
|
||||||
"Might be caused by an previous timeout.");
|
"Might be caused by an previous timeout.");
|
||||||
|
@ -140,7 +146,7 @@ public class RequestBlocksHandler implements MessageListener {
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
||||||
if (networkEnvelop instanceof GetBsqBlocksResponse) {
|
if (networkEnvelop instanceof GetBsqBlocksResponse) {
|
||||||
if (connection.getPeersNodeAddressOptional().isPresent() && connection.getPeersNodeAddressOptional().get().equals(peersNodeAddress)) {
|
if (connection.getPeersNodeAddressOptional().isPresent() && connection.getPeersNodeAddressOptional().get().equals(nodeAddress)) {
|
||||||
Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection);
|
Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection);
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
GetBsqBlocksResponse getBsqBlocksResponse = (GetBsqBlocksResponse) networkEnvelop;
|
GetBsqBlocksResponse getBsqBlocksResponse = (GetBsqBlocksResponse) networkEnvelop;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.bisq.core.dao.blockchain.p2p.messages;
|
package io.bisq.core.dao.node.messages;
|
||||||
|
|
||||||
import io.bisq.common.app.Capabilities;
|
import io.bisq.common.app.Capabilities;
|
||||||
import io.bisq.common.app.Version;
|
import io.bisq.common.app.Version;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.bisq.core.dao.blockchain.p2p.messages;
|
package io.bisq.core.dao.node.messages;
|
||||||
|
|
||||||
import io.bisq.common.app.Version;
|
import io.bisq.common.app.Version;
|
||||||
import io.bisq.common.proto.network.NetworkEnvelope;
|
import io.bisq.common.proto.network.NetworkEnvelope;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.bisq.core.dao.blockchain.p2p.messages;
|
package io.bisq.core.dao.node.messages;
|
||||||
|
|
||||||
import io.bisq.common.app.Version;
|
import io.bisq.common.app.Version;
|
||||||
import io.bisq.common.proto.network.NetworkEnvelope;
|
import io.bisq.common.proto.network.NetworkEnvelope;
|
27
core/src/main/java/io/bisq/core/dao/request/VoteRequest.java
Normal file
27
core/src/main/java/io/bisq/core/dao/request/VoteRequest.java
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all vote requests like compensation request, general purpose request, remove altcoin request, change fee request, etc.
|
||||||
|
*/
|
||||||
|
public abstract class VoteRequest {
|
||||||
|
|
||||||
|
public VoteRequest() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.request.altcoin;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.request.VoteRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for removing an altcoin. Altcoins are added if they fulfill the formal requirements but can be requested by
|
||||||
|
* stakeholders to get removed for any reasons (e.g. majority of stakeholder consider it a scam coin).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO implement
|
||||||
|
public class RemoveAltcoinRequest extends VoteRequest {
|
||||||
|
|
||||||
|
public RemoveAltcoinRequest() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package io.bisq.core.dao.request.compensation;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
|
public class CompensationAmountException extends Exception {
|
||||||
|
public Coin required;
|
||||||
|
public Coin provided;
|
||||||
|
|
||||||
|
public CompensationAmountException(Coin required, Coin provided) {
|
||||||
|
this.required = required;
|
||||||
|
this.provided = provided;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,10 +15,11 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.compensation;
|
package io.bisq.core.dao.request.compensation;
|
||||||
|
|
||||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||||
|
import io.bisq.core.dao.request.VoteRequest;
|
||||||
import io.bisq.generated.protobuffer.PB;
|
import io.bisq.generated.protobuffer.PB;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -35,12 +36,12 @@ import java.util.Optional;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
// Represents the state of the CompensationRequest data
|
// Represents the local and mutual state of the CompensationRequest data
|
||||||
// TODO cleanup
|
// TODO cleanup, not completed yet
|
||||||
@Getter
|
@Getter
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class CompensationRequest implements PersistablePayload {
|
public final class CompensationRequest extends VoteRequest implements PersistablePayload {
|
||||||
|
|
||||||
private final CompensationRequestPayload payload;
|
private final CompensationRequestPayload payload;
|
||||||
|
|
||||||
|
@ -60,11 +61,7 @@ public final class CompensationRequest implements PersistablePayload {
|
||||||
@Setter
|
@Setter
|
||||||
private Coin compensationRequestFee;
|
private Coin compensationRequestFee;
|
||||||
@Setter
|
@Setter
|
||||||
private Transaction feeTx;
|
private Transaction tx;
|
||||||
@Setter
|
|
||||||
Transaction txWithBtcFee;
|
|
||||||
@Setter
|
|
||||||
private Transaction signedTx;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Map<String, String> extraDataMap;
|
private Map<String, String> extraDataMap;
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.compensation;
|
package io.bisq.core.dao.request.compensation;
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
import io.bisq.common.proto.persistable.PersistableEnvelope;
|
import io.bisq.common.proto.persistable.PersistableEnvelope;
|
|
@ -15,14 +15,12 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.compensation;
|
package io.bisq.core.dao.request.compensation;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
import io.bisq.common.app.DevEnv;
|
import io.bisq.common.app.DevEnv;
|
||||||
import io.bisq.common.app.Version;
|
|
||||||
import io.bisq.common.crypto.Hash;
|
|
||||||
import io.bisq.common.crypto.KeyRing;
|
import io.bisq.common.crypto.KeyRing;
|
||||||
import io.bisq.common.proto.persistable.PersistedDataHost;
|
import io.bisq.common.proto.persistable.PersistedDataHost;
|
||||||
import io.bisq.common.storage.Storage;
|
import io.bisq.common.storage.Storage;
|
||||||
|
@ -32,12 +30,12 @@ import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import io.bisq.core.btc.exceptions.WalletException;
|
import io.bisq.core.btc.exceptions.WalletException;
|
||||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||||
import io.bisq.core.btc.wallet.ChangeBelowDustException;
|
|
||||||
import io.bisq.core.dao.DaoConstants;
|
|
||||||
import io.bisq.core.dao.DaoPeriodService;
|
import io.bisq.core.dao.DaoPeriodService;
|
||||||
|
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||||
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
||||||
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
||||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
import io.bisq.core.dao.request.compensation.consensus.OpReturnData;
|
||||||
|
import io.bisq.core.dao.request.compensation.consensus.Restrictions;
|
||||||
import io.bisq.core.provider.fee.FeeService;
|
import io.bisq.core.provider.fee.FeeService;
|
||||||
import io.bisq.network.p2p.P2PService;
|
import io.bisq.network.p2p.P2PService;
|
||||||
import io.bisq.network.p2p.storage.HashMapChangedListener;
|
import io.bisq.network.p2p.storage.HashMapChangedListener;
|
||||||
|
@ -47,24 +45,24 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.collections.transformation.FilteredList;
|
import javafx.collections.transformation.FilteredList;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.bitcoinj.core.*;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.core.InsufficientMoneyException;
|
||||||
|
import org.bitcoinj.core.Transaction;
|
||||||
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
import org.bitcoinj.crypto.DeterministicKey;
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class CompensationRequestManager implements PersistedDataHost, BsqBlockChainListener, HashMapChangedListener {
|
public class CompensationRequestManager implements PersistedDataHost, BsqBlockChainListener, HashMapChangedListener {
|
||||||
private static final Logger log = LoggerFactory.getLogger(CompensationRequestManager.class);
|
|
||||||
|
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final DaoPeriodService daoPeriodService;
|
private final DaoPeriodService daoPeriodService;
|
||||||
|
@ -113,10 +111,14 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
p2PService.addHashSetChangedListener(this);
|
p2PService.addHashSetChangedListener(this);
|
||||||
|
|
||||||
// At startup the P2PDataStorage initializes earlier, otherwise we ge the listener called.
|
// At startup the P2PDataStorage initializes earlier, otherwise we get the listener called.
|
||||||
p2PService.getP2PDataStorage().getMap().values().forEach(e -> {
|
p2PService.getP2PDataStorage().getMap().values().forEach(e -> {
|
||||||
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
|
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
|
||||||
if (protectedStoragePayload instanceof CompensationRequestPayload)
|
if (protectedStoragePayload instanceof CompensationRequestPayload)
|
||||||
|
@ -141,19 +143,21 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompensationRequest prepareCompensationRequest(CompensationRequestPayload compensationRequestPayload)
|
public CompensationRequest prepareCompensationRequest(CompensationRequestPayload compensationRequestPayload)
|
||||||
throws InsufficientMoneyException, ChangeBelowDustException, TransactionVerificationException, WalletException, IOException {
|
throws InsufficientMoneyException, TransactionVerificationException, WalletException, CompensationAmountException {
|
||||||
CompensationRequest compensationRequest = new CompensationRequest(compensationRequestPayload);
|
|
||||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
|
||||||
compensationRequest.setCompensationRequestFee(feeService.getCreateCompensationRequestFee());
|
|
||||||
compensationRequest.setFeeTx(bsqWalletService.getPreparedBurnFeeTx(compensationRequest.getCompensationRequestFee()));
|
|
||||||
|
|
||||||
String bsqAddress = compensationRequestPayload.getBsqAddress();
|
final Coin minRequestAmount = Restrictions.getMinCompensationRequestAmount();
|
||||||
// Remove initial B
|
if (compensationRequestPayload.getRequestedBsq().compareTo(minRequestAmount) < 0) {
|
||||||
bsqAddress = bsqAddress.substring(1, bsqAddress.length());
|
throw new CompensationAmountException(minRequestAmount, compensationRequestPayload.getRequestedBsq());
|
||||||
checkArgument(!compensationRequest.getFeeTx().getInputs().isEmpty(), "preparedTx inputs must not be empty");
|
}
|
||||||
|
|
||||||
|
CompensationRequest compensationRequest = new CompensationRequest(compensationRequestPayload);
|
||||||
|
compensationRequest.setCompensationRequestFee(feeService.getCreateCompensationRequestFee());
|
||||||
|
final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTx(compensationRequest.getCompensationRequestFee());
|
||||||
|
//compensationRequest.setFeeTx(preparedBurnFeeTx);
|
||||||
|
checkArgument(!preparedBurnFeeTx.getInputs().isEmpty(), "preparedTx inputs must not be empty");
|
||||||
|
|
||||||
// We use the key of the first BSQ input for signing the data
|
// We use the key of the first BSQ input for signing the data
|
||||||
TransactionOutput connectedOutput = compensationRequest.getFeeTx().getInputs().get(0).getConnectedOutput();
|
TransactionOutput connectedOutput = preparedBurnFeeTx.getInputs().get(0).getConnectedOutput();
|
||||||
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
||||||
DeterministicKey bsqKeyPair = bsqWalletService.findKeyFromPubKeyHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
DeterministicKey bsqKeyPair = bsqWalletService.findKeyFromPubKeyHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
||||||
checkNotNull(bsqKeyPair, "bsqKeyPair must not be null");
|
checkNotNull(bsqKeyPair, "bsqKeyPair must not be null");
|
||||||
|
@ -165,27 +169,17 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
||||||
String signature = bsqKeyPair.signMessage(payloadAsJson);
|
String signature = bsqKeyPair.signMessage(payloadAsJson);
|
||||||
compensationRequestPayload.setSignature(signature);
|
compensationRequestPayload.setSignature(signature);
|
||||||
|
|
||||||
String dataAndSig = payloadAsJson + signature;
|
|
||||||
byte[] dataAndSigAsBytes = dataAndSig.getBytes();
|
|
||||||
outputStream.write(DaoConstants.OP_RETURN_TYPE_COMPENSATION_REQUEST);
|
|
||||||
outputStream.write(Version.COMPENSATION_REQUEST_VERSION);
|
|
||||||
outputStream.write(Hash.getSha256Ripemd160hash(dataAndSigAsBytes));
|
|
||||||
byte opReturnData[] = outputStream.toByteArray();
|
|
||||||
|
|
||||||
//TODO should we store the hash in the compensationRequestPayload object?
|
//TODO should we store the hash in the compensationRequestPayload object?
|
||||||
|
String dataAndSig = payloadAsJson + signature;
|
||||||
|
byte[] opReturnData = OpReturnData.getBytes(dataAndSig);
|
||||||
|
|
||||||
//TODO 1 Btc output (small payment to own compensation receiving address)
|
//TODO 1 Btc output (small payment to own compensation receiving address)
|
||||||
compensationRequest.setTxWithBtcFee(
|
final Transaction txWithBtcFee = btcWalletService.completePreparedCompensationRequestTx(
|
||||||
btcWalletService.completePreparedCompensationRequestTx(
|
|
||||||
compensationRequest.getRequestedBsq(),
|
compensationRequest.getRequestedBsq(),
|
||||||
compensationRequest.getIssuanceAddress(bsqWalletService),
|
compensationRequest.getIssuanceAddress(bsqWalletService),
|
||||||
compensationRequest.getFeeTx(),
|
preparedBurnFeeTx,
|
||||||
opReturnData));
|
opReturnData);
|
||||||
if (contains(compensationRequestPayload)) {log.error("Req found");}
|
compensationRequest.setTx(bsqWalletService.signTx(txWithBtcFee));
|
||||||
compensationRequest.setSignedTx(bsqWalletService.signTx(compensationRequest.getTxWithBtcFee()));
|
|
||||||
if (contains(compensationRequestPayload)) {log.error("Req found");}
|
|
||||||
}
|
|
||||||
if (contains(compensationRequestPayload)) {log.error("Req found");}
|
|
||||||
return compensationRequest;
|
return compensationRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,10 +187,13 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
||||||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||||
// if it gets committed 2 times
|
// if it gets committed 2 times
|
||||||
// We clone before commit to avoid unwanted side effects
|
// We clone before commit to avoid unwanted side effects
|
||||||
final Transaction clonedTransaction = btcWalletService.getClonedTransaction(compensationRequest.getTxWithBtcFee());
|
final Transaction tx = compensationRequest.getTx();
|
||||||
bsqWalletService.commitTx(compensationRequest.getTxWithBtcFee());
|
bsqWalletService.commitTx(tx);
|
||||||
btcWalletService.commitTx(clonedTransaction);
|
|
||||||
bsqWalletService.broadcastTx(compensationRequest.getSignedTx(), new FutureCallback<Transaction>() {
|
final Transaction clonedTx = btcWalletService.getClonedTransaction(tx);
|
||||||
|
btcWalletService.commitTx(clonedTx);
|
||||||
|
|
||||||
|
bsqWalletService.broadcastTx(tx, new FutureCallback<Transaction>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@Nullable Transaction transaction) {
|
public void onSuccess(@Nullable Transaction transaction) {
|
||||||
checkNotNull(transaction, "Transaction must not be null at broadcastTx callback.");
|
checkNotNull(transaction, "Transaction must not be null at broadcastTx callback.");
|
||||||
|
@ -224,21 +221,21 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
||||||
} else {
|
} else {
|
||||||
final String msg = "removeCompensationRequest called for a CompensationRequest which is not ours.";
|
final String msg = "removeCompensationRequest called for a CompensationRequest which is not ours.";
|
||||||
log.warn(msg);
|
log.warn(msg);
|
||||||
if (DevEnv.DEV_MODE)
|
if (DevEnv.isDevMode())
|
||||||
throw new RuntimeException(msg);
|
throw new RuntimeException(msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final String msg = "removeCompensationRequest called with a CompensationRequest which is outside of the CompensationRequest phase.";
|
final String msg = "removeCompensationRequest called with a CompensationRequest which is outside of the CompensationRequest phase.";
|
||||||
log.warn(msg);
|
log.warn(msg);
|
||||||
if (DevEnv.DEV_MODE)
|
if (DevEnv.isDevMode())
|
||||||
throw new RuntimeException(msg);
|
throw new RuntimeException(msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInPhaseOrUnconfirmed(CompensationRequestPayload payload) {
|
private boolean isInPhaseOrUnconfirmed(CompensationRequestPayload payload) {
|
||||||
return bsqBlockChain.getTxMap().get(payload.getTxId()) == null || daoPeriodService.isInPhase(payload, DaoPeriodService.Phase.COMPENSATION_REQUESTS);
|
return bsqBlockChain.getTxMap().get(payload.getTxId()) == null || daoPeriodService.isTxInPhase(payload.getTxId(), DaoPeriodService.Phase.COMPENSATION_REQUESTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMine(CompensationRequest compensationRequest) {
|
public boolean isMine(CompensationRequest compensationRequest) {
|
||||||
|
@ -295,7 +292,7 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
||||||
} else {
|
} else {
|
||||||
final String msg = "onRemoved called of a CompensationRequest which is outside of the CompensationRequest phase is invalid and we ignore it.";
|
final String msg = "onRemoved called of a CompensationRequest which is outside of the CompensationRequest phase is invalid and we ignore it.";
|
||||||
log.warn(msg);
|
log.warn(msg);
|
||||||
if (DevEnv.DEV_MODE)
|
if (DevEnv.isDevMode())
|
||||||
throw new RuntimeException(msg);
|
throw new RuntimeException(msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -336,9 +333,9 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
||||||
|
|
||||||
private void updateFilteredLists() {
|
private void updateFilteredLists() {
|
||||||
// TODO: Does this only need to be set once to keep the list updated?
|
// TODO: Does this only need to be set once to keep the list updated?
|
||||||
pastRequests.setPredicate(daoPeriodService::isInPastCycle);
|
pastRequests.setPredicate(request -> daoPeriodService.isTxInPastCycle(request.getPayload().getTxId()));
|
||||||
activeRequests.setPredicate(compensationRequest -> {
|
activeRequests.setPredicate(compensationRequest -> {
|
||||||
return daoPeriodService.isInCurrentCycle(compensationRequest) ||
|
return daoPeriodService.isTxInCurrentCycle(compensationRequest.getPayload().getTxId()) ||
|
||||||
(bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId()) == null &&
|
(bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId()) == null &&
|
||||||
isMine(compensationRequest));
|
isMine(compensationRequest));
|
||||||
});
|
});
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bisq.core.dao.compensation;
|
package io.bisq.core.dao.request.compensation;
|
||||||
|
|
||||||
import io.bisq.common.app.Capabilities;
|
import io.bisq.common.app.Capabilities;
|
||||||
import io.bisq.common.app.Version;
|
import io.bisq.common.app.Version;
|
||||||
|
@ -37,6 +37,9 @@ import java.security.PublicKey;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload sent over wire as well as it gets persisted, containing all base data for a compensation request
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Data
|
@Data
|
||||||
public final class CompensationRequestPayload implements LazyProcessedPayload, PersistableProtectedPayload, CapabilityRequiringPayload {
|
public final class CompensationRequestPayload implements LazyProcessedPayload, PersistableProtectedPayload, CapabilityRequiringPayload {
|
||||||
|
@ -180,7 +183,7 @@ public final class CompensationRequestPayload implements LazyProcessedPayload, P
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
//TODo not needed?
|
//TODO not needed?
|
||||||
@Override
|
@Override
|
||||||
public long getTTL() {
|
public long getTTL() {
|
||||||
return TimeUnit.DAYS.toMillis(30);
|
return TimeUnit.DAYS.toMillis(30);
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.request.compensation.consensus;
|
||||||
|
|
||||||
|
import io.bisq.common.app.Version;
|
||||||
|
import io.bisq.common.crypto.Hash;
|
||||||
|
import io.bisq.core.dao.OpReturnTypes;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class OpReturnData {
|
||||||
|
|
||||||
|
public static byte[] getBytes(String input) {
|
||||||
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
byte[] dataAndSigAsBytes = input.getBytes();
|
||||||
|
outputStream.write(OpReturnTypes.COMPENSATION_REQUEST);
|
||||||
|
outputStream.write(Version.COMPENSATION_REQUEST_VERSION);
|
||||||
|
outputStream.write(Hash.getSha256Ripemd160hash(dataAndSigAsBytes));
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Not expected to happen ever
|
||||||
|
e.printStackTrace();
|
||||||
|
log.error(e.toString());
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.request.compensation.consensus;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
|
public class Restrictions {
|
||||||
|
|
||||||
|
public static Coin getMinCompensationRequestAmount() {
|
||||||
|
return Coin.valueOf(5_000); // 50 BSQ
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Coin getMaxCompensationRequestAmount() {
|
||||||
|
return Coin.valueOf(5_000_000); // 50 000 BSQ
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.request.compensation.consensus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation for compensation requests
|
||||||
|
*/
|
||||||
|
// TODO move all validation code here...
|
||||||
|
public class Validation {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.request.generic;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.request.VoteRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for anything not covered by specific vote requests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO implement
|
||||||
|
public class GenericRequest extends VoteRequest {
|
||||||
|
|
||||||
|
public GenericRequest() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bisq.core.dao.request.param;
|
||||||
|
|
||||||
|
import io.bisq.core.dao.request.VoteRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for changing certain parameters used in Bisq like trading fee, default security deposit, trade limits,...
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO implement
|
||||||
|
public class ChangeParamRequest extends VoteRequest {
|
||||||
|
|
||||||
|
public ChangeParamRequest() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ package io.bisq.core.dao.vote;
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
import io.bisq.core.dao.request.compensation.CompensationRequest;
|
||||||
|
|
||||||
public final class CompensationRequestVoteItem implements PersistablePayload {
|
public final class CompensationRequestVoteItem implements PersistablePayload {
|
||||||
public final CompensationRequest compensationRequest;
|
public final CompensationRequest compensationRequest;
|
||||||
|
|
|
@ -29,9 +29,9 @@ import io.bisq.core.app.BisqEnvironment;
|
||||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||||
import io.bisq.core.dao.DaoPeriodService;
|
import io.bisq.core.dao.DaoPeriodService;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
import io.bisq.core.dao.request.compensation.CompensationRequest;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||||
import io.bisq.core.dao.compensation.CompensationRequestPayload;
|
import io.bisq.core.dao.request.compensation.CompensationRequestPayload;
|
||||||
import io.bisq.core.provider.fee.FeeService;
|
import io.bisq.core.provider.fee.FeeService;
|
||||||
import io.bisq.generated.protobuffer.PB;
|
import io.bisq.generated.protobuffer.PB;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -91,6 +91,10 @@ public class VotingManager implements PersistedDataHost {
|
||||||
this.votingDefaultValues = votingDefaultValues;
|
this.votingDefaultValues = votingDefaultValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readPersisted() {
|
public void readPersisted() {
|
||||||
if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) {
|
if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) {
|
||||||
|
|
22
core/src/main/java/io/bisq/core/dao/vote/package-info.java
Normal file
22
core/src/main/java/io/bisq/core/dao/vote/package-info.java
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* This file is part of bisq.
|
||||||
|
*
|
||||||
|
* bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vote package was based on outdated vote concept. Ignore it for now...
|
||||||
|
*/
|
||||||
|
//TODO impl. new concept for blind voting
|
||||||
|
package io.bisq.core.dao.vote;
|
|
@ -81,10 +81,7 @@ public class FilterManager {
|
||||||
private final ObjectProperty<Filter> filterProperty = new SimpleObjectProperty<>();
|
private final ObjectProperty<Filter> filterProperty = new SimpleObjectProperty<>();
|
||||||
private final List<Listener> listeners = new ArrayList<>();
|
private final List<Listener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
private final String pubKeyAsHex;
|
||||||
private static final String pubKeyAsHex = DevEnv.USE_DEV_PRIVILEGE_KEYS ?
|
|
||||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
|
||||||
"022ac7b7766b0aedff82962522c2c14fb8d1961dabef6e5cfd10edc679456a32f1";
|
|
||||||
private ECKey filterSigningKey;
|
private ECKey filterSigningKey;
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,7 +96,8 @@ public class FilterManager {
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
BisqEnvironment bisqEnvironment,
|
BisqEnvironment bisqEnvironment,
|
||||||
ProvidersRepository providersRepository,
|
ProvidersRepository providersRepository,
|
||||||
@Named(AppOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
|
@Named(AppOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg,
|
||||||
|
@Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
@ -107,6 +105,9 @@ public class FilterManager {
|
||||||
this.bisqEnvironment = bisqEnvironment;
|
this.bisqEnvironment = bisqEnvironment;
|
||||||
this.providersRepository = providersRepository;
|
this.providersRepository = providersRepository;
|
||||||
this.ignoreDevMsg = ignoreDevMsg;
|
this.ignoreDevMsg = ignoreDevMsg;
|
||||||
|
pubKeyAsHex = useDevPrivilegeKeys ?
|
||||||
|
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||||
|
"022ac7b7766b0aedff82962522c2c14fb8d1961dabef6e5cfd10edc679456a32f1";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
|
|
|
@ -270,6 +270,10 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||||
return Coin.valueOf(offerPayload.getMinAmount());
|
return Coin.valueOf(offerPayload.getMinAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRange() {
|
||||||
|
return offerPayload.getAmount() != offerPayload.getMinAmount();
|
||||||
|
}
|
||||||
|
|
||||||
public Date getDate() {
|
public Date getDate() {
|
||||||
return new Date(offerPayload.getDate());
|
return new Date(offerPayload.getDate());
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,14 @@ public class OfferBookService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void activateOffer(Offer offer, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
||||||
|
addOffer(offer, resultHandler, errorMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivateOffer(OfferPayload offerPayload, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
||||||
|
removeOffer(offerPayload, resultHandler, errorMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
public void removeOffer(OfferPayload offerPayload, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
public void removeOffer(OfferPayload offerPayload, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
||||||
if (p2PService.removeData(offerPayload, true)) {
|
if (p2PService.removeData(offerPayload, true)) {
|
||||||
log.trace("Remove offer from network was successful. OfferPayload ID = " + offerPayload.getId());
|
log.trace("Remove offer from network was successful. OfferPayload ID = " + offerPayload.getId());
|
||||||
|
|
|
@ -19,6 +19,7 @@ package io.bisq.core.offer;
|
||||||
|
|
||||||
import io.bisq.common.Timer;
|
import io.bisq.common.Timer;
|
||||||
import io.bisq.common.UserThread;
|
import io.bisq.common.UserThread;
|
||||||
|
import io.bisq.common.proto.ProtoUtil;
|
||||||
import io.bisq.common.storage.Storage;
|
import io.bisq.common.storage.Storage;
|
||||||
import io.bisq.core.trade.Tradable;
|
import io.bisq.core.trade.Tradable;
|
||||||
import io.bisq.core.trade.TradableList;
|
import io.bisq.core.trade.TradableList;
|
||||||
|
@ -40,29 +41,34 @@ public final class OpenOffer implements Tradable {
|
||||||
AVAILABLE,
|
AVAILABLE,
|
||||||
RESERVED,
|
RESERVED,
|
||||||
CLOSED,
|
CLOSED,
|
||||||
CANCELED
|
CANCELED,
|
||||||
|
DEACTIVATED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Offer offer;
|
private final Offer offer;
|
||||||
@Getter
|
@Getter
|
||||||
private State state = State.AVAILABLE;
|
private State state;
|
||||||
|
|
||||||
transient private Storage<TradableList<OpenOffer>> storage;
|
transient private Storage<TradableList<OpenOffer>> storage;
|
||||||
|
|
||||||
public OpenOffer(Offer offer, Storage<TradableList<OpenOffer>> storage) {
|
public OpenOffer(Offer offer, Storage<TradableList<OpenOffer>> storage) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
state = State.AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// PROTO BUFFER
|
// PROTO BUFFER
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private OpenOffer(Offer offer) {
|
private OpenOffer(Offer offer, State state) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
}
|
this.state = state;
|
||||||
|
|
||||||
|
if (this.state == State.RESERVED)
|
||||||
|
setState(State.AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PB.Tradable toProtoMessage() {
|
public PB.Tradable toProtoMessage() {
|
||||||
|
@ -73,11 +79,8 @@ public final class OpenOffer implements Tradable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Tradable fromProto(PB.OpenOffer proto) {
|
public static Tradable fromProto(PB.OpenOffer proto) {
|
||||||
OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()));
|
return new OpenOffer(Offer.fromProto(proto.getOffer()),
|
||||||
// If we have a reserved state from the local db we reset it
|
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()));
|
||||||
if (openOffer.getState() == State.RESERVED)
|
|
||||||
openOffer.setState(State.AVAILABLE);
|
|
||||||
return openOffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,6 +121,10 @@ public final class OpenOffer implements Tradable {
|
||||||
stopTimeout();
|
stopTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDeactivated() {
|
||||||
|
return state == State.DEACTIVATED;
|
||||||
|
}
|
||||||
|
|
||||||
private void startTimeout() {
|
private void startTimeout() {
|
||||||
stopTimeout();
|
stopTimeout();
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import io.bisq.network.p2p.*;
|
||||||
import io.bisq.network.p2p.peers.PeerManager;
|
import io.bisq.network.p2p.peers.PeerManager;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -327,11 +328,45 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from my offers
|
public void activateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
|
Offer offer = openOffer.getOffer();
|
||||||
|
openOffer.setStorage(openOfferTradableListStorage);
|
||||||
|
offerBookService.activateOffer(offer,
|
||||||
|
() -> {
|
||||||
|
openOffer.setState(OpenOffer.State.AVAILABLE);
|
||||||
|
log.debug("activateOpenOffer, offerId={}", offer.getId());
|
||||||
|
resultHandler.handleResult();
|
||||||
|
},
|
||||||
|
errorMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
|
Offer offer = openOffer.getOffer();
|
||||||
|
openOffer.setStorage(openOfferTradableListStorage);
|
||||||
|
offerBookService.deactivateOffer(offer.getOfferPayload(),
|
||||||
|
() -> {
|
||||||
|
openOffer.setState(OpenOffer.State.DEACTIVATED);
|
||||||
|
log.debug("deactivateOpenOffer, offerId={}", offer.getId());
|
||||||
|
resultHandler.handleResult();
|
||||||
|
},
|
||||||
|
errorMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
public void removeOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
public void removeOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
|
if (openOffer.isDeactivated()) {
|
||||||
|
openOffer.setStorage(openOfferTradableListStorage);
|
||||||
|
onRemoved(openOffer, resultHandler, offer);
|
||||||
|
} else {
|
||||||
offerBookService.removeOffer(offer.getOfferPayload(),
|
offerBookService.removeOffer(offer.getOfferPayload(),
|
||||||
() -> {
|
() -> {
|
||||||
|
onRemoved(openOffer, resultHandler, offer);
|
||||||
|
},
|
||||||
|
errorMessageHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
|
||||||
offer.setState(Offer.State.REMOVED);
|
offer.setState(Offer.State.REMOVED);
|
||||||
openOffer.setState(OpenOffer.State.CANCELED);
|
openOffer.setState(OpenOffer.State.CANCELED);
|
||||||
openOffers.remove(openOffer);
|
openOffers.remove(openOffer);
|
||||||
|
@ -339,8 +374,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
log.debug("removeOpenOffer, offerId={}", offer.getId());
|
log.debug("removeOpenOffer, offerId={}", offer.getId());
|
||||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
},
|
|
||||||
errorMessageHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close openOffer after deposit published
|
// Close openOffer after deposit published
|
||||||
|
@ -483,13 +516,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
final OpenOffer openOffer = openOffersList.get(i);
|
final OpenOffer openOffer = openOffersList.get(i);
|
||||||
UserThread.runAfterRandomDelay(() -> {
|
UserThread.runAfterRandomDelay(() -> {
|
||||||
if (openOffers.contains(openOffer)) {
|
if (openOffers.contains(openOffer)) {
|
||||||
// The openOffer.getId().contains("_") check is because there was once a version
|
|
||||||
// where we encoded the version nr in the offer id with a "_" as separator.
|
|
||||||
// That caused several issues and was reverted. So if there are still old offers out with that
|
|
||||||
// special offer ID format those must not be published as they cause failed taker attempts
|
|
||||||
// with lost taker fee.
|
|
||||||
String id = openOffer.getId();
|
String id = openOffer.getId();
|
||||||
if (id != null && !id.contains("_"))
|
if (id != null && !openOffer.isDeactivated())
|
||||||
republishOffer(openOffer);
|
republishOffer(openOffer);
|
||||||
else
|
else
|
||||||
log.warn("You have an offer with an invalid offer ID: offerID=" + id);
|
log.warn("You have an offer with an invalid offer ID: offerID=" + id);
|
||||||
|
@ -567,7 +595,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
final OpenOffer openOffer = openOffersList.get(i);
|
final OpenOffer openOffer = openOffersList.get(i);
|
||||||
UserThread.runAfterRandomDelay(() -> {
|
UserThread.runAfterRandomDelay(() -> {
|
||||||
// we need to check if in the meantime the offer has been removed
|
// we need to check if in the meantime the offer has been removed
|
||||||
if (openOffers.contains(openOffer))
|
if (openOffers.contains(openOffer) && !openOffer.isDeactivated())
|
||||||
refreshOffer(openOffer);
|
refreshOffer(openOffer);
|
||||||
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue