Extract snapshot code from BsqBlockChain to SnapshotManager. Rename *Verification classes to *Controller. Add BsqBlockController.

This commit is contained in:
Manfred Karrer 2018-03-09 14:56:40 -05:00
parent e55bba63fb
commit c611eed0a6
No known key found for this signature in database
GPG key ID: 401250966A6B2C46
28 changed files with 635 additions and 442 deletions

View file

@ -63,21 +63,21 @@ public class DaoModule extends AppModule {
bind(FullNode.class).in(Singleton.class);
bind(BsqNodeProvider.class).in(Singleton.class);
bind(BsqBlockChain.class).in(Singleton.class);
bind(ReadModel.class).in(Singleton.class);
bind(WriteModel.class).in(Singleton.class);
bind(BsqBlockChainReadModel.class).in(Singleton.class);
bind(BsqBlockChainWriteModel.class).in(Singleton.class);
bind(SnapshotManager.class).in(Singleton.class);
bind(BsqBlockChainChangeDispatcher.class).in(Singleton.class);
bind(GenesisTxVerification.class).in(Singleton.class);
bind(BsqTxVerification.class).in(Singleton.class);
bind(TxInputsVerification.class).in(Singleton.class);
bind(TxInputVerification.class).in(Singleton.class);
bind(TxOutputsVerification.class).in(Singleton.class);
bind(TxOutputVerification.class).in(Singleton.class);
bind(OpReturnVerification.class).in(Singleton.class);
bind(CompensationRequestVerification.class).in(Singleton.class);
bind(VotingVerification.class).in(Singleton.class);
bind(IssuanceVerification.class).in(Singleton.class);
bind(GenesisTxController.class).in(Singleton.class);
bind(BsqTxController.class).in(Singleton.class);
bind(TxInputsController.class).in(Singleton.class);
bind(TxInputController.class).in(Singleton.class);
bind(TxOutputsController.class).in(Singleton.class);
bind(TxOutputController.class).in(Singleton.class);
bind(OpReturnController.class).in(Singleton.class);
bind(CompensationRequestController.class).in(Singleton.class);
bind(VotingController.class).in(Singleton.class);
bind(IssuanceController.class).in(Singleton.class);
bind(JsonBlockChainExporter.class).in(Singleton.class);
bind(DaoPeriodService.class).in(Singleton.class);

View file

@ -20,35 +20,40 @@ package io.bisq.core.dao.blockchain;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.Message;
import io.bisq.common.proto.persistable.PersistableEnvelope;
import io.bisq.common.proto.persistable.PersistenceProtoResolver;
import io.bisq.common.storage.Storage;
import io.bisq.common.util.FunctionalReadWriteLock;
import io.bisq.common.util.Tuple2;
import io.bisq.core.dao.DaoOptionKeys;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxType;
import io.bisq.core.dao.blockchain.vo.util.TxIdIndexTuple;
import io.bisq.generated.protobuffer.PB;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.Coin;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
// Represents mutable state of BSQ blocks
// We get accessed the data from different threads so we need to make sure it is thread safe.
/**
* Mutual state of the BSQ blockchain data.
* <p>
* We only have one thread which is writing data (ParseBlock or ParseBlocks thread from the lite node or full node executors).
* Based on that limited requirement our threading model can be relaxed.
* We use ReentrantReadWriteLock in a functional style.
* <p>
* We limit the access to BsqBlockChain to package private scope and only the BsqBlockChainReadModel and
* BsqBlockChainWriteModel have access to have better overview and control about access.
*/
@Slf4j
public class BsqBlockChain implements PersistableEnvelope {
@ -66,6 +71,16 @@ public class BsqBlockChain implements PersistableEnvelope {
return height % grid == 0 && height >= getSnapshotHeight(genesisHeight, height, grid);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onBlockAdded(BsqBlock bsqBlock);
}
private static final int SNAPSHOT_GRID = 100; // set high to deactivate
private static final int ISSUANCE_MATURITY = 144 * 30; // 30 days
private static final Coin GENESIS_TOTAL_SUPPLY = Coin.parseCoin("2.5");
@ -86,26 +101,24 @@ public class BsqBlockChain implements PersistableEnvelope {
// Instance fields
///////////////////////////////////////////////////////////////////////////////////////////
// Persisted data
private final String genesisTxId;
private final int genesisBlockHeight;
private final LinkedList<BsqBlock> bsqBlocks;
private final Map<String, Tx> txMap;
private final Map<TxIdIndexTuple, TxOutput> unspentTxOutputsMap;
private final String genesisTxId;
private final int genesisBlockHeight;
private int chainHeadHeight = 0;
@Nullable
@Getter
private Tx genesisTx;
// not impl in PB yet
private final Set<Tuple2<Long, Integer>> compensationRequestFees;
private final Set<Tuple2<Long, Integer>> votingFees;
// transient
private final List<Listener> listeners = new ArrayList<>();
private int chainHeadHeight = 0;
@Nullable
transient private Storage<BsqBlockChain> storage;
@Nullable
transient private BsqBlockChain snapshotCandidate;
private Tx genesisTx;
transient private final FunctionalReadWriteLock lock;
@ -115,14 +128,11 @@ public class BsqBlockChain implements PersistableEnvelope {
@SuppressWarnings("WeakerAccess")
@Inject
public BsqBlockChain(PersistenceProtoResolver persistenceProtoResolver,
@Named(Storage.STORAGE_DIR) File storageDir,
@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
public BsqBlockChain(@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
@Named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) int genesisBlockHeight) {
this.genesisTxId = genesisTxId;
this.genesisBlockHeight = genesisBlockHeight;
storage = new Storage<>(storageDir, persistenceProtoResolver);
bsqBlocks = new LinkedList<>();
txMap = new HashMap<>();
@ -155,7 +165,7 @@ public class BsqBlockChain implements PersistableEnvelope {
lock = new FunctionalReadWriteLock(true);
// not impl yet in PB
// TODO not impl yet in PB
compensationRequestFees = new HashSet<>();
votingFees = new HashSet<>();
}
@ -201,92 +211,79 @@ public class BsqBlockChain implements PersistableEnvelope {
///////////////////////////////////////////////////////////////////////////////////////////
// Atomic access
// Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public <T> T callFunctionWithWriteLock(Supplier<T> supplier) {
return lock.write(supplier);
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
//*****************************************************************************************
//*****************************************************************************************
// WRITE ACCESS
//*****************************************************************************************
//*****************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Public write access
// Write access: BsqBlockChain
///////////////////////////////////////////////////////////////////////////////////////////
void applySnapshot() {
void applySnapshot(BsqBlockChain snapshot) {
lock.write(() -> {
checkNotNull(storage, "storage must not be null");
BsqBlockChain snapshot = storage.initAndGetPersistedWithFileName("BsqBlockChain", 100);
bsqBlocks.clear();
bsqBlocks.addAll(snapshot.bsqBlocks);
txMap.clear();
txMap.putAll(snapshot.txMap);
unspentTxOutputsMap.clear();
chainHeadHeight = 0;
genesisTx = null;
unspentTxOutputsMap.putAll(snapshot.unspentTxOutputsMap);
if (snapshot != null) {
log.info("applySnapshot snapshot.chainHeadHeight=" + snapshot.chainHeadHeight);
bsqBlocks.addAll(snapshot.bsqBlocks);
txMap.putAll(snapshot.txMap);
unspentTxOutputsMap.putAll(snapshot.unspentTxOutputsMap);
chainHeadHeight = snapshot.chainHeadHeight;
genesisTx = snapshot.genesisTx;
} else {
log.info("Try to apply snapshot but no stored snapshot available");
}
printDetails();
chainHeadHeight = snapshot.chainHeadHeight;
genesisTx = snapshot.genesisTx;
});
}
void setCreateCompensationRequestFee(long fee, int blockHeight) {
lock.write(() -> compensationRequestFees.add(new Tuple2<>(fee, blockHeight)));
}
void setVotingFee(long fee, int blockHeight) {
lock.write(() -> votingFees.add(new Tuple2<>(fee, blockHeight)));
///////////////////////////////////////////////////////////////////////////////////////////
// Write access: BsqBlock
///////////////////////////////////////////////////////////////////////////////////////////
void addBlock(BsqBlock bsqBlock) {
lock.write(() -> {
bsqBlocks.add(bsqBlock);
bsqBlock.getTxs().forEach(BsqBlockChain.this::addTxToMap);
chainHeadHeight = bsqBlock.getHeight();
printDetails();
listeners.forEach(l -> l.onBlockAdded(bsqBlock));
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Package scope write access
// Write access: Tx
///////////////////////////////////////////////////////////////////////////////////////////
//TODO refactor logic out
void addBlock(BsqBlock block) throws BlockNotConnectingException {
try {
lock.write2(() -> {
if (!bsqBlocks.contains(block)) {
if (bsqBlocks.isEmpty() || (bsqBlocks.getLast().getHash().equals(block.getPreviousBlockHash()) &&
bsqBlocks.getLast().getHeight() + 1 == block.getHeight())) {
bsqBlocks.add(block);
block.getTxs().forEach(BsqBlockChain.this::addTxToMap);
chainHeadHeight = block.getHeight();
maybeMakeSnapshot();
printDetails();
} else {
log.warn("addBlock called with a not connecting block:\n" +
"height()={}, hash()={}, head.height()={}, head.hash()={}",
block.getHeight(), block.getHash(), bsqBlocks.getLast().getHeight(), bsqBlocks.getLast().getHash());
throw new BlockNotConnectingException(block);
}
} else {
log.trace("We got that block already");
}
return null;
});
} catch (Exception e) {
throw new BlockNotConnectingException(block);
} catch (Throwable e) {
log.error(e.toString());
e.printStackTrace();
throw e;
}
void setGenesisTx(Tx tx) {
lock.write(() -> genesisTx = tx);
}
void addTxToMap(Tx tx) {
lock.write(() -> txMap.put(tx.getId(), tx));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Write access: TxOutput
///////////////////////////////////////////////////////////////////////////////////////////
void addUnspentTxOutput(TxOutput txOutput) {
lock.write(() -> {
checkArgument(txOutput.isVerified(), "txOutput must be verified at addUnspentTxOutput");
@ -298,35 +295,123 @@ public class BsqBlockChain implements PersistableEnvelope {
lock.write(() -> unspentTxOutputsMap.remove(txOutput.getTxIdIndexTuple()));
}
void setGenesisTx(Tx tx) {
lock.write(() -> genesisTx = tx);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public read access
// Write access: Misc
///////////////////////////////////////////////////////////////////////////////////////////
public String getGenesisTxId() {
return genesisTxId;
void setCreateCompensationRequestFee(long fee, int blockHeight) {
lock.write(() -> compensationRequestFees.add(new Tuple2<>(fee, blockHeight)));
}
public int getGenesisBlockHeight() {
return lock.read(() -> genesisBlockHeight);
void setVotingFee(long fee, int blockHeight) {
lock.write(() -> votingFees.add(new Tuple2<>(fee, blockHeight)));
}
public BsqBlockChain getClone() {
return getClone(this);
//*****************************************************************************************
//*****************************************************************************************
// READ ACCESS
//*****************************************************************************************
//*****************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Read access: BsqBlockChain
///////////////////////////////////////////////////////////////////////////////////////////
BsqBlockChain getClone() {
return lock.read(() -> getClone(this));
}
private BsqBlockChain getClone(BsqBlockChain bsqBlockChain) {
BsqBlockChain getClone(BsqBlockChain bsqBlockChain) {
return lock.read(() -> (BsqBlockChain) BsqBlockChain.fromProto(bsqBlockChain.getBsqBlockChainBuilder().build()));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Read access: BsqBlock
///////////////////////////////////////////////////////////////////////////////////////////
LinkedList<BsqBlock> getBsqBlocks() {
return lock.read(() -> bsqBlocks);
}
boolean containsBlock(BsqBlock bsqBlock) {
return lock.read(() -> bsqBlocks.contains(bsqBlock));
}
public int getChainHeadHeight() {
return chainHeadHeight;
}
public int getGenesisBlockHeight() {
return genesisBlockHeight;
}
List<BsqBlock> getClonedBlocksFrom(int fromBlockHeight) {
return lock.read(() -> {
BsqBlockChain clone = getClone();
List<BsqBlock> filtered = clone.bsqBlocks.stream()
.filter(block -> block.getHeight() >= fromBlockHeight)
.collect(Collectors.toList());
filtered.forEach(BsqBlock::reset);
return filtered;
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Read access: Tx
///////////////////////////////////////////////////////////////////////////////////////////
private Optional<Tx> getTx(String txId) {
return lock.read(() -> txMap.get(txId) != null ? Optional.of(txMap.get(txId)) : Optional.<Tx>empty());
}
public Map<String, Tx> getTxMap() {
return lock.read(() -> txMap);
}
public Optional<Tx> findTx(String txId) {
return lock.read(() -> {
Tx tx = getTxMap().get(txId);
if (tx != null)
return Optional.of(tx);
else
return Optional.empty();
});
}
public Set<Tx> getTransactions() {
return lock.read(() -> getTxMap().entrySet().stream().map(Map.Entry::getValue).collect(Collectors.toSet()));
}
public Set<Tx> getFeeTransactions() {
return lock.read(() -> getTxMap().entrySet().stream().filter(e -> e.getValue().getBurntFee() > 0).map(Map.Entry::getValue).collect(Collectors.toSet()));
}
public boolean hasTxBurntFee(String txId) {
return lock.read(() -> getTx(txId).map(Tx::getBurntFee).filter(fee -> fee > 0).isPresent());
}
public boolean containsTx(String txId) {
return lock.read(() -> getTx(txId).isPresent());
}
@Nullable
public Tx getGenesisTx() {
return genesisTx;
}
public String getGenesisTxId() {
return genesisTxId;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Read access: TxOutput
///////////////////////////////////////////////////////////////////////////////////////////
private Optional<TxOutput> getUnspentTxOutput(TxIdIndexTuple txIdIndexTuple) {
return lock.read(() -> unspentTxOutputsMap.entrySet().stream()
.filter(e -> e.getKey().equals(txIdIndexTuple))
@ -337,55 +422,10 @@ public class BsqBlockChain implements PersistableEnvelope {
return lock.read(() -> getSpendableTxOutput(txId, index).isPresent());
}
public boolean hasTxBurntFee(String txId) {
return lock.read(() -> getTx(txId).map(Tx::getBurntFee).filter(fee -> fee > 0).isPresent());
}
public Optional<TxType> getTxType(String txId) {
return lock.read(() -> getTx(txId).map(Tx::getTxType));
}
public boolean containsTx(String txId) {
return lock.read(() -> getTx(txId).isPresent());
}
public Optional<Tx> findTx(String txId) {
Tx tx = getTxMap().get(txId);
if (tx != null)
return Optional.of(tx);
else
return Optional.empty();
}
public int getChainHeadHeight() {
return lock.read(() -> chainHeadHeight);
}
public Map<String, Tx> getTxMap() {
return lock.read(() -> txMap);
}
List<BsqBlock> getResetBlocksFrom(int fromBlockHeight) {
return lock.read(() -> {
BsqBlockChain clone = getClone();
List<BsqBlock> filtered = clone.bsqBlocks.stream()
.filter(block -> block.getHeight() >= fromBlockHeight)
.collect(Collectors.toList());
filtered.forEach(BsqBlock::reset);
return filtered;
});
}
public Coin getTotalBurntFee() {
return lock.read(() -> Coin.valueOf(getTxMap().entrySet().stream().mapToLong(e -> e.getValue().getBurntFee()).sum()));
}
public Set<Tx> getFeeTransactions() {
return lock.read(() -> getTxMap().entrySet().stream().filter(e -> e.getValue().getBurntFee() > 0).map(Map.Entry::getValue).collect(Collectors.toSet()));
}
public Coin getIssuedAmount() {
return lock.read(() -> BsqBlockChain.GENESIS_TOTAL_SUPPLY);
private Set<TxOutput> getAllTxOutputs() {
return txMap.values().stream()
.flatMap(tx -> tx.getOutputs().stream())
.collect(Collectors.toSet());
}
public Set<TxOutput> getUnspentTxOutputs() {
@ -396,24 +436,43 @@ public class BsqBlockChain implements PersistableEnvelope {
return lock.read(() -> getAllTxOutputs().stream().filter(e -> e.isVerified() && !e.isUnspent()).collect(Collectors.toSet()));
}
public Set<Tx> getTransactions() {
return lock.read(() -> getTxMap().entrySet().stream().map(Map.Entry::getValue).collect(Collectors.toSet()));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Package scope read access
///////////////////////////////////////////////////////////////////////////////////////////
Optional<TxOutput> getSpendableTxOutput(String txId, int index) {
return lock.read(() -> getSpendableTxOutput(new TxIdIndexTuple(txId, index)));
}
Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
return lock.read(() -> getUnspentTxOutput(txIdIndexTuple)
.filter(this::isTxOutputMature));
}
private Optional<TxOutput> getSpendableTxOutput(String txId, int index) {
return lock.read(() -> getSpendableTxOutput(new TxIdIndexTuple(txId, index)));
}
//TODO
// for genesis we don't need it and for issuance we need more implemented first
private boolean isTxOutputMature(TxOutput spendingTxOutput) {
return lock.read(() -> true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Read access: TxType
///////////////////////////////////////////////////////////////////////////////////////////
public Optional<TxType> getTxType(String txId) {
return lock.read(() -> getTx(txId).map(Tx::getTxType));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Read access: Misc
///////////////////////////////////////////////////////////////////////////////////////////
public Coin getTotalBurntFee() {
return lock.read(() -> Coin.valueOf(getTxMap().entrySet().stream().mapToLong(e -> e.getValue().getBurntFee()).sum()));
}
public Coin getIssuedAmount() {
return lock.read(() -> BsqBlockChain.GENESIS_TOTAL_SUPPLY);
}
long getCreateCompensationRequestFee(int blockHeight) {
return lock.read(() -> {
long fee = -1;
@ -462,54 +521,7 @@ public class BsqBlockChain implements PersistableEnvelope {
.collect(Collectors.toSet()));
}
//TODO
// for genesis we don't need it and for issuance we need more implemented first
private boolean isTxOutputMature(TxOutput spendingTxOutput) {
return lock.read(() -> true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private Optional<Tx> getTx(String txId) {
return lock.read(() -> txMap.get(txId) != null ? Optional.of(txMap.get(txId)) : Optional.<Tx>empty());
}
private boolean isSnapshotHeight(int height) {
return isSnapshotHeight(genesisBlockHeight, height, SNAPSHOT_GRID);
}
private void maybeMakeSnapshot() {
lock.read(() -> {
if (isSnapshotHeight(getChainHeadHeight()) &&
(snapshotCandidate == null ||
snapshotCandidate.chainHeadHeight != getChainHeadHeight())) {
// At trigger event we store the latest snapshotCandidate to disc
if (snapshotCandidate != null) {
// We clone because storage is in a threaded context
final BsqBlockChain cloned = getClone(snapshotCandidate);
checkNotNull(storage, "storage must not be null");
storage.queueUpForSave(cloned);
// don't 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);
// don't access cloned anymore with methods as locks are transient!
log.debug("Cloned new snapshotCandidate at height " + snapshotCandidate.chainHeadHeight);
}
});
}
private Set<TxOutput> getAllTxOutputs() {
return txMap.values().stream()
.flatMap(tx -> tx.getOutputs().stream())
.collect(Collectors.toSet());
}
private void printDetails() {
void printDetails() {
log.debug("\nchainHeadHeight={}\n" +
" blocks.size={}\n" +
" txMap.size={}\n" +
@ -523,5 +535,12 @@ public class BsqBlockChain implements PersistableEnvelope {
compensationRequestFees.size(),
votingFees.size());
}
// Probably not needed anymore
public <T> T callFunctionWithWriteLock(Supplier<T> supplier) {
return lock.write(supplier);
}
}

View file

@ -24,6 +24,7 @@ import io.bisq.core.dao.blockchain.vo.util.TxIdIndexTuple;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -31,34 +32,44 @@ import java.util.Optional;
/**
* Encapsulates read access to BsqBlockChain.
*/
public class ReadModel {
public class BsqBlockChainReadModel {
private BsqBlockChain bsqBlockChain;
@Inject
public ReadModel(BsqBlockChain bsqBlockChain) {
public BsqBlockChainReadModel(BsqBlockChain bsqBlockChain) {
this.bsqBlockChain = bsqBlockChain;
}
public Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
return bsqBlockChain.getSpendableTxOutput(txIdIndexTuple);
}
public Optional<TxOutput> getSpendableTxOutput(String txId, int index) {
return getSpendableTxOutput(new TxIdIndexTuple(txId, index));
}
public boolean isCompensationRequestPeriodValid(int blockHeight) {
return bsqBlockChain.isCompensationRequestPeriodValid(blockHeight);
}
public long getCreateCompensationRequestFee(int blockHeight) {
return bsqBlockChain.getCreateCompensationRequestFee(blockHeight);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Block
///////////////////////////////////////////////////////////////////////////////////////////
public int getChainHeadHeight() {
return bsqBlockChain.getChainHeadHeight();
}
public boolean containsBlock(BsqBlock bsqBlock) {
return bsqBlockChain.containsBlock(bsqBlock);
}
public List<BsqBlock> getResetBlocksFrom(int fromBlockHeight) {
return bsqBlockChain.getClonedBlocksFrom(fromBlockHeight);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Tx
///////////////////////////////////////////////////////////////////////////////////////////
public Map<String, Tx> getTxMap() {
return bsqBlockChain.getTxMap();
}
public Tx getGenesisTx() {
return bsqBlockChain.getGenesisTx();
}
public String getGenesisTxId() {
return bsqBlockChain.getGenesisTxId();
}
@ -67,31 +78,48 @@ public class ReadModel {
return bsqBlockChain.getGenesisBlockHeight();
}
public boolean containsBlock(BsqBlock bsqBlock) {
return bsqBlockChain.containsBlock(bsqBlock);
public boolean containsTx(String txId) {
return bsqBlockChain.containsTx(txId);
}
public List<BsqBlock> getResetBlocksFrom(int fromBlockHeight) {
return bsqBlockChain.getResetBlocksFrom(fromBlockHeight);
///////////////////////////////////////////////////////////////////////////////////////////
// TxOutput
///////////////////////////////////////////////////////////////////////////////////////////
public Optional<TxOutput> getSpendableTxOutput(TxIdIndexTuple txIdIndexTuple) {
return bsqBlockChain.getSpendableTxOutput(txIdIndexTuple);
}
public Map<String, Tx> getTxMap() {
return bsqBlockChain.getTxMap();
public Optional<TxOutput> getSpendableTxOutput(String txId, int index) {
return getSpendableTxOutput(new TxIdIndexTuple(txId, index));
}
public Tx getGenesisTx() {
return bsqBlockChain.getGenesisTx();
public boolean isTxOutputSpendable(String txId, int index) {
return bsqBlockChain.isTxOutputSpendable(txId, index);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
public boolean isCompensationRequestPeriodValid(int blockHeight) {
return bsqBlockChain.isCompensationRequestPeriodValid(blockHeight);
}
public long getCreateCompensationRequestFee(int blockHeight) {
return bsqBlockChain.getCreateCompensationRequestFee(blockHeight);
}
public Coin getIssuedAmount() {
return bsqBlockChain.getIssuedAmount();
}
public boolean containsTx(String txId) {
return bsqBlockChain.containsTx(txId);
public LinkedList<BsqBlock> getBsqBlocks() {
return bsqBlockChain.getBsqBlocks();
}
public boolean isTxOutputSpendable(String txId, int index) {
return bsqBlockChain.isTxOutputSpendable(txId, index);
public BsqBlockChain getClone() {
return bsqBlockChain.getClone();
}
}

View file

@ -17,7 +17,6 @@
package io.bisq.core.dao.blockchain;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
@ -27,33 +26,53 @@ import javax.inject.Inject;
/**
* Encapsulates write access to BsqBlockChain.
*/
public class WriteModel {
public class BsqBlockChainWriteModel {
private BsqBlockChain bsqBlockChain;
@Inject
public WriteModel(BsqBlockChain bsqBlockChain) {
public BsqBlockChainWriteModel(BsqBlockChain bsqBlockChain) {
this.bsqBlockChain = bsqBlockChain;
}
public void removeUnspentTxOutput(TxOutput spendableTxOutput) {
bsqBlockChain.removeUnspentTxOutput(spendableTxOutput);
///////////////////////////////////////////////////////////////////////////////////////////
// Block
///////////////////////////////////////////////////////////////////////////////////////////
public void addBlock(BsqBlock bsqBlock) {
bsqBlockChain.addBlock(bsqBlock);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Tx
///////////////////////////////////////////////////////////////////////////////////////////
public void setGenesisTx(Tx tx) {
bsqBlockChain.setGenesisTx(tx);
}
public void addTxToMap(Tx tx) {
bsqBlockChain.addTxToMap(tx);
}
///////////////////////////////////////////////////////////////////////////////////////////
// TxOutput
///////////////////////////////////////////////////////////////////////////////////////////
public void addUnspentTxOutput(TxOutput txOutput) {
bsqBlockChain.addUnspentTxOutput(txOutput);
}
public void setGenesisTx(Tx tx) {
bsqBlockChain.setGenesisTx(tx);
public void removeUnspentTxOutput(TxOutput spendableTxOutput) {
bsqBlockChain.removeUnspentTxOutput(spendableTxOutput);
}
public void addBlock(BsqBlock bsqBlock) throws BlockNotConnectingException {
bsqBlockChain.addBlock(bsqBlock);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
public void setCreateCompensationRequestFee(long value, int genesisBlockHeight) {
bsqBlockChain.setCreateCompensationRequestFee(value, genesisBlockHeight);

View file

@ -17,19 +17,81 @@
package io.bisq.core.dao.blockchain;
import javax.inject.Inject;
import io.bisq.common.proto.persistable.PersistenceProtoResolver;
import io.bisq.common.storage.Storage;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import lombok.extern.slf4j.Slf4j;
//TODO move snapshot related code from bsqBlockChain here
public class SnapshotManager {
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Manages snapshots of the BsqBlockChain.
*/
//TODO add tests; check if current logic is correct.
@Slf4j
public class SnapshotManager implements BsqBlockChain.Listener {
private static final int SNAPSHOT_GRID = 100;
private final BsqBlockChain bsqBlockChain;
private final Storage<BsqBlockChain> storage;
private BsqBlockChain snapshotCandidate;
@Inject
public SnapshotManager(BsqBlockChain bsqBlockChain) {
public SnapshotManager(BsqBlockChain bsqBlockChain,
PersistenceProtoResolver persistenceProtoResolver,
@Named(Storage.STORAGE_DIR) File storageDir) {
this.bsqBlockChain = bsqBlockChain;
storage = new Storage<>(storageDir, persistenceProtoResolver);
bsqBlockChain.addListener(this);
}
public void applySnapshot() {
bsqBlockChain.applySnapshot();
checkNotNull(storage, "storage must not be null");
BsqBlockChain snapshot = storage.initAndGetPersistedWithFileName("BsqBlockChain", 100);
if (snapshot != null) {
log.info("applySnapshot snapshot.chainHeadHeight=" + snapshot.getChainHeadHeight());
bsqBlockChain.applySnapshot(snapshot);
} else {
log.info("Try to apply snapshot but no stored snapshot available");
}
bsqBlockChain.printDetails();
}
private int getSnapshotHeight(int genesisHeight, int height, int grid) {
return Math.round(Math.max(genesisHeight + 3 * grid, height) / grid) * grid - grid;
}
private boolean isSnapshotHeight(int height) {
return isSnapshotHeight(bsqBlockChain.getGenesisBlockHeight(), height, SNAPSHOT_GRID);
}
private boolean isSnapshotHeight(int genesisHeight, int height, int grid) {
return height % grid == 0 && height >= getSnapshotHeight(genesisHeight, height, grid);
}
@Override
public void onBlockAdded(BsqBlock bsqBlock) {
final int chainHeadHeight = bsqBlockChain.getChainHeadHeight();
if (isSnapshotHeight(chainHeadHeight) &&
(snapshotCandidate == null ||
snapshotCandidate.getChainHeadHeight() != chainHeadHeight)) {
// At trigger event we store the latest snapshotCandidate to disc
if (snapshotCandidate != null) {
// We clone because storage is in a threaded context
final BsqBlockChain cloned = bsqBlockChain.getClone(snapshotCandidate);
storage.queueUpForSave(cloned);
log.info("Saved snapshotCandidate to Disc at height " + chainHeadHeight);
}
// Now we clone and keep it in memory for the next trigger
snapshotCandidate = bsqBlockChain.getClone(bsqBlockChain);
// don't access cloned anymore with methods as locks are transient!
log.debug("Cloned new snapshotCandidate at height " + chainHeadHeight);
}
}
}

View file

@ -28,6 +28,7 @@ import io.bisq.common.storage.Storage;
import io.bisq.common.util.Utilities;
import io.bisq.core.dao.DaoOptionKeys;
import io.bisq.core.dao.blockchain.BsqBlockChain;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxType;
@ -45,18 +46,18 @@ import java.util.stream.Collectors;
@Slf4j
public class JsonBlockChainExporter {
private final BsqBlockChainReadModel bsqBlockChainReadModel;
private final boolean dumpBlockchainData;
private final BsqBlockChain bsqBlockChain;
private final ListeningExecutorService executor = Utilities.getListeningExecutorService("JsonExporter", 1, 1, 1200);
private File txDir, txOutputDir, bsqBlockChainDir;
private JsonFileManager txFileManager, txOutputFileManager, bsqBlockChainFileManager;
@Inject
public JsonBlockChainExporter(BsqBlockChain bsqBlockChain,
public JsonBlockChainExporter(BsqBlockChainReadModel bsqBlockChainReadModel,
@Named(Storage.STORAGE_DIR) File storageDir,
@Named(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA) boolean dumpBlockchainData) {
this.bsqBlockChain = bsqBlockChain;
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
this.dumpBlockchainData = dumpBlockchainData;
init(storageDir, dumpBlockchainData);
@ -104,7 +105,7 @@ public class JsonBlockChainExporter {
public void maybeExport() {
if (dumpBlockchainData) {
ListenableFuture<Void> future = executor.submit(() -> {
final BsqBlockChain bsqBlockChainClone = bsqBlockChain.getClone();
final BsqBlockChain bsqBlockChainClone = bsqBlockChainReadModel.getClone();
for (Tx tx : bsqBlockChainClone.getTxMap().values()) {
String txId = tx.getId();
JsonTxType txType = tx.getTxType() != TxType.UNDEFINED_TX_TYPE ? JsonTxType.valueOf(tx.getTxType().name()) : null;

View file

@ -20,9 +20,9 @@ package io.bisq.core.dao.node;
import com.google.inject.Inject;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.SnapshotManager;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.provider.fee.FeeService;
import io.bisq.network.p2p.P2PService;
import io.bisq.network.p2p.P2PServiceListener;
@ -42,13 +42,12 @@ public abstract class BsqNode {
@SuppressWarnings("WeakerAccess")
protected final P2PService p2PService;
protected final ReadModel readModel;
protected final BsqBlockChainReadModel bsqBlockChainReadModel;
@SuppressWarnings("WeakerAccess")
protected final List<BsqBlockChainListener> bsqBlockChainListeners = new ArrayList<>();
private final String genesisTxId;
private final int genesisBlockHeight;
private final SnapshotManager snapshotManager;
@org.jetbrains.annotations.NotNull
@Getter
protected boolean parseBlockchainComplete;
@SuppressWarnings("WeakerAccess")
@ -60,22 +59,22 @@ public abstract class BsqNode {
@SuppressWarnings("WeakerAccess")
@Inject
public BsqNode(WriteModel writeModel,
ReadModel readModel,
public BsqNode(BsqBlockChainWriteModel bsqBlockChainWriteModel,
BsqBlockChainReadModel bsqBlockChainReadModel,
SnapshotManager snapshotManager,
P2PService p2PService,
FeeService feeService) {
this.p2PService = p2PService;
this.readModel = readModel;
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
genesisTxId = readModel.getGenesisTxId();
genesisBlockHeight = readModel.getGenesisBlockHeight();
genesisTxId = bsqBlockChainReadModel.getGenesisTxId();
genesisBlockHeight = bsqBlockChainReadModel.getGenesisBlockHeight();
this.snapshotManager = snapshotManager;
writeModel.setCreateCompensationRequestFee(feeService.getCreateCompensationRequestFee().value,
bsqBlockChainWriteModel.setCreateCompensationRequestFee(feeService.getCreateCompensationRequestFee().value,
genesisBlockHeight);
writeModel.setVotingFee(feeService.getVotingTxFee().value,
bsqBlockChainWriteModel.setVotingFee(feeService.getVotingTxFee().value,
genesisBlockHeight);
}
@ -156,7 +155,7 @@ public abstract class BsqNode {
@SuppressWarnings("WeakerAccess")
protected int getStartBlockHeight() {
final int startBlockHeight = Math.max(genesisBlockHeight, readModel.getChainHeadHeight() + 1);
final int startBlockHeight = Math.max(genesisBlockHeight, bsqBlockChainReadModel.getChainHeadHeight() + 1);
log.info("Start parse blocks:\n" +
" Start block height={}\n" +
" Genesis txId={}\n" +
@ -165,7 +164,7 @@ public abstract class BsqNode {
startBlockHeight,
genesisTxId,
genesisBlockHeight,
readModel.getChainHeadHeight());
bsqBlockChainReadModel.getChainHeadHeight());
return startBlockHeight;
}

View file

@ -18,11 +18,11 @@
package io.bisq.core.dao.node;
import io.bisq.common.app.DevEnv;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxInput;
import io.bisq.core.dao.node.consensus.BsqTxVerification;
import io.bisq.core.dao.node.consensus.GenesisTxVerification;
import io.bisq.core.dao.node.consensus.BsqBlockController;
import io.bisq.core.dao.node.consensus.BsqTxController;
import io.bisq.core.dao.node.consensus.GenesisTxController;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.concurrent.Immutable;
@ -44,9 +44,9 @@ import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
@Immutable
public abstract class BsqParser {
protected final WriteModel writeModel;
private final GenesisTxVerification genesisTxVerification;
private final BsqTxVerification bsqTxVerification;
protected final BsqBlockController bsqBlockController;
private final GenesisTxController genesisTxController;
private final BsqTxController bsqTxController;
///////////////////////////////////////////////////////////////////////////////////////////
@ -55,12 +55,12 @@ public abstract class BsqParser {
@SuppressWarnings("WeakerAccess")
@Inject
public BsqParser(WriteModel writeModel,
GenesisTxVerification genesisTxVerification,
BsqTxVerification bsqTxVerification) {
this.writeModel = writeModel;
this.genesisTxVerification = genesisTxVerification;
this.bsqTxVerification = bsqTxVerification;
public BsqParser(BsqBlockController bsqBlockController,
GenesisTxController genesisTxController,
BsqTxController bsqTxController) {
this.bsqBlockController = bsqBlockController;
this.genesisTxController = genesisTxController;
this.bsqTxController = bsqTxController;
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -70,8 +70,8 @@ public abstract class BsqParser {
protected void checkForGenesisTx(int blockHeight,
List<Tx> bsqTxsInBlock,
Tx tx) {
if (genesisTxVerification.isGenesisTx(tx, blockHeight)) {
genesisTxVerification.applyStateChange(tx);
if (genesisTxController.isGenesisTx(tx, blockHeight)) {
genesisTxController.applyStateChange(tx);
bsqTxsInBlock.add(tx);
}
}
@ -122,7 +122,7 @@ public abstract class BsqParser {
// we check if we have any valid BSQ from that tx set
bsqTxsInBlock.addAll(txsWithoutInputsFromSameBlock.stream()
.filter(tx -> bsqTxVerification.isBsqTx(blockHeight, tx))
.filter(tx -> bsqTxController.isBsqTx(blockHeight, tx))
.collect(Collectors.toList()));
log.debug("Parsing of all txsWithoutInputsFromSameBlock is done.");

View file

@ -0,0 +1,65 @@
/*
* 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.node.consensus;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import lombok.extern.slf4j.Slf4j;
import javax.inject.Inject;
import java.util.LinkedList;
@Slf4j
public class BsqBlockController {
private final BsqBlockChainWriteModel bsqBlockChainWriteModel;
private final BsqBlockChainReadModel bsqBlockChainReadModel;
@Inject
public BsqBlockController(BsqBlockChainWriteModel bsqBlockChainWriteModel, BsqBlockChainReadModel bsqBlockChainReadModel) {
this.bsqBlockChainWriteModel = bsqBlockChainWriteModel;
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
}
public void addBlockIfValid(BsqBlock bsqBlock) throws BlockNotConnectingException {
LinkedList<BsqBlock> bsqBlocks = bsqBlockChainReadModel.getBsqBlocks();
if (!bsqBlocks.contains(bsqBlock)) {
if (isBlockConnecting(bsqBlock, bsqBlocks)) {
bsqBlockChainWriteModel.addBlock(bsqBlock);
} else {
log.warn("addBlock called with a not connecting block:\n" +
"height()={}, hash()={}, head.height()={}, head.hash()={}",
bsqBlock.getHeight(), bsqBlock.getHash(), bsqBlocks.getLast().getHeight(), bsqBlocks.getLast().getHash());
throw new BlockNotConnectingException(bsqBlock);
}
} else {
log.warn("We got that block already. Ignore the call.");
}
}
private boolean isBlockConnecting(BsqBlock bsqBlock, LinkedList<BsqBlock> bsqBlocks) {
// Case 1: bsqBlocks is empty
// Case 2: bsqBlocks not empty. Last block must match new blocks getPreviousBlockHash and
// height of last block +1 must be new blocks height
return bsqBlocks.isEmpty() ||
(bsqBlocks.getLast().getHash().equals(bsqBlock.getPreviousBlockHash()) &&
bsqBlocks.getLast().getHeight() + 1 == bsqBlock.getHeight());
}
}

View file

@ -29,25 +29,25 @@ import javax.inject.Inject;
* Verifies if a given transaction is a BSQ transaction.
*/
@Slf4j
public class BsqTxVerification {
public class BsqTxController {
private final TxInputsVerification txInputsVerification;
private final TxOutputsVerification txOutputsVerification;
private final TxInputsController txInputsController;
private final TxOutputsController txOutputsController;
@Inject
public BsqTxVerification(TxInputsVerification txInputsVerification,
TxOutputsVerification txOutputsVerification) {
this.txInputsVerification = txInputsVerification;
this.txOutputsVerification = txOutputsVerification;
public BsqTxController(TxInputsController txInputsController,
TxOutputsController txOutputsController) {
this.txInputsController = txInputsController;
this.txOutputsController = txOutputsController;
}
public boolean isBsqTx(int blockHeight, Tx tx) {
BsqInputBalance bsqInputBalance = txInputsVerification.getBsqInputBalance(tx, blockHeight);
BsqInputBalance bsqInputBalance = txInputsController.getBsqInputBalance(tx, blockHeight);
final boolean bsqInputBalancePositive = bsqInputBalance.isPositive();
if (bsqInputBalancePositive) {
txInputsVerification.applyStateChange(tx);
txOutputsVerification.iterate(tx, blockHeight, bsqInputBalance);
txInputsController.applyStateChange(tx);
txOutputsController.iterate(tx, blockHeight, bsqInputBalance);
}
// Lets check if we have left over BSQ (burned fees)

View file

@ -18,7 +18,7 @@
package io.bisq.core.dao.node.consensus;
import io.bisq.common.app.Version;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxOutputType;
@ -31,23 +31,23 @@ import static com.google.common.base.Preconditions.checkArgument;
/**
* Verifies if OP_RETURN data matches rules for a compensation request tx and applies state change.
*/
public class CompensationRequestVerification {
private final ReadModel readModel;
public class CompensationRequestController {
private final BsqBlockChainReadModel bsqBlockChainReadModel;
@Inject
public CompensationRequestVerification(ReadModel readModel) {
this.readModel = readModel;
public CompensationRequestController(BsqBlockChainReadModel bsqBlockChainReadModel) {
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
}
public boolean verify(byte[] opReturnData, long bsqFee, int blockHeight, TxOutputsVerification.MutableState mutableState) {
public boolean verify(byte[] opReturnData, long bsqFee, int blockHeight, TxOutputsController.MutableState mutableState) {
return mutableState.getCompRequestIssuanceOutputCandidate() != null &&
opReturnData.length == 22 &&
Version.COMPENSATION_REQUEST_VERSION == opReturnData[1] &&
bsqFee == readModel.getCreateCompensationRequestFee(blockHeight) &&
readModel.isCompensationRequestPeriodValid(blockHeight);
bsqFee == bsqBlockChainReadModel.getCreateCompensationRequestFee(blockHeight) &&
bsqBlockChainReadModel.isCompensationRequestPeriodValid(blockHeight);
}
public void applyStateChange(Tx tx, TxOutput opReturnTxOutput, TxOutputsVerification.MutableState mutableState) {
public void applyStateChange(Tx tx, TxOutput opReturnTxOutput, TxOutputsController.MutableState mutableState) {
opReturnTxOutput.setTxOutputType(TxOutputType.COMPENSATION_REQUEST_OP_RETURN_OUTPUT);
checkArgument(mutableState.getCompRequestIssuanceOutputCandidate() != null, "mutableState.getCompRequestIssuanceOutputCandidate() must nto be null");
mutableState.getCompRequestIssuanceOutputCandidate().setTxOutputType(TxOutputType.COMPENSATION_REQUEST_ISSUANCE_CANDIDATE_OUTPUT);

View file

@ -18,7 +18,7 @@
package io.bisq.core.dao.node.consensus;
import io.bisq.core.dao.DaoOptionKeys;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxType;
@ -28,17 +28,17 @@ import javax.inject.Named;
/**
* Verifies if a given transaction is a BSQ genesis transaction.
*/
public class GenesisTxVerification {
public class GenesisTxController {
private final WriteModel writeModel;
private final BsqBlockChainWriteModel bsqBlockChainWriteModel;
private final String genesisTxId;
private final int genesisBlockHeight;
@Inject
public GenesisTxVerification(WriteModel writeModel,
@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
@Named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) int genesisBlockHeight) {
this.writeModel = writeModel;
public GenesisTxController(BsqBlockChainWriteModel bsqBlockChainWriteModel,
@Named(DaoOptionKeys.GENESIS_TX_ID) String genesisTxId,
@Named(DaoOptionKeys.GENESIS_BLOCK_HEIGHT) int genesisBlockHeight) {
this.bsqBlockChainWriteModel = bsqBlockChainWriteModel;
this.genesisTxId = genesisTxId;
this.genesisBlockHeight = genesisBlockHeight;
}
@ -51,11 +51,11 @@ public class GenesisTxVerification {
tx.getOutputs().forEach(txOutput -> {
txOutput.setUnspent(true);
txOutput.setVerified(true);
writeModel.addUnspentTxOutput(txOutput);
bsqBlockChainWriteModel.addUnspentTxOutput(txOutput);
});
tx.setTxType(TxType.GENESIS);
writeModel.setGenesisTx(tx);
writeModel.addTxToMap(tx);
bsqBlockChainWriteModel.setGenesisTx(tx);
bsqBlockChainWriteModel.addTxToMap(tx);
}
}

View file

@ -22,12 +22,12 @@ import lombok.extern.slf4j.Slf4j;
import javax.inject.Inject;
@Slf4j
public class IssuanceVerification {
public class IssuanceController {
/* private static final long MIN_BSQ_ISSUANCE_AMOUNT = 1000;
private static final long MAX_BSQ_ISSUANCE_AMOUNT = 10_000_000;*/
@Inject
public IssuanceVerification() {
public IssuanceController() {
}
/* public boolean maybeProcessData(Tx tx) {

View file

@ -31,18 +31,18 @@ import static com.google.common.base.Preconditions.checkArgument;
* Verifies if a given transaction is a BSQ OP_RETURN transaction.
*/
@Slf4j
public class OpReturnVerification {
private final CompensationRequestVerification compensationRequestVerification;
private final VotingVerification votingVerification;
public class OpReturnController {
private final CompensationRequestController compensationRequestController;
private final VotingController votingController;
@Inject
public OpReturnVerification(CompensationRequestVerification compensationRequestVerification,
VotingVerification votingVerification) {
this.compensationRequestVerification = compensationRequestVerification;
this.votingVerification = votingVerification;
public OpReturnController(CompensationRequestController compensationRequestController,
VotingController votingController) {
this.compensationRequestController = compensationRequestController;
this.votingController = votingController;
}
public void process(TxOutput txOutput, Tx tx, int index, long bsqFee, int blockHeight, TxOutputsVerification.MutableState mutableState) {
public void process(TxOutput txOutput, Tx tx, int index, long bsqFee, int blockHeight, TxOutputsController.MutableState mutableState) {
final long txOutputValue = txOutput.getValue();
// A BSQ OP_RETURN has to be the last output, the txOutputValue has to be 0 as well as there have to be a BSQ fee.
if (txOutputValue == 0 && index == tx.getOutputs().size() - 1 && bsqFee > 0) {
@ -53,8 +53,8 @@ public class OpReturnVerification {
// Check with the type byte which kind of OP_RETURN we have.
switch (opReturnData[0]) {
case OpReturnTypes.COMPENSATION_REQUEST:
if (compensationRequestVerification.verify(opReturnData, bsqFee, blockHeight, mutableState)) {
compensationRequestVerification.applyStateChange(tx, txOutput, mutableState);
if (compensationRequestController.verify(opReturnData, bsqFee, blockHeight, mutableState)) {
compensationRequestController.applyStateChange(tx, txOutput, mutableState);
}
case OpReturnTypes.VOTE:
// TODO

View file

@ -19,9 +19,9 @@ package io.bisq.core.dao.node.consensus;
import javax.inject.Inject;
public class PeriodVerification {
public class PeriodController {
@Inject
public PeriodVerification() {
public PeriodController() {
}
}

View file

@ -17,8 +17,8 @@
package io.bisq.core.dao.node.consensus;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.vo.SpentInfo;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxInput;
@ -33,27 +33,27 @@ import java.util.Optional;
*/
@Slf4j
public class TxInputVerification {
public class TxInputController {
private final WriteModel writeModel;
private final ReadModel readModel;
private final BsqBlockChainWriteModel bsqBlockChainWriteModel;
private final BsqBlockChainReadModel bsqBlockChainReadModel;
@Inject
public TxInputVerification(WriteModel writeModel, ReadModel readModel) {
this.writeModel = writeModel;
this.readModel = readModel;
public TxInputController(BsqBlockChainWriteModel bsqBlockChainWriteModel, BsqBlockChainReadModel bsqBlockChainReadModel) {
this.bsqBlockChainWriteModel = bsqBlockChainWriteModel;
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
}
Optional<TxOutput> getOptionalSpendableTxOutput(TxInput input) {
// TODO check if Tuple indexes of inputs outputs are not messed up...
// Get spendable BSQ output for txIdIndexTuple... (get output used as input in tx if it's spendable BSQ)
return readModel.getSpendableTxOutput(input.getTxIdIndexTuple());
return bsqBlockChainReadModel.getSpendableTxOutput(input.getTxIdIndexTuple());
}
void applyStateChange(TxInput input, TxOutput spendableTxOutput, int blockHeight, Tx tx, int inputIndex) {
// The output is BSQ, set it as spent, update bsqBlockChain and add to available BSQ for this tx
spendableTxOutput.setUnspent(false);
writeModel.removeUnspentTxOutput(spendableTxOutput);
bsqBlockChainWriteModel.removeUnspentTxOutput(spendableTxOutput);
spendableTxOutput.setSpentInfo(new SpentInfo(blockHeight, tx.getId(), inputIndex));
input.setConnectedTxOutput(spendableTxOutput);
}

View file

@ -17,7 +17,7 @@
package io.bisq.core.dao.node.consensus;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxInput;
import io.bisq.core.dao.blockchain.vo.TxOutput;
@ -30,31 +30,31 @@ import java.util.Optional;
* Calculate the available BSQ balance from all inputs and apply state change.
*/
@Slf4j
public class TxInputsVerification {
public class TxInputsController {
private final WriteModel writeModel;
private final TxInputVerification txInputVerification;
private final BsqBlockChainWriteModel bsqBlockChainWriteModel;
private final TxInputController txInputController;
@Inject
public TxInputsVerification(WriteModel writeModel, TxInputVerification txInputVerification) {
this.writeModel = writeModel;
this.txInputVerification = txInputVerification;
public TxInputsController(BsqBlockChainWriteModel bsqBlockChainWriteModel, TxInputController txInputController) {
this.bsqBlockChainWriteModel = bsqBlockChainWriteModel;
this.txInputController = txInputController;
}
BsqTxVerification.BsqInputBalance getBsqInputBalance(Tx tx, int blockHeight) {
BsqTxVerification.BsqInputBalance bsqInputBalance = new BsqTxVerification.BsqInputBalance();
BsqTxController.BsqInputBalance getBsqInputBalance(Tx tx, int blockHeight) {
BsqTxController.BsqInputBalance bsqInputBalance = new BsqTxController.BsqInputBalance();
for (int inputIndex = 0; inputIndex < tx.getInputs().size(); inputIndex++) {
TxInput input = tx.getInputs().get(inputIndex);
final Optional<TxOutput> optionalSpendableTxOutput = txInputVerification.getOptionalSpendableTxOutput(input);
final Optional<TxOutput> optionalSpendableTxOutput = txInputController.getOptionalSpendableTxOutput(input);
if (optionalSpendableTxOutput.isPresent()) {
bsqInputBalance.add(optionalSpendableTxOutput.get().getValue());
txInputVerification.applyStateChange(input, optionalSpendableTxOutput.get(), blockHeight, tx, inputIndex);
txInputController.applyStateChange(input, optionalSpendableTxOutput.get(), blockHeight, tx, inputIndex);
}
}
return bsqInputBalance;
}
void applyStateChange(Tx tx) {
writeModel.addTxToMap(tx);
bsqBlockChainWriteModel.addTxToMap(tx);
}
}

View file

@ -17,7 +17,7 @@
package io.bisq.core.dao.node.consensus;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.blockchain.vo.TxOutput;
import io.bisq.core.dao.blockchain.vo.TxOutputType;
@ -33,22 +33,22 @@ import static com.google.common.base.Preconditions.checkArgument;
*/
@Slf4j
public class TxOutputVerification {
private final WriteModel writeModel;
private final OpReturnVerification opReturnVerification;
public class TxOutputController {
private final BsqBlockChainWriteModel bsqBlockChainWriteModel;
private final OpReturnController opReturnController;
@Inject
public TxOutputVerification(WriteModel writeModel, OpReturnVerification opReturnVerification) {
this.writeModel = writeModel;
this.opReturnVerification = opReturnVerification;
public TxOutputController(BsqBlockChainWriteModel bsqBlockChainWriteModel, OpReturnController opReturnController) {
this.bsqBlockChainWriteModel = bsqBlockChainWriteModel;
this.opReturnController = opReturnController;
}
void verify(Tx tx,
TxOutput txOutput,
int index,
int blockHeight,
BsqTxVerification.BsqInputBalance bsqInputBalance,
TxOutputsVerification.MutableState mutableState) {
BsqTxController.BsqInputBalance bsqInputBalance,
TxOutputsController.MutableState mutableState) {
final long txOutputValue = txOutput.getValue();
final long bsqInputBalanceValue = bsqInputBalance.getValue();
@ -86,7 +86,7 @@ public class TxOutputVerification {
}
} else {
// We got a OP_RETURN output.
opReturnVerification.process(txOutput, tx, index, bsqInputBalanceValue, blockHeight, mutableState);
opReturnController.process(txOutput, tx, index, bsqInputBalanceValue, blockHeight, mutableState);
}
} else {
@ -99,7 +99,7 @@ public class TxOutputVerification {
txOutput.setVerified(true);
txOutput.setUnspent(true);
txOutput.setTxOutputType(TxOutputType.BSQ_OUTPUT);
writeModel.addUnspentTxOutput(txOutput);
bsqBlockChainWriteModel.addUnspentTxOutput(txOutput);
}
private void applyStateChangeForBtcOutput(TxOutput txOutput) {

View file

@ -32,21 +32,21 @@ import java.util.List;
*/
@Slf4j
public class TxOutputsVerification {
public class TxOutputsController {
private final TxOutputVerification txOutputVerification;
private final TxOutputController txOutputController;
@Inject
public TxOutputsVerification(TxOutputVerification txOutputVerification) {
this.txOutputVerification = txOutputVerification;
public TxOutputsController(TxOutputController txOutputController) {
this.txOutputController = txOutputController;
}
void iterate(Tx tx, int blockHeight, BsqTxVerification.BsqInputBalance bsqInputBalance) {
void iterate(Tx tx, int blockHeight, BsqTxController.BsqInputBalance bsqInputBalance) {
// We use order of output index. An output is a BSQ utxo as long there is enough input value
final List<TxOutput> outputs = tx.getOutputs();
MutableState mutableState = new MutableState();
for (int index = 0; index < outputs.size(); index++) {
txOutputVerification.verify(tx, outputs.get(index), index, blockHeight, bsqInputBalance, mutableState);
txOutputController.verify(tx, outputs.get(index), index, blockHeight, bsqInputBalance, mutableState);
}
}

View file

@ -22,9 +22,9 @@ import lombok.extern.slf4j.Slf4j;
import javax.inject.Inject;
@Slf4j
public class VotingVerification {
public class VotingController {
@Inject
public VotingVerification() {
public VotingController() {
}
}

View file

@ -21,9 +21,9 @@ import com.google.inject.Inject;
import io.bisq.common.UserThread;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.SnapshotManager;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.json.JsonBlockChainExporter;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
@ -51,16 +51,16 @@ public class FullNode extends BsqNode {
@SuppressWarnings("WeakerAccess")
@Inject
public FullNode(WriteModel writeModel,
ReadModel readModel,
public FullNode(BsqBlockChainWriteModel bsqBlockChainWriteModel,
BsqBlockChainReadModel bsqBlockChainReadModel,
SnapshotManager snapshotManager,
P2PService p2PService,
FullNodeExecutor bsqFullNodeExecutor,
JsonBlockChainExporter jsonBlockChainExporter,
FeeService feeService,
FullNodeNetworkManager fullNodeNetworkManager) {
super(writeModel,
readModel,
super(bsqBlockChainWriteModel,
bsqBlockChainReadModel,
snapshotManager,
p2PService,
feeService);

View file

@ -20,14 +20,14 @@ package io.bisq.core.dao.node.full;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.neemre.btcdcli4j.core.domain.Block;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.exceptions.BsqBlockchainException;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.node.BsqParser;
import io.bisq.core.dao.node.consensus.BsqTxVerification;
import io.bisq.core.dao.node.consensus.GenesisTxVerification;
import io.bisq.core.dao.node.consensus.BsqBlockController;
import io.bisq.core.dao.node.consensus.BsqTxController;
import io.bisq.core.dao.node.consensus.GenesisTxController;
import io.bisq.core.dao.node.full.rpc.RpcService;
import lombok.extern.slf4j.Slf4j;
@ -57,10 +57,10 @@ public class FullNodeParser extends BsqParser {
@Inject
public FullNodeParser(RpcService rpcService,
WriteModel writeModel,
GenesisTxVerification genesisTxVerification,
BsqTxVerification bsqTxVerification) {
super(writeModel, genesisTxVerification, bsqTxVerification);
BsqBlockController bsqBlockController,
GenesisTxController genesisTxController,
BsqTxController bsqTxController) {
super(bsqBlockController, genesisTxController, bsqTxController);
this.rpcService = rpcService;
}
@ -95,7 +95,7 @@ public class FullNodeParser extends BsqParser {
btcdBlock.getHash(),
btcdBlock.getPreviousBlockHash(),
ImmutableList.copyOf(bsqTxsInBlock));
writeModel.addBlock(bsqBlock);
bsqBlockController.addBlockIfValid(bsqBlock);
log.info("parseBlock took {} ms at blockHeight {}; bsqTxsInBlock.size={}",
System.currentTimeMillis() - startTs, bsqBlock.getHeight(), bsqTxsInBlock.size());
return bsqBlock;

View file

@ -3,7 +3,7 @@ package io.bisq.core.dao.node.full.network;
import io.bisq.common.UserThread;
import io.bisq.common.app.Log;
import io.bisq.common.proto.network.NetworkEnvelope;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.node.messages.GetBsqBlocksRequest;
import io.bisq.core.dao.node.messages.NewBsqBlockBroadcastMessage;
@ -35,7 +35,7 @@ public class FullNodeNetworkManager implements MessageListener, PeerManager.List
private final NetworkNode networkNode;
private final PeerManager peerManager;
private final Broadcaster broadcaster;
private final ReadModel readModel;
private final BsqBlockChainReadModel bsqBlockChainReadModel;
// Key is connection UID
private final Map<String, GetBsqBlocksRequestHandler> getBlocksRequestHandlers = new HashMap<>();
@ -50,11 +50,11 @@ public class FullNodeNetworkManager implements MessageListener, PeerManager.List
public FullNodeNetworkManager(NetworkNode networkNode,
PeerManager peerManager,
Broadcaster broadcaster,
ReadModel readModel) {
BsqBlockChainReadModel bsqBlockChainReadModel) {
this.networkNode = networkNode;
this.peerManager = peerManager;
this.broadcaster = broadcaster;
this.readModel = readModel;
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
// seedNodeAddresses can be empty (in case there is only 1 seed node, the seed node starting up has no other seed nodes)
networkNode.addMessageListener(this);
@ -113,7 +113,7 @@ public class FullNodeNetworkManager implements MessageListener, PeerManager.List
final String uid = connection.getUid();
if (!getBlocksRequestHandlers.containsKey(uid)) {
GetBsqBlocksRequestHandler requestHandler = new GetBsqBlocksRequestHandler(networkNode,
readModel,
bsqBlockChainReadModel,
new GetBsqBlocksRequestHandler.Listener() {
@Override
public void onComplete() {

View file

@ -6,7 +6,7 @@ import com.google.common.util.concurrent.SettableFuture;
import io.bisq.common.Timer;
import io.bisq.common.UserThread;
import io.bisq.common.app.Log;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.node.messages.GetBsqBlocksRequest;
import io.bisq.core.dao.node.messages.GetBsqBlocksResponse;
@ -43,7 +43,7 @@ class GetBsqBlocksRequestHandler {
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode;
private final ReadModel readModel;
private final BsqBlockChainReadModel bsqBlockChainReadModel;
private final Listener listener;
private Timer timeoutTimer;
private boolean stopped;
@ -53,9 +53,9 @@ class GetBsqBlocksRequestHandler {
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public GetBsqBlocksRequestHandler(NetworkNode networkNode, ReadModel readModel, Listener listener) {
public GetBsqBlocksRequestHandler(NetworkNode networkNode, BsqBlockChainReadModel bsqBlockChainReadModel, Listener listener) {
this.networkNode = networkNode;
this.readModel = readModel;
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
this.listener = listener;
}
@ -66,7 +66,7 @@ class GetBsqBlocksRequestHandler {
public void onGetBsqBlocksRequest(GetBsqBlocksRequest getBsqBlocksRequest, final Connection connection) {
Log.traceCall(getBsqBlocksRequest + "\n\tconnection=" + connection);
List<BsqBlock> bsqBlocks = readModel.getResetBlocksFrom(getBsqBlocksRequest.getFromBlockHeight());
List<BsqBlock> bsqBlocks = bsqBlockChainReadModel.getResetBlocksFrom(getBsqBlocksRequest.getFromBlockHeight());
final GetBsqBlocksResponse bsqBlocksResponse = new GetBsqBlocksResponse(bsqBlocks, getBsqBlocksRequest.getNonce());
log.debug("bsqBlocksResponse " + bsqBlocksResponse.getRequestNonce());

View file

@ -21,9 +21,9 @@ import com.google.inject.Inject;
import io.bisq.common.UserThread;
import io.bisq.common.handlers.ErrorMessageHandler;
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainWriteModel;
import io.bisq.core.dao.blockchain.SnapshotManager;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.node.BsqNode;
@ -58,15 +58,15 @@ public class LiteNode extends BsqNode {
@SuppressWarnings("WeakerAccess")
@Inject
public LiteNode(WriteModel writeModel,
ReadModel readModel,
public LiteNode(BsqBlockChainWriteModel bsqBlockChainWriteModel,
BsqBlockChainReadModel bsqBlockChainReadModel,
SnapshotManager snapshotManager,
P2PService p2PService,
LiteNodeExecutor bsqLiteNodeExecutor,
FeeService feeService,
LiteNodeNetworkManager liteNodeNetworkManager) {
super(writeModel,
readModel,
super(bsqBlockChainWriteModel,
bsqBlockChainReadModel,
snapshotManager,
p2PService,
feeService);
@ -150,7 +150,7 @@ public class LiteNode extends BsqNode {
// We reset all mutable data in case the provider would not have done it.
bsqBlock.reset();
log.info("onNewBlockReceived: bsqBlock={}", bsqBlock.getHeight());
if (!readModel.containsBlock(bsqBlock)) {
if (!bsqBlockChainReadModel.containsBlock(bsqBlock)) {
bsqLiteNodeExecutor.parseBlock(bsqBlock,
this::notifyListenersOnNewBlock,
getErrorHandler());

View file

@ -17,13 +17,13 @@
package io.bisq.core.dao.node.lite;
import io.bisq.core.dao.blockchain.WriteModel;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
import io.bisq.core.dao.blockchain.vo.BsqBlock;
import io.bisq.core.dao.blockchain.vo.Tx;
import io.bisq.core.dao.node.BsqParser;
import io.bisq.core.dao.node.consensus.BsqTxVerification;
import io.bisq.core.dao.node.consensus.GenesisTxVerification;
import io.bisq.core.dao.node.consensus.BsqBlockController;
import io.bisq.core.dao.node.consensus.BsqTxController;
import io.bisq.core.dao.node.consensus.GenesisTxController;
import lombok.extern.slf4j.Slf4j;
import javax.inject.Inject;
@ -40,10 +40,10 @@ import java.util.function.Consumer;
public class LiteNodeParser extends BsqParser {
@Inject
public LiteNodeParser(WriteModel writeModel,
GenesisTxVerification genesisTxVerification,
BsqTxVerification bsqTxVerification) {
super(writeModel, genesisTxVerification, bsqTxVerification);
public LiteNodeParser(BsqBlockController bsqBlockController,
GenesisTxController genesisTxController,
BsqTxController bsqTxController) {
super(bsqBlockController, genesisTxController, bsqTxController);
}
void parseBsqBlocks(List<BsqBlock> bsqBlocks,
@ -62,6 +62,6 @@ public class LiteNodeParser extends BsqParser {
List<Tx> bsqTxsInBlock = new ArrayList<>();
bsqBlock.getTxs().forEach(tx -> checkForGenesisTx(blockHeight, bsqTxsInBlock, tx));
recursiveFindBsqTxs(bsqTxsInBlock, txList, blockHeight, 0, 5300);
writeModel.addBlock(bsqBlock);
bsqBlockController.addBlockIfValid(bsqBlock);
}
}

View file

@ -33,7 +33,7 @@ import io.bisq.core.btc.wallet.BtcWalletService;
import io.bisq.core.dao.DaoPeriodService;
import io.bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import io.bisq.core.dao.blockchain.BsqBlockChainListener;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.request.compensation.consensus.OpReturnData;
import io.bisq.core.dao.request.compensation.consensus.Restrictions;
import io.bisq.core.provider.fee.FeeService;
@ -68,7 +68,7 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
private final DaoPeriodService daoPeriodService;
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final ReadModel readModel;
private final BsqBlockChainReadModel bsqBlockChainReadModel;
private final Storage<CompensationRequestList> compensationRequestsStorage;
private final PublicKey signaturePubKey;
private final FeeService feeService;
@ -90,7 +90,7 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
DaoPeriodService daoPeriodService,
ReadModel readModel,
BsqBlockChainReadModel bsqBlockChainReadModel,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
KeyRing keyRing,
Storage<CompensationRequestList> compensationRequestsStorage,
@ -99,7 +99,7 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.daoPeriodService = daoPeriodService;
this.readModel = readModel;
this.bsqBlockChainReadModel = bsqBlockChainReadModel;
this.compensationRequestsStorage = compensationRequestsStorage;
this.feeService = feeService;
@ -235,7 +235,7 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
}
private boolean isInPhaseOrUnconfirmed(CompensationRequestPayload payload) {
return readModel.getTxMap().get(payload.getTxId()) == null || daoPeriodService.isTxInPhase(payload.getTxId(), DaoPeriodService.Phase.COMPENSATION_REQUESTS);
return bsqBlockChainReadModel.getTxMap().get(payload.getTxId()) == null || daoPeriodService.isTxInPhase(payload.getTxId(), DaoPeriodService.Phase.COMPENSATION_REQUESTS);
}
public boolean isMine(CompensationRequest compensationRequest) {
@ -336,7 +336,7 @@ public class CompensationRequestManager implements PersistedDataHost, BsqBlockCh
pastRequests.setPredicate(request -> daoPeriodService.isTxInPastCycle(request.getPayload().getTxId()));
activeRequests.setPredicate(compensationRequest -> {
return daoPeriodService.isTxInCurrentCycle(compensationRequest.getPayload().getTxId()) ||
(readModel.getTxMap().get(compensationRequest.getPayload().getTxId()) == null &&
(bsqBlockChainReadModel.getTxMap().get(compensationRequest.getPayload().getTxId()) == null &&
isMine(compensationRequest));
});
}

View file

@ -4,16 +4,16 @@ import com.neemre.btcdcli4j.core.BitcoindException;
import com.neemre.btcdcli4j.core.CommunicationException;
import com.neemre.btcdcli4j.core.domain.Block;
import io.bisq.common.proto.persistable.PersistenceProtoResolver;
import io.bisq.core.dao.blockchain.ReadModel;
import io.bisq.core.dao.blockchain.BsqBlockChainReadModel;
import io.bisq.core.dao.blockchain.exceptions.BlockNotConnectingException;
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.util.TxIdIndexTuple;
import io.bisq.core.dao.node.consensus.BsqTxVerification;
import io.bisq.core.dao.node.consensus.IssuanceVerification;
import io.bisq.core.dao.node.consensus.OpReturnVerification;
import io.bisq.core.dao.node.consensus.BsqTxController;
import io.bisq.core.dao.node.consensus.IssuanceController;
import io.bisq.core.dao.node.consensus.OpReturnController;
import io.bisq.core.dao.node.full.rpc.RpcService;
import mockit.Expectations;
import mockit.Injectable;
@ -39,12 +39,12 @@ import static org.junit.Assert.assertTrue;
@RunWith(JMockit.class)
public class FullNodeParserTest {
@Tested(availableDuringSetup = true)
ReadModel readModel;
BsqBlockChainReadModel bsqBlockChainReadModel;
@Tested(fullyInitialized = true, availableDuringSetup = true)
FullNodeParser fullNodeParser;
@Tested(fullyInitialized = true, availableDuringSetup = true)
BsqTxVerification bsqTxVerification;
BsqTxController bsqTxController;
@Injectable
PersistenceProtoResolver persistenceProtoResolver;
@ -58,9 +58,9 @@ public class FullNodeParserTest {
@Injectable
RpcService rpcService;
@Injectable
OpReturnVerification opReturnVerification;
OpReturnController opReturnController;
@Injectable
IssuanceVerification issuanceVerification;
IssuanceController issuanceController;
@Test
public void testIsBsqTx() {
@ -77,24 +77,24 @@ public class FullNodeParserTest {
// 1) - null, 0 -> not BSQ transaction
// 2) - 100, null -> BSQ transaction
// 3) - 0, 100 -> BSQ transaction
new Expectations(readModel) {{
readModel.getSpendableTxOutput(new TxIdIndexTuple("tx1", 0));
new Expectations(bsqBlockChainReadModel) {{
bsqBlockChainReadModel.getSpendableTxOutput(new TxIdIndexTuple("tx1", 0));
result = Optional.empty();
result = Optional.of(new TxOutput(0, 100, "txout1", null, null, null, height));
result = Optional.of(new TxOutput(0, 0, "txout1", null, null, null, height));
readModel.getSpendableTxOutput(new TxIdIndexTuple("tx1", 1));
bsqBlockChainReadModel.getSpendableTxOutput(new TxIdIndexTuple("tx1", 1));
result = Optional.of(new TxOutput(0, 0, "txout2", null, null, null, height));
result = Optional.empty();
result = Optional.of(new TxOutput(0, 100, "txout2", null, null, null, height));
}};
// First time there is no BSQ value to spend so it's not a bsq transaction
assertFalse(bsqTxVerification.isBsqTx(height, tx));
assertFalse(bsqTxController.isBsqTx(height, tx));
// Second time there is BSQ in the first txout
assertTrue(bsqTxVerification.isBsqTx(height, tx));
assertTrue(bsqTxController.isBsqTx(height, tx));
// Third time there is BSQ in the second txout
assertTrue(bsqTxVerification.isBsqTx(height, tx));
assertTrue(bsqTxController.isBsqTx(height, tx));
}
@Test
@ -170,25 +170,25 @@ public class FullNodeParserTest {
});
// Verify that the genesis tx has been added to the bsq blockchain with the correct issuance amount
assertTrue(readModel.getGenesisTx() == genesisTx);
assertTrue(readModel.getIssuedAmount().getValue() == issuance.getValue());
assertTrue(bsqBlockChainReadModel.getGenesisTx() == genesisTx);
assertTrue(bsqBlockChainReadModel.getIssuedAmount().getValue() == issuance.getValue());
// And that other txs are not added
assertFalse(readModel.containsTx(cbId199));
assertFalse(readModel.containsTx(cbId200));
assertFalse(readModel.containsTx(cbId201));
assertFalse(bsqBlockChainReadModel.containsTx(cbId199));
assertFalse(bsqBlockChainReadModel.containsTx(cbId200));
assertFalse(bsqBlockChainReadModel.containsTx(cbId201));
// But bsq txs are added
assertTrue(readModel.containsTx(bsqTx1Id));
TxOutput bsqOut1 = readModel.getSpendableTxOutput(bsqTx1Id, 0).get();
assertTrue(bsqBlockChainReadModel.containsTx(bsqTx1Id));
TxOutput bsqOut1 = bsqBlockChainReadModel.getSpendableTxOutput(bsqTx1Id, 0).get();
assertTrue(bsqOut1.isUnspent());
assertTrue(bsqOut1.getValue() == bsqTx1Value1);
TxOutput bsqOut2 = readModel.getSpendableTxOutput(bsqTx1Id, 1).get();
TxOutput bsqOut2 = bsqBlockChainReadModel.getSpendableTxOutput(bsqTx1Id, 1).get();
assertTrue(bsqOut2.isUnspent());
assertTrue(bsqOut2.getValue() == bsqTx1Value2);
assertFalse(readModel.isTxOutputSpendable(genesisId, 0));
assertTrue(readModel.isTxOutputSpendable(bsqTx1Id, 0));
assertTrue(readModel.isTxOutputSpendable(bsqTx1Id, 1));
assertFalse(bsqBlockChainReadModel.isTxOutputSpendable(genesisId, 0));
assertTrue(bsqBlockChainReadModel.isTxOutputSpendable(bsqTx1Id, 0));
assertTrue(bsqBlockChainReadModel.isTxOutputSpendable(bsqTx1Id, 1));
}
}