Merge branch 'master' into tau3-refactoring/core-seed-nodes-repository

This commit is contained in:
Chris Beams 2018-03-10 10:59:42 +01:00
commit cef131151c
No known key found for this signature in database
GPG key ID: 3D214F8F5BC5ED73
279 changed files with 8663 additions and 4477 deletions

View file

@ -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>

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -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}

View file

@ -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:

View file

@ -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=Αποδοχή συναλλαγών από τις ακόλουθες χώρες εκτός Ευρώ:

View file

@ -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

View file

@ -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:

View file

@ -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).

View file

@ -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=Примите сделки от этих не- Евро стран:

View file

@ -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:

View file

@ -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=接受来自这些非欧元国家的交易:

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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";
}

View file

@ -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) {

View file

@ -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";
}

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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) {

View file

@ -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() {

View file

@ -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());
}

View file

@ -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";
}

View file

@ -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;

View file

@ -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;

View file

@ -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())

View file

@ -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));

View file

@ -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();

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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";
}

View file

@ -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 &&

View 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;
}

View file

@ -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);
}
});

View file

@ -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 {

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -29,6 +29,7 @@ public enum TxType {
PAY_TRADE_FEE,
COMPENSATION_REQUEST,
VOTE,
VOTE_REVEAL,
ISSUANCE,
LOCK_UP,
UN_LOCK;

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;

View 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;
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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() {
}
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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.");
}
}
}
}

View file

@ -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());

View file

@ -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);
}
}

View 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();
}
};
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View 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() {
}
}

View file

@ -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() {
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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));
});

View file

@ -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);

View file

@ -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];
}
}
}

View file

@ -0,0 +1,31 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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
}
}

View file

@ -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 {
}

View file

@ -0,0 +1,31 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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() {
}
}

View file

@ -0,0 +1,31 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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() {
}
}

View file

@ -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;

View file

@ -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()) {

View file

@ -0,0 +1,22 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* 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;

View file

@ -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() {

View file

@ -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());
}

View file

@ -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());

View file

@ -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();

View file

@ -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