Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add irregular txType, add check for total balance, prevent proposal withhold attack #2587

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c20964f
Don't create vote reveal tx in last block of phase
ManfredKarrer Mar 26, 2019
ce1da64
Add IRREGULAR txType for txs which are not rule conform but not burnt
ManfredKarrer Mar 26, 2019
ff86831
Merge branch 'master' into add-check-for-bsq-balance
ManfredKarrer Mar 26, 2019
3d5ff1c
Update core/src/main/java/bisq/core/dao/governance/votereveal/VoteRev…
sqrrm Mar 26, 2019
83b7ed9
Update core/src/main/java/bisq/core/dao/governance/votereveal/VoteRev…
sqrrm Mar 26, 2019
a3e1e67
Update core/src/main/java/bisq/core/dao/node/parser/TxParser.java
sqrrm Mar 26, 2019
2758db5
Update desktop/src/main/java/bisq/desktop/main/dao/wallet/dashboard/B…
sqrrm Mar 27, 2019
7479b88
Keep invalid state for txs which cannot have been created by the app
ManfredKarrer Mar 27, 2019
121c0f6
Merge branch 'add-check-for-bsq-balance' of https://github.com/Manfre…
ManfredKarrer Mar 27, 2019
bf83687
Rename BTC_DAO_TESTNET to BTC_DAO_TESTNET2 to enforce new network
ManfredKarrer Mar 27, 2019
8016f45
Add try/catch to print error in case the file does not exist
ManfredKarrer Mar 27, 2019
793d006
Rename BTC_DAO_TESTNET2 to BTC_DAO_REGTEST
ManfredKarrer Mar 27, 2019
1bd6f03
Add new genesis tx for DAO_REGTEST
ManfredKarrer Mar 27, 2019
999fe85
Reduce durations of phases for DAO_REGTEST
ManfredKarrer Mar 27, 2019
fa8b8c5
use new DAO_REGTEST server and seeds
ManfredKarrer Mar 27, 2019
c1edc62
Fix incorrect seed address
ManfredKarrer Mar 27, 2019
73b0a17
Add log for seeds
ManfredKarrer Mar 27, 2019
c6947ba
Merge branch 'master' into add-check-for-bsq-balance
ManfredKarrer Mar 27, 2019
8704ba9
Remove unused param
ManfredKarrer Mar 27, 2019
af3aedb
Update core/src/main/java/bisq/core/dao/governance/param/Param.java
sqrrm Mar 27, 2019
f0787d8
Update core/src/main/java/bisq/core/dao/governance/param/Param.java
sqrrm Mar 27, 2019
9202d28
Update core/src/main/java/bisq/core/dao/governance/param/Param.java
sqrrm Mar 27, 2019
499d91e
Improve formatting
ManfredKarrer Mar 28, 2019
f3ad4ea
Cleanup, add string
ManfredKarrer Mar 28, 2019
39ee4c0
Protect against proposal withhold attack
ManfredKarrer Mar 28, 2019
c3a8c27
Update core/src/main/java/bisq/core/dao/governance/voteresult/VoteRes…
sqrrm Mar 28, 2019
24811ff
Update core/src/main/java/bisq/core/dao/governance/voteresult/VoteRes…
sqrrm Mar 28, 2019
695ed13
Fix if else case
ManfredKarrer Mar 29, 2019
96a20ae
Merge branch 'add-check-for-bsq-balance' of https://github.com/Manfre…
ManfredKarrer Mar 29, 2019
18a3f1e
Merge branch 'master' into add-check-for-bsq-balance
ManfredKarrer Mar 29, 2019
acf2648
Use lockupTxId instead of uid
ManfredKarrer Mar 29, 2019
bb7ff47
Add random delay for proposalPayload publishing
ManfredKarrer Mar 29, 2019
30059eb
Cleanup
ManfredKarrer Mar 29, 2019
995844f
Remove setFitToRowsForTableView, update merit at activate
ManfredKarrer Mar 29, 2019
a08b910
Add support for displaying burned BSQ from invalid txs
ManfredKarrer Mar 30, 2019
bec73dc
Add popup in case the utxo balance does not match the bsq balance
ManfredKarrer Mar 30, 2019
4b11e58
Set MAINNET_GENESIS_TOTAL_SUPPLY of old mainnet genesis
ManfredKarrer Mar 30, 2019
d5c7e0a
Improve Merit handling
ManfredKarrer Mar 30, 2019
a28805b
Use onParseBlockCompleteAfterBatchProcessing to avoid sequence issues
ManfredKarrer Mar 30, 2019
a71a573
Add better comments, cleanup
ManfredKarrer Mar 30, 2019
1e6f0bf
Refactoring: Rename method
ManfredKarrer Mar 30, 2019
b39c8be
Refactoring: Rename method
ManfredKarrer Mar 30, 2019
e2d9fe1
Fix comment
ManfredKarrer Mar 30, 2019
69b134b
Refactoring: Rename method
ManfredKarrer Mar 30, 2019
d422a73
Handle merits better
ManfredKarrer Mar 30, 2019
823cec0
Improve handling fo p2p network data broadcasts
ManfredKarrer Mar 30, 2019
d5fc7cb
Use burnedBsq field in Tx for burnedFee and invalidatedBsq
ManfredKarrer Mar 31, 2019
fe646e5
Add string validations
ManfredKarrer Mar 31, 2019
e624625
Fix missing close handler and avoid nullPointer exception
ManfredKarrer Mar 31, 2019
122bc80
Use null instead of empty string for txId
ManfredKarrer Mar 31, 2019
73db81a
Add more validation
ManfredKarrer Mar 31, 2019
c7bd2ee
Add BTC_DAO_TESTNET again to keep supporting current dao testnet
ManfredKarrer Mar 31, 2019
fa3ec34
Merge branch 'master' into add-check-for-bsq-balance
ManfredKarrer Mar 31, 2019
431f76e
Add BTC_DAO_TESTNET
ManfredKarrer Mar 31, 2019
e74ce12
Improve comments, cleanup
ManfredKarrer Mar 31, 2019
30a710f
Cleanup
ManfredKarrer Mar 31, 2019
dad4b04
Update comment
ManfredKarrer Mar 31, 2019
4d56ce9
Apply ExtraDataMapValidator for all extraDataMap fields
ManfredKarrer Mar 31, 2019
e9e4b49
Add number of irregular txs to UI
ManfredKarrer Mar 31, 2019
0e2bb14
Break up lines
ManfredKarrer Mar 31, 2019
f95f770
Update witness file
ManfredKarrer Mar 31, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -45,7 +45,6 @@
public class BallotListPresentation implements BallotListService.BallotListChangeListener, DaoStateListener {
private final BallotListService ballotListService;
private final PeriodService periodService;
private final DaoStateService daoStateService;
private final ProposalValidatorProvider proposalValidatorProvider;

@Getter
Expand All @@ -65,31 +64,20 @@ public BallotListPresentation(BallotListService ballotListService,
ProposalValidatorProvider proposalValidatorProvider) {
this.ballotListService = ballotListService;
this.periodService = periodService;
this.daoStateService = daoStateService;
this.proposalValidatorProvider = proposalValidatorProvider;

daoStateService.addDaoStateListener(this);
ballotListService.addListener(this);
}


///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////

@Override
public void onNewBlockHeight(int blockHeight) {
if (daoStateService.isParseBlockChainComplete()) {
ballotsOfCycle.setPredicate(ballot -> periodService.isTxInCorrectCycle(ballot.getTxId(), blockHeight));
}
}

@Override
public void onParseBlockChainComplete() {
ballotsOfCycle.setPredicate(ballot -> periodService.isTxInCorrectCycle(ballot.getTxId(), daoStateService.getChainHeight()));
}

@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
ballotsOfCycle.setPredicate(ballot -> periodService.isTxInCorrectCycle(ballot.getTxId(), block.getHeight()));
onListChanged(ballotListService.getValidatedBallotList());
}

Expand Down
Expand Up @@ -136,20 +136,20 @@ private void fillListFromAppendOnlyDataStore() {
p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().forEach(e -> onAppendOnlyDataAdded(e, false));
}

private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean doLog) {
private void onAppendOnlyDataAdded(PersistableNetworkPayload persistableNetworkPayload, boolean fromBroadcastMessage) {
if (persistableNetworkPayload instanceof BlindVotePayload) {
BlindVotePayload blindVotePayload = (BlindVotePayload) persistableNetworkPayload;
if (!blindVotePayloads.contains(blindVotePayload)) {
BlindVote blindVote = blindVotePayload.getBlindVote();
String txId = blindVote.getTxId();
// We don't check the phase and the cycle as we want to add all object independently when we receive it
// (or when we start the app to fill our list from the data we gor from the seed node).
// We don't validate as we might receive blindVotes from other cycles or phases at startup.
// Beside that we might receive payloads we requested at the vote result phase in case we missed some
// payloads. We prefer here resilience over protection against late publishing attacks.
if (blindVoteValidator.areDataFieldsValid(blindVote)) {
// We don't validate as we might receive blindVotes from other cycles or phases at startup.
blindVotePayloads.add(blindVotePayload);
if (doLog) {
if (fromBroadcastMessage) {
log.info("We received a blindVotePayload. blindVoteTxId={}", txId);
}
blindVotePayloads.add(blindVotePayload);
} else {
log.warn("We received an invalid blindVotePayload. blindVoteTxId={}", txId);
}
Expand Down
Expand Up @@ -135,7 +135,7 @@ public MyBlindVoteListService(P2PService p2PService,
this.myVoteListService = myVoteListService;
this.myProposalListService = myProposalListService;

numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishOnceWellConnected();
numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishMyBlindVoteOnceWellConnected();
}


Expand Down Expand Up @@ -176,7 +176,7 @@ public void readPersisted() {

@Override
public void onParseBlockChainComplete() {
rePublishOnceWellConnected();
rePublishMyBlindVoteOnceWellConnected();
}


Expand Down Expand Up @@ -351,15 +351,16 @@ private Transaction getBlindVoteTx(Coin stake, Coin fee, byte[] opReturnData)
return bsqWalletService.signTx(txWithBtcFee);
}

private void rePublishOnceWellConnected() {
private void rePublishMyBlindVoteOnceWellConnected() {
// We republish at each startup at any block during the cycle. We filter anyway for valid blind votes
// of that cycle so it is 1 blind vote getting rebroadcast at each startup to my neighbors.
int minPeers = BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? 4 : 1;
if ((p2PService.getNumConnectedPeers().get() >= minPeers && p2PService.isBootstrapped()) ||
BisqEnvironment.getBaseCurrencyNetwork().isRegtest()) {
int chainHeight = periodService.getChainHeight();
myBlindVoteList.stream()
.filter(blindVote -> periodService.isTxInPhaseAndCycle(blindVote.getTxId(),
DaoPhase.Phase.BLIND_VOTE,
chainHeight))
periodService.getChainHeight()))
.forEach(blindVote -> addToP2PNetwork(blindVote, null));

// We delay removal of listener as we call that inside listener itself.
Expand All @@ -369,13 +370,15 @@ private void rePublishOnceWellConnected() {

private void addToP2PNetwork(BlindVote blindVote, @Nullable ErrorMessageHandler errorMessageHandler) {
BlindVotePayload blindVotePayload = new BlindVotePayload(blindVote);
// We use reBroadcast flag here as we only broadcast our own blindVote and want to be sure it gets distributed
// well.
boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);

if (success) {
log.info("We added a blindVotePayload to the P2P network as append only data. blindVoteTxId={}",
blindVote.getTxId());
} else {
final String msg = "Adding of blindVotePayload to P2P network failed. blindVoteTxId=" + blindVote.getTxId();
String msg = "Adding of blindVotePayload to P2P network failed. blindVoteTxId=" + blindVote.getTxId();
log.error(msg);
if (errorMessageHandler != null)
errorMessageHandler.handleErrorMessage(msg);
Expand Down
Expand Up @@ -54,28 +54,27 @@ public static MeritList decryptMeritList(byte[] encryptedMeritList, SecretKey se
}

public static long getMeritStake(String blindVoteTxId, MeritList meritList, DaoStateService daoStateService) {
int txChainHeight = daoStateService.getTx(blindVoteTxId).map(Tx::getBlockHeight).orElse(0);
return getMeritStake(blindVoteTxId, meritList, txChainHeight);
}

private static long getMeritStake(String blindVoteTxId, MeritList meritList, int txChainHeight) {
// We need to take the chain height when the blindVoteTx got published so we get the same merit for the vote even at
// later blocks (merit decreases with each block).
if (txChainHeight == 0) {
int blindVoteTxHeight = daoStateService.getTx(blindVoteTxId).map(Tx::getBlockHeight).orElse(0);
if (blindVoteTxHeight == 0) {
log.error("Error at getMeritStake: blindVoteTx not found in daoStateService. blindVoteTxId=" + blindVoteTxId);
return 0;
}

// We only use past issuance. In case we would calculate the merit after the vote result phase we have the
// issuance from the same cycle but we must not add that to the merit.
return meritList.getList().stream()
.filter(merit -> isSignatureValid(merit.getSignature(), merit.getIssuance().getPubKey(), blindVoteTxId))
.filter(merit -> merit.getIssuance().getChainHeight() <= blindVoteTxHeight)
.mapToLong(merit -> {
try {
Issuance issuance = merit.getIssuance();
checkArgument(issuance.getIssuanceType() == IssuanceType.COMPENSATION,
"issuance must be of type COMPENSATION");
return getWeightedMeritAmount(issuance.getAmount(),
issuance.getChainHeight(),
txChainHeight,
blindVoteTxHeight,
ManfredKarrer marked this conversation as resolved.
Show resolved Hide resolved
BLOCKS_PER_YEAR);
} catch (Throwable t) {
log.error("Error at getMeritStake: error={}, merit={}", t.toString(), merit);
Expand Down Expand Up @@ -145,17 +144,20 @@ public static long getWeightedMeritAmount(long amount, int issuanceHeight, int b
public static long getCurrentlyAvailableMerit(MeritList meritList, int currentChainHeight) {
// We need to take the chain height when the blindVoteTx got published so we get the same merit for the vote even at
// later blocks (merit decreases with each block).
// We add 1 block to currentChainHeight so that the displayed merit would match the merit in case we get the
// blind vote tx into the next block.
int height = currentChainHeight + 1;
return meritList.getList().stream()
.mapToLong(merit -> {
try {
Issuance issuance = merit.getIssuance();
checkArgument(issuance.getIssuanceType() == IssuanceType.COMPENSATION, "issuance must be of type COMPENSATION");
int issuanceHeight = issuance.getChainHeight();
checkArgument(issuanceHeight <= currentChainHeight,
checkArgument(issuanceHeight <= height,
"issuanceHeight must not be larger as currentChainHeight");
return getWeightedMeritAmount(issuance.getAmount(),
issuanceHeight,
currentChainHeight,
height,
BLOCKS_PER_YEAR);
} catch (Throwable t) {
log.error("Error at getCurrentlyAvailableMerit: " + t.toString());
Expand Down
Expand Up @@ -126,7 +126,7 @@ public static MyVote fromProto(PB.MyVote proto) {
// API
///////////////////////////////////////////////////////////////////////////////////////////

public String getTxId() {
public String getBlindVoteTxId() {
return blindVote.getTxId();
}

Expand Down
Expand Up @@ -64,7 +64,7 @@ public static PersistableEnvelope fromProto(PB.MyVoteList proto) {
@Override
public String toString() {
return "List of TxId's in MyVoteList: " + getList().stream()
.map(MyVote::getTxId)
.map(MyVote::getBlindVoteTxId)
.collect(Collectors.toList());
}
}
Expand Down
Expand Up @@ -30,6 +30,7 @@

import bisq.network.p2p.P2PService;

import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import bisq.common.crypto.KeyRing;
import bisq.common.handlers.ErrorMessageHandler;
Expand Down Expand Up @@ -93,7 +94,7 @@ public MyProposalListService(P2PService p2PService,

signaturePubKey = keyRing.getPubKeyRing().getSignaturePubKey();

numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishOnceWellConnected();
numConnectedPeersListener = (observable, oldValue, newValue) -> rePublishMyProposalsOnceWellConnected();
daoStateService.addDaoStateListener(this);
p2PService.getNumConnectedPeers().addListener(numConnectedPeersListener);
}
Expand Down Expand Up @@ -122,7 +123,7 @@ public void readPersisted() {

@Override
public void onParseBlockChainComplete() {
rePublishOnceWellConnected();
rePublishMyProposalsOnceWellConnected();
}


Expand Down Expand Up @@ -216,26 +217,23 @@ private boolean addToP2PNetworkAsProtectedData(Proposal proposal) {
return p2PService.addProtectedStorageEntry(new TempProposalPayload(proposal, signaturePubKey), true);
}

private void rePublishOnceWellConnected() {
private void rePublishMyProposalsOnceWellConnected() {
// We republish at each startup at any block during the cycle. We filter anyway for valid blind votes
// of that cycle so it is 1 blind vote getting rebroadcast at each startup to my neighbors.
int minPeers = BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? 4 : 1;
if ((p2PService.getNumConnectedPeers().get() >= minPeers && p2PService.isBootstrapped()) ||
BisqEnvironment.getBaseCurrencyNetwork().isRegtest()) {
p2PService.getNumConnectedPeers().removeListener(numConnectedPeersListener);
rePublish();
myProposalList.stream()
.filter(proposal -> periodService.isTxInPhaseAndCycle(proposal.getTxId(),
DaoPhase.Phase.PROPOSAL,
periodService.getChainHeight()))
.forEach(this::addToP2PNetworkAsProtectedData);

// We delay removal of listener as we call that inside listener itself.
UserThread.execute(() -> p2PService.getNumConnectedPeers().removeListener(numConnectedPeersListener));
}
}

private void rePublish() {
myProposalList.forEach(proposal -> {
final String txId = proposal.getTxId();
if (periodService.isTxInPhaseAndCycle(txId, DaoPhase.Phase.PROPOSAL, periodService.getChainHeight())) {
boolean result = addToP2PNetworkAsProtectedData(proposal);
if (!result)
log.warn("Adding of proposal to P2P network failed.\nproposal=" + proposal);
}
});
}

private void persist() {
storage.queueUpForSave();
}
Expand Down