Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add rest api endpoints for bsq explorer #7207

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions core/src/main/java/bisq/core/dao/DaoFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.governance.period.CycleService;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.governance.proposal.MyProposalListService;
import bisq.core.dao.governance.proposal.ProposalConsensus;
import bisq.core.dao.governance.proposal.ProposalListPresentation;
Expand Down Expand Up @@ -66,6 +67,7 @@
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.Cycle;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.dao.state.model.governance.IssuanceType;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.Role;
Expand Down Expand Up @@ -805,4 +807,14 @@ public Set<String> getAllDonationAddresses() {
public boolean isParseBlockChainComplete() {
return daoStateService.isParseBlockChainComplete();
}

public long getIssuanceForCycle(Cycle cycle) {
return daoStateService.getEvaluatedProposalList().stream()
.filter(EvaluatedProposal::isAccepted)
.filter(evaluatedProposal -> cycleService.isTxInCycle(cycle, evaluatedProposal.getProposal().getTxId()))
.filter(e -> e.getProposal() instanceof IssuanceProposal)
.map(e -> (IssuanceProposal) e.getProposal())
.mapToLong(e -> e.getRequestedBsq().value)
.sum();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,16 @@ public boolean isTxInCycle(Cycle cycle, String txId) {
return daoStateService.getTx(txId).filter(tx -> isBlockHeightInCycle(tx.getBlockHeight(), cycle)).isPresent();
}

public boolean isBlockHeightInCycle(int blockHeight, Cycle cycle) {
return blockHeight >= cycle.getHeightOfFirstBlock() &&
blockHeight <= cycle.getHeightOfLastBlock();
}


///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////

private boolean isBlockHeightInCycle(int blockHeight, Cycle cycle) {
return blockHeight >= cycle.getHeightOfFirstBlock() &&
blockHeight <= cycle.getHeightOfLastBlock();
}

private Optional<Cycle> maybeCreateNewCycle(int blockHeight, LinkedList<Cycle> cycles) {
// We want to set the correct phase and cycle before we start parsing a new block.
// For Genesis block we did it already in the start method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ public double getRequiredThreshold(Proposal proposal) {
return daoStateService.getParamValueAsPercentDouble(proposal.getThresholdParam(), chainHeight);
}

// We use it in the RestApi do not have a dependency to javafx/
public List<Proposal> getTempProposalsAsArrayList() {
return new ArrayList<>(tempProposals);
}


///////////////////////////////////////////////////////////////////////////////////////////
// Private
Expand Down
59 changes: 59 additions & 0 deletions core/src/main/java/bisq/core/dao/state/DaoStateService.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
Expand All @@ -75,6 +77,7 @@ public class DaoStateService implements DaoSetupService {
@Getter
private boolean parseBlockChainComplete;
private boolean allowDaoStateChange;
private final Map<String, Set<String>> cachedTxIdSetByAddress = new HashMap<>();


///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -299,6 +302,10 @@ public void onParseBlockComplete(Block block) {
// generate a hash of the state.
allowDaoStateChange = false;
daoStateListeners.forEach(l -> l.onDaoStateChanged(block));

if (!block.getTxs().isEmpty()) {
cachedTxIdSetByAddress.clear();
}
}

// Called after parsing of all pending blocks is completed
Expand Down Expand Up @@ -493,6 +500,10 @@ public TreeMap<TxOutputKey, TxOutput> getUnspentTxOutputMap() {
return daoState.getUnspentTxOutputMap();
}

public TreeMap<TxOutputKey, SpentInfo> getSpentInfoMap() {
return daoState.getSpentInfoMap();
}

public void addUnspentTxOutput(TxOutput txOutput) {
assertDaoStateChange();
getUnspentTxOutputMap().put(txOutput.getKey(), txOutput);
Expand Down Expand Up @@ -1027,6 +1038,54 @@ public Optional<SpentInfo> getSpentInfo(TxOutput txOutput) {
}


///////////////////////////////////////////////////////////////////////////////////////////
// Addresses
///////////////////////////////////////////////////////////////////////////////////////////

public Map<String, Set<String>> getTxIdSetByAddress() {
// We clear it at each new (non-empty) block, so it gets recreated
if (!cachedTxIdSetByAddress.isEmpty()) {
return cachedTxIdSetByAddress;
}

Map<TxOutputKey, String> txIdByConnectedTxOutputKey = new HashMap<>();
// Add tx ids and addresses from tx outputs
getUnorderedTxStream()
.forEach(tx -> {
tx.getTxOutputs().stream()
.filter(this::isBsqTxOutputType)
.filter(txOutput -> txOutput.getAddress() != null)
.filter(txOutput -> !txOutput.getAddress().isEmpty())
.forEach(txOutput -> {
String address = txOutput.getAddress();
Set<String> txIdSet = cachedTxIdSetByAddress.getOrDefault(address, new HashSet<>());
String txId = tx.getId();
txIdSet.add(txId);
cachedTxIdSetByAddress.put(address, txIdSet);
tx.getTxInputs().forEach(txInput -> {
txIdByConnectedTxOutputKey.put(txInput.getConnectedTxOutputKey(), txId);
});
});
});

// Add tx ids and addresses from connected outputs (inputs)
getUnorderedTxOutputStream()
.filter(this::isBsqTxOutputType)
.filter(txOutput -> txOutput.getAddress() != null)
.filter(txOutput -> !txOutput.getAddress().isEmpty())
.forEach(txOutput -> {
String txId = txIdByConnectedTxOutputKey.get(txOutput.getKey());
if (txId != null) {
String address = txOutput.getAddress();
Set<String> txIdSet = cachedTxIdSetByAddress.getOrDefault(address, new HashSet<>());
txIdSet.add(txId);
cachedTxIdSetByAddress.put(address, txIdSet);
}
});

return cachedTxIdSetByAddress;
}

///////////////////////////////////////////////////////////////////////////////////////////
// Vote result data
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down
27 changes: 27 additions & 0 deletions core/src/main/java/bisq/core/offer/OfferBookService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package bisq.core.offer;

import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.util.JsonUtil;
Expand Down Expand Up @@ -213,6 +214,32 @@ public void addOfferBookChangedListener(OfferBookChangedListener offerBookChange
offerBookChangedListeners.add(offerBookChangedListener);
}

public List<OfferForJson> getOfferForJsonList() {
return getOffers().stream()
.map(offer -> {
try {
OfferDirection inverseDirection = offer.getDirection() == OfferDirection.BUY ? OfferDirection.SELL : OfferDirection.BUY;
OfferDirection offerDirection = CurrencyUtil.isCryptoCurrency(offer.getCurrencyCode()) ? inverseDirection : offer.getDirection();
return new OfferForJson(offerDirection,
offer.getCurrencyCode(),
offer.getMinAmount(),
offer.getAmount(),
offer.getPrice(),
offer.getDate(),
offer.getId(),
offer.isUseMarketBasedPrice(),
offer.getMarketPriceMargin(),
offer.getPaymentMethod()
);
} catch (Throwable t) {
// In case an offer was corrupted with null values we ignore it
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}


///////////////////////////////////////////////////////////////////////////////////////////
// Private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ public ObservableSet<TradeStatistics3> getObservableTradeStatisticsSet() {
return observableTradeStatisticsSet;
}

public List<TradeStatistics3> getTradeStatisticsList(long dateStart, long dateEnd) {
return observableTradeStatisticsSet.stream()
.filter(x -> x.getDateAsLong() > dateStart && x.getDateAsLong() <= dateEnd)
.sorted((o1, o2) -> (Long.compare(o2.getDateAsLong(), o1.getDateAsLong())))
.collect(Collectors.toList());
}

private void maybeDumpStatistics() {
if (!dumpStatistics) {
return;
Expand Down
16 changes: 16 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -863,11 +863,27 @@
<sha256 value="5de4666f7f6b003d982f48f18c8e22facef6707365a74e20df7cbad98c931dd7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-java" version="3.6.1">
<artifact name="protobuf-java-3.6.1.jar">
<sha256 value="fb66d913ff0578553b2e28a3338cbbbe2657e6cfe0e98d939f23aea219daf508"
origin="Generated by Gradle"/>
</artifact>
<artifact name="protobuf-java-3.6.1.pom">
<sha256 value="3d1cf96c47b28508d2290b86266f4d0e8ea534b0c7656825050d5bbce2fe51cc"
origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-parent" version="3.19.1">
<artifact name="protobuf-parent-3.19.1.pom">
<sha256 value="83d413b2a79d6357d2ca78fd623143424e8f6ecc72cfa83bf2ae2ae258a93a44" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-parent" version="3.6.1">
<artifact name="protobuf-parent-3.6.1.pom">
<sha256 value="b83819781441b566ca1d334813cdd203aa7e2f7ae2baad674dceb1fe2d4bb441"
origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protoc" version="3.19.1">
<artifact name="protoc-3.19.1-linux-x86_64.exe">
<sha256 value="0b1099f537d44ee862b0c3708f32f1d66cfd76e2e2c1437f0c57a98476c20605" origin="Generated by Gradle"/>
Expand Down
8 changes: 8 additions & 0 deletions restapi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ dependencies {
implementation(libs.google.guice) {
exclude(module: 'guava')
}
implementation(libs.bitcoinj) {
exclude(module: 'bcprov-jdk15on')
exclude(module: 'guava')
exclude(module: 'jsr305')
exclude(module: 'okhttp')
exclude(module: 'okio')
exclude(module: 'slf4j-api')
}

implementation libs.google.guava
implementation libs.google.guice
Expand Down
128 changes: 128 additions & 0 deletions restapi/src/main/java/bisq/restapi/BlockDataToJsonConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package bisq.restapi;

import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.PubKeyScript;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxType;

import com.google.common.io.BaseEncoding;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;



import bisq.restapi.dto.JsonBlock;
import bisq.restapi.dto.JsonScriptPubKey;
import bisq.restapi.dto.JsonSpentInfo;
import bisq.restapi.dto.JsonTx;
import bisq.restapi.dto.JsonTxInput;
import bisq.restapi.dto.JsonTxOutput;
import bisq.restapi.dto.JsonTxOutputType;
import bisq.restapi.dto.JsonTxType;

@Slf4j
public class BlockDataToJsonConverter {
public static JsonBlock getJsonBlock(DaoStateService daoStateService, Block block) {
List<JsonTx> jsonTxs = block.getTxs().stream()
.map(tx -> getJsonTx(daoStateService, tx))
.collect(Collectors.toList());
return new JsonBlock(block.getHeight(),
block.getTime(),
block.getHash(),
block.getPreviousBlockHash(),
jsonTxs);
}

public static JsonTx getJsonTx(DaoStateService daoStateService, Tx tx) {
JsonTxType jsonTxType = getJsonTxType(tx);
String jsonTxTypeDisplayString = getJsonTxTypeDisplayString(jsonTxType);
return new JsonTx(tx.getId(),
tx.getBlockHeight(),
tx.getBlockHash(),
tx.getTime(),
getJsonTxInputs(daoStateService, tx),
getJsonTxOutputs(daoStateService, tx),
jsonTxType,
jsonTxTypeDisplayString,
tx.getBurntFee(),
tx.getInvalidatedBsq(),
tx.getUnlockBlockHeight());
}

private static List<JsonTxInput> getJsonTxInputs(DaoStateService daoStateService, Tx tx) {
return tx.getTxInputs().stream()
.map(txInput -> {
Optional<TxOutput> optionalTxOutput = daoStateService.getConnectedTxOutput(txInput);
if (optionalTxOutput.isPresent()) {
TxOutput connectedTxOutput = optionalTxOutput.get();
boolean isBsqTxOutputType = daoStateService.isBsqTxOutputType(connectedTxOutput);
return new JsonTxInput(txInput.getConnectedTxOutputIndex(),
txInput.getConnectedTxOutputTxId(),
connectedTxOutput.getValue(),
isBsqTxOutputType,
connectedTxOutput.getAddress(),
tx.getTime());
} else {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

private static List<JsonTxOutput> getJsonTxOutputs(DaoStateService daoStateService, Tx tx) {
JsonTxType jsonTxType = getJsonTxType(tx);
String jsonTxTypeDisplayString = getJsonTxTypeDisplayString(jsonTxType);
return tx.getTxOutputs().stream()
.map(txOutput -> {
boolean isBsqTxOutputType = daoStateService.isBsqTxOutputType(txOutput);
long bsqAmount = isBsqTxOutputType ? txOutput.getValue() : 0;
long btcAmount = !isBsqTxOutputType ? txOutput.getValue() : 0;
PubKeyScript pubKeyScript = txOutput.getPubKeyScript();
JsonScriptPubKey scriptPubKey = pubKeyScript != null ? new JsonScriptPubKey(pubKeyScript) : null;
JsonSpentInfo spentInfo = daoStateService.getSpentInfo(txOutput).map(JsonSpentInfo::new).orElse(null);
JsonTxOutputType txOutputType = JsonTxOutputType.valueOf(txOutput.getTxOutputType().name());
int lockTime = txOutput.getLockTime();
BaseEncoding HEX = BaseEncoding.base16().lowerCase();
String opReturn = txOutput.getOpReturnData() != null ? HEX.encode(txOutput.getOpReturnData()) : null;
boolean isUnspent = daoStateService.isUnspent(txOutput.getKey());
return new JsonTxOutput(tx.getId(),
txOutput.getIndex(),
bsqAmount,
btcAmount,
tx.getBlockHeight(),
isBsqTxOutputType,
tx.getBurntFee(),
tx.getInvalidatedBsq(),
txOutput.getAddress(),
scriptPubKey,
spentInfo,
tx.getTime(),
jsonTxType,
jsonTxTypeDisplayString,
txOutputType,
txOutputType.getDisplayString(),
opReturn,
lockTime,
isUnspent
);
})
.collect(Collectors.toList());
}

private static String getJsonTxTypeDisplayString(JsonTxType jsonTxType) {
return jsonTxType != null ? jsonTxType.getDisplayString() : "";
}

private static JsonTxType getJsonTxType(Tx tx) {
TxType txType = tx.getTxType();
return txType != null ? JsonTxType.valueOf(txType.name()) : null;
}
}
Loading
Loading