diff --git a/common/src/main/java/bisq/common/util/ExtraDataMapValidator.java b/common/src/main/java/bisq/common/util/ExtraDataMapValidator.java
new file mode 100644
index 00000000000..df158557165
--- /dev/null
+++ b/common/src/main/java/bisq/common/util/ExtraDataMapValidator.java
@@ -0,0 +1,83 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.common.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Validator for extraDataMap fields used in network payloads.
+ * Ensures that we don't get the network attacked by huge data inserted there.
+ */
+@Slf4j
+public class ExtraDataMapValidator {
+ // ExtraDataMap is only used for exceptional cases to not break backward compatibility.
+ // We don't expect many entries there.
+ public final static int MAX_SIZE = 10;
+ public final static int MAX_KEY_LENGTH = 100;
+ public final static int MAX_VALUE_LENGTH = 100000; // 100 kb
+
+ public static Map getValidatedExtraDataMap(@Nullable Map extraDataMap) {
+ return getValidatedExtraDataMap(extraDataMap, MAX_SIZE, MAX_KEY_LENGTH, MAX_VALUE_LENGTH);
+ }
+
+ public static Map getValidatedExtraDataMap(@Nullable Map extraDataMap, int maxSize,
+ int maxKeyLength, int maxValueLength) {
+ if (extraDataMap == null)
+ return null;
+
+ try {
+ checkArgument(extraDataMap.entrySet().size() <= maxSize,
+ "Size of map must not exceed " + maxSize);
+ extraDataMap.forEach((key, value) -> {
+ checkArgument(key.length() <= maxKeyLength,
+ "Length of key must not exceed " + maxKeyLength);
+ checkArgument(value.length() <= maxValueLength,
+ "Length of value must not exceed " + maxValueLength);
+ });
+ return extraDataMap;
+ } catch (Throwable t) {
+ return new HashMap<>();
+ }
+ }
+
+ public static void validate(@Nullable Map extraDataMap) {
+ validate(extraDataMap, MAX_SIZE, MAX_KEY_LENGTH, MAX_VALUE_LENGTH);
+ }
+
+ public static void validate(@Nullable Map extraDataMap, int maxSize, int maxKeyLength,
+ int maxValueLength) {
+ if (extraDataMap == null)
+ return;
+
+ checkArgument(extraDataMap.entrySet().size() <= maxSize,
+ "Size of map must not exceed " + maxSize);
+ extraDataMap.forEach((key, value) -> {
+ checkArgument(key.length() <= maxKeyLength,
+ "Length of key must not exceed " + maxKeyLength);
+ checkArgument(value.length() <= maxValueLength,
+ "Length of value must not exceed " + maxValueLength);
+ });
+ }
+}
diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto
index f2f9b57a528..65afa4df9f8 100644
--- a/common/src/main/proto/pb.proto
+++ b/common/src/main/proto/pb.proto
@@ -1420,7 +1420,7 @@ message Tx {
// Because of the way how PB implements inheritence we need to use the super class as type
repeated BaseTxOutput tx_outputs = 1;
TxType txType = 2;
- int64 burnt_fee = 3;
+ int64 burnt_bsq = 3;
}
enum TxType {
@@ -1440,6 +1440,7 @@ enum TxType {
UNLOCK = 13;
ASSET_LISTING_FEE = 14;
PROOF_OF_BURN = 15;
+ IRREGULAR = 16;
}
message TxInput {
diff --git a/core/src/main/java/bisq/core/alert/Alert.java b/core/src/main/java/bisq/core/alert/Alert.java
index cb2569f3901..4081b231f26 100644
--- a/core/src/main/java/bisq/core/alert/Alert.java
+++ b/core/src/main/java/bisq/core/alert/Alert.java
@@ -22,6 +22,7 @@
import bisq.common.app.Version;
import bisq.common.crypto.Sig;
+import bisq.common.util.ExtraDataMapValidator;
import io.bisq.generated.protobuffer.PB;
@@ -91,7 +92,7 @@ public Alert(String message,
this.version = version;
this.ownerPubKeyBytes = ownerPubKeyBytes;
this.signatureAsBase64 = signatureAsBase64;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
ownerPubKey = Sig.getPublicKeyFromBytes(ownerPubKeyBytes);
}
diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java
index dcd87d1c38d..fcc3142e5c8 100644
--- a/core/src/main/java/bisq/core/app/BisqExecutable.java
+++ b/core/src/main/java/bisq/core/app/BisqExecutable.java
@@ -494,7 +494,7 @@ protected void customizeOptionParsing(OptionParser parser) {
format("Base currency network (default: %s)", BisqEnvironment.getDefaultBaseCurrencyNetwork().name()))
.withRequiredArg()
.ofType(String.class)
- .describedAs(format("%s|%s|%s|%s", BTC_MAINNET, BTC_TESTNET, BTC_REGTEST, BTC_DAO_TESTNET, BTC_DAO_BETANET));
+ .describedAs(format("%s|%s|%s|%s", BTC_MAINNET, BTC_TESTNET, BTC_REGTEST, BTC_DAO_TESTNET, BTC_DAO_BETANET, BTC_DAO_REGTEST));
parser.accepts(BtcOptionKeys.REG_TEST_HOST,
format("Bitcoin regtest host when using BTC_REGTEST network (default: %s)", RegTestHost.DEFAULT_HOST))
diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java
index b8d355a0455..07668b9b241 100644
--- a/core/src/main/java/bisq/core/app/BisqSetup.java
+++ b/core/src/main/java/bisq/core/app/BisqSetup.java
@@ -418,7 +418,7 @@ private void maybeShowTac() {
private void checkIfLocalHostNodeIsRunning() {
// For DAO testnet we ignore local btc node
- if (BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet()) {
+ if (BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() || BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet()) {
step3();
} else {
Thread checkIfLocalHostNodeIsRunningThread = new Thread(() -> {
diff --git a/core/src/main/java/bisq/core/arbitration/Arbitrator.java b/core/src/main/java/bisq/core/arbitration/Arbitrator.java
index a9b6d738c45..ffc6a8f4670 100644
--- a/core/src/main/java/bisq/core/arbitration/Arbitrator.java
+++ b/core/src/main/java/bisq/core/arbitration/Arbitrator.java
@@ -23,6 +23,7 @@
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
+import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.Utilities;
import io.bisq.generated.protobuffer.PB;
@@ -91,7 +92,7 @@ public Arbitrator(NodeAddress nodeAddress,
this.registrationSignature = registrationSignature;
this.emailAddress = emailAddress;
this.info = info;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
}
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/arbitration/Mediator.java b/core/src/main/java/bisq/core/arbitration/Mediator.java
index b5d4e3ca643..0673b5465c0 100644
--- a/core/src/main/java/bisq/core/arbitration/Mediator.java
+++ b/core/src/main/java/bisq/core/arbitration/Mediator.java
@@ -23,6 +23,7 @@
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
+import bisq.common.util.ExtraDataMapValidator;
import io.bisq.generated.protobuffer.PB;
@@ -84,7 +85,7 @@ public Mediator(NodeAddress nodeAddress,
this.registrationSignature = registrationSignature;
this.emailAddress = emailAddress;
this.info = info;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java b/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java
index 12d50336223..c4468563299 100644
--- a/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java
+++ b/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java
@@ -28,8 +28,9 @@ public enum BaseCurrencyNetwork {
BTC_MAINNET(MainNetParams.get(), "BTC", "MAINNET", "Bitcoin"),
BTC_TESTNET(TestNet3Params.get(), "BTC", "TESTNET", "Bitcoin"),
BTC_REGTEST(RegTestParams.get(), "BTC", "REGTEST", "Bitcoin"),
- BTC_DAO_TESTNET(RegTestParams.get(), "BTC", "REGTEST", "Bitcoin"), // server side regtest
- BTC_DAO_BETANET(MainNetParams.get(), "BTC", "MAINNET", "Bitcoin"); // mainnet test genesis
+ BTC_DAO_TESTNET(RegTestParams.get(), "BTC", "REGTEST", "Bitcoin"), // server side regtest until v0.9.5
+ BTC_DAO_BETANET(MainNetParams.get(), "BTC", "MAINNET", "Bitcoin"), // mainnet test genesis
+ BTC_DAO_REGTEST(RegTestParams.get(), "BTC", "REGTEST", "Bitcoin"); // server side regtest after v0.9.5, had breaking code changes so we started over again
@Getter
private final NetworkParameters parameters;
@@ -59,6 +60,10 @@ public boolean isDaoTestNet() {
return "BTC_DAO_TESTNET".equals(name());
}
+ public boolean isDaoRegTest() {
+ return "BTC_DAO_REGTEST".equals(name());
+ }
+
public boolean isDaoBetaNet() {
return "BTC_DAO_BETANET".equals(name());
}
diff --git a/core/src/main/java/bisq/core/btc/BitcoinModule.java b/core/src/main/java/bisq/core/btc/BitcoinModule.java
index 60c55d31c65..29b174539fd 100644
--- a/core/src/main/java/bisq/core/btc/BitcoinModule.java
+++ b/core/src/main/java/bisq/core/btc/BitcoinModule.java
@@ -54,13 +54,15 @@ public BitcoinModule(Environment environment) {
@Override
protected void configure() {
- // We we have selected BTC_DAO_TESTNET we use our master regtest node, otherwise the specified host or default
- // (localhost)
+ // If we we have selected BTC_DAO_REGTEST or BTC_DAO_TESTNET we use our master regtest node,
+ // otherwise the specified host or default (localhost)
String regTestHost = environment.getProperty(BtcOptionKeys.REG_TEST_HOST, String.class, "");
if (regTestHost.isEmpty()) {
regTestHost = BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet() ?
"104.248.31.39" :
- RegTestHost.DEFAULT_HOST;
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "134.209.242.206" :
+ RegTestHost.DEFAULT_HOST;
}
RegTestHost.HOST = regTestHost;
diff --git a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
index e8c19ea9c60..6ecdbf453ca 100644
--- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
+++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
@@ -235,7 +235,7 @@ private PeerGroup createPeerGroup() {
// For dao testnet (server side regtest) we prevent to connect to a localhost node to avoid confusion
// if local btc node is not synced with our dao testnet master node.
- if (BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet())
+ if (BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() || BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet())
peerGroup.setUseLocalhostPeerWhenPossible(false);
return peerGroup;
diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java
index e3413b6ea9e..7c5c90f772a 100644
--- a/core/src/main/java/bisq/core/dao/DaoFacade.java
+++ b/core/src/main/java/bisq/core/dao/DaoFacade.java
@@ -56,6 +56,7 @@
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.DaoStateStorageService;
import bisq.core.dao.state.model.blockchain.BaseTx;
+import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
@@ -543,6 +544,28 @@ public long getTotalAmountOfConfiscatedTxOutputs() {
return daoStateService.getTotalAmountOfConfiscatedTxOutputs();
}
+ public long getTotalAmountOfInvalidatedBsq() {
+ return daoStateService.getTotalAmountOfInvalidatedBsq();
+ }
+
+ // Contains burned fee and invalidated bsq due invalid txs
+ public long getTotalAmountOfBurntBsq() {
+ return daoStateService.getTotalAmountOfBurntBsq();
+ }
+
+ public List getInvalidTxs() {
+ return daoStateService.getInvalidTxs();
+ }
+
+ public List getIrregularTxs() {
+ return daoStateService.getIrregularTxs();
+ }
+
+ public long getTotalAmountOfUnspentTxOutputs() {
+ // Does not consider confiscated outputs (they stay as utxo)
+ return daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum();
+ }
+
public Optional getLockTime(String txId) {
return daoStateService.getLockTime(txId);
}
@@ -589,7 +612,7 @@ public int getNumIssuanceTransactions(IssuanceType issuanceType) {
return daoStateService.getIssuanceSet(issuanceType).size();
}
- public Set getFeeTxs() {
+ public Set getBurntFeeTxs() {
return daoStateService.getBurntFeeTxs();
}
diff --git a/core/src/main/java/bisq/core/dao/governance/ballot/BallotListPresentation.java b/core/src/main/java/bisq/core/dao/governance/ballot/BallotListPresentation.java
index ada2b27499a..e7e148061a3 100644
--- a/core/src/main/java/bisq/core/dao/governance/ballot/BallotListPresentation.java
+++ b/core/src/main/java/bisq/core/dao/governance/ballot/BallotListPresentation.java
@@ -45,7 +45,6 @@
public class BallotListPresentation implements BallotListService.BallotListChangeListener, DaoStateListener {
private final BallotListService ballotListService;
private final PeriodService periodService;
- private final DaoStateService daoStateService;
private final ProposalValidatorProvider proposalValidatorProvider;
@Getter
@@ -65,31 +64,20 @@ public BallotListPresentation(BallotListService ballotListService,
ProposalValidatorProvider proposalValidatorProvider) {
this.ballotListService = ballotListService;
this.periodService = periodService;
- this.daoStateService = daoStateService;
this.proposalValidatorProvider = proposalValidatorProvider;
daoStateService.addDaoStateListener(this);
ballotListService.addListener(this);
}
+
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
- @Override
- public void onNewBlockHeight(int blockHeight) {
- if (daoStateService.isParseBlockChainComplete()) {
- ballotsOfCycle.setPredicate(ballot -> periodService.isTxInCorrectCycle(ballot.getTxId(), blockHeight));
- }
- }
-
- @Override
- public void onParseBlockChainComplete() {
- ballotsOfCycle.setPredicate(ballot -> periodService.isTxInCorrectCycle(ballot.getTxId(), daoStateService.getChainHeight()));
- }
-
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
+ ballotsOfCycle.setPredicate(ballot -> periodService.isTxInCorrectCycle(ballot.getTxId(), block.getHeight()));
onListChanged(ballotListService.getValidatedBallotList());
}
diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVote.java b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVote.java
index c5fa1c92cf8..09e961bcf16 100644
--- a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVote.java
+++ b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVote.java
@@ -21,6 +21,7 @@
import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload;
+import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.Utilities;
import io.bisq.generated.protobuffer.PB;
@@ -65,7 +66,7 @@ public BlindVote(byte[] encryptedVotes,
this.txId = txId;
this.stake = stake;
this.encryptedMeritList = encryptedMeritList;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteListService.java
index eb920dcbc61..bafaeb98aac 100644
--- a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteListService.java
+++ b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteListService.java
@@ -136,20 +136,20 @@ private void fillListFromAppendOnlyDataStore() {
p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().forEach(e -> onAppendOnlyDataAdded(e, false));
}
- private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean doLog) {
+ private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean fromBroadcastMessage) {
if (persistableNetworkPayload instanceof BlindVotePayload) {
BlindVotePayload blindVotePayload = (BlindVotePayload) persistableNetworkPayload;
if (!blindVotePayloads.contains(blindVotePayload)) {
BlindVote blindVote = blindVotePayload.getBlindVote();
String txId = blindVote.getTxId();
- // We don't check the phase and the cycle as we want to add all object independently when we receive it
- // (or when we start the app to fill our list from the data we gor from the seed node).
+ // We don't validate as we might receive blindVotes from other cycles or phases at startup.
+ // Beside that we might receive payloads we requested at the vote result phase in case we missed some
+ // payloads. We prefer here resilience over protection against late publishing attacks.
if (blindVoteValidator.areDataFieldsValid(blindVote)) {
- // We don't validate as we might receive blindVotes from other cycles or phases at startup.
- blindVotePayloads.add(blindVotePayload);
- if (doLog) {
+ if (fromBroadcastMessage) {
log.info("We received a blindVotePayload. blindVoteTxId={}", txId);
}
+ blindVotePayloads.add(blindVotePayload);
} else {
log.warn("We received an invalid blindVotePayload. blindVoteTxId={}", txId);
}
diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java
index e16542887f4..4b1035a25e8 100644
--- a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java
+++ b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteValidator.java
@@ -17,12 +17,15 @@
package bisq.core.dao.governance.blindvote;
+import bisq.core.btc.wallet.Restrictions;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.governance.proposal.ProposalValidationException;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.governance.DaoPhase;
+import bisq.common.util.ExtraDataMapValidator;
+
import javax.inject.Inject;
import java.util.Optional;
@@ -58,12 +61,20 @@ private void validateDataFields(BlindVote blindVote) throws ProposalValidationEx
checkNotNull(blindVote.getEncryptedVotes(), "encryptedProposalList must not be null");
checkArgument(blindVote.getEncryptedVotes().length > 0,
"encryptedProposalList must not be empty");
- checkNotNull(blindVote.getTxId(), "txId must not be null");
- checkArgument(!blindVote.getTxId().isEmpty(), "txId must not be empty");
- checkArgument(blindVote.getStake() > 0, "stake must be positive");
+ checkArgument(blindVote.getEncryptedVotes().length <= 100000,
+ "encryptedProposalList must not exceed 100kb");
+
+ checkNotNull(blindVote.getTxId(), "Tx ID must not be null");
+ checkArgument(blindVote.getTxId().length() == 64, "Tx ID must be 64 chars");
+ checkArgument(blindVote.getStake() >= Restrictions.getMinNonDustOutput().value, "Stake must be at least MinNonDustOutput");
+
checkNotNull(blindVote.getEncryptedMeritList(), "getEncryptedMeritList must not be null");
checkArgument(blindVote.getEncryptedMeritList().length > 0,
"getEncryptedMeritList must not be empty");
+ checkArgument(blindVote.getEncryptedMeritList().length <= 100000,
+ "getEncryptedMeritList must not exceed 100kb");
+
+ ExtraDataMapValidator.validate(blindVote.getExtraDataMap());
} catch (Throwable e) {
log.warn(e.toString());
throw new ProposalValidationException(e);
diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
index 443f6d0a9db..bf25bac3505 100644
--- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
+++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
@@ -135,7 +135,7 @@ public MyBlindVoteListService(P2PService p2PService,
this.myVoteListService = myVoteListService;
this.myProposalListService = myProposalListService;
- numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishOnceWellConnected();
+ numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishMyBlindVoteOnceWellConnected();
}
@@ -176,7 +176,7 @@ public void readPersisted() {
@Override
public void onParseBlockChainComplete() {
- rePublishOnceWellConnected();
+ rePublishMyBlindVoteOnceWellConnected();
}
@@ -351,15 +351,19 @@ private Transaction getBlindVoteTx(Coin stake, Coin fee, byte[] opReturnData)
return bsqWalletService.signTx(txWithBtcFee);
}
- private void rePublishOnceWellConnected() {
+ private void rePublishMyBlindVoteOnceWellConnected() {
+ // We republish at each startup at any block during the cycle. We filter anyway for valid blind votes
+ // of that cycle so it is 1 blind vote getting rebroadcast at each startup to my neighbors.
+ // Republishing only will have effect if the payload creation date is < 5 hours as other nodes would not
+ // accept payloads which are too old or are in future.
+ // Only payloads received from seed nodes would ignore that date check.
int minPeers = BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? 4 : 1;
if ((p2PService.getNumConnectedPeers().get() >= minPeers && p2PService.isBootstrapped()) ||
BisqEnvironment.getBaseCurrencyNetwork().isRegtest()) {
- int chainHeight = periodService.getChainHeight();
myBlindVoteList.stream()
.filter(blindVote -> periodService.isTxInPhaseAndCycle(blindVote.getTxId(),
DaoPhase.Phase.BLIND_VOTE,
- chainHeight))
+ periodService.getChainHeight()))
.forEach(blindVote -> addToP2PNetwork(blindVote, null));
// We delay removal of listener as we call that inside listener itself.
@@ -369,13 +373,15 @@ private void rePublishOnceWellConnected() {
private void addToP2PNetwork(BlindVote blindVote, @Nullable ErrorMessageHandler errorMessageHandler) {
BlindVotePayload blindVotePayload = new BlindVotePayload(blindVote);
+ // We use reBroadcast flag here as we only broadcast our own blindVote and want to be sure it gets distributed
+ // well.
boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);
if (success) {
log.info("We added a blindVotePayload to the P2P network as append only data. blindVoteTxId={}",
blindVote.getTxId());
} else {
- final String msg = "Adding of blindVotePayload to P2P network failed. blindVoteTxId=" + blindVote.getTxId();
+ String msg = "Adding of blindVotePayload to P2P network failed. blindVoteTxId=" + blindVote.getTxId();
log.error(msg);
if (errorMessageHandler != null)
errorMessageHandler.handleErrorMessage(msg);
diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java b/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java
index 569796343b8..d5f77c6035c 100644
--- a/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java
+++ b/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java
@@ -92,7 +92,8 @@ public void sendRepublishRequest() {
private void sendRepublishRequest(NodeAddress nodeAddress) {
RepublishGovernanceDataRequest republishGovernanceDataRequest = new RepublishGovernanceDataRequest();
if (timeoutTimer == null) {
- timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
+ timeoutTimer = UserThread.runAfter(() -> {
+ // setup before sending to avoid race conditions
if (!stopped) {
String errorMessage = "A timeout occurred at sending republishGovernanceDataRequest:" +
" to nodeAddress:" + nodeAddress;
diff --git a/core/src/main/java/bisq/core/dao/governance/merit/MeritConsensus.java b/core/src/main/java/bisq/core/dao/governance/merit/MeritConsensus.java
index b77347495ce..29d0a03167b 100644
--- a/core/src/main/java/bisq/core/dao/governance/merit/MeritConsensus.java
+++ b/core/src/main/java/bisq/core/dao/governance/merit/MeritConsensus.java
@@ -54,20 +54,19 @@ public static MeritList decryptMeritList(byte[] encryptedMeritList, SecretKey se
}
public static long getMeritStake(String blindVoteTxId, MeritList meritList, DaoStateService daoStateService) {
- int txChainHeight = daoStateService.getTx(blindVoteTxId).map(Tx::getBlockHeight).orElse(0);
- return getMeritStake(blindVoteTxId, meritList, txChainHeight);
- }
-
- private static long getMeritStake(String blindVoteTxId, MeritList meritList, int txChainHeight) {
// We need to take the chain height when the blindVoteTx got published so we get the same merit for the vote even at
// later blocks (merit decreases with each block).
- if (txChainHeight == 0) {
+ int blindVoteTxHeight = daoStateService.getTx(blindVoteTxId).map(Tx::getBlockHeight).orElse(0);
+ if (blindVoteTxHeight == 0) {
log.error("Error at getMeritStake: blindVoteTx not found in daoStateService. blindVoteTxId=" + blindVoteTxId);
return 0;
}
+ // We only use past issuance. In case we would calculate the merit after the vote result phase we have the
+ // issuance from the same cycle but we must not add that to the merit.
return meritList.getList().stream()
.filter(merit -> isSignatureValid(merit.getSignature(), merit.getIssuance().getPubKey(), blindVoteTxId))
+ .filter(merit -> merit.getIssuance().getChainHeight() <= blindVoteTxHeight)
.mapToLong(merit -> {
try {
Issuance issuance = merit.getIssuance();
@@ -75,7 +74,7 @@ private static long getMeritStake(String blindVoteTxId, MeritList meritList, int
"issuance must be of type COMPENSATION");
return getWeightedMeritAmount(issuance.getAmount(),
issuance.getChainHeight(),
- txChainHeight,
+ blindVoteTxHeight,
BLOCKS_PER_YEAR);
} catch (Throwable t) {
log.error("Error at getMeritStake: error={}, merit={}", t.toString(), merit);
@@ -145,17 +144,20 @@ public static long getWeightedMeritAmount(long amount, int issuanceHeight, int b
public static long getCurrentlyAvailableMerit(MeritList meritList, int currentChainHeight) {
// We need to take the chain height when the blindVoteTx got published so we get the same merit for the vote even at
// later blocks (merit decreases with each block).
+ // We add 1 block to currentChainHeight so that the displayed merit would match the merit in case we get the
+ // blind vote tx into the next block.
+ int height = currentChainHeight + 1;
return meritList.getList().stream()
.mapToLong(merit -> {
try {
Issuance issuance = merit.getIssuance();
checkArgument(issuance.getIssuanceType() == IssuanceType.COMPENSATION, "issuance must be of type COMPENSATION");
int issuanceHeight = issuance.getChainHeight();
- checkArgument(issuanceHeight <= currentChainHeight,
+ checkArgument(issuanceHeight <= height,
"issuanceHeight must not be larger as currentChainHeight");
return getWeightedMeritAmount(issuance.getAmount(),
issuanceHeight,
- currentChainHeight,
+ height,
BLOCKS_PER_YEAR);
} catch (Throwable t) {
log.error("Error at getCurrentlyAvailableMerit: " + t.toString());
diff --git a/core/src/main/java/bisq/core/dao/governance/myvote/MyVote.java b/core/src/main/java/bisq/core/dao/governance/myvote/MyVote.java
index 92c3ac8d925..507eaaadb70 100644
--- a/core/src/main/java/bisq/core/dao/governance/myvote/MyVote.java
+++ b/core/src/main/java/bisq/core/dao/governance/myvote/MyVote.java
@@ -126,7 +126,7 @@ public static MyVote fromProto(PB.MyVote proto) {
// API
///////////////////////////////////////////////////////////////////////////////////////////
- public String getTxId() {
+ public String getBlindVoteTxId() {
return blindVote.getTxId();
}
diff --git a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java
index fd7421e6645..169aeb21e03 100644
--- a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java
+++ b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java
@@ -64,7 +64,7 @@ public static PersistableEnvelope fromProto(PB.MyVoteList proto) {
@Override
public String toString() {
return "List of TxId's in MyVoteList: " + getList().stream()
- .map(MyVote::getTxId)
+ .map(MyVote::getBlindVoteTxId)
.collect(Collectors.toList());
}
}
diff --git a/core/src/main/java/bisq/core/dao/governance/param/Param.java b/core/src/main/java/bisq/core/dao/governance/param/Param.java
index c8aed6948d3..81fa09ede95 100644
--- a/core/src/main/java/bisq/core/dao/governance/param/Param.java
+++ b/core/src/main/java/bisq/core/dao/governance/param/Param.java
@@ -142,7 +142,9 @@ public enum Param {
"4" : // regtest
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ?
"144" : // daoBetaNet; 1 day
- "380", // testnet or dao testnet (server side regtest); 2.6 days
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "134" : // dao regtest; 0.93 days
+ "380", // testnet or dao testnet (server side regtest); 2.6 days
ParamType.BLOCK, 2, 2),
PHASE_BREAK1(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ?
"149" : // mainnet; 1 day
@@ -150,7 +152,9 @@ public enum Param {
"1" : // regtest
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ?
"10" : // daoBetaNet
- "10", // testnet or dao testnet (server side regtest)
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "10" : // dao regtest
+ "10", // testnet or dao testnet (server side regtest)
ParamType.BLOCK, 2, 2),
PHASE_BLIND_VOTE(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ?
"451" : // mainnet; 3 days
@@ -158,7 +162,9 @@ public enum Param {
"2" : // regtest
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ?
"144" : // daoBetaNet; 1 day
- "300", // testnet or dao testnet (server side regtest); 2 days
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "134" : // dao regtest; 0.93 days
+ "300", // testnet or dao testnet (server side regtest); 2 days
ParamType.BLOCK, 2, 2),
PHASE_BREAK2(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ?
"9" : // mainnet
@@ -166,7 +172,9 @@ public enum Param {
"1" : // regtest
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ?
"10" : // daoBetaNet
- "10", // testnet or dao testnet (server side regtest)
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "10" : // dao regtest
+ "10", // testnet or dao testnet (server side regtest)
ParamType.BLOCK, 2, 2),
PHASE_VOTE_REVEAL(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ?
"451" : // mainnet; 3 days
@@ -174,7 +182,9 @@ public enum Param {
"2" : // regtest
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ?
"144" : // daoBetaNet; 1 day
- "300", // testnet or dao testnet (server side regtest); 2 days
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "132" : // dao regtest; 0.93 days
+ "300", // testnet or dao testnet (server side regtest); 2 days
ParamType.BLOCK, 2, 2),
PHASE_BREAK3(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ?
"9" : // mainnet
@@ -182,7 +192,9 @@ public enum Param {
"1" : // regtest
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ?
"10" : // daoBetaNet
- "10", // testnet or dao testnet (server side regtest)
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "10" : // dao regtest
+ "10", // testnet or dao testnet (server side regtest)
ParamType.BLOCK, 2, 2),
PHASE_RESULT(BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ?
"10" : // mainnet
@@ -190,7 +202,9 @@ public enum Param {
"2" : // regtest
BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet() ?
"10" : // daoBetaNet
- "2", // testnet or dao testnet (server side regtest)
+ BisqEnvironment.getBaseCurrencyNetwork().isDaoRegTest() ?
+ "2" : // dao regtest
+ "2", // testnet or dao testnet (server side regtest)
ParamType.BLOCK, 2, 2);
@Getter
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java
index 3649cedcb12..0357ce7958e 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java
@@ -30,6 +30,7 @@
import bisq.network.p2p.P2PService;
+import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import bisq.common.crypto.KeyRing;
import bisq.common.handlers.ErrorMessageHandler;
@@ -93,7 +94,7 @@ public MyProposalListService(P2PService p2PService,
signaturePubKey = keyRing.getPubKeyRing().getSignaturePubKey();
- numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishOnceWellConnected();
+ numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishMyProposalsOnceWellConnected();
daoStateService.addDaoStateListener(this);
p2PService.getNumConnectedPeers().addListener(numConnectedPeersListener);
}
@@ -122,7 +123,7 @@ public void readPersisted() {
@Override
public void onParseBlockChainComplete() {
- rePublishOnceWellConnected();
+ rePublishMyProposalsOnceWellConnected();
}
@@ -216,26 +217,23 @@ private boolean addToP2PNetworkAsProtectedData(Proposal proposal) {
return p2PService.addProtectedStorageEntry(new TempProposalPayload(proposal, signaturePubKey), true);
}
- private void rePublishOnceWellConnected() {
+ private void rePublishMyProposalsOnceWellConnected() {
+ // We republish at each startup at any block during the cycle. We filter anyway for valid blind votes
+ // of that cycle so it is 1 blind vote getting rebroadcast at each startup to my neighbors.
int minPeers = BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? 4 : 1;
if ((p2PService.getNumConnectedPeers().get() >= minPeers && p2PService.isBootstrapped()) ||
BisqEnvironment.getBaseCurrencyNetwork().isRegtest()) {
- p2PService.getNumConnectedPeers().removeListener(numConnectedPeersListener);
- rePublish();
+ myProposalList.stream()
+ .filter(proposal -> periodService.isTxInPhaseAndCycle(proposal.getTxId(),
+ DaoPhase.Phase.PROPOSAL,
+ periodService.getChainHeight()))
+ .forEach(this::addToP2PNetworkAsProtectedData);
+
+ // We delay removal of listener as we call that inside listener itself.
+ UserThread.execute(() -> p2PService.getNumConnectedPeers().removeListener(numConnectedPeersListener));
}
}
- private void rePublish() {
- myProposalList.forEach(proposal -> {
- final String txId = proposal.getTxId();
- if (periodService.isTxInPhaseAndCycle(txId, DaoPhase.Phase.PROPOSAL, periodService.getChainHeight())) {
- boolean result = addToP2PNetworkAsProtectedData(proposal);
- if (!result)
- log.warn("Adding of proposal to P2P network failed.\nproposal=" + proposal);
- }
- });
- }
-
private void persist() {
storage.queueUpForSave();
}
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java
index cb264b875f8..464d840ef96 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java
@@ -159,11 +159,12 @@ public void onAdded(PersistableNetworkPayload payload) {
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
- int heightForRepublishing = periodService.getFirstBlockOfPhase(daoStateService.getChainHeight(), DaoPhase.Phase.BREAK1);
- if (block.getHeight() == heightForRepublishing) {
+ // We try to broadcast at any block in the break1 phase. If we have received the data already we do not
+ // broadcast so we do not flood the network.
+ if (periodService.isInPhase(block.getHeight(), DaoPhase.Phase.BREAK1)) {
// We only republish if we are completed with parsing old blocks, otherwise we would republish old
// proposals all the time
- publishToAppendOnlyDataStore();
+ maybePublishToAppendOnlyDataStore();
fillListFromAppendOnlyDataStore();
}
}
@@ -214,36 +215,49 @@ private void fillListFromAppendOnlyDataStore() {
p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().forEach(e -> onAppendOnlyDataAdded(e, false));
}
- private void publishToAppendOnlyDataStore() {
+ private void maybePublishToAppendOnlyDataStore() {
+ // We set reBroadcast to false to avoid to flood the network.
+ // If we have 20 proposals and 200 nodes with 10 neighbor peers we would send 40 000 messages if we would set
+ // reBroadcast to !
tempProposals.stream()
.filter(proposal -> validatorProvider.getValidator(proposal).isValidAndConfirmed(proposal))
.map(ProposalPayload::new)
.forEach(proposalPayload -> {
- boolean success = p2PService.addPersistableNetworkPayload(proposalPayload, true);
- if (success)
+ boolean success = p2PService.addPersistableNetworkPayload(proposalPayload, false);
+ if (success) {
log.info("We published a ProposalPayload to the P2P network as append-only data. proposalTxId={}",
proposalPayload.getProposal().getTxId());
- else
- log.warn("publishToAppendOnlyDataStore failed for proposal " + proposalPayload.getProposal());
+ }
+ // If we had data already we did not broadcast and success is false
});
}
- private void onProtectedDataAdded(ProtectedStorageEntry entry, boolean doLog) {
+ private void onProtectedDataAdded(ProtectedStorageEntry entry, boolean fromBroadcastMessage) {
ProtectedStoragePayload protectedStoragePayload = entry.getProtectedStoragePayload();
if (protectedStoragePayload instanceof TempProposalPayload) {
Proposal proposal = ((TempProposalPayload) protectedStoragePayload).getProposal();
// We do not validate if we are in current cycle and if tx is confirmed yet as the tx might be not
- // available/confirmed. But we check if we are in the proposal phase.
- if (!tempProposals.contains(proposal)) {
- if (validatorProvider.getValidator(proposal).isValidOrUnconfirmed(proposal)) {
- if (doLog) {
- log.info("We received a TempProposalPayload and store it to our protectedStoreList. proposalTxId={}",
- proposal.getTxId());
+ // available/confirmed.
+ // We check if we are in the proposal or break1 phase. We are tolerant to accept tempProposals in the break1
+ // phase to avoid risks that a proposal published very closely to the end of the proposal phase will not be
+ // sufficiently broadcast.
+ // When we receive tempProposals from the seed node at startup we only keep those which are in the current
+ // proposal/break1 phase if we are in that phase. We ignore tempProposals in case we are not in the
+ // proposal/break1 phase as they are not used anyway but the proposalPayloads will be relevant once we
+ // left the proposal/break1 phase.
+ if (periodService.isInPhase(daoStateService.getChainHeight(), DaoPhase.Phase.PROPOSAL) ||
+ periodService.isInPhase(daoStateService.getChainHeight(), DaoPhase.Phase.BREAK1)) {
+ if (!tempProposals.contains(proposal)) {
+ if (validatorProvider.getValidator(proposal).areDataFieldsValid(proposal)) {
+ if (fromBroadcastMessage) {
+ log.info("We received a TempProposalPayload and store it to our protectedStoreList. proposalTxId={}",
+ proposal.getTxId());
+ }
+ tempProposals.add(proposal);
+ } else {
+ log.debug("We received an invalid proposal from the P2P network. Proposal.txId={}, blockHeight={}",
+ proposal.getTxId(), daoStateService.getChainHeight());
}
- tempProposals.add(proposal);
- } else {
- log.debug("We received an invalid proposal from the P2P network. Proposal.txId={}, blockHeight={}",
- proposal.getTxId(), daoStateService.getChainHeight());
}
}
}
@@ -275,13 +289,17 @@ private void onProtectedDataRemoved(ProtectedStorageEntry entry) {
}
}
- private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean doLog) {
+ private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean fromBroadcastMessage) {
if (persistableNetworkPayload instanceof ProposalPayload) {
ProposalPayload proposalPayload = (ProposalPayload) persistableNetworkPayload;
if (!proposalPayloads.contains(proposalPayload)) {
Proposal proposal = proposalPayload.getProposal();
+
+ // We don't validate phase and cycle as we might receive proposals from other cycles or phases at startup.
+ // Beside that we might receive payloads we requested at the vote result phase in case we missed some
+ // payloads. We prefer here resilience over protection against late publishing attacks.
if (validatorProvider.getValidator(proposal).areDataFieldsValid(proposal)) {
- if (doLog) {
+ if (fromBroadcastMessage) {
log.info("We received a ProposalPayload and store it to our appendOnlyStoreList. proposalTxId={}",
proposal.getTxId());
}
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java
index 66acd772ec3..e0f0118b1cc 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalValidator.java
@@ -28,10 +28,13 @@
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.ReimbursementProposal;
+import bisq.common.util.ExtraDataMapValidator;
+
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang3.Validate.notEmpty;
/**
@@ -61,6 +64,12 @@ public void validateDataFields(Proposal proposal) throws ProposalValidationExcep
try {
notEmpty(proposal.getName(), "name must not be empty");
notEmpty(proposal.getLink(), "link must not be empty");
+ checkArgument(proposal.getName().length() <= 200, "Name must not exceed 200 chars");
+ checkArgument(proposal.getLink().length() <= 200, "Link must not exceed 200 chars");
+ if (proposal.getTxId() != null)
+ checkArgument(proposal.getTxId().length() == 64, "Tx ID must be 64 chars");
+
+ ExtraDataMapValidator.validate(proposal.getExtraDataMap());
} catch (Throwable throwable) {
throw new ProposalValidationException(throwable);
}
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondValidator.java b/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondValidator.java
index dd6d68225db..02124b7949f 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondValidator.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondValidator.java
@@ -29,6 +29,7 @@
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang3.Validate.notEmpty;
/**
@@ -48,6 +49,7 @@ public void validateDataFields(Proposal proposal) throws ProposalValidationExcep
super.validateDataFields(proposal);
ConfiscateBondProposal confiscateBondProposal = (ConfiscateBondProposal) proposal;
notEmpty(confiscateBondProposal.getLockupTxId(), "LockupTxId must not be empty");
+ checkArgument(confiscateBondProposal.getLockupTxId().length() == 64, "LockupTxId must be 64 chars");
} catch (ProposalValidationException e) {
throw e;
} catch (Throwable throwable) {
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamValidator.java b/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamValidator.java
index 8e86d9976a1..e209bc204f7 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamValidator.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamValidator.java
@@ -65,6 +65,7 @@ public void validateDataFields(Proposal proposal) throws ProposalValidationExcep
super.validateDataFields(proposal);
ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal;
validateParamValue(changeParamProposal.getParam(), changeParamProposal.getParamValue(), getBlockHeight(proposal));
+ checkArgument(changeParamProposal.getParamValue().length() <= 200, "ParamValue must not exceed 200 chars");
} catch (ProposalValidationException e) {
throw e;
} catch (Throwable throwable) {
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetValidator.java b/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetValidator.java
index 9a6f59d7c2f..a3712482c98 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetValidator.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetValidator.java
@@ -29,6 +29,7 @@
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang3.Validate.notEmpty;
/**
@@ -49,6 +50,10 @@ public void validateDataFields(Proposal proposal) throws ProposalValidationExcep
RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) proposal;
notEmpty(removeAssetProposal.getTickerSymbol(), "TickerSymbol must not be empty");
+
+ // We want to avoid that someone cause damage by inserting a super long string. Real ticker symbols
+ // are usually very short but we don't want to add additional restrictions here.
+ checkArgument(removeAssetProposal.getTickerSymbol().length() <= 100, "TickerSymbol must not exceed 100 chars");
} catch (ProposalValidationException e) {
throw e;
} catch (Throwable throwable) {
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java
index 8ba16390920..38b993f1e6d 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java
@@ -28,6 +28,7 @@
import bisq.common.app.Capability;
import bisq.common.crypto.Sig;
import bisq.common.proto.persistable.PersistablePayload;
+import bisq.common.util.ExtraDataMapValidator;
import io.bisq.generated.protobuffer.PB;
@@ -88,7 +89,7 @@ private TempProposalPayload(Proposal proposal,
@Nullable Map extraDataMap) {
this.proposal = proposal;
this.ownerPubKeyEncoded = ownerPubPubKeyEncoded;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
ownerPubKey = Sig.getPublicKeyFromBytes(ownerPubKeyEncoded);
}
diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java
index 6ee09711b15..8042ac5c735 100644
--- a/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java
+++ b/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java
@@ -43,6 +43,7 @@ public class MissingDataRequestService implements DaoSetupService {
private final BlindVoteListService blindVoteListService;
private final ProposalService proposalService;
private final P2PService p2PService;
+ private boolean reRepublishAllGovernanceDataDone;
@Inject
public MissingDataRequestService(RepublishGovernanceDataHandler republishGovernanceDataHandler,
@@ -77,38 +78,44 @@ public void sendRepublishRequest() {
republishGovernanceDataHandler.sendRepublishRequest();
}
+ // Can be triggered with shortcut ctrl+UP or alt+UP
public void reRepublishAllGovernanceData() {
- ObservableList proposalPayloads = proposalService.getProposalPayloads();
- proposalPayloads.forEach(proposalPayload -> {
- // We want a random delay between 0.1 and 30 sec. depending on the number of items
- int delay = Math.max(100, Math.min(30_000, new Random().nextInt(proposalPayloads.size() * 500)));
- UserThread.runAfter(() -> {
- boolean success = p2PService.addPersistableNetworkPayload(proposalPayload, true);
- String txId = proposalPayload.getProposal().getTxId();
- if (success) {
- log.debug("We received a RepublishGovernanceDataRequest and re-published a proposalPayload to " +
- "the P2P network as append only data. proposalTxId={}", txId);
- } else {
- log.error("Adding of proposalPayload to P2P network failed. proposalTxId={}", txId);
- }
- }, delay, TimeUnit.MILLISECONDS);
- });
-
- ObservableList blindVotePayloads = blindVoteListService.getBlindVotePayloads();
- blindVotePayloads
- .forEach(blindVotePayload -> {
- // We want a random delay between 0.1 and 30 sec. depending on the number of items
- int delay = Math.max(100, Math.min(30_000, new Random().nextInt(blindVotePayloads.size() * 500)));
- UserThread.runAfter(() -> {
- boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);
- String txId = blindVotePayload.getBlindVote().getTxId();
- if (success) {
- log.debug("We received a RepublishGovernanceDataRequest and re-published a blindVotePayload to " +
- "the P2P network as append only data. blindVoteTxId={}", txId);
- } else {
- log.error("Adding of blindVotePayload to P2P network failed. blindVoteTxId={}", txId);
- }
- }, delay, TimeUnit.MILLISECONDS);
- });
+ // We only want to do it once in case we would get flooded with requests.
+ if (!reRepublishAllGovernanceDataDone) {
+ reRepublishAllGovernanceDataDone = true;
+ ObservableList proposalPayloads = proposalService.getProposalPayloads();
+ proposalPayloads.forEach(proposalPayload -> {
+ // We want a random delay between 0.1 and 300 sec. depending on the number of items.
+ // We send all proposals including those from old cycles.
+ int delay = Math.max(100, Math.min(300_000, new Random().nextInt(proposalPayloads.size() * 1000)));
+ UserThread.runAfter(() -> {
+ boolean success = p2PService.addPersistableNetworkPayload(proposalPayload, true);
+ String txId = proposalPayload.getProposal().getTxId();
+ if (success) {
+ log.debug("We received a RepublishGovernanceDataRequest and re-published a proposalPayload to " +
+ "the P2P network as append only data. proposalTxId={}", txId);
+ } else {
+ log.error("Adding of proposalPayload to P2P network failed. proposalTxId={}", txId);
+ }
+ }, delay, TimeUnit.MILLISECONDS);
+ });
+
+ ObservableList blindVotePayloads = blindVoteListService.getBlindVotePayloads();
+ blindVotePayloads.forEach(blindVotePayload -> {
+ // We want a random delay between 0.1 and 300 sec. depending on the number of items.
+ // We send all blindVotes including those from old cycles.
+ int delay = Math.max(100, Math.min(300_000, new Random().nextInt(blindVotePayloads.size() * 1000)));
+ UserThread.runAfter(() -> {
+ boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);
+ String txId = blindVotePayload.getBlindVote().getTxId();
+ if (success) {
+ log.debug("We received a RepublishGovernanceDataRequest and re-published a blindVotePayload to " +
+ "the P2P network as append only data. blindVoteTxId={}", txId);
+ } else {
+ log.error("Adding of blindVotePayload to P2P network failed. blindVoteTxId={}", txId);
+ }
+ }, delay, TimeUnit.MILLISECONDS);
+ });
+ }
}
}
diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java
index 15c50a1b096..1f0301db8b8 100644
--- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java
+++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java
@@ -109,10 +109,15 @@ public static TxOutput getConnectedBlindVoteStakeOutput(Tx voteRevealTx, DaoStat
Optional optionalBlindVoteStakeOutput = daoStateService.getConnectedTxOutput(stakeTxInput);
checkArgument(optionalBlindVoteStakeOutput.isPresent(), "blindVoteStakeOutput must be present");
TxOutput blindVoteStakeOutput = optionalBlindVoteStakeOutput.get();
- checkArgument(blindVoteStakeOutput.getTxOutputType() == TxOutputType.BLIND_VOTE_LOCK_STAKE_OUTPUT,
- "blindVoteStakeOutput must be of type BLIND_VOTE_LOCK_STAKE_OUTPUT but is " +
- blindVoteStakeOutput.getTxOutputType() + ". VoteRevealTx=" + voteRevealTx);
+ if (blindVoteStakeOutput.getTxOutputType() != TxOutputType.BLIND_VOTE_LOCK_STAKE_OUTPUT) {
+ String message = "blindVoteStakeOutput must be of type BLIND_VOTE_LOCK_STAKE_OUTPUT but is " +
+ blindVoteStakeOutput.getTxOutputType();
+ log.warn(message + ". VoteRevealTx=" + voteRevealTx);
+ throw new VoteResultException.ValidationException(message + ". VoteRevealTxId=" + voteRevealTx.getId());
+ }
return blindVoteStakeOutput;
+ } catch (VoteResultException.ValidationException t) {
+ throw t;
} catch (Throwable t) {
throw new VoteResultException.ValidationException(t);
}
diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java
index b1be48bc1c7..03f5f7ffa9e 100644
--- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java
+++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java
@@ -358,29 +358,44 @@ private DecryptedBallotsWithMerits getDecryptedBallotsWithMerits(
private BallotList createBallotList(VoteWithProposalTxIdList voteWithProposalTxIdList)
throws VoteResultException.MissingBallotException {
- // We convert the list to a map with proposalTxId as key and the vote as value
- Map voteByTxIdMap = voteWithProposalTxIdList.stream()
- .filter(voteWithProposalTxId -> voteWithProposalTxId.getVote() != null)
- .collect(Collectors.toMap(VoteWithProposalTxId::getProposalTxId, VoteWithProposalTxId::getVote));
+ // voteWithProposalTxIdList is the list of ProposalTxId + vote from the blind vote (decrypted vote data)
- // We make a map with proposalTxId as key and the ballot as value out of our stored ballot list
- Map ballotByTxIdMap = ballotListService.getValidatedBallotList().stream()
+ // We convert the list to a map with proposalTxId as key and the vote as value. As the vote can be null we
+ // wrap it into an optional.
+ Map> voteByTxIdMap = voteWithProposalTxIdList.stream()
+ .collect(Collectors.toMap(VoteWithProposalTxId::getProposalTxId, e -> Optional.ofNullable(e.getVote())));
+
+ // We make a map with proposalTxId as key and the ballot as value out of our stored ballot list.
+ // This can contain ballots which have been added later and have a null value for the vote.
+ Map ballotByTxIdMap = ballotListService.getValidBallotsOfCycle().stream()
.collect(Collectors.toMap(Ballot::getTxId, ballot -> ballot));
+ // It could be that we missed some proposalPayloads.
+ // If we have votes with proposals which are not found in our ballots we add it to missingBallots.
List missingBallots = new ArrayList<>();
List ballots = voteByTxIdMap.entrySet().stream()
.map(entry -> {
String txId = entry.getKey();
if (ballotByTxIdMap.containsKey(txId)) {
- // why not use proposalList?
Ballot ballot = ballotByTxIdMap.get(txId);
// We create a new Ballot with the proposal from the ballot list and the vote from our decrypted votes
- Vote vote = entry.getValue();
// We clone the ballot instead applying the vote to the existing ballot from ballotListService
// The items from ballotListService.getBallotList() contains my votes.
- // Maybe we should cross verify if the vote we had in our local list matches my own vote we
- // received from the network?
- return new Ballot(ballot.getProposal(), vote);
+
+ if (ballot.getVote() != null) {
+ // If we had set a vote it was an own active vote
+ if (!entry.getValue().isPresent()) {
+ log.warn("We found a local vote but don't have that vote in the data from the " +
+ "blind vote. ballot={}", ballot);
+ } else if (ballot.getVote() != entry.getValue().get()) {
+ log.warn("We found a local vote but the vote from the " +
+ "blind vote does not match. ballot={}, vote from blindVote data={}",
+ ballot, entry.getValue().get());
+ }
+ }
+
+ // We only return accepted or rejected votes
+ return entry.getValue().map(vote -> new Ballot(ballot.getProposal(), vote)).orElse(null);
} else {
// We got a vote but we don't have the ballot (which includes the proposal)
// We add it to the missing list to handle it as exception later. We want all missing data so we
@@ -396,6 +411,17 @@ private BallotList createBallotList(VoteWithProposalTxIdList voteWithProposalTxI
if (!missingBallots.isEmpty())
throw new VoteResultException.MissingBallotException(ballots, missingBallots);
+ // If we received a proposal after we had already voted we consider it as an proposal withhold attack and
+ // treat the proposal as it was voted with a rejected vote.
+ ballotByTxIdMap.entrySet().stream()
+ .filter(e -> !voteByTxIdMap.keySet().contains(e.getKey()))
+ .map(Map.Entry::getValue)
+ .forEach(ballot -> {
+ log.warn("We have a proposal which was not part of our blind vote and reject it. " +
+ "Proposal ={}" + ballot.getProposal());
+ ballots.add(new Ballot(ballot.getProposal(), new Vote(false)));
+ });
+
// Let's keep the data more deterministic by sorting it by txId. Though we are not using the sorting.
ballots.sort(Comparator.comparing(Ballot::getTxId));
return new BallotList(ballots);
diff --git a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java
index e3433fa4043..5c208fe1436 100644
--- a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java
+++ b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java
@@ -32,7 +32,6 @@
import bisq.core.dao.governance.myvote.MyVote;
import bisq.core.dao.governance.myvote.MyVoteListService;
import bisq.core.dao.governance.period.PeriodService;
-import bisq.core.dao.node.BsqNodeProvider;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
@@ -42,6 +41,7 @@
import bisq.network.p2p.P2PService;
+import bisq.common.UserThread;
import bisq.common.util.Utilities;
import org.bitcoinj.core.InsufficientMoneyException;
@@ -102,8 +102,7 @@ public VoteRevealService(DaoStateService daoStateService,
BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
P2PService p2PService,
- WalletsManager walletsManager,
- BsqNodeProvider bsqNodeProvider) {
+ WalletsManager walletsManager) {
this.daoStateService = daoStateService;
this.blindVoteListService = blindVoteListService;
this.periodService = periodService;
@@ -179,9 +178,13 @@ private void maybeRevealVotes(int chainHeight) {
.filter(myVote -> myVote.getRevealTxId() == null) // we have not already revealed
.forEach(myVote -> {
boolean isInVoteRevealPhase = periodService.getPhaseForHeight(chainHeight) == DaoPhase.Phase.VOTE_REVEAL;
- boolean isBlindVoteTxInCorrectPhaseAndCycle = periodService.isTxInPhaseAndCycle(myVote.getTxId(), DaoPhase.Phase.BLIND_VOTE, chainHeight);
- if (isInVoteRevealPhase && isBlindVoteTxInCorrectPhaseAndCycle) {
- log.info("We call revealVote at blockHeight {} for blindVoteTxId {}", chainHeight, myVote.getTxId());
+ // If we would create the tx in the last block it would be confirmed in the best case in th next
+ // block which would be already the break and would invalidate the vote reveal.
+ boolean isLastBlockInPhase = chainHeight == periodService.getLastBlockOfPhase(chainHeight, DaoPhase.Phase.VOTE_REVEAL);
+ String blindVoteTxId = myVote.getBlindVoteTxId();
+ boolean isBlindVoteTxInCorrectPhaseAndCycle = periodService.isTxInPhaseAndCycle(blindVoteTxId, DaoPhase.Phase.BLIND_VOTE, chainHeight);
+ if (isInVoteRevealPhase && !isLastBlockInPhase && isBlindVoteTxInCorrectPhaseAndCycle) {
+ log.info("We call revealVote at blockHeight {} for blindVoteTxId {}", chainHeight, blindVoteTxId);
// Standard case that we are in the correct phase and cycle and create the reveal tx.
revealVote(myVote, true);
} else {
@@ -192,7 +195,7 @@ private void maybeRevealVotes(int chainHeight) {
boolean missedPhaseSameCycle = isAfterVoteRevealPhase && isBlindVoteTxInCorrectPhaseAndCycle;
// If we missed the cycle we don't care about the phase anymore.
- boolean isBlindVoteTxInPastCycle = periodService.isTxInPastCycle(myVote.getTxId(), chainHeight);
+ boolean isBlindVoteTxInPastCycle = periodService.isTxInPastCycle(blindVoteTxId, chainHeight);
if (missedPhaseSameCycle || isBlindVoteTxInPastCycle) {
// Exceptional case that the user missed the vote reveal phase. We still publish the vote
@@ -205,7 +208,7 @@ private void maybeRevealVotes(int chainHeight) {
// publish the vote reveal tx but are aware that is is invalid.
log.warn("We missed the vote reveal phase but publish now the tx to unlock our locked " +
"BSQ from the blind vote tx. BlindVoteTxId={}, blockHeight={}",
- myVote.getTxId(), chainHeight);
+ blindVoteTxId, chainHeight);
// We handle the exception here inside the stream iteration as we have not get triggered from an
// outside user intent anyway. We keep errors in a observable list so clients can observe that to
@@ -233,7 +236,7 @@ private void revealVote(MyVote myVote, boolean isInVoteRevealPhase) {
// myVote is already tested if it is in current cycle at maybeRevealVotes
// We expect that the blind vote tx and stake output is available. If not we throw an exception.
TxOutput stakeTxOutput = daoStateService.getUnspentBlindVoteStakeTxOutputs().stream()
- .filter(txOutput -> txOutput.getTxId().equals(myVote.getTxId()))
+ .filter(txOutput -> txOutput.getTxId().equals(myVote.getBlindVoteTxId()))
.findFirst()
.orElseThrow(() -> new VoteRevealException("stakeTxOutput is not found for myVote.", myVote));
@@ -253,7 +256,7 @@ private void revealVote(MyVote myVote, boolean isInVoteRevealPhase) {
} catch (IOException | WalletException | TransactionVerificationException
| InsufficientMoneyException e) {
voteRevealExceptions.add(new VoteRevealException("Exception at calling revealVote.",
- e, myVote.getTxId()));
+ e, myVote.getBlindVoteTxId()));
} catch (VoteRevealException e) {
voteRevealExceptions.add(e);
}
@@ -284,12 +287,18 @@ private Transaction getVoteRevealTx(TxOutput stakeTxOutput, byte[] opReturnData)
}
private void rePublishBlindVotePayloadList(List blindVoteList) {
+ // If we have 20 blind votes from 20 voters we would have 400 messages sent to their 10 neighbor peers.
+ // Most of the neighbors will already have the data so they will not continue broadcast.
+ // To not flood the network too much we use a long random delay to spread the load over 5 minutes.
+ // As this is only for extra resilience we don't care so much for the case that the user might shut down the
+ // app before we are finished with our delayed broadcast.
+ // We cannot set reBroadcast to false as otherwise it would not have any effect as we have the data already and
+ // broadcast would only be triggered at new data.
blindVoteList.stream()
.map(BlindVotePayload::new)
.forEach(blindVotePayload -> {
- boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);
- if (!success)
- log.warn("publishToAppendOnlyDataStore failed for blindVote " + blindVotePayload.getBlindVote());
+ UserThread.runAfterRandomDelay(() -> p2PService.addPersistableNetworkPayload(blindVotePayload, true),
+ 1, 300);
});
}
}
diff --git a/core/src/main/java/bisq/core/dao/monitoring/BlindVoteStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/BlindVoteStateMonitoringService.java
index 7690d7cbe22..6daf9e95ef7 100644
--- a/core/src/main/java/bisq/core/dao/monitoring/BlindVoteStateMonitoringService.java
+++ b/core/src/main/java/bisq/core/dao/monitoring/BlindVoteStateMonitoringService.java
@@ -159,7 +159,7 @@ public void onParseBlockChainComplete() {
// We wait for processing messages until we have completed batch processing
- // We request data from last 5 cycles. We ignore possible duration changes done by voting as that request
+ // We request data from last 5 cycles. We ignore possible duration changes done by voting.
// period is arbitrary anyway...
Cycle currentCycle = periodService.getCurrentCycle();
checkNotNull(currentCycle, "currentCycle must not be null");
diff --git a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java
index abf8240c881..41533981efd 100644
--- a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java
+++ b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java
@@ -20,13 +20,16 @@
import bisq.core.dao.DaoSetupService;
import bisq.core.dao.monitoring.model.DaoStateBlock;
import bisq.core.dao.monitoring.model.DaoStateHash;
+import bisq.core.dao.monitoring.model.UtxoMismatch;
import bisq.core.dao.monitoring.network.DaoStateNetworkService;
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
import bisq.core.dao.monitoring.network.messages.NewDaoStateHashMessage;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.GenesisTxInfo;
+import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.Block;
+import bisq.core.dao.state.model.governance.IssuanceType;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.Connection;
@@ -38,6 +41,9 @@
import org.apache.commons.lang3.ArrayUtils;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@@ -86,6 +92,8 @@ public interface Listener {
private boolean parseBlockChainComplete;
@Getter
private boolean isInConflict;
+ @Getter
+ private ObservableList utxoMismatches = FXCollections.observableArrayList();
///////////////////////////////////////////////////////////////////////////////////////////
@@ -134,6 +142,21 @@ public void onParseBlockChainComplete() {
daoStateNetworkService.requestHashesFromAllConnectedSeedNodes(fromHeight);
}
+ @Override
+ public void onDaoStateChanged(Block block) {
+ long genesisTotalSupply = daoStateService.getGenesisTotalSupply().value;
+ long compensationIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.COMPENSATION);
+ long reimbursementIssuance = daoStateService.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT);
+ long totalAmountOfBurntBsq = daoStateService.getTotalAmountOfBurntBsq();
+ // confiscated funds are still in the utxo set
+ long sumUtxo = daoStateService.getUnspentTxOutputMap().values().stream().mapToLong(BaseTxOutput::getValue).sum();
+ long sumBsq = genesisTotalSupply + compensationIssuance + reimbursementIssuance - totalAmountOfBurntBsq;
+
+ if (sumBsq != sumUtxo) {
+ utxoMismatches.add(new UtxoMismatch(block.getHeight(), sumUtxo, sumBsq));
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// StateNetworkService.Listener
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java
index bfd837b0704..91b62c649c0 100644
--- a/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java
+++ b/core/src/main/java/bisq/core/dao/monitoring/ProposalStateMonitoringService.java
@@ -161,7 +161,7 @@ public void onParseBlockChainComplete() {
// We wait for processing messages until we have completed batch processing
- // We request data from last 5 cycles. We ignore possible duration changes done by voting as that request
+ // We request data from last 5 cycles. We ignore possible duration changes done by voting.
// period is arbitrary anyway...
Cycle currentCycle = periodService.getCurrentCycle();
checkNotNull(currentCycle, "currentCycle must not be null");
diff --git a/core/src/main/java/bisq/core/dao/monitoring/model/UtxoMismatch.java b/core/src/main/java/bisq/core/dao/monitoring/model/UtxoMismatch.java
new file mode 100644
index 00000000000..1509023c922
--- /dev/null
+++ b/core/src/main/java/bisq/core/dao/monitoring/model/UtxoMismatch.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.dao.monitoring.model;
+
+import lombok.Value;
+
+@Value
+public class UtxoMismatch {
+ private final int height;
+ private final long sumUtxo;
+ private final long sumBsq;
+
+ public UtxoMismatch(int height, long sumUtxo, long sumBsq) {
+ this.height = height;
+ this.sumUtxo = sumUtxo;
+ this.sumBsq = sumBsq;
+ }
+}
diff --git a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java
index 712d32a4c64..d747395653f 100644
--- a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java
+++ b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java
@@ -193,7 +193,8 @@ private JsonTx getJsonTx(Tx tx) {
getJsonTxOutputs(tx),
jsonTxType,
jsonTxTypeDisplayString,
- daoStateService.getBurntFee(tx.getId()),
+ tx.getBurntFee(),
+ tx.getInvalidatedBsq(),
tx.getUnlockBlockHeight());
}
@@ -239,7 +240,8 @@ private List getJsonTxOutputs(Tx tx) {
btcAmount,
tx.getBlockHeight(),
isBsqTxOutputType,
- daoStateService.getBurntFee(tx.getId()),
+ tx.getBurntFee(),
+ tx.getInvalidatedBsq(),
txOutput.getAddress(),
scriptPubKey,
spentInfo,
diff --git a/core/src/main/java/bisq/core/dao/node/explorer/JsonTx.java b/core/src/main/java/bisq/core/dao/node/explorer/JsonTx.java
index a23ff2413d1..145826bf60a 100644
--- a/core/src/main/java/bisq/core/dao/node/explorer/JsonTx.java
+++ b/core/src/main/java/bisq/core/dao/node/explorer/JsonTx.java
@@ -36,9 +36,26 @@ class JsonTx {
private final JsonTxType txType;
private final String txTypeDisplayString;
private final long burntFee;
+ private final long invalidatedBsq;
// If not set it is -1. LockTime of 0 is a valid value.
private final int unlockBlockHeight;
+ JsonTx(String id, int blockHeight, String blockHash, long time, List inputs,
+ List outputs, JsonTxType txType, String txTypeDisplayString, long burntFee,
+ long invalidatedBsq, int unlockBlockHeight) {
+ this.id = id;
+ this.blockHeight = blockHeight;
+ this.blockHash = blockHash;
+ this.time = time;
+ this.inputs = inputs;
+ this.outputs = outputs;
+ this.txType = txType;
+ this.txTypeDisplayString = txTypeDisplayString;
+ this.burntFee = burntFee;
+ this.invalidatedBsq = invalidatedBsq;
+ this.unlockBlockHeight = unlockBlockHeight;
+ }
+
// Enums must not be used directly for hashCode or equals as it delivers the Object.hashCode (internal address)!
// The equals and hashCode methods cannot be overwritten in Enums.
@Override
@@ -50,6 +67,7 @@ public boolean equals(Object o) {
return blockHeight == jsonTx.blockHeight &&
time == jsonTx.time &&
burntFee == jsonTx.burntFee &&
+ invalidatedBsq == jsonTx.invalidatedBsq &&
unlockBlockHeight == jsonTx.unlockBlockHeight &&
Objects.equals(txVersion, jsonTx.txVersion) &&
Objects.equals(id, jsonTx.id) &&
@@ -62,7 +80,7 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
-
- return Objects.hash(super.hashCode(), txVersion, id, blockHeight, blockHash, time, inputs, outputs, txType.name(), txTypeDisplayString, burntFee, unlockBlockHeight);
+ return Objects.hash(super.hashCode(), txVersion, id, blockHeight, blockHash, time, inputs, outputs,
+ txType.name(), txTypeDisplayString, burntFee, invalidatedBsq, unlockBlockHeight);
}
}
diff --git a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutput.java b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutput.java
index a2cd3ea4b3e..65ce85374f5 100644
--- a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutput.java
+++ b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutput.java
@@ -35,6 +35,7 @@ class JsonTxOutput {
private final int height;
private final boolean isVerified; // isBsqTxOutputType
private final long burntFee;
+ private final long invalidatedBsq;
private final String address;
@Nullable
private final JsonScriptPubKey scriptPubKey;
@@ -50,6 +51,31 @@ class JsonTxOutput {
private final int lockTime;
private final boolean isUnspent;
+ JsonTxOutput(String txId, int index, long bsqAmount, long btcAmount, int height, boolean isVerified, long burntFee,
+ long invalidatedBsq, String address, JsonScriptPubKey scriptPubKey, JsonSpentInfo spentInfo,
+ long time, JsonTxType txType, String txTypeDisplayString, JsonTxOutputType txOutputType,
+ String txOutputTypeDisplayString, String opReturn, int lockTime, boolean isUnspent) {
+ this.txId = txId;
+ this.index = index;
+ this.bsqAmount = bsqAmount;
+ this.btcAmount = btcAmount;
+ this.height = height;
+ this.isVerified = isVerified;
+ this.burntFee = burntFee;
+ this.invalidatedBsq = invalidatedBsq;
+ this.address = address;
+ this.scriptPubKey = scriptPubKey;
+ this.spentInfo = spentInfo;
+ this.time = time;
+ this.txType = txType;
+ this.txTypeDisplayString = txTypeDisplayString;
+ this.txOutputType = txOutputType;
+ this.txOutputTypeDisplayString = txOutputTypeDisplayString;
+ this.opReturn = opReturn;
+ this.lockTime = lockTime;
+ this.isUnspent = isUnspent;
+ }
+
String getId() {
return txId + ":" + index;
}
@@ -68,6 +94,7 @@ public boolean equals(Object o) {
height == that.height &&
isVerified == that.isVerified &&
burntFee == that.burntFee &&
+ invalidatedBsq == that.invalidatedBsq &&
time == that.time &&
lockTime == that.lockTime &&
isUnspent == that.isUnspent &&
@@ -85,7 +112,8 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
-
- return Objects.hash(super.hashCode(), txVersion, txId, index, bsqAmount, btcAmount, height, isVerified, burntFee, address, scriptPubKey, spentInfo, time, txType.name(), txTypeDisplayString, txOutputType, txOutputTypeDisplayString, opReturn, lockTime, isUnspent);
+ return Objects.hash(super.hashCode(), txVersion, txId, index, bsqAmount, btcAmount, height, isVerified,
+ burntFee, invalidatedBsq, address, scriptPubKey, spentInfo, time, txType.name(), txTypeDisplayString,
+ txOutputType, txOutputTypeDisplayString, opReturn, lockTime, isUnspent);
}
}
diff --git a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java
index f0e972c6ea8..d14e17f8a56 100644
--- a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java
+++ b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java
@@ -36,7 +36,8 @@ enum JsonTxType {
LOCKUP("Lockup"),
UNLOCK("Unlock"),
ASSET_LISTING_FEE("Asset listing fee"),
- PROOF_OF_BURN("Proof of burn");
+ PROOF_OF_BURN("Proof of burn"),
+ IRREGULAR("Irregular");
@Getter
private String displayString;
diff --git a/core/src/main/java/bisq/core/dao/node/full/RpcService.java b/core/src/main/java/bisq/core/dao/node/full/RpcService.java
index 3225905e9f3..b956a288167 100644
--- a/core/src/main/java/bisq/core/dao/node/full/RpcService.java
+++ b/core/src/main/java/bisq/core/dao/node/full/RpcService.java
@@ -99,12 +99,10 @@ public RpcService(Preferences preferences,
boolean isPortSet = rpcPort != null && !rpcPort.isEmpty();
boolean isMainnet = BisqEnvironment.getBaseCurrencyNetwork().isMainnet();
boolean isTestnet = BisqEnvironment.getBaseCurrencyNetwork().isTestnet();
- boolean isDaoTestNet = BisqEnvironment.getBaseCurrencyNetwork().isDaoTestNet();
boolean isDaoBetaNet = BisqEnvironment.getBaseCurrencyNetwork().isDaoBetaNet();
this.rpcPort = isPortSet ? rpcPort :
isMainnet || isDaoBetaNet ? "8332" :
isTestnet ? "18332" :
- isDaoTestNet ? "18443" :
"18443"; // regtest
this.rpcBlockPort = rpcBlockPort != null && !rpcBlockPort.isEmpty() ? rpcBlockPort : "5125";
diff --git a/core/src/main/java/bisq/core/dao/node/parser/TempTx.java b/core/src/main/java/bisq/core/dao/node/parser/TempTx.java
index a2e37ff2f22..67d6c1c593a 100644
--- a/core/src/main/java/bisq/core/dao/node/parser/TempTx.java
+++ b/core/src/main/java/bisq/core/dao/node/parser/TempTx.java
@@ -55,12 +55,7 @@ static TempTx fromRawTx(RawTx rawTx) {
// Mutable data
@Nullable
private TxType txType;
- private long burntFee;
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // PROTO BUFFER
- ///////////////////////////////////////////////////////////////////////////////////////////
+ private long burntBsq;
private TempTx(String txVersion,
String id,
@@ -70,7 +65,7 @@ private TempTx(String txVersion,
ImmutableList txInputs,
ImmutableList tempTxOutputs,
@Nullable TxType txType,
- long burntFee) {
+ long burntBsq) {
super(txVersion,
id,
blockHeight,
@@ -79,7 +74,7 @@ private TempTx(String txVersion,
txInputs);
this.tempTxOutputs = tempTxOutputs;
this.txType = txType;
- this.burntFee = burntFee;
+ this.burntBsq = burntBsq;
}
@Override
@@ -87,7 +82,7 @@ public String toString() {
return "TempTx{" +
"\n txOutputs=" + tempTxOutputs +
",\n txType=" + txType +
- ",\n burntFee=" + burntFee +
+ ",\n burntBsq=" + burntBsq +
"\n} " + super.toString();
}
@@ -103,13 +98,13 @@ public boolean equals(Object o) {
String name = txType != null ? txType.name() : "";
String name1 = tempTx.txType != null ? tempTx.txType.name() : "";
boolean isTxTypeEquals = name.equals(name1);
- return burntFee == tempTx.burntFee &&
+ return burntBsq == tempTx.burntBsq &&
Objects.equals(tempTxOutputs, tempTx.tempTxOutputs) &&
isTxTypeEquals;
}
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), tempTxOutputs, txType, burntFee);
+ return Objects.hash(super.hashCode(), tempTxOutputs, txType, burntBsq);
}
}
diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java
index 5decbbbe4e4..b19fadfc781 100644
--- a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java
+++ b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java
@@ -142,7 +142,7 @@ private Optional findTx(RawTx rawTx) {
long burntBsq = remainingInputValue + burntBondValue;
boolean hasBurntBsq = burntBsq > 0;
if (hasBurntBsq)
- tempTx.setBurntFee(burntBsq);
+ tempTx.setBurntBsq(burntBsq);
//****************************************************************************************
@@ -150,18 +150,23 @@ private Optional findTx(RawTx rawTx) {
//****************************************************************************************
applyTxTypeAndTxOutputType(blockHeight, tempTx, remainingInputValue);
-
- TxType txType = evaluateTxType(tempTx, optionalOpReturnType, hasBurntBsq, unLockInputValid);
- tempTx.setTxType(txType);
+ TxType txType;
+ if (tempTx.getTxType() != TxType.IRREGULAR && tempTx.getTxType() != TxType.INVALID) {
+ txType = evaluateTxType(tempTx, optionalOpReturnType, hasBurntBsq, unLockInputValid);
+ tempTx.setTxType(txType);
+ } else {
+ txType = tempTx.getTxType();
+ }
if (isTxInvalid(tempTx, bsqOutputFound, hasBurntBond)) {
tempTx.setTxType(TxType.INVALID);
+ // We consider all BSQ inputs as burned if the tx is invalid.
+ tempTx.setBurntBsq(accumulatedInputValue);
txOutputParser.invalidateUTXOCandidates();
-
- if (hasBurntBsq) {
- log.warn("We have destroyed BSQ because of an invalid tx. Burned BSQ={}. tx={}",
- burntBsq / 100D, tempTx);
- }
+ log.warn("We have destroyed BSQ because of an invalid tx. Burned BSQ={}. tx={}", accumulatedInputValue / 100D, tempTx);
+ } else if (txType == TxType.IRREGULAR) {
+ log.warn("We have an irregular tx {}", tempTx);
+ txOutputParser.commitUTXOCandidates();
} else {
txOutputParser.commitUTXOCandidates();
}
@@ -231,8 +236,8 @@ private void applyTxTypeAndTxOutputType(int blockHeight, TempTx tempTx, long bsq
private void processProposal(int blockHeight, TempTx tempTx, long bsqFee) {
boolean isFeeAndPhaseValid = isFeeAndPhaseValid(tempTx.getId(), blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE);
if (!isFeeAndPhaseValid) {
- // TODO don't burn in such cases
- tempTx.setTxType(TxType.INVALID);
+ // We tolerate such an incorrect tx and do not burn the BSQ
+ tempTx.setTxType(TxType.IRREGULAR);
}
}
@@ -249,17 +254,17 @@ private void processIssuance(int blockHeight, TempTx tempTx, long bsqFee) {
"As the BSQ fee is set it must be either a buggy tx or an manually crafted invalid tx.");
// Even though the request part if invalid the BSQ transfer and change output should still be valid
// as long as the BSQ change <= BSQ inputs.
- // TODO do we want to burn in such a case?
- tempTx.setTxType(TxType.INVALID);
+ // We tolerate such an incorrect tx and do not burn the BSQ
+ tempTx.setTxType(TxType.IRREGULAR);
}
} else {
// This could be a valid compensation request that failed to be included in a block during the
// correct phase due to no fault of the user. Better not burn the change as long as the BSQ inputs
// cover the value of the outputs.
- // TODO don't burn in such cases
- tempTx.setTxType(TxType.INVALID);
+ // We tolerate such an incorrect tx and do not burn the BSQ
+ tempTx.setTxType(TxType.IRREGULAR);
- // TODO don't burn in such cases
+ // Make sure the optionalIssuanceCandidate is set to BTC
optionalIssuanceCandidate.ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
// Empty Optional case is a possible valid case where a random tx matches our opReturn rules but it is not a
// valid BSQ tx.
@@ -269,11 +274,11 @@ private void processIssuance(int blockHeight, TempTx tempTx, long bsqFee) {
private void processBlindVote(int blockHeight, TempTx tempTx, long bsqFee) {
boolean isFeeAndPhaseValid = isFeeAndPhaseValid(tempTx.getId(), blockHeight, bsqFee, DaoPhase.Phase.BLIND_VOTE, Param.BLIND_VOTE_FEE);
if (!isFeeAndPhaseValid) {
- // TODO don't burn in such cases
- tempTx.setTxType(TxType.INVALID);
+ // We tolerate such an incorrect tx and do not burn the BSQ
+ tempTx.setTxType(TxType.IRREGULAR);
- // TODO don't burn in such cases
- txOutputParser.getOptionalBlindVoteLockStakeOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
+ // Set the stake output from BLIND_VOTE_LOCK_STAKE_OUTPUT to BSQ
+ txOutputParser.getOptionalBlindVoteLockStakeOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BSQ_OUTPUT));
// Empty Optional case is a possible valid case where a random tx matches our opReturn rules but it is not a
// valid BSQ tx.
}
@@ -374,7 +379,6 @@ static TxType evaluateTxType(TempTx tempTx, Optional optionalOpRet
if (!isUnLockInputValid)
return TxType.INVALID;
- // UNLOCK tx has no fee, no OpReturn
return TxType.UNLOCK;
}
@@ -393,8 +397,7 @@ static TxType evaluateTxTypeFromOpReturnType(TempTx tempTx, OpReturnType opRetur
boolean hasCorrectNumOutputs = tempTx.getTempTxOutputs().size() >= 3;
if (!hasCorrectNumOutputs) {
log.warn("Compensation/reimbursement request tx need to have at least 3 outputs");
- // This is not an issuance request but it should still not burn the BSQ change
- // TODO do we want to burn in such a case?
+ // Such a transaction cannot be created by the Bisq client and is considered invalid.
return TxType.INVALID;
}
@@ -403,8 +406,7 @@ static TxType evaluateTxTypeFromOpReturnType(TempTx tempTx, OpReturnType opRetur
if (!hasIssuanceOutput) {
log.warn("Compensation/reimbursement request txOutput type of output at index 1 need to be ISSUANCE_CANDIDATE_OUTPUT. " +
"TxOutputType={}", issuanceTxOutput.getTxOutputType());
- // This is not an issuance request but it should still not burn the BSQ change
- // TODO do we want to burn in such a case?
+ // Such a transaction cannot be created by the Bisq client and is considered invalid.
return TxType.INVALID;
}
@@ -423,8 +425,9 @@ static TxType evaluateTxTypeFromOpReturnType(TempTx tempTx, OpReturnType opRetur
return TxType.PROOF_OF_BURN;
default:
log.warn("We got a BSQ tx with an unknown OP_RETURN. tx={}, opReturnType={}", tempTx, opReturnType);
- // TODO do we want to burn in such a case?
- return TxType.INVALID;
+ // We tolerate such an incorrect tx and do not burn the BSQ. We might need that in case we add new
+ // opReturn types in future.
+ return TxType.IRREGULAR;
}
}
}
diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java
index 0050af09cd2..05c6f3d9018 100644
--- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java
+++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java
@@ -355,6 +355,14 @@ public Optional getTx(String txId) {
return getTxStream().filter(tx -> tx.getId().equals(txId)).findAny();
}
+ public List getInvalidTxs() {
+ return getTxStream().filter(tx -> tx.getTxType() == TxType.INVALID).collect(Collectors.toList());
+ }
+
+ public List getIrregularTxs() {
+ return getTxStream().filter(tx -> tx.getTxType() == TxType.IRREGULAR).collect(Collectors.toList());
+ }
+
public boolean containsTx(String txId) {
return getTx(txId).isPresent();
}
@@ -382,9 +390,7 @@ public boolean hasTxBurntFee(String txId) {
}
public long getTotalBurntFee() {
- return getTxStream()
- .mapToLong(Tx::getBurntFee)
- .sum();
+ return getTxStream().mapToLong(Tx::getBurntFee).sum();
}
public Set getBurntFeeTxs() {
@@ -811,6 +817,15 @@ public long getTotalAmountOfConfiscatedTxOutputs() {
.sum();
}
+ public long getTotalAmountOfInvalidatedBsq() {
+ return getTxStream().mapToLong(Tx::getInvalidatedBsq).sum();
+ }
+
+ // Contains burnt fee and invalidated bsq due invalid txs
+ public long getTotalAmountOfBurntBsq() {
+ return getTxStream().mapToLong(Tx::getBurntBsq).sum();
+ }
+
// Confiscate bond
public void confiscateBond(String lockupTxId) {
Optional optionalTxOutput = getLockupTxOutput(lockupTxId);
diff --git a/core/src/main/java/bisq/core/dao/state/GenesisTxInfo.java b/core/src/main/java/bisq/core/dao/state/GenesisTxInfo.java
index e970b2e6a12..aced9faa3f1 100644
--- a/core/src/main/java/bisq/core/dao/state/GenesisTxInfo.java
+++ b/core/src/main/java/bisq/core/dao/state/GenesisTxInfo.java
@@ -46,7 +46,7 @@ public class GenesisTxInfo {
private static final String MAINNET_GENESIS_TX_ID = "81855816eca165f17f0668898faa8724a105196e90ffc4993f4cac980176674e";
private static final int MAINNET_GENESIS_BLOCK_HEIGHT = 524717; // 2018-05-27
- private static final Coin MAINNET_GENESIS_TOTAL_SUPPLY = Coin.parseCoin("2.5"); // 2.5M BSQ / 2.50000000 BTC
+ private static final Coin MAINNET_GENESIS_TOTAL_SUPPLY = Coin.parseCoin("0.001");
private static final String TESTNET_GENESIS_TX_ID = "09e70ce0ab7a962a82a2ca84c9ae8a89140bf1c3fb6f7efad6162e39e4b362ae";
private static final int TESTNET_GENESIS_BLOCK_HEIGHT = 1446300; // 2018-12-02
@@ -60,6 +60,10 @@ public class GenesisTxInfo {
private static final int DAO_BETANET_GENESIS_BLOCK_HEIGHT = 567405; // 2019-03-16
private static final Coin DAO_BETANET_GENESIS_TOTAL_SUPPLY = Coin.parseCoin("0.49998644"); // 499 986.44 BSQ / 0.49998644 BTC
+ private static final String DAO_REGTEST_GENESIS_TX_ID = "d594ad0c5de53e261b5784e5eb2acec8b807c45b74450401f488d36b8acf2e14";
+ private static final int DAO_REGTEST_GENESIS_BLOCK_HEIGHT = 104; // 2019-03-26
+ private static final Coin DAO_REGTEST_GENESIS_TOTAL_SUPPLY = Coin.parseCoin("2.5"); // 2.5M BSQ / 2.50000000 BTC
+
private static final String REGTEST_GENESIS_TX_ID = "30af0050040befd8af25068cc697e418e09c2d8ebd8d411d2240591b9ec203cf";
private static final int REGTEST_GENESIS_BLOCK_HEIGHT = 111;
private static final Coin REGTEST_GENESIS_TOTAL_SUPPLY = Coin.parseCoin("2.5"); // 2.5M BSQ / 2.50000000 BTC
@@ -108,6 +112,7 @@ public GenesisTxInfo(@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
boolean isTestnet = baseCurrencyNetwork.isTestnet();
boolean isDaoTestNet = baseCurrencyNetwork.isDaoTestNet();
boolean isDaoBetaNet = baseCurrencyNetwork.isDaoBetaNet();
+ boolean isDaoRegTest = baseCurrencyNetwork.isDaoRegTest();
boolean isRegtest = baseCurrencyNetwork.isRegtest();
if (!genesisTxId.isEmpty()) {
this.genesisTxId = genesisTxId;
@@ -119,6 +124,8 @@ public GenesisTxInfo(@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
this.genesisTxId = DAO_TESTNET_GENESIS_TX_ID;
} else if (isDaoBetaNet) {
this.genesisTxId = DAO_BETANET_GENESIS_TX_ID;
+ } else if (isDaoRegTest) {
+ this.genesisTxId = DAO_REGTEST_GENESIS_TX_ID;
} else if (isRegtest) {
this.genesisTxId = REGTEST_GENESIS_TX_ID;
} else {
@@ -135,6 +142,8 @@ public GenesisTxInfo(@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
this.genesisBlockHeight = DAO_TESTNET_GENESIS_BLOCK_HEIGHT;
} else if (isDaoBetaNet) {
this.genesisBlockHeight = DAO_BETANET_GENESIS_BLOCK_HEIGHT;
+ } else if (isDaoRegTest) {
+ this.genesisBlockHeight = DAO_REGTEST_GENESIS_BLOCK_HEIGHT;
} else if (isRegtest) {
this.genesisBlockHeight = REGTEST_GENESIS_BLOCK_HEIGHT;
} else {
@@ -151,6 +160,8 @@ public GenesisTxInfo(@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
this.genesisTotalSupply = DAO_TESTNET_GENESIS_TOTAL_SUPPLY.value;
} else if (isDaoBetaNet) {
this.genesisTotalSupply = DAO_BETANET_GENESIS_TOTAL_SUPPLY.value;
+ } else if (isDaoRegTest) {
+ this.genesisTotalSupply = DAO_REGTEST_GENESIS_TOTAL_SUPPLY.value;
} else if (isRegtest) {
this.genesisTotalSupply = REGTEST_GENESIS_TOTAL_SUPPLY.value;
} else {
diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/Tx.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/Tx.java
index 16365306113..63ca99189a5 100644
--- a/core/src/main/java/bisq/core/dao/state/model/blockchain/Tx.java
+++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/Tx.java
@@ -57,13 +57,14 @@ public static Tx fromTempTx(TempTx tempTx) {
tempTx.getTxInputs(),
txOutputs,
tempTx.getTxType(),
- tempTx.getBurntFee());
+ tempTx.getBurntBsq());
}
private final ImmutableList txOutputs;
@Nullable
private final TxType txType;
- private final long burntFee;
+ // Can be burned fee or in case of an invalid tx the burned BSQ from all BSQ inputs
+ private final long burntBsq;
///////////////////////////////////////////////////////////////////////////////////////////
@@ -78,7 +79,7 @@ private Tx(String txVersion,
ImmutableList txInputs,
ImmutableList txOutputs,
@Nullable TxType txType,
- long burntFee) {
+ long burntBsq) {
super(txVersion,
id,
blockHeight,
@@ -87,7 +88,7 @@ private Tx(String txVersion,
txInputs);
this.txOutputs = txOutputs;
this.txType = txType;
- this.burntFee = burntFee;
+ this.burntBsq = burntBsq;
}
@@ -97,7 +98,7 @@ public PB.BaseTx toProtoMessage() {
.addAllTxOutputs(txOutputs.stream()
.map(TxOutput::toProtoMessage)
.collect(Collectors.toList()))
- .setBurntFee(burntFee);
+ .setBurntBsq(burntBsq);
Optional.ofNullable(txType).ifPresent(txType -> builder.setTxType(txType.toProtoMessage()));
return getBaseTxBuilder().setTx(builder).build();
}
@@ -122,7 +123,7 @@ public static Tx fromProto(PB.BaseTx protoBaseTx) {
txInputs,
outputs,
TxType.fromProto(protoTx.getTxType()),
- protoTx.getBurntFee());
+ protoTx.getBurntBsq());
}
@@ -135,6 +136,18 @@ public TxOutput getLastTxOutput() {
}
+ public long getBurntBsq() {
+ return burntBsq;
+ }
+
+ public long getBurntFee() {
+ return txType == TxType.INVALID ? 0 : burntBsq;
+ }
+
+ public long getInvalidatedBsq() {
+ return txType == TxType.INVALID ? burntBsq : 0;
+ }
+
public int getLockTime() {
return getLockupOutput().getLockTime();
}
@@ -158,7 +171,7 @@ public String toString() {
return "Tx{" +
"\n txOutputs=" + txOutputs +
",\n txType=" + txType +
- ",\n burntFee=" + burntFee +
+ ",\n burntBsq=" + burntBsq +
"\n} " + super.toString();
}
@@ -175,13 +188,13 @@ public boolean equals(Object o) {
String name1 = tx.txType != null ? tx.txType.name() : "";
boolean isTxTypeEquals = name.equals(name1);
- return burntFee == tx.burntFee &&
+ return burntBsq == tx.burntBsq &&
Objects.equals(txOutputs, tx.txOutputs) &&
isTxTypeEquals;
}
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), txOutputs, txType, burntFee);
+ return Objects.hash(super.hashCode(), txOutputs, txType, burntBsq);
}
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java
index 8e5339db7fc..5083ae9690a 100644
--- a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java
+++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java
@@ -45,7 +45,8 @@ public enum TxType implements ImmutableDaoStateModel {
LOCKUP(true, false),
UNLOCK(true, false),
ASSET_LISTING_FEE(true, true),
- PROOF_OF_BURN(true, true);
+ PROOF_OF_BURN(true, true),
+ IRREGULAR(false, false); // the params are here irrelevant as we can have any tx which violated the rules set to irregular
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/Ballot.java b/core/src/main/java/bisq/core/dao/state/model/governance/Ballot.java
index 2e862e00035..b3b48e7f5c3 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/Ballot.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/Ballot.java
@@ -64,8 +64,7 @@ public Ballot(Proposal proposal) {
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- public Ballot(Proposal proposal,
- @Nullable Vote vote) {
+ public Ballot(Proposal proposal, @Nullable Vote vote) {
this.proposal = proposal;
this.vote = vote;
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/ChangeParamProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/ChangeParamProposal.java
index 895dd14fc31..b78decf2cf5 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/ChangeParamProposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/ChangeParamProposal.java
@@ -55,7 +55,7 @@ public ChangeParamProposal(String name,
paramValue,
Version.PROPOSAL,
new Date().getTime(),
- "",
+ null,
extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java
index 29e1812a652..45d749923f4 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/CompensationProposal.java
@@ -62,7 +62,7 @@ public CompensationProposal(String name,
requestedBsq.value,
Version.COMPENSATION_REQUEST,
new Date().getTime(),
- "",
+ null,
extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/ConfiscateBondProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/ConfiscateBondProposal.java
index 6d578b685da..38eb9ef320d 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/ConfiscateBondProposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/ConfiscateBondProposal.java
@@ -53,7 +53,7 @@ public ConfiscateBondProposal(String name,
lockupTxId,
Version.PROPOSAL,
new Date().getTime(),
- "",
+ null,
extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/GenericProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/GenericProposal.java
index 3963c539b48..92002f559f5 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/GenericProposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/GenericProposal.java
@@ -50,7 +50,7 @@ public GenericProposal(String name,
link,
Version.PROPOSAL,
new Date().getTime(),
- "",
+ null,
extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java
index 7b2081f45db..395b29c9597 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/Proposal.java
@@ -26,6 +26,7 @@
import bisq.common.proto.ProtobufferRuntimeException;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload;
+import bisq.common.util.ExtraDataMapValidator;
import io.bisq.generated.protobuffer.PB;
@@ -64,13 +65,13 @@ protected Proposal(String name,
byte version,
long creationDate,
@Nullable String txId,
- @SuppressWarnings("NullableProblems") Map extraDataMap) {
+ @Nullable Map extraDataMap) {
this.name = name;
this.link = link;
this.version = version;
this.creationDate = creationDate;
this.txId = txId;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
}
@@ -143,7 +144,7 @@ public Date getCreationDateAsDate() {
@Override
public String toString() {
return "Proposal{" +
- "\n uid='" + txId + '\'' +
+ "\n txId='" + txId + '\'' +
",\n name='" + name + '\'' +
",\n link='" + link + '\'' +
",\n txId='" + txId + '\'' +
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java
index b5b0bb91a4f..40146666101 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/ReimbursementProposal.java
@@ -62,7 +62,7 @@ public ReimbursementProposal(String name,
requestedBsq.value,
Version.REIMBURSEMENT_REQUEST,
new Date().getTime(),
- "",
+ null,
extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/RemoveAssetProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/RemoveAssetProposal.java
index 0b4ebe14b11..f4452e10163 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/RemoveAssetProposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/RemoveAssetProposal.java
@@ -53,7 +53,7 @@ public RemoveAssetProposal(String name,
tickerSymbol,
Version.PROPOSAL,
new Date().getTime(),
- "",
+ null,
extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/RoleProposal.java b/core/src/main/java/bisq/core/dao/state/model/governance/RoleProposal.java
index bc783bec78e..c4caa7adb68 100644
--- a/core/src/main/java/bisq/core/dao/state/model/governance/RoleProposal.java
+++ b/core/src/main/java/bisq/core/dao/state/model/governance/RoleProposal.java
@@ -54,7 +54,7 @@ public RoleProposal(Role role, Map extraDataMap) {
role.getBondedRoleType().getUnlockTimeInBlocks(),
Version.PROPOSAL,
new Date().getTime(),
- "",
+ null,
extraDataMap);
}
diff --git a/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java b/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java
index e213b60586a..03d6d8961b6 100644
--- a/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java
+++ b/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java
@@ -115,6 +115,8 @@ public void onCommitTx(Transaction tx, TxType txType, Wallet wallet) {
case PROOF_OF_BURN:
changeOutputIndex = 0;
break;
+ case IRREGULAR:
+ return;
default:
return;
}
diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java
index c7b3db9a898..b2fc9f92020 100644
--- a/core/src/main/java/bisq/core/filter/Filter.java
+++ b/core/src/main/java/bisq/core/filter/Filter.java
@@ -21,6 +21,7 @@
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
import bisq.common.crypto.Sig;
+import bisq.common.util.ExtraDataMapValidator;
import io.bisq.generated.protobuffer.PB;
@@ -146,7 +147,7 @@ public Filter(List bannedOfferIds,
disableDao);
this.signatureAsBase64 = signatureAsBase64;
this.ownerPubKeyBytes = ownerPubKeyBytes;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
ownerPubKey = Sig.getPublicKeyFromBytes(ownerPubKeyBytes);
}
diff --git a/core/src/main/java/bisq/core/network/p2p/seed/DefaultSeedNodeRepository.java b/core/src/main/java/bisq/core/network/p2p/seed/DefaultSeedNodeRepository.java
index aa385dab463..6b3a021789f 100644
--- a/core/src/main/java/bisq/core/network/p2p/seed/DefaultSeedNodeRepository.java
+++ b/core/src/main/java/bisq/core/network/p2p/seed/DefaultSeedNodeRepository.java
@@ -37,14 +37,19 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+
import javax.annotation.Nullable;
+// If a new BaseCurrencyNetwork type gets added we need to add the resource file for it as well!
+@Slf4j
public class DefaultSeedNodeRepository implements SeedNodeRepository {
//TODO add support for localhost addresses
private static final Pattern pattern = Pattern.compile("^([a-z0-9]+\\.onion:\\d+)");
private static final String ENDING = ".seednodes";
private static final Collection cache = new HashSet<>();
private final BisqEnvironment bisqEnvironment;
+ @Nullable
private final String seedNodes;
@Inject
@@ -55,35 +60,42 @@ public DefaultSeedNodeRepository(BisqEnvironment environment,
}
private void reload() {
+ try {
+ // see if there are any seed nodes configured manually
+ if (seedNodes != null && !seedNodes.isEmpty()) {
+ cache.clear();
+ Arrays.stream(seedNodes.split(",")).forEach(s -> cache.add(new NodeAddress(s)));
- // see if there are any seed nodes configured manually
- if (seedNodes != null && !seedNodes.isEmpty()) {
- cache.clear();
- Arrays.stream(seedNodes.split(",")).forEach(s -> cache.add(new NodeAddress(s)));
-
- return;
- }
-
- // else, we fetch the seed nodes from our resources
- final InputStream fileInputStream = DefaultSeedNodeRepository.class.getClassLoader().getResourceAsStream(BisqEnvironment.getBaseCurrencyNetwork().name().toLowerCase() + ENDING);
- final BufferedReader seedNodeFile = new BufferedReader(new InputStreamReader(fileInputStream));
-
- // only clear if we have a fresh data source (otherwise, an exception would prevent us from getting here)
- cache.clear();
+ return;
+ }
- // refill the cache
- seedNodeFile.lines().forEach(line -> {
- final Matcher matcher = pattern.matcher(line);
- if (matcher.find())
- cache.add(new NodeAddress(matcher.group(1)));
+ // else, we fetch the seed nodes from our resources
+ InputStream fileInputStream = DefaultSeedNodeRepository.class.getClassLoader().getResourceAsStream(BisqEnvironment.getBaseCurrencyNetwork().name().toLowerCase() + ENDING);
+ BufferedReader seedNodeFile = new BufferedReader(new InputStreamReader(fileInputStream));
- // Maybe better include in regex...
- if (line.startsWith("localhost"))
- cache.add(new NodeAddress(line));
- });
+ // only clear if we have a fresh data source (otherwise, an exception would prevent us from getting here)
+ cache.clear();
- // filter
- cache.removeAll(bisqEnvironment.getBannedSeedNodes().stream().map(NodeAddress::new).collect(Collectors.toSet()));
+ // refill the cache
+ seedNodeFile.lines().forEach(line -> {
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.find())
+ cache.add(new NodeAddress(matcher.group(1)));
+
+ // Maybe better include in regex...
+ if (line.startsWith("localhost"))
+ cache.add(new NodeAddress(line));
+ });
+
+ // filter
+ cache.removeAll(bisqEnvironment.getBannedSeedNodes().stream().map(NodeAddress::new).collect(Collectors.toSet()));
+
+ log.info("Seed nodes: {}", cache);
+ } catch (Throwable t) {
+ log.error(t.toString());
+ t.printStackTrace();
+ throw t;
+ }
}
public Collection getSeedNodeAddresses() {
diff --git a/core/src/main/java/bisq/core/offer/OfferPayload.java b/core/src/main/java/bisq/core/offer/OfferPayload.java
index eb7e37cb030..822f62ce225 100644
--- a/core/src/main/java/bisq/core/offer/OfferPayload.java
+++ b/core/src/main/java/bisq/core/offer/OfferPayload.java
@@ -24,6 +24,7 @@
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
+import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.JsonExclude;
import io.bisq.generated.protobuffer.PB;
@@ -235,7 +236,7 @@ public OfferPayload(String id,
this.upperClosePrice = upperClosePrice;
this.isPrivateOffer = isPrivateOffer;
this.hashOfChallenge = hashOfChallenge;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
this.protocolVersion = protocolVersion;
}
diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java
index f68b99838ae..47608fed711 100644
--- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java
+++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java
@@ -29,6 +29,7 @@
import bisq.common.crypto.Sig;
import bisq.common.proto.persistable.PersistablePayload;
+import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.JsonExclude;
import io.bisq.generated.protobuffer.PB;
@@ -147,7 +148,7 @@ public TradeStatistics(OfferPayload offerPayload,
this.tradeDate = tradeDate;
this.depositTxId = depositTxId;
this.signaturePubKeyBytes = signaturePubKeyBytes;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
signaturePubKey = Sig.getPublicKeyFromBytes(signaturePubKeyBytes);
}
diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
index 443b489fe67..ecd3b529371 100644
--- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
+++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
@@ -32,6 +32,7 @@
import bisq.common.app.Capability;
import bisq.common.crypto.Hash;
import bisq.common.proto.persistable.PersistableEnvelope;
+import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.JsonExclude;
import bisq.common.util.Utilities;
@@ -150,7 +151,7 @@ public TradeStatistics2(OfferPayload.Direction direction,
this.tradeAmount = tradeAmount;
this.tradeDate = tradeDate;
this.depositTxId = depositTxId;
- this.extraDataMap = extraDataMap;
+ this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
if (hash == null)
// We create hash from all fields excluding hash itself. We use json as simple data serialisation.
diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java
index 754e6b75a0e..d28bc95e8a7 100644
--- a/core/src/main/java/bisq/core/user/Preferences.java
+++ b/core/src/main/java/bisq/core/user/Preferences.java
@@ -655,6 +655,8 @@ public BlockChainExplorer getBlockChainExplorer() {
return BTC_DAO_TEST_NET_EXPLORERS.get(0);
case BTC_DAO_BETANET:
return prefPayload.getBlockChainExplorerMainNet();
+ case BTC_DAO_REGTEST:
+ return BTC_DAO_TEST_NET_EXPLORERS.get(0);
default:
throw new RuntimeException("BaseCurrencyNetwork not defined. BaseCurrencyNetwork=" + baseCurrencyNetwork);
}
@@ -672,6 +674,8 @@ public ArrayList getBlockChainExplorers() {
return BTC_DAO_TEST_NET_EXPLORERS;
case BTC_DAO_BETANET:
return BTC_MAIN_NET_EXPLORERS;
+ case BTC_DAO_REGTEST:
+ return BTC_DAO_TEST_NET_EXPLORERS;
default:
throw new RuntimeException("BaseCurrencyNetwork not defined. BaseCurrencyNetwork=" + baseCurrencyNetwork);
}
diff --git a/core/src/main/resources/btc_dao_regtest.seednodes b/core/src/main/resources/btc_dao_regtest.seednodes
new file mode 100644
index 00000000000..4c6367383f9
--- /dev/null
+++ b/core/src/main/resources/btc_dao_regtest.seednodes
@@ -0,0 +1,6 @@
+# nodeaddress.onion:port [(@owner)]
+2bnvhfkdrnx5hrlv.onion:8005
+b3jnw7fyph2jsu6n.onion:8005
+
+# omentgpxrxy5lehq.onion:8005
+# r7cucuwouvhdhdgo.onion:8005
diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties
index 0af3223e2f4..cae8d4511f2 100644
--- a/core/src/main/resources/i18n/displayStrings.properties
+++ b/core/src/main/resources/i18n/displayStrings.properties
@@ -1422,6 +1422,7 @@ dao.results.cycle.duration.value={0} block(s)
dao.results.cycle.value.postFix.isDefaultValue=(default value)
dao.results.cycle.value.postFix.hasChanged=(has been changed in voting)
+dao.results.invalidVotes.proposal=Proposal
dao.results.invalidVotes=We had invalid votes in that voting cycle. That can happen if a vote was \
not distributed well in the P2P network.\n{0}
@@ -1823,6 +1824,8 @@ dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.type.enum.ASSET_LISTING_FEE=Asset listing fee
# suppress inspection "UnusedProperty"
dao.tx.type.enum.PROOF_OF_BURN=Proof of burn
+# suppress inspection "UnusedProperty"
+dao.tx.type.enum.IRREGULAR=Irregular
dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\n\
@@ -1893,6 +1896,10 @@ dao.monitor.daoState.table.blockHeight=Block height
dao.monitor.daoState.table.hash=Hash of DAO state
dao.monitor.daoState.table.prev=Previous hash
dao.monitor.daoState.conflictTable.headline=DAO state hashes from peers in conflict
+dao.monitor.daoState.utxoConflicts=UTXO conflicts
+dao.monitor.daoState.utxoConflicts.blockHeight=Block height: {0}
+dao.monitor.daoState.utxoConflicts.sumUtxo=Sum of all UTXO: {0} BSQ
+dao.monitor.daoState.utxoConflicts.sumBsq=Sum of all BSQ: {0} BSQ
dao.monitor.proposal.headline=Proposals state
dao.monitor.proposal.daoStateInSync=Your local proposals state is in consensus with the network
@@ -1936,6 +1943,7 @@ dao.factsAndFigures.supply.totalLockedUpAmount=Locked up in bonds
dao.factsAndFigures.supply.totalUnlockingAmount=Unlocking BSQ from bonds
dao.factsAndFigures.supply.totalUnlockedAmount=Unlocked BSQ from bonds
dao.factsAndFigures.supply.totalConfiscatedAmount=Confiscated BSQ from bonds
+dao.factsAndFigures.supply.invalidTxs=Burned BSQ (invalid transactions)
dao.factsAndFigures.supply.burntAmount=Burned BSQ (fees)
dao.factsAndFigures.transactions.genesis=Genesis transaction
@@ -1947,6 +1955,8 @@ dao.factsAndFigures.transactions.utxo=No. of all unspent transaction outputs
dao.factsAndFigures.transactions.compensationIssuanceTx=No. of all compensation request issuance transactions
dao.factsAndFigures.transactions.reimbursementIssuanceTx=No. of all reimbursement request issuance transactions
dao.factsAndFigures.transactions.burntTx=No. of all fee payments transactions
+dao.factsAndFigures.transactions.invalidTx=No. of all invalid transactions
+dao.factsAndFigures.transactions.irregularTx=No. of all irregular transactions
####################################################################
# Windows
@@ -2412,9 +2422,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Year
time.month=Month
diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties
index 0312a6d0d3d..6dc69f3d26c 100644
--- a/core/src/main/resources/i18n/displayStrings_de.properties
+++ b/core/src/main/resources/i18n/displayStrings_de.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin-Testnetzwerk
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin-Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin-DAO-Testnetzwerk
+BTC_DAO_TESTNET=Bitcoin-DAO-Testnetzwerk (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanetz (Bitcoin Hauptnetz)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin-DAO-Testnetzwerk
time.year=Jahr
time.month=Monat
diff --git a/core/src/main/resources/i18n/displayStrings_el.properties b/core/src/main/resources/i18n/displayStrings_el.properties
index 557cd909b61..3bbb8678afc 100644
--- a/core/src/main/resources/i18n/displayStrings_el.properties
+++ b/core/src/main/resources/i18n/displayStrings_el.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Έτος
time.month=Μήνας
diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties
index 22301a0f1a3..473f941b593 100644
--- a/core/src/main/resources/i18n/displayStrings_es.properties
+++ b/core/src/main/resources/i18n/displayStrings_es.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Red de prueba de Bitcoin
# suppress inspection "UnusedProperty"
BTC_REGTEST=Regtest Bitcoin
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Red de prueba de Bitcoin
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Año
time.month=Mes
diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties
index 1854bc075b4..8603086c340 100644
--- a/core/src/main/resources/i18n/displayStrings_fa.properties
+++ b/core/src/main/resources/i18n/displayStrings_fa.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=سال
time.month=ماه
diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties
index 36c7963f2a1..9118ef9179a 100644
--- a/core/src/main/resources/i18n/displayStrings_fr.properties
+++ b/core/src/main/resources/i18n/displayStrings_fr.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Année
time.month=Mois
diff --git a/core/src/main/resources/i18n/displayStrings_hu.properties b/core/src/main/resources/i18n/displayStrings_hu.properties
index f81188c94e9..1594ab18264 100644
--- a/core/src/main/resources/i18n/displayStrings_hu.properties
+++ b/core/src/main/resources/i18n/displayStrings_hu.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Év
time.month=Hónap
diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties
index 5a17ab10479..44c3028e959 100644
--- a/core/src/main/resources/i18n/displayStrings_pt.properties
+++ b/core/src/main/resources/i18n/displayStrings_pt.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Testnet do Bitcoin
# suppress inspection "UnusedProperty"
BTC_REGTEST=Regtest do Bitcoin
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Ano
time.month=Mês
diff --git a/core/src/main/resources/i18n/displayStrings_ro.properties b/core/src/main/resources/i18n/displayStrings_ro.properties
index 931ce06f3f8..6debf3f89f5 100644
--- a/core/src/main/resources/i18n/displayStrings_ro.properties
+++ b/core/src/main/resources/i18n/displayStrings_ro.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=An
time.month=Lună
diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties
index d57126f6a7e..aeb9c659bcc 100644
--- a/core/src/main/resources/i18n/displayStrings_ru.properties
+++ b/core/src/main/resources/i18n/displayStrings_ru.properties
@@ -2107,6 +2107,8 @@ BTC_REGTEST=Биткойн режим регрессионного тестир
BTC_DAO_TESTNET=Биткойн DAO Testnet
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Бета-сеть ДАО Биткойн (основная сеть Биткойн)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Биткойн DAO Testnet
time.year=Год
time.month=Месяц
diff --git a/core/src/main/resources/i18n/displayStrings_sr.properties b/core/src/main/resources/i18n/displayStrings_sr.properties
index 3a925fdc160..79f21a259ae 100644
--- a/core/src/main/resources/i18n/displayStrings_sr.properties
+++ b/core/src/main/resources/i18n/displayStrings_sr.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitkoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitkoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Godina
time.month=Mesec
diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties
index b9d0edb93cd..951c62c6541 100644
--- a/core/src/main/resources/i18n/displayStrings_th.properties
+++ b/core/src/main/resources/i18n/displayStrings_th.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=ปี
time.month=เดือน
diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties
index 5212ca166dd..4ebbc00fb1a 100644
--- a/core/src/main/resources/i18n/displayStrings_vi.properties
+++ b/core/src/main/resources/i18n/displayStrings_vi.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=Bitcoin Testnet
# suppress inspection "UnusedProperty"
BTC_REGTEST=Bitcoin Regtest
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=Năm
time.month=Tháng
diff --git a/core/src/main/resources/i18n/displayStrings_zh.properties b/core/src/main/resources/i18n/displayStrings_zh.properties
index 16898e0d645..aada127d2f7 100644
--- a/core/src/main/resources/i18n/displayStrings_zh.properties
+++ b/core/src/main/resources/i18n/displayStrings_zh.properties
@@ -2104,9 +2104,11 @@ BTC_TESTNET=比特币测试网络
# suppress inspection "UnusedProperty"
BTC_REGTEST=比特币回归测试
# suppress inspection "UnusedProperty"
-BTC_DAO_TESTNET=Bitcoin DAO Testnet
+BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated)
# suppress inspection "UnusedProperty"
BTC_DAO_BETANET=Bitcoin DAO Betanet (Bitcoin Mainnet)
+# suppress inspection "UnusedProperty"
+BTC_DAO_REGTEST=Bitcoin DAO Testnet
time.year=年线
time.month=月线
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java
index 65986ee0291..0de2e7e6252 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java
@@ -291,13 +291,14 @@ private void updateWithBsqBlockChainData() {
Coin issuedAmountFromGenesis = daoFacade.getGenesisTotalSupply();
Coin issuedAmountFromCompRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.COMPENSATION));
Coin issuedAmountFromReimbursementRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT));
- Coin burntFee = Coin.valueOf(daoFacade.getTotalBurntFee());
Coin totalConfiscatedAmount = Coin.valueOf(daoFacade.getTotalAmountOfConfiscatedTxOutputs());
+ // Contains burnt fee and invalidated bsq due invalid txs
+ Coin totalAmountOfBurntBsq = Coin.valueOf(daoFacade.getTotalAmountOfBurntBsq());
availableAmount = issuedAmountFromGenesis
.add(issuedAmountFromCompRequests)
.add(issuedAmountFromReimbursementRequests)
- .subtract(burntFee)
+ .subtract(totalAmountOfBurntBsq)
.subtract(totalConfiscatedAmount);
availableAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(availableAmount));
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java
index 1d28e15ab0a..43b0b77b033 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java
@@ -64,8 +64,10 @@
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -87,8 +89,8 @@ public class SupplyView extends ActivatableView implements DaoSt
private int gridRow = 0;
private TextField genesisIssueAmountTextField, compRequestIssueAmountTextField, reimbursementAmountTextField,
- burntAmountTextField, totalLockedUpAmountTextField, totalUnlockingAmountTextField,
- totalUnlockedAmountTextField, totalConfiscatedAmountTextField;
+ totalBurntFeeAmountTextField, totalLockedUpAmountTextField, totalUnlockingAmountTextField,
+ totalUnlockedAmountTextField, totalConfiscatedAmountTextField, totalAmountOfInvalidatedBsqTextField;
private XYChart.Series seriesBSQIssued, seriesBSQBurnt;
private static final Map ADJUSTERS = new HashMap<>();
@@ -165,11 +167,10 @@ private void createSupplyIncreasedInformation() {
private void createSupplyReducedInformation() {
addTitledGroupBg(root, ++gridRow, 2, Res.get("dao.factsAndFigures.supply.burnt"), Layout.GROUP_DISTANCE);
- Tuple3