mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 10:22:18 +01:00
Merge remote-tracking branch 'origin/DAO' into disk-protobuffer
This commit is contained in:
commit
9e0056a276
102
common/src/main/java/io/bisq/common/storage/JsonFileManager.java
Normal file
102
common/src/main/java/io/bisq/common/storage/JsonFileManager.java
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,29 +99,37 @@ 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) {
|
||||
bsqFullNodeExecutor.parseBlocks(startBlockHeight,
|
||||
chainHeadHeight,
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
this::onNewBsqBlock,
|
||||
() -> {
|
||||
// we are done but it might be that new blocks have arrived in the meantime,
|
||||
// so we try again with startBlockHeight set to current chainHeadHeight
|
||||
// We also set up the listener in the else main branch where we check
|
||||
// if we at chainTip, so do nto include here another check as it would
|
||||
// not trigger the listener registration.
|
||||
parseBlocksWithChainHeadHeight(chainHeadHeight,
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
}, throwable -> {
|
||||
if (throwable instanceof BlockNotConnectingException) {
|
||||
startReOrgFromLastSnapshot();
|
||||
} else {
|
||||
log.error(throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
});
|
||||
if (startBlockHeight <= chainHeadHeight) {
|
||||
bsqFullNodeExecutor.parseBlocks(startBlockHeight,
|
||||
chainHeadHeight,
|
||||
genesisBlockHeight,
|
||||
genesisTxId,
|
||||
this::onNewBsqBlock,
|
||||
() -> {
|
||||
// we are done but it might be that new blocks have arrived in the meantime,
|
||||
// so we try again with startBlockHeight set to current chainHeadHeight
|
||||
// We also set up the listener in the else main branch where we check
|
||||
// if we at chainTip, so do nto include here another check as it would
|
||||
// not trigger the listener registration.
|
||||
parseBlocksWithChainHeadHeight(chainHeadHeight,
|
||||
genesisBlockHeight,
|
||||
genesisTxId);
|
||||
}, throwable -> {
|
||||
if (throwable instanceof BlockNotConnectingException) {
|
||||
startReOrgFromLastSnapshot();
|
||||
} else {
|
||||
log.error(throwable.toString());
|
||||
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);
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -67,7 +72,7 @@ public class BsqChainState implements Persistable {
|
||||
//mainnet
|
||||
// this tx has a lot of outputs
|
||||
// https://blockchain.info/de/tx/ee921650ab3f978881b8fe291e0c025e0da2b7dc684003d7a03d9649dfee2e15
|
||||
// BLOCK_HEIGHT 411779
|
||||
// BLOCK_HEIGHT 411779
|
||||
// 411812 has 693 recursions
|
||||
// MAIN NET
|
||||
private static final String GENESIS_TX_ID = "b26371e2145f52c94b3d30713a9e38305bfc665fc27cd554e794b5e369d99ef5";
|
||||
@ -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,27 +99,20 @@ 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;
|
||||
private int chainHeadHeight = 0;
|
||||
private Tx genesisTx;
|
||||
|
||||
// transient
|
||||
// transient
|
||||
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,126 +193,58 @@ public class BsqChainState implements Persistable {
|
||||
|
||||
void addBlock(BsqBlock block) throws BlockNotConnectingException {
|
||||
try {
|
||||
writeLock.lock();
|
||||
|
||||
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);
|
||||
chainHeadHeight = block.getHeight();
|
||||
maybeMakeSnapshot();
|
||||
//printDetails();
|
||||
} else if (!blocks.isEmpty()) {
|
||||
log.warn("addBlock called with a not connecting block:\n" +
|
||||
"height()={}, hash()={}, head.height()={}, head.hash()={}",
|
||||
block.getHeight(), block.getHash(), blocks.getLast().getHeight(), blocks.getLast().getHash());
|
||||
throw new BlockNotConnectingException(block);
|
||||
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(BsqChainState.this::addTxToMap);
|
||||
chainHeadHeight = block.getHeight();
|
||||
maybeMakeSnapshot();
|
||||
//printDetails();
|
||||
} else if (!blocks.isEmpty()) {
|
||||
log.warn("addBlock called with a not connecting block:\n" +
|
||||
"height()={}, hash()={}, head.height()={}, head.hash()={}",
|
||||
block.getHeight(), block.getHash(), blocks.getLast().getHeight(), blocks.getLast().getHash());
|
||||
throw new BlockNotConnectingException(block);
|
||||
}
|
||||
} else {
|
||||
log.trace("We got that block already");
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
return genesisTxId;
|
||||
}
|
||||
|
||||
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 Utilities.<BsqChainState>deserialize(serialize);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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")) {
|
||||
opReturnData = Utils.HEX.decode(chunks[1]);
|
||||
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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
35
core/src/main/java/io/bisq/core/dao/blockchain/vo/TxVo.java
Normal file
35
core/src/main/java/io/bisq/core/dao/blockchain/vo/TxVo.java
Normal 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;
|
||||
}
|
@ -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(() -> {
|
||||
|
@ -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);
|
||||
}
|
||||
)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user