diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index 0bb2b80c6fb..5cb84c2db7a 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -56,6 +56,7 @@ message NetworkEnvelope { AddPersistableNetworkPayloadMessage add_persistable_network_payload_message = 31; AckMessage ack_message = 32; + RepublishGovernanceDataRequest republish_governance_data_request = 33; } } @@ -319,6 +320,9 @@ message NewBlockBroadcastMessage { BaseBlock raw_block = 1; } +message RepublishGovernanceDataRequest { +} + /////////////////////////////////////////////////////////////////////////////////////////// // Payload /////////////////////////////////////////////////////////////////////////////////////////// @@ -931,6 +935,9 @@ message PersistableEnvelope { MyBlindVoteList my_blind_vote_list = 22; MeritList merit_list = 23; BondedRoleList bonded_role_list = 24; + RemovedAssetList removed_asset_list = 25; + EvaluatedProposalList evaluated_proposal_list = 26; + DecryptedBallotsWithMeritsList decrypted_ballots_with_merits_list = 27; } } @@ -1470,11 +1477,11 @@ message Proposal { string tx_id = 5; oneof message { CompensationProposal compensation_proposal = 6; - GenericProposal generic_proposal = 7; - ChangeParamProposal change_param_proposal = 8; - RemoveAltcoinProposal remove_altcoin_proposal = 9; - ConfiscateBondProposal confiscate_bond_proposal = 10; - BondedRoleProposal bonded_role_proposal = 11; + ChangeParamProposal change_param_proposal = 7; + BondedRoleProposal bonded_role_proposal = 8; + ConfiscateBondProposal confiscate_bond_proposal = 9; + GenericProposal generic_proposal = 10; + RemoveAssetProposal remove_asset_proposal = 11; } } @@ -1483,24 +1490,32 @@ message CompensationProposal { string bsq_address = 2; } -message GenericProposal { -} - message ChangeParamProposal { string param = 1; // name of enum int64 param_value = 2; } -message RemoveAltcoinProposal { - string currency_code = 1; +message BondedRoleProposal { + BondedRole bonded_role = 1; } message ConfiscateBondProposal { bytes hash = 1; } -message BondedRoleProposal { - BondedRole bonded_role = 1; +message GenericProposal { +} + +message RemoveAssetProposal { + string ticker_symbol = 1; +} + +message RemovedAsset { + string ticker_symbol = 1; + string remove_reason = 2; +} +message RemovedAssetList { + repeated RemovedAsset removed_asset = 1; } message BondedRole { @@ -1616,6 +1631,39 @@ message MeritList { repeated Merit merit = 1; } +message ProposalVoteResult { + Proposal proposal = 1; + int64 stake_of_Accepted_votes = 2; + int64 stake_of_Rejected_votes = 3; + int32 num_accepted_votes = 4; + int32 num_rejected_votes = 5; + int32 num_ignored_votes = 6; +} + +message EvaluatedProposal { + bool is_accepted = 1; + ProposalVoteResult proposal_vote_result = 2; + int64 required_quorum = 3; + int64 required_threshold = 4; +} + +message EvaluatedProposalList { + repeated EvaluatedProposal evaluated_proposal = 1; +} + +message DecryptedBallotsWithMerits { + bytes hash_of_blind_vote_list = 1; + string vote_reveal_tx_id = 2; + string blind_vote_tx_id = 3; + int64 stake = 4; + BallotList ballot_list = 5; + MeritList merit_list = 6; +} + +message DecryptedBallotsWithMeritsList { + repeated DecryptedBallotsWithMerits decrypted_ballots_with_merits = 1; +} + /////////////////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java index bd0fcc8e77a..4518da4ec94 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java @@ -92,6 +92,7 @@ protected void setupHandlers() { bisqSetup.setDisplaySecurityRecommendationHandler(key -> log.info("onDisplaySecurityRecommendationHandler")); bisqSetup.setDisplayLocalhostHandler(key -> log.info("onDisplayLocalhostHandler")); bisqSetup.setWrongOSArchitectureHandler(msg -> log.info("onWrongOSArchitectureHandler. msg={}", msg)); + bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.info("voteResultException={}", voteResultException)); //TODO move to bisqSetup corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.info("getCorruptedDatabaseFiles. files={}", files)); diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 726328127e3..e7511c6bd71 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -30,6 +30,8 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.dao.DaoSetup; +import bisq.core.dao.governance.voteresult.VoteResultException; +import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.filter.FilterManager; import bisq.core.locale.Res; import bisq.core.notifications.MobileNotificationService; @@ -147,6 +149,7 @@ public interface BisqSetupCompleteListener { private final DisputeMsgEvents disputeMsgEvents; private final PriceAlert priceAlert; private final MarketAlerts marketAlerts; + private final VoteResultService voteResultService; private final BSFormatter formatter; @Setter @Nullable @@ -173,6 +176,9 @@ public interface BisqSetupCompleteListener { private BiConsumer displayUpdateHandler; @Setter @Nullable + private Consumer voteResultExceptionHandler; + @Setter + @Nullable private Consumer displayPrivateNotificationHandler; @Getter @@ -216,6 +222,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, DisputeMsgEvents disputeMsgEvents, PriceAlert priceAlert, MarketAlerts marketAlerts, + VoteResultService voteResultService, BSFormatter formatter) { @@ -251,6 +258,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, this.disputeMsgEvents = disputeMsgEvents; this.priceAlert = priceAlert; this.marketAlerts = marketAlerts; + this.voteResultService = voteResultService; this.formatter = formatter; } @@ -632,6 +640,15 @@ public void onBalanceChanged(Coin balance, Transaction tx) { } }); + voteResultService.getVoteResultExceptions().addListener((ListChangeListener) c -> { + c.next(); + if (c.wasAdded() && voteResultExceptionHandler != null) { + c.getAddedSubList().forEach(e -> { + voteResultExceptionHandler.accept(e); + }); + } + }); + mobileNotificationService.onAllServicesInitialized(); myOfferTakenEvents.onAllServicesInitialized(); tradeEvents.onAllServicesInitialized(); diff --git a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java index af059c8dba9..871163fdf17 100644 --- a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java +++ b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java @@ -18,10 +18,12 @@ package bisq.core.app.misc; import bisq.core.dao.DaoSetup; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; import bisq.core.dao.governance.myvote.MyVoteListService; import bisq.core.dao.governance.role.BondedRolesService; +import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.filter.FilterManager; import bisq.core.payment.AccountAgeWitnessService; import bisq.core.trade.statistics.TradeStatisticsManager; @@ -50,7 +52,9 @@ public AppSetupWithP2PAndDAO(EncryptionService encryptionService, MyVoteListService myVoteListService, BallotListService ballotListService, MyBlindVoteListService myBlindVoteListService, - BondedRolesService bondedRolesService) { + BondedRolesService bondedRolesService, + AssetService assetService, + VoteResultService voteResultService) { super(encryptionService, keyRing, p2PService, @@ -64,6 +68,8 @@ public AppSetupWithP2PAndDAO(EncryptionService encryptionService, persistedDataHosts.add(ballotListService); persistedDataHosts.add(myBlindVoteListService); persistedDataHosts.add(bondedRolesService); + persistedDataHosts.add(assetService); + persistedDataHosts.add(voteResultService); } @Override diff --git a/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java b/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java index 9aeb3ddcfa4..51c27f66e09 100644 --- a/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java +++ b/core/src/main/java/bisq/core/btc/BaseCurrencyNetwork.java @@ -17,9 +17,6 @@ package bisq.core.btc; -import bisq.core.app.BisqEnvironment; -import bisq.core.provider.fee.FeeService; - import org.libdohj.params.DashMainNetParams; import org.libdohj.params.DashRegTestParams; import org.libdohj.params.DashTestNet3Params; @@ -88,15 +85,6 @@ public boolean isDash() { } public long getDefaultMinFeePerByte() { - switch (BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()) { - case "BTC": - return 1; - case "LTC": - return FeeService.LTC_REFERENCE_DEFAULT_MIN_TX_FEE.value; - case "DASH": - return FeeService.DASH_REFERENCE_DEFAULT_MIN_TX_FEE.value; - default: - throw new RuntimeException("Unsupported code at getDefaultMinFee: " + BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()); - } + return 1; } } diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 23cac2c828a..4ad6817f79f 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -37,9 +37,12 @@ import bisq.core.dao.governance.proposal.ProposalListPresentation; import bisq.core.dao.governance.proposal.ProposalWithTransaction; import bisq.core.dao.governance.proposal.TxException; +import bisq.core.dao.governance.proposal.compensation.CompensationConsensus; import bisq.core.dao.governance.proposal.compensation.CompensationProposalService; import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposalService; +import bisq.core.dao.governance.proposal.generic.GenericProposalService; import bisq.core.dao.governance.proposal.param.ChangeParamProposalService; +import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposalService; import bisq.core.dao.governance.proposal.role.BondedRoleProposalService; import bisq.core.dao.governance.role.BondedRole; import bisq.core.dao.governance.role.BondedRolesService; @@ -78,6 +81,10 @@ import javax.annotation.Nullable; + + +import bisq.asset.Asset; + /** * Provides a facade to interact with the Dao domain. Hides complexity and domain details to clients (e.g. UI or APIs) * by providing a reduced API and/or aggregating subroutines. @@ -95,10 +102,11 @@ public class DaoFacade implements DaoSetupService { private final ChangeParamProposalService changeParamProposalService; private final ConfiscateBondProposalService confiscateBondProposalService; private final BondedRoleProposalService bondedRoleProposalService; + private final GenericProposalService genericProposalService; + private final RemoveAssetProposalService removeAssetProposalService; private final BondedRolesService bondedRolesService; private final LockupService lockupService; private final UnlockService unlockService; - private final ProposalConsensus proposalConsensus; private final ObjectProperty phaseProperty = new SimpleObjectProperty<>(DaoPhase.Phase.UNDEFINED); @@ -115,10 +123,11 @@ public DaoFacade(MyProposalListService myProposalListService, ChangeParamProposalService changeParamProposalService, ConfiscateBondProposalService confiscateBondProposalService, BondedRoleProposalService bondedRoleProposalService, + GenericProposalService genericProposalService, + RemoveAssetProposalService removeAssetProposalService, BondedRolesService bondedRolesService, LockupService lockupService, - UnlockService unlockService, - ProposalConsensus proposalConsensus) { + UnlockService unlockService) { this.proposalListPresentation = proposalListPresentation; this.ballotListService = ballotListService; this.ballotListPresentation = ballotListPresentation; @@ -131,10 +140,11 @@ public DaoFacade(MyProposalListService myProposalListService, this.changeParamProposalService = changeParamProposalService; this.confiscateBondProposalService = confiscateBondProposalService; this.bondedRoleProposalService = bondedRoleProposalService; + this.genericProposalService = genericProposalService; + this.removeAssetProposalService = removeAssetProposalService; this.bondedRolesService = bondedRolesService; this.lockupService = lockupService; this.unlockService = unlockService; - this.proposalConsensus = proposalConsensus; } @@ -232,13 +242,26 @@ public ProposalWithTransaction getBondedRoleProposalWithTransaction(BondedRole b return bondedRoleProposalService.createProposalWithTransaction(bondedRole); } + public ProposalWithTransaction getGenericProposalWithTransaction(String name, + String link) + throws ValidationException, InsufficientMoneyException, TxException { + return genericProposalService.createProposalWithTransaction(name, link); + } + + public ProposalWithTransaction getRemoveAssetProposalWithTransaction(String name, + String link, + Asset asset) + throws ValidationException, InsufficientMoneyException, TxException { + return removeAssetProposalService.createProposalWithTransaction(name, link, asset); + } + public List getBondedRoleList() { return bondedRolesService.getBondedRoleList(); } // Show fee public Coin getProposalFee(int chainHeight) { - return proposalConsensus.getFee(bsqStateService, chainHeight); + return ProposalConsensus.getFee(bsqStateService, chainHeight); } // Publish proposal tx, proposal payload and and persist it to myProposalList @@ -464,16 +487,16 @@ public boolean isInPhaseButNotLastBlock(DaoPhase.Phase phase) { return periodService.isInPhaseButNotLastBlock(phase); } - public boolean isTxInCorrectCycle(int txHeight, int chainHeadHeight) { - return periodService.isTxInCorrectCycle(txHeight, chainHeadHeight); + public boolean isTxInCorrectCycle(int txHeight, int chainHeight) { + return periodService.isTxInCorrectCycle(txHeight, chainHeight); } - public boolean isTxInCorrectCycle(String txId, int chainHeadHeight) { - return periodService.isTxInCorrectCycle(txId, chainHeadHeight); + public boolean isTxInCorrectCycle(String txId, int chainHeight) { + return periodService.isTxInCorrectCycle(txId, chainHeight); } - public boolean isTxInPhaseAndCycle(String txId, DaoPhase.Phase phase, int chainHeadHeight) { - return periodService.isTxInPhaseAndCycle(txId, phase, chainHeadHeight); + public boolean isTxInPhaseAndCycle(String txId, DaoPhase.Phase phase, int chainHeight) { + return periodService.isTxInPhaseAndCycle(txId, phase, chainHeight); } public boolean isUnspent(TxOutputKey key) { @@ -487,4 +510,16 @@ public Optional getBondedRoleFromHash(byte[] hash) { public boolean isUnlocking(BondedRole bondedRole) { return bsqStateService.isUnlocking(bondedRole); } + + public Coin getMinCompensationRequestAmount() { + return CompensationConsensus.getMinCompensationRequestAmount(bsqStateService, periodService.getChainHeight()); + } + + public Coin getMaxCompensationRequestAmount() { + return CompensationConsensus.getMaxCompensationRequestAmount(bsqStateService, periodService.getChainHeight()); + } + + public long getPramValue(Param param) { + return bsqStateService.getParamValue(param, periodService.getChainHeight()); + } } diff --git a/core/src/main/java/bisq/core/dao/DaoModule.java b/core/src/main/java/bisq/core/dao/DaoModule.java index f757c65eeb3..cd7286a46cc 100644 --- a/core/src/main/java/bisq/core/dao/DaoModule.java +++ b/core/src/main/java/bisq/core/dao/DaoModule.java @@ -19,26 +19,30 @@ import bisq.core.dao.bonding.lockup.LockupService; import bisq.core.dao.bonding.unlock.UnlockService; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.BallotListPresentation; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.BlindVoteListService; import bisq.core.dao.governance.blindvote.BlindVoteValidator; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; +import bisq.core.dao.governance.blindvote.network.RepublishGovernanceDataHandler; import bisq.core.dao.governance.blindvote.storage.BlindVoteStorageService; import bisq.core.dao.governance.blindvote.storage.BlindVoteStore; import bisq.core.dao.governance.myvote.MyVoteListService; import bisq.core.dao.governance.proposal.MyProposalListService; -import bisq.core.dao.governance.proposal.ProposalConsensus; import bisq.core.dao.governance.proposal.ProposalListPresentation; import bisq.core.dao.governance.proposal.ProposalService; import bisq.core.dao.governance.proposal.ProposalValidator; -import bisq.core.dao.governance.proposal.compensation.CompensationConsensus; import bisq.core.dao.governance.proposal.compensation.CompensationProposalService; import bisq.core.dao.governance.proposal.compensation.CompensationValidator; import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposalService; import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondValidator; +import bisq.core.dao.governance.proposal.generic.GenericProposalService; +import bisq.core.dao.governance.proposal.generic.GenericProposalValidator; import bisq.core.dao.governance.proposal.param.ChangeParamProposalService; import bisq.core.dao.governance.proposal.param.ChangeParamValidator; +import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposalService; +import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetValidator; import bisq.core.dao.governance.proposal.role.BondedRoleProposalService; import bisq.core.dao.governance.proposal.role.BondedRoleValidator; import bisq.core.dao.governance.proposal.storage.appendonly.ProposalStorageService; @@ -46,6 +50,7 @@ import bisq.core.dao.governance.proposal.storage.temp.TempProposalStorageService; import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore; import bisq.core.dao.governance.role.BondedRolesService; +import bisq.core.dao.governance.voteresult.MissingDataRequestService; import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.dao.governance.voteresult.issuance.IssuanceService; import bisq.core.dao.governance.votereveal.VoteRevealService; @@ -109,7 +114,6 @@ protected void configure() { bind(TxParser.class).in(Singleton.class); // Proposal - bind(ProposalConsensus.class).in(Singleton.class); bind(ProposalService.class).in(Singleton.class); bind(MyProposalListService.class).in(Singleton.class); @@ -122,7 +126,6 @@ protected void configure() { bind(ProposalValidator.class).in(Singleton.class); bind(CompensationValidator.class).in(Singleton.class); - bind(CompensationConsensus.class).in(Singleton.class); bind(CompensationProposalService.class).in(Singleton.class); bind(ChangeParamValidator.class).in(Singleton.class); @@ -134,6 +137,12 @@ protected void configure() { bind(ConfiscateBondValidator.class).in(Singleton.class); bind(ConfiscateBondProposalService.class).in(Singleton.class); + bind(GenericProposalValidator.class).in(Singleton.class); + bind(GenericProposalService.class).in(Singleton.class); + + bind(RemoveAssetValidator.class).in(Singleton.class); + bind(RemoveAssetProposalService.class).in(Singleton.class); + // Ballot bind(BallotListService.class).in(Singleton.class); @@ -154,7 +163,9 @@ protected void configure() { // VoteResult bind(VoteResultService.class).in(Singleton.class); + bind(MissingDataRequestService.class).in(Singleton.class); bind(IssuanceService.class).in(Singleton.class); + bind(RepublishGovernanceDataHandler.class).in(Singleton.class); // Genesis String genesisTxId = environment.getProperty(DaoOptionKeys.GENESIS_TX_ID, String.class, ""); @@ -168,6 +179,9 @@ protected void configure() { bind(UnlockService.class).in(Singleton.class); bind(BondedRolesService.class).in(Singleton.class); + // Asset + bind(AssetService.class).in(Singleton.class); + // Options bindConstant().annotatedWith(named(DaoOptionKeys.RPC_USER)).to(environment.getRequiredProperty(DaoOptionKeys.RPC_USER)); bindConstant().annotatedWith(named(DaoOptionKeys.RPC_PASSWORD)).to(environment.getRequiredProperty(DaoOptionKeys.RPC_PASSWORD)); diff --git a/core/src/main/java/bisq/core/dao/DaoSetup.java b/core/src/main/java/bisq/core/dao/DaoSetup.java index e5fd684f707..02a1214f7d2 100644 --- a/core/src/main/java/bisq/core/dao/DaoSetup.java +++ b/core/src/main/java/bisq/core/dao/DaoSetup.java @@ -21,6 +21,7 @@ import bisq.core.dao.governance.blindvote.BlindVoteListService; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; import bisq.core.dao.governance.proposal.ProposalService; +import bisq.core.dao.governance.voteresult.MissingDataRequestService; import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.dao.governance.votereveal.VoteRevealService; import bisq.core.dao.node.BsqNode; @@ -47,6 +48,7 @@ public class DaoSetup { private final VoteRevealService voteRevealService; private final VoteResultService voteResultService; private final BsqNode bsqNode; + private final MissingDataRequestService missingDataRequestService; private final DaoFacade daoFacade; private final ExportJsonFilesService exportJsonFilesService; @@ -60,6 +62,7 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, MyBlindVoteListService myBlindVoteListService, VoteRevealService voteRevealService, VoteResultService voteResultService, + MissingDataRequestService missingDataRequestService, DaoFacade daoFacade, ExportJsonFilesService exportJsonFilesService) { this.bsqStateService = bsqStateService; @@ -70,6 +73,7 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, this.myBlindVoteListService = myBlindVoteListService; this.voteRevealService = voteRevealService; this.voteResultService = voteResultService; + this.missingDataRequestService = missingDataRequestService; this.daoFacade = daoFacade; this.exportJsonFilesService = exportJsonFilesService; @@ -87,8 +91,9 @@ public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) { myBlindVoteListService.addListeners(); voteRevealService.addListeners(); voteResultService.addListeners(); - exportJsonFilesService.addListeners(); + missingDataRequestService.addListeners(); daoFacade.addListeners(); + exportJsonFilesService.addListeners(); bsqStateService.start(); cycleService.start(); @@ -98,8 +103,9 @@ public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) { myBlindVoteListService.start(); voteRevealService.start(); voteResultService.start(); - exportJsonFilesService.start(); + missingDataRequestService.start(); daoFacade.start(); + exportJsonFilesService.start(); bsqNode.setErrorMessageHandler(errorMessageHandler); bsqNode.start(); diff --git a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java new file mode 100644 index 00000000000..26eca88e1a3 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java @@ -0,0 +1,120 @@ +/* + * 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.governance.asset; + +import bisq.core.app.BisqEnvironment; +import bisq.core.dao.state.BsqStateService; +import bisq.core.locale.CurrencyUtil; + +import bisq.common.proto.persistable.PersistedDataHost; +import bisq.common.storage.Storage; + +import javax.inject.Inject; + +import java.util.List; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AssetService implements PersistedDataHost { + + + private final Storage storage; + private final BsqStateService bsqStateService; + @Getter + private final RemovedAssetsList removedAssetsList = new RemovedAssetsList(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public AssetService(Storage storage, BsqStateService bsqStateService) { + this.storage = storage; + this.bsqStateService = bsqStateService; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PersistedDataHost + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void readPersisted() { + if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) { + RemovedAssetsList persisted = storage.initAndGetPersisted(removedAssetsList, 100); + if (persisted != null) { + removedAssetsList.clear(); + removedAssetsList.addAll(persisted.getList()); + } + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void addToRemovedAssetsListByVoting(String tickerSymbol) { + log.info("Asset '{}' was removed by DAO voting", CurrencyUtil.getNameAndCode(tickerSymbol)); + removedAssetsList.add(new RemovedAsset(tickerSymbol, RemoveReason.VOTING)); + persist(); + } + + public boolean hasPaidBSQFee(String tickerSymbol) { + //TODO + return false; + } + + + public boolean isAssetRemoved(String tickerSymbol) { + boolean isRemoved = removedAssetsList.getList().stream() + .anyMatch(removedAsset -> removedAsset.getTickerSymbol().equals(tickerSymbol)); + if (isRemoved) + log.info("Asset '{}' was removed", CurrencyUtil.getNameAndCode(tickerSymbol)); + + return isRemoved; + } + + public boolean isAssetRemovedByVoting1(String tickerSymbol) { + boolean isRemoved = getRemovedAssetsByRemoveReason(RemoveReason.VOTING).stream() + .anyMatch(removedAsset -> removedAsset.getTickerSymbol().equals(tickerSymbol)); + if (isRemoved) + log.info("Asset '{}' was removed by DAO voting", CurrencyUtil.getNameAndCode(tickerSymbol)); + + return isRemoved; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private List getRemovedAssetsByRemoveReason(RemoveReason removeReason) { + return removedAssetsList.getList().stream() + .filter(e -> e.getRemoveReason() == removeReason) + .collect(Collectors.toList()); + } + + private void persist() { + storage.queueUpForSave(20); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/asset/RemoveReason.java b/core/src/main/java/bisq/core/dao/governance/asset/RemoveReason.java new file mode 100644 index 00000000000..81635722b10 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/asset/RemoveReason.java @@ -0,0 +1,23 @@ +/* + * 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.governance.asset; + +public enum RemoveReason { + VOTING, + INACTIVITY +} diff --git a/core/src/main/java/bisq/core/dao/governance/asset/RemovedAsset.java b/core/src/main/java/bisq/core/dao/governance/asset/RemovedAsset.java new file mode 100644 index 00000000000..908b2adb083 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/asset/RemovedAsset.java @@ -0,0 +1,53 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.asset; + +import bisq.common.proto.ProtoUtil; +import bisq.common.proto.persistable.PersistablePayload; + +import io.bisq.generated.protobuffer.PB; + +import lombok.Value; + +@Value +public class RemovedAsset implements PersistablePayload { + private final String tickerSymbol; + private final RemoveReason removeReason; + + public RemovedAsset(String tickerSymbol, RemoveReason removeReason) { + this.tickerSymbol = tickerSymbol; + this.removeReason = removeReason; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public PB.RemovedAsset toProtoMessage() { + PB.RemovedAsset.Builder builder = PB.RemovedAsset.newBuilder() + .setTickerSymbol(tickerSymbol) + .setRemoveReason(removeReason.name()); + return builder.build(); + } + + public static RemovedAsset fromProto(PB.RemovedAsset proto) { + return new RemovedAsset(proto.getTickerSymbol(), + ProtoUtil.enumFromProto(RemoveReason.class, proto.getRemoveReason())); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/asset/RemovedAssetsList.java b/core/src/main/java/bisq/core/dao/governance/asset/RemovedAssetsList.java new file mode 100644 index 00000000000..9c655886cea --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/asset/RemovedAssetsList.java @@ -0,0 +1,75 @@ +/* + * 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.governance.asset; + +import bisq.core.dao.governance.ConsensusCritical; + +import bisq.common.proto.persistable.PersistableList; + +import io.bisq.generated.protobuffer.PB; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.EqualsAndHashCode; + +/** + * PersistableEnvelope wrapper for list of removedAssets. + */ +@EqualsAndHashCode(callSuper = true) +public class RemovedAssetsList extends PersistableList implements ConsensusCritical { + + public RemovedAssetsList(List list) { + super(list); + } + + public RemovedAssetsList() { + super(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public PB.PersistableEnvelope toProtoMessage() { + return PB.PersistableEnvelope.newBuilder().setRemovedAssetList(getBuilder()).build(); + } + + public PB.RemovedAssetList.Builder getBuilder() { + return PB.RemovedAssetList.newBuilder() + .addAllRemovedAsset(getList().stream() + .map(RemovedAsset::toProtoMessage) + .collect(Collectors.toList())); + } + + public static RemovedAssetsList fromProto(PB.RemovedAssetList proto) { + return new RemovedAssetsList(new ArrayList<>(proto.getRemovedAssetList().stream() + .map(RemovedAsset::fromProto) + .collect(Collectors.toList()))); + } + + @Override + public String toString() { + return "List of tickerSymbols in RemovedAssetList: " + getList().stream() + .map(RemovedAsset::getTickerSymbol) + .collect(Collectors.toList()); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteConsensus.java b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteConsensus.java index e38a5ed150f..812febe4301 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteConsensus.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/BlindVoteConsensus.java @@ -115,8 +115,8 @@ public static byte[] getOpReturnData(byte[] hash) throws IOException { } } - public static Coin getFee(BsqStateService bsqStateService, int chainHeadHeight) { - Coin fee = Coin.valueOf(bsqStateService.getParamValue(Param.BLIND_VOTE_FEE, chainHeadHeight)); + public static Coin getFee(BsqStateService bsqStateService, int chainHeight) { + Coin fee = Coin.valueOf(bsqStateService.getParamValue(Param.BLIND_VOTE_FEE, chainHeight)); log.info("Fee for blind vote: " + fee); return fee; } 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 5596a597569..676f239a46d 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 @@ -39,6 +39,7 @@ import java.util.List; import java.util.stream.Collectors; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** @@ -49,8 +50,8 @@ public class BlindVoteListService implements AppendOnlyDataStoreListener, BsqSta private final BsqStateService bsqStateService; private final P2PService p2PService; private final BlindVoteValidator blindVoteValidator; - - private final ObservableList appendOnlyStoreList = FXCollections.observableArrayList(); + @Getter + private final ObservableList blindVotePayloads = FXCollections.observableArrayList(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -122,7 +123,7 @@ public void onAdded(PersistableNetworkPayload payload) { /////////////////////////////////////////////////////////////////////////////////////////// public List getBlindVotesInPhaseAndCycle() { - return appendOnlyStoreList.stream() + return blindVotePayloads.stream() .filter(blindVotePayload -> blindVoteValidator.isTxInPhaseAndCycle(blindVotePayload.getBlindVote())) .map(BlindVotePayload::getBlindVote) .collect(Collectors.toList()); @@ -140,14 +141,14 @@ private void fillListFromAppendOnlyDataStore() { private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload) { if (persistableNetworkPayload instanceof BlindVotePayload) { BlindVotePayload blindVotePayload = (BlindVotePayload) persistableNetworkPayload; - if (!appendOnlyStoreList.contains(blindVotePayload)) { + 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). if (blindVoteValidator.areDataFieldsValid(blindVote)) { // We don't validate as we might receive blindVotes from other cycles or phases at startup. - appendOnlyStoreList.add(blindVotePayload); + blindVotePayloads.add(blindVotePayload); log.info("We received a blindVotePayload. blindVoteTxId={}", txId); } else { log.warn("We received an invalid blindVotePayload. blindVoteTxId={}", txId); 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 new file mode 100644 index 00000000000..75e34d2bbc5 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java @@ -0,0 +1,214 @@ +/* + * 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.governance.blindvote.network; + +import bisq.core.dao.governance.blindvote.network.messages.RepublishGovernanceDataRequest; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.network.Connection; +import bisq.network.p2p.network.NetworkNode; +import bisq.network.p2p.peers.PeerManager; +import bisq.network.p2p.peers.peerexchange.Peer; +import bisq.network.p2p.seed.SeedNodeRepository; + +import bisq.common.Timer; +import bisq.common.UserThread; +import bisq.common.app.Capabilities; + +import javax.inject.Inject; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.SettableFuture; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +import org.jetbrains.annotations.NotNull; + +/** + * Responsible for sending a RepublishGovernanceDataRequest to full nodes. + * Processing of RepublishBlindVotesRequests at full nodes is done in the FullNodeNetworkService. + */ +@Slf4j +public final class RepublishGovernanceDataHandler { + private static final long TIMEOUT = 120; + + private final Collection seedNodeAddresses; + private final NetworkNode networkNode; + private final PeerManager peerManager; + + private boolean stopped; + private Timer timeoutTimer; + + @Inject + public RepublishGovernanceDataHandler(NetworkNode networkNode, + PeerManager peerManager, + SeedNodeRepository seedNodesRepository) { + this.networkNode = networkNode; + this.peerManager = peerManager; + this.seedNodeAddresses = new HashSet<>(seedNodesRepository.getSeedNodeAddresses()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void sendRepublishRequest() { + // First try if we have a seed node in our connections. All seed nodes are full nodes. + if (!stopped) + connectToNextNode(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void sendRepublishRequest(NodeAddress nodeAddress) { + RepublishGovernanceDataRequest republishGovernanceDataRequest = new RepublishGovernanceDataRequest(); + if (timeoutTimer == null) { + timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions + if (!stopped) { + String errorMessage = "A timeout occurred at sending republishGovernanceDataRequest:" + + " to nodeAddress:" + nodeAddress; + log.warn(errorMessage); + connectToNextNode(); + } else { + log.warn("We have stopped already. We ignore that timeoutTimer.run call. " + + "Might be caused by an previous networkNode.sendMessage.onFailure."); + } + }, + TIMEOUT); + } + + log.info("We send to peer {} a republishGovernanceDataRequest.", nodeAddress); + SettableFuture future = networkNode.sendMessage(nodeAddress, republishGovernanceDataRequest); + Futures.addCallback(future, new FutureCallback<>() { + @Override + public void onSuccess(Connection connection) { + if (!stopped) { + log.info("Sending of RepublishGovernanceDataRequest message to peer {} succeeded.", nodeAddress.getFullAddress()); + stop(); + } else { + log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call." + + "Might be caused by an previous timeout."); + } + } + + @Override + public void onFailure(@NotNull Throwable throwable) { + if (!stopped) { + String errorMessage = "Sending republishGovernanceDataRequest to " + nodeAddress + + " failed. That is expected if the peer is offline.\n\t" + + "\n\tException=" + throwable.getMessage(); + log.info(errorMessage); + handleFault(nodeAddress); + connectToNextNode(); + } else { + log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call. " + + "Might be caused by an previous timeout."); + } + } + }); + } + + private void connectToNextNode() { + // First we try our connected seed nodes + Optional connectionToSeedNodeOptional = networkNode.getConfirmedConnections().stream() + .filter(peerManager::isSeedNode) + .findAny(); + if (connectionToSeedNodeOptional.isPresent() && + connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().isPresent()) { + NodeAddress nodeAddress = connectionToSeedNodeOptional.get().getPeersNodeAddressOptional().get(); + sendRepublishRequest(nodeAddress); + } else { + // If connected seed nodes did not confirm receipt of message we try next seed node from seedNodeAddresses + List list = seedNodeAddresses.stream() + .filter(e -> peerManager.isSeedNode(e) && !peerManager.isSelf(e)) + .collect(Collectors.toList()); + Collections.shuffle(list); + + if (!list.isEmpty()) { + NodeAddress nodeAddress = list.get(0); + seedNodeAddresses.remove(nodeAddress); + sendRepublishRequest(nodeAddress); + } else { + log.warn("No more seed nodes available. We try any of our other peers."); + connectToAnyFullNode(); + } + } + } + + private void connectToAnyFullNode() { + List required = new ArrayList<>(Collections.singletonList( + Capabilities.Capability.DAO_FULL_NODE.ordinal() + )); + + List list = peerManager.getLivePeers(null).stream() + .filter(peer -> Capabilities.isCapabilitySupported(required, peer.getSupportedCapabilities())) + .collect(Collectors.toList()); + + if (list.isEmpty()) + list = peerManager.getReportedPeers().stream() + .filter(peer -> Capabilities.isCapabilitySupported(required, peer.getSupportedCapabilities())) + .collect(Collectors.toList()); + + if (list.isEmpty()) + list = peerManager.getPersistedPeers().stream() + .filter(peer -> Capabilities.isCapabilitySupported(required, peer.getSupportedCapabilities())) + .collect(Collectors.toList()); + + if (!list.isEmpty()) { + // We avoid the complexity to maintain the results of all our peers and to iterate all until we find a good peer, + // but we prefer simplicity with the risk that we don't get the data so we request from max 4 peers in parallel + // assuming that at least one will republish and therefore we should receive all data. + list = new ArrayList<>(list.subList(0, Math.min(list.size(), 4))); + list.stream() + .map(Peer::getNodeAddress) + .forEach(this::sendRepublishRequest); + } else { + log.warn("No other nodes found. We try again in 60 seconds."); + UserThread.runAfter(this::connectToNextNode, 60); + } + } + + private void handleFault(NodeAddress nodeAddress) { + peerManager.handleConnectionFault(nodeAddress); + } + + private void stop() { + stopped = true; + stopTimeoutTimer(); + } + + private void stopTimeoutTimer() { + if (timeoutTimer != null) { + timeoutTimer.stop(); + timeoutTimer = null; + } + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/network/messages/RepublishGovernanceDataRequest.java b/core/src/main/java/bisq/core/dao/governance/blindvote/network/messages/RepublishGovernanceDataRequest.java new file mode 100644 index 00000000000..c11ac0500c7 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/network/messages/RepublishGovernanceDataRequest.java @@ -0,0 +1,76 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.blindvote.network.messages; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; + +import bisq.common.app.Capabilities; +import bisq.common.app.Version; +import bisq.common.proto.network.NetworkEnvelope; + +import io.bisq.generated.protobuffer.PB; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode(callSuper = true) +@Getter +public final class RepublishGovernanceDataRequest extends NetworkEnvelope implements DirectMessage, CapabilityRequiringPayload { + + public RepublishGovernanceDataRequest() { + this(Version.getP2PMessageVersion()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private RepublishGovernanceDataRequest(int messageVersion) { + super(messageVersion); + } + + @Override + public PB.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setRepublishGovernanceDataRequest(PB.RepublishGovernanceDataRequest.newBuilder()) + .build(); + } + + public static NetworkEnvelope fromProto(PB.RepublishGovernanceDataRequest proto, int messageVersion) { + return new RepublishGovernanceDataRequest(messageVersion); + } + + @Override + public List getRequiredCapabilities() { + return new ArrayList<>(Collections.singletonList( + Capabilities.Capability.DAO_FULL_NODE.ordinal() + )); + } + + @Override + public String toString() { + return "RepublishGovernanceDataRequest{" + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVotePayload.java b/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVotePayload.java index 7033a28731e..ef081a1a874 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVotePayload.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVotePayload.java @@ -27,6 +27,7 @@ import bisq.common.app.Capabilities; import bisq.common.crypto.Hash; import bisq.common.proto.persistable.PersistableEnvelope; +import bisq.common.util.Utilities; import io.bisq.generated.protobuffer.PB; @@ -138,4 +139,13 @@ public List getRequiredCapabilities() { Capabilities.Capability.BLIND_VOTE.ordinal() )); } + + @Override + public String toString() { + return "BlindVotePayload{" + + "\n blindVote=" + blindVote + + ",\n date=" + new Date(date) + + ",\n hash=" + Utilities.bytesAsHexString(hash) + + "\n}"; + } } 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 22dc7e4be7d..c26ef3ef6cd 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 @@ -41,12 +41,13 @@ public class MeritConsensus { // Value with 144 blocks a day and 365 days would be 52560. We take a close round number instead. private static final int BLOCKS_PER_YEAR = 50_000; - public static MeritList decryptMeritList(byte[] encryptedMeritList, SecretKey secretKey) throws VoteResultException { + public static MeritList decryptMeritList(byte[] encryptedMeritList, SecretKey secretKey) + throws VoteResultException.DecryptionException { try { - final byte[] decrypted = Encryption.decrypt(encryptedMeritList, secretKey); + byte[] decrypted = Encryption.decrypt(encryptedMeritList, secretKey); return MeritList.getMeritListFromBytes(decrypted); } catch (Throwable t) { - throw new VoteResultException(t); + throw new VoteResultException.DecryptionException(t); } } diff --git a/core/src/main/java/bisq/core/dao/governance/merit/MeritList.java b/core/src/main/java/bisq/core/dao/governance/merit/MeritList.java index d9ca1d39981..7332257bf24 100644 --- a/core/src/main/java/bisq/core/dao/governance/merit/MeritList.java +++ b/core/src/main/java/bisq/core/dao/governance/merit/MeritList.java @@ -50,7 +50,7 @@ public PB.MeritList toProtoMessage() { return getBuilder().build(); } - private PB.MeritList.Builder getBuilder() { + public PB.MeritList.Builder getBuilder() { return PB.MeritList.newBuilder() .addAllMerit(getList().stream() .map(Merit::toProtoMessage) diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalService.java index ec87e34898b..56f45cf2691 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalService.java @@ -43,7 +43,6 @@ public abstract class BaseProposalService { protected final BsqWalletService bsqWalletService; protected final BtcWalletService btcWalletService; protected final BsqStateService bsqStateService; - protected final ProposalConsensus proposalConsensus; protected final ProposalValidator proposalValidator; @Nullable protected String name; @@ -58,12 +57,10 @@ public abstract class BaseProposalService { public BaseProposalService(BsqWalletService bsqWalletService, BtcWalletService btcWalletService, BsqStateService bsqStateService, - ProposalConsensus proposalConsensus, ProposalValidator proposalValidator) { this.bsqWalletService = bsqWalletService; this.btcWalletService = btcWalletService; this.bsqStateService = bsqStateService; - this.proposalConsensus = proposalConsensus; this.proposalValidator = proposalValidator; } @@ -87,12 +84,12 @@ protected ProposalWithTransaction createProposalWithTransaction(String name, // The hashOfPayload used in the opReturnData is created with the txId set to null. protected Transaction createTransaction(R proposal) throws InsufficientMoneyException, TxException { try { - final Coin fee = proposalConsensus.getFee(bsqStateService, bsqStateService.getChainHeight()); + final Coin fee = ProposalConsensus.getFee(bsqStateService, bsqStateService.getChainHeight()); // We create a prepared Bsq Tx for the proposal fee. final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedProposalTx(fee); // payload does not have txId at that moment - byte[] hashOfPayload = proposalConsensus.getHashOfPayload(proposal); + byte[] hashOfPayload = ProposalConsensus.getHashOfPayload(proposal); byte[] opReturnData = getOpReturnData(hashOfPayload); // We add the BTC inputs for the miner fee. @@ -108,7 +105,7 @@ protected Transaction createTransaction(R proposal) throws InsufficientMoneyExce } protected byte[] getOpReturnData(byte[] hashOfPayload) { - return proposalConsensus.getOpReturnData(hashOfPayload, OpReturnType.PROPOSAL.getType(), Version.PROPOSAL); + return ProposalConsensus.getOpReturnData(hashOfPayload, OpReturnType.PROPOSAL.getType(), Version.PROPOSAL); } protected Transaction completeTx(Transaction preparedBurnFeeTx, byte[] opReturnData, Proposal proposal) diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/Proposal.java b/core/src/main/java/bisq/core/dao/governance/proposal/Proposal.java index 501f23c1376..bd73f1b0a26 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/Proposal.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/Proposal.java @@ -20,7 +20,9 @@ import bisq.core.dao.governance.ConsensusCritical; import bisq.core.dao.governance.proposal.compensation.CompensationProposal; import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal; +import bisq.core.dao.governance.proposal.generic.GenericProposal; import bisq.core.dao.governance.proposal.param.ChangeParamProposal; +import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal; import bisq.core.dao.governance.proposal.role.BondedRoleProposal; import bisq.core.dao.state.blockchain.TxType; import bisq.core.dao.state.governance.Param; @@ -91,16 +93,16 @@ public static Proposal fromProto(PB.Proposal proto) { switch (proto.getMessageCase()) { case COMPENSATION_PROPOSAL: return CompensationProposal.fromProto(proto); - case GENERIC_PROPOSAL: - throw new ProtobufferRuntimeException("Not implemented yet: " + proto); case CHANGE_PARAM_PROPOSAL: return ChangeParamProposal.fromProto(proto); - case REMOVE_ALTCOIN_PROPOSAL: - throw new ProtobufferRuntimeException("Not implemented yet: " + proto); - case CONFISCATE_BOND_PROPOSAL: - return ConfiscateBondProposal.fromProto(proto); case BONDED_ROLE_PROPOSAL: return BondedRoleProposal.fromProto(proto); + case CONFISCATE_BOND_PROPOSAL: + return ConfiscateBondProposal.fromProto(proto); + case GENERIC_PROPOSAL: + return GenericProposal.fromProto(proto); + case REMOVE_ASSET_PROPOSAL: + return RemoveAssetProposal.fromProto(proto); case MESSAGE_NOT_SET: default: throw new ProtobufferRuntimeException("Unknown message case: " + proto); diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalConsensus.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalConsensus.java index dd37beda1fd..112df7a7998 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalConsensus.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalConsensus.java @@ -34,16 +34,16 @@ */ @Slf4j public class ProposalConsensus { - public Coin getFee(BsqStateService bsqStateService, int chainHeadHeight) { - return Coin.valueOf(bsqStateService.getParamValue(Param.PROPOSAL_FEE, chainHeadHeight)); + public static Coin getFee(BsqStateService bsqStateService, int chainHeight) { + return Coin.valueOf(bsqStateService.getParamValue(Param.PROPOSAL_FEE, chainHeight)); } - public byte[] getHashOfPayload(Proposal payload) { + public static byte[] getHashOfPayload(Proposal payload) { final byte[] bytes = payload.toProtoMessage().toByteArray(); return Hash.getSha256Ripemd160hash(bytes); } - public byte[] getOpReturnData(byte[] hashOfPayload, byte opReturnType, byte version) { + public static byte[] getOpReturnData(byte[] hashOfPayload, byte opReturnType, byte version) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { outputStream.write(opReturnType); outputStream.write(version); diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalType.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalType.java index 94422066e20..343ae773faf 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalType.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalType.java @@ -21,11 +21,11 @@ public enum ProposalType { COMPENSATION_REQUEST, - BONDED_ROLE, - REMOVE_ALTCOIN, CHANGE_PARAM, + BONDED_ROLE, + CONFISCATE_BOND, GENERIC, - CONFISCATE_BOND; + REMOVE_ASSET; public String getDisplayName() { return Res.get("dao.proposal.type." + name()); 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 26b2dbf510e..be72a1aea02 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 @@ -36,8 +36,8 @@ @Slf4j public class ProposalValidator { - private final BsqStateService bsqStateService; - private final PeriodService periodService; + protected final BsqStateService bsqStateService; + protected final PeriodService periodService; @Inject public ProposalValidator(BsqStateService bsqStateService, PeriodService periodService) { diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationConsensus.java b/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationConsensus.java index dacd4834c0b..fc8a0775dd0 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationConsensus.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationConsensus.java @@ -17,19 +17,21 @@ package bisq.core.dao.governance.proposal.compensation; -import bisq.core.dao.governance.proposal.ProposalConsensus; +import bisq.core.dao.state.BsqStateService; +import bisq.core.dao.state.governance.Param; import org.bitcoinj.core.Coin; import lombok.extern.slf4j.Slf4j; @Slf4j -public class CompensationConsensus extends ProposalConsensus { - public static Coin getMinCompensationRequestAmount() { - return Coin.valueOf(1_000); // 10 BSQ +public class CompensationConsensus { + public static Coin getMinCompensationRequestAmount(BsqStateService bsqStateService, int chainHeight) { + return Coin.valueOf(bsqStateService.getParamValue(Param.COMPENSATION_REQUEST_MIN_AMOUNT, chainHeight)); } - static Coin getMaxCompensationRequestAmount() { - return Coin.valueOf(20_000_000); // 200 000 BSQ + public static Coin getMaxCompensationRequestAmount(BsqStateService bsqStateService, int chainHeight) { + return Coin.valueOf(bsqStateService.getParamValue(Param.COMPENSATION_REQUEST_MAX_AMOUNT, chainHeight)); } + } diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationProposalService.java index e5cc3695793..22e07d1f2f8 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationProposalService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/compensation/CompensationProposalService.java @@ -53,12 +53,10 @@ public class CompensationProposalService extends BaseProposalService= 0, - "Requested BSQ must not be less than MinCompensationRequestAmount"); + Coin maxCompensationRequestAmount = CompensationConsensus.getMaxCompensationRequestAmount(bsqStateService, periodService.getChainHeight()); + checkArgument(requestedBsq.compareTo(maxCompensationRequestAmount) <= 0, + "Requested BSQ must not exceed " + (maxCompensationRequestAmount.value / 100L) + " BSQ"); + Coin minCompensationRequestAmount = CompensationConsensus.getMinCompensationRequestAmount(bsqStateService, periodService.getChainHeight()); + checkArgument(requestedBsq.compareTo(minCompensationRequestAmount) >= 0, + "Requested BSQ must not be less than " + (minCompensationRequestAmount.value / 100L) + " BSQ"); + } catch (Throwable throwable) { throw new ValidationException(throwable); } diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondProposalService.java index 987de19aa6e..4e351668e80 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondProposalService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/confiscatebond/ConfiscateBondProposalService.java @@ -21,7 +21,6 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.exceptions.ValidationException; import bisq.core.dao.governance.proposal.BaseProposalService; -import bisq.core.dao.governance.proposal.ProposalConsensus; import bisq.core.dao.governance.proposal.ProposalWithTransaction; import bisq.core.dao.governance.proposal.TxException; import bisq.core.dao.state.BsqStateService; @@ -48,12 +47,10 @@ public class ConfiscateBondProposalService extends BaseProposalService. + */ + +package bisq.core.dao.governance.proposal.generic; + +import bisq.core.dao.governance.proposal.Proposal; +import bisq.core.dao.governance.proposal.ProposalType; +import bisq.core.dao.state.blockchain.TxType; +import bisq.core.dao.state.governance.Param; + +import bisq.common.app.Version; + +import io.bisq.generated.protobuffer.PB; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@Slf4j +@EqualsAndHashCode(callSuper = true) +@Value +public final class GenericProposal extends Proposal { + + GenericProposal(String name, + String link) { + this(name, + link, + Version.PROPOSAL, + new Date().getTime(), + ""); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private GenericProposal(String name, + String link, + byte version, + long creationDate, + String txId) { + super(name, + link, + version, + creationDate, + txId); + } + + @Override + public PB.Proposal.Builder getProposalBuilder() { + final PB.GenericProposal.Builder builder = PB.GenericProposal.newBuilder(); + return super.getProposalBuilder().setGenericProposal(builder); + } + + public static GenericProposal fromProto(PB.Proposal proto) { + return new GenericProposal(proto.getName(), + proto.getLink(), + (byte) proto.getVersion(), + proto.getCreationDate(), + proto.getTxId()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ProposalType getType() { + return ProposalType.GENERIC; + } + + @Override + public Param getQuorumParam() { + return Param.QUORUM_GENERIC; + } + + @Override + public Param getThresholdParam() { + return Param.THRESHOLD_GENERIC; + } + + @Override + public TxType getTxType() { + return TxType.PROPOSAL; + } + + @Override + public Proposal cloneProposalAndAddTxId(String txId) { + return new GenericProposal(getName(), + getLink(), + getVersion(), + getCreationDate().getTime(), + txId); + } + + @Override + public String toString() { + return "GenericProposal{" + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/generic/GenericProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/generic/GenericProposalService.java new file mode 100644 index 00000000000..6d88307a2ca --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/proposal/generic/GenericProposalService.java @@ -0,0 +1,66 @@ +/* + * 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.governance.proposal.generic; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.dao.exceptions.ValidationException; +import bisq.core.dao.governance.proposal.BaseProposalService; +import bisq.core.dao.governance.proposal.ProposalWithTransaction; +import bisq.core.dao.governance.proposal.TxException; +import bisq.core.dao.state.BsqStateService; + +import org.bitcoinj.core.InsufficientMoneyException; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +/** + * Creates GenericProposal and transaction. + */ +@Slf4j +public class GenericProposalService extends BaseProposalService { + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public GenericProposalService(BsqWalletService bsqWalletService, + BtcWalletService btcWalletService, + BsqStateService bsqStateService, + GenericProposalValidator proposalValidator) { + super(bsqWalletService, + btcWalletService, + bsqStateService, + proposalValidator); + } + + public ProposalWithTransaction createProposalWithTransaction(String name, String link) + throws ValidationException, InsufficientMoneyException, TxException { + + return super.createProposalWithTransaction(name, link); + } + + @Override + protected GenericProposal createProposalWithoutTxId() { + return new GenericProposal(name, link); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/generic/GenericProposalValidator.java b/core/src/main/java/bisq/core/dao/governance/proposal/generic/GenericProposalValidator.java new file mode 100644 index 00000000000..0435d2782b1 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/proposal/generic/GenericProposalValidator.java @@ -0,0 +1,49 @@ +/* + * 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.governance.proposal.generic; + +import bisq.core.dao.exceptions.ValidationException; +import bisq.core.dao.governance.proposal.Proposal; +import bisq.core.dao.governance.proposal.ProposalValidator; +import bisq.core.dao.state.BsqStateService; +import bisq.core.dao.state.period.PeriodService; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class GenericProposalValidator extends ProposalValidator { + + @Inject + public GenericProposalValidator(BsqStateService bsqStateService, PeriodService periodService) { + super(bsqStateService, periodService); + } + + @Override + public void validateDataFields(Proposal proposal) throws ValidationException { + try { + super.validateDataFields(proposal); + + GenericProposal genericProposalProposal = (GenericProposal) proposal; + //TODO + } catch (Throwable throwable) { + throw new ValidationException(throwable); + } + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamProposalService.java index 64f179bdb48..4df9922855c 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamProposalService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/param/ChangeParamProposalService.java @@ -21,7 +21,6 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.exceptions.ValidationException; import bisq.core.dao.governance.proposal.BaseProposalService; -import bisq.core.dao.governance.proposal.ProposalConsensus; import bisq.core.dao.governance.proposal.ProposalWithTransaction; import bisq.core.dao.governance.proposal.TxException; import bisq.core.dao.state.BsqStateService; @@ -50,12 +49,10 @@ public class ChangeParamProposalService extends BaseProposalService. */ -package bisq.core.dao.governance.voteresult; +package bisq.core.dao.governance.proposal.param; -import bisq.core.dao.governance.ballot.Ballot; - -import java.util.List; - -import lombok.EqualsAndHashCode; -import lombok.Value; - -@EqualsAndHashCode(callSuper = true) -@Value -public class MissingBallotException extends Exception { - private List existingBallots; - private List proposalTxIdsOfMissingBallots; - - public MissingBallotException(List existingBallots, List proposalTxIdsOfMissingBallots) { - this.existingBallots = existingBallots; - this.proposalTxIdsOfMissingBallots = proposalTxIdsOfMissingBallots; +public class ChangeParamValidationException extends Exception { + public ChangeParamValidationException(String message) { + super(message); } } 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 e919a4087e5..dd123a325fb 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 @@ -17,22 +17,33 @@ package bisq.core.dao.governance.proposal.param; +import bisq.core.btc.wallet.Restrictions; import bisq.core.dao.exceptions.ValidationException; import bisq.core.dao.governance.proposal.Proposal; import bisq.core.dao.governance.proposal.ProposalValidator; import bisq.core.dao.state.BsqStateService; +import bisq.core.dao.state.governance.Param; import bisq.core.dao.state.period.PeriodService; +import bisq.core.locale.Res; +import bisq.core.util.BsqFormatter; import javax.inject.Inject; +import com.google.common.annotations.VisibleForTesting; + import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkArgument; + @Slf4j public class ChangeParamValidator extends ProposalValidator { + private BsqFormatter bsqFormatter; + @Inject - public ChangeParamValidator(BsqStateService bsqStateService, PeriodService periodService) { + public ChangeParamValidator(BsqStateService bsqStateService, PeriodService periodService, BsqFormatter bsqFormatter) { super(bsqStateService, periodService); + this.bsqFormatter = bsqFormatter; } @Override @@ -41,9 +52,113 @@ public void validateDataFields(Proposal proposal) throws ValidationException { super.validateDataFields(proposal); ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal; - //TODO + + validateParamValue(changeParamProposal.getParam(), changeParamProposal.getParamValue()); } catch (Throwable throwable) { throw new ValidationException(throwable); } } + + // TODO + public void validateParamValue(Param param, long paramValue) throws ChangeParamValidationException { + // max 4 times the current value. min 25% of current value as general boundaries + checkMinMax(param, paramValue, 300, -75); + + switch (param) { + case UNDEFINED: + break; + + case DEFAULT_MAKER_FEE_BSQ: + break; + case DEFAULT_TAKER_FEE_BSQ: + break; + case DEFAULT_MAKER_FEE_BTC: + break; + case DEFAULT_TAKER_FEE_BTC: + break; + + case PROPOSAL_FEE: + break; + case BLIND_VOTE_FEE: + break; + + case COMPENSATION_REQUEST_MIN_AMOUNT: + if (paramValue < Restrictions.getMinNonDustOutput().value) + throw new ChangeParamValidationException(Res.get("validation.amountBelowDust", Restrictions.getMinNonDustOutput().value)); + checkMinMax(param, paramValue, 100, -50); + break; + case COMPENSATION_REQUEST_MAX_AMOUNT: + checkMinMax(param, paramValue, 100, -50); + break; + + case QUORUM_COMP_REQUEST: + break; + case QUORUM_CHANGE_PARAM: + break; + case QUORUM_ROLE: + break; + case QUORUM_CONFISCATION: + break; + case QUORUM_GENERIC: + break; + case QUORUM_REMOVE_ASSET: + break; + + case THRESHOLD_COMP_REQUEST: + break; + case THRESHOLD_CHANGE_PARAM: + break; + case THRESHOLD_ROLE: + break; + case THRESHOLD_CONFISCATION: + break; + case THRESHOLD_GENERIC: + break; + case THRESHOLD_REMOVE_ASSET: + break; + + case PHASE_UNDEFINED: + break; + case PHASE_PROPOSAL: + break; + case PHASE_BREAK1: + break; + case PHASE_BLIND_VOTE: + break; + case PHASE_BREAK2: + break; + case PHASE_VOTE_REVEAL: + break; + case PHASE_BREAK3: + break; + case PHASE_RESULT: + break; + } + } + + private void checkMinMax(Param param, long paramValue, long maxPercentChange, long minPercentChange) throws ChangeParamValidationException { + long max = getNewValueByPercentChange(param, maxPercentChange); + if (paramValue > max) + throw new ChangeParamValidationException(Res.get("validation.inputTooLarge", bsqFormatter.formatParamValue(param, max))); + long min = getNewValueByPercentChange(param, minPercentChange); + if (paramValue < min) + throw new ChangeParamValidationException(Res.get("validation.inputTooSmall", bsqFormatter.formatParamValue(param, min))); + } + + /** + * @param param The param to change + * @param percentChange 100 means 100% more than current value -> 2 times current value. -50 means half of the current value + * @return The new value. + */ + //TODO add test + // TODO use multiplier to make it more intuitive? (4,4) means 4 times current value for max and divided by 4 to get min value) + @VisibleForTesting + long getNewValueByPercentChange(Param param, long percentChange) { + checkArgument(percentChange > -100, "percentChange must be bigger than -100"); + return (getCurrentValue(param) * 100 * (100 + percentChange)) / 10000; + } + + private long getCurrentValue(Param param) { + return bsqStateService.getParamValue(param, periodService.getChainHeight()); + } } diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetProposal.java b/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetProposal.java new file mode 100644 index 00000000000..5f497aa022c --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetProposal.java @@ -0,0 +1,133 @@ +/* + * 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.governance.proposal.removeAsset; + +import bisq.core.dao.governance.proposal.Proposal; +import bisq.core.dao.governance.proposal.ProposalType; +import bisq.core.dao.state.blockchain.TxType; +import bisq.core.dao.state.governance.Param; + +import bisq.common.app.Version; + +import io.bisq.generated.protobuffer.PB; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@Slf4j +@EqualsAndHashCode(callSuper = true) +@Value +public final class RemoveAssetProposal extends Proposal { + private final String tickerSymbol; + + RemoveAssetProposal(String name, + String link, + String tickerSymbol) { + this(name, + link, + tickerSymbol, + Version.PROPOSAL, + new Date().getTime(), + ""); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private RemoveAssetProposal(String name, + String link, + String tickerSymbol, + byte version, + long creationDate, + String txId) { + super(name, + link, + version, + creationDate, + txId); + + this.tickerSymbol = tickerSymbol; + } + + @Override + public PB.Proposal.Builder getProposalBuilder() { + final PB.RemoveAssetProposal.Builder builder = PB.RemoveAssetProposal.newBuilder() + .setTickerSymbol(tickerSymbol); + return super.getProposalBuilder().setRemoveAssetProposal(builder); + } + + public static RemoveAssetProposal fromProto(PB.Proposal proto) { + final PB.RemoveAssetProposal proposalProto = proto.getRemoveAssetProposal(); + return new RemoveAssetProposal(proto.getName(), + proto.getLink(), + proposalProto.getTickerSymbol(), + (byte) proto.getVersion(), + proto.getCreationDate(), + proto.getTxId()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ProposalType getType() { + return ProposalType.REMOVE_ASSET; + } + + @Override + public Param getQuorumParam() { + return Param.QUORUM_REMOVE_ASSET; + } + + @Override + public Param getThresholdParam() { + return Param.THRESHOLD_REMOVE_ASSET; + } + + @Override + public TxType getTxType() { + return TxType.PROPOSAL; + } + + @Override + public Proposal cloneProposalAndAddTxId(String txId) { + return new RemoveAssetProposal(getName(), + getLink(), + getTickerSymbol(), + getVersion(), + getCreationDate().getTime(), + txId); + } + + @Override + public String toString() { + return "GenericProposal{" + + "\n tickerSymbol=" + tickerSymbol + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetProposalService.java new file mode 100644 index 00000000000..ba20114335d --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetProposalService.java @@ -0,0 +1,77 @@ +/* + * This file is part of Bisq. + * + * bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with bisq. If not, see . + */ + +package bisq.core.dao.governance.proposal.removeAsset; + +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.dao.exceptions.ValidationException; +import bisq.core.dao.governance.proposal.BaseProposalService; +import bisq.core.dao.governance.proposal.ProposalWithTransaction; +import bisq.core.dao.governance.proposal.TxException; +import bisq.core.dao.state.BsqStateService; + +import org.bitcoinj.core.InsufficientMoneyException; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + + + +import bisq.asset.Asset; + +/** + * Creates RemoveAssetProposal and transaction. + */ +@Slf4j +public class RemoveAssetProposalService extends BaseProposalService { + private Asset asset; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public RemoveAssetProposalService(BsqWalletService bsqWalletService, + BtcWalletService btcWalletService, + BsqStateService bsqStateService, + RemoveAssetValidator proposalValidator) { + super(bsqWalletService, + btcWalletService, + bsqStateService, + proposalValidator); + } + + public ProposalWithTransaction createProposalWithTransaction(String name, + String link, + Asset asset) + throws ValidationException, InsufficientMoneyException, TxException { + this.asset = asset; + + return super.createProposalWithTransaction(name, link); + } + + @Override + protected RemoveAssetProposal createProposalWithoutTxId() { + return new RemoveAssetProposal( + name, + link, + asset.getTickerSymbol()); + } +} 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 new file mode 100644 index 00000000000..d88e4fa2a63 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/proposal/removeAsset/RemoveAssetValidator.java @@ -0,0 +1,49 @@ +/* + * 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.governance.proposal.removeAsset; + +import bisq.core.dao.exceptions.ValidationException; +import bisq.core.dao.governance.proposal.Proposal; +import bisq.core.dao.governance.proposal.ProposalValidator; +import bisq.core.dao.state.BsqStateService; +import bisq.core.dao.state.period.PeriodService; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RemoveAssetValidator extends ProposalValidator { + + @Inject + public RemoveAssetValidator(BsqStateService bsqStateService, PeriodService periodService) { + super(bsqStateService, periodService); + } + + @Override + public void validateDataFields(Proposal proposal) throws ValidationException { + try { + super.validateDataFields(proposal); + + RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) proposal; + //TODO + } catch (Throwable throwable) { + throw new ValidationException(throwable); + } + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposal.java b/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposal.java index 1fd2dcff610..0a961b5e29d 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposal.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposal.java @@ -100,12 +100,12 @@ public ProposalType getType() { @Override public Param getQuorumParam() { - return Param.QUORUM_PROPOSAL; + return Param.QUORUM_ROLE; } @Override public Param getThresholdParam() { - return Param.THRESHOLD_PROPOSAL; + return Param.THRESHOLD_ROLE; } @Override diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposalService.java index 0630fbb2964..afccb954f4b 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposalService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/role/BondedRoleProposalService.java @@ -21,7 +21,6 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.dao.exceptions.ValidationException; import bisq.core.dao.governance.proposal.BaseProposalService; -import bisq.core.dao.governance.proposal.ProposalConsensus; import bisq.core.dao.governance.proposal.ProposalWithTransaction; import bisq.core.dao.governance.proposal.TxException; import bisq.core.dao.governance.role.BondedRole; @@ -49,12 +48,10 @@ public class BondedRoleProposalService extends BaseProposalService getVote(String proposalTxId) { return ballotList.stream() .filter(ballot -> ballot.getTxId().equals(proposalTxId)) diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/DecryptedBallotsWithMeritsList.java b/core/src/main/java/bisq/core/dao/governance/voteresult/DecryptedBallotsWithMeritsList.java new file mode 100644 index 00000000000..ba57ec0cda4 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/DecryptedBallotsWithMeritsList.java @@ -0,0 +1,76 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.voteresult; + +import bisq.core.dao.governance.ConsensusCritical; + +import bisq.common.proto.persistable.PersistableList; + +import io.bisq.generated.protobuffer.PB; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.EqualsAndHashCode; + +/** + * PersistableEnvelope wrapper for list of decryptedBallotsWithMerits. + */ +@EqualsAndHashCode(callSuper = true) +public class DecryptedBallotsWithMeritsList extends PersistableList implements ConsensusCritical { + + public DecryptedBallotsWithMeritsList(List list) { + super(list); + } + + public DecryptedBallotsWithMeritsList() { + super(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public PB.PersistableEnvelope toProtoMessage() { + return PB.PersistableEnvelope.newBuilder().setDecryptedBallotsWithMeritsList(getBuilder()).build(); + } + + private PB.DecryptedBallotsWithMeritsList.Builder getBuilder() { + return PB.DecryptedBallotsWithMeritsList.newBuilder() + .addAllDecryptedBallotsWithMerits(getList().stream() + .map(DecryptedBallotsWithMerits::toProtoMessage) + .collect(Collectors.toList())); + } + + public static DecryptedBallotsWithMeritsList fromProto(PB.DecryptedBallotsWithMeritsList proto) { + return new DecryptedBallotsWithMeritsList(new ArrayList<>(proto.getDecryptedBallotsWithMeritsList().stream() + .map(DecryptedBallotsWithMerits::fromProto) + .collect(Collectors.toList()))); + } + + @Override + public String toString() { + return "List of blindVoteTxId's in DecryptedBallotsWithMeritsList: " + getList().stream() + .map(DecryptedBallotsWithMerits::getBlindVoteTxId) + .collect(Collectors.toList()); + } +} + diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/EvaluatedProposal.java b/core/src/main/java/bisq/core/dao/governance/voteresult/EvaluatedProposal.java index bacd412f313..4945f741a99 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/EvaluatedProposal.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/EvaluatedProposal.java @@ -19,10 +19,14 @@ import bisq.core.dao.governance.proposal.Proposal; +import bisq.common.proto.persistable.PersistablePayload; + +import io.bisq.generated.protobuffer.PB; + import lombok.Value; @Value -public class EvaluatedProposal { +public class EvaluatedProposal implements PersistablePayload { private final boolean isAccepted; private final ProposalVoteResult proposalVoteResult; private final long requiredQuorum; @@ -35,6 +39,33 @@ public class EvaluatedProposal { this.requiredThreshold = requiredThreshold; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public PB.EvaluatedProposal toProtoMessage() { + PB.EvaluatedProposal.Builder builder = PB.EvaluatedProposal.newBuilder() + .setIsAccepted(isAccepted) + .setProposalVoteResult(proposalVoteResult.toProtoMessage()) + .setRequiredQuorum(requiredQuorum) + .setRequiredThreshold(requiredThreshold); + return builder.build(); + } + + public static EvaluatedProposal fromProto(PB.EvaluatedProposal proto) { + return new EvaluatedProposal(proto.getIsAccepted(), + ProposalVoteResult.fromProto(proto.getProposalVoteResult()), + proto.getRequiredQuorum(), + proto.getRequiredThreshold()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + public Proposal getProposal() { return proposalVoteResult.getProposal(); } diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/EvaluatedProposalList.java b/core/src/main/java/bisq/core/dao/governance/voteresult/EvaluatedProposalList.java new file mode 100644 index 00000000000..0ebe0003015 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/EvaluatedProposalList.java @@ -0,0 +1,76 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.voteresult; + +import bisq.core.dao.governance.ConsensusCritical; + +import bisq.common.proto.persistable.PersistableList; + +import io.bisq.generated.protobuffer.PB; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.EqualsAndHashCode; + +/** + * PersistableEnvelope wrapper for list of evaluatedProposals. + */ +@EqualsAndHashCode(callSuper = true) +public class EvaluatedProposalList extends PersistableList implements ConsensusCritical { + + public EvaluatedProposalList(List list) { + super(list); + } + + public EvaluatedProposalList() { + super(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public PB.PersistableEnvelope toProtoMessage() { + return PB.PersistableEnvelope.newBuilder().setEvaluatedProposalList(getBuilder()).build(); + } + + public PB.EvaluatedProposalList.Builder getBuilder() { + return PB.EvaluatedProposalList.newBuilder() + .addAllEvaluatedProposal(getList().stream() + .map(EvaluatedProposal::toProtoMessage) + .collect(Collectors.toList())); + } + + public static EvaluatedProposalList fromProto(PB.EvaluatedProposalList proto) { + return new EvaluatedProposalList(new ArrayList<>(proto.getEvaluatedProposalList().stream() + .map(EvaluatedProposal::fromProto) + .collect(Collectors.toList()))); + } + + @Override + public String toString() { + return "List of proposalTxId's in EvaluatedProposalList: " + getList().stream() + .map(EvaluatedProposal::getProposalTxId) + .collect(Collectors.toList()); + } +} + 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 new file mode 100644 index 00000000000..d0b47df820a --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java @@ -0,0 +1,69 @@ +/* + * 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.governance.voteresult; + +import bisq.core.dao.DaoSetupService; +import bisq.core.dao.governance.blindvote.network.RepublishGovernanceDataHandler; + +import javax.inject.Inject; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +import lombok.Getter; + +public class MissingDataRequestService implements DaoSetupService { + private final RepublishGovernanceDataHandler republishGovernanceDataHandler; + + @Getter + private final ObservableList voteResultExceptions = FXCollections.observableArrayList(); + + @Inject + public MissingDataRequestService(RepublishGovernanceDataHandler republishGovernanceDataHandler) { + this.republishGovernanceDataHandler = republishGovernanceDataHandler; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoSetupService + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void addListeners() { + voteResultExceptions.addListener((ListChangeListener) c -> { + c.next(); + if (c.wasAdded()) { + republishGovernanceDataHandler.sendRepublishRequest(); + } + }); + } + + @Override + public void start() { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void addVoteResultException(VoteResultException voteResultException) { + this.voteResultExceptions.add(voteResultException); + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/ProposalVoteResult.java b/core/src/main/java/bisq/core/dao/governance/voteresult/ProposalVoteResult.java index 42c66a2b3af..14b0d3172d8 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/ProposalVoteResult.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/ProposalVoteResult.java @@ -19,6 +19,10 @@ import bisq.core.dao.governance.proposal.Proposal; +import bisq.common.proto.persistable.PersistablePayload; + +import io.bisq.generated.protobuffer.PB; + import lombok.Value; import lombok.extern.slf4j.Slf4j; @@ -26,7 +30,7 @@ @Value @Slf4j -public class ProposalVoteResult { +public class ProposalVoteResult implements PersistablePayload { private final Proposal proposal; private final long stakeOfAcceptedVotes; private final long stakeOfRejectedVotes; @@ -44,6 +48,36 @@ public class ProposalVoteResult { this.numIgnoredVotes = numIgnoredVotes; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public PB.ProposalVoteResult toProtoMessage() { + PB.ProposalVoteResult.Builder builder = PB.ProposalVoteResult.newBuilder() + .setProposal(proposal.toProtoMessage()) + .setStakeOfAcceptedVotes(stakeOfAcceptedVotes) + .setStakeOfRejectedVotes(stakeOfRejectedVotes) + .setNumAcceptedVotes(numAcceptedVotes) + .setNumRejectedVotes(numRejectedVotes) + .setNumIgnoredVotes(numIgnoredVotes); + return builder.build(); + } + + public static ProposalVoteResult fromProto(PB.ProposalVoteResult proto) { + return new ProposalVoteResult(Proposal.fromProto(proto.getProposal()), + proto.getStakeOfAcceptedVotes(), + proto.getStakeOfRejectedVotes(), + proto.getNumAcceptedVotes(), + proto.getNumRejectedVotes(), + proto.getNumIgnoredVotes()); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + public int getNumActiveVotes() { return numAcceptedVotes + numRejectedVotes; } 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 2b9bf79209e..3b0ad118e7d 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 @@ -54,35 +54,41 @@ public static byte[] getHashOfBlindVoteList(byte[] opReturnData) { return Arrays.copyOfRange(opReturnData, 2, 22); } - public static VoteWithProposalTxIdList decryptVotes(byte[] encryptedVotes, SecretKey secretKey) throws VoteResultException { + public static VoteWithProposalTxIdList decryptVotes(byte[] encryptedVotes, SecretKey secretKey) + throws VoteResultException.DecryptionException { try { byte[] decrypted = Encryption.decrypt(encryptedVotes, secretKey); return VoteWithProposalTxIdList.getVoteWithProposalTxIdListFromBytes(decrypted); } catch (Throwable t) { - throw new VoteResultException(t); + throw new VoteResultException.DecryptionException(t); } } // We compare first by stake and in case we have multiple entries with same stake we use the // hex encoded hashOfProposalList for comparision @Nullable - public static byte[] getMajorityHash(List hashWithStakeList) throws VoteResultException { - checkArgument(!hashWithStakeList.isEmpty(), "hashWithStakeList must not be empty"); - hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed() - .thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash()))); - - // If there are conflicting data views (multiple hashes) we only consider the voting round as valid if - // the majority is a super majority with > 80%. - if (hashWithStakeList.size() > 1) { - long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum(); - long stakeOfFirst = hashWithStakeList.get(0).getStake(); - if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) { - throw new VoteResultException("The winning data view has less then 80% of the total stake of " + - "all data views. We consider the voting cycle as invalid if the winning data view does not " + - "reach a super majority."); + public static byte[] getMajorityHash(List hashWithStakeList) + throws VoteResultException.ConsensusException, VoteResultException.ValidationException { + try { + checkArgument(!hashWithStakeList.isEmpty(), "hashWithStakeList must not be empty"); + hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed() + .thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash()))); + + // If there are conflicting data views (multiple hashes) we only consider the voting round as valid if + // the majority is a super majority with > 80%. + if (hashWithStakeList.size() > 1) { + long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum(); + long stakeOfFirst = hashWithStakeList.get(0).getStake(); + if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) { + throw new VoteResultException.ConsensusException("The winning data view has less then 80% of the " + + "total stake of all data views. We consider the voting cycle as invalid if the " + + "winning data view does not reach a super majority."); + } } + return hashWithStakeList.get(0).getHash(); + } catch (Throwable t) { + throw new VoteResultException.ValidationException(t); } - return hashWithStakeList.get(0).getHash(); } // Key is stored after version and type bytes and list of Blind votes. It has 16 bytes @@ -92,24 +98,24 @@ public static SecretKey getSecretKey(byte[] opReturnData) { } public static TxOutput getConnectedBlindVoteStakeOutput(Tx voteRevealTx, BsqStateService bsqStateService) - throws VoteResultException { + throws VoteResultException.ValidationException { try { // We use the stake output of the blind vote tx as first input - final TxInput stakeTxInput = voteRevealTx.getTxInputs().get(0); + TxInput stakeTxInput = voteRevealTx.getTxInputs().get(0); Optional optionalBlindVoteStakeOutput = bsqStateService.getConnectedTxOutput(stakeTxInput); checkArgument(optionalBlindVoteStakeOutput.isPresent(), "blindVoteStakeOutput must be present"); - final TxOutput blindVoteStakeOutput = optionalBlindVoteStakeOutput.get(); + TxOutput blindVoteStakeOutput = optionalBlindVoteStakeOutput.get(); checkArgument(blindVoteStakeOutput.getTxOutputType() == TxOutputType.BLIND_VOTE_LOCK_STAKE_OUTPUT, "blindVoteStakeOutput must be of type BLIND_VOTE_LOCK_STAKE_OUTPUT"); return blindVoteStakeOutput; } catch (Throwable t) { - throw new VoteResultException(t); + throw new VoteResultException.ValidationException(t); } } public static Tx getBlindVoteTx(TxOutput blindVoteStakeOutput, BsqStateService bsqStateService, PeriodService periodService, int chainHeight) - throws VoteResultException { + throws VoteResultException.ValidationException { try { String blindVoteTxId = blindVoteStakeOutput.getTxId(); Optional optionalBlindVoteTx = bsqStateService.getTx(blindVoteTxId); @@ -128,7 +134,7 @@ public static Tx getBlindVoteTx(TxOutput blindVoteStakeOutput, BsqStateService b + blindVoteTx.getBlockHeight()); return blindVoteTx; } catch (Throwable t) { - throw new VoteResultException(t); + throw new VoteResultException.ValidationException(t); } } } diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java index 35a0dbb45b5..04ea068b795 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java @@ -17,35 +17,112 @@ package bisq.core.dao.governance.voteresult; -import bisq.core.dao.governance.blindvote.storage.BlindVotePayload; +import bisq.core.dao.governance.ballot.Ballot; -import lombok.Getter; +import java.util.List; -import javax.annotation.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Value; public class VoteResultException extends Exception { - @Getter - @Nullable - private BlindVotePayload blindVotePayload; - public VoteResultException(String message, Exception cause, BlindVotePayload blindVotePayload) { - super(message, cause); - this.blindVotePayload = blindVotePayload; + VoteResultException(Throwable cause) { + super(cause); } - public VoteResultException(String message) { + private VoteResultException(String message) { super(message); } - public VoteResultException(Throwable cause) { - super(cause); + private VoteResultException(String message, Throwable cause) { + super(message, cause); } @Override public String toString() { return "VoteResultException{" + - "\n blindVotePayload=" + blindVotePayload + - "\n cause=" + getCause() + "\n} " + super.toString(); } + + @EqualsAndHashCode(callSuper = true) + public static class ConsensusException extends VoteResultException { + + ConsensusException(String message) { + super(message); + } + + @Override + public String toString() { + return "ConsensusException{" + + "\n} " + super.toString(); + } + } + + @EqualsAndHashCode(callSuper = true) + public static class ValidationException extends VoteResultException { + + ValidationException(Throwable cause) { + super("Validation of vote result failed.", cause); + } + + @Override + public String toString() { + return "VoteResultException{" + + "\n cause=" + getCause() + + "\n} " + super.toString(); + } + } + + @EqualsAndHashCode(callSuper = true) + public static abstract class MissingDataException extends VoteResultException { + private MissingDataException(String message) { + super(message); + } + } + + @EqualsAndHashCode(callSuper = true) + @Value + public static class MissingBlindVoteDataException extends MissingDataException { + private String blindVoteTxId; + + MissingBlindVoteDataException(String blindVoteTxId) { + super("Blind vote tx ID " + blindVoteTxId + " is missing"); + this.blindVoteTxId = blindVoteTxId; + } + + @Override + public String toString() { + return "MissingBlindVoteDataException{" + + "\n blindVoteTxId='" + blindVoteTxId + '\'' + + "\n} " + super.toString(); + } + } + + @EqualsAndHashCode(callSuper = true) + @Value + public static class MissingBallotException extends MissingDataException { + private List existingBallots; + private List proposalTxIdsOfMissingBallots; + + MissingBallotException(List existingBallots, List proposalTxIdsOfMissingBallots) { + super("Missing ballots. proposalTxIdsOfMissingBallots=" + proposalTxIdsOfMissingBallots); + this.existingBallots = existingBallots; + this.proposalTxIdsOfMissingBallots = proposalTxIdsOfMissingBallots; + } + } + + + @EqualsAndHashCode(callSuper = true) + @Value + public static class DecryptionException extends VoteResultException { + public DecryptionException(Throwable cause) { + super(cause); + } + + @Override + public String toString() { + return "DecryptionException{" + + "\n} " + super.toString(); + } + } } 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 44ab17d2c42..425789f3091 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 @@ -17,7 +17,9 @@ package bisq.core.dao.governance.voteresult; +import bisq.core.app.BisqEnvironment; import bisq.core.dao.DaoSetupService; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.Ballot; import bisq.core.dao.governance.ballot.BallotList; import bisq.core.dao.governance.ballot.BallotListService; @@ -34,6 +36,7 @@ import bisq.core.dao.governance.proposal.compensation.CompensationProposal; import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal; import bisq.core.dao.governance.proposal.param.ChangeParamProposal; +import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal; import bisq.core.dao.governance.proposal.role.BondedRoleProposal; import bisq.core.dao.governance.role.BondedRole; import bisq.core.dao.governance.role.BondedRolesService; @@ -49,9 +52,12 @@ import bisq.core.dao.state.governance.ParamChange; import bisq.core.dao.state.period.DaoPhase; import bisq.core.dao.state.period.PeriodService; +import bisq.core.locale.CurrencyUtil; import bisq.network.p2p.storage.P2PDataStorage; +import bisq.common.proto.persistable.PersistedDataHost; +import bisq.common.storage.Storage; import bisq.common.util.Utilities; import javax.inject.Inject; @@ -89,7 +95,7 @@ * the missing blindVotes from the network. */ @Slf4j -public class VoteResultService implements BsqStateListener, DaoSetupService { +public class VoteResultService implements BsqStateListener, DaoSetupService, PersistedDataHost { private final VoteRevealService voteRevealService; private final ProposalListPresentation proposalListPresentation; private final BsqStateService bsqStateService; @@ -98,15 +104,17 @@ public class VoteResultService implements BsqStateListener, DaoSetupService { private final BlindVoteListService blindVoteListService; private final BondedRolesService bondedRolesService; private final IssuanceService issuanceService; + private final AssetService assetService; + private final MissingDataRequestService missingDataRequestService; @Getter private final ObservableList voteResultExceptions = FXCollections.observableArrayList(); - // Use a list to have order by cycle - // TODO persist, PB + private final Storage evaluatedProposalStorage; + private Storage decryptedBallotsWithMeritsStorage; @Getter - private final Set allEvaluatedProposals = new HashSet<>(); + private final EvaluatedProposalList evaluatedProposalList = new EvaluatedProposalList(); @Getter - private final Set allDecryptedBallotsWithMerits = new HashSet<>(); + private final DecryptedBallotsWithMeritsList decryptedBallotsWithMeritsList = new DecryptedBallotsWithMeritsList(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -121,7 +129,11 @@ public VoteResultService(VoteRevealService voteRevealService, BallotListService ballotListService, BlindVoteListService blindVoteListService, BondedRolesService bondedRolesService, - IssuanceService issuanceService) { + IssuanceService issuanceService, + AssetService assetService, + MissingDataRequestService missingDataRequestService, + Storage evaluatedProposalStorage, + Storage decryptedBallotsWithMeritsStorage) { this.voteRevealService = voteRevealService; this.proposalListPresentation = proposalListPresentation; this.bsqStateService = bsqStateService; @@ -130,34 +142,47 @@ public VoteResultService(VoteRevealService voteRevealService, this.blindVoteListService = blindVoteListService; this.bondedRolesService = bondedRolesService; this.issuanceService = issuanceService; + this.assetService = assetService; + this.missingDataRequestService = missingDataRequestService; + this.evaluatedProposalStorage = evaluatedProposalStorage; + this.decryptedBallotsWithMeritsStorage = decryptedBallotsWithMeritsStorage; } /////////////////////////////////////////////////////////////////////////////////////////// - // DaoSetupService + // PersistedDataHost /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void addListeners() { - bsqStateService.addBsqStateListener(this); - } + public void readPersisted() { + if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) { + EvaluatedProposalList persisted1 = evaluatedProposalStorage.initAndGetPersisted(evaluatedProposalList, 100); + if (persisted1 != null) { + evaluatedProposalList.clear(); + evaluatedProposalList.addAll(persisted1.getList()); + } - @Override - public void start() { - maybeCalculateVoteResult(bsqStateService.getChainHeight()); + DecryptedBallotsWithMeritsList persisted2 = decryptedBallotsWithMeritsStorage.initAndGetPersisted(decryptedBallotsWithMeritsList, 100); + if (persisted2 != null) { + decryptedBallotsWithMeritsList.clear(); + decryptedBallotsWithMeritsList.addAll(persisted2.getList()); + } + } } /////////////////////////////////////////////////////////////////////////////////////////// - // API + // DaoSetupService /////////////////////////////////////////////////////////////////////////////////////////// - public Set getAllAcceptedEvaluatedProposals() { - return getAcceptedEvaluatedProposals(allEvaluatedProposals); + @Override + public void addListeners() { + bsqStateService.addBsqStateListener(this); } - public Set getAllRejectedEvaluatedProposals() { - return getRejectedEvaluatedProposals(allEvaluatedProposals); + @Override + public void start() { + maybeCalculateVoteResult(bsqStateService.getChainHeight()); } @@ -187,7 +212,10 @@ public void onParseBlockChainComplete() { private void maybeCalculateVoteResult(int chainHeight) { if (isInVoteResultPhase(chainHeight)) { Set decryptedBallotsWithMeritsSet = getDecryptedBallotsWithMeritsSet(chainHeight); - allDecryptedBallotsWithMerits.addAll(decryptedBallotsWithMeritsSet); + decryptedBallotsWithMeritsSet.stream() + .filter(e -> !decryptedBallotsWithMeritsList.getList().contains(e)) + .forEach(decryptedBallotsWithMeritsList::add); + persistDecryptedBallotsWithMerits(); if (!decryptedBallotsWithMeritsSet.isEmpty()) { // From the decryptedBallotsWithMerits we create a map with the hash of the blind vote list as key and the @@ -215,20 +243,29 @@ private void maybeCalculateVoteResult(int chainHeight) { Set acceptedEvaluatedProposals = getAcceptedEvaluatedProposals(evaluatedProposals); applyAcceptedProposals(acceptedEvaluatedProposals, chainHeight); - allEvaluatedProposals.addAll(evaluatedProposals); + evaluatedProposals.stream() + .filter(e -> !evaluatedProposalList.getList().contains(e)) + .forEach(evaluatedProposalList::add); + persistEvaluatedProposals(); log.info("processAllVoteResults completed"); } else { log.warn("Our list of received blind votes do not match the list from the majority of voters."); // TODO request missing blind votes } - } catch (VoteResultException e) { + } catch (VoteResultException.ValidationException e) { + log.error(e.toString()); + e.printStackTrace(); + voteResultExceptions.add(e); + } catch (VoteResultException.ConsensusException e) { log.error(e.toString()); e.printStackTrace(); //TODO notify application of that case (e.g. add error handler) // The vote cycle is invalid as conflicting data views of the blind vote data exist and the winner // did not reach super majority of 80%. + + voteResultExceptions.add(e); } } else { log.info("There have not been any votes in that cycle. chainHeight={}", chainHeight); @@ -274,28 +311,44 @@ private Set getDecryptedBallotsWithMeritsSet(int cha .findAny(); if (optionalBlindVote.isPresent()) { BlindVote blindVote = optionalBlindVote.get(); - VoteWithProposalTxIdList voteWithProposalTxIdList = VoteResultConsensus.decryptVotes(blindVote.getEncryptedVotes(), secretKey); - MeritList meritList = MeritConsensus.decryptMeritList(blindVote.getEncryptedMeritList(), secretKey); - - // We lookup for the proposals we have in our local list which match the txId from the - // voteWithProposalTxIdList and create a ballot list with the proposal and the vote from - // the voteWithProposalTxIdList - BallotList ballotList = createBallotList(voteWithProposalTxIdList); - return new DecryptedBallotsWithMerits(hashOfBlindVoteList, voteRevealTxId, blindVoteTxId, blindVoteStake, ballotList, meritList); + try { + VoteWithProposalTxIdList voteWithProposalTxIdList = VoteResultConsensus.decryptVotes(blindVote.getEncryptedVotes(), secretKey); + MeritList meritList = MeritConsensus.decryptMeritList(blindVote.getEncryptedMeritList(), secretKey); + // We lookup for the proposals we have in our local list which match the txId from the + // voteWithProposalTxIdList and create a ballot list with the proposal and the vote from + // the voteWithProposalTxIdList + BallotList ballotList = createBallotList(voteWithProposalTxIdList); + return new DecryptedBallotsWithMerits(hashOfBlindVoteList, voteRevealTxId, blindVoteTxId, blindVoteStake, ballotList, meritList); + } catch (VoteResultException.MissingBallotException missingBallotException) { + //TODO handle case that we are missing proposals + log.warn("We are missing proposals to create the vote result: " + missingBallotException.toString()); + missingDataRequestService.addVoteResultException(missingBallotException); + voteResultExceptions.add(missingBallotException); + return null; + } catch (VoteResultException.DecryptionException decryptionException) { + log.error("Could not decrypt data: " + decryptionException.toString()); + voteResultExceptions.add(decryptionException); + return null; + } } else { //TODO handle recovering log.warn("We have a blindVoteTx but we do not have the corresponding blindVote in our local list.\n" + "That can happen if the blindVote item was not properly broadcast. We will go on " + "and see if that blindVote was part of the majority data view. If so we should " + "recover the missing blind vote by a request to our peers. blindVoteTxId={}", blindVoteTxId); + + VoteResultException.MissingBlindVoteDataException voteResultException = new VoteResultException.MissingBlindVoteDataException(blindVoteTxId); + missingDataRequestService.addVoteResultException(voteResultException); + voteResultExceptions.add(voteResultException); return null; } - } catch (MissingBallotException e) { - //TODO handle case that we are missing proposals - log.error("We are missing proposals to create the vote result: " + e.toString()); + } catch (VoteResultException.ValidationException e) { + log.error("Could not create DecryptedBallotsWithMerits because of voteResultValidationException: " + e.toString()); + voteResultExceptions.add(e); return null; } catch (Throwable e) { - log.error("Could not create DecryptedBallotsWithMerits: " + e.toString()); + log.error("Could not create DecryptedBallotsWithMerits because of an unknown exception: " + e.toString()); + voteResultExceptions.add(new VoteResultException(e)); return null; } }) @@ -303,7 +356,8 @@ private Set getDecryptedBallotsWithMeritsSet(int cha .collect(Collectors.toSet()); } - private BallotList createBallotList(VoteWithProposalTxIdList voteWithProposalTxIdList) throws MissingBallotException { + 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) @@ -339,7 +393,7 @@ private BallotList createBallotList(VoteWithProposalTxIdList voteWithProposalTxI .collect(Collectors.toList()); if (!missingBallots.isEmpty()) - throw new MissingBallotException(ballots, missingBallots); + throw new VoteResultException.MissingBallotException(ballots, missingBallots); // 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)); @@ -366,7 +420,8 @@ private Map getStakeByHashOfBlindVoteListMap(Set return map; } - private byte[] getMajorityBlindVoteListHash(Map map) throws VoteResultException { + private byte[] getMajorityBlindVoteListHash(Map map) + throws VoteResultException.ValidationException, VoteResultException.ConsensusException { List list = map.entrySet().stream() .map(entry -> new HashWithStake(entry.getKey().bytes, entry.getValue())) .collect(Collectors.toList()); @@ -545,6 +600,7 @@ private void applyAcceptedProposals(Set acceptedEvaluatedProp applyParamChange(acceptedEvaluatedProposals, chainHeight); applyBondedRole(acceptedEvaluatedProposals, chainHeight); applyConfiscateBond(acceptedEvaluatedProposals, chainHeight); + applyRemoveAsset(acceptedEvaluatedProposals, chainHeight); } private void applyIssuance(Set acceptedEvaluatedProposals, int chainHeight) { @@ -601,7 +657,6 @@ private void applyAcceptedChangeParamProposal(ChangeParamProposal changeParamPro StringBuilder sb = new StringBuilder(); sb.append("\n################################################################################\n"); sb.append("We changed a parameter. ProposalTxId=").append(changeParamProposal.getTxId()) - .append("\nfor changeParamProposal with UID ").append(changeParamProposal.getTxId()) .append("\nParam: ").append(changeParamProposal.getParam().name()) .append(" new value: ").append(changeParamProposal.getParamValue()) .append("\n################################################################################\n"); @@ -627,7 +682,6 @@ private void applyBondedRole(Set acceptedEvaluatedProposals, StringBuilder sb = new StringBuilder(); sb.append("\n################################################################################\n"); sb.append("We added a bonded role. ProposalTxId=").append(bondedRoleProposal.getTxId()) - .append("\nfor bondedRoleProposal with UID ").append(bondedRoleProposal.getTxId()) .append("\nBondedRole: ").append(bondedRole.getDisplayString()) .append("\n################################################################################\n"); log.info(sb.toString()); @@ -643,8 +697,7 @@ private void applyConfiscateBond(Set acceptedEvaluatedProposa StringBuilder sb = new StringBuilder(); sb.append("\n################################################################################\n"); - sb.append("We confiscated bond. ProposalTxId=").append(confiscateBondProposal.getTxId()) - .append("\nfor confiscateBondProposal with UID ").append(confiscateBondProposal.getTxId()) + sb.append("We confiscated a bond. ProposalTxId=").append(confiscateBondProposal.getTxId()) .append("\nHashOfBondId: ").append(Utilities.encodeToHex(confiscateBondProposal.getHash())) .append("\n################################################################################\n"); log.info(sb.toString()); @@ -652,6 +705,23 @@ private void applyConfiscateBond(Set acceptedEvaluatedProposa }); } + private void applyRemoveAsset(Set acceptedEvaluatedProposals, int chainHeight) { + acceptedEvaluatedProposals.forEach(evaluatedProposal -> { + if (evaluatedProposal.getProposal() instanceof RemoveAssetProposal) { + RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) evaluatedProposal.getProposal(); + String tickerSymbol = removeAssetProposal.getTickerSymbol(); + assetService.addToRemovedAssetsListByVoting(tickerSymbol); + + StringBuilder sb = new StringBuilder(); + sb.append("\n################################################################################\n"); + sb.append("We removed an asset. ProposalTxId=").append(removeAssetProposal.getTxId()) + .append("\nAsset: ").append(CurrencyUtil.getNameByCode(tickerSymbol)) + .append("\n################################################################################\n"); + log.info(sb.toString()); + } + }); + } + private Set getAcceptedEvaluatedProposals(Set evaluatedProposals) { return evaluatedProposals.stream() .filter(EvaluatedProposal::isAccepted) @@ -668,6 +738,14 @@ private boolean isInVoteResultPhase(int chainHeight) { return periodService.getFirstBlockOfPhase(chainHeight, DaoPhase.Phase.RESULT) == chainHeight; } + private void persistEvaluatedProposals() { + evaluatedProposalStorage.queueUpForSave(20); + } + + private void persistDecryptedBallotsWithMerits() { + decryptedBallotsWithMeritsStorage.queueUpForSave(20); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Inner classes diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/issuance/IssuanceService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/issuance/IssuanceService.java index ae6bd0d8cfa..f25d29dd06b 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/issuance/IssuanceService.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/issuance/IssuanceService.java @@ -72,7 +72,7 @@ public void issueBsq(CompensationProposal compensationProposal, int chainHeight) StringBuilder sb = new StringBuilder(); sb.append("\n################################################################################\n"); sb.append("We issued new BSQ to tx with ID ").append(txOutput.getTxId()) - .append("\nfor compensationProposal with UID ").append(compensationProposal.getTxId()) + .append("\nIssued BSQ: ").append(compensationProposal.getRequestedBsq()) .append("\n################################################################################\n"); log.info(sb.toString()); } else { diff --git a/core/src/main/java/bisq/core/dao/node/full/FullNode.java b/core/src/main/java/bisq/core/dao/node/full/FullNode.java index db3e676eb0d..945b6930439 100644 --- a/core/src/main/java/bisq/core/dao/node/full/FullNode.java +++ b/core/src/main/java/bisq/core/dao/node/full/FullNode.java @@ -80,6 +80,8 @@ public FullNode(BlockParser blockParser, @Override public void start() { + fullNodeNetworkService.start(); + rpcService.setup(() -> { super.onInitialized(); startParseBlocks(); @@ -153,12 +155,12 @@ private void onNewBlock(Block block) { fullNodeNetworkService.publishNewBlock(block); } - private void parseBlocksIfNewBlockAvailable(int chainHeadHeight) { - rpcService.requestChainHeadHeight(newChainHeadHeight -> { - if (newChainHeadHeight > chainHeadHeight) { + private void parseBlocksIfNewBlockAvailable(int chainHeight) { + rpcService.requestChainHeadHeight(newChainHeight -> { + if (newChainHeight > chainHeight) { log.info("During parsing new blocks have arrived. We parse again with those missing blocks." + - "ChainHeadHeight={}, newChainHeadHeight={}", chainHeadHeight, newChainHeadHeight); - parseBlocksOnHeadHeight(chainHeadHeight + 1, newChainHeadHeight); + "ChainHeadHeight={}, newChainHeadHeight={}", chainHeight, newChainHeight); + parseBlocksOnHeadHeight(chainHeight + 1, newChainHeight); } else { log.info("parseBlocksIfNewBlockAvailable did not result in a new block, so we complete."); onParseBlockChainComplete(); @@ -169,23 +171,23 @@ private void parseBlocksIfNewBlockAvailable(int chainHeadHeight) { private void requestChainHeadHeightAndParseBlocks(int startBlockHeight) { log.info("requestChainHeadHeightAndParseBlocks with startBlockHeight={}", startBlockHeight); - rpcService.requestChainHeadHeight(chainHeadHeight -> parseBlocksOnHeadHeight(startBlockHeight, chainHeadHeight), + rpcService.requestChainHeadHeight(chainHeight -> parseBlocksOnHeadHeight(startBlockHeight, chainHeight), this::handleError); } - private void parseBlocksOnHeadHeight(int startBlockHeight, int chainHeadHeight) { - if (startBlockHeight <= chainHeadHeight) { - log.info("parseBlocks with startBlockHeight={} and chainHeadHeight={}", startBlockHeight, chainHeadHeight); + private void parseBlocksOnHeadHeight(int startBlockHeight, int chainHeight) { + if (startBlockHeight <= chainHeight) { + log.info("parseBlocks with startBlockHeight={} and chainHeight={}", startBlockHeight, chainHeight); parseBlocks(startBlockHeight, - chainHeadHeight, + chainHeight, this::onNewBlock, () -> { // We are done but it might be that new blocks have arrived in the meantime, - // so we try again with startBlockHeight set to current chainHeadHeight + // so we try again with startBlockHeight set to current chainHeight // We also set up the listener in the else main branch where we check // if we are at chainTip, so do not include here another check as it would // not trigger the listener registration. - parseBlocksIfNewBlockAvailable(chainHeadHeight); + parseBlocksIfNewBlockAvailable(chainHeight); }, throwable -> { if (throwable instanceof BlockNotConnectingException) { startReOrgFromLastSnapshot(); @@ -202,15 +204,15 @@ private void parseBlocksOnHeadHeight(int startBlockHeight, int chainHeadHeight) } private void parseBlocks(int startBlockHeight, - int chainHeadHeight, + int chainHeight, Consumer newBlockHandler, ResultHandler resultHandler, Consumer errorHandler) { - parseBlock(startBlockHeight, chainHeadHeight, newBlockHandler, resultHandler, errorHandler); + parseBlock(startBlockHeight, chainHeight, newBlockHandler, resultHandler, errorHandler); } // Recursively request and parse all blocks - private void parseBlock(int blockHeight, int chainHeadHeight, + private void parseBlock(int blockHeight, int chainHeight, Consumer newBlockHandler, ResultHandler resultHandler, Consumer errorHandler) { rpcService.requestBtcBlock(blockHeight, @@ -220,10 +222,10 @@ private void parseBlock(int blockHeight, int chainHeadHeight, Block block = blockParser.parseBlock(rawBlock); newBlockHandler.accept(block); - // Increment blockHeight and recursively call parseBlockAsync until we reach chainHeadHeight - if (blockHeight < chainHeadHeight) { + // Increment blockHeight and recursively call parseBlockAsync until we reach chainHeight + if (blockHeight < chainHeight) { final int newBlockHeight = blockHeight + 1; - parseBlock(newBlockHeight, chainHeadHeight, newBlockHandler, resultHandler, errorHandler); + parseBlock(newBlockHeight, chainHeight, newBlockHandler, resultHandler, errorHandler); } else { // We are done resultHandler.handleResult(); 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 f971c1fcae3..febb7d99565 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 @@ -188,9 +188,9 @@ public void blockDetected(com.neemre.btcdcli4j.core.domain.RawBlock rawBtcBlock) void requestChainHeadHeight(Consumer resultHandler, Consumer errorHandler) { ListenableFuture future = executor.submit(client::getBlockCount); - Futures.addCallback(future, new FutureCallback() { - public void onSuccess(Integer chainHeadHeight) { - UserThread.execute(() -> resultHandler.accept(chainHeadHeight)); + Futures.addCallback(future, new FutureCallback<>() { + public void onSuccess(Integer chainHeight) { + UserThread.execute(() -> resultHandler.accept(chainHeight)); } public void onFailure(@NotNull Throwable throwable) { diff --git a/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java b/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java index 4f19f5962c4..573d5145c92 100644 --- a/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java +++ b/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java @@ -17,12 +17,18 @@ package bisq.core.dao.node.full.network; +import bisq.core.dao.governance.blindvote.BlindVoteListService; +import bisq.core.dao.governance.blindvote.network.messages.RepublishGovernanceDataRequest; +import bisq.core.dao.governance.blindvote.storage.BlindVotePayload; +import bisq.core.dao.governance.proposal.ProposalService; +import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload; import bisq.core.dao.node.messages.GetBlocksRequest; import bisq.core.dao.node.messages.NewBlockBroadcastMessage; import bisq.core.dao.state.BsqStateService; import bisq.core.dao.state.blockchain.Block; import bisq.core.dao.state.blockchain.RawBlock; +import bisq.network.p2p.P2PService; import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.MessageListener; import bisq.network.p2p.network.NetworkNode; @@ -35,8 +41,12 @@ import javax.inject.Inject; +import javafx.collections.ObservableList; + import java.util.HashMap; import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @@ -58,6 +68,9 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List private final NetworkNode networkNode; private final PeerManager peerManager; private final Broadcaster broadcaster; + private final BlindVoteListService blindVoteListService; + private final ProposalService proposalService; + private final P2PService p2PService; private final BsqStateService bsqStateService; // Key is connection UID @@ -73,20 +86,28 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List public FullNodeNetworkService(NetworkNode networkNode, PeerManager peerManager, Broadcaster broadcaster, + BlindVoteListService blindVoteListService, + ProposalService proposalService, + P2PService p2PService, BsqStateService bsqStateService) { this.networkNode = networkNode; this.peerManager = peerManager; this.broadcaster = broadcaster; + this.blindVoteListService = blindVoteListService; + this.proposalService = proposalService; + this.p2PService = p2PService; this.bsqStateService = bsqStateService; - - networkNode.addMessageListener(this); - peerManager.addListener(this); } /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// + public void start() { + networkNode.addMessageListener(this); + peerManager.addListener(this); + } + @SuppressWarnings("Duplicates") public void shutDown() { Log.traceCall(); @@ -173,6 +194,39 @@ public void onFault(String errorMessage, @Nullable Connection connection) { } else { log.warn("We have stopped already. We ignore that onMessage call."); } + } else if (networkEnvelope instanceof RepublishGovernanceDataRequest) { + 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.info("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); + }); + + 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.info("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); + }); } } } diff --git a/core/src/main/java/bisq/core/dao/node/full/network/GetBlocksRequestHandler.java b/core/src/main/java/bisq/core/dao/node/full/network/GetBlocksRequestHandler.java index 74d984b7ceb..9791030f32b 100644 --- a/core/src/main/java/bisq/core/dao/node/full/network/GetBlocksRequestHandler.java +++ b/core/src/main/java/bisq/core/dao/node/full/network/GetBlocksRequestHandler.java @@ -99,7 +99,8 @@ public void onGetBlocksRequest(GetBlocksRequest getBlocksRequest, final Connecti connection.getPeersNodeAddressOptional(), getBlocksRequest.getFromBlockHeight()); if (timeoutTimer == null) { timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions - String errorMessage = "A timeout occurred for getBlocksResponse:" + getBlocksResponse + + String errorMessage = "A timeout occurred for getBlocksResponse.requestNonce:" + + getBlocksResponse.getRequestNonce() + " on connection:" + connection; handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection); }, @@ -111,8 +112,8 @@ public void onGetBlocksRequest(GetBlocksRequest getBlocksRequest, final Connecti @Override public void onSuccess(Connection connection) { if (!stopped) { - log.info("Send DataResponse to {} succeeded. getBlocksResponse={}", - connection.getPeersNodeAddressOptional(), getBlocksResponse); + log.info("Send DataResponse to {} succeeded. getBlocksResponse.getBlocks().size()={}", + connection.getPeersNodeAddressOptional(), getBlocksResponse.getBlocks().size()); cleanup(); listener.onComplete(); } else { diff --git a/core/src/main/java/bisq/core/dao/node/lite/LiteNode.java b/core/src/main/java/bisq/core/dao/node/lite/LiteNode.java index 35cb2eb6ff5..96e264c4ee8 100644 --- a/core/src/main/java/bisq/core/dao/node/lite/LiteNode.java +++ b/core/src/main/java/bisq/core/dao/node/lite/LiteNode.java @@ -73,7 +73,7 @@ public LiteNode(BlockParser blockParser, public void start() { super.onInitialized(); - liteNodeNetworkService.init(); + liteNodeNetworkService.start(); } @Override diff --git a/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java b/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java index e9e9a0dd35c..d9a20d66342 100644 --- a/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java +++ b/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java @@ -117,7 +117,7 @@ public LiteNodeNetworkService(NetworkNode networkNode, // API /////////////////////////////////////////////////////////////////////////////////////////// - public void init() { + public void start() { networkNode.addMessageListener(this); networkNode.addConnectionListener(this); peerManager.addListener(this); diff --git a/core/src/main/java/bisq/core/dao/state/SnapshotManager.java b/core/src/main/java/bisq/core/dao/state/SnapshotManager.java index 68d780ecb53..fee52cd4c24 100644 --- a/core/src/main/java/bisq/core/dao/state/SnapshotManager.java +++ b/core/src/main/java/bisq/core/dao/state/SnapshotManager.java @@ -76,22 +76,22 @@ public void onNewBlockHeight(int blockHeight) { @Override public void onParseTxsComplete(Block block) { - final int chainHeadHeight = block.getHeight(); - if (isSnapshotHeight(chainHeadHeight) && + int chainHeight = block.getHeight(); + if (isSnapshotHeight(chainHeight) && (snapshotCandidate == null || - snapshotCandidate.getChainHeight() != chainHeadHeight)) { + snapshotCandidate.getChainHeight() != chainHeight)) { // At trigger event we store the latest snapshotCandidate to disc if (snapshotCandidate != null) { // We clone because storage is in a threaded context final BsqState cloned = bsqState.getClone(snapshotCandidate); if (cloned.getBlocks().getLast().getHeight() >= genesisTxInfo.getGenesisBlockHeight()) storage.queueUpForSave(cloned); - log.info("Saved snapshotCandidate to Disc at height " + chainHeadHeight); + log.info("Saved snapshotCandidate to Disc at height " + chainHeight); } // Now we clone and keep it in memory for the next trigger snapshotCandidate = bsqState.getClone(); // don't access cloned anymore with methods as locks are transient! - log.info("Cloned new snapshotCandidate at height " + chainHeadHeight); + log.info("Cloned new snapshotCandidate at height " + chainHeight); } } @@ -108,7 +108,7 @@ public void applySnapshot() { checkNotNull(storage, "storage must not be null"); BsqState persisted = storage.initAndGetPersisted(bsqState, 100); if (persisted != null) { - log.info("applySnapshot persisted.chainHeadHeight=" + new LinkedList<>(persisted.getBlocks()).getLast().getHeight()); + log.info("applySnapshot persisted.chainHeight=" + new LinkedList<>(persisted.getBlocks()).getLast().getHeight()); if (persisted.getBlocks().getLast().getHeight() >= genesisTxInfo.getGenesisBlockHeight()) bsqStateService.applySnapshot(persisted); } else { diff --git a/core/src/main/java/bisq/core/dao/state/blockchain/TempTxOutput.java b/core/src/main/java/bisq/core/dao/state/blockchain/TempTxOutput.java index 16e4fb67984..2f8e8c7bc7f 100644 --- a/core/src/main/java/bisq/core/dao/state/blockchain/TempTxOutput.java +++ b/core/src/main/java/bisq/core/dao/state/blockchain/TempTxOutput.java @@ -43,8 +43,11 @@ public static TempTxOutput fromRawTxOutput(RawTxOutput txOutput) { } private TxOutputType txOutputType; + + // The lockTime is stored in the first output of the LOCKUP tx. // If not set it is -1, 0 is a valid value. private int lockTime; + // The unlockBlockHeight is stored in the first output of the UNLOCK tx. private int unlockBlockHeight; private TempTxOutput(int index, diff --git a/core/src/main/java/bisq/core/dao/state/blockchain/Tx.java b/core/src/main/java/bisq/core/dao/state/blockchain/Tx.java index b496b01b79f..d7917e04279 100644 --- a/core/src/main/java/bisq/core/dao/state/blockchain/Tx.java +++ b/core/src/main/java/bisq/core/dao/state/blockchain/Tx.java @@ -131,16 +131,12 @@ public TxOutput getLastTxOutput() { } - /** - * The lockTime is stored in the first output of the LOCKUP tx. - */ + // The lockTime is stored in the first output of the LOCKUP tx. public int getLockTime() { return txOutputs.get(0).getLockTime(); } - /** - * The unlockBlockHeight is stored in the first output of the LOCKUP tx. - */ + // The unlockBlockHeight is stored in the first output of the UNLOCK tx. public int getUnlockBlockHeight() { return txOutputs.get(0).getUnlockBlockHeight(); } diff --git a/core/src/main/java/bisq/core/dao/state/blockchain/TxOutput.java b/core/src/main/java/bisq/core/dao/state/blockchain/TxOutput.java index 4565be1eeb4..a38b66cfe1f 100644 --- a/core/src/main/java/bisq/core/dao/state/blockchain/TxOutput.java +++ b/core/src/main/java/bisq/core/dao/state/blockchain/TxOutput.java @@ -49,8 +49,11 @@ public static TxOutput fromTempOutput(TempTxOutput tempTxOutput) { } private final TxOutputType txOutputType; + + // The lockTime is stored in the first output of the LOCKUP tx. // If not set it is -1, 0 is a valid value. private final int lockTime; + // The unlockBlockHeight is stored in the first output of the UNLOCK tx. private final int unlockBlockHeight; public TxOutput(int index, diff --git a/core/src/main/java/bisq/core/dao/state/governance/Param.java b/core/src/main/java/bisq/core/dao/state/governance/Param.java index e0acbdf517a..10c1e316652 100644 --- a/core/src/main/java/bisq/core/dao/state/governance/Param.java +++ b/core/src/main/java/bisq/core/dao/state/governance/Param.java @@ -42,52 +42,79 @@ public enum Param { UNDEFINED(0), - // TODO trade fee is not implemented yet to be actually used. - // FeeService is setting the fee atm.... - // 0.2% 100 = 1%, 1 is 0.01% - BSQ_MAKER_FEE_IN_PERCENT(20), - BSQ_TAKER_FEE_IN_PERCENT(20), - BTC_MAKER_FEE_IN_PERCENT(20), - BTC_TAKER_FEE_IN_PERCENT(20), + // Fee in BSQ satoshi for a 1 BTC trade. 200 Satoshi = 2 BSQ = 0.02%. + // About 2 USD if 1 BSQ = 1 USD for a 1 BTC trade which is about 10% of the BTC fee., + // Might need adjustment if BSQ/BTC rate changes. + DEFAULT_MAKER_FEE_BSQ(200), // 0.02% + DEFAULT_TAKER_FEE_BSQ(200), + // 0.05 BSQ (5 satoshi) for a 1 BTC trade. 0.05 USD if 1 BSQ = 1 USD, 10 % of the BTC fee + MIN_MAKER_FEE_BSQ(5), // 0.0005%. + MIN_TAKER_FEE_BSQ(5), + + + // Fee in BTC satoshi for a 1 BTC trade. 200_000 Satoshi = 0.00200000 BTC = 0.2%. + // 20 USD at BTC price 10_000 USD for a 1 BTC trade; + DEFAULT_MAKER_FEE_BTC(200_000), + DEFAULT_TAKER_FEE_BTC(200_000), // 0.2% + MIN_MAKER_FEE_BTC(5_000), // 0.005%. + MIN_TAKER_FEE_BTC(5_000), // Fees proposal/voting. Atm we don't use diff. fees for diff. proposal types - PROPOSAL_FEE(100), // 5 BSQ TODO change low dev - BLIND_VOTE_FEE(200), // 10 BSQ TODO change low dev - - // Quorum for voting in BSQ stake - QUORUM_PROPOSAL(100), // 10 000 BSQ TODO change low dev value - QUORUM_COMP_REQUEST(100), // 10 000 BSQ TODO change low dev value - QUORUM_CHANGE_PARAM(300), // 100 000 BSQ TODO change low dev value - QUORUM_REMOVE_ASSET(400), // 10 000 BSQ TODO change low dev value - QUORUM_CONFISCATION(500), // 10 000 BSQ TODO change low dev value + // See: https://github.com/bisq-network/proposals/issues/46 + PROPOSAL_FEE(200), // 2 BSQ + BLIND_VOTE_FEE(200), // 2 BSQ + + // As BSQ based validation values can change over time if BSQ value rise we need to support that in the Params as well + COMPENSATION_REQUEST_MIN_AMOUNT(1_000), // 10 BSQ + COMPENSATION_REQUEST_MAX_AMOUNT(10_000_000), // 100 000 BSQ + + // Quorum required for voting result to be valid. + // Quorum is the min. amount of total BSQ (earned+stake) which was used for voting on a request. + // E.g. If only 2000 BSQ was used on a vote but 10 000 is required the result is invalid even if the voters voted + // 100% for acceptance. This should prevent that changes can be done with low stakeholder participation. + QUORUM_COMP_REQUEST(2_000_000), // 20 000 BSQ + QUORUM_CHANGE_PARAM(10_000_000), // 100 000 BSQ + QUORUM_ROLE(5_000_000), // 50 000 BSQ + QUORUM_CONFISCATION(20_000_000), // 200 000 BSQ + QUORUM_GENERIC(500_000), // 5 000 BSQ + QUORUM_REMOVE_ASSET(1_000_000), // 10 000 BSQ // Threshold for voting in % with precision of 2 (e.g. 5000 -> 50.00%) - THRESHOLD_PROPOSAL(5_000), // 50% + // This is the required amount of weighted vote result needed for acceptance of the result. + // E.g. If the result ends up in 65% weighted vote for acceptance and threshold was 50% it is accepted. + // The result must be larger than the threshold. A 50% vote result for a threshold with 50% is not sufficient, + // it requires min. 50.01%. THRESHOLD_COMP_REQUEST(5_000), // 50% - THRESHOLD_CHANGE_PARAM(7_500), // 75% -> that might change the THRESHOLD_CHANGE_PARAM and QUORUM_CHANGE_PARAM! + THRESHOLD_CHANGE_PARAM(7_500), // 75% That might change the THRESHOLD_CHANGE_PARAM and QUORUM_CHANGE_PARAM as well. So we have to be careful here! + THRESHOLD_ROLE(5_000), // 50% + THRESHOLD_CONFISCATION(8_500), // 85% Confiscation is considered an exceptional case and need very high consensus among the stakeholders. + THRESHOLD_GENERIC(5_000), // 50% THRESHOLD_REMOVE_ASSET(5_000), // 50% - THRESHOLD_CONFISCATION(8_500), // 85% - // Period phase (16 blocks atm) + //TODO add asset listing params (nr. of trades, volume, time, fee which defines listing state) + + // TODO for dev testing we use short periods... + // Period phase (11 blocks atm) + PHASE_UNDEFINED(0), + PHASE_PROPOSAL(2), + PHASE_BREAK1(1), + PHASE_BLIND_VOTE(2), + PHASE_BREAK2(1), + PHASE_VOTE_REVEAL(2), + PHASE_BREAK3(1), + PHASE_RESULT(2); + + // See: https://github.com/bisq-network/proposals/issues/46 + /* PHASE_UNDEFINED(0), - PHASE_PROPOSAL(2), // 24 days - PHASE_BREAK1(1), // 10 blocks - PHASE_BLIND_VOTE(2), // 4 days - PHASE_BREAK2(1), // 10 blocks - PHASE_VOTE_REVEAL(2), // 2 days - PHASE_BREAK3(1), // 10 blocks - PHASE_RESULT(2), // 1 block - PHASE_BREAK4(1); // 10 blocks - - /*PHASE_UNDEFINED(0), - PHASE_PROPOSAL(3456), // 24 days - PHASE_BREAK1(10), // 10 blocks - PHASE_BLIND_VOTE(576), // 4 days + PHASE_PROPOSAL(3600), // 24 days + PHASE_BREAK1(150), // 1 day + PHASE_BLIND_VOTE(600), // 4 days PHASE_BREAK2(10), // 10 blocks - PHASE_VOTE_REVEAL(432), // 2 days + PHASE_VOTE_REVEAL(300), // 2 days PHASE_BREAK3(10), // 10 blocks - PHASE_RESULT(1), // 1 block - PHASE_BREAK4(10); // 10 blocks*/ + PHASE_RESULT(10); // 10 block + */ @Getter private long defaultValue; diff --git a/core/src/main/java/bisq/core/dao/state/period/PeriodService.java b/core/src/main/java/bisq/core/dao/state/period/PeriodService.java index 780241993fc..2ac09506e92 100644 --- a/core/src/main/java/bisq/core/dao/state/period/PeriodService.java +++ b/core/src/main/java/bisq/core/dao/state/period/PeriodService.java @@ -129,9 +129,9 @@ public int getDurationForPhase(DaoPhase.Phase phase, int height) { .orElse(0); } - public boolean isTxInPastCycle(String txId, int chainHeadHeight) { + public boolean isTxInPastCycle(String txId, int chainHeight) { return getOptionalTx(txId) - .filter(tx -> isTxInPastCycle(tx.getBlockHeight(), chainHeadHeight)) + .filter(tx -> isTxInPastCycle(tx.getBlockHeight(), chainHeight)) .isPresent(); } diff --git a/core/src/main/java/bisq/core/locale/CurrencyUtil.java b/core/src/main/java/bisq/core/locale/CurrencyUtil.java index 55148efc858..d9468d634fc 100644 --- a/core/src/main/java/bisq/core/locale/CurrencyUtil.java +++ b/core/src/main/java/bisq/core/locale/CurrencyUtil.java @@ -19,6 +19,7 @@ import bisq.core.app.BisqEnvironment; import bisq.core.btc.BaseCurrencyNetwork; +import bisq.core.dao.governance.asset.AssetService; import bisq.common.app.DevEnv; @@ -33,6 +34,7 @@ import java.util.Set; import java.util.stream.Collectors; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkArgument; @@ -52,6 +54,7 @@ public static void setup() { setBaseCurrencyCode(BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()); } + @Getter private static final AssetRegistry assetRegistry = new AssetRegistry(); private static String baseCurrencyCode = "BTC"; @@ -348,7 +351,6 @@ public static FiatCurrency getCurrencyByCountryCode(String countryCode) { return new FiatCurrency(currency.getCurrencyCode()); } - public static String getNameByCode(String currencyCode) { if (isCryptoCurrency(currencyCode)) return getCryptoCurrency(currencyCode).get().getName(); @@ -361,6 +363,12 @@ public static String getNameByCode(String currencyCode) { } } + public static Optional findCryptoCurrencyByName(String currencyName) { + return getAllSortedCryptoCurrencies().stream() + .filter(e -> e.getName().equals(currencyName)) + .findAny(); + } + public static String getNameAndCode(String currencyCode) { return getNameByCode(currencyCode) + " (" + currencyCode + ")"; } @@ -437,4 +445,17 @@ public static Optional findAsset(AssetRegistry assetRegistry, String curr // If we are in mainnet we need have a mainet asset defined. throw new IllegalArgumentException("We are on mainnet and we could not find an asset with network type mainnet"); } + + public static Optional findAsset(String tickerSymbol, BaseCurrencyNetwork baseCurrencyNetwork) { + return assetRegistry.stream() + .filter(asset -> asset.getTickerSymbol().equals(tickerSymbol)) + .filter(asset -> assetMatchesNetwork(asset, baseCurrencyNetwork)) + .findAny(); + } + + public static List getWhiteListedSortedCryptoCurrencies(AssetService assetService) { + return getAllSortedCryptoCurrencies().stream() + .filter(e -> !assetService.isAssetRemoved(e.getCode())) + .collect(Collectors.toList()); + } } diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index 39ea1414e59..bc87e11f4af 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -89,13 +89,14 @@ public static Coin getMakerFee(BsqWalletService bsqWalletService, Preferences pr @Nullable public static Coin getMakerFee(boolean isCurrencyForMakerFeeBtc, @Nullable Coin amount, boolean marketPriceAvailable, double marketPriceMargin) { if (amount != null) { - final Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getMakerFeePerBtc(isCurrencyForMakerFeeBtc), amount); + Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getMakerFeePerBtc(isCurrencyForMakerFeeBtc), amount); double makerFeeAsDouble = (double) feePerBtc.value; if (marketPriceAvailable) { if (marketPriceMargin > 0) makerFeeAsDouble = makerFeeAsDouble * Math.sqrt(marketPriceMargin * 100); else makerFeeAsDouble = 0; + // For BTC we round so min value change is 100 satoshi if (isCurrencyForMakerFeeBtc) makerFeeAsDouble = MathUtils.roundDouble(makerFeeAsDouble / 100, 0) * 100; diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java index dc56715e2bf..9a119c57c14 100644 --- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java @@ -26,6 +26,7 @@ import bisq.core.arbitration.messages.OpenNewDisputeMessage; import bisq.core.arbitration.messages.PeerOpenedDisputeMessage; import bisq.core.arbitration.messages.PeerPublishedDisputePayoutTxMessage; +import bisq.core.dao.governance.blindvote.network.messages.RepublishGovernanceDataRequest; import bisq.core.dao.governance.proposal.storage.temp.TempProposalPayload; import bisq.core.dao.node.messages.GetBlocksRequest; import bisq.core.dao.node.messages.GetBlocksResponse; @@ -157,6 +158,9 @@ public NetworkEnvelope fromProto(PB.NetworkEnvelope proto) throws ProtobufferExc return AddPersistableNetworkPayloadMessage.fromProto(proto.getAddPersistableNetworkPayloadMessage(), this, messageVersion); case ACK_MESSAGE: return AckMessage.fromProto(proto.getAckMessage(), messageVersion); + case REPUBLISH_GOVERNANCE_DATA_REQUEST: + return RepublishGovernanceDataRequest.fromProto(proto.getRepublishGovernanceDataRequest(), messageVersion); + default: throw new ProtobufferException("Unknown proto message case (PB.NetworkEnvelope). messageCase=" + proto.getMessageCase() + "; proto raw data=" + proto.toString()); diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java index 7d1f32b1df0..ddecb704b41 100644 --- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java @@ -20,6 +20,7 @@ import bisq.core.arbitration.DisputeList; import bisq.core.btc.model.AddressEntryList; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.dao.governance.asset.RemovedAssetsList; import bisq.core.dao.governance.ballot.BallotList; import bisq.core.dao.governance.blindvote.MyBlindVoteList; import bisq.core.dao.governance.blindvote.storage.BlindVoteStore; @@ -29,6 +30,8 @@ import bisq.core.dao.governance.proposal.storage.appendonly.ProposalStore; import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore; import bisq.core.dao.governance.role.BondedRoleList; +import bisq.core.dao.governance.voteresult.DecryptedBallotsWithMeritsList; +import bisq.core.dao.governance.voteresult.EvaluatedProposalList; import bisq.core.dao.state.BsqState; import bisq.core.payment.AccountAgeWitnessStore; import bisq.core.payment.PaymentAccountList; @@ -132,6 +135,12 @@ public PersistableEnvelope fromProto(PB.PersistableEnvelope proto) { return MeritList.fromProto(proto.getMeritList()); case BONDED_ROLE_LIST: return BondedRoleList.fromProto(proto.getBondedRoleList()); + case REMOVED_ASSET_LIST: + return RemovedAssetsList.fromProto(proto.getRemovedAssetList()); + case EVALUATED_PROPOSAL_LIST: + return EvaluatedProposalList.fromProto(proto.getEvaluatedProposalList()); + case DECRYPTED_BALLOTS_WITH_MERITS_LIST: + return DecryptedBallotsWithMeritsList.fromProto(proto.getDecryptedBallotsWithMeritsList()); default: throw new ProtobufferRuntimeException("Unknown proto message case(PB.PersistableEnvelope). " + "messageCase=" + proto.getMessageCase() + "; proto raw data=" + proto.toString()); diff --git a/core/src/main/java/bisq/core/provider/fee/FeeService.java b/core/src/main/java/bisq/core/provider/fee/FeeService.java index 9ffc31fdac2..f6a878541a3 100644 --- a/core/src/main/java/bisq/core/provider/fee/FeeService.java +++ b/core/src/main/java/bisq/core/provider/fee/FeeService.java @@ -18,13 +18,15 @@ package bisq.core.provider.fee; import bisq.core.app.BisqEnvironment; +import bisq.core.dao.state.BsqStateService; +import bisq.core.dao.state.governance.Param; +import bisq.core.dao.state.period.PeriodService; import bisq.common.UserThread; import bisq.common.handlers.FaultHandler; import bisq.common.util.Tuple2; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import com.google.inject.Inject; @@ -53,40 +55,53 @@ @Slf4j public class FeeService { - // fixed min fee - public static final Coin BTC_REFERENCE_DEFAULT_MIN_TX_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; // 5000 - // https://litecoin.info/Transaction_fees min fee is 100_000 - public static final Coin LTC_REFERENCE_DEFAULT_MIN_TX_FEE = Coin.valueOf(100_000); - //TODO check - // min tx fee per tx is 10000 now, 1000 in sept 2017 - public static final Coin DASH_REFERENCE_DEFAULT_MIN_TX_FEE = Coin.valueOf(10_000); + /////////////////////////////////////////////////////////////////////////////////////////// + // Static + /////////////////////////////////////////////////////////////////////////////////////////// + + // Miner fees are between 1-600 sat/byte. We try to stay on the safe side. BTC_DEFAULT_TX_FEE is only used if our + // fee service would not deliver data. + private static final long BTC_DEFAULT_TX_FEE = 50; + private static final long MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN = 2; + private static BsqStateService bsqStateService; + private static PeriodService periodService; + + private static long getFeeFromParam(Param parm) { + return bsqStateService != null && periodService != null ? bsqStateService.getParamValue(parm, periodService.getChainHeight()) : 0; + } + + public static Coin getMakerFeePerBtc(boolean currencyForFeeIsBtc) { + long fee = currencyForFeeIsBtc ? getFeeFromParam(Param.DEFAULT_MAKER_FEE_BTC) : getFeeFromParam(Param.DEFAULT_MAKER_FEE_BSQ); + return Coin.valueOf(fee); + } + + public static Coin getMinMakerFee(boolean currencyForFeeIsBtc) { + long fee = currencyForFeeIsBtc ? getFeeFromParam(Param.MIN_MAKER_FEE_BTC) : getFeeFromParam(Param.MIN_MAKER_FEE_BSQ); + return Coin.valueOf(fee); + } - // DEFAULT_TX_FEE used in FeeRequestService for non-BTC currencies and for BTC only if we cannot access fee service - // fees are per byte - public static final long BTC_DEFAULT_TX_FEE = 200; // fees are between 20-600 sat/byte. We try to stay on the safe side. - public static final long LTC_DEFAULT_TX_FEE = LTC_REFERENCE_DEFAULT_MIN_TX_FEE.value / 200; - public static final long DASH_DEFAULT_TX_FEE = DASH_REFERENCE_DEFAULT_MIN_TX_FEE.value / 200; // 200 bytes tx -> 200*50=10000 + public static Coin getTakerFeePerBtc(boolean currencyForFeeIsBtc) { + long fee = currencyForFeeIsBtc ? getFeeFromParam(Param.DEFAULT_TAKER_FEE_BTC) : getFeeFromParam(Param.DEFAULT_TAKER_FEE_BSQ); + return Coin.valueOf(fee); + } - private static long MIN_MAKER_FEE_IN_BASE_CUR; - private static long MIN_TAKER_FEE_IN_BASE_CUR; - private static long DEFAULT_MAKER_FEE_IN_BASE_CUR; - private static long DEFAULT_TAKER_FEE_IN_BASE_CUR; + public static Coin getMinTakerFee(boolean currencyForFeeIsBtc) { + long fee = currencyForFeeIsBtc ? getFeeFromParam(Param.MIN_TAKER_FEE_BTC) : getFeeFromParam(Param.MIN_TAKER_FEE_BSQ); + return Coin.valueOf(fee); + } - private static final long MIN_MAKER_FEE_IN_BSQ = 5; - private static final long MIN_TAKER_FEE_IN_BSQ = 5; - private static final long DEFAULT_MAKER_FEE_IN_BSQ = 200; // about 2 USD at 1 BSQ = 1 USD for a 1 BTC trade - private static final long DEFAULT_TAKER_FEE_IN_BSQ = 200; - public static final long MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN = 2; + /////////////////////////////////////////////////////////////////////////////////////////// + // Class fields + /////////////////////////////////////////////////////////////////////////////////////////// private final FeeProvider feeProvider; - private final String baseCurrencyCode; - private long txFeePerByte; + private final IntegerProperty feeUpdateCounter = new SimpleIntegerProperty(0); + private long txFeePerByte = BTC_DEFAULT_TX_FEE; private Map timeStampMap; - private long epochInSecondAtLastRequest; private long lastRequest; - private IntegerProperty feeUpdateCounter = new SimpleIntegerProperty(0); private long minFeePerByte; + private long epochInSecondAtLastRequest; /////////////////////////////////////////////////////////////////////////////////////////// @@ -94,50 +109,33 @@ public class FeeService { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public FeeService(FeeProvider feeProvider) { + public FeeService(FeeProvider feeProvider, BsqStateService bsqStateService, PeriodService periodService) { this.feeProvider = feeProvider; - baseCurrencyCode = BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode(); - - /* How to calculate: - MIN_MAKER_FEE_IN_BASE_CUR = target fiat price * 100000000 / price (in btc: 0.5*100000000/2500) - DEFAULT_MAKER_FEE_IN_BASE_CUR = target fiat price * (100000000 / price) / maxTradeAmount - (in btc: 5*100000000/2500 / 1) - (in ltc: 5*100000000/40 / 50) - */ - switch (baseCurrencyCode) { - case "BTC": - MIN_MAKER_FEE_IN_BASE_CUR = 5_000; // 0.5 USD at BTC price 10000 USD - MIN_TAKER_FEE_IN_BASE_CUR = 5_000; - DEFAULT_MAKER_FEE_IN_BASE_CUR = 200_000; // 20 USD at BTC price 10000 USD for a 1 BTC trade - DEFAULT_TAKER_FEE_IN_BASE_CUR = 200_000; - txFeePerByte = BTC_DEFAULT_TX_FEE; - break; - case "LTC": - MIN_MAKER_FEE_IN_BASE_CUR = 1_200_000; // 0.5 USD at LTC price 40 USD - MIN_TAKER_FEE_IN_BASE_CUR = 1_200_000; - DEFAULT_MAKER_FEE_IN_BASE_CUR = 240_000; // 5 USD at LTC price 40 USD for 50 LTC (maxTradeAmount) - DEFAULT_TAKER_FEE_IN_BASE_CUR = 360_000; // 7.5 USD at LTC price 40 USD - txFeePerByte = LTC_DEFAULT_TX_FEE; - break; - case "DASH": - MIN_MAKER_FEE_IN_BASE_CUR = 300_000; // 0.5 USD at DASH price 150 USD - MIN_TAKER_FEE_IN_BASE_CUR = 300_000; - DEFAULT_MAKER_FEE_IN_BASE_CUR = 160_000; // 5 USD at DASH price 150 USD - DEFAULT_TAKER_FEE_IN_BASE_CUR = 240_000; // 7.5 USD at DASH price 150 USD for 20 DASH (maxTradeAmount) - txFeePerByte = DASH_DEFAULT_TX_FEE; - break; - default: - throw new RuntimeException("baseCurrencyCode not defined. baseCurrencyCode=" + baseCurrencyCode); - } + FeeService.bsqStateService = bsqStateService; + FeeService.periodService = periodService; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + public void onAllServicesInitialized() { minFeePerByte = BisqEnvironment.getBaseCurrencyNetwork().getDefaultMinFeePerByte(); - requestFees(null, null); + requestFees(); // We update all 5 min. - UserThread.runPeriodically(() -> requestFees(null, null), 5, TimeUnit.MINUTES); + UserThread.runPeriodically(this::requestFees, 5, TimeUnit.MINUTES); + } + + + public void requestFees() { + requestFees(null, null); + } + + public void requestFees(Runnable resultHandler) { + requestFees(resultHandler, null); } public void requestFees(@Nullable Runnable resultHandler, @Nullable FaultHandler faultHandler) { @@ -155,7 +153,7 @@ public void onSuccess(@Nullable Tuple2, Map> res timeStampMap = result.first; epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs"); final Map map = result.second; - txFeePerByte = map.get(baseCurrencyCode); + txFeePerByte = map.get("BTC"); if (txFeePerByte < minFeePerByte) { log.warn("The delivered fee per byte is smaller than the min. default fee of 5 sat/byte"); @@ -163,7 +161,7 @@ public void onSuccess(@Nullable Tuple2, Map> res } feeUpdateCounter.set(feeUpdateCounter.get() + 1); - log.info("{} tx fee: txFeePerByte={}", baseCurrencyCode, txFeePerByte); + log.info("BTC tx fee: txFeePerByte={}", txFeePerByte); if (resultHandler != null) resultHandler.run(); }); @@ -193,22 +191,6 @@ public Coin getTxFeePerByte() { return Coin.valueOf(txFeePerByte); } - public static Coin getMakerFeePerBtc(boolean currencyForMakerFeeBtc) { - return currencyForMakerFeeBtc ? Coin.valueOf(DEFAULT_MAKER_FEE_IN_BASE_CUR) : Coin.valueOf(DEFAULT_MAKER_FEE_IN_BSQ); - } - - public static Coin getMinMakerFee(boolean currencyForMakerFeeBtc) { - return currencyForMakerFeeBtc ? Coin.valueOf(MIN_MAKER_FEE_IN_BASE_CUR) : Coin.valueOf(MIN_MAKER_FEE_IN_BSQ); - } - - public static Coin getTakerFeePerBtc(boolean currencyForTakerFeeBtc) { - return currencyForTakerFeeBtc ? Coin.valueOf(DEFAULT_TAKER_FEE_IN_BASE_CUR) : Coin.valueOf(DEFAULT_TAKER_FEE_IN_BSQ); - } - - public static Coin getMinTakerFee(boolean currencyForTakerFeeBtc) { - return currencyForTakerFeeBtc ? Coin.valueOf(MIN_TAKER_FEE_IN_BASE_CUR) : Coin.valueOf(MIN_TAKER_FEE_IN_BSQ); - } - public ReadOnlyIntegerProperty feeUpdateCounterProperty() { return feeUpdateCounter; } diff --git a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java index ea7bfb46ba5..2b396f67605 100644 --- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java +++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java @@ -20,11 +20,13 @@ import bisq.core.arbitration.DisputeManager; import bisq.core.btc.model.AddressEntryList; import bisq.core.dao.DaoOptionKeys; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; import bisq.core.dao.governance.myvote.MyVoteListService; import bisq.core.dao.governance.proposal.MyProposalListService; import bisq.core.dao.governance.role.BondedRolesService; +import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.offer.OpenOfferManager; import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; @@ -67,6 +69,8 @@ public static List getPersistedDataHosts(Injector injector) { persistedDataHosts.add(injector.getInstance(MyVoteListService.class)); persistedDataHosts.add(injector.getInstance(MyProposalListService.class)); persistedDataHosts.add(injector.getInstance(BondedRolesService.class)); + persistedDataHosts.add(injector.getInstance(AssetService.class)); + persistedDataHosts.add(injector.getInstance(VoteResultService.class)); } return persistedDataHosts; } diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java index 7be153f351d..b47cb5dad4f 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java @@ -18,6 +18,8 @@ package bisq.core.trade.statistics; import bisq.core.app.AppOptionKeys; +import bisq.core.dao.governance.asset.AssetService; +import bisq.core.locale.CryptoCurrency; import bisq.core.locale.CurrencyTuple; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; @@ -32,14 +34,19 @@ import bisq.common.UserThread; import bisq.common.storage.JsonFileManager; import bisq.common.storage.Storage; +import bisq.common.util.Tuple2; import bisq.common.util.Utilities; +import org.bitcoinj.core.Coin; + import com.google.inject.Inject; import com.google.inject.name.Named; import javafx.collections.FXCollections; import javafx.collections.ObservableSet; +import java.time.Duration; + import java.io.File; import java.util.ArrayList; @@ -59,29 +66,11 @@ @Slf4j public class TradeStatisticsManager { - static TradeStatistics2 ConvertToTradeStatistics2(TradeStatistics tradeStatistics) { - return new TradeStatistics2(tradeStatistics.getDirection(), - tradeStatistics.getBaseCurrency(), - tradeStatistics.getCounterCurrency(), - tradeStatistics.getOfferPaymentMethod(), - tradeStatistics.getOfferDate(), - tradeStatistics.isOfferUseMarketBasedPrice(), - tradeStatistics.getOfferMarketPriceMargin(), - tradeStatistics.getOfferAmount(), - tradeStatistics.getOfferMinAmount(), - tradeStatistics.getOfferId(), - tradeStatistics.getTradePrice().getValue(), - tradeStatistics.getTradeAmount().getValue(), - tradeStatistics.getTradeDate().getTime(), - tradeStatistics.getDepositTxId(), - null, - tradeStatistics.getExtraDataMap()); - } - private final JsonFileManager jsonFileManager; private final P2PService p2PService; private final PriceFeedService priceFeedService; private final ReferralIdService referralIdService; + private final AssetService assetService; private final boolean dumpStatistics; private final ObservableSet observableTradeStatisticsSet = FXCollections.observableSet(); @@ -91,11 +80,13 @@ public TradeStatisticsManager(P2PService p2PService, TradeStatistics2StorageService tradeStatistics2StorageService, AppendOnlyDataStoreService appendOnlyDataStoreService, ReferralIdService referralIdService, + AssetService assetService, @Named(Storage.STORAGE_DIR) File storageDir, @Named(AppOptionKeys.DUMP_STATISTICS) boolean dumpStatistics) { this.p2PService = p2PService; this.priceFeedService = priceFeedService; this.referralIdService = referralIdService; + this.assetService = assetService; this.dumpStatistics = dumpStatistics; jsonFileManager = new JsonFileManager(storageDir); @@ -130,8 +121,7 @@ public void onAllServicesInitialized() { priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet); dump(); - // print all currencies sorted by nr. of trades - // printAllCurrencyStats(); + checkTradeActivity(); } public void publishTradeStatistics(List trades) { @@ -208,151 +198,102 @@ private void dump() { } } - // To have automatic check and removal we would need new fields in the asset class for the date when it was - // added/released and a property if it was removed due either getting blocked by the DAO stakehodlers in voting or - // removed due lack of activity. - // For now we use the old script below to print the usage of the coins. - private void printAllCurrencyStats() { - Map> map1 = new HashMap<>(); - for (TradeStatistics2 tradeStatistics : observableTradeStatisticsSet) { - if (CurrencyUtil.isFiatCurrency(tradeStatistics.getCounterCurrency())) { - String counterCurrency = CurrencyUtil.getNameAndCode(tradeStatistics.getCounterCurrency()); - if (!map1.containsKey(counterCurrency)) - map1.put(counterCurrency, new HashSet<>()); - - map1.get(counterCurrency).add(tradeStatistics); - } - } + private void checkTradeActivity() { + Date compareDate = new Date(new Date().getTime() - Duration.ofDays(120).toMillis()); + long minTradeAmount = Coin.parseCoin("0.01").value; + long minNumOfTrades = 3; - StringBuilder sb1 = new StringBuilder("\nAll traded Fiat currencies:\n"); - map1.entrySet().stream() - .sorted((o1, o2) -> Integer.compare(o2.getValue().size(), o1.getValue().size())) - .forEach(e -> sb1.append(e.getKey()).append(": ").append(e.getValue().size()).append("\n")); - log.error(sb1.toString()); + Map> tradeStatMap = new HashMap<>(); + observableTradeStatisticsSet.stream() + .filter(e -> CurrencyUtil.isCryptoCurrency(e.getBaseCurrency())) + .filter(e -> e.getTradeDate().getTime() > compareDate.getTime()) + .forEach(e -> { + tradeStatMap.putIfAbsent(e.getBaseCurrency(), new Tuple2<>(0L, 0)); + Tuple2 tuple2 = tradeStatMap.get(e.getBaseCurrency()); + long accumulatedTradeAmount = tuple2.first + e.getTradeAmount().getValue(); + int numTrades = tuple2.second + 1; + tradeStatMap.put(e.getBaseCurrency(), new Tuple2<>(accumulatedTradeAmount, numTrades)); + }); + StringBuilder sufficientlyTraded = new StringBuilder("\nSufficiently traded assets:"); + StringBuilder insufficientlyTraded = new StringBuilder("\nInsufficiently traded assets:"); + StringBuilder notTraded = new StringBuilder("\nNot traded assets:"); + List whiteListedSortedCryptoCurrencies = CurrencyUtil.getWhiteListedSortedCryptoCurrencies(assetService); + Set assetsToRemove = new HashSet<>(whiteListedSortedCryptoCurrencies); + whiteListedSortedCryptoCurrencies.forEach(e -> { + String code = e.getCode(); + if (!isWarmingUp(code) && !hasPaidBSQFee(code)) { + String nameAndCode = CurrencyUtil.getNameAndCode(code); + if (tradeStatMap.containsKey(code)) { + Tuple2 tuple = tradeStatMap.get(code); + Long tradeAmount = tuple.first; + Integer numTrades = tuple.second; + if (tradeAmount >= minTradeAmount || numTrades >= minNumOfTrades) { + assetsToRemove.remove(e); + sufficientlyTraded.append("\n") + .append(nameAndCode) + .append(": Trade amount: ") + .append(Coin.valueOf(tradeAmount).toFriendlyString()) + .append(", number of trades: ") + .append(numTrades); + } else { + insufficientlyTraded.append("\n") + .append(nameAndCode) + .append(": Trade amount: ") + .append(Coin.valueOf(tradeAmount).toFriendlyString()) + .append(", number of trades: ") + .append(numTrades); + } + } else { + assetsToRemove.remove(e); + notTraded.append("\n").append(nameAndCode); + } + } + }); - Map> map2 = new HashMap<>(); - for (TradeStatistics2 tradeStatistics : observableTradeStatisticsSet) { - if (CurrencyUtil.isCryptoCurrency(tradeStatistics.getBaseCurrency())) { - final String code = CurrencyUtil.getNameAndCode(tradeStatistics.getBaseCurrency()); - if (!map2.containsKey(code)) - map2.put(code, new HashSet<>()); + log.debug(sufficientlyTraded.toString()); + log.debug(insufficientlyTraded.toString()); + log.debug(notTraded.toString()); + } - map2.get(code).add(tradeStatistics); - } - } + private boolean hasPaidBSQFee(String code) { + return assetService.hasPaidBSQFee(code); + } - List allCryptoCurrencies = new ArrayList<>(); - Set coinsWithValidator = new HashSet<>(); - - // List of coins with validator before 0.6.0 hard requirements for address validator - coinsWithValidator.add("BTC"); - coinsWithValidator.add("BSQ"); - coinsWithValidator.add("LTC"); - coinsWithValidator.add("DOGE"); - coinsWithValidator.add("DASH"); - coinsWithValidator.add("ETH"); - coinsWithValidator.add("PIVX"); - coinsWithValidator.add("IOP"); - coinsWithValidator.add("888"); - coinsWithValidator.add("ZEC"); - coinsWithValidator.add("GBYTE"); - coinsWithValidator.add("NXT"); - - // All those need to have a address validator + private boolean isWarmingUp(String code) { Set newlyAdded = new HashSet<>(); - // v0.6.0 - newlyAdded.add("DCT"); - newlyAdded.add("PNC"); - newlyAdded.add("WAC"); - newlyAdded.add("ZEN"); - newlyAdded.add("ELLA"); - newlyAdded.add("XCN"); - newlyAdded.add("TRC"); - newlyAdded.add("INXT"); - newlyAdded.add("PART"); - // v0.6.1 - newlyAdded.add("MAD"); - newlyAdded.add("BCH"); - newlyAdded.add("BCHC"); - newlyAdded.add("BTG"); - // v0.6.2 - newlyAdded.add("CAGE"); - newlyAdded.add("CRED"); - newlyAdded.add("XSPEC"); - // v0.6.3 - newlyAdded.add("WILD"); - newlyAdded.add("ONION"); - // v0.6.4 - newlyAdded.add("CREA"); - newlyAdded.add("XIN"); - // v0.6.5 - newlyAdded.add("BETR"); - newlyAdded.add("MVT"); - newlyAdded.add("REF"); - // v0.6.6 - newlyAdded.add("STL"); - newlyAdded.add("DAI"); - newlyAdded.add("YTN"); - newlyAdded.add("DARX"); - newlyAdded.add("ODN"); - newlyAdded.add("CDT"); - newlyAdded.add("DGM"); - newlyAdded.add("SCS"); - newlyAdded.add("SOS"); - newlyAdded.add("ACH"); - newlyAdded.add("VDN"); - // v0.7.0 - newlyAdded.add("ALC"); - newlyAdded.add("DIN"); - newlyAdded.add("NAH"); - newlyAdded.add("ROI"); - newlyAdded.add("WMCC"); - newlyAdded.add("RTO"); - newlyAdded.add("KOTO"); - newlyAdded.add("PHR"); - newlyAdded.add("UBQ"); - newlyAdded.add("QWARK"); - newlyAdded.add("GEO"); - newlyAdded.add("GRANS"); - newlyAdded.add("ICH"); - - // TODO add remaining coins since 0.7.0 - //newlyAdded.clear(); - /* new AssetRegistry().stream() - .sorted(Comparator.comparing(o -> o.getName().toLowerCase())) - .filter(e -> !e.getTickerSymbol().equals("BSQ")) // BSQ is not out yet... - .filter(e -> !e.getTickerSymbol().equals("BTC")) - .map(e -> e.getTickerSymbol()) // We want to get rid of duplicated entries for regtest/testnet... - .distinct() - .forEach(e -> newlyAdded.add(e));*/ - - coinsWithValidator.addAll(newlyAdded); - - CurrencyUtil.getAllSortedCryptoCurrencies() - .forEach(e -> allCryptoCurrencies.add(e.getNameAndCode())); - StringBuilder sb2 = new StringBuilder("\nAll traded Crypto currencies:\n"); - StringBuilder sb3 = new StringBuilder("\nNever traded Crypto currencies:\n"); - map2.entrySet().stream() - .sorted((o1, o2) -> Integer.compare(o2.getValue().size(), o1.getValue().size())) - .forEach(e -> { - String key = e.getKey(); - sb2.append(key).append(": ").append(e.getValue().size()).append("\n"); - // key is: USD Tether (USDT) - String code = key.substring(key.indexOf("(") + 1, key.length() - 1); - if (!coinsWithValidator.contains(code) && !newlyAdded.contains(code)) - allCryptoCurrencies.remove(key); - }); - log.error(sb2.toString()); - // Not considered age of newly added coins, so take care with removal if coin was added recently. - allCryptoCurrencies.sort(String::compareTo); - allCryptoCurrencies - .forEach(e -> { - // key is: USD Tether (USDT) - String code = e.substring(e.indexOf("(") + 1, e.length() - 1); - if (!coinsWithValidator.contains(code) && !newlyAdded.contains(code)) - sb3.append(e).append("\n"); - }); - log.error(sb3.toString()); + // v0.7.1 Jul 4 2018 + newlyAdded.add("ZOC"); + newlyAdded.add("AQUA"); + newlyAdded.add("BTDX"); + newlyAdded.add("BTCC"); + newlyAdded.add("BTI"); + newlyAdded.add("CRDS"); + newlyAdded.add("CNMC"); + newlyAdded.add("TARI"); + newlyAdded.add("DAC"); + newlyAdded.add("DRIP"); + newlyAdded.add("FTO"); + newlyAdded.add("GRFT"); + newlyAdded.add("LIKE"); + newlyAdded.add("LOBS"); + newlyAdded.add("MAX"); + newlyAdded.add("MEC"); + newlyAdded.add("MCC"); + newlyAdded.add("XMN"); + newlyAdded.add("XMY"); + newlyAdded.add("NANO"); + newlyAdded.add("NPW"); + newlyAdded.add("NIM"); + newlyAdded.add("PIX"); + newlyAdded.add("PXL"); + newlyAdded.add("PRIV"); + newlyAdded.add("TRIT"); + newlyAdded.add("WAVI"); + + // v0.8.0 Aug 22 2018 + // none added + + return newlyAdded.contains(code); } } diff --git a/core/src/main/java/bisq/core/util/BsqFormatter.java b/core/src/main/java/bisq/core/util/BsqFormatter.java index 6ad5fc4a70f..fa33b346101 100644 --- a/core/src/main/java/bisq/core/util/BsqFormatter.java +++ b/core/src/main/java/bisq/core/util/BsqFormatter.java @@ -18,6 +18,8 @@ package bisq.core.util; import bisq.core.app.BisqEnvironment; +import bisq.core.dao.state.governance.Param; +import bisq.core.locale.Res; import bisq.core.provider.price.MarketPrice; import bisq.common.app.DevEnv; @@ -114,4 +116,105 @@ public Coin parseSatoshiToBtc(String satoshi) { return Coin.ZERO; } } + + + public String formatParamValue(Param param, long value) { + switch (param) { + case UNDEFINED: + return Res.get("shared.na"); + + case DEFAULT_MAKER_FEE_BSQ: + case DEFAULT_TAKER_FEE_BSQ: + case DEFAULT_MAKER_FEE_BTC: + case DEFAULT_TAKER_FEE_BTC: + return formatToPercentWithSymbol(value / 10000d); + + case PROPOSAL_FEE: + case BLIND_VOTE_FEE: + case COMPENSATION_REQUEST_MIN_AMOUNT: + case COMPENSATION_REQUEST_MAX_AMOUNT: + return formatCoinWithCode(Coin.valueOf(value)); + + case QUORUM_COMP_REQUEST: + case QUORUM_CHANGE_PARAM: + case QUORUM_ROLE: + case QUORUM_CONFISCATION: + case QUORUM_GENERIC: + case QUORUM_REMOVE_ASSET: + return formatCoinWithCode(Coin.valueOf(value)); + + case THRESHOLD_COMP_REQUEST: + case THRESHOLD_CHANGE_PARAM: + case THRESHOLD_ROLE: + case THRESHOLD_CONFISCATION: + case THRESHOLD_GENERIC: + case THRESHOLD_REMOVE_ASSET: + return formatToPercentWithSymbol(value / 10000d); + + case PHASE_UNDEFINED: + return Res.get("shared.na"); + case PHASE_PROPOSAL: + case PHASE_BREAK1: + case PHASE_BLIND_VOTE: + case PHASE_BREAK2: + case PHASE_VOTE_REVEAL: + case PHASE_BREAK3: + case PHASE_RESULT: + return Res.get("dao.param.blocks", value); + + default: + return Res.get("shared.na"); + } + } + + public long parseParamValue(Param param, String inputValue) { + switch (param) { + case UNDEFINED: + return 0; + + case DEFAULT_MAKER_FEE_BSQ: + case DEFAULT_TAKER_FEE_BSQ: + case DEFAULT_MAKER_FEE_BTC: + case DEFAULT_TAKER_FEE_BTC: + return (long) (parsePercentStringToDouble(inputValue) * 10000); + + case PROPOSAL_FEE: + case BLIND_VOTE_FEE: + case COMPENSATION_REQUEST_MIN_AMOUNT: + case COMPENSATION_REQUEST_MAX_AMOUNT: + return parseToCoin(inputValue).value; + + + case QUORUM_COMP_REQUEST: + case QUORUM_CHANGE_PARAM: + case QUORUM_ROLE: + case QUORUM_CONFISCATION: + case QUORUM_GENERIC: + case QUORUM_REMOVE_ASSET: + return parseToCoin(inputValue).value; + + + case THRESHOLD_COMP_REQUEST: + case THRESHOLD_CHANGE_PARAM: + case THRESHOLD_ROLE: + case THRESHOLD_CONFISCATION: + case THRESHOLD_GENERIC: + case THRESHOLD_REMOVE_ASSET: + return (long) (parsePercentStringToDouble(inputValue) * 10000); + + case PHASE_UNDEFINED: + return 0; + case PHASE_PROPOSAL: + case PHASE_BREAK1: + case PHASE_BLIND_VOTE: + case PHASE_BREAK2: + case PHASE_VOTE_REVEAL: + case PHASE_BREAK3: + case PHASE_RESULT: + return Long.valueOf(inputValue); + + default: + return 0; + } + } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4bd74618faa..0b7810c1c79 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1190,42 +1190,66 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Undefined + +# suppress inspection "UnusedProperty" +dao.param.DEFAULT_MAKER_FEE_BSQ=BSQ maker fee +# suppress inspection "UnusedProperty" +dao.param.DEFAULT_TAKER_FEE_BSQ=BSQ taker fee +# suppress inspection "UnusedProperty" +dao.param.MIN_MAKER_FEE_BSQ=Min. BSQ maker fee # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.MIN_TAKER_FEE_BSQ=Min. BSQ taker fee # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BTC=BTC maker fee # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=BTC taker fee # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +# suppress inspection "UnusedProperty" +dao.param.MIN_MAKER_FEE_BTC=Min. BTC maker fee +# suppress inspection "UnusedProperty" +dao.param.MIN_TAKER_FEE_BTC=Min. BTC taker fee # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" -dao.param.PROPOSAL_FEE=Proposal fee +dao.param.PROPOSAL_FEE=Proposal fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BLIND_VOTE_FEE=Voting fee +dao.param.BLIND_VOTE_FEE=Voting fee in BSQ # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.COMPENSATION_REQUEST_MIN_AMOUNT=Compensation request min. BSQ amount +# suppress inspection "UnusedProperty" +dao.param.COMPENSATION_REQUEST_MAX_AMOUNT=Compensation request max. BSQ amount + # suppress inspection "UnusedProperty" -dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request +dao.param.QUORUM_GENERIC=Required quorum in BSQ for generic proposal # suppress inspection "UnusedProperty" -dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter +dao.param.QUORUM_COMP_REQUEST=Required quorum in BSQ for compensation request # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_CHANGE_PARAM=Required quorum in BSQ for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation +dao.param.QUORUM_REMOVE_ASSET=Required quorum in BSQ for removing an asset +# suppress inspection "UnusedProperty" +dao.param.QUORUM_CONFISCATION=Required quorum in BSQ for bond confiscation +# suppress inspection "UnusedProperty" +dao.param.QUORUM_ROLE=Required quorum in BSQ for taking a bonded role + + # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold in % for generic proposal # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request +dao.param.THRESHOLD_COMP_REQUEST=Required threshold in % for compensation request # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter +dao.param.THRESHOLD_CHANGE_PARAM=Required threshold in % for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold in % for removing an asset # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation +dao.param.THRESHOLD_CONFISCATION=Required threshold in % for bond confiscation +# suppress inspection "UnusedProperty" +dao.param.THRESHOLD_ROLE=Required threshold in % for taking a bonded role + +dao.param.currentValue=Current value: {0} +dao.param.blocks={0} blocks # suppress inspection "UnusedProperty" dao.results.cycle.duration.label=Duration of {0} @@ -1252,8 +1276,6 @@ dao.phase.PHASE_VOTE_REVEAL=Vote reveal phase dao.phase.PHASE_BREAK3=Break 3 # suppress inspection "UnusedProperty" dao.phase.PHASE_RESULT=Result phase -# suppress inspection "UnusedProperty" -dao.phase.PHASE_BREAK4=Break 4 dao.results.votes.table.header.stakeAndMerit=Vote weight dao.results.votes.table.header.stake=Stake @@ -1352,7 +1374,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Compensation request # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Proposal for removing an altcoin +dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Proposal for changing a parameter # suppress inspection "UnusedProperty" @@ -1365,7 +1387,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Compensation request # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" @@ -1405,6 +1427,7 @@ dao.proposal.display.proposalFee=Proposal fee: dao.proposal.display.myVote=My vote: dao.proposal.display.voteResult=Vote result summary: dao.proposal.display.bondedRoleComboBox.label=Choose bonded role type +dao.proposal.display.tickerSymbol.label=Ticker Symbol dao.proposal.table.header.proposalType=Proposal type dao.proposal.table.header.link=Link @@ -1422,6 +1445,7 @@ dao.proposal.display.paramComboBox.label=Choose parameter dao.proposal.display.paramValue=Parameter value: dao.proposal.display.confiscateBondComboBox.label=Choose bond +dao.proposal.display.assetComboBox.label=Asset to remove dao.blindVote=blind vote @@ -2342,3 +2366,5 @@ validation.iban.invalidLength=Number must have length 15 to 34 chars. validation.interacETransfer.invalidAreaCode=Non-Canadian area code validation.interacETransfer.invalidPhone=Invalid phone number format and not an email address validation.inputTooLarge=Input must not be larger than {0} +validation.inputTooSmall=Input has to be larger than {0} +validation.amountBelowDust=The amount below the dust limit of {0} is not allowed. diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties index 3b1f6c31eb7..b4badf1de4c 100644 --- a/core/src/main/resources/i18n/displayStrings_de.properties +++ b/core/src/main/resources/i18n/displayStrings_de.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Wahlergebnisse für gewählten Vorsch # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Undefiniert # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Erstellergebühr in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Erstellergebühr in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Abnehmergebühr in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Abnehmergebühr in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Erstellergebühr in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Erstellergebühr in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Abnehmergebühr in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Abnehmergebühr in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,7 +1065,7 @@ dao.param.PROPOSAL_FEE=Vorschlag-Gebühr dao.param.BLIND_VOTE_FEE=Stimm-Gebühr # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Benötigtes Quorum für Vorschlag +dao.param.QUORUM_GENERIC=Benötigtes Quorum für Vorschlag # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Benötigtes Quorum für Entlohnungsanfrage # suppress inspection "UnusedProperty" @@ -1076,7 +1076,7 @@ dao.param.QUORUM_REMOVE_ASSET=Benötigtes Quorum um einen Altcoin zu entfernen dao.param.QUORUM_CONFISCATION=Benötigtes Quorum für Kopplung Konfiszierung # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Benötigter Schwellwert für Vorschlag +dao.param.THRESHOLD_GENERIC=Benötigter Schwellwert für Vorschlag # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Benötigter Schwellwert für Entlohnungsanfrage # suppress inspection "UnusedProperty" @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Entlohnungsanfrage # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Vorschlag für Gekoppelte Rolle # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Vorschlag einen Altcoin zu entfernen +dao.proposal.type.REMOVE_ASSET=Vorschlag einen Altcoin zu entfernen # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Vorschlag einen Parameter zu ändern # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Entlohnungsanfrage # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Gekoppelte Rolle # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Einen Altcoin entfernen +dao.proposal.type.short.REMOVE_ASSET=Einen Altcoin entfernen # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Einen Parameter ändern # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_el.properties b/core/src/main/resources/i18n/displayStrings_el.properties index b310930a16a..9d5626fb291 100644 --- a/core/src/main/resources/i18n/displayStrings_el.properties +++ b/core/src/main/resources/i18n/displayStrings_el.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Ακαθόριστο # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Αίτημα αποζημίωσης # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Πρόταση για απόσυρση εναλλακτικού νομίσματος +dao.proposal.type.REMOVE_ASSET=Πρόταση για απόσυρση εναλλακτικού νομίσματος # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Πρόταση για αλλαγή παραμέτρου # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Αίτημα αποζημίωσης # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties index 01f34044775..bda4746b0c8 100644 --- a/core/src/main/resources/i18n/displayStrings_es.properties +++ b/core/src/main/resources/i18n/displayStrings_es.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Indefinido # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Petición de compensación # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Propuesta para eliminar una altcoin +dao.proposal.type.REMOVE_ASSET=Propuesta para eliminar una altcoin # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Propuesta para cambiar un parámetro # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Petición de compensación # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties index 2845d0f7b0f..37addf4dbf9 100644 --- a/core/src/main/resources/i18n/displayStrings_fa.properties +++ b/core/src/main/resources/i18n/displayStrings_fa.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=تعریف نشده # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=درخواست خسارت # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=پیشنهاد برای حذف یک آلت کوین +dao.proposal.type.REMOVE_ASSET=پیشنهاد برای حذف یک آلت کوین # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=پیشنهاد برای تغییر یک پارامتر # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=درخواست خسارت # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties index 1e6bc9c1e57..e9133d17079 100644 --- a/core/src/main/resources/i18n/displayStrings_fr.properties +++ b/core/src/main/resources/i18n/displayStrings_fr.properties @@ -975,7 +975,7 @@ dao.phase.short.BREAK4=Break dao.proposal.type.COMPENSATION_REQUEST=Requête de compensation dao.proposal.type.GENERIC=Generic proposal dao.proposal.type.CHANGE_PARAM=Proposal for changing a parameter -dao.proposal.type.REMOVE_ALTCOIN=Proposal for removing an altcoin +dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset dao.proposal.details=Proposal details dao.proposal.selectedProposal=Selected proposal dao.proposal.active.header=Active proposals diff --git a/core/src/main/resources/i18n/displayStrings_hu.properties b/core/src/main/resources/i18n/displayStrings_hu.properties index 5b5c5930ac5..c1fea77564b 100644 --- a/core/src/main/resources/i18n/displayStrings_hu.properties +++ b/core/src/main/resources/i18n/displayStrings_hu.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Határozatlan # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Kártérítési kérelem # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Proposal for removing an altcoin +dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Proposal for changing a parameter # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Kártérítési kérelem # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties index e9926a8b11f..661d5f6eb92 100644 --- a/core/src/main/resources/i18n/displayStrings_pt.properties +++ b/core/src/main/resources/i18n/displayStrings_pt.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Indefinido # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Pedido de compensação # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Proposal for removing an altcoin +dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Proposal for changing a parameter # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Pedido de compensação # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_ro.properties b/core/src/main/resources/i18n/displayStrings_ro.properties index a9975beb23d..9fb902dbd71 100644 --- a/core/src/main/resources/i18n/displayStrings_ro.properties +++ b/core/src/main/resources/i18n/displayStrings_ro.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Nedefinit # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Solicitare de despăgubire # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Proposal for removing an altcoin +dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Proposal for changing a parameter # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Solicitare de despăgubire # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties index f28a7a2a4e7..39d3b14dfc1 100644 --- a/core/src/main/resources/i18n/displayStrings_ru.properties +++ b/core/src/main/resources/i18n/displayStrings_ru.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Результаты голосова # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Неопределено # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Взнос BSQ создателя +dao.param.DEFAULT_MAKER_FEE_BSQ=Взнос BSQ создателя # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Взнос BSQ получателя +dao.param.DEFAULT_TAKER_FEE_BSQ=Взнос BSQ получателя # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Взнос BТС создателя +dao.param.DEFAULT_MAKER_FEE_BTC=Взнос BТС создателя # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Взнос BТС получателя +dao.param.DEFAULT_TAKER_FEE_BTC=Взнос BТС получателя # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,7 +1065,7 @@ dao.param.PROPOSAL_FEE=Сбор за предложение dao.param.BLIND_VOTE_FEE=Сбор за голосование # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Необходимый кворум для предложения +dao.param.QUORUM_GENERIC=Необходимый кворум для предложения # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Необходимый кворум для запроса компенсации # suppress inspection "UnusedProperty" @@ -1076,7 +1076,7 @@ dao.param.QUORUM_REMOVE_ASSET=Необходимый кворум для уда dao.param.QUORUM_CONFISCATION=Необходимый кворум для конфискации гарантийного депозита # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Необходимый порог для предложения +dao.param.THRESHOLD_GENERIC=Необходимый порог для предложения # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Необходимый порог для запроса компенсации # suppress inspection "UnusedProperty" @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Запрос компенсации # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Предложение учредить обеспеченную роль # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Предложение удалить алткойн +dao.proposal.type.REMOVE_ASSET=Предложение удалить алткойн # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Предложение по изменению параметра # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Запрос компенсации # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Обеспеченная роль # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Удаление алткойна +dao.proposal.type.short.REMOVE_ASSET=Удаление алткойна # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Изменение параметра # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_sr.properties b/core/src/main/resources/i18n/displayStrings_sr.properties index c7412d6a3b4..7ad7712fe7a 100644 --- a/core/src/main/resources/i18n/displayStrings_sr.properties +++ b/core/src/main/resources/i18n/displayStrings_sr.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Undefined # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Zahtev za nadoknadu # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Proposal for removing an altcoin +dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Proposal for changing a parameter # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Zahtev za nadoknadu # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties index 9094f2c6867..02dd86bc1bd 100644 --- a/core/src/main/resources/i18n/displayStrings_th.properties +++ b/core/src/main/resources/i18n/displayStrings_th.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=ไม่ได้กำหนด # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=คำขอชดเชย # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=ข้อเสนอสำหรับการลบ altcoin +dao.proposal.type.REMOVE_ASSET=ข้อเสนอสำหรับการลบ altcoin # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=ข้อเสนอสำหรับการเปลี่ยนข้อจำกัด # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=คำขอชดเชย # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties index 29be79c8b38..8d2b88e2b14 100644 --- a/core/src/main/resources/i18n/displayStrings_vi.properties +++ b/core/src/main/resources/i18n/displayStrings_vi.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Không xác định # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=Yêu cầu bồi thường # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Đề xuất gỡ bỏ một altcoin +dao.proposal.type.REMOVE_ASSET=Đề xuất gỡ bỏ một altcoin # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=Đề xuất thay đổi một thông số # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=Yêu cầu bồi thường # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/core/src/main/resources/i18n/displayStrings_zh.properties b/core/src/main/resources/i18n/displayStrings_zh.properties index bce3e6284e4..e005e37bb24 100644 --- a/core/src/main/resources/i18n/displayStrings_zh.properties +++ b/core/src/main/resources/i18n/displayStrings_zh.properties @@ -1050,13 +1050,13 @@ dao.results.proposals.voting.detail.header=Vote results for selected proposal # suppress inspection "UnusedProperty" dao.param.UNDEFINED=Undefined # suppress inspection "UnusedProperty" -dao.param.BSQ_MAKER_FEE_IN_PERCENT=Maker fee in BSQ +dao.param.DEFAULT_MAKER_FEE_BSQ=Maker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BSQ_TAKER_FEE_IN_PERCENT=Taker fee in BSQ +dao.param.DEFAULT_TAKER_FEE_BSQ=Taker fee in BSQ # suppress inspection "UnusedProperty" -dao.param.BTC_MAKER_FEE_IN_PERCENT=Maker fee in BTC +dao.param.DEFAULT_MAKER_FEE_BTC=Maker fee in BTC # suppress inspection "UnusedProperty" -dao.param.BTC_TAKER_FEE_IN_PERCENT=Taker fee in BTC +dao.param.DEFAULT_TAKER_FEE_BTC=Taker fee in BTC # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1065,24 +1065,24 @@ dao.param.PROPOSAL_FEE=Proposal fee dao.param.BLIND_VOTE_FEE=Voting fee # suppress inspection "UnusedProperty" -dao.param.QUORUM_PROPOSAL=Required quorum for proposal +dao.param.QUORUM_GENERIC=Required quorum for proposal # suppress inspection "UnusedProperty" dao.param.QUORUM_COMP_REQUEST=Required quorum for compensation request # suppress inspection "UnusedProperty" dao.param.QUORUM_CHANGE_PARAM=Required quorum for changing a parameter # suppress inspection "UnusedProperty" -dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an altcoin +dao.param.QUORUM_REMOVE_ASSET=Required quorum for removing an asset # suppress inspection "UnusedProperty" dao.param.QUORUM_CONFISCATION=Required quorum for bond confiscation # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_PROPOSAL=Required threshold for proposal +dao.param.THRESHOLD_GENERIC=Required threshold for proposal # suppress inspection "UnusedProperty" dao.param.THRESHOLD_COMP_REQUEST=Required threshold for compensation request # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CHANGE_PARAM=Required threshold for changing a parameter # suppress inspection "UnusedProperty" -dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an altcoin +dao.param.THRESHOLD_REMOVE_ASSET=Required threshold for removing an asset # suppress inspection "UnusedProperty" dao.param.THRESHOLD_CONFISCATION=Required threshold for bond confiscation @@ -1211,7 +1211,7 @@ dao.proposal.type.COMPENSATION_REQUEST=赔偿要求 # suppress inspection "UnusedProperty" dao.proposal.type.BONDED_ROLE=Proposal for a bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.REMOVE_ALTCOIN=Proposal for removing an altcoin +dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset # suppress inspection "UnusedProperty" dao.proposal.type.CHANGE_PARAM=修改参数的提议 # suppress inspection "UnusedProperty" @@ -1224,7 +1224,7 @@ dao.proposal.type.short.COMPENSATION_REQUEST=赔偿要求 # suppress inspection "UnusedProperty" dao.proposal.type.short.BONDED_ROLE=Bonded role # suppress inspection "UnusedProperty" -dao.proposal.type.short.REMOVE_ALTCOIN=Removing an altcoin +dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin # suppress inspection "UnusedProperty" dao.proposal.type.short.CHANGE_PARAM=Changing a parameter # suppress inspection "UnusedProperty" diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java index b8c7ccdfea4..4d397b22104 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java @@ -21,6 +21,7 @@ import bisq.desktop.util.FormBuilder; import bisq.desktop.util.Layout; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; @@ -48,18 +49,15 @@ import java.util.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import static bisq.desktop.util.FormBuilder.addLabelInputTextField; import static bisq.desktop.util.FormBuilder.addLabelTextField; import static bisq.desktop.util.FormBuilder.addLabelTextFieldWithCopyIcon; public class CryptoCurrencyForm extends PaymentMethodForm { - private static final Logger log = LoggerFactory.getLogger(CryptoCurrencyForm.class); - private final CryptoCurrencyAccount cryptoCurrencyAccount; private final AltCoinAddressValidator altCoinAddressValidator; + private final AssetService assetService; + private InputTextField addressInputTextField; private ComboBox currencyComboBox; @@ -80,10 +78,12 @@ public CryptoCurrencyForm(PaymentAccount paymentAccount, InputValidator inputValidator, GridPane gridPane, int gridRow, - BSFormatter formatter) { + BSFormatter formatter, + AssetService assetService) { super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter); this.cryptoCurrencyAccount = (CryptoCurrencyAccount) paymentAccount; this.altCoinAddressValidator = altCoinAddressValidator; + this.assetService = assetService; } @Override @@ -162,7 +162,7 @@ protected void addTradeCurrencyComboBox() { currencyComboBox = FormBuilder.addLabelSearchComboBox(gridPane, ++gridRow, Res.get("payment.altcoin"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; currencyComboBox.setPromptText(Res.get("payment.select.altcoin")); - currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllSortedCryptoCurrencies())); + currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getWhiteListedSortedCryptoCurrencies(assetService))); currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 15)); currencyComboBox.setConverter(new StringConverter() { @Override diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index 21cff615b22..3235fafa8cf 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -282,6 +282,9 @@ private void setupHandlers() { .onAction(() -> GUIUtil.reSyncSPVChain(walletsSetup, preferences)) .show(); }); + bisqSetup.setVoteResultExceptionHandler(voteResultException -> { + new Popup<>().error(voteResultException.toString()).show(); + }); bisqSetup.setChainFileLockedExceptionHandler(msg -> { new Popup<>().warning(msg) diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java index 8914e3fb3fd..62299584228 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java @@ -29,6 +29,7 @@ import bisq.desktop.util.ImageUtil; import bisq.desktop.util.Layout; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; @@ -76,6 +77,7 @@ public class AltCoinAccountsView extends ActivatableViewAndModeladdLabelComboBox(root, gridRow, Res.getWithCol("account.notifications.marketAlert.selectPaymentAccount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - paymentAccountsComboBox.setPromptText(Res.get("shared.select")); - paymentAccountsComboBox.setConverter(new StringConverter() { + paymentAccountsComboBox.setConverter(new StringConverter<>() { @Override public String toString(PaymentAccount paymentAccount) { return paymentAccount.getAccountName(); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/bonding/lockup/LockupView.java b/desktop/src/main/java/bisq/desktop/main/dao/bonding/lockup/LockupView.java index e0a97c2bbc5..4681ebeaeef 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/bonding/lockup/LockupView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/bonding/lockup/LockupView.java @@ -119,8 +119,7 @@ public void initialize() { timeInputTextField.setValidator(timeInputTextFieldValidator); lockupTypeComboBox = FormBuilder.addLabelComboBox(root, ++gridRow, Res.get("dao.bonding.lock.type")).second; - lockupTypeComboBox.setPromptText(Res.get("shared.select")); - lockupTypeComboBox.setConverter(new StringConverter() { + lockupTypeComboBox.setConverter(new StringConverter<>() { @Override public String toString(LockupType lockupType) { return lockupType.getDisplayString(); @@ -141,8 +140,7 @@ public LockupType fromString(String string) { lockupTypeComboBox.getSelectionModel().select(0); bondedRolesComboBox = FormBuilder.addLabelComboBox(root, ++gridRow, Res.get("dao.bonding.lock.bondedRoles")).second; - bondedRolesComboBox.setPromptText(Res.get("shared.select")); - bondedRolesComboBox.setConverter(new StringConverter() { + bondedRolesComboBox.setConverter(new StringConverter<>() { @Override public String toString(BondedRole bondedRole) { return bondedRole.getDisplayString(); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java index 50cb4b9095c..4bef53b56cf 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java @@ -26,16 +26,18 @@ import bisq.desktop.util.validation.BsqAddressValidator; import bisq.desktop.util.validation.BsqValidator; +import bisq.core.btc.BaseCurrencyNetwork; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.dao.DaoFacade; import bisq.core.dao.governance.ballot.Ballot; import bisq.core.dao.governance.ballot.vote.Vote; import bisq.core.dao.governance.proposal.Proposal; import bisq.core.dao.governance.proposal.ProposalType; -import bisq.core.dao.governance.proposal.compensation.CompensationConsensus; import bisq.core.dao.governance.proposal.compensation.CompensationProposal; import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal; +import bisq.core.dao.governance.proposal.generic.GenericProposal; import bisq.core.dao.governance.proposal.param.ChangeParamProposal; +import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal; import bisq.core.dao.governance.proposal.role.BondedRoleProposal; import bisq.core.dao.governance.role.BondedRole; import bisq.core.dao.governance.role.BondedRoleType; @@ -43,10 +45,10 @@ import bisq.core.dao.governance.voteresult.ProposalVoteResult; import bisq.core.dao.state.blockchain.Tx; import bisq.core.dao.state.governance.Param; +import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.util.BsqFormatter; import bisq.core.util.validation.InputValidator; -import bisq.core.util.validation.IntegerValidator; import bisq.common.util.Tuple2; @@ -84,6 +86,10 @@ import static bisq.desktop.util.FormBuilder.*; import static com.google.common.base.Preconditions.checkNotNull; + + +import bisq.asset.Asset; + @SuppressWarnings("ConstantConditions") @Slf4j public class ProposalDisplay { @@ -106,6 +112,8 @@ public class ProposalDisplay { public ComboBox confiscateBondComboBox; @Nullable public ComboBox bondedRoleTypeComboBox; + @Nullable + public ComboBox assetComboBox; @Getter private int gridRow; @@ -120,6 +128,7 @@ public class ProposalDisplay { private List comboBoxes = new ArrayList<>(); private final ChangeListener focusOutListener; private final ChangeListener inputListener; + private ChangeListener paramChangeListener; public ProposalDisplay(GridPane gridPane, BsqFormatter bsqFormatter, BsqWalletService bsqWalletService, DaoFacade daoFacade) { @@ -156,18 +165,20 @@ public void createAllFields(String title, int gridRowStartIndex, double top, Pro case COMPENSATION_REQUEST: titledGroupBgRowSpan += 1; break; - case BONDED_ROLE: - break; - case REMOVE_ALTCOIN: - break; case CHANGE_PARAM: titledGroupBgRowSpan += 1; break; - case GENERIC: + case BONDED_ROLE: break; case CONFISCATE_BOND: break; + case GENERIC: + titledGroupBgRowSpan -= 1; + break; + case REMOVE_ASSET: + break; } + // at isMakeProposalScreen we show fee but no uid and txID (+1) // otherwise we don't show fee but show uid and txID (+2) if (isMakeProposalScreen) @@ -204,7 +215,8 @@ public void createAllFields(String title, int gridRowStartIndex, double top, Pro requestedBsqTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.requestedBsq")).second; BsqValidator bsqValidator = new BsqValidator(bsqFormatter); - bsqValidator.setMinValue(CompensationConsensus.getMinCompensationRequestAmount()); + bsqValidator.setMinValue(daoFacade.getMinCompensationRequestAmount()); + bsqValidator.setMaxValue(daoFacade.getMaxCompensationRequestAmount()); checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null"); requestedBsqTextField.setValidator(bsqValidator); inputControls.add(requestedBsqTextField); @@ -217,38 +229,16 @@ public void createAllFields(String title, int gridRowStartIndex, double top, Pro bsqAddressTextField.setValidator(new BsqAddressValidator(bsqFormatter)); inputControls.add(bsqAddressTextField); break; - case BONDED_ROLE: - bondedRoleTypeComboBox = FormBuilder.addLabelComboBox(gridPane, ++gridRow, - Res.getWithCol("dao.proposal.display.bondedRoleComboBox.label")).second; - checkNotNull(bondedRoleTypeComboBox, "bondedRoleTypeComboBox must not be null"); - bondedRoleTypeComboBox.setPromptText(Res.get("shared.select")); - bondedRoleTypeComboBox.setItems(FXCollections.observableArrayList(BondedRoleType.values())); - bondedRoleTypeComboBox.setConverter(new StringConverter() { - @Override - public String toString(BondedRoleType bondedRoleType) { - return bondedRoleType != null ? bondedRoleType.getDisplayString() : ""; - } - - @Override - public BondedRoleType fromString(String string) { - return null; - } - }); - comboBoxes.add(bondedRoleTypeComboBox); - break; - case REMOVE_ALTCOIN: - break; case CHANGE_PARAM: checkNotNull(gridPane, "gridPane must not be null"); paramComboBox = FormBuilder.addLabelComboBox(gridPane, ++gridRow, Res.getWithCol("dao.proposal.display.paramComboBox.label")).second; checkNotNull(paramComboBox, "paramComboBox must not be null"); - paramComboBox.setPromptText(Res.get("shared.select")); List list = Arrays.stream(Param.values()) .filter(e -> e != Param.UNDEFINED && e != Param.PHASE_UNDEFINED) .collect(Collectors.toList()); paramComboBox.setItems(FXCollections.observableArrayList(list)); - paramComboBox.setConverter(new StringConverter() { + paramComboBox.setConverter(new StringConverter<>() { @Override public String toString(Param param) { return param != null ? param.getDisplayString() : ""; @@ -263,18 +253,45 @@ public Param fromString(String string) { paramValueTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.paramValue")).second; //noinspection ConstantConditions - paramValueTextField.setValidator(new IntegerValidator()); + + //TODO use custom param validator + paramValueTextField.setValidator(new InputValidator()); + inputControls.add(paramValueTextField); + + paramChangeListener = (observable, oldValue, newValue) -> { + if (newValue != null) { + paramValueTextField.clear(); + String currentValue = bsqFormatter.formatParamValue(newValue, daoFacade.getPramValue(newValue)); + paramValueTextField.setPromptText(Res.get("dao.param.currentValue", currentValue)); + } + }; + paramComboBox.getSelectionModel().selectedItemProperty().addListener(paramChangeListener); break; - case GENERIC: + case BONDED_ROLE: + bondedRoleTypeComboBox = FormBuilder.addLabelComboBox(gridPane, ++gridRow, + Res.getWithCol("dao.proposal.display.bondedRoleComboBox.label")).second; + checkNotNull(bondedRoleTypeComboBox, "bondedRoleTypeComboBox must not be null"); + bondedRoleTypeComboBox.setItems(FXCollections.observableArrayList(BondedRoleType.values())); + bondedRoleTypeComboBox.setConverter(new StringConverter<>() { + @Override + public String toString(BondedRoleType bondedRoleType) { + return bondedRoleType != null ? bondedRoleType.getDisplayString() : ""; + } + + @Override + public BondedRoleType fromString(String string) { + return null; + } + }); + comboBoxes.add(bondedRoleTypeComboBox); break; case CONFISCATE_BOND: confiscateBondComboBox = FormBuilder.addLabelComboBox(gridPane, ++gridRow, Res.getWithCol("dao.proposal.display.confiscateBondComboBox.label")).second; checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null"); - confiscateBondComboBox.setPromptText(Res.get("shared.select")); confiscateBondComboBox.setItems(FXCollections.observableArrayList(daoFacade.getValidBondedRoleList())); - confiscateBondComboBox.setConverter(new StringConverter() { + confiscateBondComboBox.setConverter(new StringConverter<>() { @Override public String toString(BondedRole bondedRole) { return bondedRole != null ? bondedRole.getDisplayString() : ""; @@ -287,6 +304,31 @@ public BondedRole fromString(String string) { }); comboBoxes.add(confiscateBondComboBox); break; + case GENERIC: + break; + case REMOVE_ASSET: + assetComboBox = FormBuilder.addLabelComboBox(gridPane, ++gridRow, + Res.getWithCol("dao.proposal.display.assetComboBox.label")).second; + checkNotNull(assetComboBox, "assetComboBox must not be null"); + List assetList = CurrencyUtil.getAssetRegistry().stream() + .filter(e -> !e.getTickerSymbol().equals("BSQ")) + .filter(e -> !e.getTickerSymbol().equals("BTC")) + .filter(e -> CurrencyUtil.assetMatchesNetwork(e, BaseCurrencyNetwork.BTC_MAINNET)) + .collect(Collectors.toList()); + assetComboBox.setItems(FXCollections.observableArrayList(assetList)); + assetComboBox.setConverter(new StringConverter<>() { + @Override + public String toString(Asset asset) { + return asset != null ? CurrencyUtil.getNameAndCode(asset.getTickerSymbol()) : ""; + } + + @Override + public Asset fromString(String string) { + return null; + } + }); + comboBoxes.add(assetComboBox); + break; } if (!isMakeProposalScreen) { @@ -411,12 +453,18 @@ public void applyProposalPayload(Proposal proposal) { checkNotNull(bondedRoleTypeComboBox, "bondedRoleComboBox must not be null"); BondedRole bondedRole = bondedRoleProposal.getBondedRole(); bondedRoleTypeComboBox.getSelectionModel().select(bondedRole.getBondedRoleType()); - } else if (proposal instanceof ConfiscateBondProposal) { ConfiscateBondProposal confiscateBondProposal = (ConfiscateBondProposal) proposal; checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null"); daoFacade.getBondedRoleFromHash(confiscateBondProposal.getHash()) .ifPresent(bondedRole -> confiscateBondComboBox.getSelectionModel().select(bondedRole)); + } else if (proposal instanceof GenericProposal) { + // do nothing + } else if (proposal instanceof RemoveAssetProposal) { + RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) proposal; + checkNotNull(assetComboBox, "assetComboBox must not be null"); + CurrencyUtil.findAsset(removeAssetProposal.getTickerSymbol(), BaseCurrencyNetwork.BTC_MAINNET) + .ifPresent(asset -> assetComboBox.getSelectionModel().select(asset)); } int chainHeight; if (txIdTextField != null) { @@ -453,6 +501,9 @@ public void removeListeners() { //noinspection unchecked comboBox.getSelectionModel().selectedItemProperty().removeListener(inputListener); }); + + if (paramComboBox != null && paramChangeListener != null) + paramComboBox.getSelectionModel().selectedItemProperty().removeListener(paramChangeListener); } public void clearForm() { diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java index 1bdd32dba58..1545dc5d7d6 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java @@ -36,13 +36,13 @@ import bisq.core.dao.governance.proposal.ProposalType; import bisq.core.dao.governance.proposal.ProposalWithTransaction; import bisq.core.dao.governance.proposal.TxException; +import bisq.core.dao.governance.proposal.param.ChangeParamValidator; import bisq.core.dao.governance.role.BondedRole; import bisq.core.dao.state.BsqStateListener; import bisq.core.dao.state.blockchain.Block; import bisq.core.dao.state.governance.Param; import bisq.core.dao.state.period.DaoPhase; import bisq.core.locale.Res; -import bisq.core.provider.fee.FeeService; import bisq.core.util.BSFormatter; import bisq.core.util.BsqFormatter; @@ -70,7 +70,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -78,6 +77,10 @@ import static bisq.desktop.util.FormBuilder.addTitledGroupBg; import static com.google.common.base.Preconditions.checkNotNull; + + +import bisq.asset.Asset; + @FxmlView public class MakeProposalView extends ActivatableView implements BsqStateListener { private final DaoFacade daoFacade; @@ -85,6 +88,7 @@ public class MakeProposalView extends ActivatableView implements private final WalletsSetup walletsSetup; private final P2PService p2PService; private final PhasesView phasesView; + private final ChangeParamValidator changeParamValidator; private final BSFormatter btcFormatter; private final BsqFormatter bsqFormatter; @@ -107,8 +111,8 @@ private MakeProposalView(DaoFacade daoFacade, BsqWalletService bsqWalletService, WalletsSetup walletsSetup, P2PService p2PService, - FeeService feeService, PhasesView phasesView, + ChangeParamValidator changeParamValidator, BSFormatter btcFormatter, BsqFormatter bsqFormatter) { this.daoFacade = daoFacade; @@ -116,6 +120,7 @@ private MakeProposalView(DaoFacade daoFacade, this.walletsSetup = walletsSetup; this.p2PService = p2PService; this.phasesView = phasesView; + this.changeParamValidator = changeParamValidator; this.btcFormatter = btcFormatter; this.bsqFormatter = bsqFormatter; } @@ -128,7 +133,7 @@ public void initialize() { addTitledGroupBg(root, ++gridRow, 1, Res.get("dao.proposal.create.selectProposalType"), Layout.GROUP_DISTANCE); proposalTypeComboBox = FormBuilder.addLabelComboBox(root, gridRow, Res.getWithCol("dao.proposal.create.proposalType"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - proposalTypeComboBox.setConverter(new StringConverter() { + proposalTypeComboBox.setConverter(new StringConverter<>() { @Override public String toString(ProposalType proposalType) { return proposalType.getDisplayName(); @@ -139,7 +144,6 @@ public ProposalType fromString(String string) { return null; } }); - proposalTypeComboBox.setPromptText(Res.get("shared.select")); proposalTypeChangeListener = (observable, oldValue, newValue) -> { selectedProposalType = newValue; removeProposalDisplay(); @@ -147,11 +151,7 @@ public ProposalType fromString(String string) { }; alwaysVisibleGridRowIndex = gridRow + 1; - //TODO remove filter once all are implemented - List proposalTypes = Arrays.stream(ProposalType.values()) - .filter(proposalType -> proposalType != ProposalType.GENERIC && - proposalType != ProposalType.REMOVE_ALTCOIN) - .collect(Collectors.toList()); + List proposalTypes = Arrays.asList(ProposalType.values()); proposalTypeComboBox.setItems(FXCollections.observableArrayList(proposalTypes)); } @@ -207,12 +207,15 @@ public void onParseBlockChainComplete() { private void publishMyProposal(ProposalType type) { try { - final ProposalWithTransaction proposalWithTransaction = getProposalWithTransaction(type); + ProposalWithTransaction proposalWithTransaction = getProposalWithTransaction(type); + if (proposalWithTransaction == null) + return; + Proposal proposal = proposalWithTransaction.getProposal(); Transaction transaction = proposalWithTransaction.getTransaction(); Coin miningFee = transaction.getFee(); int txSize = transaction.bitcoinSerialize().length; - final Coin fee = daoFacade.getProposalFee(daoFacade.getChainHeight()); + Coin fee = daoFacade.getProposalFee(daoFacade.getChainHeight()); GUIUtil.showBsqFeeInfoPopup(fee, miningFee, txSize, bsqFormatter, btcFormatter, Res.get("dao.proposal"), () -> doPublishMyProposal(proposal, transaction)); @@ -248,6 +251,7 @@ private void doPublishMyProposal(Proposal proposal, Transaction transaction) { errorMessage -> new Popup<>().warning(errorMessage).show()); } + @Nullable private ProposalWithTransaction getProposalWithTransaction(ProposalType type) throws InsufficientMoneyException, ValidationException, TxException { @@ -262,16 +266,6 @@ private ProposalWithTransaction getProposalWithTransaction(ProposalType type) proposalDisplay.linkInputTextField.getText(), bsqFormatter.parseToCoin(proposalDisplay.requestedBsqTextField.getText()), proposalDisplay.bsqAddressTextField.getText()); - case BONDED_ROLE: - checkNotNull(proposalDisplay.bondedRoleTypeComboBox, - "proposalDisplay.bondedRoleTypeComboBox must not be null"); - bondedRole = new BondedRole(proposalDisplay.nameTextField.getText(), - proposalDisplay.linkInputTextField.getText(), - proposalDisplay.bondedRoleTypeComboBox.getSelectionModel().getSelectedItem()); - return daoFacade.getBondedRoleProposalWithTransaction(bondedRole); - case REMOVE_ALTCOIN: - //TODO - throw new RuntimeException("Not implemented yet"); case CHANGE_PARAM: checkNotNull(proposalDisplay.paramComboBox, "proposalDisplay.paramComboBox must no tbe null"); @@ -283,20 +277,27 @@ private ProposalWithTransaction getProposalWithTransaction(ProposalType type) String paramValueAsString = proposalDisplay.paramValueTextField.getText(); if (paramValueAsString == null || paramValueAsString.isEmpty()) throw new ValidationException("paramValue is null or empty"); - long paramValue; + try { - paramValue = Long.valueOf(paramValueAsString); - } catch (Throwable t) { - throw new ValidationException("paramValue is not a long value", t); + long paramValue = bsqFormatter.parseParamValue(selectedParam, paramValueAsString); + log.info("Change param: paramValue={}, paramValueAsString={}", paramValue, paramValueAsString); + + changeParamValidator.validateParamValue(selectedParam, paramValue); + return daoFacade.getParamProposalWithTransaction(proposalDisplay.nameTextField.getText(), + proposalDisplay.linkInputTextField.getText(), + selectedParam, + paramValue); + } catch (Throwable e) { + new Popup<>().warning(e.getMessage()).show(); + return null; } - //TODO add more custom param validation - return daoFacade.getParamProposalWithTransaction(proposalDisplay.nameTextField.getText(), + case BONDED_ROLE: + checkNotNull(proposalDisplay.bondedRoleTypeComboBox, + "proposalDisplay.bondedRoleTypeComboBox must not be null"); + bondedRole = new BondedRole(proposalDisplay.nameTextField.getText(), proposalDisplay.linkInputTextField.getText(), - selectedParam, - paramValue); - case GENERIC: - //TODO - throw new RuntimeException("Not implemented yet"); + proposalDisplay.bondedRoleTypeComboBox.getSelectionModel().getSelectedItem()); + return daoFacade.getBondedRoleProposalWithTransaction(bondedRole); case CONFISCATE_BOND: checkNotNull(proposalDisplay.confiscateBondComboBox, "proposalDisplay.confiscateBondComboBox must not be null"); @@ -304,6 +305,16 @@ private ProposalWithTransaction getProposalWithTransaction(ProposalType type) return daoFacade.getConfiscateBondProposalWithTransaction(proposalDisplay.nameTextField.getText(), proposalDisplay.linkInputTextField.getText(), bondedRole.getHash()); + case GENERIC: + return daoFacade.getGenericProposalWithTransaction(proposalDisplay.nameTextField.getText(), + proposalDisplay.linkInputTextField.getText()); + case REMOVE_ASSET: + checkNotNull(proposalDisplay.assetComboBox, + "proposalDisplay.assetComboBox must not be null"); + Asset asset = proposalDisplay.assetComboBox.getSelectionModel().getSelectedItem(); + return daoFacade.getRemoveAssetProposalWithTransaction(proposalDisplay.nameTextField.getText(), + proposalDisplay.linkInputTextField.getText(), + asset); default: final String msg = "Undefined ProposalType " + selectedProposalType; log.error(msg); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java index 336983a52ee..17174df51e3 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java @@ -306,7 +306,7 @@ private void updateListItems() { onSelectProposal(null); } - GUIUtil.setFitToRowsForTableView(tableView, 33, 28, 80); + GUIUtil.setFitToRowsForTableView(tableView, 33, 28, 2, 4); tableView.layout(); root.layout(); } @@ -418,7 +418,7 @@ private void onRemoveProposal() { private void onSelectProposal(ProposalsListItem item) { selectedItem = item; if (selectedItem != null) { - EvaluatedProposal evaluatedProposal = voteResultService.getAllEvaluatedProposals().stream() + EvaluatedProposal evaluatedProposal = voteResultService.getEvaluatedProposalList().stream() .filter(e -> daoFacade.isTxInCorrectCycle(e.getProposal().getTxId(), daoFacade.getChainHeight())) .filter(e -> e.getProposalTxId().equals(selectedItem.getProposal().getTxId())) diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/result/ProposalListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/ProposalListItem.java index ddc3795c5c9..31fefbe611f 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/result/ProposalListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/ProposalListItem.java @@ -23,9 +23,11 @@ import bisq.core.dao.governance.proposal.compensation.CompensationProposal; import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal; import bisq.core.dao.governance.proposal.param.ChangeParamProposal; +import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal; import bisq.core.dao.governance.proposal.role.BondedRoleProposal; import bisq.core.dao.governance.role.BondedRole; import bisq.core.dao.governance.voteresult.EvaluatedProposal; +import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.util.BsqFormatter; @@ -107,23 +109,22 @@ public String getDetails() { CompensationProposal compensationProposal = (CompensationProposal) proposal; Coin requestedBsq = evaluatedProposal.isAccepted() ? compensationProposal.getRequestedBsq() : Coin.ZERO; return bsqFormatter.formatCoinWithCode(requestedBsq); + case CHANGE_PARAM: + ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal; + return changeParamProposal.getParam().getDisplayString(); case BONDED_ROLE: BondedRoleProposal bondedRoleProposal = (BondedRoleProposal) proposal; BondedRole bondedRole = bondedRoleProposal.getBondedRole(); return Res.get("dao.bond.bondedRoleType." + bondedRole.getBondedRoleType().name()); - case REMOVE_ALTCOIN: - // TODO - return "N/A"; - case CHANGE_PARAM: - ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal; - return changeParamProposal.getParam().getDisplayString(); - case GENERIC: - // TODO - return "N/A"; case CONFISCATE_BOND: ConfiscateBondProposal confiscateBondProposal = (ConfiscateBondProposal) proposal; // TODO add info to bond return confiscateBondProposal.getTxId(); + case GENERIC: + return proposal.getName(); + case REMOVE_ASSET: + RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) proposal; + return CurrencyUtil.getNameAndCode(removeAssetProposal.getTickerSymbol()); } return "-"; } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java index cd4b81981c7..96eff76e636 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java @@ -245,11 +245,11 @@ private void fillCycleList() { .filter(proposal -> cycleService.isTxInCycle(cycle, proposal.getTxId())) .collect(Collectors.toList()); - List evaluatedProposalsForCycle = voteResultService.getAllEvaluatedProposals().stream() + List evaluatedProposalsForCycle = voteResultService.getEvaluatedProposalList().stream() .filter(evaluatedProposal -> cycleService.isTxInCycle(cycle, evaluatedProposal.getProposal().getTxId())) .collect(Collectors.toList()); - List decryptedVotesForCycle = voteResultService.getAllDecryptedBallotsWithMerits().stream() + List decryptedVotesForCycle = voteResultService.getDecryptedBallotsWithMeritsList().stream() .filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getBlindVoteTxId())) .filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getVoteRevealTxId())) .collect(Collectors.toList()); @@ -270,7 +270,7 @@ private void fillCycleList() { }); Collections.reverse(cycleListItemList); - GUIUtil.setFitToRowsForTableView(cyclesTableView, 24, 28, 80); + GUIUtil.setFitToRowsForTableView(cyclesTableView, 24, 28, 2, 4); } @@ -337,7 +337,7 @@ private void createProposalsTable() { ballotByProposalTxIdMap.get(evaluatedProposal.getProposalTxId()), bsqFormatter)) .collect(Collectors.toList())); - GUIUtil.setFitToRowsForTableView(proposalsTableView, 33, 28, 80); + GUIUtil.setFitToRowsForTableView(proposalsTableView, 33, 28, 2, 4); } @@ -406,7 +406,7 @@ private void createVotesTable() { }); voteListItemList.sort(Comparator.comparing(VoteListItem::getBlindVoteTxId)); - GUIUtil.setFitToRowsForTableView(votesTableView, 33, 28, 80); + GUIUtil.setFitToRowsForTableView(votesTableView, 33, 28, 2, 4); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 32e02a7dc5d..6f02681699d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -588,7 +588,7 @@ void requestTxFee() { feeService.requestFees(() -> { txFeeFromFeeService = feeService.getTxFee(feeTxSize); calculateTotalToPay(); - }, null); + }); } void setPreferredCurrencyForMakerFeeBtc(boolean preferredCurrencyForMakerFeeBtc) { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index 8686acb0361..1e5bccee9b1 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -218,7 +218,7 @@ void initWithData(Offer offer) { log.debug("We received the tx fee response after we have shown the funding screen and ignore that " + "to avoid that the total funds to pay changes due changed tx fees."); } - }, null); + }); calculateVolume(); calculateTotalToPay(); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SelectDepositTxWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SelectDepositTxWindow.java index d24cdd7407b..469ee8848f3 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SelectDepositTxWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SelectDepositTxWindow.java @@ -99,8 +99,7 @@ private void addContent() { Tuple2> tuple = FormBuilder.addLabelComboBox(gridPane, ++rowIndex, Res.get("selectDepositTxWindow.select")); transactionsComboBox = tuple.second; - transactionsComboBox.setPromptText(Res.get("shared.select")); - transactionsComboBox.setConverter(new StringConverter() { + transactionsComboBox.setConverter(new StringConverter<>() { @Override public String toString(Transaction transaction) { return transaction.getHashAsString(); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 34ee36e2e0c..11f5f2c05a2 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -31,6 +31,7 @@ import bisq.core.app.BisqEnvironment; import bisq.core.btc.BaseCurrencyNetwork; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.locale.Country; import bisq.core.locale.CountryUtil; import bisq.core.locale.CryptoCurrency; @@ -103,6 +104,7 @@ public class PreferencesView extends ActivatableViewAndModel fiatCurrenciesListView; @@ -131,12 +133,14 @@ public class PreferencesView extends ActivatableViewAndModel Tuple2> addLabelComboBox(GridPane gridPane, label = addLabel(gridPane, rowIndex, title, top); ComboBox comboBox = new ComboBox<>(); + // We want always the promptText + comboBox.setPromptText(Res.get("shared.select")); + + // Default ComboBox does not show promptText after clear selection. + // https://stackoverflow.com/questions/50569330/how-to-reset-combobox-and-display-prompttext?noredirect=1&lq=1 + comboBox.setButtonCell(new ListCell<>() { + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(Res.get("shared.select")); + } else { + setText(comboBox.getConverter().toString(item)); + } + } + }); + GridPane.setRowIndex(comboBox, rowIndex); GridPane.setColumnIndex(comboBox, 1); GridPane.setMargin(comboBox, new Insets(top, 0, 0, 0)); diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 599616c4660..75db15a50a9 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -114,6 +114,8 @@ import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkArgument; + @Slf4j public class GUIUtil { public final static String SHOW_ALL_FLAG = "SHOW_ALL_FLAG"; @@ -582,9 +584,12 @@ public static void showBsqFeeInfoPopup(Coin fee, Coin miningFee, int txSize, Bsq .show(); } - public static void setFitToRowsForTableView(TableView tableView, int rowHeight, int headerHeight, int minHeight) { + public static void setFitToRowsForTableView(TableView tableView, int rowHeight, int headerHeight, int minNumRows, int maxNumRows) { int size = tableView.getItems().size(); - int height = Math.max(minHeight, size * rowHeight + headerHeight); + int minHeight = rowHeight * minNumRows + headerHeight; + int maxHeight = rowHeight * maxNumRows + headerHeight; + checkArgument(maxHeight >= minHeight, "maxHeight cannot be smaller as minHeight"); + int height = Math.min(maxHeight, Math.max(minHeight, size * rowHeight + headerHeight)); tableView.setMaxHeight(-1); tableView.setMinHeight(-1); tableView.setMaxHeight(height);