diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java b/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java index 934b708bd8a..75e34d2bbc5 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/network/RepublishGovernanceDataHandler.java @@ -105,7 +105,7 @@ private void sendRepublishRequest(NodeAddress nodeAddress) { TIMEOUT); } - log.warn("We send to peer {} a republishGovernanceDataRequest.", nodeAddress); + log.info("We send to peer {} a republishGovernanceDataRequest.", nodeAddress); SettableFuture future = networkNode.sendMessage(nodeAddress, republishGovernanceDataRequest); Futures.addCallback(future, new FutureCallback<>() { @Override 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 529fbf271e3..a69b8d98e91 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 @@ -114,29 +114,33 @@ public static TxOutput getConnectedBlindVoteStakeOutput(Tx voteRevealTx, DaoStat } } - public static Tx getBlindVoteTx(TxOutput blindVoteStakeOutput, DaoStateService daoStateService, - PeriodService periodService, int chainHeight) + public static void validateBlindVoteTx(String blindVoteTxId, DaoStateService daoStateService, + PeriodService periodService, int chainHeight) throws VoteResultException.ValidationException { try { - String blindVoteTxId = blindVoteStakeOutput.getTxId(); Optional optionalBlindVoteTx = daoStateService.getTx(blindVoteTxId); + checkArgument(optionalBlindVoteTx.isPresent(), "blindVoteTx with txId " + blindVoteTxId + " not found."); + Tx blindVoteTx = optionalBlindVoteTx.get(); Optional optionalTxType = daoStateService.getOptionalTxType(blindVoteTx.getId()); + checkArgument(optionalTxType.isPresent(), "optionalTxType must be present" + ". blindVoteTxId=" + blindVoteTx.getId()); + checkArgument(optionalTxType.get() == TxType.BLIND_VOTE, "blindVoteTx must have type BLIND_VOTE but is " + optionalTxType.get() + ". blindVoteTxId=" + blindVoteTx.getId()); + checkArgument(periodService.isTxInCorrectCycle(blindVoteTx.getBlockHeight(), chainHeight), "blindVoteTx is not in correct cycle. blindVoteTx.getBlockHeight()=" + blindVoteTx.getBlockHeight() + ". chainHeight=" + chainHeight + ". blindVoteTxId=" + blindVoteTx.getId()); + checkArgument(periodService.isInPhase(blindVoteTx.getBlockHeight(), DaoPhase.Phase.BLIND_VOTE), "blindVoteTx is not in BLIND_VOTE phase. blindVoteTx.getBlockHeight()=" + blindVoteTx.getBlockHeight() + ". blindVoteTxId=" + blindVoteTx.getId()); - return blindVoteTx; } catch (Throwable t) { throw new VoteResultException.ValidationException(t); } diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java index f647105f6df..965e4e9b8a8 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 @@ -170,6 +170,7 @@ public void onParseBlockChainComplete() { private void maybeCalculateVoteResult(int chainHeight) { if (isInVoteResultPhase(chainHeight)) { + long startTs = System.currentTimeMillis(); Set decryptedBallotsWithMeritsSet = getDecryptedBallotsWithMeritsSet(chainHeight); decryptedBallotsWithMeritsSet.stream() .filter(e -> !daoStateService.getDecryptedBallotsWithMeritsList().contains(e)) @@ -232,6 +233,8 @@ private void maybeCalculateVoteResult(int chainHeight) { daoStateService.getIssuanceCandidateTxOutputs().stream() .filter(txOutput -> !daoStateService.isIssuanceTx(txOutput.getTxId())) .forEach(daoStateService::addNonBsqTxOutput); + + log.info("Evaluating vote result took {} ms", System.currentTimeMillis() - startTs); } } @@ -251,14 +254,25 @@ private Set getDecryptedBallotsWithMeritsSet(int cha } Tx voteRevealTx = optionalVoteRevealTx.get(); + // If we get a voteReveal tx which was published too late we ignore it. + if (!periodService.isTxInPhaseAndCycle(voteRevealTx.getId(), DaoPhase.Phase.VOTE_REVEAL, chainHeight)) { + log.warn("We got a vote reveal tx with was not in the correct phase and/or cycle. voteRevealTxId={}", voteRevealTx.getId()); + return null; + } + try { // TODO maybe verify version in opReturn - byte[] hashOfBlindVoteList = VoteResultConsensus.getHashOfBlindVoteList(opReturnData); - SecretKey secretKey = VoteResultConsensus.getSecretKey(opReturnData); + TxOutput blindVoteStakeOutput = VoteResultConsensus.getConnectedBlindVoteStakeOutput(voteRevealTx, daoStateService); - long blindVoteStake = blindVoteStakeOutput.getValue(); - Tx blindVoteTx = VoteResultConsensus.getBlindVoteTx(blindVoteStakeOutput, daoStateService, periodService, chainHeight); - String blindVoteTxId = blindVoteTx.getId(); + String blindVoteTxId = blindVoteStakeOutput.getTxId(); + boolean isBlindVoteInCorrectPhaseAndCycle = periodService.isTxInPhaseAndCycle(blindVoteTxId, DaoPhase.Phase.BLIND_VOTE, chainHeight); + // If we get a voteReveal tx which was published too late we ignore it. + if (!isBlindVoteInCorrectPhaseAndCycle) { + log.warn("We got a blind vote tx with was not in the correct phase and/or cycle. blindVoteTxId={}", blindVoteTxId); + return null; + } + + VoteResultConsensus.validateBlindVoteTx(blindVoteStakeOutput.getTxId(), daoStateService, periodService, chainHeight); List blindVoteList = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService); Optional optionalBlindVote = blindVoteList.stream() @@ -267,12 +281,15 @@ private Set getDecryptedBallotsWithMeritsSet(int cha if (optionalBlindVote.isPresent()) { BlindVote blindVote = optionalBlindVote.get(); try { + SecretKey secretKey = VoteResultConsensus.getSecretKey(opReturnData); 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); + byte[] hashOfBlindVoteList = VoteResultConsensus.getHashOfBlindVoteList(opReturnData); + long blindVoteStake = blindVoteStakeOutput.getValue(); return new DecryptedBallotsWithMerits(hashOfBlindVoteList, blindVoteTxId, voteRevealTxId, blindVoteStake, ballotList, meritList); } catch (VoteResultException.MissingBallotException missingBallotException) { log.warn("We are missing proposals to create the vote result: " + missingBallotException.toString()); @@ -338,6 +355,7 @@ private BallotList createBallotList(VoteWithProposalTxIdList voteWithProposalTxI // We got a vote but we don't have the ballot (which includes the proposal) // We add it to the missing list to handle it as exception later. We want all missing data so we // do not throw here. + log.warn("missingBallot for proposal with txId={}. Optional tx={}", txId, daoStateService.getTx(txId)); missingBallots.add(txId); return null; } @@ -467,13 +485,13 @@ private Set getEvaluatedProposals(Set voteRevealExceptions = FXCollections.observableArrayList(); + private final BsqNode bsqNode; /////////////////////////////////////////////////////////////////////////////////////////// @@ -95,22 +96,23 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService { @Inject public VoteRevealService(DaoStateService daoStateService, BlindVoteListService blindVoteListService, - BlindVoteValidator blindVoteValidator, PeriodService periodService, MyVoteListService myVoteListService, BsqWalletService bsqWalletService, BtcWalletService btcWalletService, P2PService p2PService, - WalletsManager walletsManager) { + WalletsManager walletsManager, + BsqNodeProvider bsqNodeProvider) { this.daoStateService = daoStateService; this.blindVoteListService = blindVoteListService; - this.blindVoteValidator = blindVoteValidator; this.periodService = periodService; this.myVoteListService = myVoteListService; this.bsqWalletService = bsqWalletService; this.btcWalletService = btcWalletService; this.p2PService = p2PService; this.walletsManager = walletsManager; + + bsqNode = bsqNodeProvider.getBsqNode(); } @@ -130,7 +132,7 @@ public void addListeners() { @Override public void start() { - maybeRevealVotes(daoStateService.getChainHeight()); + maybeRevealVotes(); } @@ -151,7 +153,8 @@ public byte[] getHashOfBlindVoteList() { @Override public void onNewBlockHeight(int blockHeight) { // TODO check if we should use onParseTxsComplete for calling maybeCalculateVoteResult - maybeRevealVotes(blockHeight); + + maybeRevealVotes(); } @Override @@ -172,56 +175,77 @@ public void onParseBlockChainComplete() { // the blind vote was created in case we have not done it already. // The voter need to be at least once online in the reveal phase when he has a blind vote created, // otherwise his vote becomes invalid and his locked stake will get unlocked - private void maybeRevealVotes(int chainHeight) { - if (periodService.getPhaseForHeight(chainHeight) == DaoPhase.Phase.VOTE_REVEAL) { - myVoteListService.getMyVoteList().stream() - .filter(myVote -> myVote.getRevealTxId() == null) // we have not already revealed TODO - .filter(myVote -> periodService.isTxInCorrectCycle(myVote.getTxId(), chainHeight)) - .forEach(myVote -> { - // We handle the exception here inside the stream iteration as we have not get triggered from an - // outside user intent anyway. We keep errors in a observable list so clients can observe that to - // get notified if anything went wrong. - try { - revealVote(myVote, chainHeight); - } catch (IOException | WalletException | TransactionVerificationException - | InsufficientMoneyException e) { - voteRevealExceptions.add(new VoteRevealException("Exception at calling revealVote.", - e, myVote.getTxId())); - } catch (VoteRevealException e) { - voteRevealExceptions.add(e); + private void maybeRevealVotes() { + // We must not use daoStateService.getChainHeight() because that gets updated with each parsed block but we + // only want to publish the vote reveal tx if our current real chain height is matching the cycle and phase and + // not at any intermediate height during parsing all blocks. The bsqNode knows the latest height from either + // Bitcoin Core or from the seed node. + int chainHeight = bsqNode.getChainTipHeight(); + myVoteListService.getMyVoteList().stream() + .filter(myVote -> myVote.getRevealTxId() == null) // we have not already revealed + .forEach(myVote -> { + boolean isInVoteRevealPhase = periodService.getPhaseForHeight(chainHeight) == DaoPhase.Phase.VOTE_REVEAL; + boolean isBlindVoteTxInCorrectPhaseAndCycle = periodService.isTxInPhaseAndCycle(myVote.getTxId(), DaoPhase.Phase.BLIND_VOTE, chainHeight); + if (isInVoteRevealPhase && isBlindVoteTxInCorrectPhaseAndCycle) { + // Standard case that we are in the correct phase and cycle and create the reveal tx. + revealVote(myVote); + } else { + // Exceptional case that the user missed the vote reveal phase. We still publish the vote + // reveal tx to unlock the vote stake. + + // We cannot handle that case in the parser directly to avoid that reveal tx and unlock the + // BSQ because the blind vote tx is already in the snapshot and does not get parsed + // again. It would require a reset of the snapshot and parse all blocks again. + // As this is an exceptional case we prefer to have a simple solution instead and just + // publish the vote reveal tx but are aware that is is invalid. + log.warn("We missed the vote reveal phase but publish now the tx to unlock our locked " + + "BSQ from the blind vote tx. BlindVoteTxId={}", myVote.getTxId()); + + boolean isAfterVoteRevealPhase = periodService.getPhaseForHeight(chainHeight).ordinal() > DaoPhase.Phase.VOTE_REVEAL.ordinal(); + + // We missed the reveal phase but we are in the correct cycle + boolean missedPhaseSameCycle = isAfterVoteRevealPhase && isBlindVoteTxInCorrectPhaseAndCycle; + + // If we missed the cycle we don't care about the phase anymore. + boolean isBlindVoteTxInPastCycle = periodService.isTxInPastCycle(myVote.getTxId(), chainHeight); + + if (missedPhaseSameCycle || isBlindVoteTxInPastCycle) { + // We handle the exception here inside the stream iteration as we have not get triggered from an + // outside user intent anyway. We keep errors in a observable list so clients can observe that to + // get notified if anything went wrong. + revealVote(myVote); } - }); - } + } + }); } - private void revealVote(MyVote myVote, int chainHeight) throws IOException, WalletException, - InsufficientMoneyException, TransactionVerificationException, VoteRevealException { - // We collect all valid blind vote items we received via the p2p network. - // It might be that different nodes have a different collection of those items. - // To ensure we get a consensus of the data for later calculating the result we will put a hash of each - // voters blind vote collection into the opReturn data and check for a majority at issuance time. - // The voters "vote" with their stake at the reveal tx for their version of the blind vote collection. + private void revealVote(MyVote myVote) { + try { + // We collect all valid blind vote items we received via the p2p network. + // It might be that different nodes have a different collection of those items. + // To ensure we get a consensus of the data for later calculating the result we will put a hash of each + // voters blind vote collection into the opReturn data and check for a majority at issuance time. + // The voters "vote" with their stake at the reveal tx for their version of the blind vote collection. - // TODO make more clear by using param like here: + // TODO make more clear by using param like here: /* List blindVotes = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService); VoteRevealConsensus.getHashOfBlindVoteList(blindVotes);*/ - byte[] hashOfBlindVoteList = getHashOfBlindVoteList(); + byte[] hashOfBlindVoteList = getHashOfBlindVoteList(); - log.info("Sha256Ripemd160 hash of hashOfBlindVoteList " + Utilities.bytesAsHexString(hashOfBlindVoteList)); - byte[] opReturnData = VoteRevealConsensus.getOpReturnData(hashOfBlindVoteList, myVote.getSecretKey()); + log.info("Sha256Ripemd160 hash of hashOfBlindVoteList " + Utilities.bytesAsHexString(hashOfBlindVoteList)); + byte[] opReturnData = VoteRevealConsensus.getOpReturnData(hashOfBlindVoteList, myVote.getSecretKey()); - // We search for my unspent stake output. - // myVote is already tested if it is in current cycle at maybeRevealVotes - // We expect that the blind vote tx and stake output is available. If not we throw an exception. - TxOutput stakeTxOutput = daoStateService.getUnspentBlindVoteStakeTxOutputs().stream() - .filter(txOutput -> txOutput.getTxId().equals(myVote.getTxId())) - .findFirst() - .orElseThrow(() -> new VoteRevealException("stakeTxOutput is not found for myVote.", myVote)); + // We search for my unspent stake output. + // myVote is already tested if it is in current cycle at maybeRevealVotes + // We expect that the blind vote tx and stake output is available. If not we throw an exception. + TxOutput stakeTxOutput = daoStateService.getUnspentBlindVoteStakeTxOutputs().stream() + .filter(txOutput -> txOutput.getTxId().equals(myVote.getTxId())) + .findFirst() + .orElseThrow(() -> new VoteRevealException("stakeTxOutput is not found for myVote.", myVote)); - // TxOutput has to be in the current cycle. Phase is checked in the parser anyway. - // TODO is phase check needed and done in parser still? - if (periodService.isTxInCorrectCycle(stakeTxOutput.getTxId(), chainHeight)) { + // TxOutput has to be in the current cycle. Phase is checked in the parser anyway. + // TODO is phase check needed and done in parser still? Transaction voteRevealTx = getVoteRevealTx(stakeTxOutput, opReturnData); log.info("voteRevealTx={}", voteRevealTx); publishTx(voteRevealTx); @@ -234,11 +258,12 @@ private void revealVote(MyVote myVote, int chainHeight) throws IOException, Wall // Just for additional resilience we republish our blind votes List sortedBlindVoteListOfCycle = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService); rePublishBlindVotePayloadList(sortedBlindVoteListOfCycle); - } else { - final String msg = "Tx of stake out put is not in our cycle. That must not happen."; - log.error("{}. chainHeight={}, blindVoteTxId()={}", msg, chainHeight, myVote.getTxId()); - voteRevealExceptions.add(new VoteRevealException(msg, - stakeTxOutput.getTxId())); + } catch (IOException | WalletException | TransactionVerificationException + | InsufficientMoneyException e) { + voteRevealExceptions.add(new VoteRevealException("Exception at calling revealVote.", + e, myVote.getTxId())); + } catch (VoteRevealException e) { + voteRevealExceptions.add(e); } } diff --git a/core/src/main/java/bisq/core/dao/node/BsqNode.java b/core/src/main/java/bisq/core/dao/node/BsqNode.java index b52df186cfe..160ec4438b6 100644 --- a/core/src/main/java/bisq/core/dao/node/BsqNode.java +++ b/core/src/main/java/bisq/core/dao/node/BsqNode.java @@ -38,6 +38,7 @@ import java.util.Optional; import java.util.function.Consumer; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @@ -62,6 +63,9 @@ public abstract class BsqNode implements DaoSetupService { @Nullable protected Consumer warnMessageHandler; protected List pendingBlocks = new ArrayList<>(); + // The chain height of the latest Block we either get reported by Bitcoin Core or from the seed node + @Getter + protected int chainTipHeight; /////////////////////////////////////////////////////////////////////////////////////////// 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 3ab7c786294..840ee0ad431 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 @@ -150,6 +150,15 @@ private void addBlockHandler() { addBlockHandlerAdded = true; rpcService.addNewBtcBlockHandler(rawBlock -> { try { + // We need to call that before parsing to have set the chain tip correctly for clients + // which might listen for new blocks on daoStateService. DaoStateListener.onNewBlockHeight + // is called before the doParseBlock returns. + + // We only update chainTipHeight if we get a newer block + int blockHeight = rawBlock.getHeight(); + if (blockHeight > chainTipHeight) + chainTipHeight = blockHeight; + doParseBlock(rawBlock).ifPresent(this::onNewBlock); } catch (RequiredReorgFromSnapshotException ignore) { } @@ -188,6 +197,7 @@ private void requestChainHeadHeightAndParseBlocks(int startBlockHeight) { private void parseBlocksOnHeadHeight(int startBlockHeight, int chainHeight) { if (startBlockHeight <= chainHeight) { log.info("parseBlocks with startBlockHeight={} and chainHeight={}", startBlockHeight, chainHeight); + chainTipHeight = chainHeight; parseBlocks(startBlockHeight, chainHeight, this::onNewBlock, 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 66e221edb24..b21bccf1713 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 @@ -137,9 +137,10 @@ protected void startReOrgFromLastSnapshot() { // We received the missing blocks private void onRequestedBlocksReceived(List blockList) { - if (!blockList.isEmpty()) - log.info("We received blocks from height {} to {}", blockList.get(0).getHeight(), - blockList.get(blockList.size() - 1).getHeight()); + if (!blockList.isEmpty()) { + chainTipHeight = blockList.get(blockList.size() - 1).getHeight(); + log.info("We received blocks from height {} to {}", blockList.get(0).getHeight(), chainTipHeight); + } // 4000 blocks take about 3 seconds if DAO UI is not displayed or 7 sec. if it is displayed. // The updates at block height change are not much optimized yet, so that can be for sure improved @@ -162,7 +163,13 @@ private void onRequestedBlocksReceived(List blockList) { // We received a new block private void onNewBlockReceived(RawBlock block) { - log.info("onNewBlockReceived: block at height {}, hash={}", block.getHeight(), block.getHash()); + int blockHeight = block.getHeight(); + log.info("onNewBlockReceived: block at height {}, hash={}", blockHeight, block.getHash()); + + // We only update chainTipHeight if we get a newer block + if (blockHeight > chainTipHeight) + chainTipHeight = blockHeight; + try { doParseBlock(block); } catch (RequiredReorgFromSnapshotException ignore) { diff --git a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java index d22a15d095f..d786425e7cf 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java @@ -117,7 +117,7 @@ public Block parseBlock(RawBlock rawBlock) throws BlockHashNotConnectingExceptio .ifPresent(txList::add)); if (System.currentTimeMillis() - startTs > 0) - log.info("parseBsqTxs took {} ms", rawBlock.getRawTxs().size(), System.currentTimeMillis() - startTs); + log.info("Parsing {} transactions took {} ms", rawBlock.getRawTxs().size(), System.currentTimeMillis() - startTs); daoStateService.onParseBlockComplete(block); return block; diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java index 8badd3d168f..a728c95881e 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java @@ -200,7 +200,8 @@ private void applyTxTypeAndTxOutputType(int blockHeight, TempTx tempTx, long bsq processBlindVote(blockHeight, tempTx, bsqFee); break; case VOTE_REVEAL: - processVoteReveal(blockHeight, tempTx); + // We do not check phase or cycle as a late voteReveal tx is considered a valid BSQ tx. + // The vote result though will ignore such votes. break; case LOCKUP: case ASSET_LISTING_FEE: @@ -230,14 +231,14 @@ private void applyTxTypeAndTxOutputType(int blockHeight, TempTx tempTx, long bsq } private void processProposal(int blockHeight, TempTx tempTx, long bsqFee) { - boolean isFeeAndPhaseValid = isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE); + boolean isFeeAndPhaseValid = isFeeAndPhaseValid(tempTx.getId(), blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE); if (!isFeeAndPhaseValid) { tempTx.setTxType(TxType.INVALID); } } private void processIssuance(int blockHeight, TempTx tempTx, long bsqFee) { - boolean isFeeAndPhaseValid = isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE); + boolean isFeeAndPhaseValid = isFeeAndPhaseValid(tempTx.getId(), blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE); Optional optionalIssuanceCandidate = txOutputParser.getOptionalIssuanceCandidate(); if (isFeeAndPhaseValid) { if (optionalIssuanceCandidate.isPresent()) { @@ -258,7 +259,7 @@ private void processIssuance(int blockHeight, TempTx tempTx, long bsqFee) { } private void processBlindVote(int blockHeight, TempTx tempTx, long bsqFee) { - boolean isFeeAndPhaseValid = isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.BLIND_VOTE, Param.BLIND_VOTE_FEE); + boolean isFeeAndPhaseValid = isFeeAndPhaseValid(tempTx.getId(), blockHeight, bsqFee, DaoPhase.Phase.BLIND_VOTE, Param.BLIND_VOTE_FEE); if (!isFeeAndPhaseValid) { tempTx.setTxType(TxType.INVALID); txOutputParser.getOptionalBlindVoteLockStakeOutput().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT)); @@ -267,22 +268,6 @@ private void processBlindVote(int blockHeight, TempTx tempTx, long bsqFee) { } } - private void processVoteReveal(int blockHeight, TempTx tempTx) { - boolean isPhaseValid = isPhaseValid(blockHeight, DaoPhase.Phase.VOTE_REVEAL); - if (!isPhaseValid) { - tempTx.setTxType(TxType.INVALID); - } - - // We must not use an `if else` here! - if (!isPhaseValid || !txInputParser.isVoteRevealInputValid()) { - txOutputParser.getOptionalVoteRevealUnlockStakeOutput().ifPresent( - tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT)); - // Empty Optional case is a possible valid case where a random tx matches our opReturn rules but it is not a - // valid BSQ tx. - } - } - - /** * Whether the BSQ fee and phase is valid for a transaction. * @@ -292,10 +277,11 @@ private void processVoteReveal(int blockHeight, TempTx tempTx) { * @param param The parameter for the fee, e.g {@code Param.PROPOSAL_FEE}. * @return True if the fee and phase was valid, false otherwise. */ - private boolean isFeeAndPhaseValid(int blockHeight, long bsqFee, DaoPhase.Phase phase, Param param) { + private boolean isFeeAndPhaseValid(String txId, int blockHeight, long bsqFee, DaoPhase.Phase phase, Param param) { // The leftover BSQ balance from the inputs is the BSQ fee in case we are in an OP_RETURN output - if (!isPhaseValid(blockHeight, phase)) { + if (!periodService.isInPhase(blockHeight, phase)) { + log.warn("Tx with ID {} is not in required phase ({}). blockHeight={}", txId, phase, blockHeight); return false; } long paramValue = daoStateService.getParamValueAsCoin(param, blockHeight).value; @@ -306,14 +292,6 @@ private boolean isFeeAndPhaseValid(int blockHeight, long bsqFee, DaoPhase.Phase return isFeeCorrect; } - private boolean isPhaseValid(int blockHeight, DaoPhase.Phase phase) { - boolean isInPhase = periodService.isInPhase(blockHeight, phase); - if (!isInPhase) { - log.warn("Not in {} phase. blockHeight={}", phase, blockHeight); - } - return isInPhase; - } - /////////////////////////////////////////////////////////////////////////////////////////// // Static methods diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index dcadd3f7824..f2802f4abbf 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -775,7 +775,7 @@ public void confiscateBond(String lockupTxId) { if (optionalTxOutput.isPresent()) { TxOutput lockupTxOutput = optionalTxOutput.get(); if (isUnspent(lockupTxOutput.getKey())) { - log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey()); + log.warn("confiscateBond: lockupTxOutput {} is still unspent so we can confiscate it.", lockupTxOutput.getKey()); doConfiscateBond(lockupTxId); } else { // We lookup for the unlock tx which need to be still in unlocking state @@ -784,7 +784,7 @@ public void confiscateBond(String lockupTxId) { String unlockTxId = optionalSpentInfo.get().getTxId(); if (isUnlockingAndUnspent(unlockTxId)) { // We found the unlock tx is still not spend - log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey()); + log.warn("confiscateBond: lockupTxOutput {} is still unspent so we can We confiscate it.", lockupTxOutput.getKey()); doConfiscateBond(lockupTxId); } else { // We could be more radical here and confiscate the output if it is unspent but lock time is over,