Merge remote-tracking branch 'origin/DAO' into disk-protobuffer

This commit is contained in:
Mike Rosseel 2017-04-20 08:51:17 +02:00
commit 9e0056a276
34 changed files with 1094 additions and 651 deletions

View File

@ -0,0 +1,102 @@
/*
* 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 io.bisq.common.storage;
import io.bisq.common.UserThread;
import io.bisq.common.util.Utilities;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
public class JsonFileManager {
private final ThreadPoolExecutor executor = Utilities.getThreadPoolExecutor("saveToDiscExecutor", 5, 50, 60);
private File dir;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public JsonFileManager(File dir) {
this.dir = dir;
if (!dir.exists())
if (!dir.mkdir())
log.warn("make dir failed");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
UserThread.execute(JsonFileManager.this::shutDown);
}, "WriteOnlyFileManager.ShutDownHook"));
}
public void shutDown() {
executor.shutdown();
try {
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void writeToDisc(String json, String fileName) {
executor.execute(() -> {
File jsonFile = new File(Paths.get(dir.getAbsolutePath(), fileName + ".json").toString());
File tempFile = null;
PrintWriter printWriter = null;
try {
tempFile = File.createTempFile("temp", null, dir);
if (!executor.isShutdown() && !executor.isTerminated() && !executor.isTerminating())
tempFile.deleteOnExit();
printWriter = new PrintWriter(tempFile);
printWriter.println(json);
if (Utilities.isWindows()) {
// Work around an issue on Windows whereby you can't rename over existing files.
final File canonical = jsonFile.getCanonicalFile();
if (canonical.exists() && !canonical.delete()) {
throw new IOException("Failed to delete canonical file for replacement with save");
}
if (!tempFile.renameTo(canonical)) {
throw new IOException("Failed to rename " + tempFile + " to " + canonical);
}
} else if (!tempFile.renameTo(jsonFile)) {
throw new IOException("Failed to rename " + tempFile + " to " + jsonFile);
}
} catch (Throwable t) {
log.error("storageFile " + jsonFile.toString());
t.printStackTrace();
} finally {
if (tempFile != null && tempFile.exists()) {
log.warn("Temp file still exists after failed save. We will delete it now. storageFile=" + fileName);
if (!tempFile.delete())
log.error("Cannot delete temp file.");
}
if (printWriter != null)
printWriter.close();
}
});
}
}

View File

@ -0,0 +1,79 @@
package io.bisq.common.util;
import lombok.Getter;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
public class FunctionalReadWriteLock {
@Getter
private final Lock readLock;
@Getter
private final Lock writeLock;
public FunctionalReadWriteLock(boolean isFair) {
this(new ReentrantReadWriteLock(isFair));
}
public FunctionalReadWriteLock(ReadWriteLock lock) {
readLock = lock.readLock();
writeLock = lock.writeLock();
}
public <T> T read(Supplier<T> block) {
readLock.lock();
try {
return block.get();
} finally {
readLock.unlock();
}
}
public void read(Runnable block) {
readLock.lock();
try {
block.run();
} finally {
readLock.unlock();
}
}
public void write1(Callable block) throws Exception {
readLock.lock();
try {
block.call();
} finally {
readLock.unlock();
}
}
public <T> T write(Supplier<T> block) {
writeLock.lock();
try {
return block.get();
} finally {
writeLock.unlock();
}
}
public void write(Runnable block) {
writeLock.lock();
try {
block.run();
} finally {
writeLock.unlock();
}
}
public void write2(Callable block) throws Exception {
writeLock.lock();
try {
block.call();
} finally {
writeLock.unlock();
}
}
}

View File

@ -110,19 +110,19 @@ public class AppSetupWithP2P extends AppSetup {
@Override
public void onRequestingDataCompleted() {
log.info("p2pNetworkInitialized");
log.info("onRequestingDataCompleted");
p2pNetworkInitialized.set(true);
}
@Override
public void onNoSeedNodeAvailable() {
log.info("p2pNetworkInitialized");
log.info("onNoSeedNodeAvailable");
p2pNetworkInitialized.set(true);
}
@Override
public void onNoPeersAvailable() {
log.info("p2pNetworkInitialized");
log.info("onNoPeersAvailable");
p2pNetworkInitialized.set(true);
}

View File

@ -22,7 +22,7 @@ import io.bisq.common.app.AppModule;
import io.bisq.core.dao.blockchain.BsqBlockchainManager;
import io.bisq.core.dao.blockchain.BsqFullNode;
import io.bisq.core.dao.blockchain.BsqLiteNode;
import io.bisq.core.dao.blockchain.json.JsonExporter;
import io.bisq.core.dao.blockchain.json.JsonChainStateExporter;
import io.bisq.core.dao.blockchain.parse.*;
import io.bisq.core.dao.compensation.CompensationRequestManager;
import io.bisq.core.dao.compensation.CompensationRequestModel;
@ -60,7 +60,7 @@ public class DaoModule extends AppModule {
bind(VotingVerification.class).in(Singleton.class);
bind(IssuanceVerification.class).in(Singleton.class);
bind(JsonExporter.class).in(Singleton.class);
bind(JsonChainStateExporter.class).in(Singleton.class);
bind(DaoPeriodService.class).in(Singleton.class);
bind(VotingService.class).in(Singleton.class);

View File

@ -21,11 +21,12 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.Inject;
import io.bisq.common.UserThread;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.common.network.Msg;
import io.bisq.common.util.Utilities;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.json.JsonExporter;
import io.bisq.core.dao.blockchain.json.JsonChainStateExporter;
import io.bisq.core.dao.blockchain.p2p.GetBsqBlocksRequest;
import io.bisq.core.dao.blockchain.p2p.GetBsqBlocksResponse;
import io.bisq.core.dao.blockchain.p2p.NewBsqBlockBroadcastMsg;
@ -46,7 +47,7 @@ import org.jetbrains.annotations.NotNull;
public class BsqFullNode extends BsqNode {
private BsqFullNodeExecutor bsqFullNodeExecutor;
private final JsonExporter jsonExporter;
private final JsonChainStateExporter jsonChainStateExporter;
@Getter
private boolean parseBlockchainComplete;
@ -61,14 +62,14 @@ public class BsqFullNode extends BsqNode {
BsqParser bsqParser,
BsqFullNodeExecutor bsqFullNodeExecutor,
BsqChainState bsqChainState,
JsonExporter jsonExporter,
JsonChainStateExporter jsonChainStateExporter,
FeeService feeService) {
super(p2PService,
bsqParser,
bsqChainState,
feeService);
this.bsqFullNodeExecutor = bsqFullNodeExecutor;
this.jsonExporter = jsonExporter;
this.jsonChainStateExporter = jsonChainStateExporter;
}
@ -98,7 +99,9 @@ public class BsqFullNode extends BsqNode {
@Override
protected void parseBlocks(int startBlockHeight, int genesisBlockHeight, String genesisTxId, Integer chainHeadHeight) {
log.info("parseBlocks with from={} with chainHeadHeight={}", startBlockHeight, chainHeadHeight);
if (chainHeadHeight != startBlockHeight) {
if (startBlockHeight <= chainHeadHeight) {
bsqFullNodeExecutor.parseBlocks(startBlockHeight,
chainHeadHeight,
genesisBlockHeight,
@ -121,6 +124,12 @@ public class BsqFullNode extends BsqNode {
throwable.printStackTrace();
}
});
} else {
log.warn("We are trying to start with a block which is above the chain height of bitcoin core. We need probably wait longer until bitcoin core has fully synced. We try again after a delay of 1 min.");
UserThread.runAfter(() -> {
parseBlocksWithChainHeadHeight(startBlockHeight, genesisBlockHeight, genesisTxId);
}, 60);
}
} else {
// We dont have received new blocks in the meantime so we are completed and we register our handler
onParseBlockchainComplete(genesisBlockHeight, genesisTxId);
@ -178,7 +187,7 @@ public class BsqFullNode extends BsqNode {
@Override
protected void onNewBsqBlock(BsqBlock bsqBlock) {
super.onNewBsqBlock(bsqBlock);
jsonExporter.maybeExport();
jsonChainStateExporter.maybeExport();
if (parseBlockchainComplete && p2pNetworkReady)
publishNewBlock(bsqBlock);
}

View File

@ -33,14 +33,14 @@ public class PubKeyScript implements Serializable {
@JsonExclude
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
private final Integer reqSigs;
private final int reqSigs;
private final ScriptTypes type;
private final ImmutableList<String> addresses;
private final String asm;
private final String hex;
public PubKeyScript(com.neemre.btcdcli4j.core.domain.PubKeyScript scriptPubKey) {
this(scriptPubKey.getReqSigs(),
this(scriptPubKey.getReqSigs() != null ? scriptPubKey.getReqSigs() : 0,
ScriptTypes.forName(scriptPubKey.getType().getName()),
scriptPubKey.getAddresses() != null ? ImmutableList.copyOf(scriptPubKey.getAddresses()) : null,
scriptPubKey.getAsm(),

View File

@ -0,0 +1,153 @@
/*
* 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 io.bisq.core.dao.blockchain.json;
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 com.google.inject.Inject;
import io.bisq.common.storage.JsonFileManager;
import io.bisq.common.storage.Storage;
import io.bisq.common.util.Utilities;
import io.bisq.core.dao.DaoOptionKeys;
import io.bisq.core.dao.blockchain.parse.BsqChainState;
import io.bisq.core.dao.blockchain.vo.Tx;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Utils;
import org.jetbrains.annotations.NotNull;
import javax.inject.Named;
import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
public class JsonChainStateExporter {
private final boolean dumpBlockchainData;
private final BsqChainState bsqChainState;
private final ListeningExecutorService executor = Utilities.getListeningExecutorService("JsonExporter", 1, 1, 1200);
private File txDir, txOutputDir, bsqChainStateDir;
private JsonFileManager txFileManager, txOutputFileManager, bsqChainStateFileManager;
@Inject
public JsonChainStateExporter(BsqChainState bsqChainState,
@Named(Storage.STORAGE_DIR) File storageDir,
@Named(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
this.bsqChainState = bsqChainState;
this.dumpBlockchainData = dumpBlockchainData;
if (dumpBlockchainData) {
txDir = new File(Paths.get(storageDir.getAbsolutePath(), "tx").toString());
if (!txDir.exists())
if (!txDir.mkdir())
log.warn("make txDir failed.\ntxDir=" + txDir.getAbsolutePath());
txOutputDir = new File(Paths.get(storageDir.getAbsolutePath(), "txo").toString());
if (!txOutputDir.exists())
if (!txOutputDir.mkdir())
log.warn("make txOutputDir failed.\ntxOutputDir=" + txOutputDir.getAbsolutePath());
bsqChainStateDir = new File(Paths.get(storageDir.getAbsolutePath(), "all").toString());
if (!bsqChainStateDir.exists())
if (!bsqChainStateDir.mkdir())
log.warn("make bsqChainStateDir failed.\nbsqChainStateDir=" + bsqChainStateDir.getAbsolutePath());
txFileManager = new JsonFileManager(txDir);
txOutputFileManager = new JsonFileManager(txOutputDir);
bsqChainStateFileManager = new JsonFileManager(bsqChainStateDir);
}
}
public void shutDown() {
if (dumpBlockchainData) {
txFileManager.shutDown();
txOutputFileManager.shutDown();
bsqChainStateFileManager.shutDown();
}
}
public void maybeExport() {
if (dumpBlockchainData) {
ListenableFuture<Void> future = executor.submit(() -> {
final BsqChainState bsqChainStateClone = bsqChainState.getClone();
for (Tx tx : bsqChainStateClone.getTxMap().values()) {
String txId = tx.getId();
JsonTxType txType = tx.getTxType() != null ? JsonTxType.valueOf(tx.getTxType().name()) : null;
List<JsonTxOutput> outputs = new ArrayList<>();
tx.getOutputs().stream().forEach(txOutput -> {
final JsonTxOutput outputForJson = new JsonTxOutput(txId,
txOutput.getIndex(),
txOutput.isVerified() ? txOutput.getValue() : 0,
!txOutput.isVerified() ? txOutput.getValue() : 0,
txOutput.getBlockHeight(),
txOutput.isVerified(),
tx.getBurntFee(),
txOutput.getAddress(),
new JsonScriptPubKey(txOutput.getPubKeyScript()),
txOutput.getSpentInfo() != null ?
new JsonSpentInfo(txOutput.getSpentInfo()) : null,
txOutput.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.getInputs().stream()
.map(txInput -> new JsonTxInput(txInput.getSpendingTxOutputIndex(),
txInput.getSpendingTxId(),
txInput.getBsqValue(),
txInput.isVerified()))
.collect(Collectors.toList());
final JsonTx jsonTx = new JsonTx(txId,
tx.getBlockHeight(),
tx.getBlockHash(),
inputs,
outputs,
tx.isVerified(),
txType,
txType != null ? txType.getDisplayString() : "",
tx.getBurntFee());
txFileManager.writeToDisc(Utilities.objectToJson(jsonTx), txId);
}
bsqChainStateFileManager.writeToDisc(Utilities.objectToJson(bsqChainStateClone), "bsqChainState");
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

@ -1,152 +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 io.bisq.core.dao.blockchain.json;
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 com.google.inject.Inject;
import io.bisq.common.storage.PlainTextWrapper;
import io.bisq.common.storage.Storage;
import io.bisq.common.util.Utilities;
import io.bisq.core.dao.DaoOptionKeys;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import io.bisq.core.dao.blockchain.parse.BsqChainState;
import io.bisq.core.dao.blockchain.vo.SpentInfo;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import javax.inject.Named;
import java.io.File;
@Slf4j
public class JsonExporter {
private final Storage<PlainTextWrapper> jsonStorage;
private final boolean dumpBlockchainData;
private final File storageDir;
private final ListeningExecutorService executor = Utilities.getListeningExecutorService("JsonExporter", 1, 1, 1200);
private BsqChainState bsqChainState;
@Inject
public JsonExporter(Storage<PlainTextWrapper> jsonStorage,
BsqChainState bsqChainState,
@Named(Storage.STORAGE_DIR) File storageDir,
@Named(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
this.bsqChainState = bsqChainState;
this.storageDir = storageDir;
this.jsonStorage = jsonStorage;
this.dumpBlockchainData = dumpBlockchainData;
if (dumpBlockchainData)
this.jsonStorage.initWithFileName("bsqChainState.json");
}
public void maybeExport() {
if (dumpBlockchainData) {
/* List<TxOutputForJson> list = bsqChainState.getVerifiedTxOutputSet().stream()
.map(this::getTxOutputForJson)
.collect(Collectors.toList());
list.sort((o1, o2) -> (o1.getSortData().compareTo(o2.getSortData())));
TxOutputForJson[] array = new TxOutputForJson[list.size()];
list.toArray(array);*/
//jsonStorage.queueUpForSave(new PlainTextWrapper(Utilities.objectToJson(array)), 5000);
ListenableFuture<Void> future = executor.submit(() -> {
jsonStorage.queueUpForSave(new PlainTextWrapper(Utilities.objectToJson(bsqChainState.getClone())), 5000);
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();
}
});
// keep the individual file storage option as code as we dont know yet what we will use.
/* log.error("txOutputForJson " + txOutputForJson);
File txoDir = new File(Paths.get(storageDir.getAbsolutePath(), "txo").toString());
if (!txoDir.exists())
if (!txoDir.mkdir())
log.warn("make txoDir failed.\ntxoDir=" + txoDir.getAbsolutePath());
File txoFile = new File(Paths.get(txoDir.getAbsolutePath(),
txOutput.getTxId() + ":" + outputIndex + ".json").toString());
// Nr of write requests might be a bit heavy, consider write whole list to one file
FileManager<PlainTextWrapper> fileManager = new FileManager<>(storageDir, txoFile, 1);
fileManager.saveLater(new PlainTextWrapper(Utilities.objectToJson(txOutputForJson)));*/
}
}
private TxOutputForJson getTxOutputForJson(TxOutput txOutput) {
String txId = txOutput.getTxId();
int outputIndex = txOutput.getIndex();
final long bsqAmount = txOutput.getValue();
final int height = txOutput.getBlockHeight();
/* final boolean isBsqCoinBase = txOutput.isIssuanceOutput();
final boolean verified = txOutput.isVerified();
final long burnedFee = txOutput.getBurnedFee();
final long btcTxFee = txOutput.getBtcTxFee();*/
// TODO
final boolean isBsqCoinBase = false;
final boolean verified = true;
final long burnedFee = 0;
final long btcTxFee = 0;
PubKeyScript pubKeyScript = txOutput.getPubKeyScript();
final ScriptPubKeyForJson scriptPubKey = new ScriptPubKeyForJson(pubKeyScript.getAddresses(),
pubKeyScript.getAsm(),
pubKeyScript.getHex(),
pubKeyScript.getReqSigs(),
pubKeyScript.getType().toString());
SpentInfoForJson spentInfoJson = null;
// SpentInfo spentInfo = txOutput.getSpentInfo();
SpentInfo spentInfo = null;
if (spentInfo != null)
spentInfoJson = new SpentInfoForJson(spentInfo.getBlockHeight(),
spentInfo.getInputIndex(),
spentInfo.getTxId());
final long time = txOutput.getTime();
final String txVersion = "";//txOutput.getTxVersion();
return new TxOutputForJson(txId,
outputIndex,
bsqAmount,
height,
isBsqCoinBase,
verified,
burnedFee,
btcTxFee,
scriptPubKey,
spentInfoJson,
time,
txVersion
);
}
}

View File

@ -17,15 +17,26 @@
package io.bisq.core.dao.blockchain.json;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@Value
public class ScriptPubKeyForJson {
public class JsonScriptPubKey {
private final List<String> addresses;
private final String asm;
private final String hex;
private final int reqSigs;
private final String type;
public JsonScriptPubKey(PubKeyScript pubKeyScript) {
addresses = pubKeyScript.getAddresses();
asm = pubKeyScript.getAsm();
hex = pubKeyScript.getHex();
reqSigs = pubKeyScript.getReqSigs();
type = pubKeyScript.getType().toString();
}
}

View File

@ -17,11 +17,18 @@
package io.bisq.core.dao.blockchain.json;
import io.bisq.core.dao.blockchain.vo.SpentInfo;
import lombok.Value;
@Value
public class SpentInfoForJson {
public class JsonSpentInfo {
private final long height;
private final int inputIndex;
private final String txId;
public JsonSpentInfo(SpentInfo spentInfo) {
height = spentInfo.getBlockHeight();
inputIndex = spentInfo.getInputIndex();
txId = spentInfo.getTxId();
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 io.bisq.core.dao.blockchain.json;
import io.bisq.common.app.Version;
import lombok.Value;
import java.util.List;
@Value
public class JsonTx {
private final String txVersion = Version.BSQ_TX_VERSION;
private final String id;
private final int blockHeight;
private final String blockHash;
private final List<JsonTxInput> inputs;
private final List<JsonTxOutput> outputs;
private final boolean isVerified;
private final JsonTxType txType;
private final String txTypeDisplayString;
private final long burntFee;
}

View File

@ -0,0 +1,31 @@
/*
* 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 io.bisq.core.dao.blockchain.json;
import lombok.Value;
import javax.annotation.concurrent.Immutable;
@Value
@Immutable
public class JsonTxInput {
private final int spendingTxOutputIndex;
private final String spendingTxId;
private final long bsqAmount;
private final boolean isVerified;
}

View File

@ -17,25 +17,28 @@
package io.bisq.core.dao.blockchain.json;
import io.bisq.common.app.Version;
import lombok.Value;
@Value
public class TxOutputForJson {
public class JsonTxOutput {
private final String txVersion = Version.BSQ_TX_VERSION;
private final String txId;
private final int outputIndex;
private final long bsqAmount;
private final long btcAmount;
private final int height;
private final boolean isBsqCoinBase;
private final boolean isVerified;
private final long burnedFee;
private final long btcTxFee;
private final ScriptPubKeyForJson scriptPubKey;
private final SpentInfoForJson spentInfo;
private final long burntFee;
private final String address;
private final JsonScriptPubKey scriptPubKey;
private final JsonSpentInfo spentInfo;
private final long time;
private final String txVersion;
private final JsonTxType txType;
private final String txTypeDisplayString;
private final String opReturn;
public String getSortData() {
return height + txId + outputIndex;
public String getId() {
return txId + ":" + outputIndex;
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 io.bisq.core.dao.blockchain.json;
import lombok.Getter;
public enum JsonTxType {
UNDEFINED("Undefined"),
GENESIS("Genesis"),
SEND_BSQ("Send BSQ"),
PAY_TRADE_FEE("Pay trade fee"),
COMPENSATION_REQUEST("Compensation request"),
VOTE("Vote"),
ISSUANCE("Issuance");
@Getter
private String displayString;
JsonTxType(String displayString) {
this.displayString = displayString;
}
}

View File

@ -22,20 +22,25 @@ import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import io.bisq.common.proto.PersistenceProtoResolver;
import io.bisq.common.storage.Storage;
import io.bisq.common.util.FunctionalReadWriteLock;
import io.bisq.common.util.Tuple2;
import io.bisq.common.util.Utilities;
import io.bisq.core.app.BisqEnvironment;
import io.bisq.core.btc.BitcoinNetwork;
import io.bisq.core.dao.DaoOptionKeys;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.vo.*;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxIdIndexTuple;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import lombok.extern.slf4j.Slf4j;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
@ -81,8 +86,10 @@ public class BsqChainState implements Persistable {
private static final String REG_TEST_GENESIS_TX_ID = "3bc7bc9484e112ec8ddd1a1c984379819245ac463b9ce40fa8b5bf771c0f9236";
private static final int REG_TEST_GENESIS_BLOCK_HEIGHT = 102;
// TEST NET
private static final String TEST_NET_GENESIS_TX_ID = "";
private static final int TEST_NET_GENESIS_BLOCK_HEIGHT = 0;
// https://testnet.blockexplorer.com/block/00000000f1cd94c6ccc458a922f2a42c975c3447180f0db1e56322a26ab3f0ec
private static final String TEST_NET_GENESIS_TX_ID = "8853756990acfc1784aac1ee1a50d331c915a46876bb4ad98f260ef2d35da845";
private static final int TEST_NET_GENESIS_BLOCK_HEIGHT = 327626; //Mar 16, 2015
// block 376078 has 2843 recursions and caused once a StackOverflowError, a second run worked. Took 1,2 sec.
///////////////////////////////////////////////////////////////////////////////////////////
@ -92,15 +99,9 @@ public class BsqChainState implements Persistable {
// Persisted data
private final LinkedList<BsqBlock> blocks = new LinkedList<>();
private final Map<String, Tx> txMap = new HashMap<>();
private final Set<TxOutput> unspentTxOutputSet = new HashSet<>();
private final Map<TxIdIndexTuple, SpentInfo> spentInfoByTxOutputMap = new HashMap<>();
private final Map<String, Long> burnedFeeByTxIdMap = new HashMap<>();
private final Map<TxIdIndexTuple, TxOutput> unspentTxOutputsMap = new HashMap<>();
private final Set<Tuple2<Long, Integer>> compensationRequestFees = new HashSet<>();
private final Set<Tuple2<Long, Integer>> votingFees = new HashSet<>();
private final Set<TxOutput> compensationRequestOpReturnTxOutputs = new HashSet<>();
private final Set<String> compensationRequestBtcAddresses = new HashSet<>();
private final Set<TxOutput> votingTxOutputs = new HashSet<>();
private final Map<String, Set<TxOutput>> issuanceBtcTxOutputsByBtcAddressMap = new HashMap<>();
private final String genesisTxId;
private final int genesisBlockHeight;
@ -111,8 +112,7 @@ public class BsqChainState implements Persistable {
transient private final boolean dumpBlockchainData;
transient private final Storage<BsqChainState> snapshotBsqChainStateStorage;
transient private BsqChainState snapshotCandidate;
transient private final ReentrantReadWriteLock.WriteLock writeLock;
transient private final ReentrantReadWriteLock.ReadLock readLock;
transient private FunctionalReadWriteLock lock;
///////////////////////////////////////////////////////////////////////////////////////////
@ -140,63 +140,50 @@ public class BsqChainState implements Persistable {
genesisBlockHeight = TEST_NET_GENESIS_BLOCK_HEIGHT;
}
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
readLock = lock.readLock();
writeLock = lock.writeLock();
lock = new FunctionalReadWriteLock(true);
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
lock = new FunctionalReadWriteLock(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public write access
///////////////////////////////////////////////////////////////////////////////////////////
public void applySnapshot() {
try {
writeLock.lock();
lock.write(() -> {
BsqChainState snapshot = snapshotBsqChainStateStorage.initAndGetPersistedWithFileName("BsqChainState");
blocks.clear();
txMap.clear();
unspentTxOutputSet.clear();
spentInfoByTxOutputMap.clear();
burnedFeeByTxIdMap.clear();
unspentTxOutputsMap.clear();
chainHeadHeight = 0;
genesisTx = null;
if (snapshot != null) {
blocks.addAll(snapshot.blocks);
txMap.putAll(snapshot.txMap);
unspentTxOutputSet.addAll(snapshot.unspentTxOutputSet);
spentInfoByTxOutputMap.putAll(snapshot.spentInfoByTxOutputMap);
burnedFeeByTxIdMap.putAll(snapshot.burnedFeeByTxIdMap);
unspentTxOutputsMap.putAll(snapshot.unspentTxOutputsMap);
chainHeadHeight = snapshot.chainHeadHeight;
genesisTx = snapshot.genesisTx;
}
printDetails();
} finally {
writeLock.unlock();
}
});
}
public void setCreateCompensationRequestFee(long fee, int blockHeight) {
try {
writeLock.lock();
lock.write(() -> {
compensationRequestFees.add(new Tuple2<>(fee, blockHeight));
} finally {
writeLock.unlock();
}
});
}
public void setVotingFee(long fee, int blockHeight) {
try {
writeLock.lock();
lock.write(() -> {
votingFees.add(new Tuple2<>(fee, blockHeight));
} finally {
writeLock.unlock();
}
});
}
@ -206,13 +193,12 @@ public class BsqChainState implements Persistable {
void addBlock(BsqBlock block) throws BlockNotConnectingException {
try {
writeLock.lock();
lock.write2(() -> {
if (!blocks.contains(block)) {
if (blocks.isEmpty() || (blocks.getLast().getHash().equals(block.getPreviousBlockHash()) &&
blocks.getLast().getHeight() + 1 == block.getHeight())) {
blocks.add(block);
block.getTxs().stream().forEach(this::addTx);
block.getTxs().stream().forEach(BsqChainState.this::addTxToMap);
chainHeadHeight = block.getHeight();
maybeMakeSnapshot();
//printDetails();
@ -225,107 +211,40 @@ public class BsqChainState implements Persistable {
} else {
log.trace("We got that block already");
}
} finally {
writeLock.unlock();
return null;
});
} catch (Exception e) {
throw new BlockNotConnectingException(block);
} catch (Throwable e) {
log.error(e.toString());
e.printStackTrace();
throw e;
}
}
void addTx(Tx tx) {
try {
writeLock.lock();
void addTxToMap(Tx tx) {
lock.write(() -> {
txMap.put(tx.getId(), tx);
} finally {
writeLock.unlock();
}
}
void addSpentTxWithSpentInfo(TxOutput spentTxOutput, SpentInfo spentInfo) {
// we only use spentInfoByTxOutputMap for json export
if (dumpBlockchainData) {
try {
writeLock.lock();
spentInfoByTxOutputMap.put(spentTxOutput.getTxIdIndexTuple(), spentInfo);
} finally {
writeLock.unlock();
}
}
}
void setGenesisTx(Tx tx) {
try {
writeLock.lock();
genesisTx = tx;
} finally {
writeLock.unlock();
}
});
}
void addUnspentTxOutput(TxOutput txOutput) {
try {
writeLock.lock();
unspentTxOutputSet.add(txOutput);
} finally {
writeLock.unlock();
}
lock.write(() -> {
checkArgument(txOutput.isVerified(), "txOutput must be verified at addUnspentTxOutput");
unspentTxOutputsMap.put(txOutput.getTxIdIndexTuple(), txOutput);
});
}
void removeUnspentTxOutput(TxOutput txOutput) {
try {
writeLock.lock();
unspentTxOutputSet.remove(txOutput);
} finally {
writeLock.unlock();
}
lock.write(() -> {
unspentTxOutputsMap.remove(txOutput.getTxIdIndexTuple());
});
}
void addBurnedFee(String txId, long burnedFee) {
try {
writeLock.lock();
burnedFeeByTxIdMap.put(txId, burnedFee);
} finally {
writeLock.unlock();
}
}
void addCompensationRequestOpReturnOutput(TxOutput opReturnTxOutput) {
try {
writeLock.lock();
compensationRequestOpReturnTxOutputs.add(opReturnTxOutput);
} finally {
writeLock.unlock();
}
}
void adCompensationRequestBtcTxOutputs(String btcAddress) {
try {
writeLock.lock();
compensationRequestBtcAddresses.add(btcAddress);
} finally {
writeLock.unlock();
}
}
void addVotingOpReturnOutput(TxOutput opReturnTxOutput) {
try {
writeLock.lock();
votingTxOutputs.add(opReturnTxOutput);
} finally {
writeLock.unlock();
}
}
void addIssuanceBtcTxOutput(TxOutput btcTxOutput) {
try {
writeLock.lock();
if (!issuanceBtcTxOutputsByBtcAddressMap.containsKey(btcTxOutput.getAddress()))
issuanceBtcTxOutputsByBtcAddressMap.put(btcTxOutput.getAddress(), new HashSet<>());
issuanceBtcTxOutputsByBtcAddressMap.get(btcTxOutput.getAddress()).add(btcTxOutput);
} finally {
writeLock.unlock();
}
void setGenesisTx(Tx tx) {
lock.write(() -> {
genesisTx = tx;
});
}
@ -334,89 +253,75 @@ public class BsqChainState implements Persistable {
///////////////////////////////////////////////////////////////////////////////////////////
public String getGenesisTxId() {
try {
readLock.lock();
return genesisTxId;
} finally {
readLock.unlock();
}
}
public int getGenesisBlockHeight() {
try {
readLock.lock();
return lock.read(() -> {
return genesisBlockHeight;
} finally {
readLock.unlock();
}
});
}
public BsqChainState getClone() {
final byte[] serialize;
try {
readLock.lock();
serialize = Utilities.serialize(this);
} finally {
readLock.unlock();
}
return lock.read(() -> {
final byte[] serialize = Utilities.serialize(this);
return Utilities.<BsqChainState>deserialize(serialize);
});
}
public boolean containsBlock(BsqBlock bsqBlock) {
try {
readLock.lock();
return lock.read(() -> {
return blocks.contains(bsqBlock);
} finally {
readLock.unlock();
});
}
Optional<TxOutput> getUnspentTxOutput(TxIdIndexTuple txIdIndexTuple) {
return lock.read(() -> {
return unspentTxOutputsMap.entrySet().stream()
.filter(e -> e.getKey().equals(txIdIndexTuple))
.map(Map.Entry::getValue).findAny();
});
}
public boolean isTxOutputSpendable(String txId, int index) {
try {
readLock.lock();
return lock.read(() -> {
return getSpendableTxOutput(txId, index).isPresent();
} finally {
readLock.unlock();
}
});
}
public byte[] getSerializedBlocksFrom(int fromBlockHeight) {
try {
readLock.lock();
return lock.read(() -> {
List<BsqBlock> filtered = blocks.stream()
.filter(block -> block.getHeight() >= fromBlockHeight)
.collect(Collectors.toList());
filtered.stream().forEach(BsqBlock::reset);
return Utilities.<ArrayList<BsqBlock>>serialize(new ArrayList<>(filtered));
} finally {
readLock.unlock();
}
});
}
public boolean hasTxBurnedFee(String txId) {
try {
readLock.lock();
return burnedFeeByTxIdMap.containsKey(txId) && burnedFeeByTxIdMap.get(txId) > 0;
} finally {
readLock.unlock();
}
public boolean hasTxBurntFee(String txId) {
return lock.read(() -> {
return getTx(txId).map(Tx::getBurntFee).filter(fee -> fee > 0).isPresent();
});
}
public boolean containsTx(String txId) {
try {
readLock.lock();
return lock.read(() -> {
return getTx(txId).isPresent();
} finally {
readLock.unlock();
}
});
}
public int getChainHeadHeight() {
try {
readLock.lock();
return lock.read(() -> {
return chainHeadHeight;
} finally {
readLock.unlock();
});
}
// Only used for Json Exporter
public Map<String, Tx> getTxMap() {
return lock.read(() -> {
return txMap;
});
}
@ -425,24 +330,20 @@ public class BsqChainState implements Persistable {
///////////////////////////////////////////////////////////////////////////////////////////
Optional<TxOutput> getSpendableTxOutput(String txId, int index) {
try {
readLock.lock();
final Optional<TxOutput> spendingTxOutputOptional = getTx(txId).flatMap(e -> e.getTxOutput(index));
if (spendingTxOutputOptional.isPresent() &&
unspentTxOutputSet.contains(spendingTxOutputOptional.get()) &&
isTxOutputMature(spendingTxOutputOptional.get())) {
return spendingTxOutputOptional;
} else {
return Optional.<TxOutput>empty();
}
} finally {
readLock.unlock();
return lock.read(() -> {
return getSpendableTxOutput(new TxIdIndexTuple(txId, index));
});
}
Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
return lock.read(() -> {
return getUnspentTxOutput(txIdIndexTuple)
.filter(this::isTxOutputMature);
});
}
long getCreateCompensationRequestFee(int blockHeight) {
try {
readLock.lock();
return lock.read(() -> {
long fee = -1;
for (Tuple2<Long, Integer> feeAtHeight : compensationRequestFees) {
if (feeAtHeight.second <= blockHeight)
@ -450,25 +351,19 @@ public class BsqChainState implements Persistable {
}
checkArgument(fee > -1, "compensationRequestFees must be set");
return fee;
} finally {
readLock.unlock();
}
});
}
//TODO not impl yet
boolean isCompensationRequestPeriodValid(int blockHeight) {
try {
readLock.lock();
return lock.read(() -> {
return true;
} finally {
readLock.unlock();
}
});
}
long getVotingFee(int blockHeight) {
try {
readLock.lock();
return lock.read(() -> {
long fee = -1;
for (Tuple2<Long, Integer> feeAtHeight : votingFees) {
if (feeAtHeight.second <= blockHeight)
@ -476,37 +371,41 @@ public class BsqChainState implements Persistable {
}
checkArgument(fee > -1, "compensationRequestFees must be set");
return fee;
} finally {
readLock.unlock();
}
});
}
//TODO not impl yet
boolean isVotingPeriodValid(int blockHeight) {
try {
readLock.lock();
return lock.read(() -> {
return true;
} finally {
readLock.unlock();
}
});
}
boolean containsCompensationRequestBtcAddress(String btcAddress) {
try {
readLock.lock();
return compensationRequestBtcAddresses.contains(btcAddress);
} finally {
readLock.unlock();
}
boolean existsCompensationRequestBtcAddress(String btcAddress) {
return lock.read(() -> {
return getAllTxOutputs().stream()
.filter(txOutput -> txOutput.isCompensationRequestBtcOutput() &&
txOutput.getAddress().equals(btcAddress))
.findAny()
.isPresent();
});
}
Set<TxOutput> issuanceTxOutputsByBtcAddress(String btcAddress) {
try {
readLock.lock();
return issuanceBtcTxOutputsByBtcAddressMap.get(btcAddress);
} finally {
readLock.unlock();
Set<TxOutput> findSponsoringBtcOutputsWithSameBtcAddress(String btcAddress) {
return lock.read(() -> {
return getAllTxOutputs().stream()
.filter(txOutput -> txOutput.isSponsoringBtcOutput() &&
txOutput.getAddress().equals(btcAddress))
.collect(Collectors.toSet());
});
}
//TODO
// for genesis we dont need it and for issuance we need more implemented first
boolean isTxOutputMature(TxOutput spendingTxOutput) {
return lock.read(() -> {
return true;
});
}
@ -514,34 +413,10 @@ public class BsqChainState implements Persistable {
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private BsqChainState getClone(BsqChainState bsqChainState) {
final byte[] serialize = Utilities.serialize(bsqChainState);
return Utilities.<BsqChainState>deserialize(serialize);
}
//TODO
// for genesis we dont need it and for issuance we need more implemented first
private boolean isTxOutputMature(TxOutput spendingTxOutput) {
try {
readLock.lock();
return true;
} finally {
readLock.unlock();
}
}
private Optional<Tx> getTx(String txId) {
try {
readLock.lock();
return lock.read(() -> {
return txMap.get(txId) != null ? Optional.of(txMap.get(txId)) : Optional.<Tx>empty();
} finally {
readLock.unlock();
}
}
private int getSnapshotHeight(int height) {
return getSnapshotHeight(genesisBlockHeight, height, SNAPSHOT_GRID);
});
}
private boolean isSnapshotHeight(int height) {
@ -549,8 +424,7 @@ public class BsqChainState implements Persistable {
}
private void maybeMakeSnapshot() {
try {
readLock.lock();
lock.read(() -> {
// dont access snapshotCandidate.getChainHeadHeight() as locks are transient and woudl give a null pointer!
if (isSnapshotHeight(getChainHeadHeight()) &&
(snapshotCandidate == null ||
@ -558,47 +432,41 @@ public class BsqChainState implements Persistable {
// At trigger event we store the latest snapshotCandidate to disc
if (snapshotCandidate != null) {
// We clone because storage is in a threaded context
final BsqChainState cloned = getClone(snapshotCandidate);
final byte[] serialize = Utilities.serialize(snapshotCandidate);
final BsqChainState cloned = Utilities.<BsqChainState>deserialize(serialize);
snapshotBsqChainStateStorage.queueUpForSave(cloned);
// dont access cloned anymore with methods as locks are transient!
log.info("Saved snapshotCandidate to Disc at height " + cloned.chainHeadHeight);
}
// Now we clone and keep it in memory for the next trigger
snapshotCandidate = getClone(this);
final byte[] serialize = Utilities.serialize(this);
snapshotCandidate = Utilities.<BsqChainState>deserialize(serialize);
// dont access cloned anymore with methods as locks are transient!
log.debug("Cloned new snapshotCandidate at height " + snapshotCandidate.chainHeadHeight);
}
} finally {
readLock.unlock();
});
}
private Set<TxOutput> getAllTxOutputs() {
return txMap.values().stream()
.flatMap(tx -> tx.getOutputs().stream())
.collect(Collectors.toSet());
}
private void printDetails() {
log.info("\nchainHeadHeight={}\n" +
" blocks.size={}\n" +
" txMap.size={}\n" +
" unspentTxOutputSet.size={}\n" +
" spentInfoByTxOutputMap.size={}\n" +
" burnedFeeByTxIdMap.size={}\n" +
" unspentTxOutputsMap.size={}\n" +
" compensationRequestFees.size={}\n" +
" votingFees.size={}\n" +
" compensationRequestOpReturnTxOutputs.size={}\n" +
" compensationRequestBtcAddresses.size={}\n" +
" votingTxOutputs.size={}\n" +
" issuanceBtcTxOutputsByBtcAddressMap.size={}\n" +
" blocks data size in kb={}\n",
getChainHeadHeight(),
blocks.size(),
txMap.size(),
unspentTxOutputSet.size(),
spentInfoByTxOutputMap.size(),
burnedFeeByTxIdMap.size(),
unspentTxOutputsMap.size(),
compensationRequestFees.size(),
votingFees.size(),
compensationRequestOpReturnTxOutputs.size(),
compensationRequestBtcAddresses.size(),
votingTxOutputs.size(),
issuanceBtcTxOutputsByBtcAddressMap.size(),
Utilities.serialize(blocks.toArray()).length / 1000d);
}
}

View File

@ -103,10 +103,11 @@ public class BsqParser {
List<Tx> bsqTxsInBlock = findBsqTxsInBlock(btcdBlock,
genesisBlockHeight,
genesisTxId);
final BsqBlock bsqBlock = new BsqBlock(ImmutableList.copyOf(bsqTxsInBlock),
btcdBlock.getHeight(),
final BsqBlockVo bsqBlockVo = new BsqBlockVo(btcdBlock.getHeight(),
btcdBlock.getHash(),
btcdBlock.getPreviousBlockHash());
final BsqBlock bsqBlock = new BsqBlock(bsqBlockVo,
ImmutableList.copyOf(bsqTxsInBlock));
bsqChainState.addBlock(bsqBlock);
newBlockHandler.accept(bsqBlock);
@ -161,10 +162,11 @@ public class BsqParser {
List<Tx> bsqTxsInBlock = findBsqTxsInBlock(btcdBlock,
genesisBlockHeight,
genesisTxId);
BsqBlock bsqBlock = new BsqBlock(ImmutableList.copyOf(bsqTxsInBlock),
btcdBlock.getHeight(),
final BsqBlockVo bsqBlockVo = new BsqBlockVo(btcdBlock.getHeight(),
btcdBlock.getHash(),
btcdBlock.getPreviousBlockHash());
final BsqBlock bsqBlock = new BsqBlock(bsqBlockVo,
ImmutableList.copyOf(bsqTxsInBlock));
bsqChainState.addBlock(bsqBlock);
return bsqBlock;
}
@ -180,9 +182,16 @@ public class BsqParser {
List<Tx> bsqTxsInBlock,
Tx tx) {
if (tx.getId().equals(genesisTxId) && blockHeight == genesisBlockHeight) {
tx.getOutputs().stream().forEach(bsqChainState::addUnspentTxOutput);
tx.getOutputs().stream().forEach(txOutput -> {
txOutput.setUnspent(true);
txOutput.setVerified(true);
bsqChainState.addUnspentTxOutput(txOutput);
});
tx.setTxType(TxType.GENESIS);
tx.setVerified(true);
bsqChainState.setGenesisTx(tx);
bsqChainState.addTx(tx);
bsqChainState.addTxToMap(tx);
bsqTxsInBlock.add(tx);
}
}
@ -263,19 +272,20 @@ public class BsqParser {
long availableValue = 0;
for (int inputIndex = 0; inputIndex < tx.getInputs().size(); inputIndex++) {
TxInput input = tx.getInputs().get(inputIndex);
Optional<TxOutput> spendableTxOutput = bsqChainState.getSpendableTxOutput(input.getSpendingTxId(),
input.getSpendingTxOutputIndex());
Optional<TxOutput> spendableTxOutput = bsqChainState.getSpendableTxOutput(input.getTxIdIndexTuple());
if (spendableTxOutput.isPresent()) {
final TxOutput spentTxOutput = spendableTxOutput.get();
spentTxOutput.setUnspent(false);
bsqChainState.removeUnspentTxOutput(spentTxOutput);
bsqChainState.addSpentTxWithSpentInfo(spentTxOutput, new SpentInfo(blockHeight, tx.getId(), inputIndex));
spentTxOutput.setSpentInfo(new SpentInfo(blockHeight, tx.getId(), inputIndex));
input.setBsqValue(spentTxOutput.getValue());
input.setVerified(true);
availableValue = availableValue + spentTxOutput.getValue();
}
}
// If we have an input with BSQ we iterate the outputs
if (availableValue > 0) {
bsqChainState.addTx(tx);
bsqChainState.addTxToMap(tx);
isBsqTx = true;
// We use order of output index. An output is a BSQ utxo as long there is enough input value
@ -283,27 +293,30 @@ public class BsqParser {
TxOutput btcOutput = null;
for (int index = 0; index < outputs.size(); index++) {
TxOutput txOutput = outputs.get(index);
final long txOutputValue = txOutput.getValue();
// We ignore OP_RETURN outputs with txOutputValue 0
if (availableValue >= txOutputValue && txOutputValue != 0) {
// We are spending available tokens
txOutput.setVerified(true);
txOutput.setUnspent(true);
bsqChainState.addUnspentTxOutput(txOutput);
tx.setTxType(TxType.SEND_BSQ);
txOutput.setTxOutputType(TxOutputType.BSQ_OUTPUT);
availableValue -= txOutputValue;
if (availableValue == 0) {
log.debug("We don't have anymore BSQ to spend");
break;
}
} else if (issuanceVerification.maybeProcessData(outputs, index)) {
} else if (issuanceVerification.maybeProcessData(tx, index)) {
// it is not a BSQ or OP_RETURN output
// check is we have a sponsor tx
log.debug("We got a issuance tx and process the data");
break;
} else if (opReturnVerification.maybeProcessOpReturnData(outputs, index, availableValue, blockHeight, btcOutput)) {
} else if (opReturnVerification.maybeProcessOpReturnData(tx, index, availableValue, blockHeight, btcOutput)) {
log.debug("We processed valid DAO OP_RETURN data");
} else {
btcOutput = txOutput;
txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
// The other outputs are not BSQ outputs so we skip them but we
// jump to the last output as that might be an OP_RETURN with DAO data
index = Math.max(index, outputs.size() - 2);
@ -314,7 +327,9 @@ public class BsqParser {
log.debug("BSQ have been left which was not spent. Burned BSQ amount={}, tx={}",
availableValue,
tx.toString());
bsqChainState.addBurnedFee(tx.getId(), availableValue);
tx.setBurntFee(availableValue);
if (tx.getTxType() == null)
tx.setTxType(TxType.PAY_TRADE_FEE);
}
}

View File

@ -18,7 +18,10 @@
package io.bisq.core.dao.blockchain.parse;
import io.bisq.common.app.Version;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxOutputType;
import io.bisq.core.dao.blockchain.vo.TxType;
import javax.inject.Inject;
@ -30,13 +33,14 @@ public class CompensationRequestVerification {
this.bsqChainState = bsqChainState;
}
boolean maybeProcessData(byte[] opReturnData, TxOutput txOutput, long availableValue, int blockHeight, TxOutput btcOutput) {
if (btcOutput != null &&
boolean maybeProcessData(Tx tx, byte[] opReturnData, TxOutput opReturnTxOutput, long fee, int blockHeight, TxOutput btcTxOutput) {
if (btcTxOutput != null &&
Version.COMPENSATION_REQUEST_VERSION == opReturnData[1] &&
availableValue == bsqChainState.getCreateCompensationRequestFee(blockHeight) &&
fee == bsqChainState.getCreateCompensationRequestFee(blockHeight) &&
bsqChainState.isCompensationRequestPeriodValid(blockHeight)) {
bsqChainState.addCompensationRequestOpReturnOutput(txOutput);
bsqChainState.adCompensationRequestBtcTxOutputs(btcOutput.getAddress());
opReturnTxOutput.setTxOutputType(TxOutputType.COMPENSATION_REQUEST_OP_RETURN_OUTPUT);
btcTxOutput.setTxOutputType(TxOutputType.COMPENSATION_REQUEST_BTC_OUTPUT);
tx.setTxType(TxType.COMPENSATION_REQUEST);
return true;
}
return false;

View File

@ -17,7 +17,10 @@
package io.bisq.core.dao.blockchain.parse;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxOutputType;
import io.bisq.core.dao.blockchain.vo.TxType;
import io.bisq.core.dao.compensation.CompensationRequest;
import io.bisq.core.dao.compensation.CompensationRequestModel;
import lombok.extern.slf4j.Slf4j;
@ -48,7 +51,8 @@ public class IssuanceVerification {
this.compensationRequestModel = compensationRequestModel;
}
boolean maybeProcessData(List<TxOutput> outputs, int outputIndex) {
boolean maybeProcessData(Tx tx, int outputIndex) {
List<TxOutput> outputs = tx.getOutputs();
if (outputIndex == 0 && outputs.size() >= 2) {
TxOutput btcTxOutput = outputs.get(0);
TxOutput bsqTxOutput = outputs.get(1);
@ -60,7 +64,10 @@ public class IssuanceVerification {
final long requestedBtc = compensationRequest1.getCompensationRequestPayload().getRequestedBtc().value;
long alreadyFundedBtc = 0;
final int height = btcTxOutput.getBlockHeight();
Set<TxOutput> issuanceTxs = bsqChainState.issuanceTxOutputsByBtcAddress(btcAddress);
Set<TxOutput> issuanceTxs = bsqChainState.findSponsoringBtcOutputsWithSameBtcAddress(btcAddress);
// Sorting rule: the txs are sorted by inter-block dependency and
// at each recursive iteration we add another sorted list which can be parsed, so we have a reproducible
// sorting.
for (TxOutput txOutput : issuanceTxs) {
if (txOutput.getBlockHeight() < height ||
(txOutput.getBlockHeight() == height &&
@ -70,12 +77,13 @@ public class IssuanceVerification {
}
final long btcAmount = btcTxOutput.getValue();
if (periodVerification.isInSponsorPeriod(height) &&
bsqChainState.containsCompensationRequestBtcAddress(btcAddress) &&
bsqChainState.existsCompensationRequestBtcAddress(btcAddress) &&
votingVerification.isCompensationRequestAccepted(compensationRequest1) &&
alreadyFundedBtc + btcAmount <= requestedBtc &&
bsqAmount >= MIN_BSQ_ISSUANCE_AMOUNT && bsqAmount <= MAX_BSQ_ISSUANCE_AMOUNT &&
votingVerification.isConversionRateValid(height, btcAmount, bsqAmount)) {
bsqChainState.addIssuanceBtcTxOutput(btcTxOutput);
btcTxOutput.setTxOutputType(TxOutputType.SPONSORING_BTC_OUTPUT);
tx.setTxType(TxType.ISSUANCE);
return true;
}
}

View File

@ -18,7 +18,9 @@
package io.bisq.core.dao.blockchain.parse;
import io.bisq.core.dao.DaoConstants;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxOutputType;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Utils;
@ -37,7 +39,9 @@ public class OpReturnVerification {
this.votingVerification = votingVerification;
}
boolean maybeProcessOpReturnData(List<TxOutput> txOutputs, int index, long availableValue, int blockHeight, TxOutput btcOutput) {
boolean maybeProcessOpReturnData(Tx tx, int index, long availableValue,
int blockHeight, TxOutput btcOutput) {
List<TxOutput> txOutputs = tx.getOutputs();
TxOutput txOutput = txOutputs.get(index);
final long txOutputValue = txOutput.getValue();
if (txOutputValue == 0 && index == txOutputs.size() - 1 && availableValue > 0) {
@ -45,11 +49,13 @@ public class OpReturnVerification {
// the txOutputValue is 0 as well we expect that availableValue>0
byte[] opReturnData = txOutput.getOpReturnData();
if (opReturnData != null && opReturnData.length > 1) {
txOutput.setTxOutputType(TxOutputType.OP_RETURN_OUTPUT);
switch (opReturnData[0]) {
case DaoConstants.OP_RETURN_TYPE_COMPENSATION_REQUEST:
return compensationRequestVerification.maybeProcessData(opReturnData, txOutput, availableValue, blockHeight, btcOutput);
return compensationRequestVerification.maybeProcessData(tx, opReturnData, txOutput,
availableValue, blockHeight, btcOutput);
case DaoConstants.OP_RETURN_TYPE_VOTE:
return votingVerification.maybeProcessData(opReturnData, txOutput, availableValue, blockHeight);
return votingVerification.maybeProcessData(tx, opReturnData, txOutput, availableValue, blockHeight);
default:
log.warn("OP_RETURN data version does not match expected version bytes. opReturnData={}",
Utils.HEX.encode(opReturnData));

View File

@ -32,9 +32,7 @@ import com.neemre.btcdcli4j.daemon.event.BlockListener;
import io.bisq.core.dao.DaoOptionKeys;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import io.bisq.core.dao.blockchain.exceptions.BsqBlockchainException;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxInput;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.*;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@ -105,7 +103,7 @@ public class RpcService {
this.client = client;
} catch (IOException | BitcoindException | CommunicationException e) {
if (e instanceof CommunicationException)
log.error("Maybe the rpc port is not set correctly? rpcPort=" + rpcPort);
log.error("Probably Bitcoin core is not running or the rpc port is not set correctly. rpcPort=" + rpcPort);
log.error(e.toString());
e.printStackTrace();
log.error(e.getCause() != null ? e.getCause().toString() : "e.getCause()=null");
@ -149,7 +147,7 @@ public class RpcService {
final List<TxInput> txInputs = rawTransaction.getVIn()
.stream()
.filter(rawInput -> rawInput != null && rawInput.getVOut() != null && rawInput.getTxId() != null)
.map(rawInput -> new TxInput(rawInput.getVOut(), rawInput.getTxId()))
.map(rawInput -> new TxInput(new TxInputVo(rawInput.getTxId(), rawInput.getVOut())))
.collect(Collectors.toList());
final List<TxOutput> txOutputs = rawTransaction.getVOut()
@ -160,32 +158,39 @@ public class RpcService {
final com.neemre.btcdcli4j.core.domain.PubKeyScript scriptPubKey = rawOutput.getScriptPubKey();
if (scriptPubKey.getType().equals(ScriptTypes.NULL_DATA)) {
String[] chunks = scriptPubKey.getAsm().split(" ");
// TODO only store BSQ OP_RETURN date filtered by type byte
if (chunks.length == 2 && chunks[0].equals("OP_RETURN")) {
try {
opReturnData = Utils.HEX.decode(chunks[1]);
} catch (Throwable t) {
// We get sometimes exceptions, seems BitcoinJ
// cannot handle all existing OP_RETURN data, but we ignore them
// anyway as our OP_RETURN data is valid in BitcoinJ
log.warn(t.toString());
}
}
}
// We dont support raw MS which are the only case where scriptPubKey.getAddresses()>1
String address = scriptPubKey.getAddresses() != null &&
scriptPubKey.getAddresses().size() == 1 ? scriptPubKey.getAddresses().get(0) : null;
final PubKeyScript pubKeyScript = dumpBlockchainData ? new PubKeyScript(scriptPubKey) : null;
return new TxOutput(rawOutput.getN(),
return new TxOutput(new TxOutputVo(rawOutput.getN(),
rawOutput.getValue().movePointRight(8).longValue(),
rawTransaction.getTxId(),
pubKeyScript,
address,
opReturnData,
blockHeight,
time);
time));
}
)
.collect(Collectors.toList());
return new Tx(txId,
return new Tx(new TxVo(txId,
blockHeight,
rawTransaction.getBlockHash(),
rawTransaction.getBlockHash()),
ImmutableList.copyOf(txInputs),
ImmutableList.copyOf(txOutputs),
false);
ImmutableList.copyOf(txOutputs));
} catch (BitcoindException | CommunicationException e) {
throw new BsqBlockchainException(e.getMessage(), e);
}

View File

@ -18,7 +18,10 @@
package io.bisq.core.dao.blockchain.parse;
import io.bisq.common.app.Version;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxOutputType;
import io.bisq.core.dao.blockchain.vo.TxType;
import io.bisq.core.dao.compensation.CompensationRequest;
import lombok.extern.slf4j.Slf4j;
@ -44,15 +47,16 @@ public class VotingVerification {
return false;
}
boolean maybeProcessData(byte[] opReturnData, TxOutput txOutput, long availableValue, int blockHeight) {
boolean maybeProcessData(Tx tx, byte[] opReturnData, TxOutput txOutput, long fee, int blockHeight) {
final int sizeOfCompRequestsVotes = (int) opReturnData[22];
if (Version.VOTING_VERSION == opReturnData[1] &&
sizeOfCompRequestsVotes % 2 == 0 &&
(opReturnData.length - 1) % 2 == 0 &&
opReturnData.length >= 23 + sizeOfCompRequestsVotes * 2 &&
availableValue == bsqChainState.getVotingFee(blockHeight) &&
fee == bsqChainState.getVotingFee(blockHeight) &&
bsqChainState.isVotingPeriodValid(blockHeight)) {
bsqChainState.addVotingOpReturnOutput(txOutput);
txOutput.setTxOutputType(TxOutputType.VOTING_OP_RETURN_OUTPUT);
tx.setTxType(TxType.VOTE);
return true;
}
return false;

View File

@ -19,28 +19,35 @@ package io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import lombok.Value;
import lombok.Data;
import lombok.experimental.Delegate;
import javax.annotation.concurrent.Immutable;
import java.util.List;
@Value
@Immutable
@Data
public class BsqBlock implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
@Delegate
private final BsqBlockVo bsqBlockVo;
private final List<Tx> txs;
private final int height;
private final String hash;
private String previousBlockHash;
public BsqBlock(BsqBlockVo bsqBlockVo, List<Tx> txs) {
this.bsqBlockVo = bsqBlockVo;
this.txs = txs;
}
@Override
public String toString() {
return "BsqBlock{" +
"\n height=" + height +
",\n hash='" + hash + '\'' +
",\n txs.size='" + txs.size() + '\'' +
",\n previousBlockHash='" + previousBlockHash + '\'' +
"\n height=" + getHeight() +
",\n hash='" + getHash() + '\'' +
",\n previousBlockHash='" + getPreviousBlockHash() + '\'' +
",\n txs='" + txs + '\'' +
"\n}";
}
public void reset() {
txs.stream().forEach(Tx::reset);
}
}

View File

@ -0,0 +1,35 @@
/*
* 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 io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import lombok.Value;
import javax.annotation.concurrent.Immutable;
@Value
@Immutable
public class BsqBlockVo implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private final int height;
private final String hash;
private String previousBlockHash;
}

View File

@ -19,38 +19,54 @@ package io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import lombok.Value;
import lombok.Data;
import lombok.experimental.Delegate;
import javax.annotation.concurrent.Immutable;
import java.util.List;
import java.util.Optional;
@Value
@Immutable
@Data
public class Tx implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private final String id;
private final int blockHeight;
private final String blockHash;
@Delegate
private final TxVo txVo;
private final List<TxInput> inputs;
private final List<TxOutput> outputs;
private final boolean isGenesisTx;
private final String txVersion = Version.BSQ_TX_VERSION;
private boolean isVerified;
private long burntFee;
private TxType txType;
public Tx(TxVo txVo, List<TxInput> inputs, List<TxOutput> outputs) {
this.txVo = txVo;
this.inputs = inputs;
this.outputs = outputs;
}
public Optional<TxOutput> getTxOutput(int index) {
return outputs.size() > index ? Optional.of(outputs.get(index)) : Optional.<TxOutput>empty();
}
public void reset() {
isVerified = false;
burntFee = 0;
txType = null;
inputs.stream().forEach(TxInput::reset);
outputs.stream().forEach(TxOutput::reset);
}
@Override
public String toString() {
return "Tx{" +
"\nid='" + id + '\'' +
",\nblockHash=" + blockHash +
"\ntxVersion='" + getTxVersion() + '\'' +
",\nid='" + getId() + '\'' +
",\nblockHeight=" + getBlockHeight() +
",\nblockHash=" + getBlockHash() +
",\ninputs=" + inputs +
",\noutputs=" + outputs +
",\nisGenesisTx=" + isGenesisTx +
",\ntxVersion=" + txVersion +
",\ntxVersion=" + getTxVersion() +
"}\n";
}
}

View File

@ -19,23 +19,36 @@ package io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import lombok.Value;
import lombok.Data;
import lombok.experimental.Delegate;
import javax.annotation.concurrent.Immutable;
@Value
@Immutable
@Data
public class TxInput implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private final int spendingTxOutputIndex;
private final String spendingTxId;
@Delegate
private final TxInputVo txInputVo;
private long bsqValue;
private boolean isVerified;
public TxInput(TxInputVo txInputVo) {
this.txInputVo = txInputVo;
}
public void reset() {
bsqValue = 0;
isVerified = false;
}
@Override
public String toString() {
return "TxInput{" +
"\nspendingTxOutputIndex=" + spendingTxOutputIndex +
",\nspendingTxId='" + spendingTxId + '\'' +
"}\n";
"\n spendingTxId=" + getSpendingTxId() +
",\n spendingTxOutputIndex=" + getSpendingTxOutputIndex() +
",\n bsqValue='" + bsqValue + '\'' +
",\n isVerified='" + isVerified + '\'' +
"\n}";
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import lombok.Value;
import javax.annotation.concurrent.Immutable;
@Value
@Immutable
public class TxInputVo implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private final String spendingTxId;
private final int spendingTxOutputIndex;
public TxIdIndexTuple getTxIdIndexTuple() {
return new TxIdIndexTuple(spendingTxId, spendingTxOutputIndex);
}
}

View File

@ -19,97 +19,56 @@ package io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import io.bisq.common.util.JsonExclude;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import lombok.Data;
import lombok.experimental.Delegate;
import org.bitcoinj.core.Utils;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Slf4j
@Value
@Immutable
@Data
public class TxOutput implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private final int index;
private final long value;
private final String txId;
private final PubKeyScript pubKeyScript;
@Nullable
@JsonExclude
private final String address;
@Nullable
@JsonExclude
private final byte[] opReturnData;
private final int blockHeight;
private final long time;
@Delegate
private final TxOutputVo txOutputVo;
public TxOutput(int index,
long value,
String txId,
PubKeyScript pubKeyScript,
@Nullable String address,
@Nullable byte[] opReturnData,
int blockHeight,
long time) {
this.index = index;
this.value = value;
this.txId = txId;
this.pubKeyScript = pubKeyScript;
this.address = address;
this.opReturnData = opReturnData;
this.blockHeight = blockHeight;
this.time = time;
private boolean isUnspent;
private boolean isVerified;
private TxOutputType txOutputType;
private SpentInfo spentInfo;
public TxOutput(TxOutputVo txOutputVo) {
this.txOutputVo = txOutputVo;
}
/* public String getAddress() {
String address = "";
// Only at raw MS outputs addresses have more then 1 entry
// We do not support raw MS for BSQ but lets see if is needed anyway, might be removed
final List<String> addresses = pubKeyScript.getAddresses();
if (addresses.size() == 1) {
address = addresses.get(0);
} else if (addresses.size() > 1) {
final String msg = "We got a raw Multisig script. That is not supported for BSQ tokens.";
log.warn(msg);
address = addresses.toString();
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
} else {
final String msg = "We got no address. Unsupported pubKeyScript";
log.warn(msg);
if (DevEnv.DEV_MODE)
throw new RuntimeException(msg);
}
return address;
}*/
public String getId() {
return txId + ":" + index;
}
public String getSortString() {
return blockHeight + ":" + txId;
}
public TxIdIndexTuple getTxIdIndexTuple() {
return new TxIdIndexTuple(txId, index);
public void reset() {
isUnspent = false;
isVerified = false;
txOutputType = null;
spentInfo = null;
}
@Override
public String toString() {
return "TxOutput{" +
"\n index=" + index +
",\n value=" + value +
",\n txId='" + txId + '\'' +
",\n pubKeyScript=" + pubKeyScript +
",\n blockHeight=" + blockHeight +
",\n opReturnData=" + (opReturnData != null ? Utils.HEX.encode(opReturnData) : "null") +
",\n time=" + time +
"\n index=" + getIndex() +
",\n value=" + getValue() +
",\n txId='" + getId() + '\'' +
",\n pubKeyScript=" + getPubKeyScript() +
",\n address='" + getAddress() + '\'' +
",\n opReturnData=" + (getOpReturnData() != null ? Utils.HEX.encode(getOpReturnData()) : "null") +
",\n blockHeight=" + getBlockHeight() +
",\n time=" + getTime() +
",\n isUnspent=" + isUnspent +
",\n isVerified=" + isVerified +
",\n txOutputType=" + txOutputType +
",\n spentInfo=" + (spentInfo != null ? spentInfo.toString() : "null") +
"\n}";
}
public boolean isCompensationRequestBtcOutput() {
return txOutputType == TxOutputType.COMPENSATION_REQUEST_BTC_OUTPUT;
}
public boolean isSponsoringBtcOutput() {
return txOutputType == TxOutputType.SPONSORING_BTC_OUTPUT;
}
}

View File

@ -0,0 +1,29 @@
/*
* 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 io.bisq.core.dao.blockchain.vo;
public enum TxOutputType {
UNDEFINED,
BSQ_OUTPUT,
BTC_OUTPUT,
OP_RETURN_OUTPUT,
COMPENSATION_REQUEST_OP_RETURN_OUTPUT,
COMPENSATION_REQUEST_BTC_OUTPUT,
SPONSORING_BTC_OUTPUT,
VOTING_OP_RETURN_OUTPUT
}

View File

@ -0,0 +1,55 @@
/*
* 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 io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import io.bisq.common.util.JsonExclude;
import io.bisq.core.dao.blockchain.btcd.PubKeyScript;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Slf4j
@Value
@Immutable
public class TxOutputVo implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private final int index;
private final long value;
private final String txId;
private final PubKeyScript pubKeyScript;
@Nullable
private final String address;
@Nullable
@JsonExclude
private final byte[] opReturnData;
private final int blockHeight;
private final long time;
public String getId() {
return txId + ":" + index;
}
public TxIdIndexTuple getTxIdIndexTuple() {
return new TxIdIndexTuple(txId, index);
}
}

View File

@ -0,0 +1,28 @@
/*
* 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 io.bisq.core.dao.blockchain.vo;
public enum TxType {
UNDEFINED,
GENESIS,
SEND_BSQ,
PAY_TRADE_FEE,
COMPENSATION_REQUEST,
VOTE,
ISSUANCE
}

View File

@ -0,0 +1,35 @@
/*
* 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 io.bisq.core.dao.blockchain.vo;
import io.bisq.common.app.Version;
import io.bisq.common.persistence.Persistable;
import lombok.Value;
import javax.annotation.concurrent.Immutable;
@Value
@Immutable
public class TxVo implements Persistable {
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private final String txVersion = Version.BSQ_TX_VERSION;
private final String id;
private final int blockHeight;
private final String blockHash;
}

View File

@ -37,6 +37,7 @@ import io.bisq.core.app.AppOptionKeys;
import io.bisq.core.app.BisqEnvironment;
import io.bisq.core.arbitration.ArbitratorManager;
import io.bisq.core.btc.wallet.*;
import io.bisq.core.dao.blockchain.json.JsonChainStateExporter;
import io.bisq.core.filter.FilterManager;
import io.bisq.core.offer.OpenOfferManager;
import io.bisq.core.trade.TradeManager;
@ -412,6 +413,7 @@ public class BisqApp extends Application {
if (injector != null) {
injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(TradeManager.class).shutDown();
injector.getInstance(JsonChainStateExporter.class).shutDown();
//noinspection CodeBlock2Expr
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
injector.getInstance(P2PService.class).shutDown(() -> {

View File

@ -178,7 +178,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
return new BsqTxListItem(transaction,
bsqWalletService,
btcWalletService,
bsqChainState.hasTxBurnedFee(transaction.getHashAsString()),
bsqChainState.hasTxBurntFee(transaction.getHashAsString()),
bsqFormatter);
}
)

View File

@ -25,8 +25,8 @@ public class SeedNodesRepository {
// local dev test
// DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("23bnormzh2mvkz3z.onion:8000"),
// testnet (not operated by bisq devs)
new NodeAddress("znmy44wcstn2rkva.onion:8001"),
// testnet
new NodeAddress("nbphlanpgbei4okt.onion:8001"),
// regtest
// For development you need to change that to your local onion addresses