mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 22:45:21 +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">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.5</version>
|
||||
<groupId>io.bisq.exchange</groupId>
|
||||
<version>-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -128,7 +128,7 @@
|
|||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.bisq</groupId>
|
||||
<groupId>io.bisq.exchange</groupId>
|
||||
<artifactId>consensus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</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,..)
|
||||
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.
|
||||
// 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).
|
||||
// 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).
|
||||
public static final boolean USE_DEV_PRIVILEGE_KEYS = false;
|
||||
public static final String DEV_PRIVILEGE_PUB_KEY = "027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee";
|
||||
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
|
||||
// offers are filled with default values. Intended to make dev testing faster.
|
||||
@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_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
|
||||
// Therefore all sub versions start again with 1
|
||||
// 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) {
|
||||
return getSubVersion(version, 0);
|
||||
|
@ -120,4 +120,5 @@ public class Version {
|
|||
|
||||
public static final byte COMPENSATION_REQUEST_VERSION = (byte) 0x01;
|
||||
public static final byte VOTING_VERSION = (byte) 0x01;
|
||||
public static final byte VOTING_RELEASE_VERSION = (byte) 0x01;
|
||||
}
|
||||
|
|
|
@ -96,22 +96,19 @@ public class CurrencyUtil {
|
|||
result.add(new CryptoCurrency("BCH", "Bitcoin Cash"));
|
||||
result.add(new CryptoCurrency("BCHC", "Bitcoin Clashic"));
|
||||
result.add(new CryptoCurrency("BTG", "Bitcoin Gold"));
|
||||
result.add(new CryptoCurrency("DARX", "BitDaric"));
|
||||
result.add(new CryptoCurrency("BURST", "Burstcoin"));
|
||||
result.add(new CryptoCurrency("GBYTE", "Byte"));
|
||||
result.add(new CryptoCurrency("CAGE", "Cagecoin"));
|
||||
result.add(new CryptoCurrency("XCP", "Counterparty"));
|
||||
result.add(new CryptoCurrency("CREA", "Creativecoin"));
|
||||
result.add(new CryptoCurrency("XCN", "Cryptonite"));
|
||||
result.add(new CryptoCurrency("DAI", "Dai Stablecoin", true));
|
||||
result.add(new CryptoCurrency("DNET", "DarkNet"));
|
||||
if (!baseCurrencyCode.equals("DASH"))
|
||||
result.add(new CryptoCurrency("DASH", "Dash"));
|
||||
result.add(new CryptoCurrency("DCT", "DECENT"));
|
||||
result.add(new CryptoCurrency("DCR", "Decred"));
|
||||
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("ELLA", "Ellaism"));
|
||||
result.add(new CryptoCurrency("ESP", "Espers"));
|
||||
|
@ -133,7 +130,6 @@ public class CurrencyUtil {
|
|||
result.add(new CryptoCurrency("NMC", "Namecoin"));
|
||||
result.add(new CryptoCurrency("NBT", "NuBits"));
|
||||
result.add(new CryptoCurrency("NXT", "Nxt"));
|
||||
result.add(new CryptoCurrency("ODN", "Obsidian"));
|
||||
result.add(new CryptoCurrency("888", "OctoCoin"));
|
||||
result.add(new CryptoCurrency("PART", "Particl"));
|
||||
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("XSPEC", "Spectrecoin"));
|
||||
result.add(new CryptoCurrency("STEEM", "STEEM"));
|
||||
result.add(new CryptoCurrency("STL", "Stellite"));
|
||||
|
||||
result.add(new CryptoCurrency("TRC", "Terracoin"));
|
||||
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("WAC", "WACoins"));
|
||||
result.add(new CryptoCurrency("WILD", "WILD Token", true));
|
||||
result.add(new CryptoCurrency("YTN", "Yenten"));
|
||||
result.add(new CryptoCurrency("XZC", "Zcoin"));
|
||||
result.add(new CryptoCurrency("ZEC", "Zcash"));
|
||||
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);
|
||||
|
||||
// Util for printing all altcoins for adding to FAQ page
|
||||
|
@ -185,8 +194,7 @@ public class CurrencyUtil {
|
|||
if (!baseCurrencyCode.equals("DASH"))
|
||||
result.add(new CryptoCurrency("DASH", "Dash"));
|
||||
result.add(new CryptoCurrency("DCR", "Decred"));
|
||||
if (!baseCurrencyCode.equals("DOGE"))
|
||||
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
|
||||
result.add(new CryptoCurrency("ONION", "DeepOnion"));
|
||||
result.add(new CryptoCurrency("ETH", "Ether"));
|
||||
result.add(new CryptoCurrency("ETC", "Ether Classic"));
|
||||
result.add(new CryptoCurrency("GRC", "Gridcoin"));
|
||||
|
@ -239,7 +247,72 @@ public class CurrencyUtil {
|
|||
new FiatCurrency("HKD"),
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ public class Res {
|
|||
|
||||
static {
|
||||
GlobalSettings.localeProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if ("en".equalsIgnoreCase(newValue.getLanguage()))
|
||||
newValue = Locale.ROOT;
|
||||
resourceBundle = ResourceBundle.getBundle("i18n.displayStrings", newValue, new UTF8Control());
|
||||
});
|
||||
}
|
||||
|
@ -101,7 +103,7 @@ public class Res {
|
|||
.replace("bitcoin", baseCurrencyNameLowerCase);
|
||||
} catch (MissingResourceException e) {
|
||||
log.warn("Missing resource for key: " + key);
|
||||
if (DevEnv.DEV_MODE)
|
||||
if (DevEnv.isDevMode())
|
||||
throw new RuntimeException("Missing resource for key: " + key);
|
||||
|
||||
return key;
|
||||
|
@ -141,4 +143,4 @@ class UTF8Control extends ResourceBundle.Control {
|
|||
}
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ package io.bisq.protobuffer;
|
|||
option java_package = "io.bisq.generated.protobuffer";
|
||||
option java_outer_classname = "PB";
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Network messages
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -60,7 +58,6 @@ message NetworkEnvelope {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Implementations of NetworkEnvelope
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -87,7 +84,6 @@ message GetUpdatedDataRequest {
|
|||
repeated bytes excluded_keys = 3;
|
||||
}
|
||||
|
||||
|
||||
// peers
|
||||
|
||||
message GetPeersRequest {
|
||||
|
@ -112,7 +108,6 @@ message Pong {
|
|||
int32 request_nonce = 1;
|
||||
}
|
||||
|
||||
|
||||
// offer
|
||||
|
||||
message OfferAvailabilityRequest {
|
||||
|
@ -135,7 +130,6 @@ message RefreshOfferMessage {
|
|||
int32 sequence_number = 4;
|
||||
}
|
||||
|
||||
|
||||
// storage
|
||||
|
||||
message AddDataMessage {
|
||||
|
@ -154,7 +148,6 @@ message AddPersistableNetworkPayloadMessage {
|
|||
PersistableNetworkPayload payload = 1;
|
||||
}
|
||||
|
||||
|
||||
// misc
|
||||
|
||||
message CloseConnectionMessage {
|
||||
|
@ -168,7 +161,6 @@ message PrefixedSealedAndSignedMessage {
|
|||
string uid = 4;
|
||||
}
|
||||
|
||||
|
||||
// trade
|
||||
|
||||
message PayDepositRequest {
|
||||
|
@ -241,7 +233,7 @@ message PayoutTxPublishedMessage {
|
|||
string trade_id = 1;
|
||||
bytes payout_tx = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
string uid = 4;
|
||||
string uid = 4;
|
||||
}
|
||||
|
||||
// dispute
|
||||
|
@ -292,7 +284,6 @@ message PrivateNotificationMessage {
|
|||
PrivateNotificationPayload private_notification_payload = 3;
|
||||
}
|
||||
|
||||
|
||||
// DAO
|
||||
|
||||
message GetBsqBlocksRequest {
|
||||
|
@ -309,7 +300,6 @@ message NewBsqBlockBroadcastMessage {
|
|||
BsqBlock bsq_block = 1;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Payload
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -339,7 +329,6 @@ message SealedAndSigned {
|
|||
bytes sig_public_key_bytes = 4;
|
||||
}
|
||||
|
||||
|
||||
// storage
|
||||
|
||||
message StoragePayload {
|
||||
|
@ -374,7 +363,6 @@ message ProtectedStorageEntry {
|
|||
int64 creation_time_stamp = 5;
|
||||
}
|
||||
|
||||
|
||||
// mailbox
|
||||
|
||||
message StorageEntryWrapper {
|
||||
|
@ -394,7 +382,6 @@ message DataAndSeqNrPair {
|
|||
int32 sequence_number = 2;
|
||||
}
|
||||
|
||||
|
||||
// misc
|
||||
|
||||
message PrivateNotificationPayload {
|
||||
|
@ -409,7 +396,6 @@ message PaymentAccountFilter {
|
|||
string value = 3;
|
||||
}
|
||||
|
||||
|
||||
// DAO
|
||||
|
||||
enum ScriptType {
|
||||
|
@ -441,7 +427,7 @@ message TxInput {
|
|||
message SpentInfo {
|
||||
int64 block_height = 1;
|
||||
string tx_id = 2;
|
||||
int32 input_index= 3;
|
||||
int32 input_index = 3;
|
||||
}
|
||||
|
||||
enum TxOutputType {
|
||||
|
@ -449,10 +435,10 @@ enum TxOutputType {
|
|||
UNDEFINED = 1;
|
||||
BSQ_OUTPUT = 2;
|
||||
BTC_OUTPUT = 3;
|
||||
OP_RETURN_OUTPUT = 4;
|
||||
COMPENSATION_REQUEST_OP_RETURN_OUTPUT = 5;
|
||||
COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT = 6;
|
||||
VOTING_OP_RETURN_OUTPUT = 7;
|
||||
COMPENSATION_REQUEST_OP_RETURN_OUTPUT = 4;
|
||||
COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT = 5;
|
||||
VOTE_OP_RETURN_OUTPUT = 6;
|
||||
VOTE_REVEAL_OP_RETURN_OUTPUT = 7;
|
||||
}
|
||||
|
||||
message TxOutput {
|
||||
|
@ -462,21 +448,13 @@ message TxOutput {
|
|||
PubKeyScript pub_key_script = 4;
|
||||
string address = 5;
|
||||
bytes op_return_data = 6;
|
||||
int32 block_height= 7;
|
||||
int32 block_height = 7;
|
||||
bool is_unspent = 8;
|
||||
bool is_verified = 9;
|
||||
TxOutputType tx_output_type = 10;
|
||||
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 {
|
||||
PB_ERROR_TX_TYPE = 0;
|
||||
UNDEFINED_TX_TYPE = 1;
|
||||
|
@ -487,17 +465,22 @@ enum TxType {
|
|||
PAY_TRADE_FEE = 6;
|
||||
COMPENSATION_REQUEST = 7;
|
||||
VOTE = 8;
|
||||
ISSUANCE = 9;
|
||||
LOCK_UP = 10;
|
||||
UN_LOCK = 11;
|
||||
VOTE_REVEAL = 9;
|
||||
ISSUANCE = 10;
|
||||
LOCK_UP = 11;
|
||||
UN_LOCK = 12;
|
||||
}
|
||||
|
||||
message Tx {
|
||||
TxVo tx_vo = 1;
|
||||
repeated TxInput inputs = 2;
|
||||
repeated TxOutput outputs = 3;
|
||||
int64 burnt_fee = 4;
|
||||
TxType tx_type = 5;
|
||||
string tx_version = 1;
|
||||
string id = 2;
|
||||
int32 block_height = 3;
|
||||
string block_hash = 4;
|
||||
int64 time = 5;
|
||||
repeated TxInput inputs = 6;
|
||||
repeated TxOutput outputs = 7;
|
||||
int64 burnt_fee = 8;
|
||||
TxType tx_type = 9;
|
||||
}
|
||||
|
||||
message BsqBlock {
|
||||
|
@ -512,8 +495,6 @@ message TxIdIndexTuple {
|
|||
int32 index = 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Storage payload
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -641,7 +622,7 @@ message OfferPayload {
|
|||
string maker_payment_account_id = 16;
|
||||
string offer_fee_payment_tx_id = 17;
|
||||
string country_code = 18;
|
||||
repeated string accepted_country_codes =19;
|
||||
repeated string accepted_country_codes = 19;
|
||||
string bank_id = 20;
|
||||
repeated string accepted_bank_ids = 21;
|
||||
string version_nr = 22;
|
||||
|
@ -668,7 +649,6 @@ message AccountAgeWitness {
|
|||
int64 date = 2;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Dispute payload
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -740,7 +720,6 @@ message DisputeResult {
|
|||
bool is_loser_publisher = 15;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Trade payload
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -785,7 +764,6 @@ enum AvailabilityResult {
|
|||
USER_IGNORED = 8;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PaymentAccount payload
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -807,6 +785,12 @@ message PaymentAccountPayload {
|
|||
PerfectMoneyAccountPayload perfect_money_account_payload = 12;
|
||||
SwishAccountPayload swish_account_payload = 13;
|
||||
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;
|
||||
}
|
||||
|
@ -885,7 +869,7 @@ message SepaAccountPayload {
|
|||
string holder_name = 1;
|
||||
string iban = 2;
|
||||
string bic = 3;
|
||||
string email = 4; // not used anymore but keep for backward compatibility
|
||||
string email = 4; // not used anymore but keep for backward compatibility
|
||||
repeated string accepted_country_codes = 5;
|
||||
}
|
||||
|
||||
|
@ -917,6 +901,32 @@ message OKPayAccountPayload {
|
|||
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 {
|
||||
string account_nr = 1;
|
||||
}
|
||||
|
@ -931,7 +941,6 @@ message USPostalMoneyOrderAccountPayload {
|
|||
string holder_name = 2;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PersistableEnvelope
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -962,7 +971,6 @@ message PersistableEnvelope {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Collections
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1028,10 +1036,9 @@ message PaymentAccountList {
|
|||
}
|
||||
|
||||
message CompensationRequestList {
|
||||
repeated CompensationRequest compensation_request = 1;
|
||||
repeated CompensationRequest compensation_request = 1;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Offer/Trade
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1066,6 +1073,7 @@ message OpenOffer {
|
|||
RESERVED = 2;
|
||||
CLOSED = 3;
|
||||
CANCELED = 4;
|
||||
DEACTIVATED = 5;
|
||||
}
|
||||
|
||||
Offer offer = 1;
|
||||
|
@ -1076,7 +1084,7 @@ message Tradable {
|
|||
oneof message {
|
||||
OpenOffer open_offer = 1;
|
||||
BuyerAsMakerTrade buyer_as_maker_trade = 2;
|
||||
BuyerAsTakerTrade buyer_as_taker_trade= 3;
|
||||
BuyerAsTakerTrade buyer_as_taker_trade = 3;
|
||||
SellerAsMakerTrade seller_as_maker_trade = 4;
|
||||
SellerAsTakerTrade seller_as_taker_trade = 5;
|
||||
}
|
||||
|
@ -1191,22 +1199,22 @@ message SellerAsTakerTrade {
|
|||
}
|
||||
|
||||
message ProcessModel {
|
||||
TradingPeer trading_peer = 1;
|
||||
string offer_id = 2;
|
||||
string account_id = 3;
|
||||
PubKeyRing pub_key_ring = 4;
|
||||
string take_offer_fee_tx_id = 5;
|
||||
bytes payout_tx_signature = 6;
|
||||
repeated NodeAddress taker_accepted_arbitrator_node_addresses = 7;
|
||||
repeated NodeAddress taker_accepted_mediator_node_addresses = 8;
|
||||
bytes prepared_deposit_tx = 9;
|
||||
repeated RawTransactionInput raw_transaction_inputs = 10;
|
||||
int64 change_output_value = 11;
|
||||
string change_output_address = 12;
|
||||
bool use_savings_wallet = 13;
|
||||
int64 funds_needed_for_trade_as_long = 14;
|
||||
bytes my_multi_sig_pub_key = 15;
|
||||
NodeAddress temp_trading_peer_node_address = 16;
|
||||
TradingPeer trading_peer = 1;
|
||||
string offer_id = 2;
|
||||
string account_id = 3;
|
||||
PubKeyRing pub_key_ring = 4;
|
||||
string take_offer_fee_tx_id = 5;
|
||||
bytes payout_tx_signature = 6;
|
||||
repeated NodeAddress taker_accepted_arbitrator_node_addresses = 7;
|
||||
repeated NodeAddress taker_accepted_mediator_node_addresses = 8;
|
||||
bytes prepared_deposit_tx = 9;
|
||||
repeated RawTransactionInput raw_transaction_inputs = 10;
|
||||
int64 change_output_value = 11;
|
||||
string change_output_address = 12;
|
||||
bool use_savings_wallet = 13;
|
||||
int64 funds_needed_for_trade_as_long = 14;
|
||||
bytes my_multi_sig_pub_key = 15;
|
||||
NodeAddress temp_trading_peer_node_address = 16;
|
||||
}
|
||||
|
||||
message TradingPeer {
|
||||
|
@ -1227,11 +1235,9 @@ message TradingPeer {
|
|||
}
|
||||
|
||||
message AccountAgeWitnessMap {
|
||||
map<string, AccountAgeWitness> account_age_witness_map = 1;
|
||||
map<string, AccountAgeWitness> account_age_witness_map = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Dispute
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1240,7 +1246,6 @@ message DisputeList {
|
|||
repeated Dispute dispute = 1;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Preferences
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1286,7 +1291,6 @@ message PreferencesPayload {
|
|||
int32 bitcoin_nodes_option_ordinal = 38;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UserPayload
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1305,7 +1309,6 @@ message UserPayload {
|
|||
Mediator registered_mediator = 11;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DAO
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1353,7 +1356,7 @@ message VoteItemsList {
|
|||
}
|
||||
|
||||
message CompensationRequestPayload {
|
||||
uint32 version = 1; // https://stackoverflow.com/questions/17594881/store-a-single-byte-in-a-protobuf-message
|
||||
uint32 version = 1; // https://stackoverflow.com/questions/17594881/store-a-single-byte-in-a-protobuf-message
|
||||
int64 creation_date = 2;
|
||||
string uid = 3;
|
||||
string name = 4;
|
||||
|
@ -1391,8 +1394,6 @@ message CompensationRequestVoteItemCollection {
|
|||
repeated CompensationRequestVoteItem compensation_request_vote_item = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1453,8 +1454,6 @@ message Region {
|
|||
string name = 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Mock
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -49,8 +49,8 @@ shared.sell=sell
|
|||
shared.buying=buying
|
||||
shared.selling=selling
|
||||
shared.P2P=P2P
|
||||
shared.offer=offer
|
||||
shared.offers=offers
|
||||
shared.oneOffer=offer
|
||||
shared.multipleOffers=offers
|
||||
shared.Offer=Offer
|
||||
shared.openOffers=open offers
|
||||
shared.trade=trade
|
||||
|
@ -84,6 +84,8 @@ shared.bankName=Bank name
|
|||
shared.acceptedBanks=Accepted banks
|
||||
shared.amountMinMax=Amount (min - max)
|
||||
shared.remove=Remove
|
||||
shared.deactivate=Deactivate
|
||||
shared.activate=Activate
|
||||
shared.goTo=Go to {0}
|
||||
shared.BTCMinMax=BTC (min - max)
|
||||
shared.removeOffer=Remove offer
|
||||
|
@ -98,6 +100,8 @@ shared.fundFromSavingsWalletButton=Transfer funds from Bisq wallet
|
|||
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.distanceInPercent=Distance in % from market price
|
||||
shared.belowInPercent=Below % from market price
|
||||
shared.aboveInPercent=Above % from market price
|
||||
shared.enterPercentageValue=Enter % value
|
||||
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\".
|
||||
|
@ -262,10 +266,10 @@ market.tabs.trades=Trades
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Offer book for {0}
|
||||
market.offerBook.leftButtonAltcoin=I want to buy {0} (sell {1})
|
||||
market.offerBook.rightButtonAltcoin=I want to sell {0} (buy {1})
|
||||
market.offerBook.leftButtonFiat=I want to buy {0} with {1}
|
||||
market.offerBook.rightButtonFiat=I want to sell {0} for {1}
|
||||
market.offerBook.buyAltcoin=Buy {0} (sell {1})
|
||||
market.offerBook.sellAltcoin=Sell {0} (buy {1})
|
||||
market.offerBook.buyWithFiat=Buy {0}
|
||||
market.offerBook.sellWithFiat=Sell {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Sell {0} to
|
||||
market.offerBook.buyOffersHeaderLabel=Buy {0} from
|
||||
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.removeOffer.success=Remove offer was successful.
|
||||
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.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.tradeId=Please don't forget to add the trade ID
|
||||
# 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.
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
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.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.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.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.
|
||||
|
@ -653,7 +659,7 @@ funds.withdrawal.noFundsAvailable=No funds are available for withdrawal
|
|||
funds.withdrawal.confirmWithdrawalRequest=Confirm withdrawal request
|
||||
funds.withdrawal.withdrawMultipleAddresses=Withdraw from multiple addresses ({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.setAmount=Set the amount to withdraw
|
||||
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.deviation=Max. deviation from market price:
|
||||
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.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.ignorePeers=Ignore peers with onion address (comma sep.):
|
||||
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.BREAK3=
|
||||
|
||||
dao.compensation.selectedRequest=Selected 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.remove=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.successfullyFunded=Compensation request successfully funded.
|
||||
|
||||
dao.compensation.past.header=Past compensation request
|
||||
|
||||
dao.compensation.create.createNew=Create new compensation request
|
||||
dao.compensation.create.create.button=Create 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.requestedBsq=Requested amount in BSQ:
|
||||
dao.compensation.display.bsqAddress=BSQ address:
|
||||
dao.compensation.display.txId=BTC Transaction ID:
|
||||
|
||||
dao.voting.item.title=Compensation request
|
||||
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\
|
||||
Missing: {0}
|
||||
|
||||
|
||||
####################################################################
|
||||
# Windows
|
||||
####################################################################
|
||||
|
@ -1560,14 +1568,6 @@ LTC_MAINNET=Litecoin Mainnet
|
|||
LTC_TESTNET=Litecoin Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
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"
|
||||
DASH_MAINNET=DASH Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1629,6 +1629,7 @@ seed.restore.error=An error occurred when restoring the wallets with seed words.
|
|||
# Payment methods
|
||||
####################################################################
|
||||
|
||||
payment.account=Account
|
||||
payment.account.no=Account no.:
|
||||
payment.account.name=Account name:
|
||||
payment.account.owner=Account owner full name
|
||||
|
@ -1648,9 +1649,6 @@ payment.restore.default=No, restore default currency
|
|||
payment.email=Email:
|
||||
payment.country=Country:
|
||||
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.altcoin.address=Altcoin address:
|
||||
payment.altcoin=Altcoin:
|
||||
|
@ -1658,7 +1656,13 @@ payment.select.altcoin=Select or search altcoin
|
|||
payment.secret=Secret question:
|
||||
payment.answer=Answer:
|
||||
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.salt=Salt for account age verification:
|
||||
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● Frost Send Money\n\
|
||||
\t● U.S. Bank Send Money\n\
|
||||
\t● TD Bank\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\
|
||||
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. \
|
||||
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
|
||||
# dynamic values are not recognized by IntelliJ
|
||||
|
@ -1741,6 +1760,12 @@ WESTERN_UNION_SHORT=Western Union
|
|||
|
||||
# Do not translate brand names
|
||||
OK_PAY=OKPay
|
||||
UPHOLD=Uphold
|
||||
CASH_APP=Cash App
|
||||
MONEY_BEAM=MoneyBeam (N26)
|
||||
VENMO=Venmo
|
||||
POPMONEY=Popmoney
|
||||
REVOLUT=Revolut
|
||||
PERFECT_MONEY=Perfect Money
|
||||
ALI_PAY=AliPay
|
||||
SEPA=SEPA
|
||||
|
@ -1755,6 +1780,18 @@ BLOCK_CHAINS=Altcoins
|
|||
# suppress inspection "UnusedProperty"
|
||||
OK_PAY_SHORT=OKPay
|
||||
# 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
|
||||
# suppress inspection "UnusedProperty"
|
||||
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.bsq.insufficientBalance=Amount exceeds the available balance of {0}.
|
||||
validation.btc.exceedsMaxTradeLimit=Amount larger than your trade limit of {0} is not allowed.
|
||||
validation.bsq.amountBelowMinAmount=Min. amount is {0}
|
||||
|
||||
#new
|
||||
validation.invalidInput=Invalid input: {0}
|
||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=verkaufen
|
|||
shared.buying=kaufe
|
||||
shared.selling=verkaufe
|
||||
shared.P2P=P2P
|
||||
shared.offers=Angebote
|
||||
shared.oneOffer=Angebot
|
||||
shared.multipleOffers=Angebote
|
||||
shared.Offer=Angebot
|
||||
shared.openOffers=offene Angebote
|
||||
shared.trades=Händel
|
||||
|
@ -96,6 +97,8 @@ shared.fundFromSavingsWalletButton=Gelder aus Bisq-Wallet überweisen
|
|||
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.distanceInPercent=Abstand vom Marktpreis in %
|
||||
shared.belowInPercent=Unter dem Marktpreis in %
|
||||
shared.aboveInPercent=Über dem Marktpreis in %
|
||||
shared.enterPercentageValue=%-Wert eingeben
|
||||
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\".
|
||||
|
@ -166,6 +169,7 @@ shared.viewContractAsJson=Vertrag im JSON-Format ansehen
|
|||
shared.contract.title=Vertrag für den Handel mit der ID: {0}
|
||||
shared.paymentDetails=Zahlungsdetails des BTC-{0}:
|
||||
shared.securityDeposit=Kaution
|
||||
shared.yourSecurityDeposit=Deine Kaution
|
||||
shared.contract=Vertrag
|
||||
shared.messageArrived=Nachricht angekommen.
|
||||
shared.messageStoredInMailbox=Nachricht in Postfach gespeichert.
|
||||
|
@ -254,10 +258,10 @@ market.tabs.trades=Händel
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Angebotsbuch für {0}
|
||||
market.offerBook.leftButtonAltcoin=Ich möchte {0} kaufen ({1} verkaufen)
|
||||
market.offerBook.rightButtonAltcoin=Ich möchte {0} verkaufen ({1} kaufen)
|
||||
market.offerBook.leftButtonFiat=Ich möchte {0} mit {1} kaufen
|
||||
market.offerBook.rightButtonFiat=Ich möchte {0} für {1} verkaufen
|
||||
market.offerBook.buyAltcoin={0} kaufen ({1} verkaufen)
|
||||
market.offerBook.sellAltcoin={0} verkaufen ({1} kaufen)
|
||||
market.offerBook.buyWithFiat={0} kaufen
|
||||
market.offerBook.sellWithFiat={0} verkaufen
|
||||
market.offerBook.sellOffersHeaderLabel=Verkaufe {0} an
|
||||
market.offerBook.buyOffersHeaderLabel=Kaufe {0} von
|
||||
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.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.vote=Für Entschädigung stimmen
|
||||
dao.compensation.active.fund=Entschädigungsanfrage finanzieren
|
||||
|
@ -1324,13 +1328,6 @@ LTC_TESTNET=Litecoin-Testnetzwerk
|
|||
# suppress inspection "UnusedProperty"
|
||||
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"
|
||||
DASH_MAINNET=DASH-Hauptnetz
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1399,7 +1396,6 @@ payment.restore.default=Nein, Standardwährung wiederherstellen
|
|||
payment.email=E-Mail:
|
||||
payment.country=Land:
|
||||
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.altcoin.address=Altcoin-Adresse:
|
||||
payment.altcoin=Altcoin:
|
||||
|
@ -1407,7 +1403,7 @@ payment.select.altcoin=Altcoin wählen oder suchen
|
|||
payment.secret=Geheimfrage:
|
||||
payment.answer=Antwort:
|
||||
payment.wallet=Wallet-ID:
|
||||
payment.supported.okpay=Unterstützte Währungen:
|
||||
payment.supportedCurrencies=Unterstützte Währungen:
|
||||
payment.limitations=Einschränkungen:
|
||||
payment.accept.euro=Händel aus diesen Euroländern akzeptieren:
|
||||
payment.accept.nonEuro=Händel aus diesen Nicht-Euroländern akzeptieren:
|
||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=πώληση
|
|||
shared.buying=αγοράζοντας
|
||||
shared.selling=πουλώντας
|
||||
shared.P2P=P2P
|
||||
shared.offers=προσφορές
|
||||
shared.oneOffer=προσφορά
|
||||
shared.multipleOffers=προσφορές
|
||||
shared.Offer=Προσφορά
|
||||
shared.openOffers=ανοιχτές προσφορές
|
||||
shared.trades=συναλλαγές
|
||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Συναλλαγές
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Καθολικό προσφορών για {0}
|
||||
market.offerBook.leftButtonAltcoin=Θέλω να αγοράσω {0} (πώληση {1})
|
||||
market.offerBook.rightButtonAltcoin=Θέλω να πουλήσω {0} (αγορά {1})
|
||||
market.offerBook.leftButtonFiat=Θέλω να αγοράσω {0} με {1}
|
||||
market.offerBook.rightButtonFiat=Θέλω να πουλήσω {0} με {1}
|
||||
market.offerBook.buyAltcoin=Θέλω να αγοράσω {0} (πώληση {1})
|
||||
market.offerBook.sellAltcoin=Θέλω να πουλήσω {0} (αγορά {1})
|
||||
market.offerBook.buyWithFiat=Αγορά {0}
|
||||
market.offerBook.sellWithFiat=Πώληση {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Πουλήστε το {0} στο
|
||||
market.offerBook.buyOffersHeaderLabel=Αγοράστε {0} από
|
||||
market.offerBook.buy=Θέλω να αγοράσω bitcoin
|
||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Ενεργά αιτήματα
|
|||
dao.compensation.menuItem.pastRequests=Προηγούμενα αιτήματα
|
||||
|
||||
dao.compensation.active.header=Ενεργό αίτημα αποζημίωσης
|
||||
dao.compensation.active.selectedRequest=Επιλεγμένο αίτημα αποζημίωσης
|
||||
dao.compensation.selectedRequest=Επιλεγμένο αίτημα αποζημίωσης
|
||||
dao.compensation.active.notOpenAnymore=Το αίτημα αποζημίωσης δεν είναι πλέον ανοιχτό προς χρηματοδότηση. Περίμενε μέχρι την έναρξη της επόμενης περιόδου χρηματοδότησης.
|
||||
dao.compensation.active.vote=Ψήφισμα περί αιτήματος αποζημίωσης
|
||||
dao.compensation.active.fund=Χρηματοδότηση αιτήματος αποζημίωσης
|
||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Litecoin Testnet
|
|||
# suppress inspection "UnusedProperty"
|
||||
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"
|
||||
DASH_MAINNET=DASH Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Όχι, επανάφερε το προεπιλεγμέ
|
|||
payment.email=Email:
|
||||
payment.country=Χώρα:
|
||||
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.altcoin.address=Διεύθυνση altcoin:
|
||||
payment.altcoin=Altcoin:
|
||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Διάλεξε ή αναζήτησε altcoin:
|
|||
payment.secret=Ερώτηση ασφάλειας:
|
||||
payment.answer=Απάντηση:
|
||||
payment.wallet=Ταυτότητα πορτοφολιού:
|
||||
payment.supported.okpay= Υποστηριζόμενα νομίσματα:
|
||||
payment.supportedCurrencies=Υποστηριζόμενα νομίσματα:
|
||||
payment.limitations=Περιορισμοί:
|
||||
payment.accept.euro=Αποδοχή συναλλαγών από τις ακόλουθες χώρες Ευρώ:
|
||||
payment.accept.nonEuro=Αποδοχή συναλλαγών από τις ακόλουθες χώρες εκτός Ευρώ:
|
||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=vender
|
|||
shared.buying=Comprando
|
||||
shared.selling=Vendiendo
|
||||
shared.P2P=P2P
|
||||
shared.offers=ofertas
|
||||
shared.oneOffer=oferta
|
||||
shared.multipleOffers=ofertas
|
||||
shared.Offer=Oferta
|
||||
shared.openOffers=ofertas abiertas
|
||||
shared.trades=Intercambios
|
||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Intercambios
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Libro de ofertas para {0}
|
||||
market.offerBook.leftButtonAltcoin=Quiero comprar {0} (vender {1})
|
||||
market.offerBook.rightButtonAltcoin=Quiero vender {0} (comprar {1})
|
||||
market.offerBook.leftButtonFiat=Quiero comprar {0} con {1}
|
||||
market.offerBook.rightButtonFiat=Quiero vender {0} por {1}
|
||||
market.offerBook.buyAltcoin=Quiero comprar {0} (vender {1})
|
||||
market.offerBook.sellAltcoin=Quiero vender {0} (comprar {1})
|
||||
market.offerBook.buyWithFiat=Comprar {0}
|
||||
market.offerBook.sellWithFiat=Vender {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Vender {0} a
|
||||
market.offerBook.buyOffersHeaderLabel=Compre {0} desde
|
||||
market.offerBook.buy=Quiero comprar bitcoin
|
||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Activar solicitudes
|
|||
dao.compensation.menuItem.pastRequests=Solicitudes pasadas
|
||||
|
||||
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.vote=Votar solicitud de compensación
|
||||
dao.compensation.active.fund=Financiar solicitud de compensación
|
||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Testnet Litecoin
|
|||
# suppress inspection "UnusedProperty"
|
||||
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"
|
||||
DASH_MAINNET=DASH Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=No, restaurar moneda por defecto.
|
|||
payment.email=Email:
|
||||
payment.country=País:
|
||||
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.altcoin.address=Dirección de altcoin:
|
||||
payment.altcoin=Altcoin:
|
||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Seleccione o busque altcoin
|
|||
payment.secret=Pregunta secreta:
|
||||
payment.answer=Respuesta:
|
||||
payment.wallet=ID de cartera:
|
||||
payment.supported.okpay=Monedas soportadas:
|
||||
payment.supportedCurrencies=Monedas soportadas:
|
||||
payment.limitations=Límitaciones:
|
||||
payment.accept.euro=Aceptar intercambios desde estos países 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.selling=vendendo
|
||||
shared.P2P=P2P
|
||||
shared.offers=ofertas
|
||||
shared.oneOffer=oferta
|
||||
shared.multipleOffers=ofertas
|
||||
shared.Offer=Oferta
|
||||
shared.openOffers=abrir ofertas
|
||||
shared.trades=negociações
|
||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Negociações
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Livro de ofertas para {0}
|
||||
market.offerBook.leftButtonAltcoin=Eu quero comprar {0} (vender {1})
|
||||
market.offerBook.rightButtonAltcoin=Eu quero vender {0} (comprar {1})
|
||||
market.offerBook.leftButtonFiat=Eu quero comprar {0} com {1}
|
||||
market.offerBook.rightButtonFiat=Eu quero vender {0} por {1}
|
||||
market.offerBook.buyAltcoin=Eu quero comprar {0} (vender {1})
|
||||
market.offerBook.sellAltcoin=Eu quero vender {0} (comprar {1})
|
||||
market.offerBook.buyWithFiat=Compre {0}
|
||||
market.offerBook.sellWithFiat=Vender {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Vender {0} para
|
||||
market.offerBook.buyOffersHeaderLabel=Compre {0} de
|
||||
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.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.vote=Votar em pedido de compensação
|
||||
dao.compensation.active.fund=Financiar pedido de compensação
|
||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Litecoin Testnet
|
|||
# suppress inspection "UnusedProperty"
|
||||
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"
|
||||
DASH_MAINNET=DASH Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Não, resturar para a moeda padrão
|
|||
payment.email=Email:
|
||||
payment.country=País:
|
||||
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.altcoin.address=Endereço altcoin:
|
||||
payment.altcoin=Altcoin:
|
||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Selecionar ou buscar altcoin
|
|||
payment.secret=Pergunta secreta:
|
||||
payment.answer=Resposta:
|
||||
payment.wallet=ID da carteira:
|
||||
payment.supported.okpay=Moedas suportadas:
|
||||
payment.supportedCurrencies=Moedas suportadas:
|
||||
payment.limitations=Limitações:
|
||||
payment.accept.euro=Aceitar negociações destes países 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.selling=vânzând
|
||||
shared.P2P=P2P
|
||||
shared.offer=oferă
|
||||
shared.offers=oferte
|
||||
shared.oneOffer=ofertă
|
||||
shared.multipleOffers=oferte
|
||||
shared.Offer=Ofertă
|
||||
shared.openOffers=oferte deschise
|
||||
shared.trade=tranzacționează
|
||||
|
@ -189,6 +189,7 @@ shared.takerTxFee=Acceptant: {0}
|
|||
shared.securityDepositBox.description=Depozitul de securitate pentru BTC {0}
|
||||
shared.iConfirm=Confirm
|
||||
shared.tradingFeeInBsqInfo=echivalentul al {0} folosit drept comision de minare
|
||||
shared.paidWithBsq=plătit cu BSQ
|
||||
shared.availableBsqBalance=Sold disponibil BSQ:
|
||||
shared.unverifiedBsqBalance=Sold BSQ neverificat:
|
||||
shared.totalBsqBalance=Soldul total BSQ
|
||||
|
@ -260,12 +261,12 @@ market.tabs.trades=Tranzacții
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Cartea de oferte pentru {0}
|
||||
market.offerBook.leftButtonAltcoin=Doresc să cumpăr {0} (vând {1})
|
||||
market.offerBook.rightButtonAltcoin=Doresc să vând {0} (cumpăr {1})
|
||||
market.offerBook.leftButtonFiat=Doresc să cumpăr {0} cu {1}
|
||||
market.offerBook.rightButtonFiat=Doresc să vând {0} contra {1}
|
||||
market.offerBook.sellOffersHeaderLabel=Vindem {0} la
|
||||
market.offerBook.buyOffersHeaderLabel=Cumpărați {0} de la
|
||||
market.offerBook.buyAltcoin=Cumpărare {0} (vânzare {1})
|
||||
market.offerBook.sellAltcoin=Vânzare {0} (cumpărare {1})
|
||||
market.offerBook.buyWithFiat=Cumpără {0}
|
||||
market.offerBook.sellWithFiat=Vinde {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Vinde {0} către
|
||||
market.offerBook.buyOffersHeaderLabel=Cumpără {0} de la
|
||||
market.offerBook.buy=Doresc să cumpăr 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.placeOfferSpinnerInfo=Publicare ofertei în desfășurare ...
|
||||
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.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.takeOfferSpinnerInfo=Acceptă oferta în curs ...
|
||||
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.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}
|
||||
|
@ -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.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"
|
||||
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.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.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.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.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.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"
|
||||
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.
|
||||
|
@ -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.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.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"
|
||||
portfolio.pending.step3_seller.onPaymentReceived.part1=Ai primit plata {0} de la partenerul tău de tranzacționare?\n\n
|
||||
# suppress inspection "TrailingSpacesInProperty"
|
||||
|
@ -561,6 +564,10 @@ portfolio.pending.step5_seller.received=Ai primit:
|
|||
portfolio.pending.role=Rolul meu
|
||||
portfolio.pending.tradeInformation=Informații tranzacții
|
||||
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.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
|
||||
|
@ -758,7 +765,7 @@ settings.net.roundTripTimeColumn=Tur-retur
|
|||
settings.net.sentBytesColumn=Trimis
|
||||
settings.net.receivedBytesColumn=Recepționat
|
||||
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.notKnownYet=Încă necunoscut...
|
||||
|
@ -771,11 +778,10 @@ settings.net.inbound=Intrare
|
|||
settings.net.outbound=ieșire
|
||||
settings.net.reSyncSPVChainLabel=Resincronizează lanțul SPV
|
||||
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.reSyncSPVAfterRestartCompleted=Resincronizarea s-a încheiat. Te rog repornește aplicația.
|
||||
|
||||
settings.net.reSyncSPVFailed=Fișierul lanțului SPV nu a putut fi șters
|
||||
settings.net.reSyncSPVFailed=Nu s-a putut șterge fișierul lanțului SPV.\nEroare: {0}
|
||||
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.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.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.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.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.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.remove=Înlătură solicitarea de despăgubire
|
||||
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.restartInfo=Trebuie să repornești pentru a aplica schimbările
|
||||
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.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}
|
||||
|
||||
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.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.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.priceRelay=reluare preț
|
||||
popup.warning.priceRelay=preț releu
|
||||
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ă).
|
||||
|
@ -1349,6 +1362,7 @@ navigation.arbitratorSelection=\"Selecție arbitrii\"
|
|||
navigation.funds.availableForWithdrawal=\"Fonduri/Trimite fonduri\"
|
||||
navigation.portfolio.myOpenOffers=\"Portofoliu/Ofertele mele deschise\"
|
||||
navigation.portfolio.pending=\"Portofoliu/Tranzacții deschise\"
|
||||
navigation.portfolio.closedTrades=\"Portofoliu/Istoric\"
|
||||
navigation.funds.depositFunds=\"Fonduri/Încasare fonduri\"
|
||||
navigation.settings.preferences=\"Setări/Preferințe\"
|
||||
navigation.funds.transactions=\"Fonduri/Tranzacții\"
|
||||
|
@ -1391,13 +1405,6 @@ LTC_TESTNET=Litecoin Testnet
|
|||
# suppress inspection "UnusedProperty"
|
||||
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"
|
||||
DASH_MAINNET=Rețeaua principală DASH
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1470,7 +1477,6 @@ payment.restore.default=Nu, restaurează valuta prestabilită
|
|||
payment.email=E-mail:
|
||||
payment.country=Țara:
|
||||
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.altcoin.address=Adresă altcoin:
|
||||
payment.altcoin=Altcoin:
|
||||
|
@ -1478,7 +1484,7 @@ payment.select.altcoin=Selectează sau caută altcoin
|
|||
payment.secret=Întrebarea secretă:
|
||||
payment.answer=Răspuns:
|
||||
payment.wallet=Cod portofel:
|
||||
payment.supported.okpay=Valute acceptate:
|
||||
payment.supportedCurrencies=Valute acceptate:
|
||||
payment.limitations=Limite:
|
||||
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).
|
||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=продать
|
|||
shared.buying=покупка
|
||||
shared.selling=продажа
|
||||
shared.P2P=P2P
|
||||
shared.offers=предложения
|
||||
shared.oneOffer=предложение
|
||||
shared.multipleOffers=предложения
|
||||
shared.Offer=Предложение
|
||||
shared.openOffers=открыть предложения
|
||||
shared.trades=сделок
|
||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Сделки
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Книга предложений на {0}
|
||||
market.offerBook.leftButtonAltcoin=Я хочу купить {0} (продать {1})
|
||||
market.offerBook.rightButtonAltcoin=Я хочу продать {0} (купить {1})
|
||||
market.offerBook.leftButtonFiat=Я хочу купить {0} за {1}
|
||||
market.offerBook.rightButtonFiat=Я хочу продать {0} за {1}
|
||||
market.offerBook.buyAltcoin=Я хочу купить {0} (продать {1})
|
||||
market.offerBook.sellAltcoin=Я хочу продать {0} (купить {1})
|
||||
market.offerBook.buyWithFiat=Купить {0}
|
||||
market.offerBook.sellWithFiat=Продать {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Продать {0} до
|
||||
market.offerBook.buyOffersHeaderLabel=Купите {0} из
|
||||
market.offerBook.buy=Я хочу купить Биткоин
|
||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=Текущие запросы
|
|||
dao.compensation.menuItem.pastRequests=Прошлые запросы
|
||||
|
||||
dao.compensation.active.header=Запрос активной компенсации
|
||||
dao.compensation.active.selectedRequest=Запрос выбранной компенсации
|
||||
dao.compensation.selectedRequest=Запрос выбранной компенсации
|
||||
dao.compensation.active.notOpenAnymore=Этот запрос компенсации более не открыт для оплаты. Пожалуйста подождите до начала следующего периода оплаты.
|
||||
dao.compensation.active.vote=Голосование по запросу компенсации
|
||||
dao.compensation.active.fund=Найти запрос компенсации
|
||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Litecoin Тест-сеть
|
|||
# suppress inspection "UnusedProperty"
|
||||
LTC_REGTEST=Litecoin Регистр.-тест
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
DOGE_MAINNET=Dogecoin основная сеть
|
||||
# suppress inspection "UnusedProperty"
|
||||
DOGE_TESTNET=Dogecoin тест-сеть
|
||||
# suppress inspection "UnusedProperty"
|
||||
DOGE_REGTEST=Dogecoin Регистр. -тест
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
DASH_MAINNET=DASH Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Нет, восстановить валюту по ум
|
|||
payment.email=Электронная почта:
|
||||
payment.country=Страна:
|
||||
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.altcoin.address=Альткоин-адрес:
|
||||
payment.altcoin=Альткоин:
|
||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Выбор или поиск альткоин
|
|||
payment.secret=Секретный вопрос:
|
||||
payment.answer=Ответ:
|
||||
payment.wallet=Идентификатор кошелька:
|
||||
payment.supported.okpay=Поддерживаемые валюты:
|
||||
payment.supportedCurrencies=Поддерживаемые валюты:
|
||||
payment.limitations=Ограничения:
|
||||
payment.accept.euro=Принять сделки от этих стран Евро:
|
||||
payment.accept.nonEuro=Примите сделки от этих не- Евро стран:
|
||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=prodaj
|
|||
shared.buying=kupujem
|
||||
shared.selling=prodajem
|
||||
shared.P2P=P2P
|
||||
shared.offers=ponude
|
||||
shared.oneOffer=ponuda
|
||||
shared.multipleOffers=ponude
|
||||
shared.Offer=Ponuda
|
||||
shared.openOffers=otvorene ponude
|
||||
shared.trades=trgovine
|
||||
|
@ -254,10 +255,10 @@ market.tabs.trades=Trgovine
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title=Knjiga ponuda za {0}
|
||||
market.offerBook.leftButtonAltcoin=Želim da kupim {0} (prodaja {1})
|
||||
market.offerBook.rightButtonAltcoin=Želim da prodam {0} (kupovina {1})
|
||||
market.offerBook.leftButtonFiat=Želim da kupim {0} sa {1}
|
||||
market.offerBook.rightButtonFiat=Želim da prodam {0} za {1}
|
||||
market.offerBook.buyAltcoin=Želim da kupim {0} (prodaja {1})
|
||||
market.offerBook.sellAltcoin=Želim da prodam {0} (kupovina {1})
|
||||
market.offerBook.buyWithFiat=Kupi {0}
|
||||
market.offerBook.sellWithFiat=Prodaj {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Prodaj {0} u
|
||||
market.offerBook.buyOffersHeaderLabel=Kupite {0} iz
|
||||
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.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.vote=Glasaj na zahtev za nadoknadu
|
||||
dao.compensation.active.fund=Finansiraj zahtev za nadoknadu
|
||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=Lajtkoin Testnet
|
|||
# suppress inspection "UnusedProperty"
|
||||
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"
|
||||
DASH_MAINNET=DASH Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=Ne, vrati podrazumevanu valutu
|
|||
payment.email=Email:
|
||||
payment.country=Država:
|
||||
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.altcoin.address=Altkoin adresa:
|
||||
payment.altcoin=Altkoin:
|
||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=Izaberi ili traži altkoin
|
|||
payment.secret=Tajno pitanje:
|
||||
payment.answer=Odgovor:
|
||||
payment.wallet=ID novčanika:
|
||||
payment.supported.okpay=Podržane valute:
|
||||
payment.supportedCurrencies=Podržane valute:
|
||||
payment.limitations=Ograničenja:
|
||||
payment.accept.euro=Prihvati trgovine od ovih Euro država:
|
||||
payment.accept.nonEuro=Prihvati trgovine od ovih ne-Euro država:
|
||||
|
|
|
@ -49,7 +49,8 @@ shared.sell=卖
|
|||
shared.buying=买入
|
||||
shared.selling=卖出
|
||||
shared.P2P=P2P
|
||||
shared.offers=委托
|
||||
shared.oneOffer=委托
|
||||
shared.multipleOffers=委托
|
||||
shared.Offer=委托
|
||||
shared.openOffers=发布中委托
|
||||
shared.trades=交易
|
||||
|
@ -254,10 +255,10 @@ market.tabs.trades=行情图
|
|||
|
||||
# OfferBookChartView
|
||||
market.offerBook.chart.title={0} 的委托列表
|
||||
market.offerBook.leftButtonAltcoin=我想要买入 {0} (卖出 {1})
|
||||
market.offerBook.rightButtonAltcoin=我想要卖出 {0} (买入 {1})
|
||||
market.offerBook.leftButtonFiat=我想要用 {1} 买入 {0}
|
||||
market.offerBook.rightButtonFiat=我想要用 {1} 卖出 {0}
|
||||
market.offerBook.buyAltcoin=我想要买入 {0} (卖出 {1})
|
||||
market.offerBook.sellAltcoin=我想要卖出 {0} (买入 {1})
|
||||
market.offerBook.buyWithFiat=购买{0}
|
||||
market.offerBook.sellWithFiat=出售 {0}
|
||||
market.offerBook.sellOffersHeaderLabel=出售 {0} 到
|
||||
market.offerBook.buyOffersHeaderLabel=从中购买 {0}
|
||||
market.offerBook.buy=我想要买入比特币
|
||||
|
@ -848,7 +849,7 @@ dao.compensation.menuItem.activeRequests=活动要求
|
|||
dao.compensation.menuItem.pastRequests=过期要求
|
||||
|
||||
dao.compensation.active.header=活动赔偿要求
|
||||
dao.compensation.active.selectedRequest=选定赔偿要求
|
||||
dao.compensation.selectedRequest=选定赔偿要求
|
||||
dao.compensation.active.notOpenAnymore=这笔补偿申请不再对资金开放。 请等到下一个资助期开始。
|
||||
dao.compensation.active.vote=在赔偿要求上投票
|
||||
dao.compensation.active.fund=为赔偿要求充值
|
||||
|
@ -1280,13 +1281,6 @@ LTC_TESTNET=莱特币测试网络
|
|||
# suppress inspection "UnusedProperty"
|
||||
LTC_REGTEST=莱特币回归测试
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
DOGE_MAINNET=狗狗币主干网络
|
||||
# suppress inspection "UnusedProperty"
|
||||
DOGE_TESTNET=狗狗币测试网络
|
||||
# suppress inspection "UnusedProperty"
|
||||
DOGE_REGTEST=狗狗币回归测试
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
DASH_MAINNET=达世币主干网络
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
@ -1355,7 +1349,6 @@ payment.restore.default=不,恢复默认值
|
|||
payment.email=电子邮箱:
|
||||
payment.country=国家:
|
||||
payment.extras=额外要求:
|
||||
payment.us.info=不支持美国银行使用WIRE或ACH方式的转账. 因为WIRE方式太昂贵而ACH方式有很高的撤销风险.\n请使用\"ClearXchange\", \"US Postal Money Order\" 或 \"Cash/ATM Deposit\" 支付方式.
|
||||
payment.email.mobile=电子邮箱或手机号码:
|
||||
payment.altcoin.address=数字货币地址:
|
||||
payment.altcoin=数字货币:
|
||||
|
@ -1363,7 +1356,7 @@ payment.select.altcoin=选择或搜索数字货币
|
|||
payment.secret=密保问题:
|
||||
payment.answer=答案:
|
||||
payment.wallet=钱包ID:
|
||||
payment.supported.okpay=支持的货币:
|
||||
payment.supportedCurrencies=支持的货币:
|
||||
payment.limitations=限制条件:
|
||||
payment.accept.euro=接受来自这些欧元国家的交易:
|
||||
payment.accept.nonEuro=接受来自这些非欧元国家的交易:
|
||||
|
|
|
@ -45,7 +45,6 @@ payment.email=Email:
|
|||
payment.country=Country:
|
||||
payment.owner.email=Account holder email:
|
||||
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.altcoin.address=Altcoin address:
|
||||
payment.address=address:
|
||||
|
@ -253,7 +252,8 @@ shared.sellingCurrency=Vendre {0} (Acheter des bitcoins)
|
|||
shared.buying=acheter
|
||||
shared.selling=vendre
|
||||
shared.P2P=De pair à pair
|
||||
shared.offers=Offres
|
||||
shared.oneOffer=offre
|
||||
shared.multipleOffers=offres
|
||||
shared.Offer=Offre
|
||||
shared.openOffers=Ouvrir les offres
|
||||
shared.openTrades=Opérations ouvertes
|
||||
|
@ -430,10 +430,10 @@ market.tabs.offerBook=Livre d'offre
|
|||
market.tabs.spread=Distribution
|
||||
market.tabs.trades=Échanges
|
||||
market.offerBook.chart.title=Livre d'offre pour {0}
|
||||
market.offerBook.leftButtonAltcoin=Je veux acheter {0} (vendre {1})
|
||||
market.offerBook.rightButtonAltcoin=Je veux vendre {0} (buy {1})
|
||||
market.offerBook.leftButtonFiat=Je veux acheter {0} avec {1}
|
||||
market.offerBook.rightButtonFiat=Je veux vendre {0} pour {1}
|
||||
market.offerBook.buyAltcoin=Je veux acheter {0} (vendre {1})
|
||||
market.offerBook.sellAltcoin=Je veux vendre {0} (buy {1})
|
||||
market.offerBook.buyWithFiat=Acheter {0}
|
||||
market.offerBook.sellWithFiat=Vendre {0}
|
||||
market.offerBook.sellOffersHeaderLabel=Vendre {0} à
|
||||
market.offerBook.buyOffersHeaderLabel=Acheter {0} à partir de
|
||||
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">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.5</version>
|
||||
<groupId>io.bisq.exchange</groupId>
|
||||
<version>-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
10
core/pom.xml
10
core/pom.xml
|
@ -5,25 +5,25 @@
|
|||
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.5</version>
|
||||
<groupId>io.bisq.exchange</groupId>
|
||||
<version>-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>core</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.bisq</groupId>
|
||||
<groupId>io.bisq.exchange</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.bisq</groupId>
|
||||
<groupId>io.bisq.exchange</groupId>
|
||||
<artifactId>consensus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.bisq</groupId>
|
||||
<groupId>io.bisq.exchange</groupId>
|
||||
<artifactId>network</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
|
|
@ -49,10 +49,7 @@ public class AlertManager {
|
|||
private final ObjectProperty<Alert> alertMessageProperty = new SimpleObjectProperty<>();
|
||||
|
||||
// Pub key for developer global alert message
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private static final String pubKeyAsHex = DevEnv.USE_DEV_PRIVILEGE_KEYS ?
|
||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||
"036d8a1dfcb406886037d2381da006358722823e1940acc2598c844bbc0fd1026f";
|
||||
private final String pubKeyAsHex;
|
||||
private ECKey alertSigningKey;
|
||||
|
||||
|
||||
|
@ -61,7 +58,11 @@ public class AlertManager {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@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.keyRing = keyRing;
|
||||
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<>();
|
||||
|
||||
// Pub key for developer global privateNotification message
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private static final String pubKeyAsHex = DevEnv.USE_DEV_PRIVILEGE_KEYS ?
|
||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||
"02ba7c5de295adfe57b60029f3637a2c6b1d0e969a8aaefb9e0ddc3a7963f26925";
|
||||
private final String pubKeyAsHex;
|
||||
|
||||
private ECKey privateNotificationSigningKey;
|
||||
private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
|
||||
|
@ -64,7 +61,10 @@ public class PrivateNotificationManager {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@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.keyRing = keyRing;
|
||||
|
||||
|
@ -72,6 +72,9 @@ public class PrivateNotificationManager {
|
|||
this.p2PService.addDecryptedDirectMessageListener(this::handleMessage);
|
||||
this.p2PService.addDecryptedMailboxListener(this::handleMessage);
|
||||
}
|
||||
pubKeyAsHex = useDevPrivilegeKeys ?
|
||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||
"02ba7c5de295adfe57b60029f3637a2c6b1d0e969a8aaefb9e0ddc3a7963f26925";
|
||||
}
|
||||
|
||||
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 DUMP_STATISTICS = "dumpStatistics";
|
||||
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 BooleanProperty p2pNetWorkReady;
|
||||
protected final TradeStatisticsManager tradeStatisticsManager;
|
||||
protected ArrayList<PersistedDataHost> persistedDataHosts;
|
||||
|
||||
@Inject
|
||||
public AppSetupWithP2P(EncryptionService encryptionService,
|
||||
|
@ -50,16 +51,16 @@ public class AppSetupWithP2P extends AppSetup {
|
|||
TradeStatisticsManager tradeStatisticsManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
FilterManager filterManager) {
|
||||
super(encryptionService,keyRing);
|
||||
super(encryptionService, keyRing);
|
||||
this.p2PService = p2PService;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.filterManager = filterManager;
|
||||
this.persistedDataHosts = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPersistedDataHosts() {
|
||||
ArrayList<PersistedDataHost> persistedDataHosts = new ArrayList<>();
|
||||
persistedDataHosts.add(p2PService);
|
||||
|
||||
// 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.core.dao.DaoManager;
|
||||
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.filter.FilterManager;
|
||||
import io.bisq.core.payment.AccountAgeWitnessService;
|
||||
import io.bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
|
@ -39,7 +40,8 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
|||
TradeStatisticsManager tradeStatisticsManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
FilterManager filterManager,
|
||||
DaoManager daoManager) {
|
||||
DaoManager daoManager,
|
||||
CompensationRequestManager compensationRequestManager) {
|
||||
super(encryptionService,
|
||||
keyRing,
|
||||
p2PService,
|
||||
|
@ -47,6 +49,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
|||
accountAgeWitnessService,
|
||||
filterManager);
|
||||
this.daoManager = daoManager;
|
||||
this.persistedDataHosts.add(compensationRequestManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -177,10 +177,10 @@ public class BisqEnvironment extends StandardEnvironment {
|
|||
@Getter
|
||||
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,
|
||||
myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress,
|
||||
socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, regTestBsqGenesisTxId;
|
||||
socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, genesisTxId, genesisBlockHeight;
|
||||
|
||||
|
||||
public BisqEnvironment(OptionSet options) {
|
||||
|
@ -212,6 +212,12 @@ public class BisqEnvironment extends StandardEnvironment {
|
|||
ignoreDevMsg = commandLineProperties.containsProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY) ?
|
||||
(String) commandLineProperties.getProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY) :
|
||||
"";
|
||||
useDevPrivilegeKeys = commandLineProperties.containsProperty(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) ?
|
||||
(String) commandLineProperties.getProperty(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) :
|
||||
"";
|
||||
useDevMode = commandLineProperties.containsProperty(AppOptionKeys.USE_DEV_MODE) ?
|
||||
(String) commandLineProperties.getProperty(AppOptionKeys.USE_DEV_MODE) :
|
||||
"";
|
||||
dumpStatistics = commandLineProperties.containsProperty(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) ?
|
||||
(String) commandLineProperties.getProperty(DaoOptionKeys.FULL_DAO_NODE) :
|
||||
"";
|
||||
regTestBsqGenesisTxId = commandLineProperties.containsProperty(DaoOptionKeys.REG_TEST_GENESIS_TX_ID) ?
|
||||
(String) commandLineProperties.getProperty(DaoOptionKeys.REG_TEST_GENESIS_TX_ID) :
|
||||
genesisTxId = commandLineProperties.containsProperty(DaoOptionKeys.GENESIS_TX_ID) ?
|
||||
(String) commandLineProperties.getProperty(DaoOptionKeys.GENESIS_TX_ID) :
|
||||
"";
|
||||
genesisBlockHeight = commandLineProperties.containsProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) ?
|
||||
(String) commandLineProperties.getProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) :
|
||||
"";
|
||||
|
||||
btcNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_NODES) ?
|
||||
|
@ -403,6 +412,8 @@ public class BisqEnvironment extends StandardEnvironment {
|
|||
|
||||
setProperty(AppOptionKeys.APP_DATA_DIR_KEY, appDataDir);
|
||||
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.APP_NAME_KEY, appName);
|
||||
setProperty(AppOptionKeys.MAX_MEMORY, maxMemory);
|
||||
|
@ -415,7 +426,8 @@ public class BisqEnvironment extends StandardEnvironment {
|
|||
setProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT, rpcBlockNotificationPort);
|
||||
setProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA, dumpBlockchainData);
|
||||
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.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))
|
||||
.withRequiredArg()
|
||||
.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,
|
||||
description("If set to true the trade statistics are stored as json file in the data dir.", false))
|
||||
.withRequiredArg()
|
||||
|
@ -188,10 +197,12 @@ public abstract class BisqExecutable {
|
|||
"set as well.", false))
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
parser.accepts(DaoOptionKeys.REG_TEST_GENESIS_TX_ID,
|
||||
description("Reg test BSQ genesis transaction id when not using the hard coded one", ""))
|
||||
parser.accepts(DaoOptionKeys.GENESIS_TX_ID,
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
public static BisqEnvironment getBisqEnvironment(OptionSet options) {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package io.bisq.core.arbitration;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.name.Named;
|
||||
import io.bisq.common.Timer;
|
||||
import io.bisq.common.UserThread;
|
||||
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.ResultHandler;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.app.AppOptionKeys;
|
||||
import io.bisq.core.filter.FilterManager;
|
||||
import io.bisq.core.user.Preferences;
|
||||
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 REPEATED_REPUBLISH_AT_STARTUP_SEC = 60;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
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"
|
||||
));
|
||||
private final List<String> publicKeys;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance fields
|
||||
|
@ -103,12 +86,36 @@ public class ArbitratorManager {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@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.arbitratorService = arbitratorService;
|
||||
this.user = user;
|
||||
this.preferences = preferences;
|
||||
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() {
|
||||
|
|
|
@ -20,7 +20,6 @@ package io.bisq.core.btc;
|
|||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import lombok.Getter;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
|
@ -36,10 +35,6 @@ public enum BaseCurrencyNetwork {
|
|||
LTC_TESTNET(LitecoinTestNet3Params.get(), "LTC", "TESTNET", "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_TESTNET(DashTestNet3Params.get(), "DASH", "TESTNET", "Dash"),
|
||||
DASH_REGTEST(DashRegTestParams.get(), "DASH", "REGTEST", "Dash");
|
||||
|
@ -80,21 +75,14 @@ public enum BaseCurrencyNetwork {
|
|||
return "DASH".equals(currencyCode);
|
||||
}
|
||||
|
||||
public boolean isDoge() {
|
||||
return "DOGE".equals(currencyCode);
|
||||
}
|
||||
|
||||
|
||||
public Coin getDefaultMinFee() {
|
||||
public long getDefaultMinFeePerByte() {
|
||||
switch (BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()) {
|
||||
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":
|
||||
return FeeService.LTC_REFERENCE_DEFAULT_MIN_TX_FEE;
|
||||
case "DOGE":
|
||||
return FeeService.DOGE_REFERENCE_DEFAULT_MIN_TX_FEE;
|
||||
return FeeService.LTC_REFERENCE_DEFAULT_MIN_TX_FEE.value;
|
||||
case "DASH":
|
||||
return FeeService.DASH_REFERENCE_DEFAULT_MIN_TX_FEE;
|
||||
return FeeService.DASH_REFERENCE_DEFAULT_MIN_TX_FEE.value;
|
||||
default:
|
||||
throw new RuntimeException("Unsupported code at getDefaultMinFee: " + BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode());
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ public enum RegTestHost {
|
|||
LOCALHOST,
|
||||
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";
|
||||
|
||||
}
|
||||
|
|
|
@ -54,9 +54,6 @@ public class Restrictions {
|
|||
case "LTC":
|
||||
MIN_TRADE_AMOUNT = Coin.valueOf(100_000); // 0.04 EUR @ 40 EUR/LTC
|
||||
break;
|
||||
case "DOGE":
|
||||
MIN_TRADE_AMOUNT = Coin.valueOf(1_000_000_000L); // 0.03 EUR at DOGE price 0.003 EUR;
|
||||
break;
|
||||
case "DASH":
|
||||
MIN_TRADE_AMOUNT = Coin.valueOf(20_000L); // 0.03 EUR at @ 150 EUR/DASH;
|
||||
break;
|
||||
|
@ -74,9 +71,6 @@ public class Restrictions {
|
|||
case "LTC":
|
||||
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(1_200_000_000); // 500 EUR @ 40 EUR/LTC
|
||||
break;
|
||||
case "DOGE":
|
||||
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(20_000_000_000_000L); // 500 EUR @ 0.0025 EUR/DOGE;
|
||||
break;
|
||||
case "DASH":
|
||||
MAX_BUYER_SECURITY_DEPOSIT = Coin.valueOf(300_000_000L); // 450 EUR @ 150 EUR/DASH;
|
||||
break;
|
||||
|
@ -94,9 +88,6 @@ public class Restrictions {
|
|||
case "LTC":
|
||||
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(6_000_000); // 2.4 EUR @ 40 EUR/LTC
|
||||
break;
|
||||
case "DOGE":
|
||||
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(100_000_000_000L); // 2.5 EUR @ 0.0025 EUR/DOGE;
|
||||
break;
|
||||
case "DASH":
|
||||
MIN_BUYER_SECURITY_DEPOSIT = Coin.valueOf(1_500_000L); // 2.5 EUR @ 150 EUR/DASH;
|
||||
break;
|
||||
|
@ -113,9 +104,6 @@ public class Restrictions {
|
|||
case "LTC":
|
||||
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(200_000_000); // 75 EUR @ 40 EUR/LTC
|
||||
break;
|
||||
case "DOGE":
|
||||
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(3_000_000_000_000L); // 75 EUR @ 0.0025 EUR/DOGE;
|
||||
break;
|
||||
case "DASH":
|
||||
DEFAULT_BUYER_SECURITY_DEPOSIT = Coin.valueOf(50_000_000L); // 75 EUR @ 150 EUR/DASH;
|
||||
break;
|
||||
|
@ -132,9 +120,6 @@ public class Restrictions {
|
|||
case "LTC":
|
||||
SELLER_SECURITY_DEPOSIT = Coin.valueOf(60_000_000); // 25 EUR @ 40 EUR/LTC
|
||||
break;
|
||||
case "DOGE":
|
||||
SELLER_SECURITY_DEPOSIT = Coin.valueOf(1_000_000_000_000L); // 25 EUR @ 0.0025 EUR/DOGE;
|
||||
break;
|
||||
case "DASH":
|
||||
SELLER_SECURITY_DEPOSIT = Coin.valueOf(15_000_000L); // 25 EUR @ 150 EUR/DASH;
|
||||
break;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
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 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.exceptions.TransactionVerificationException;
|
||||
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.BsqBlockChainListener;
|
||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
||||
import io.bisq.core.dao.blockchain.vo.Tx;
|
||||
import io.bisq.core.dao.blockchain.vo.TxOutput;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
|
@ -396,7 +396,7 @@ public class BsqWalletService extends WalletService implements BsqBlockChainList
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction getPreparedBurnFeeTx(Coin fee) throws
|
||||
InsufficientBsqException, ChangeBelowDustException {
|
||||
InsufficientBsqException {
|
||||
Transaction tx = new Transaction(params);
|
||||
|
||||
// We might have no output if inputs match fee.
|
||||
|
@ -405,7 +405,7 @@ public class BsqWalletService extends WalletService implements BsqBlockChainList
|
|||
|
||||
// TODO check dust output
|
||||
CoinSelection coinSelection = bsqCoinSelector.select(fee, wallet.calculateAllSpendCandidates());
|
||||
coinSelection.gathered.stream().forEach(tx::addInput);
|
||||
coinSelection.gathered.forEach(tx::addInput);
|
||||
try {
|
||||
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
|
||||
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.Futures;
|
||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.btc.*;
|
||||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import io.bisq.core.btc.exceptions.WalletException;
|
||||
|
@ -136,21 +135,21 @@ public class BtcWalletService extends WalletService {
|
|||
|
||||
// preparedCompensationRequestTx has following structure:
|
||||
// inputs [1-n] BSQ inputs for request fee
|
||||
// inputs [1-n] BTC inputs for BSQ issuance and miner fee
|
||||
// outputs [0-1] BSQ request fee change output
|
||||
// outputs [1] BSQ issuance output
|
||||
// outputs [0-1] BTC change output from issuance and miner fee inputs
|
||||
// outputs [0-1] OP_RETURN with opReturnData
|
||||
// inputs [1-n] BTC inputs for BSQ issuance and miner fee
|
||||
// outputs [0-1] BSQ request fee change output (>= 2730 Satoshi)
|
||||
// outputs [1] Potentially BSQ issuance output (>= 2730 Satoshi)
|
||||
// outputs [0-1] BTC change output from issuance and miner fee inputs (>= 2730 Satoshi)
|
||||
// outputs [0-1] OP_RETURN with opReturnData and amount 0
|
||||
// mining fee: BTC mining fee + burned BSQ fee
|
||||
|
||||
Transaction preparedTx = new Transaction(params);
|
||||
// Copy inputs from BSQ fee tx
|
||||
feeTx.getInputs().stream().forEach(preparedTx::addInput);
|
||||
feeTx.getInputs().forEach(preparedTx::addInput);
|
||||
int indexOfBtcFirstInput = feeTx.getInputs().size();
|
||||
|
||||
// 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.
|
||||
feeTx.getOutputs().stream().forEach(preparedTx::addOutput);
|
||||
feeTx.getOutputs().forEach(preparedTx::addOutput);
|
||||
|
||||
// BSQ issuance output
|
||||
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
|
||||
Transaction resultTx = null;
|
||||
boolean isFeeOutsideTolerance;
|
||||
boolean opReturnIsOnlyOutput;
|
||||
do {
|
||||
counter++;
|
||||
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
|
||||
// a change 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
|
||||
if (opReturnData != null)
|
||||
|
@ -343,7 +344,9 @@ public class BtcWalletService extends WalletService {
|
|||
// calculated fee must be inside of a tolerance range with tx fee
|
||||
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
|
||||
for (int i = preparedBsqTxInputs.size(); i < resultTx.getInputs().size(); i++) {
|
||||
|
@ -603,10 +606,6 @@ public class BtcWalletService extends WalletService {
|
|||
do {
|
||||
counter++;
|
||||
fee = txFeeForWithdrawalPerByte.multiply(txSize);
|
||||
final Coin defaultMinFee = BisqEnvironment.getBaseCurrencyNetwork().getDefaultMinFee();
|
||||
if (fee.compareTo(defaultMinFee) < 0)
|
||||
fee = defaultMinFee;
|
||||
|
||||
newTransaction.clearOutputs();
|
||||
newTransaction.addOutput(amount.subtract(fee), toAddress);
|
||||
|
||||
|
@ -621,9 +620,10 @@ public class BtcWalletService extends WalletService {
|
|||
tx = sendRequest.tx;
|
||||
txSize = tx.bitcoinSerialize().length;
|
||||
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)
|
||||
log.error("Could not calculate the fee. Tx=" + tx);
|
||||
|
||||
|
@ -722,17 +722,13 @@ public class BtcWalletService extends WalletService {
|
|||
do {
|
||||
counter++;
|
||||
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);
|
||||
wallet.completeTx(sendRequest);
|
||||
tx = sendRequest.tx;
|
||||
txSize = tx.bitcoinSerialize().length;
|
||||
printTx("FeeEstimationTransaction", tx);
|
||||
}
|
||||
while (counter < 10 && Math.abs(tx.getFee().value - txFeeForWithdrawalPerByte.multiply(txSize).value) > 1000);
|
||||
while (feeEstimationNotSatisfied(counter, tx));
|
||||
if (counter == 10)
|
||||
log.error("Could not calculate the fee. Tx=" + tx);
|
||||
|
||||
|
@ -773,9 +769,6 @@ public class BtcWalletService extends WalletService {
|
|||
do {
|
||||
counter++;
|
||||
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
|
||||
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddressString(), amount, fee, null, aesKey);
|
||||
wallet.completeTx(sendRequest);
|
||||
|
@ -783,7 +776,7 @@ public class BtcWalletService extends WalletService {
|
|||
txSize = tx.bitcoinSerialize().length;
|
||||
printTx("FeeEstimationTransactionForMultipleAddresses", tx);
|
||||
}
|
||||
while (counter < 10 && Math.abs(tx.getFee().value - txFeeForWithdrawalPerByte.multiply(txSize).value) > 1000);
|
||||
while (feeEstimationNotSatisfied(counter, tx));
|
||||
if (counter == 10)
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -873,7 +874,7 @@ public class BtcWalletService extends WalletService {
|
|||
"The amount is too low (dust limit).");
|
||||
|
||||
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.");
|
||||
|
||||
tx.addOutput(netValue, Address.fromBase58(params, toAddress));
|
||||
|
|
|
@ -427,7 +427,7 @@ public abstract class WalletService {
|
|||
return outputs;
|
||||
}
|
||||
|
||||
Coin getTxFeeForWithdrawalPerByte() {
|
||||
public Coin getTxFeeForWithdrawalPerByte() {
|
||||
Coin fee = (preferences.isUseCustomWithdrawalTxFee()) ?
|
||||
Coin.valueOf(preferences.getWithdrawalTxFeeInBytes()) :
|
||||
feeService.getTxFeePerByte();
|
||||
|
|
|
@ -196,9 +196,18 @@ public class WalletsSetup {
|
|||
};
|
||||
|
||||
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()) {
|
||||
configPeerNodesForLocalHostBitcoinNode();
|
||||
walletConfig.setMinBroadcastConnections(1);
|
||||
walletConfig.setPeerNodesForLocalHost();
|
||||
} else {
|
||||
configPeerNodes(socks5Proxy);
|
||||
}
|
||||
|
@ -268,26 +277,16 @@ public class WalletsSetup {
|
|||
return mode;
|
||||
}
|
||||
|
||||
private void configPeerNodesForRegTest() {
|
||||
walletConfig.setMinBroadcastConnections(1);
|
||||
if (regTestHost == RegTestHost.REG_TEST_SERVER) {
|
||||
try {
|
||||
walletConfig.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
|
||||
} catch (UnknownHostException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (regTestHost == RegTestHost.LOCALHOST) {
|
||||
walletConfig.setPeerNodesForLocalHost();
|
||||
private void configPeerNodesForRegTestServer() {
|
||||
try {
|
||||
walletConfig.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
|
||||
} catch (UnknownHostException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void configPeerNodesForLocalHostBitcoinNode() {
|
||||
walletConfig.setMinBroadcastConnections(1);
|
||||
walletConfig.setPeerNodesForLocalHost();
|
||||
}
|
||||
|
||||
private void configPeerNodes(@Nullable Socks5Proxy proxy) {
|
||||
WalletSetupPreferences walletSetupPreferences = new WalletSetupPreferences(preferences);
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ package io.bisq.core.dao;
|
|||
import com.google.inject.Inject;
|
||||
import io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.dao.blockchain.BsqNode;
|
||||
import io.bisq.core.dao.blockchain.BsqNodeProvider;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.node.BsqNode;
|
||||
import io.bisq.core.dao.node.BsqNodeProvider;
|
||||
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.vote.VotingManager;
|
||||
|
||||
/**
|
||||
|
@ -58,4 +58,11 @@ public class DaoManager {
|
|||
bsqNode.onAllServicesInitialized(errorMessageHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
daoPeriodService.shutDown();
|
||||
voteManager.shutDown();
|
||||
compensationRequestManager.shutDown();
|
||||
bsqNode.shutDown();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,23 @@
|
|||
package io.bisq.core.dao;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.name.Names;
|
||||
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.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.parse.*;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.node.BsqNodeProvider;
|
||||
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.VotingManager;
|
||||
import io.bisq.core.dao.vote.VotingService;
|
||||
|
@ -43,16 +52,26 @@ public class DaoModule extends AppModule {
|
|||
protected void configure() {
|
||||
bind(DaoManager.class).in(Singleton.class);
|
||||
|
||||
bind(BsqLiteNode.class).in(Singleton.class);
|
||||
bind(BsqFullNode.class).in(Singleton.class);
|
||||
bind(LiteNodeNetworkManager.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(BsqBlockChain.class).in(Singleton.class);
|
||||
bind(BsqFullNodeExecutor.class).in(Singleton.class);
|
||||
bind(BsqLiteNodeExecutor.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(CompensationRequestVerification.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));
|
||||
bindConstant().annotatedWith(named(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;
|
||||
|
||||
/**
|
||||
* Provides program argument options used in the DAO domain.
|
||||
*/
|
||||
public class DaoOptionKeys {
|
||||
public static final String RPC_USER = "rpcUser";
|
||||
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 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 io.bisq.common.app.DevEnv;
|
||||
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.compensation.CompensationRequest;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestPayload;
|
||||
import io.bisq.core.dao.vote.VotingDefaultValues;
|
||||
import io.bisq.core.dao.vote.VotingService;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
|
@ -32,6 +30,8 @@ import javafx.beans.property.SimpleObjectProperty;
|
|||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -84,6 +84,7 @@ public class DaoPeriodService {
|
|||
private BsqBlockChain bsqBlockChain;
|
||||
private final VotingDefaultValues votingDefaultValues;
|
||||
private final VotingService votingService;
|
||||
private final int genesisBlockHeight;
|
||||
@Getter
|
||||
private ObjectProperty<Phase> phaseProperty = new SimpleObjectProperty<>(Phase.UNDEFINED);
|
||||
private int chainHeight;
|
||||
|
@ -97,13 +98,13 @@ public class DaoPeriodService {
|
|||
public DaoPeriodService(BtcWalletService btcWalletService,
|
||||
BsqBlockChain bsqBlockChain,
|
||||
VotingDefaultValues votingDefaultValues,
|
||||
VotingService votingService) {
|
||||
VotingService votingService,
|
||||
@Named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) int genesisBlockHeight) {
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.bsqBlockChain = bsqBlockChain;
|
||||
this.votingDefaultValues = votingDefaultValues;
|
||||
this.votingService = votingService;
|
||||
|
||||
|
||||
this.genesisBlockHeight = genesisBlockHeight;
|
||||
}
|
||||
|
||||
|
||||
|
@ -111,6 +112,10 @@ public class DaoPeriodService {
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void shutDown() {
|
||||
|
||||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
btcWalletService.getChainHeightProperty().addListener((observable, oldValue, newValue) -> {
|
||||
onChainHeightChanged((int) newValue);
|
||||
|
@ -118,54 +123,54 @@ public class DaoPeriodService {
|
|||
onChainHeightChanged(btcWalletService.getChainHeightProperty().get());
|
||||
}
|
||||
|
||||
public boolean isInPhase(CompensationRequestPayload compensationRequestPayload, Phase phase) {
|
||||
Tx tx = bsqBlockChain.getTxMap().get(compensationRequestPayload.getTxId());
|
||||
return tx != null && isTxHeightInPhase(tx.getBlockHeight(),
|
||||
public boolean isTxInPhase(String txId, Phase phase) {
|
||||
Tx tx = bsqBlockChain.getTxMap().get(txId);
|
||||
return tx != null && isTxInPhase(tx.getBlockHeight(),
|
||||
chainHeight,
|
||||
BsqBlockChain.getGenesisHeight(),
|
||||
genesisBlockHeight,
|
||||
phase.getDurationInBlocks(),
|
||||
getNumBlocksOfCycle());
|
||||
}
|
||||
|
||||
public boolean isInCurrentCycle(CompensationRequest compensationRequest) {
|
||||
Tx tx = bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId());
|
||||
return tx != null && isInCurrentCycle(tx.getBlockHeight(),
|
||||
public boolean isTxInCurrentCycle(String txId) {
|
||||
Tx tx = bsqBlockChain.getTxMap().get(txId);
|
||||
return tx != null && isTxInCurrentCycle(tx.getBlockHeight(),
|
||||
chainHeight,
|
||||
BsqBlockChain.getGenesisHeight(),
|
||||
genesisBlockHeight,
|
||||
getNumBlocksOfCycle());
|
||||
}
|
||||
|
||||
public boolean isInPastCycle(CompensationRequest compensationRequest) {
|
||||
Tx tx = bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId());
|
||||
return tx != null && isInPastCycle(tx.getBlockHeight(),
|
||||
public boolean isTxInPastCycle(String txId) {
|
||||
Tx tx = bsqBlockChain.getTxMap().get(txId);
|
||||
return tx != null && isTxInPastCycle(tx.getBlockHeight(),
|
||||
chainHeight,
|
||||
BsqBlockChain.getGenesisHeight(),
|
||||
genesisBlockHeight,
|
||||
getNumBlocksOfCycle());
|
||||
}
|
||||
|
||||
public int getNumOfStartedCycles(int chainHeight) {
|
||||
return getNumOfStartedCycles(chainHeight,
|
||||
BsqBlockChain.getGenesisHeight(),
|
||||
genesisBlockHeight,
|
||||
getNumBlocksOfCycle());
|
||||
}
|
||||
|
||||
// Not used yet be leave it
|
||||
public int getNumOfCompletedCycles(int chainHeight) {
|
||||
return getNumOfCompletedCycles(chainHeight,
|
||||
BsqBlockChain.getGenesisHeight(),
|
||||
genesisBlockHeight,
|
||||
getNumBlocksOfCycle());
|
||||
}
|
||||
|
||||
public int getAbsoluteStartBlockOfPhase(int chainHeight, Phase phase) {
|
||||
return getAbsoluteStartBlockOfPhase(chainHeight,
|
||||
BsqBlockChain.getGenesisHeight(),
|
||||
genesisBlockHeight,
|
||||
phase,
|
||||
getNumBlocksOfCycle());
|
||||
}
|
||||
|
||||
public int getAbsoluteEndBlockOfPhase(int chainHeight, Phase phase) {
|
||||
return getAbsoluteEndBlockOfPhase(chainHeight,
|
||||
BsqBlockChain.getGenesisHeight(),
|
||||
genesisBlockHeight,
|
||||
phase,
|
||||
getNumBlocksOfCycle());
|
||||
}
|
||||
|
@ -185,7 +190,7 @@ public class DaoPeriodService {
|
|||
|
||||
private void onChainHeightChanged(int 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));
|
||||
}
|
||||
|
||||
|
@ -226,7 +231,7 @@ public class DaoPeriodService {
|
|||
return Phase.BREAK3;
|
||||
else {
|
||||
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);
|
||||
else
|
||||
return Phase.UNDEFINED;
|
||||
|
@ -234,7 +239,7 @@ public class DaoPeriodService {
|
|||
}
|
||||
|
||||
@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) {
|
||||
int numBlocksOfTxHeightSinceGenesis = txHeight - genesisHeight;
|
||||
int numBlocksOfChainHeightSinceGenesis = chainHeight - genesisHeight;
|
||||
|
@ -247,7 +252,7 @@ public class DaoPeriodService {
|
|||
}
|
||||
|
||||
@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 blockAtCycleStart = genesisHeight + numOfCompletedCycles * numBlocksOfCycle;
|
||||
final int blockAtCycleEnd = blockAtCycleStart + numBlocksOfCycle - 1;
|
||||
|
@ -258,7 +263,7 @@ public class DaoPeriodService {
|
|||
}
|
||||
|
||||
@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 blockAtCycleStart = genesisHeight + numOfCompletedCycles * numBlocksOfCycle;
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.dao.blockchain.parse;
|
||||
package io.bisq.core.dao.blockchain;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
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.util.FunctionalReadWriteLock;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.dao.DaoOptionKeys;
|
||||
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 lombok.Getter;
|
||||
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 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
|
||||
// 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.
|
||||
|
||||
// BTC MAIN NET
|
||||
private static final String BTC_GENESIS_TX_ID = "e5c8313c4144d219b5f6b2dacf1d36f2d43a9039bb2fcd1bd57f8352a9c9809a";
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
public static final String BTC_GENESIS_TX_ID = "e5c8313c4144d219b5f6b2dacf1d36f2d43a9039bb2fcd1bd57f8352a9c9809a";
|
||||
public static final int BTC_GENESIS_BLOCK_HEIGHT = 477865; // 2017-07-28
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -139,31 +117,19 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
@Inject
|
||||
public BsqBlockChain(PersistenceProtoResolver persistenceProtoResolver,
|
||||
@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<>();
|
||||
txMap = new HashMap<>();
|
||||
unspentTxOutputsMap = new HashMap<>();
|
||||
compensationRequestFees = 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);
|
||||
}
|
||||
|
||||
|
@ -285,14 +251,14 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
// Package scope write access
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void addBlock(BsqBlock block) throws BlockNotConnectingException {
|
||||
public void addBlock(BsqBlock block) throws BlockNotConnectingException {
|
||||
try {
|
||||
lock.write2(() -> {
|
||||
if (!bsqBlocks.contains(block)) {
|
||||
if (bsqBlocks.isEmpty() || (bsqBlocks.getLast().getHash().equals(block.getPreviousBlockHash()) &&
|
||||
bsqBlocks.getLast().getHeight() + 1 == block.getHeight())) {
|
||||
bsqBlocks.add(block);
|
||||
block.getTxs().stream().forEach(BsqBlockChain.this::addTxToMap);
|
||||
block.getTxs().forEach(BsqBlockChain.this::addTxToMap);
|
||||
chainHeadHeight = block.getHeight();
|
||||
maybeMakeSnapshot();
|
||||
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));
|
||||
}
|
||||
|
||||
void addUnspentTxOutput(TxOutput txOutput) {
|
||||
public void addUnspentTxOutput(TxOutput txOutput) {
|
||||
lock.write(() -> {
|
||||
checkArgument(txOutput.isVerified(), "txOutput must be verified at addUnspentTxOutput");
|
||||
unspentTxOutputsMap.put(txOutput.getTxIdIndexTuple(), txOutput);
|
||||
});
|
||||
}
|
||||
|
||||
void removeUnspentTxOutput(TxOutput txOutput) {
|
||||
public void removeUnspentTxOutput(TxOutput txOutput) {
|
||||
lock.write(() -> unspentTxOutputsMap.remove(txOutput.getTxIdIndexTuple()));
|
||||
}
|
||||
|
||||
void setGenesisTx(Tx tx) {
|
||||
public void setGenesisTx(Tx tx) {
|
||||
lock.write(() -> genesisTx = tx);
|
||||
}
|
||||
|
||||
|
@ -352,7 +318,7 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
return getClone(this);
|
||||
}
|
||||
|
||||
public BsqBlockChain getClone(BsqBlockChain bsqBlockChain) {
|
||||
private BsqBlockChain getClone(BsqBlockChain bsqBlockChain) {
|
||||
return lock.read(() -> (BsqBlockChain) BsqBlockChain.fromProto(bsqBlockChain.getBsqBlockChainBuilder().build()));
|
||||
}
|
||||
|
||||
|
@ -360,7 +326,7 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
return lock.read(() -> bsqBlocks.contains(bsqBlock));
|
||||
}
|
||||
|
||||
Optional<TxOutput> getUnspentTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
||||
private Optional<TxOutput> getUnspentTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
||||
return lock.read(() -> unspentTxOutputsMap.entrySet().stream()
|
||||
.filter(e -> e.getKey().equals(txIdIndexTuple))
|
||||
.map(Map.Entry::getValue).findAny());
|
||||
|
@ -387,7 +353,7 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
if (tx != null)
|
||||
return Optional.of(tx);
|
||||
else
|
||||
return Optional.<Tx>empty();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public int getChainHeadHeight() {
|
||||
|
@ -398,13 +364,13 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
return lock.read(() -> txMap);
|
||||
}
|
||||
|
||||
public List<BsqBlock> getResettedBlocksFrom(int fromBlockHeight) {
|
||||
public List<BsqBlock> getResetBlocksFrom(int fromBlockHeight) {
|
||||
return lock.read(() -> {
|
||||
BsqBlockChain clone = getClone();
|
||||
List<BsqBlock> filtered = clone.bsqBlocks.stream()
|
||||
.filter(block -> block.getHeight() >= fromBlockHeight)
|
||||
.collect(Collectors.toList());
|
||||
filtered.stream().forEach(BsqBlock::reset);
|
||||
filtered.forEach(BsqBlock::reset);
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
|
@ -438,16 +404,16 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
// 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)));
|
||||
}
|
||||
|
||||
Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
||||
public Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
|
||||
return lock.read(() -> getUnspentTxOutput(txIdIndexTuple)
|
||||
.filter(this::isTxOutputMature));
|
||||
}
|
||||
|
||||
long getCreateCompensationRequestFee(int blockHeight) {
|
||||
public long getCreateCompensationRequestFee(int blockHeight) {
|
||||
return lock.read(() -> {
|
||||
long fee = -1;
|
||||
for (Tuple2<Long, Integer> feeAtHeight : compensationRequestFees) {
|
||||
|
@ -460,46 +426,44 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
}
|
||||
|
||||
//TODO not impl yet
|
||||
boolean isCompensationRequestPeriodValid(int blockHeight) {
|
||||
public boolean isCompensationRequestPeriodValid(int blockHeight) {
|
||||
return lock.read(() -> true);
|
||||
|
||||
}
|
||||
|
||||
long getVotingFee(int blockHeight) {
|
||||
public long getVotingFee(int blockHeight) {
|
||||
return lock.read(() -> {
|
||||
long fee = -1;
|
||||
for (Tuple2<Long, Integer> feeAtHeight : votingFees) {
|
||||
if (feeAtHeight.second <= blockHeight)
|
||||
fee = feeAtHeight.first;
|
||||
}
|
||||
checkArgument(fee > -1, "compensationRequestFees must be set");
|
||||
checkArgument(fee > -1, "votingFee must be set");
|
||||
return fee;
|
||||
});
|
||||
}
|
||||
|
||||
//TODO not impl yet
|
||||
boolean isVotingPeriodValid(int blockHeight) {
|
||||
public boolean isVotingPeriodValid(int blockHeight) {
|
||||
return lock.read(() -> true);
|
||||
}
|
||||
|
||||
boolean existsCompensationRequestBtcAddress(String btcAddress) {
|
||||
public boolean existsCompensationRequestBtcAddress(String btcAddress) {
|
||||
return lock.read(() -> getAllTxOutputs().stream()
|
||||
.filter(txOutput -> txOutput.isCompensationRequestBtcOutput() &&
|
||||
txOutput.getAddress().equals(btcAddress))
|
||||
.findAny()
|
||||
.isPresent());
|
||||
.anyMatch(txOutput -> txOutput.isCompensationRequestBtcOutput() &&
|
||||
btcAddress.equals(txOutput.getAddress())));
|
||||
}
|
||||
|
||||
Set<TxOutput> findSponsoringBtcOutputsWithSameBtcAddress(String btcAddress) {
|
||||
public Set<TxOutput> findSponsoringBtcOutputsWithSameBtcAddress(String btcAddress) {
|
||||
return lock.read(() -> getAllTxOutputs().stream()
|
||||
.filter(txOutput -> txOutput.isSponsoringBtcOutput() &&
|
||||
txOutput.getAddress().equals(btcAddress))
|
||||
btcAddress.equals(txOutput.getAddress()))
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
//TODO
|
||||
// for genesis we dont need it and for issuance we need more implemented first
|
||||
boolean isTxOutputMature(TxOutput spendingTxOutput) {
|
||||
// for genesis we don't need it and for issuance we need more implemented first
|
||||
private boolean isTxOutputMature(TxOutput spendingTxOutput) {
|
||||
return lock.read(() -> true);
|
||||
}
|
||||
|
||||
|
@ -527,12 +491,12 @@ public class BsqBlockChain implements PersistableEnvelope {
|
|||
final BsqBlockChain cloned = getClone(snapshotCandidate);
|
||||
checkNotNull(storage, "storage must not be null");
|
||||
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);
|
||||
}
|
||||
// Now we clone and keep it in memory for the next trigger
|
||||
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);
|
||||
}
|
||||
});
|
|
@ -25,6 +25,8 @@ import java.util.List;
|
|||
/**
|
||||
* Passing the BsqNode directly to the classes interested in onBsqBlockChainChanged events cause Guice dependency issues,
|
||||
* so we use that object to isolate that concern.
|
||||
*
|
||||
* TODO check if refactorings has solved the dependency problems.
|
||||
*/
|
||||
@Slf4j
|
||||
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.util.Utilities;
|
||||
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.TxOutput;
|
||||
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;
|
||||
|
||||
import io.bisq.common.app.Version;
|
||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
import lombok.Data;
|
||||
|
@ -30,15 +31,30 @@ import java.util.stream.Collectors;
|
|||
|
||||
@Data
|
||||
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<TxOutput> outputs;
|
||||
|
||||
private long burntFee;
|
||||
private TxType txType;
|
||||
|
||||
public Tx(TxVo txVo, List<TxInput> inputs, List<TxOutput> outputs) {
|
||||
this(txVo, inputs, outputs, 0, null);
|
||||
public Tx(String id, int blockHeight,
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Tx(TxVo txVo, List<TxInput> inputs, List<TxOutput> outputs, long burntFee, @Nullable TxType txType) {
|
||||
this.txVo = txVo;
|
||||
private Tx(String txVersion,
|
||||
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.outputs = outputs;
|
||||
this.burntFee = burntFee;
|
||||
|
@ -56,7 +84,11 @@ public class Tx implements PersistablePayload {
|
|||
|
||||
public PB.Tx toProtoMessage() {
|
||||
final PB.Tx.Builder builder = PB.Tx.newBuilder()
|
||||
.setTxVo(txVo.toProtoMessage())
|
||||
.setTxVersion(txVersion)
|
||||
.setId(id)
|
||||
.setBlockHeight(blockHeight)
|
||||
.setBlockHash(blockHash)
|
||||
.setTime(time)
|
||||
.addAllInputs(inputs.stream()
|
||||
.map(TxInput::toProtoMessage)
|
||||
.collect(Collectors.toList()))
|
||||
|
@ -71,7 +103,11 @@ public class Tx implements PersistablePayload {
|
|||
}
|
||||
|
||||
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() ?
|
||||
new ArrayList<>() :
|
||||
proto.getInputsList().stream()
|
||||
|
@ -92,53 +128,28 @@ public class Tx implements PersistablePayload {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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() {
|
||||
burntFee = 0;
|
||||
txType = TxType.UNDEFINED_TX_TYPE;
|
||||
inputs.stream().forEach(TxInput::reset);
|
||||
outputs.stream().forEach(TxOutput::reset);
|
||||
inputs.forEach(TxInput::reset);
|
||||
outputs.forEach(TxOutput::reset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Tx{" +
|
||||
"\ntxVersion='" + getTxVersion() + '\'' +
|
||||
",\nid='" + getId() + '\'' +
|
||||
",\nblockHeight=" + getBlockHeight() +
|
||||
",\nblockHash=" + getBlockHash() +
|
||||
",\ntime=" + new Date(getTime()) +
|
||||
"\ntxVersion='" + txVersion + '\'' +
|
||||
",\nid='" + id + '\'' +
|
||||
",\nblockHeight=" + blockHeight +
|
||||
",\nblockHash=" + blockHash +
|
||||
",\ntime=" + new Date(time) +
|
||||
",\ninputs=" + inputs +
|
||||
",\noutputs=" + outputs +
|
||||
",\nburntFee=" + burntFee +
|
||||
",\ntxType=" + txType +
|
||||
"}\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;
|
||||
|
||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||
import io.bisq.core.dao.blockchain.vo.util.TxIdIndexTuple;
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
import lombok.Data;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.google.protobuf.ByteString;
|
|||
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||
import io.bisq.common.util.JsonExclude;
|
||||
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
|
||||
import io.bisq.core.dao.blockchain.vo.util.TxIdIndexTuple;
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
|
@ -24,10 +24,10 @@ public enum TxOutputType {
|
|||
UNDEFINED,
|
||||
BSQ_OUTPUT,
|
||||
BTC_OUTPUT,
|
||||
OP_RETURN_OUTPUT,
|
||||
COMPENSATION_REQUEST_OP_RETURN_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,
|
||||
COMPENSATION_REQUEST,
|
||||
VOTE,
|
||||
VOTE_REVEAL,
|
||||
ISSUANCE,
|
||||
LOCK_UP,
|
||||
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/>.
|
||||
*/
|
||||
|
||||
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.generated.protobuffer.PB;
|
|
@ -15,45 +15,37 @@
|
|||
* 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 io.bisq.common.handlers.ErrorMessageHandler;
|
||||
import io.bisq.core.dao.blockchain.p2p.RequestManager;
|
||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
||||
import io.bisq.core.dao.blockchain.parse.BsqParser;
|
||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import io.bisq.network.p2p.P2PServiceListener;
|
||||
import io.bisq.network.p2p.seed.SeedNodeRepository;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
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.
|
||||
/**
|
||||
* 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
|
||||
public abstract class BsqNode {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Class fields
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected final P2PService p2PService;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected final BsqParser bsqParser;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected final BsqBlockChain bsqBlockChain;
|
||||
protected final SeedNodeRepository seedNodeRepository;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected final List<BsqBlockChainListener> bsqBlockChainListeners = new ArrayList<>();
|
||||
protected final String genesisTxId;
|
||||
protected final int genesisBlockHeight;
|
||||
protected RequestManager requestManager;
|
||||
|
||||
private final String genesisTxId;
|
||||
private final int genesisBlockHeight;
|
||||
@Getter
|
||||
protected boolean parseBlockchainComplete;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
|
@ -66,15 +58,11 @@ public abstract class BsqNode {
|
|||
@SuppressWarnings("WeakerAccess")
|
||||
@Inject
|
||||
public BsqNode(P2PService p2PService,
|
||||
BsqParser bsqParser,
|
||||
BsqBlockChain bsqBlockChain,
|
||||
FeeService feeService,
|
||||
SeedNodeRepository seedNodeRepository) {
|
||||
FeeService feeService) {
|
||||
|
||||
this.p2PService = p2PService;
|
||||
this.bsqParser = bsqParser;
|
||||
this.bsqBlockChain = bsqBlockChain;
|
||||
this.seedNodeRepository = seedNodeRepository;
|
||||
|
||||
genesisTxId = bsqBlockChain.getGenesisTxId();
|
||||
genesisBlockHeight = bsqBlockChain.getGenesisBlockHeight();
|
||||
|
@ -90,7 +78,25 @@ public abstract class BsqNode {
|
|||
// 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();
|
||||
log.info("onAllServicesInitialized");
|
||||
if (p2PService.isBootstrapped()) {
|
||||
|
@ -137,19 +143,14 @@ public abstract class BsqNode {
|
|||
}
|
||||
}
|
||||
|
||||
private void applySnapshot() {
|
||||
bsqBlockChain.applySnapshot();
|
||||
bsqBlockChainListeners.stream().forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected void onP2PNetworkReady() {
|
||||
p2pNetworkReady = true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected void startParseBlocks() {
|
||||
int startBlockHeight = Math.max(genesisBlockHeight, bsqBlockChain.getChainHeadHeight() + 1);
|
||||
protected int getStartBlockHeight() {
|
||||
final int startBlockHeight = Math.max(genesisBlockHeight, bsqBlockChain.getChainHeadHeight() + 1);
|
||||
log.info("Start parse blocks:\n" +
|
||||
" Start block height={}\n" +
|
||||
" Genesis txId={}\n" +
|
||||
|
@ -160,22 +161,13 @@ public abstract class BsqNode {
|
|||
genesisBlockHeight,
|
||||
bsqBlockChain.getChainHeadHeight());
|
||||
|
||||
parseBlocksWithChainHeadHeight(startBlockHeight,
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
return startBlockHeight;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
protected void notifyListenersOnNewBlock() {
|
||||
bsqBlockChainListeners.forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
|
@ -184,11 +176,13 @@ public abstract class BsqNode {
|
|||
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/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.dao.blockchain;
|
||||
package io.bisq.core.dao.node;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
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.extern.slf4j.Slf4j;
|
||||
|
||||
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
|
||||
public class BsqNodeProvider {
|
||||
|
@ -33,8 +36,8 @@ public class BsqNodeProvider {
|
|||
private final BsqNode bsqNode;
|
||||
|
||||
@Inject
|
||||
public BsqNodeProvider(BsqLiteNode bsqLiteNode,
|
||||
BsqFullNode bsqFullNode,
|
||||
public BsqNodeProvider(LiteNode bsqLiteNode,
|
||||
FullNode bsqFullNode,
|
||||
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
|
||||
@Named(DaoOptionKeys.FULL_DAO_NODE) boolean fullDaoNode) {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.dao.blockchain.parse;
|
||||
package io.bisq.core.dao.node.consensus;
|
||||
|
||||
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.TxOutput;
|
||||
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
||||
|
@ -25,6 +26,9 @@ import io.bisq.core.dao.blockchain.vo.TxType;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Verifies if OP_RETURN data matches rules for a compensation request tx and applies state change.
|
||||
*/
|
||||
public class CompensationRequestVerification {
|
||||
private final BsqBlockChain bsqBlockChain;
|
||||
|
||||
|
@ -33,17 +37,17 @@ public class CompensationRequestVerification {
|
|||
this.bsqBlockChain = bsqBlockChain;
|
||||
}
|
||||
|
||||
boolean processOpReturnData(Tx tx, byte[] opReturnData, TxOutput opReturnTxOutput, long bsqFee, int blockHeight, TxOutput btcTxOutput) {
|
||||
if (btcTxOutput != null &&
|
||||
opReturnData.length > 1 &&
|
||||
public boolean verify(byte[] opReturnData, long bsqFee, int blockHeight, TxOutputsVerification.MutableState mutableState) {
|
||||
return mutableState.getCompRequestIssuanceOutputCandidate() != null &&
|
||||
opReturnData.length == 22 &&
|
||||
Version.COMPENSATION_REQUEST_VERSION == opReturnData[1] &&
|
||||
bsqFee == bsqBlockChain.getCreateCompensationRequestFee(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;
|
||||
bsqBlockChain.isCompensationRequestPeriodValid(blockHeight);
|
||||
}
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
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.TxOutput;
|
||||
import io.bisq.core.dao.blockchain.vo.TxType;
|
||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.request.compensation.CompensationRequest;
|
||||
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
@ -32,13 +33,13 @@ import java.util.Set;
|
|||
//TODO outdated, ignore
|
||||
@Slf4j
|
||||
public class IssuanceVerification {
|
||||
public static final long MIN_BSQ_ISSUANCE_AMOUNT = 1000;
|
||||
public static final long MAX_BSQ_ISSUANCE_AMOUNT = 10_000_000;
|
||||
private static final long MIN_BSQ_ISSUANCE_AMOUNT = 1000;
|
||||
private static final long MAX_BSQ_ISSUANCE_AMOUNT = 10_000_000;
|
||||
|
||||
private final BsqBlockChain bsqBlockChain;
|
||||
private final PeriodVerification periodVerification;
|
||||
private final VotingVerification votingVerification;
|
||||
private CompensationRequestManager compensationRequestManager;
|
||||
private final CompensationRequestManager compensationRequestManager;
|
||||
|
||||
@Inject
|
||||
public IssuanceVerification(BsqBlockChain bsqBlockChain,
|
||||
|
@ -51,7 +52,7 @@ public class IssuanceVerification {
|
|||
this.compensationRequestManager = compensationRequestManager;
|
||||
}
|
||||
|
||||
boolean maybeProcessData(Tx tx) {
|
||||
public boolean maybeProcessData(Tx tx) {
|
||||
List<TxOutput> outputs = tx.getOutputs();
|
||||
if (outputs.size() >= 2) {
|
||||
TxOutput bsqTxOutput = outputs.get(0);
|
||||
|
@ -72,7 +73,7 @@ public class IssuanceVerification {
|
|||
for (TxOutput txOutput : issuanceTxs) {
|
||||
if (txOutput.getBlockHeight() < height ||
|
||||
(txOutput.getBlockHeight() == height &&
|
||||
txOutput.getId().compareTo(btcTxOutput.getId()) == 1)) {
|
||||
txOutput.getId().compareTo(btcTxOutput.getId()) > 0)) {
|
||||
alreadyFundedBtc += txOutput.getValue();
|
||||
}
|
||||
}
|
|
@ -15,18 +15,21 @@
|
|||
* 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.TxOutput;
|
||||
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
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
|
||||
public class OpReturnVerification {
|
||||
private final CompensationRequestVerification compensationRequestVerification;
|
||||
|
@ -39,35 +42,39 @@ public class OpReturnVerification {
|
|||
this.votingVerification = votingVerification;
|
||||
}
|
||||
|
||||
// FIXME bsqOutput can be null in case there is no BSQ change output at comp requests tx
|
||||
boolean processDaoOpReturnData(Tx tx, int index, long bsqFee,
|
||||
int blockHeight, TxOutput btcOutput, TxOutput bsqOutput) {
|
||||
List<TxOutput> txOutputs = tx.getOutputs();
|
||||
TxOutput txOutput = txOutputs.get(index);
|
||||
public void process(TxOutput txOutput, Tx tx, int index, long bsqFee, int blockHeight, TxOutputsVerification.MutableState mutableState) {
|
||||
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
|
||||
if (txOutputValue == 0 && index == txOutputs.size() - 1 && bsqOutput != null && bsqFee > 0) {
|
||||
// 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 == tx.getOutputs().size() - 1 && bsqFee > 0) {
|
||||
byte[] opReturnData = txOutput.getOpReturnData();
|
||||
// We expect at least the type byte
|
||||
if (opReturnData != null && opReturnData.length > 1) {
|
||||
txOutput.setTxOutputType(TxOutputType.OP_RETURN_OUTPUT);
|
||||
checkArgument(opReturnData != null, "opReturnData must not be null");
|
||||
// All BSQ OP_RETURN txs have at least a type byte
|
||||
if (opReturnData.length >= 1) {
|
||||
// Check with the type byte which kind of OP_RETURN we have.
|
||||
switch (opReturnData[0]) {
|
||||
case DaoConstants.OP_RETURN_TYPE_COMPENSATION_REQUEST:
|
||||
return compensationRequestVerification.processOpReturnData(tx, opReturnData, txOutput,
|
||||
bsqFee, blockHeight, btcOutput);
|
||||
case DaoConstants.OP_RETURN_TYPE_VOTE:
|
||||
return votingVerification.processOpReturnData(tx, opReturnData, txOutput, bsqFee, blockHeight, bsqOutput);
|
||||
case OpReturnTypes.COMPENSATION_REQUEST:
|
||||
if (compensationRequestVerification.verify(opReturnData, bsqFee, blockHeight, mutableState)) {
|
||||
compensationRequestVerification.applyStateChange(tx, txOutput, mutableState);
|
||||
}
|
||||
case OpReturnTypes.VOTE:
|
||||
// TODO
|
||||
case OpReturnTypes.VOTE_RELEASE:
|
||||
// TODO
|
||||
case OpReturnTypes.LOCK_UP:
|
||||
// TODO
|
||||
case OpReturnTypes.UNLOCK:
|
||||
// TODO
|
||||
default:
|
||||
log.warn("OP_RETURN version of the BSQ tx ={} does not match expected version bytes. opReturnData={}",
|
||||
tx.getId(), Utils.HEX.encode(opReturnData));
|
||||
break;
|
||||
}
|
||||
} 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 {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
|
@ -28,7 +30,7 @@ public class PeriodVerification {
|
|||
}
|
||||
|
||||
// TODO
|
||||
boolean isInSponsorPeriod(int blockHeight) {
|
||||
public boolean isInSponsorPeriod(int blockHeight) {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.dao.blockchain.parse;
|
||||
package io.bisq.core.dao.node.consensus;
|
||||
|
||||
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.TxOutput;
|
||||
import io.bisq.core.dao.blockchain.vo.TxOutputType;
|
||||
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 javax.inject.Inject;
|
||||
|
||||
//TODO outdated, ignore
|
||||
@SuppressWarnings("unused")
|
||||
@Slf4j
|
||||
public class VotingVerification {
|
||||
private final BsqBlockChain bsqBlockChain;
|
||||
|
@ -40,15 +42,15 @@ public class VotingVerification {
|
|||
this.periodVerification = periodVerification;
|
||||
}
|
||||
|
||||
boolean isCompensationRequestAccepted(CompensationRequest compensationRequest) {
|
||||
public boolean isCompensationRequestAccepted(CompensationRequest compensationRequest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isConversionRateValid(int blockHeight, long btcAmount, long bsqAmount) {
|
||||
public boolean isConversionRateValid(int blockHeight, long btcAmount, long bsqAmount) {
|
||||
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) {
|
||||
final int sizeOfCompRequestsVotes = (int) opReturnData[22];
|
||||
if (bsqOutput != null &&
|
||||
|
@ -57,7 +59,7 @@ public class VotingVerification {
|
|||
opReturnData.length >= 23 + sizeOfCompRequestsVotes * 2 &&
|
||||
bsqFee == bsqBlockChain.getVotingFee(blockHeight) &&
|
||||
bsqBlockChain.isVotingPeriodValid(blockHeight)) {
|
||||
txOutput.setTxOutputType(TxOutputType.VOTING_OP_RETURN_OUTPUT);
|
||||
txOutput.setTxOutputType(TxOutputType.VOTE_OP_RETURN_OUTPUT);
|
||||
tx.setTxType(TxType.VOTE);
|
||||
// TODO use bsqOutput as weight
|
||||
return true;
|
|
@ -15,32 +15,31 @@
|
|||
* 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 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.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.node.BsqNode;
|
||||
import io.bisq.core.dao.node.full.network.FullNodeNetworkManager;
|
||||
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;
|
||||
|
||||
// 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
|
||||
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;
|
||||
|
||||
|
||||
|
@ -50,20 +49,18 @@ public class BsqFullNode extends BsqNode {
|
|||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Inject
|
||||
public BsqFullNode(P2PService p2PService,
|
||||
BsqParser bsqParser,
|
||||
BsqFullNodeExecutor bsqFullNodeExecutor,
|
||||
BsqBlockChain bsqBlockChain,
|
||||
JsonBlockChainExporter jsonBlockChainExporter,
|
||||
FeeService feeService,
|
||||
SeedNodeRepository seedNodeRepository) {
|
||||
public FullNode(P2PService p2PService,
|
||||
FullNodeExecutor bsqFullNodeExecutor,
|
||||
BsqBlockChain bsqBlockChain,
|
||||
JsonBlockChainExporter jsonBlockChainExporter,
|
||||
FeeService feeService,
|
||||
FullNodeNetworkManager fullNodeNetworkManager) {
|
||||
super(p2PService,
|
||||
bsqParser,
|
||||
bsqBlockChain,
|
||||
feeService,
|
||||
seedNodeRepository);
|
||||
feeService);
|
||||
this.bsqFullNodeExecutor = bsqFullNodeExecutor;
|
||||
this.jsonBlockChainExporter = jsonBlockChainExporter;
|
||||
this.fullNodeNetworkManager = fullNodeNetworkManager;
|
||||
}
|
||||
|
||||
|
||||
|
@ -71,123 +68,55 @@ public class BsqFullNode extends BsqNode {
|
|||
// Public methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
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(() -> {
|
||||
super.onAllServicesInitialized(errorMessageHandler);
|
||||
super.onInitialized();
|
||||
startParseBlocks();
|
||||
},
|
||||
throwable -> {
|
||||
log.error(throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
errorMessageHandler.handleErrorMessage("Initializing BsqFullNode failed: Error=" + throwable.toString());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseBlocksWithChainHeadHeight(int startBlockHeight, int genesisBlockHeight, String genesisTxId) {
|
||||
log.info("parseBlocksWithChainHeadHeight startBlockHeight={}", startBlockHeight);
|
||||
bsqFullNodeExecutor.requestChainHeadHeight(chainHeadHeight -> parseBlocks(startBlockHeight, genesisBlockHeight, genesisTxId, chainHeadHeight),
|
||||
throwable -> {
|
||||
log.error(throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
});
|
||||
public void shutDown() {
|
||||
jsonBlockChainExporter.shutDown();
|
||||
fullNodeNetworkManager.shutDown();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void parseBlocks(int startBlockHeight, int genesisBlockHeight, String genesisTxId, Integer chainHeadHeight) {
|
||||
log.info("parseBlocks with from={} with chainHeadHeight={}", startBlockHeight, chainHeadHeight);
|
||||
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);
|
||||
}
|
||||
protected void startParseBlocks() {
|
||||
requestChainHeadHeightAndParseBlocks(getStartBlockHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onP2PNetworkReady() {
|
||||
super.onP2PNetworkReady();
|
||||
|
||||
if (requestManager == null && p2pNetworkReady) {
|
||||
createRequestBlocksManager();
|
||||
if (parseBlockchainComplete)
|
||||
addBlockHandler();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onParseBlockchainComplete(int genesisBlockHeight, String genesisTxId) {
|
||||
log.info("onParseBlockchainComplete");
|
||||
parseBlockchainComplete = true;
|
||||
|
||||
if (requestManager == null && p2pNetworkReady) {
|
||||
createRequestBlocksManager();
|
||||
addBlockHandler();
|
||||
}
|
||||
|
||||
bsqBlockChainListeners.stream().forEach(BsqBlockChainListener::onBsqBlockChainChanged);
|
||||
}
|
||||
|
||||
private void createRequestBlocksManager() {
|
||||
requestManager = new RequestManager(p2PService.getNetworkNode(),
|
||||
p2PService.getPeerManager(),
|
||||
p2PService.getBroadcaster(),
|
||||
seedNodeRepository.getSeedNodeAddresses(),
|
||||
bsqBlockChain,
|
||||
new RequestManager.Listener() {
|
||||
@Override
|
||||
public void onBlockReceived(GetBsqBlocksResponse getBsqBlocksResponse) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewBsqBlockBroadcastMessage(NewBsqBlockBroadcastMessage newBsqBlockBroadcastMessage) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNoSeedNodeAvailable() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage, @Nullable Connection connection) {
|
||||
|
||||
}
|
||||
});
|
||||
private void onNewBsqBlock(BsqBlock bsqBlock) {
|
||||
notifyListenersOnNewBlock();
|
||||
jsonBlockChainExporter.maybeExport();
|
||||
if (parseBlockchainComplete && p2pNetworkReady)
|
||||
fullNodeNetworkManager.publishNewBlock(bsqBlock);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void addBlockHandler() {
|
||||
// We register our handler for new blocks
|
||||
bsqFullNodeExecutor.addBlockHandler(btcdBlock -> bsqFullNodeExecutor.parseBtcdBlock(btcdBlock,
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
this::onNewBsqBlock,
|
||||
throwable -> {
|
||||
if (throwable instanceof BlockNotConnectingException) {
|
||||
|
@ -199,11 +128,55 @@ public class BsqFullNode extends BsqNode {
|
|||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewBsqBlock(BsqBlock bsqBlock) {
|
||||
super.onNewBsqBlock(bsqBlock);
|
||||
jsonBlockChainExporter.maybeExport();
|
||||
if (parseBlockchainComplete && p2pNetworkReady && requestManager != null)
|
||||
requestManager.publishNewBlock(bsqBlock);
|
||||
private void requestChainHeadHeightAndParseBlocks(int startBlockHeight) {
|
||||
log.info("parseBlocks startBlockHeight={}", startBlockHeight);
|
||||
bsqFullNodeExecutor.requestChainHeadHeight(chainHeadHeight -> parseBlocksOnHeadHeight(startBlockHeight, chainHeadHeight),
|
||||
throwable -> {
|
||||
log.error(throwable.toString());
|
||||
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/>.
|
||||
*/
|
||||
|
||||
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.Futures;
|
||||
|
@ -26,23 +26,26 @@ import io.bisq.common.UserThread;
|
|||
import io.bisq.common.handlers.ResultHandler;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||
import io.bisq.core.dao.node.full.rpc.RpcService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
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
|
||||
public class BsqFullNodeExecutor {
|
||||
public class FullNodeExecutor {
|
||||
|
||||
private final BsqParser bsqParser;
|
||||
private final FullNodeParser fullNodeParser;
|
||||
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 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")
|
||||
@Inject
|
||||
public BsqFullNodeExecutor(RpcService rpcService, BsqParser bsqParser) {
|
||||
public FullNodeExecutor(RpcService rpcService, FullNodeParser fullNodeParser) {
|
||||
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(() -> {
|
||||
rpcService.setup();
|
||||
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);
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<Integer>() {
|
||||
|
@ -87,19 +95,15 @@ public class BsqFullNodeExecutor {
|
|||
});
|
||||
}
|
||||
|
||||
public void parseBlocks(int startBlockHeight,
|
||||
int chainHeadHeight,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId,
|
||||
Consumer<BsqBlock> newBlockHandler,
|
||||
ResultHandler resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
void parseBlocks(int startBlockHeight,
|
||||
int chainHeadHeight,
|
||||
Consumer<BsqBlock> newBlockHandler,
|
||||
ResultHandler resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
||||
long startTs = System.currentTimeMillis();
|
||||
bsqParser.parseBlocks(startBlockHeight,
|
||||
fullNodeParser.parseBlocks(startBlockHeight,
|
||||
chainHeadHeight,
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
newBsqBlock -> UserThread.execute(() -> newBlockHandler.accept(newBsqBlock)));
|
||||
log.info("parseBlocks took {} ms for {} blocks", System.currentTimeMillis() - startTs, chainHeadHeight - startBlockHeight);
|
||||
return null;
|
||||
|
@ -118,14 +122,10 @@ public class BsqFullNodeExecutor {
|
|||
});
|
||||
}
|
||||
|
||||
public void parseBtcdBlock(Block btcdBlock,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId,
|
||||
Consumer<BsqBlock> resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
ListenableFuture<BsqBlock> future = parseBlocksExecutor.submit(() -> bsqParser.parseBlock(btcdBlock,
|
||||
genesisBlockHeight,
|
||||
genesisTxId));
|
||||
void parseBtcdBlock(Block btcdBlock,
|
||||
Consumer<BsqBlock> resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
ListenableFuture<BsqBlock> future = parseBlockExecutor.submit(() -> fullNodeParser.parseBlock(btcdBlock));
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<BsqBlock>() {
|
||||
@Override
|
||||
|
@ -140,7 +140,7 @@ public class BsqFullNodeExecutor {
|
|||
});
|
||||
}
|
||||
|
||||
public void addBlockHandler(Consumer<Block> blockHandler) {
|
||||
void addBlockHandler(Consumer<Block> 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.Futures;
|
||||
|
@ -6,10 +6,10 @@ import com.google.common.util.concurrent.SettableFuture;
|
|||
import io.bisq.common.Timer;
|
||||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.app.Log;
|
||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksRequest;
|
||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksResponse;
|
||||
import io.bisq.core.dao.blockchain.parse.BsqBlockChain;
|
||||
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.GetBsqBlocksResponse;
|
||||
import io.bisq.network.p2p.network.CloseConnectionReason;
|
||||
import io.bisq.network.p2p.network.Connection;
|
||||
import io.bisq.network.p2p.network.NetworkNode;
|
||||
|
@ -18,8 +18,12 @@ import org.jetbrains.annotations.NotNull;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Accepts a GetBsqBlocksRequest from a lite nodes and send back a corresponding GetBsqBlocksResponse.
|
||||
*/
|
||||
@Slf4j
|
||||
public class GetBlocksRequestHandler {
|
||||
class GetBsqBlocksRequestHandler {
|
||||
private static final long TIMEOUT = 120;
|
||||
|
||||
|
||||
|
@ -42,14 +46,14 @@ public class GetBlocksRequestHandler {
|
|||
private final Listener listener;
|
||||
private Timer timeoutTimer;
|
||||
private boolean stopped;
|
||||
private BsqBlockChain bsqBlockChain;
|
||||
private final BsqBlockChain bsqBlockChain;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public GetBlocksRequestHandler(NetworkNode networkNode, BsqBlockChain bsqBlockChain, Listener listener) {
|
||||
public GetBsqBlocksRequestHandler(NetworkNode networkNode, BsqBlockChain bsqBlockChain, Listener listener) {
|
||||
this.networkNode = networkNode;
|
||||
this.bsqBlockChain = bsqBlockChain;
|
||||
this.listener = listener;
|
||||
|
@ -60,9 +64,9 @@ public class GetBlocksRequestHandler {
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void handle(GetBsqBlocksRequest getBsqBlocksRequest, final Connection connection) {
|
||||
public void onGetBsqBlocksRequest(GetBsqBlocksRequest getBsqBlocksRequest, final Connection 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());
|
||||
log.debug("bsqBlocksResponse " + bsqBlocksResponse.getRequestNonce());
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* 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.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.TxInput;
|
||||
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.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
|
@ -53,8 +52,11 @@ import java.util.Properties;
|
|||
import java.util.function.Consumer;
|
||||
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 {
|
||||
private static final Logger log = LoggerFactory.getLogger(RpcService.class);
|
||||
|
||||
|
@ -86,7 +88,7 @@ public class RpcService {
|
|||
this.dumpBlockchainData = dumpBlockchainData;
|
||||
}
|
||||
|
||||
void setup() throws BsqBlockchainException {
|
||||
public void setup() throws BsqBlockchainException {
|
||||
try {
|
||||
long startTs = System.currentTimeMillis();
|
||||
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() {
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
|
||||
Block requestBlock(int blockHeight) throws BitcoindException, CommunicationException {
|
||||
public Block requestBlock(int blockHeight) throws BitcoindException, CommunicationException {
|
||||
final String blockHash = client.getBlockHash(blockHeight);
|
||||
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 {
|
||||
Transaction transaction = requestTx(txId);
|
||||
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 {
|
||||
RawTransaction rawTransaction = requestRawTransaction(txId);
|
||||
// rawTransaction.getTime() is in seconds but we keep it in ms internally
|
||||
|
@ -185,14 +187,14 @@ public class RpcService {
|
|||
try {
|
||||
opReturnData = Utils.HEX.decode(chunks[1]);
|
||||
} catch (Throwable t) {
|
||||
// We get sometimes exceptions, seems BitcoinJ
|
||||
// We get sometimes exceptions, seems BitcoinJ
|
||||
// cannot handle all existing OP_RETURN data, but we ignore them
|
||||
// anyway as our OP_RETURN data is valid in BitcoinJ
|
||||
log.warn("Error at Utils.HEX.decode(chunks[1]): " + t.toString() + " / chunks[1]=" + chunks[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 &&
|
||||
scriptPubKey.getAddresses().size() == 1 ? scriptPubKey.getAddresses().get(0) : null;
|
||||
final PubKeyScript pubKeyScript = dumpBlockchainData ? new PubKeyScript(scriptPubKey) : null;
|
||||
|
@ -207,11 +209,10 @@ public class RpcService {
|
|||
)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final TxVo txVo = new TxVo(txId,
|
||||
return new Tx(txId,
|
||||
blockHeight,
|
||||
rawTransaction.getBlockHash(),
|
||||
time);
|
||||
return new Tx(txVo,
|
||||
time,
|
||||
ImmutableList.copyOf(txInputs),
|
||||
ImmutableList.copyOf(txOutputs));
|
||||
} 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);
|
||||
}
|
||||
|
||||
Transaction requestTx(String txId) throws BitcoindException, CommunicationException {
|
||||
private Transaction requestTx(String txId) throws BitcoindException, CommunicationException {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
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.Futures;
|
||||
|
@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
|
|||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.handlers.ResultHandler;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.dao.blockchain.BsqBlockChain;
|
||||
import io.bisq.core.dao.blockchain.vo.BsqBlock;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -32,14 +33,17 @@ import javax.inject.Inject;
|
|||
import java.util.List;
|
||||
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
|
||||
public class BsqLiteNodeExecutor {
|
||||
public class LiteNodeExecutor {
|
||||
|
||||
private final BsqParser bsqParser;
|
||||
private final LiteNodeParser liteNodeParser;
|
||||
private final BsqBlockChain bsqBlockChain;
|
||||
|
||||
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")
|
||||
@Inject
|
||||
public BsqLiteNodeExecutor(BsqParser bsqParser, BsqBlockChain bsqBlockChain) {
|
||||
this.bsqParser = bsqParser;
|
||||
public LiteNodeExecutor(LiteNodeParser liteNodeParser, BsqBlockChain bsqBlockChain) {
|
||||
this.liteNodeParser = liteNodeParser;
|
||||
this.bsqBlockChain = bsqBlockChain;
|
||||
}
|
||||
|
||||
public void parseBsqBlocksForLiteNode(List<BsqBlock> bsqBlockList,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId,
|
||||
Consumer<BsqBlock> newBlockHandler,
|
||||
ResultHandler resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Package private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void parseBlocks(List<BsqBlock> bsqBlockList,
|
||||
Consumer<BsqBlock> newBlockHandler,
|
||||
ResultHandler resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
||||
long startTs = System.currentTimeMillis();
|
||||
bsqParser.parseBsqBlocks(bsqBlockList,
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
liteNodeParser.parseBsqBlocks(bsqBlockList,
|
||||
newBsqBlock -> UserThread.execute(() -> newBlockHandler.accept(newBsqBlock)));
|
||||
log.info("parseBlocks took {} ms for {} blocks", System.currentTimeMillis() - startTs, bsqBlockList.size());
|
||||
return null;
|
||||
|
@ -82,17 +87,12 @@ public class BsqLiteNodeExecutor {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO check why it's not handled in the parser
|
||||
public void parseBsqBlockForLiteNode(BsqBlock bsqBlock,
|
||||
int genesisBlockHeight,
|
||||
String genesisTxId,
|
||||
ResultHandler resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
ListenableFuture<Void> future = parseBlocksExecutor.submit(() -> {
|
||||
void parseBlock(BsqBlock bsqBlock,
|
||||
ResultHandler resultHandler,
|
||||
Consumer<Throwable> errorHandler) {
|
||||
ListenableFuture<Void> future = parseBlockExecutor.submit(() -> {
|
||||
long startTs = System.currentTimeMillis();
|
||||
bsqParser.parseBsqBlock(bsqBlock,
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
liteNodeParser.parseBsqBlock(bsqBlock);
|
||||
log.info("parseBlocks took {} ms", System.currentTimeMillis() - startTs);
|
||||
bsqBlockChain.addBlock(bsqBlock);
|
||||
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.UserThread;
|
||||
|
@ -6,23 +6,24 @@ import io.bisq.common.app.DevEnv;
|
|||
import io.bisq.common.app.Log;
|
||||
import io.bisq.common.proto.network.NetworkEnvelope;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksRequest;
|
||||
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.vo.BsqBlock;
|
||||
import io.bisq.core.dao.node.messages.GetBsqBlocksResponse;
|
||||
import io.bisq.core.dao.node.messages.NewBsqBlockBroadcastMessage;
|
||||
import io.bisq.network.p2p.NodeAddress;
|
||||
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.seed.SeedNodeRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.*;
|
||||
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
|
||||
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 CLEANUP_TIMER = 120;
|
||||
|
@ -40,9 +41,9 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
public interface Listener {
|
||||
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);
|
||||
}
|
||||
|
@ -54,13 +55,12 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
|
||||
private final NetworkNode networkNode;
|
||||
private final PeerManager peerManager;
|
||||
private final Broadcaster broadcaster;
|
||||
private final BsqBlockChain bsqBlockChain;
|
||||
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<String, GetBlocksRequestHandler> getBlocksRequestHandlers = new HashMap<>();
|
||||
private Timer retryTimer;
|
||||
private boolean stopped;
|
||||
|
||||
|
@ -69,25 +69,25 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public RequestManager(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
Broadcaster broadcaster,
|
||||
Set<NodeAddress> seedNodeAddresses,
|
||||
BsqBlockChain bsqBlockChain,
|
||||
Listener listener) {
|
||||
@Inject
|
||||
public LiteNodeNetworkManager(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
SeedNodeRepository seedNodesRepository) {
|
||||
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)
|
||||
this.seedNodeAddresses = new HashSet<>(seedNodeAddresses);
|
||||
this.listener = listener;
|
||||
this.seedNodeAddresses = new HashSet<>(seedNodesRepository.getSeedNodeAddresses());
|
||||
|
||||
networkNode.addMessageListener(this);
|
||||
networkNode.addConnectionListener(this);
|
||||
peerManager.addListener(this);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void shutDown() {
|
||||
Log.traceCall();
|
||||
stopped = true;
|
||||
|
@ -98,31 +98,24 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
closeAllHandlers();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
public void addListener(Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void requestBlocks(int startBlockHeight) {
|
||||
Log.traceCall();
|
||||
lastRequestedBlockHeight = startBlockHeight;
|
||||
Optional<Connection> seedNodeAddressOptional = networkNode.getConfirmedConnections().stream()
|
||||
Optional<Connection> connectionToSeedNodeOptional = networkNode.getConfirmedConnections().stream()
|
||||
.filter(peerManager::isSeedNode)
|
||||
.findAny();
|
||||
if (seedNodeAddressOptional.isPresent() &&
|
||||
seedNodeAddressOptional.get().getPeersNodeAddressOptional().isPresent()) {
|
||||
requestBlocks(seedNodeAddressOptional.get().getPeersNodeAddressOptional().get(), startBlockHeight);
|
||||
if (connectionToSeedNodeOptional.isPresent() &&
|
||||
connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().isPresent()) {
|
||||
requestBlocks(connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().get(), startBlockHeight);
|
||||
} else {
|
||||
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
|
||||
|
@ -139,13 +132,10 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
closeHandler(connection);
|
||||
|
||||
if (peerManager.isNodeBanned(closeConnectionReason, connection)) {
|
||||
final NodeAddress nodeAddress = connection.getPeersNodeAddressOptional().get();
|
||||
seedNodeAddresses.remove(nodeAddress);
|
||||
requestBlocksHandlerMap.entrySet().stream()
|
||||
.filter(e -> e.getKey().first.equals(nodeAddress))
|
||||
.findAny()
|
||||
.map(Map.Entry::getValue)
|
||||
.ifPresent(requestBlocksHandlerMap::remove);
|
||||
connection.getPeersNodeAddressOptional().ifPresent(nodeAddress -> {
|
||||
seedNodeAddresses.remove(nodeAddress);
|
||||
removeFromRequestBlocksHandlerMap(nodeAddress);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,54 +182,8 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
|
||||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
||||
if (networkEnvelop instanceof GetBsqBlocksRequest) {
|
||||
Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection);
|
||||
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);
|
||||
if (networkEnvelop instanceof NewBsqBlockBroadcastMessage) {
|
||||
listeners.forEach(listener -> listener.onNewBlockReceived((NewBsqBlockBroadcastMessage) networkEnvelop));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +196,10 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
final Tuple2<NodeAddress, Integer> key = new Tuple2<>(peersNodeAddress, startBlockHeight);
|
||||
if (!requestBlocksHandlerMap.containsKey(key)) {
|
||||
if (startBlockHeight >= lastReceivedBlockHeight) {
|
||||
RequestBlocksHandler requestBlocksHandler = new RequestBlocksHandler(networkNode, peerManager,
|
||||
RequestBlocksHandler requestBlocksHandler = new RequestBlocksHandler(networkNode,
|
||||
peerManager,
|
||||
peersNodeAddress,
|
||||
startBlockHeight,
|
||||
new RequestBlocksHandler.Listener() {
|
||||
@Override
|
||||
public void onComplete(GetBsqBlocksResponse getBsqBlocksResponse) {
|
||||
|
@ -265,7 +212,7 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
// we only notify if our request was latest
|
||||
if (startBlockHeight >= lastReceivedBlockHeight) {
|
||||
lastReceivedBlockHeight = startBlockHeight;
|
||||
listener.onBlockReceived(getBsqBlocksResponse);
|
||||
listeners.forEach(listener -> listener.onRequestedBlocksReceived(getBsqBlocksResponse));
|
||||
} else {
|
||||
log.warn("We got a response which is already obsolete because we receive a " +
|
||||
"response from a request with a higher block height. " +
|
||||
|
@ -281,19 +228,19 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
peerManager.handleConnectionFault(peersNodeAddress);
|
||||
requestBlocksHandlerMap.remove(key);
|
||||
|
||||
listener.onFault(errorMessage, connection);
|
||||
listeners.forEach(listener -> listener.onFault(errorMessage, connection));
|
||||
|
||||
tryWithNewSeedNode(startBlockHeight);
|
||||
}
|
||||
});
|
||||
requestBlocksHandlerMap.put(key, requestBlocksHandler);
|
||||
requestBlocksHandler.requestBlocks(peersNodeAddress, startBlockHeight);
|
||||
requestBlocksHandler.requestBlocks();
|
||||
} else {
|
||||
//TODO check with re-orgs
|
||||
// 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." +
|
||||
"startBlockHeight={},lastReceivedBlockHeight={}", startBlockHeight, lastReceivedBlockHeight);
|
||||
if (DevEnv.DEV_MODE)
|
||||
if (DevEnv.isDevMode())
|
||||
throw new RuntimeException("startBlockHeight must be larger than lastReceivedBlockHeight. startBlockHeight=" +
|
||||
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.");
|
||||
|
||||
UserThread.runAfter(() -> {
|
||||
if (requestBlocksHandlerMap.containsKey(peersNodeAddress)) {
|
||||
RequestBlocksHandler handler = requestBlocksHandlerMap.get(peersNodeAddress);
|
||||
if (requestBlocksHandlerMap.containsKey(key)) {
|
||||
RequestBlocksHandler handler = requestBlocksHandlerMap.get(key);
|
||||
handler.stop();
|
||||
requestBlocksHandlerMap.remove(peersNodeAddress);
|
||||
requestBlocksHandlerMap.remove(key);
|
||||
}
|
||||
}, CLEANUP_TIMER);
|
||||
}
|
||||
|
@ -342,13 +289,13 @@ public class RequestManager implements MessageListener, ConnectionListener, Peer
|
|||
requestBlocks(nextCandidate, startBlockHeight);
|
||||
} else {
|
||||
log.warn("No more seed nodes available we could try.");
|
||||
listener.onNoSeedNodeAvailable();
|
||||
listeners.forEach(Listener::onNoSeedNodeAvailable);
|
||||
}
|
||||
},
|
||||
RETRY_DELAY_SEC);
|
||||
} else {
|
||||
log.warn("We tried {} times but could not connect to a seed node.", retryCounter);
|
||||
listener.onNoSeedNodeAvailable();
|
||||
listeners.forEach(Listener::onNoSeedNodeAvailable);
|
||||
}
|
||||
} else {
|
||||
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();
|
||||
if (peersNodeAddressOptional.isPresent()) {
|
||||
NodeAddress nodeAddress = peersNodeAddressOptional.get();
|
||||
if (requestBlocksHandlerMap.containsKey(nodeAddress)) {
|
||||
requestBlocksHandlerMap.get(nodeAddress).cancel();
|
||||
requestBlocksHandlerMap.remove(nodeAddress);
|
||||
}
|
||||
removeFromRequestBlocksHandlerMap(nodeAddress);
|
||||
} else {
|
||||
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() {
|
||||
requestBlocksHandlerMap.values().stream().forEach(RequestBlocksHandler::cancel);
|
||||
requestBlocksHandlerMap.values().forEach(RequestBlocksHandler::cancel);
|
||||
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.Futures;
|
||||
|
@ -7,14 +7,15 @@ import io.bisq.common.Timer;
|
|||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.app.Log;
|
||||
import io.bisq.common.proto.network.NetworkEnvelope;
|
||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksRequest;
|
||||
import io.bisq.core.dao.blockchain.p2p.messages.GetBsqBlocksResponse;
|
||||
import io.bisq.core.dao.node.messages.GetBsqBlocksRequest;
|
||||
import io.bisq.core.dao.node.messages.GetBsqBlocksResponse;
|
||||
import io.bisq.network.p2p.NodeAddress;
|
||||
import io.bisq.network.p2p.network.CloseConnectionReason;
|
||||
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.PeerManager;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
@ -23,6 +24,9 @@ import java.util.Random;
|
|||
|
||||
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
|
||||
public class RequestBlocksHandler implements MessageListener {
|
||||
private static final long TIMEOUT = 120;
|
||||
|
@ -46,12 +50,14 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
|
||||
private final NetworkNode networkNode;
|
||||
private final PeerManager peerManager;
|
||||
@Getter
|
||||
private final NodeAddress nodeAddress;
|
||||
@Getter
|
||||
private final int startBlockHeight;
|
||||
private final Listener listener;
|
||||
private Timer timeoutTimer;
|
||||
private final int nonce = new Random().nextInt();
|
||||
private boolean stopped;
|
||||
private Connection connection;
|
||||
private NodeAddress peersNodeAddress;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -60,9 +66,13 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
|
||||
public RequestBlocksHandler(NetworkNode networkNode,
|
||||
PeerManager peerManager,
|
||||
NodeAddress nodeAddress,
|
||||
int startBlockHeight,
|
||||
Listener listener) {
|
||||
this.networkNode = networkNode;
|
||||
this.peerManager = peerManager;
|
||||
this.nodeAddress = nodeAddress;
|
||||
this.startBlockHeight = startBlockHeight;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
@ -75,10 +85,7 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestBlocks(NodeAddress nodeAddress, int startBlockHeight) {
|
||||
Log.traceCall("nodeAddress=" + nodeAddress);
|
||||
this.peersNodeAddress = nodeAddress;
|
||||
|
||||
public void requestBlocks() {
|
||||
if (!stopped) {
|
||||
GetBsqBlocksRequest getBsqBlocksRequest = new GetBsqBlocksRequest(startBlockHeight, nonce);
|
||||
log.debug("getBsqBlocksRequest " + getBsqBlocksRequest);
|
||||
|
@ -86,9 +93,9 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||
if (!stopped) {
|
||||
String errorMessage = "A timeout occurred at sending getBsqBlocksRequest:" + getBsqBlocksRequest +
|
||||
" on peersNodeAddress:" + peersNodeAddress;
|
||||
" on peersNodeAddress:" + nodeAddress;
|
||||
log.debug(errorMessage + " / RequestDataHandler=" + RequestBlocksHandler.this);
|
||||
handleFault(errorMessage, peersNodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
|
||||
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that timeoutTimer.run call. " +
|
||||
"Might be caused by an previous networkNode.sendMessage.onFailure.");
|
||||
|
@ -97,15 +104,14 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
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);
|
||||
SettableFuture<Connection> future = networkNode.sendMessage(peersNodeAddress, getBsqBlocksRequest);
|
||||
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getBsqBlocksRequest);
|
||||
Futures.addCallback(future, new FutureCallback<Connection>() {
|
||||
@Override
|
||||
public void onSuccess(Connection connection) {
|
||||
if (!stopped) {
|
||||
RequestBlocksHandler.this.connection = connection;
|
||||
log.trace("Send " + getBsqBlocksRequest + " to " + peersNodeAddress + " succeeded.");
|
||||
log.trace("Send " + getBsqBlocksRequest + " to " + nodeAddress + " succeeded.");
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call." +
|
||||
"Might be caused by an previous timeout.");
|
||||
|
@ -115,12 +121,12 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
@Override
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
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" +
|
||||
"getBsqBlocksRequest=" + getBsqBlocksRequest + "." +
|
||||
"\n\tException=" + throwable.getMessage();
|
||||
log.error(errorMessage);
|
||||
handleFault(errorMessage, peersNodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
|
||||
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call. " +
|
||||
"Might be caused by an previous timeout.");
|
||||
|
@ -140,7 +146,7 @@ public class RequestBlocksHandler implements MessageListener {
|
|||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
||||
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);
|
||||
if (!stopped) {
|
||||
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.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.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.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/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.dao.compensation;
|
||||
package io.bisq.core.dao.request.compensation;
|
||||
|
||||
import io.bisq.common.proto.persistable.PersistablePayload;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.dao.request.VoteRequest;
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
@ -35,12 +36,12 @@ import java.util.Optional;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
// Represents the state of the CompensationRequest data
|
||||
// TODO cleanup
|
||||
// Represents the local and mutual state of the CompensationRequest data
|
||||
// TODO cleanup, not completed yet
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
public final class CompensationRequest implements PersistablePayload {
|
||||
public final class CompensationRequest extends VoteRequest implements PersistablePayload {
|
||||
|
||||
private final CompensationRequestPayload payload;
|
||||
|
||||
|
@ -60,11 +61,7 @@ public final class CompensationRequest implements PersistablePayload {
|
|||
@Setter
|
||||
private Coin compensationRequestFee;
|
||||
@Setter
|
||||
private Transaction feeTx;
|
||||
@Setter
|
||||
Transaction txWithBtcFee;
|
||||
@Setter
|
||||
private Transaction signedTx;
|
||||
private Transaction tx;
|
||||
|
||||
@Nullable
|
||||
private Map<String, String> extraDataMap;
|
|
@ -15,7 +15,7 @@
|
|||
* 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 io.bisq.common.proto.persistable.PersistableEnvelope;
|
|
@ -15,14 +15,12 @@
|
|||
* 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.inject.Inject;
|
||||
import io.bisq.common.UserThread;
|
||||
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.proto.persistable.PersistedDataHost;
|
||||
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.wallet.BsqWalletService;
|
||||
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.blockchain.BsqBlockChain;
|
||||
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
|
||||
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.network.p2p.P2PService;
|
||||
import io.bisq.network.p2p.storage.HashMapChangedListener;
|
||||
|
@ -47,24 +45,24 @@ import javafx.collections.FXCollections;
|
|||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class CompensationRequestManager implements PersistedDataHost, BsqBlockChainListener, HashMapChangedListener {
|
||||
private static final Logger log = LoggerFactory.getLogger(CompensationRequestManager.class);
|
||||
|
||||
private final P2PService p2PService;
|
||||
private final DaoPeriodService daoPeriodService;
|
||||
|
@ -113,10 +111,14 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void shutDown() {
|
||||
|
||||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
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 -> {
|
||||
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
|
||||
if (protectedStoragePayload instanceof CompensationRequestPayload)
|
||||
|
@ -141,51 +143,43 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
|||
}
|
||||
|
||||
public CompensationRequest prepareCompensationRequest(CompensationRequestPayload compensationRequestPayload)
|
||||
throws InsufficientMoneyException, ChangeBelowDustException, TransactionVerificationException, WalletException, IOException {
|
||||
CompensationRequest compensationRequest = new CompensationRequest(compensationRequestPayload);
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
compensationRequest.setCompensationRequestFee(feeService.getCreateCompensationRequestFee());
|
||||
compensationRequest.setFeeTx(bsqWalletService.getPreparedBurnFeeTx(compensationRequest.getCompensationRequestFee()));
|
||||
throws InsufficientMoneyException, TransactionVerificationException, WalletException, CompensationAmountException {
|
||||
|
||||
String bsqAddress = compensationRequestPayload.getBsqAddress();
|
||||
// Remove initial B
|
||||
bsqAddress = bsqAddress.substring(1, bsqAddress.length());
|
||||
checkArgument(!compensationRequest.getFeeTx().getInputs().isEmpty(), "preparedTx inputs must not be empty");
|
||||
|
||||
// We use the key of the first BSQ input for signing the data
|
||||
TransactionOutput connectedOutput = compensationRequest.getFeeTx().getInputs().get(0).getConnectedOutput();
|
||||
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
||||
DeterministicKey bsqKeyPair = bsqWalletService.findKeyFromPubKeyHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
||||
checkNotNull(bsqKeyPair, "bsqKeyPair must not be null");
|
||||
|
||||
// We get the JSON of the object excluding signature and feeTxId
|
||||
String payloadAsJson = StringUtils.deleteWhitespace(Utilities.objectToJson(compensationRequestPayload));
|
||||
// Signs a text message using the standard Bitcoin messaging signing format and returns the signature as a base64
|
||||
// encoded string.
|
||||
String signature = bsqKeyPair.signMessage(payloadAsJson);
|
||||
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 1 Btc output (small payment to own compensation receiving address)
|
||||
compensationRequest.setTxWithBtcFee(
|
||||
btcWalletService.completePreparedCompensationRequestTx(
|
||||
compensationRequest.getRequestedBsq(),
|
||||
compensationRequest.getIssuanceAddress(bsqWalletService),
|
||||
compensationRequest.getFeeTx(),
|
||||
opReturnData));
|
||||
if (contains(compensationRequestPayload)) {log.error("Req found");}
|
||||
compensationRequest.setSignedTx(bsqWalletService.signTx(compensationRequest.getTxWithBtcFee()));
|
||||
if (contains(compensationRequestPayload)) {log.error("Req found");}
|
||||
final Coin minRequestAmount = Restrictions.getMinCompensationRequestAmount();
|
||||
if (compensationRequestPayload.getRequestedBsq().compareTo(minRequestAmount) < 0) {
|
||||
throw new CompensationAmountException(minRequestAmount, compensationRequestPayload.getRequestedBsq());
|
||||
}
|
||||
if (contains(compensationRequestPayload)) {log.error("Req found");}
|
||||
|
||||
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
|
||||
TransactionOutput connectedOutput = preparedBurnFeeTx.getInputs().get(0).getConnectedOutput();
|
||||
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
||||
DeterministicKey bsqKeyPair = bsqWalletService.findKeyFromPubKeyHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
||||
checkNotNull(bsqKeyPair, "bsqKeyPair must not be null");
|
||||
|
||||
// We get the JSON of the object excluding signature and feeTxId
|
||||
String payloadAsJson = StringUtils.deleteWhitespace(Utilities.objectToJson(compensationRequestPayload));
|
||||
// Signs a text message using the standard Bitcoin messaging signing format and returns the signature as a base64
|
||||
// encoded string.
|
||||
String signature = bsqKeyPair.signMessage(payloadAsJson);
|
||||
compensationRequestPayload.setSignature(signature);
|
||||
|
||||
//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)
|
||||
final Transaction txWithBtcFee = btcWalletService.completePreparedCompensationRequestTx(
|
||||
compensationRequest.getRequestedBsq(),
|
||||
compensationRequest.getIssuanceAddress(bsqWalletService),
|
||||
preparedBurnFeeTx,
|
||||
opReturnData);
|
||||
compensationRequest.setTx(bsqWalletService.signTx(txWithBtcFee));
|
||||
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
|
||||
// if it gets committed 2 times
|
||||
// We clone before commit to avoid unwanted side effects
|
||||
final Transaction clonedTransaction = btcWalletService.getClonedTransaction(compensationRequest.getTxWithBtcFee());
|
||||
bsqWalletService.commitTx(compensationRequest.getTxWithBtcFee());
|
||||
btcWalletService.commitTx(clonedTransaction);
|
||||
bsqWalletService.broadcastTx(compensationRequest.getSignedTx(), new FutureCallback<Transaction>() {
|
||||
final Transaction tx = compensationRequest.getTx();
|
||||
bsqWalletService.commitTx(tx);
|
||||
|
||||
final Transaction clonedTx = btcWalletService.getClonedTransaction(tx);
|
||||
btcWalletService.commitTx(clonedTx);
|
||||
|
||||
bsqWalletService.broadcastTx(tx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
checkNotNull(transaction, "Transaction must not be null at broadcastTx callback.");
|
||||
|
@ -224,21 +221,21 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
|||
} else {
|
||||
final String msg = "removeCompensationRequest called for a CompensationRequest which is not ours.";
|
||||
log.warn(msg);
|
||||
if (DevEnv.DEV_MODE)
|
||||
if (DevEnv.isDevMode())
|
||||
throw new RuntimeException(msg);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
final String msg = "removeCompensationRequest called with a CompensationRequest which is outside of the CompensationRequest phase.";
|
||||
log.warn(msg);
|
||||
if (DevEnv.DEV_MODE)
|
||||
if (DevEnv.isDevMode())
|
||||
throw new RuntimeException(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -295,7 +292,7 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
|||
} else {
|
||||
final String msg = "onRemoved called of a CompensationRequest which is outside of the CompensationRequest phase is invalid and we ignore it.";
|
||||
log.warn(msg);
|
||||
if (DevEnv.DEV_MODE)
|
||||
if (DevEnv.isDevMode())
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
});
|
||||
|
@ -336,9 +333,9 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
|
|||
|
||||
private void updateFilteredLists() {
|
||||
// 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 -> {
|
||||
return daoPeriodService.isInCurrentCycle(compensationRequest) ||
|
||||
return daoPeriodService.isTxInCurrentCycle(compensationRequest.getPayload().getTxId()) ||
|
||||
(bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId()) == null &&
|
||||
isMine(compensationRequest));
|
||||
});
|
|
@ -15,7 +15,7 @@
|
|||
* 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.Version;
|
||||
|
@ -37,6 +37,9 @@ import java.security.PublicKey;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Payload sent over wire as well as it gets persisted, containing all base data for a compensation request
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
public final class CompensationRequestPayload implements LazyProcessedPayload, PersistableProtectedPayload, CapabilityRequiringPayload {
|
||||
|
@ -180,7 +183,7 @@ public final class CompensationRequestPayload implements LazyProcessedPayload, P
|
|||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//TODo not needed?
|
||||
//TODO not needed?
|
||||
@Override
|
||||
public long getTTL() {
|
||||
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 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 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.BtcWalletService;
|
||||
import io.bisq.core.dao.DaoPeriodService;
|
||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestPayload;
|
||||
import io.bisq.core.dao.request.compensation.CompensationRequest;
|
||||
import io.bisq.core.dao.request.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.request.compensation.CompensationRequestPayload;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -91,6 +91,10 @@ public class VotingManager implements PersistedDataHost {
|
|||
this.votingDefaultValues = votingDefaultValues;
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPersisted() {
|
||||
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 List<Listener> listeners = new ArrayList<>();
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private static final String pubKeyAsHex = DevEnv.USE_DEV_PRIVILEGE_KEYS ?
|
||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||
"022ac7b7766b0aedff82962522c2c14fb8d1961dabef6e5cfd10edc679456a32f1";
|
||||
private final String pubKeyAsHex;
|
||||
private ECKey filterSigningKey;
|
||||
|
||||
|
||||
|
@ -99,7 +96,8 @@ public class FilterManager {
|
|||
Preferences preferences,
|
||||
BisqEnvironment bisqEnvironment,
|
||||
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.keyRing = keyRing;
|
||||
this.user = user;
|
||||
|
@ -107,6 +105,9 @@ public class FilterManager {
|
|||
this.bisqEnvironment = bisqEnvironment;
|
||||
this.providersRepository = providersRepository;
|
||||
this.ignoreDevMsg = ignoreDevMsg;
|
||||
pubKeyAsHex = useDevPrivilegeKeys ?
|
||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||
"022ac7b7766b0aedff82962522c2c14fb8d1961dabef6e5cfd10edc679456a32f1";
|
||||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
|
|
|
@ -270,6 +270,10 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||
return Coin.valueOf(offerPayload.getMinAmount());
|
||||
}
|
||||
|
||||
public boolean isRange() {
|
||||
return offerPayload.getAmount() != offerPayload.getMinAmount();
|
||||
}
|
||||
|
||||
public Date 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) {
|
||||
if (p2PService.removeData(offerPayload, true)) {
|
||||
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.UserThread;
|
||||
import io.bisq.common.proto.ProtoUtil;
|
||||
import io.bisq.common.storage.Storage;
|
||||
import io.bisq.core.trade.Tradable;
|
||||
import io.bisq.core.trade.TradableList;
|
||||
|
@ -40,29 +41,34 @@ public final class OpenOffer implements Tradable {
|
|||
AVAILABLE,
|
||||
RESERVED,
|
||||
CLOSED,
|
||||
CANCELED
|
||||
CANCELED,
|
||||
DEACTIVATED
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final Offer offer;
|
||||
@Getter
|
||||
private State state = State.AVAILABLE;
|
||||
private State state;
|
||||
|
||||
transient private Storage<TradableList<OpenOffer>> storage;
|
||||
|
||||
public OpenOffer(Offer offer, Storage<TradableList<OpenOffer>> storage) {
|
||||
this.offer = offer;
|
||||
this.storage = storage;
|
||||
state = State.AVAILABLE;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private OpenOffer(Offer offer) {
|
||||
private OpenOffer(Offer offer, State state) {
|
||||
this.offer = offer;
|
||||
}
|
||||
this.state = state;
|
||||
|
||||
if (this.state == State.RESERVED)
|
||||
setState(State.AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PB.Tradable toProtoMessage() {
|
||||
|
@ -73,11 +79,8 @@ public final class OpenOffer implements Tradable {
|
|||
}
|
||||
|
||||
public static Tradable fromProto(PB.OpenOffer proto) {
|
||||
OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()));
|
||||
// If we have a reserved state from the local db we reset it
|
||||
if (openOffer.getState() == State.RESERVED)
|
||||
openOffer.setState(State.AVAILABLE);
|
||||
return openOffer;
|
||||
return new OpenOffer(Offer.fromProto(proto.getOffer()),
|
||||
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -118,6 +121,10 @@ public final class OpenOffer implements Tradable {
|
|||
stopTimeout();
|
||||
}
|
||||
|
||||
public boolean isDeactivated() {
|
||||
return state == State.DEACTIVATED;
|
||||
}
|
||||
|
||||
private void startTimeout() {
|
||||
stopTimeout();
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ import io.bisq.network.p2p.*;
|
|||
import io.bisq.network.p2p.peers.PeerManager;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -327,22 +328,54 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
}
|
||||
|
||||
// Remove from my offers
|
||||
public void removeOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
public void activateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
offerBookService.removeOffer(offer.getOfferPayload(),
|
||||
openOffer.setStorage(openOfferTradableListStorage);
|
||||
offerBookService.activateOffer(offer,
|
||||
() -> {
|
||||
offer.setState(Offer.State.REMOVED);
|
||||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
openOffers.remove(openOffer);
|
||||
closedTradableManager.add(openOffer);
|
||||
log.debug("removeOpenOffer, offerId={}", offer.getId());
|
||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||
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) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
if (openOffer.isDeactivated()) {
|
||||
openOffer.setStorage(openOfferTradableListStorage);
|
||||
onRemoved(openOffer, resultHandler, offer);
|
||||
} else {
|
||||
offerBookService.removeOffer(offer.getOfferPayload(),
|
||||
() -> {
|
||||
onRemoved(openOffer, resultHandler, offer);
|
||||
},
|
||||
errorMessageHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler, Offer offer) {
|
||||
offer.setState(Offer.State.REMOVED);
|
||||
openOffer.setState(OpenOffer.State.CANCELED);
|
||||
openOffers.remove(openOffer);
|
||||
closedTradableManager.add(openOffer);
|
||||
log.debug("removeOpenOffer, offerId={}", offer.getId());
|
||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||
resultHandler.handleResult();
|
||||
}
|
||||
|
||||
// Close openOffer after deposit published
|
||||
public void closeOpenOffer(Offer offer) {
|
||||
findOpenOffer(offer.getId()).ifPresent(openOffer -> {
|
||||
|
@ -483,13 +516,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
final OpenOffer openOffer = openOffersList.get(i);
|
||||
UserThread.runAfterRandomDelay(() -> {
|
||||
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();
|
||||
if (id != null && !id.contains("_"))
|
||||
if (id != null && !openOffer.isDeactivated())
|
||||
republishOffer(openOffer);
|
||||
else
|
||||
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);
|
||||
UserThread.runAfterRandomDelay(() -> {
|
||||
// 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);
|
||||
}, 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