Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	core/src/main/java/bisq/core/dao/state/SnapshotManager.java
This commit is contained in:
Manfred Karrer 2018-09-25 19:58:15 -05:00
commit 3689e4cbf1
No known key found for this signature in database
GPG Key ID: 401250966A6B2C46
27 changed files with 391 additions and 267 deletions

View File

@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@ -53,7 +53,7 @@ import bisq.core.dao.node.BsqNodeProvider;
import bisq.core.dao.node.full.FullNode;
import bisq.core.dao.node.full.RpcService;
import bisq.core.dao.node.full.network.FullNodeNetworkService;
import bisq.core.dao.node.json.JsonBlockChainExporter;
import bisq.core.dao.node.json.ExportJsonFilesService;
import bisq.core.dao.node.lite.LiteNode;
import bisq.core.dao.node.lite.network.LiteNodeNetworkService;
import bisq.core.dao.node.parser.BlockParser;
@ -99,7 +99,7 @@ public class DaoModule extends AppModule {
bind(BsqState.class).in(Singleton.class);
bind(BsqStateService.class).in(Singleton.class);
bind(SnapshotManager.class).in(Singleton.class);
bind(JsonBlockChainExporter.class).in(Singleton.class);
bind(ExportJsonFilesService.class).in(Singleton.class);
// Period
bind(CycleService.class).in(Singleton.class);

View File

@ -25,6 +25,7 @@ import bisq.core.dao.governance.voteresult.VoteResultService;
import bisq.core.dao.governance.votereveal.VoteRevealService;
import bisq.core.dao.node.BsqNode;
import bisq.core.dao.node.BsqNodeProvider;
import bisq.core.dao.node.json.ExportJsonFilesService;
import bisq.core.dao.state.BsqStateService;
import bisq.core.dao.state.period.CycleService;
@ -35,7 +36,6 @@ import com.google.inject.Inject;
/**
* High level entry point for Dao domain.
* We initialize all main service classes here to be sure they are started.
*
*/
public class DaoSetup {
private final BsqStateService bsqStateService;
@ -47,6 +47,7 @@ public class DaoSetup {
private final VoteRevealService voteRevealService;
private final VoteResultService voteResultService;
private final BsqNode bsqNode;
private final ExportJsonFilesService exportJsonFilesService;
@Inject
public DaoSetup(BsqNodeProvider bsqNodeProvider,
@ -57,7 +58,8 @@ public class DaoSetup {
BlindVoteListService blindVoteListService,
MyBlindVoteListService myBlindVoteListService,
VoteRevealService voteRevealService,
VoteResultService voteResultService) {
VoteResultService voteResultService,
ExportJsonFilesService exportJsonFilesService) {
this.bsqStateService = bsqStateService;
this.cycleService = cycleService;
this.proposalService = proposalService;
@ -66,6 +68,7 @@ public class DaoSetup {
this.myBlindVoteListService = myBlindVoteListService;
this.voteRevealService = voteRevealService;
this.voteResultService = voteResultService;
this.exportJsonFilesService = exportJsonFilesService;
bsqNode = bsqNodeProvider.getBsqNode();
}
@ -81,6 +84,7 @@ public class DaoSetup {
myBlindVoteListService.addListeners();
voteRevealService.addListeners();
voteResultService.addListeners();
exportJsonFilesService.addListeners();
bsqStateService.start();
cycleService.start();
@ -90,6 +94,7 @@ public class DaoSetup {
myBlindVoteListService.start();
voteRevealService.start();
voteResultService.start();
exportJsonFilesService.start();
bsqNode.setErrorMessageHandler(errorMessageHandler);
bsqNode.start();

View File

@ -22,6 +22,7 @@ import bisq.core.dao.DaoSetupService;
import bisq.core.dao.governance.ballot.vote.Vote;
import bisq.core.dao.governance.proposal.ProposalService;
import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload;
import bisq.core.dao.state.period.PeriodService;
import bisq.common.proto.persistable.PersistedDataHost;
import bisq.common.storage.Storage;
@ -32,6 +33,7 @@ import javafx.collections.ListChangeListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@ -49,14 +51,16 @@ public class BallotListService implements PersistedDataHost, DaoSetupService {
}
private final ProposalService proposalService;
private final PeriodService periodService;
private final Storage<BallotList> storage;
private final BallotList ballotList = new BallotList();
private final List<BallotListChangeListener> listeners = new CopyOnWriteArrayList<>();
@Inject
public BallotListService(ProposalService proposalService, Storage<BallotList> storage) {
public BallotListService(ProposalService proposalService, PeriodService periodService, Storage<BallotList> storage) {
this.proposalService = proposalService;
this.periodService = periodService;
this.storage = storage;
}
@ -125,6 +129,12 @@ public class BallotListService implements PersistedDataHost, DaoSetupService {
return ballotList;
}
public List<Ballot> getBallotsOfCycle() {
return ballotList.stream()
.filter(ballot -> periodService.isTxInCorrectCycle(ballot.getTxId(), periodService.getChainHeight()))
.collect(Collectors.toList());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private

View File

@ -56,7 +56,7 @@ public class BlindVoteConsensus {
}
public static BallotList getSortedBallotList(BallotListService ballotListService) {
List<Ballot> ballotList = ballotListService.getBallotList().stream()
List<Ballot> ballotList = ballotListService.getBallotsOfCycle().stream()
.sorted(Comparator.comparing(Ballot::getTxId))
.collect(Collectors.toList());
log.info("Sorted ballotList: " + ballotList);

View File

@ -89,6 +89,11 @@ public class BlindVoteValidator {
public boolean isTxInPhaseAndCycle(BlindVote blindVote) {
String txId = blindVote.getTxId();
Optional<Tx> optionalTx = bsqStateService.getTx(txId);
if (!optionalTx.isPresent()) {
log.warn("Tx is not in bsqStateService. blindVoteTxId={}", txId);
return false;
}
int txHeight = optionalTx.get().getBlockHeight();
if (!periodService.isTxInCorrectCycle(txHeight, bsqStateService.getChainHeight())) {
log.debug("Tx is not in current cycle. blindVote={}", blindVote);

View File

@ -271,10 +271,11 @@ public class MyBlindVoteListService implements PersistedDataHost, BsqStateListen
// blindVoteTxId is null if we use the method from the getCurrentlyAvailableMerit call.
public MeritList getMerits(@Nullable String blindVoteTxId) {
// Create a lookup set for txIds of own comp. requests
// Create a lookup set for txIds of own comp. requests from past cycles (we ignore request form that cycle)
Set<String> myCompensationProposalTxIs = myProposalListService.getList().stream()
.filter(proposal -> proposal instanceof CompensationProposal)
.map(Proposal::getTxId)
.filter(txId -> periodService.isTxInPastCycle(txId, periodService.getChainHeight()))
.collect(Collectors.toSet());
return new MeritList(bsqStateService.getIssuanceSet().stream()

View File

@ -22,6 +22,7 @@ import bisq.core.dao.state.governance.Issuance;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.Utilities;
import io.bisq.generated.protobuffer.PB;
@ -63,4 +64,12 @@ public class Merit implements PersistablePayload, NetworkPayload, ConsensusCriti
public String getIssuanceTxId() {
return issuance.getTxId();
}
@Override
public String toString() {
return "Merit{" +
"\n issuance=" + issuance +
",\n signature=" + Utilities.bytesAsHexString(signature) +
"\n}";
}
}

View File

@ -84,7 +84,7 @@ public class MeritConsensus {
// We verify if signature of hash of blindVoteTxId is correct. EC key from first input for blind vote tx is
// used for signature.
if (pubKeyAsHex == null) {
log.error("Error at getMeritStake: pubKeyAsHex is null");
log.error("Error at isSignatureValid: pubKeyAsHex is null");
return false;
}
@ -110,16 +110,15 @@ public class MeritConsensus {
public static long getWeightedMeritAmount(long amount, int issuanceHeight, int blockHeight, int blocksPerYear) {
if (issuanceHeight > blockHeight)
throw new IllegalArgumentException("issuanceHeight must not be larger than blockHeight");
throw new IllegalArgumentException("issuanceHeight must not be larger than blockHeight. issuanceHeight=" + issuanceHeight + "; blockHeight=" + blockHeight);
if (blockHeight < 0)
throw new IllegalArgumentException("blockHeight must not be negative");
throw new IllegalArgumentException("blockHeight must not be negative. blockHeight=" + blockHeight);
if (amount < 0)
throw new IllegalArgumentException("amount must not be negative");
throw new IllegalArgumentException("amount must not be negative. amount" + amount);
if (blocksPerYear < 0)
throw new IllegalArgumentException("blocksPerYear must not be negative");
throw new IllegalArgumentException("blocksPerYear must not be negative. blocksPerYear=" + blocksPerYear);
if (issuanceHeight < 0)
throw new IllegalArgumentException("issuanceHeight must not be negative");
throw new IllegalArgumentException("issuanceHeight must not be negative. issuanceHeight=" + issuanceHeight);
// We use a linear function to apply a factor for the issuance amount of 1 if the issuance was recent and 0
// if the issuance was 2 years old or older.

View File

@ -131,7 +131,6 @@ public abstract class Proposal implements PersistablePayload, NetworkPayload, Co
public abstract Param getThresholdParam();
@Override
public String toString() {
return "Proposal{" +

View File

@ -28,7 +28,7 @@ public class EvaluatedProposal {
private final long requiredQuorum;
private final long requiredThreshold;
public EvaluatedProposal(boolean isAccepted, ProposalVoteResult proposalVoteResult, long requiredQuorum, long requiredThreshold) {
EvaluatedProposal(boolean isAccepted, ProposalVoteResult proposalVoteResult, long requiredQuorum, long requiredThreshold) {
this.isAccepted = isAccepted;
this.proposalVoteResult = proposalVoteResult;
this.requiredQuorum = requiredQuorum;

View File

@ -19,7 +19,7 @@ package bisq.core.dao.node.full;
import bisq.core.dao.node.BsqNode;
import bisq.core.dao.node.full.network.FullNodeNetworkService;
import bisq.core.dao.node.json.JsonBlockChainExporter;
import bisq.core.dao.node.json.ExportJsonFilesService;
import bisq.core.dao.node.parser.BlockParser;
import bisq.core.dao.node.parser.exceptions.BlockNotConnectingException;
import bisq.core.dao.state.BsqStateService;
@ -49,7 +49,7 @@ public class FullNode extends BsqNode {
private final RpcService rpcService;
private final FullNodeNetworkService fullNodeNetworkService;
private final JsonBlockChainExporter jsonBlockChainExporter;
private final ExportJsonFilesService exportJsonFilesService;
private boolean addBlockHandlerAdded;
@ -64,12 +64,12 @@ public class FullNode extends BsqNode {
SnapshotManager snapshotManager,
P2PService p2PService,
RpcService rpcService,
JsonBlockChainExporter jsonBlockChainExporter,
ExportJsonFilesService exportJsonFilesService,
FullNodeNetworkService fullNodeNetworkService) {
super(blockParser, bsqStateService, snapshotManager, p2PService);
this.rpcService = rpcService;
this.jsonBlockChainExporter = jsonBlockChainExporter;
this.exportJsonFilesService = exportJsonFilesService;
this.fullNodeNetworkService = fullNodeNetworkService;
}
@ -88,7 +88,7 @@ public class FullNode extends BsqNode {
}
public void shutDown() {
jsonBlockChainExporter.shutDown();
exportJsonFilesService.shutDown();
fullNodeNetworkService.shutDown();
}
@ -147,7 +147,7 @@ public class FullNode extends BsqNode {
}
private void onNewBlock(Block block) {
jsonBlockChainExporter.maybeExport();
exportJsonFilesService.exportToJson();
if (p2pNetworkReady && parseBlockchainComplete)
fullNodeNetworkService.publishNewBlock(block);

View File

@ -0,0 +1,235 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.node.json;
import bisq.core.dao.DaoOptionKeys;
import bisq.core.dao.DaoSetupService;
import bisq.core.dao.state.BsqState;
import bisq.core.dao.state.BsqStateService;
import bisq.core.dao.state.blockchain.PubKeyScript;
import bisq.core.dao.state.blockchain.TxOutput;
import bisq.core.dao.state.blockchain.TxType;
import bisq.common.storage.FileUtil;
import bisq.common.storage.JsonFileManager;
import bisq.common.storage.Storage;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Utils;
import com.google.inject.Inject;
import javax.inject.Named;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.nio.file.Paths;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
@Slf4j
public class ExportJsonFilesService implements DaoSetupService {
private final BsqStateService bsqStateService;
private final File storageDir;
private final boolean dumpBlockchainData;
private final ListeningExecutorService executor = Utilities.getListeningExecutorService("JsonExporter",
1, 1, 1200);
private JsonFileManager txFileManager, txOutputFileManager, bsqStateFileManager;
@Inject
public ExportJsonFilesService(BsqStateService bsqStateService,
@Named(Storage.STORAGE_DIR) File storageDir,
@Named(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
this.bsqStateService = bsqStateService;
this.storageDir = storageDir;
this.dumpBlockchainData = dumpBlockchainData;
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoSetupService
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void addListeners() {
}
@Override
public void start() {
if (dumpBlockchainData) {
File jsonDir = new File(Paths.get(storageDir.getAbsolutePath(), "json").toString());
File txDir = new File(Paths.get(storageDir.getAbsolutePath(), "json", "tx").toString());
File txOutputDir = new File(Paths.get(storageDir.getAbsolutePath(), "json", "txo").toString());
File bsqStateDir = new File(Paths.get(storageDir.getAbsolutePath(), "json", "all").toString());
try {
if (txDir.exists())
FileUtil.deleteDirectory(txDir);
if (txOutputDir.exists())
FileUtil.deleteDirectory(txOutputDir);
if (bsqStateDir.exists())
FileUtil.deleteDirectory(bsqStateDir);
if (jsonDir.exists())
FileUtil.deleteDirectory(jsonDir);
} catch (IOException e) {
log.error(e.toString());
e.printStackTrace();
}
if (!jsonDir.mkdir())
log.warn("make jsonDir failed.\njsonDir=" + jsonDir.getAbsolutePath());
if (!txDir.mkdir())
log.warn("make txDir failed.\ntxDir=" + txDir.getAbsolutePath());
if (!txOutputDir.mkdir())
log.warn("make txOutputDir failed.\ntxOutputDir=" + txOutputDir.getAbsolutePath());
if (!bsqStateDir.mkdir())
log.warn("make bsqStateDir failed.\nbsqStateDir=" + bsqStateDir.getAbsolutePath());
txFileManager = new JsonFileManager(txDir);
txOutputFileManager = new JsonFileManager(txOutputDir);
bsqStateFileManager = new JsonFileManager(bsqStateDir);
}
}
public void shutDown() {
if (dumpBlockchainData) {
txFileManager.shutDown();
txOutputFileManager.shutDown();
bsqStateFileManager.shutDown();
}
}
public void exportToJson() {
if (dumpBlockchainData) {
// We store the data we need once we write the data to disk (in the thread) locally.
// Access to bsqStateService is single threaded, we must not access bsqStateService from the thread.
List<JsonTxOutput> allJsonTxOutputs = new ArrayList<>();
List<JsonTx> jsonTxs = new ArrayList<>();
BsqState bsqStateClone = bsqStateService.getClone();
bsqStateService.getTxStream().forEach(tx -> {
List<JsonTxOutput> jsonTxOutputs = new ArrayList<>();
String txId = tx.getId();
long time = tx.getTime();
int blockHeight = tx.getBlockHeight();
long burntFee = bsqStateService.getBurntFee(tx.getId());
TxType txType = tx.getTxType();
JsonTxType jsonTxType = txType != null ? JsonTxType.valueOf(txType.name()) : null;
String jsonTxTypeDisplayString = jsonTxType != null ? jsonTxType.getDisplayString() : "";
tx.getTxOutputs().forEach(txOutput -> {
boolean isBsqTxOutputType = bsqStateService.isBsqTxOutputType(txOutput);
long bsqAmount = isBsqTxOutputType ? txOutput.getValue() : 0;
long btcAmount = !isBsqTxOutputType ? txOutput.getValue() : 0;
PubKeyScript pubKeyScript = txOutput.getPubKeyScript();
JsonScriptPubKey scriptPubKey = pubKeyScript != null ? new JsonScriptPubKey(pubKeyScript) : null;
JsonSpentInfo spentInfo = bsqStateService.getSpentInfo(txOutput).map(JsonSpentInfo::new).orElse(null);
JsonTxOutputType txOutputType = JsonTxOutputType.valueOf(txOutput.getTxOutputType().name());
int lockTime = txOutput.getLockTime();
String opReturn = txOutput.getOpReturnData() != null ? Utils.HEX.encode(txOutput.getOpReturnData()) : null;
JsonTxOutput jsonTxOutput = new JsonTxOutput(txId,
txOutput.getIndex(),
bsqAmount,
btcAmount,
blockHeight,
isBsqTxOutputType,
burntFee,
txOutput.getAddress(),
scriptPubKey,
spentInfo,
time,
jsonTxType,
jsonTxTypeDisplayString,
txOutputType,
txOutputType.getDisplayString(),
opReturn,
lockTime
);
jsonTxOutputs.add(jsonTxOutput);
allJsonTxOutputs.add(jsonTxOutput);
});
List<JsonTxInput> inputs = tx.getTxInputs().stream()
.map(txInput -> {
Optional<TxOutput> optionalTxOutput = bsqStateService.getConnectedTxOutput(txInput);
if (optionalTxOutput.isPresent()) {
TxOutput connectedTxOutput = optionalTxOutput.get();
boolean isBsqTxOutputType = bsqStateService.isBsqTxOutputType(connectedTxOutput);
return new JsonTxInput(txInput.getConnectedTxOutputIndex(),
txInput.getConnectedTxOutputTxId(),
connectedTxOutput.getValue(),
isBsqTxOutputType,
connectedTxOutput.getAddress(),
time);
} else {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
JsonTx jsonTx = new JsonTx(txId,
blockHeight,
tx.getBlockHash(),
time,
inputs,
jsonTxOutputs,
jsonTxType,
jsonTxTypeDisplayString,
burntFee,
tx.getUnlockBlockHeight());
jsonTxs.add(jsonTx);
});
ListenableFuture<Void> future = executor.submit(() -> {
bsqStateFileManager.writeToDisc(Utilities.objectToJson(bsqStateClone), "BsqStateService");
allJsonTxOutputs.forEach(jsonTxOutput -> txOutputFileManager.writeToDisc(Utilities.objectToJson(jsonTxOutput), jsonTxOutput.getId()));
jsonTxs.forEach(jsonTx -> txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), jsonTx.getId()));
return null;
});
Futures.addCallback(future, new FutureCallback<>() {
public void onSuccess(Void ignore) {
log.trace("onSuccess");
}
public void onFailure(@NotNull Throwable throwable) {
log.error(throwable.toString());
throwable.printStackTrace();
}
});
}
}
}

View File

@ -1,206 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.node.json;
import bisq.core.dao.DaoOptionKeys;
import bisq.core.dao.state.BsqState;
import bisq.core.dao.state.BsqStateService;
import bisq.core.dao.state.blockchain.PubKeyScript;
import bisq.core.dao.state.blockchain.SpentInfo;
import bisq.core.dao.state.blockchain.Tx;
import bisq.core.dao.state.blockchain.TxOutput;
import bisq.core.dao.state.blockchain.TxType;
import bisq.common.storage.FileUtil;
import bisq.common.storage.JsonFileManager;
import bisq.common.storage.Storage;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Utils;
import com.google.inject.Inject;
import javax.inject.Named;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.nio.file.Paths;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
@Slf4j
public class JsonBlockChainExporter {
private final BsqStateService bsqStateService;
private final boolean dumpBlockchainData;
private final ListeningExecutorService executor = Utilities.getListeningExecutorService("JsonExporter", 1, 1, 1200);
private JsonFileManager txFileManager, txOutputFileManager, jsonFileManager;
@Inject
public JsonBlockChainExporter(BsqStateService bsqStateService,
@Named(Storage.STORAGE_DIR) File storageDir,
@Named(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
this.bsqStateService = bsqStateService;
this.dumpBlockchainData = dumpBlockchainData;
init(storageDir, dumpBlockchainData);
}
private void init(@Named(Storage.STORAGE_DIR) File storageDir, @Named(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
if (dumpBlockchainData) {
File txDir = new File(Paths.get(storageDir.getAbsolutePath(), "tx").toString());
File txOutputDir = new File(Paths.get(storageDir.getAbsolutePath(), "txo").toString());
File blockchainDir = new File(Paths.get(storageDir.getAbsolutePath(), "all").toString());
try {
if (txDir.exists())
FileUtil.deleteDirectory(txDir);
if (txOutputDir.exists())
FileUtil.deleteDirectory(txOutputDir);
if (blockchainDir.exists())
FileUtil.deleteDirectory(blockchainDir);
} catch (IOException e) {
e.printStackTrace();
}
if (!txDir.mkdir())
log.warn("make txDir failed.\ntxDir=" + txDir.getAbsolutePath());
if (!txOutputDir.mkdir())
log.warn("make txOutputDir failed.\ntxOutputDir=" + txOutputDir.getAbsolutePath());
if (!blockchainDir.mkdir())
log.warn("make blockchainDir failed.\nblockchainDir=" + blockchainDir.getAbsolutePath());
txFileManager = new JsonFileManager(txDir);
txOutputFileManager = new JsonFileManager(txOutputDir);
jsonFileManager = new JsonFileManager(blockchainDir);
}
}
public void shutDown() {
if (dumpBlockchainData) {
txFileManager.shutDown();
txOutputFileManager.shutDown();
jsonFileManager.shutDown();
}
}
public void maybeExport() {
if (dumpBlockchainData) {
ListenableFuture<Void> future = executor.submit(() -> {
final BsqState bsqStateClone = bsqStateService.getClone();
Map<String, Tx> txMap = bsqStateService.getBlocksFromState(bsqStateClone).stream()
.filter(Objects::nonNull)
.flatMap(block -> block.getTxs().stream())
.collect(Collectors.toMap(Tx::getId, tx -> tx));
for (Tx tx : txMap.values()) {
String txId = tx.getId();
final Optional<TxType> optionalTxType = bsqStateService.getOptionalTxType(txId);
optionalTxType.ifPresent(txType1 -> {
JsonTxType txType = txType1 != TxType.UNDEFINED_TX_TYPE ?
JsonTxType.valueOf(txType1.name()) : null;
List<JsonTxOutput> outputs = new ArrayList<>();
tx.getTxOutputs().forEach(txOutput -> {
final Optional<SpentInfo> optionalSpentInfo = bsqStateService.getSpentInfo(txOutput);
final boolean isBsqOutput = bsqStateService.isBsqTxOutputType(txOutput);
final PubKeyScript pubKeyScript = txOutput.getPubKeyScript();
final JsonTxOutput outputForJson = new JsonTxOutput(txId,
txOutput.getIndex(),
isBsqOutput ? txOutput.getValue() : 0,
!isBsqOutput ? txOutput.getValue() : 0,
txOutput.getBlockHeight(),
isBsqOutput,
bsqStateService.getBurntFee(tx.getId()),
txOutput.getAddress(),
pubKeyScript != null ? new JsonScriptPubKey(pubKeyScript) : null,
optionalSpentInfo.map(JsonSpentInfo::new).orElse(null),
tx.getTime(),
txType,
txType != null ? txType.getDisplayString() : "",
txOutput.getOpReturnData() != null ? Utils.HEX.encode(txOutput.getOpReturnData()) : null
);
outputs.add(outputForJson);
txOutputFileManager.writeToDisc(Utilities.objectToJson(outputForJson), outputForJson.getId());
});
List<JsonTxInput> inputs = tx.getTxInputs().stream()
.map(txInput -> {
Optional<TxOutput> optionalTxOutput = bsqStateService.getConnectedTxOutput(txInput);
if (optionalTxOutput.isPresent()) {
final TxOutput connectedTxOutput = optionalTxOutput.get();
final boolean isBsqOutput = bsqStateService.isBsqTxOutputType(connectedTxOutput);
return new JsonTxInput(txInput.getConnectedTxOutputIndex(),
txInput.getConnectedTxOutputTxId(),
connectedTxOutput.getValue(),
isBsqOutput,
connectedTxOutput.getAddress(),
tx.getTime());
} else {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
final JsonTx jsonTx = new JsonTx(txId,
tx.getBlockHeight(),
tx.getBlockHash(),
tx.getTime(),
inputs,
outputs,
txType,
txType != null ? txType.getDisplayString() : "",
bsqStateService.getBurntFee(tx.getId()));
txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), txId);
});
}
jsonFileManager.writeToDisc(Utilities.objectToJson(bsqStateClone), "BsqStateService");
return null;
});
Futures.addCallback(future, new FutureCallback<Void>() {
public void onSuccess(Void ignore) {
log.trace("onSuccess");
}
public void onFailure(@NotNull Throwable throwable) {
log.error(throwable.toString());
throwable.printStackTrace();
}
});
}
}
}

View File

@ -23,9 +23,8 @@ import java.util.List;
import lombok.Value;
//TODO sync up with data model
@Value
public class JsonTx {
class JsonTx {
private final String txVersion = Version.BSQ_TX_VERSION;
private final String id;
private final int blockHeight;
@ -36,4 +35,6 @@ public class JsonTx {
private final JsonTxType txType;
private final String txTypeDisplayString;
private final long burntFee;
// If not set it is -1. LockTime of 0 is a valid value.
private final int unlockBlockHeight;
}

View File

@ -21,14 +21,13 @@ import lombok.Value;
import javax.annotation.concurrent.Immutable;
//TODO sync up with data model
@Value
@Immutable
public class JsonTxInput {
private final int spendingTxOutputIndex;
private final String spendingTxId;
class JsonTxInput {
private final int spendingTxOutputIndex; // connectedTxOutputIndex
private final String spendingTxId; // connectedTxOutputTxId
private final long bsqAmount;
private final boolean isVerified;
private final boolean isVerified; // isBsqTxOutputType
private final String address;
private final long time;
}

View File

@ -21,7 +21,8 @@ import bisq.common.app.Version;
import lombok.Value;
//TODO sync up with data model
import javax.annotation.Nullable;
@Value
public class JsonTxOutput {
private final String txVersion = Version.BSQ_TX_VERSION;
@ -30,15 +31,21 @@ public class JsonTxOutput {
private final long bsqAmount;
private final long btcAmount;
private final int height;
private final boolean isVerified;
private final boolean isVerified; // isBsqTxOutputType
private final long burntFee;
private final String address;
@Nullable
private final JsonScriptPubKey scriptPubKey;
@Nullable
private final JsonSpentInfo spentInfo;
private final long time;
private final JsonTxType txType;
private final String txTypeDisplayString;
private final JsonTxOutputType txOutputType; // new
private final String txOutputTypeDisplayString; // new
@Nullable
private final String opReturn;
private final int lockTime; // new
public String getId() {
return txId + ":" + outputIndex;

View File

@ -0,0 +1,47 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.node.json;
import lombok.Getter;
// Need to be in sync with TxOutputType
public enum JsonTxOutputType {
UNDEFINED("Undefined"),
GENESIS_OUTPUT("Genesis"),
BSQ_OUTPUT("BSQ"),
BTC_OUTPUT("BTC"),
PROPOSAL_OP_RETURN_OUTPUT("Proposal opReturn"),
COMP_REQ_OP_RETURN_OUTPUT("Compensation request opReturn"),
CONFISCATE_BOND_OP_RETURN_OUTPUT("Confiscate bond opReturn"),
ISSUANCE_CANDIDATE_OUTPUT("Issuance candidate"),
BLIND_VOTE_LOCK_STAKE_OUTPUT("Blind vote lock stake"),
BLIND_VOTE_OP_RETURN_OUTPUT("Blind vote opReturn"),
VOTE_REVEAL_UNLOCK_STAKE_OUTPUT("Vote reveal unlock stake"),
VOTE_REVEAL_OP_RETURN_OUTPUT("Vote reveal opReturn"),
LOCKUP("Lockup"),
LOCKUP_OP_RETURN_OUTPUT("Lockup opReturn"),
UNLOCK("Unlock"),
INVALID_OUTPUT("Invalid");
@Getter
private String displayString;
JsonTxOutputType(String displayString) {
this.displayString = displayString;
}
}

View File

@ -19,7 +19,7 @@ package bisq.core.dao.node.json;
import lombok.Getter;
//TODO sync up with data model
// Need to be in sync with TxOutputType
public enum JsonTxType {
UNDEFINED_TX_TYPE("Undefined"),
UNVERIFIED("Unverified"),
@ -27,13 +27,12 @@ public enum JsonTxType {
GENESIS("Genesis"),
TRANSFER_BSQ("Transfer BSQ"),
PAY_TRADE_FEE("Pay trade fee"),
PROPOSAL("Ballot"),
PROPOSAL("Proposal"),
COMPENSATION_REQUEST("Compensation request"),
VOTE("Vote"),
BLIND_VOTE("Blind vote"),
VOTE_REVEAL("Vote reveal"),
LOCK_UP("Lockup"),
UN_LOCK("Unlock");
LOCKUP("Lockup"),
UNLOCK("Unlock");
@Getter
private String displayString;

View File

@ -264,10 +264,10 @@ public class TxParser {
/**
* Whether the BSQ fee and phase is valid for a transaction.
*
* @param blockHeight The height of the block that the transaction is in.
* @param bsqFee The fee in BSQ, in satoshi.
* @param phase The current phase of the DAO, e.g {@code DaoPhase.Phase.PROPOSAL}.
* @param param The parameter for the fee, e.g {@code Param.PROPOSAL_FEE}.
* @param blockHeight The height of the block that the transaction is in.
* @param bsqFee The fee in BSQ, in satoshi.
* @param phase The current phase of the DAO, e.g {@code DaoPhase.Phase.PROPOSAL}.
* @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) {
@ -296,10 +296,10 @@ public class TxParser {
/**
* Retrieve the type of the transaction, assuming it is relevant to bisq.
*
* @param tx The temporary transaction.
* @param hasOpReturnCandidate True if we have a candidate for an OP_RETURN.
* @param remainingInputValue The remaining value of inputs not yet accounted for, in satoshi.
* @param optionalOpReturnType If present, the OP_RETURN type of the transaction.
* @param tx The temporary transaction.
* @param hasOpReturnCandidate True if we have a candidate for an OP_RETURN.
* @param remainingInputValue The remaining value of inputs not yet accounted for, in satoshi.
* @param optionalOpReturnType If present, the OP_RETURN type of the transaction.
* @return The type of the transaction, if it is relevant to bisq.
*/
@VisibleForTesting
@ -399,10 +399,10 @@ public class TxParser {
/**
* Parse and return the genesis transaction for bisq, if applicable.
*
* @param genesisTxId The transaction id of the bisq genesis transaction.
* @param genesisBlockHeight The block height of the bisq genesis transaction.
* @param genesisTotalSupply The total supply of the genesis issuance for bisq.
* @param rawTx The candidate transaction.
* @param genesisTxId The transaction id of the bisq genesis transaction.
* @param genesisBlockHeight The block height of the bisq genesis transaction.
* @param genesisTotalSupply The total supply of the genesis issuance for bisq.
* @param rawTx The candidate transaction.
* @return The genesis transaction if applicable, or Optional.empty() otherwise.
*/
public static Optional<TempTx> findGenesisTx(String genesisTxId, int genesisBlockHeight, Coin genesisTotalSupply,

View File

@ -132,10 +132,10 @@ public class BsqState implements PersistableEnvelope {
@Override
public Message toProtoMessage() {
return PB.PersistableEnvelope.newBuilder().setBsqState(getStateBuilder()).build();
return PB.PersistableEnvelope.newBuilder().setBsqState(getBsqStateBuilder()).build();
}
private PB.BsqState.Builder getStateBuilder() {
private PB.BsqState.Builder getBsqStateBuilder() {
final PB.BsqState.Builder builder = PB.BsqState.newBuilder();
builder.setChainHeight(chainHeight)
.addAllBlocks(blocks.stream().map(Block::toProtoMessage).collect(Collectors.toList()))
@ -193,10 +193,10 @@ public class BsqState implements PersistableEnvelope {
}
BsqState getClone() {
return (BsqState) BsqState.fromProto(getStateBuilder().build());
return (BsqState) BsqState.fromProto(getBsqStateBuilder().build());
}
BsqState getClone(BsqState snapshotCandidate) {
return (BsqState) BsqState.fromProto(snapshotCandidate.getStateBuilder().build());
return (BsqState) BsqState.fromProto(snapshotCandidate.getBsqStateBuilder().build());
}
}

View File

@ -120,10 +120,6 @@ public class BsqStateService implements DaoSetupService {
return bsqState.getClone();
}
public LinkedList<Block> getBlocksFromState(BsqState bsqState) {
return new LinkedList<>(bsqState.getBlocks());
}
///////////////////////////////////////////////////////////////////////////////////////////
// ChainHeight

View File

@ -29,6 +29,8 @@ import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.util.LinkedList;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@ -105,7 +107,7 @@ public class SnapshotManager implements BsqStateListener {
checkNotNull(storage, "storage must not be null");
BsqState persisted = storage.initAndGetPersisted(bsqState, 100);
if (persisted != null) {
log.info("applySnapshot persisted.chainHeadHeight=" + bsqStateService.getBlocksFromState(persisted).getLast().getHeight());
log.info("applySnapshot persisted.chainHeadHeight=" + new LinkedList<>(persisted.getBlocks()).getLast().getHeight());
if (persisted.getBlocks().getLast().getHeight() >= genesisTxInfo.getGenesisBlockHeight())
bsqStateService.applySnapshot(persisted);
} else {

View File

@ -75,4 +75,14 @@ public class Issuance implements PersistablePayload, NetworkPayload {
proto.getAmount(),
proto.getPubKey().isEmpty() ? null : proto.getPubKey());
}
@Override
public String toString() {
return "Issuance{" +
"\n txId='" + txId + '\'' +
",\n chainHeight=" + chainHeight +
",\n amount=" + amount +
",\n pubKey='" + pubKey + '\'' +
"\n}";
}
}

View File

@ -74,9 +74,9 @@ public enum Param {
PHASE_BREAK1(1), // 10 blocks
PHASE_BLIND_VOTE(2), // 4 days
PHASE_BREAK2(1), // 10 blocks
PHASE_VOTE_REVEAL(1), // 2 days
PHASE_VOTE_REVEAL(2), // 2 days
PHASE_BREAK3(1), // 10 blocks
PHASE_RESULT(1), // 1 block
PHASE_RESULT(2), // 1 block
PHASE_BREAK4(1); // 10 blocks
/*PHASE_UNDEFINED(0),

View File

@ -566,7 +566,9 @@ portfolio.pending.step3_buyer.warn.part2=The BTC seller still has not confirmed
portfolio.pending.step3_buyer.openForDispute=The BTC seller has not confirmed your payment!\nThe max. period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the arbitrator for opening a dispute.
# suppress inspection "TrailingSpacesInProperty"
portfolio.pending.step3_seller.part=Your trading partner has confirmed that he initiated the {0} payment.\n\n
portfolio.pending.step3_seller.altcoin={0}Please check on your favorite {1} blockchain explorer if the transaction to your receiving address\n\
portfolio.pending.step3_seller.altcoin.explorer=on your favorite {0} blockchain explorer
portfolio.pending.step3_seller.altcoin.wallet=at your {0} wallet
portfolio.pending.step3_seller.altcoin={0}Please check {1} if the transaction to your receiving address\n\
{2}\n\
has already sufficient blockchain confirmations.\nThe payment amount has to be {3}\n\n\
You can copy & paste your {4} address from the main screen after closing that popup.

View File

@ -309,8 +309,11 @@ public class SellerStep3View extends TradeStepView {
String id = trade.getShortId();
if (paymentAccountPayload instanceof CryptoCurrencyAccountPayload) {
String address = ((CryptoCurrencyAccountPayload) paymentAccountPayload).getAddress();
String explorerOrWalletString = trade.getOffer().getCurrencyCode().equals("XMR") ?
Res.get("portfolio.pending.step3_seller.altcoin.wallet", currencyName) :
Res.get("portfolio.pending.step3_seller.altcoin.explorer", currencyName);
//noinspection UnusedAssignment
message = Res.get("portfolio.pending.step3_seller.altcoin", part1, currencyName, address, tradeVolumeWithCode, currencyName);
message = Res.get("portfolio.pending.step3_seller.altcoin", part1, explorerOrWalletString, address, tradeVolumeWithCode, currencyName);
} else {
if (paymentAccountPayload instanceof USPostalMoneyOrderAccountPayload) {
message = Res.get("portfolio.pending.step3_seller.postal", part1, tradeVolumeWithCode, id);