mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Merge pull request #6306 from chimp1984/update_dao_node
Update dao node
This commit is contained in:
commit
ffcd96673e
@ -287,7 +287,7 @@ public class AccountAgeWitnessService {
|
||||
}
|
||||
|
||||
public Optional<AccountAgeWitness> findWitness(PaymentAccountPayload paymentAccountPayload,
|
||||
PubKeyRing pubKeyRing) {
|
||||
PubKeyRing pubKeyRing) {
|
||||
if (paymentAccountPayload == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@ -315,7 +315,7 @@ public class AccountAgeWitnessService {
|
||||
findWitness(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing());
|
||||
}
|
||||
|
||||
private Optional<AccountAgeWitness> getWitnessByHash(byte[] hash) {
|
||||
public Optional<AccountAgeWitness> getWitnessByHash(byte[] hash) {
|
||||
P2PDataStorage.ByteArray hashAsByteArray = new P2PDataStorage.ByteArray(hash);
|
||||
|
||||
// First we look up in our fast lookup cache
|
||||
@ -335,7 +335,7 @@ public class AccountAgeWitnessService {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<AccountAgeWitness> getWitnessByHashAsHex(String hashAsHex) {
|
||||
public Optional<AccountAgeWitness> getWitnessByHashAsHex(String hashAsHex) {
|
||||
return getWitnessByHash(Utilities.decodeFromHex(hashAsHex));
|
||||
}
|
||||
|
||||
@ -381,6 +381,15 @@ public class AccountAgeWitnessService {
|
||||
}
|
||||
}
|
||||
|
||||
public long getWitnessSignDate(AccountAgeWitness accountAgeWitness) {
|
||||
List<Long> dates = signedWitnessService.getVerifiedWitnessDateList(accountAgeWitness);
|
||||
if (dates.isEmpty()) {
|
||||
return -1L;
|
||||
} else {
|
||||
return dates.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return -1 if not signed
|
||||
public long getWitnessSignAge(Offer offer, Date now) {
|
||||
return findWitness(offer)
|
||||
@ -951,4 +960,8 @@ public class AccountAgeWitnessService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFilteredWitness(AccountAgeWitness accountAgeWitness) {
|
||||
return signedWitnessService.isFilteredWitness(accountAgeWitness);
|
||||
}
|
||||
}
|
||||
|
@ -19,25 +19,36 @@ package bisq.core.account.witness;
|
||||
|
||||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.sign.SignedWitnessService;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
import bisq.core.util.JsonUtil;
|
||||
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.crypto.CryptoException;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Hex;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
@ -168,4 +179,109 @@ public class AccountAgeWitnessUtils {
|
||||
accountAgeWitnessService.tradeAmountIsSufficient(trade.getAmount()),
|
||||
isSignWitnessTrade);
|
||||
}
|
||||
|
||||
static class AccountAgeWitnessDto {
|
||||
private final String profileId;
|
||||
private final String hashAsHex;
|
||||
private final long date;
|
||||
private final String pubKeyBase64;
|
||||
private final String signatureBase64;
|
||||
|
||||
public AccountAgeWitnessDto(String profileId,
|
||||
String hashAsHex,
|
||||
long date,
|
||||
String pubKeyBase64,
|
||||
String signatureBase64) {
|
||||
this.profileId = profileId;
|
||||
this.hashAsHex = hashAsHex;
|
||||
this.date = date;
|
||||
this.pubKeyBase64 = pubKeyBase64;
|
||||
this.signatureBase64 = signatureBase64;
|
||||
}
|
||||
}
|
||||
|
||||
static class SignedWitnessDto {
|
||||
private final String profileId;
|
||||
private final String hashAsHex;
|
||||
private final long accountAgeWitnessDate;
|
||||
private final long witnessSignDate;
|
||||
private final String pubKeyBase64;
|
||||
private final String signatureBase64;
|
||||
|
||||
public SignedWitnessDto(String profileId,
|
||||
String hashAsHex,
|
||||
long accountAgeWitnessDate,
|
||||
long witnessSignDate,
|
||||
String pubKeyBase64,
|
||||
String signatureBase64) {
|
||||
this.profileId = profileId;
|
||||
this.hashAsHex = hashAsHex;
|
||||
this.accountAgeWitnessDate = accountAgeWitnessDate;
|
||||
this.witnessSignDate = witnessSignDate;
|
||||
this.pubKeyBase64 = pubKeyBase64;
|
||||
this.signatureBase64 = signatureBase64;
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> signAccountAgeAndBisq2ProfileId(AccountAgeWitnessService accountAgeWitnessService,
|
||||
PaymentAccount account,
|
||||
KeyRing keyRing,
|
||||
String profileId) {
|
||||
return accountAgeWitnessService.findWitness(account.getPaymentAccountPayload(), keyRing.getPubKeyRing())
|
||||
.map(accountAgeWitness -> {
|
||||
try {
|
||||
checkArgument(!accountAgeWitnessService.isFilteredWitness(accountAgeWitness), "Invalid account age witness");
|
||||
String hashAsHex = Hex.encode(accountAgeWitness.getHash());
|
||||
long date = accountAgeWitness.getDate();
|
||||
checkArgument(date > 0, "Date must be > 0");
|
||||
String message = profileId + hashAsHex + date;
|
||||
KeyPair signatureKeyPair = keyRing.getSignatureKeyPair();
|
||||
String signatureBase64 = Sig.sign(signatureKeyPair.getPrivate(), message);
|
||||
String pubKeyBase64 = Base64.getEncoder().encodeToString(Sig.getPublicKeyBytes(signatureKeyPair.getPublic()));
|
||||
AccountAgeWitnessDto dto = new AccountAgeWitnessDto(profileId,
|
||||
hashAsHex,
|
||||
date,
|
||||
pubKeyBase64,
|
||||
signatureBase64);
|
||||
return JsonUtil.objectToJson(dto);
|
||||
} catch (CryptoException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Optional<String> signSignedWitnessAndBisq2ProfileId(AccountAgeWitnessService accountAgeWitnessService,
|
||||
PaymentAccount account,
|
||||
KeyRing keyRing,
|
||||
String profileId) {
|
||||
return accountAgeWitnessService.findWitness(account.getPaymentAccountPayload(), keyRing.getPubKeyRing())
|
||||
.map(accountAgeWitness -> {
|
||||
try {
|
||||
checkArgument(!accountAgeWitnessService.isFilteredWitness(accountAgeWitness), "Invalid account age witness");
|
||||
long witnessSignDate = accountAgeWitnessService.getWitnessSignDate(accountAgeWitness);
|
||||
long ageInDays = (System.currentTimeMillis() - witnessSignDate) / TimeUnit.DAYS.toMillis(1);
|
||||
if (!DevEnv.isDevMode()) {
|
||||
checkArgument(witnessSignDate > 0, "Account is not signed yet");
|
||||
checkArgument(ageInDays > 60, "Account must have been signed at least 61 days ago");
|
||||
}
|
||||
|
||||
String hashAsHex = Hex.encode(accountAgeWitness.getHash());
|
||||
long date = accountAgeWitness.getDate();
|
||||
checkArgument(date > 0, "AccountAgeWitness date must be > 0");
|
||||
String message = profileId + hashAsHex + date + witnessSignDate;
|
||||
KeyPair signatureKeyPair = keyRing.getSignatureKeyPair();
|
||||
String signatureBase64 = Sig.sign(signatureKeyPair.getPrivate(), message);
|
||||
String pubKeyBase64 = Base64.getEncoder().encodeToString(Sig.getPublicKeyBytes(signatureKeyPair.getPublic()));
|
||||
SignedWitnessDto dto = new SignedWitnessDto(profileId,
|
||||
hashAsHex,
|
||||
date,
|
||||
witnessSignDate,
|
||||
pubKeyBase64,
|
||||
signatureBase64);
|
||||
return JsonUtil.objectToJson(dto);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ public class ProofOfBurnService implements DaoSetupService, DaoStateListener {
|
||||
myProofOfBurnListService.addMyProofOfBurn(myProofOfBurn);
|
||||
}
|
||||
|
||||
public byte[] getHashFromOpReturnData(Tx tx) {
|
||||
public static byte[] getHashFromOpReturnData(Tx tx) {
|
||||
return ProofOfBurnConsensus.getHashFromOpReturnData(tx.getLastTxOutput().getOpReturnData());
|
||||
}
|
||||
|
||||
|
@ -1818,7 +1818,14 @@ providing cryptographic proof to the mediator or refund agent.\n\n\
|
||||
If you do not understand these requirements, do not trade L-BTC on Bisq.
|
||||
|
||||
account.fiat.yourFiatAccounts=Your national currency accounts
|
||||
|
||||
account.fiat.exportAccountAge=Export account age for Bisq 2
|
||||
account.fiat.exportAccountAge.popup=Your 'account age' and Bisq 2 profile ID got signed and the data is copied \
|
||||
to the clipboard.\n\n\This is the data in json format:\n\n\{0}\n\n\
|
||||
Go back to Bisq 2 and follow the instructions there.
|
||||
account.fiat.signedWitness=Export signed witness for Bisq 2
|
||||
account.fiat.signedWitness.popup=Your 'signed account age witness' and Bisq 2 profile ID got signed and the data is copied \
|
||||
to the clipboard.\n\n\This is the data in json format:\n\n\{0}\n\n\
|
||||
Go back to Bisq 2 and follow the instructions there.
|
||||
account.backup.title=Backup wallet
|
||||
account.backup.location=Backup location
|
||||
account.backup.selectLocation=Select backup location
|
||||
|
@ -28,7 +28,10 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import bisq.daonode.endpoints.AccountAgeApi;
|
||||
import bisq.daonode.endpoints.BondedReputationApi;
|
||||
import bisq.daonode.endpoints.ProofOfBurnApi;
|
||||
import bisq.daonode.endpoints.SignedWitnessApi;
|
||||
import bisq.daonode.error.CustomExceptionMapper;
|
||||
import bisq.daonode.error.StatusException;
|
||||
import bisq.daonode.util.StaticFileHandler;
|
||||
@ -54,6 +57,9 @@ public class DaoNodeRestApiApplication extends ResourceConfig {
|
||||
.register(CustomExceptionMapper.class)
|
||||
.register(StatusException.StatusExceptionMapper.class)
|
||||
.register(ProofOfBurnApi.class)
|
||||
.register(BondedReputationApi.class)
|
||||
.register(AccountAgeApi.class)
|
||||
.register(SignedWitnessApi.class)
|
||||
.register(SwaggerResolution.class);
|
||||
daoNodeRestApiApplication.startServer(config.daoNodeApiUrl, config.daoNodeApiPort);
|
||||
});
|
||||
@ -61,17 +67,18 @@ public class DaoNodeRestApiApplication extends ResourceConfig {
|
||||
|
||||
|
||||
@Getter
|
||||
private final DaoNode daoNode;
|
||||
private final ServiceNode serviceNode;
|
||||
|
||||
private HttpServer httpServer;
|
||||
|
||||
public DaoNodeRestApiApplication() {
|
||||
daoNode = new DaoNode();
|
||||
serviceNode = new ServiceNode();
|
||||
}
|
||||
|
||||
private void startDaoNode(String[] args, Consumer<Config> configConsumer) {
|
||||
new Thread(() -> {
|
||||
daoNode.execute(args);
|
||||
configConsumer.accept(daoNode.getConfig());
|
||||
serviceNode.execute(args);
|
||||
configConsumer.accept(serviceNode.getConfig());
|
||||
try {
|
||||
// Keep running
|
||||
Thread.currentThread().setName("daoNodeThread");
|
||||
@ -106,8 +113,8 @@ public class DaoNodeRestApiApplication extends ResourceConfig {
|
||||
}
|
||||
|
||||
private void shutDown() {
|
||||
if (daoNode != null) {
|
||||
daoNode.gracefulShutDown(this::stopServer);
|
||||
if (serviceNode != null) {
|
||||
serviceNode.gracefulShutDown(this::stopServer);
|
||||
} else {
|
||||
stopServer();
|
||||
}
|
||||
|
@ -18,10 +18,12 @@
|
||||
package bisq.daonode;
|
||||
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.app.TorSetup;
|
||||
import bisq.core.app.misc.AppSetupWithP2PAndDAO;
|
||||
import bisq.core.app.misc.ExecutableForAppWithP2p;
|
||||
import bisq.core.app.misc.ModuleForAppWithP2p;
|
||||
import bisq.core.dao.governance.bond.reputation.BondedReputationRepository;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
import bisq.core.user.Cookie;
|
||||
@ -49,14 +51,18 @@ import lombok.extern.slf4j.Slf4j;
|
||||
//todo not sure if the restart handling from seed nodes is required
|
||||
|
||||
@Slf4j
|
||||
public class DaoNode extends ExecutableForAppWithP2p {
|
||||
public class ServiceNode extends ExecutableForAppWithP2p {
|
||||
private static final long CHECK_CONNECTION_LOSS_SEC = 30;
|
||||
|
||||
private Timer checkConnectionLossTime;
|
||||
@Getter
|
||||
private DaoStateService daoStateService;
|
||||
@Getter
|
||||
private BondedReputationRepository bondedReputationRepository;
|
||||
@Getter
|
||||
private AccountAgeWitnessService accountAgeWitnessService;
|
||||
|
||||
public DaoNode() {
|
||||
public ServiceNode() {
|
||||
super("Bisq Dao Node", "bisq-dao-node", "bisq_dao_node", Version.VERSION);
|
||||
}
|
||||
|
||||
@ -117,6 +123,8 @@ public class DaoNode extends ExecutableForAppWithP2p {
|
||||
injector.getInstance(AppSetupWithP2PAndDAO.class).start();
|
||||
|
||||
daoStateService = injector.getInstance(DaoStateService.class);
|
||||
accountAgeWitnessService = injector.getInstance(AccountAgeWitnessService.class);
|
||||
bondedReputationRepository = injector.getInstance(BondedReputationRepository.class);
|
||||
|
||||
injector.getInstance(P2PService.class).addP2PServiceListener(new P2PServiceListener() {
|
||||
@Override
|
||||
@ -149,9 +157,11 @@ public class DaoNode extends ExecutableForAppWithP2p {
|
||||
boolean preventPeriodicShutdownAtSeedNode = injector.getInstance(Key.get(boolean.class,
|
||||
Names.named(Config.PREVENT_PERIODIC_SHUTDOWN_AT_SEED_NODE)));
|
||||
if (!preventPeriodicShutdownAtSeedNode) {
|
||||
startShutDownInterval(DaoNode.this);
|
||||
startShutDownInterval(ServiceNode.this);
|
||||
}
|
||||
UserThread.runAfter(() -> setupConnectionLossCheck(), 60);
|
||||
|
||||
accountAgeWitnessService.onAllServicesInitialized();
|
||||
}
|
||||
|
||||
@Override
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.daonode.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* Minimal data required for Bisq 2 bonded reputation use case.
|
||||
* Need to be in sync with the Bisq 2 BondedReputationDto class.
|
||||
*/
|
||||
@Getter
|
||||
@Slf4j
|
||||
@Schema(title = "BondedReputation")
|
||||
public class BondedReputationDto {
|
||||
private final long amount;
|
||||
private final long time;
|
||||
private final String hash;
|
||||
private final int blockHeight;
|
||||
private final int lockTime;
|
||||
|
||||
public BondedReputationDto(long amount, long time, String hash, int blockHeight, int lockTime) {
|
||||
this.amount = amount;
|
||||
this.time = time;
|
||||
this.hash = hash;
|
||||
this.blockHeight = blockHeight;
|
||||
this.lockTime = lockTime;
|
||||
}
|
||||
}
|
@ -24,23 +24,21 @@ import lombok.Getter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* Minimal data required for Bisq 2 reputation use case.
|
||||
* Minimal data required for Bisq 2 proof of burn use case.
|
||||
* Need to be in sync with the Bisq 2 ProofOfBurnDto class.
|
||||
*/
|
||||
@Getter
|
||||
@Schema(title = "ProofOfBurn")
|
||||
public class ProofOfBurnDto {
|
||||
private final String txId;
|
||||
private final long burnedAmount;
|
||||
private final int blockHeight;
|
||||
private final long amount;
|
||||
private final long time;
|
||||
private final String hash;
|
||||
private final int blockHeight;
|
||||
|
||||
public ProofOfBurnDto(String txId, long burnedAmount, int blockHeight, long time, String hash) {
|
||||
this.txId = txId;
|
||||
this.burnedAmount = burnedAmount;
|
||||
this.blockHeight = blockHeight;
|
||||
public ProofOfBurnDto(long amount, long time, String hash, int blockHeight) {
|
||||
this.amount = amount;
|
||||
this.time = time;
|
||||
this.hash = hash;
|
||||
this.blockHeight = blockHeight;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.daonode.endpoints;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitness;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import bisq.daonode.DaoNodeRestApiApplication;
|
||||
import bisq.daonode.ServiceNode;
|
||||
import bisq.daonode.dto.ProofOfBurnDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.Application;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* Endpoint for getting the account age date from a given hash as hex string.
|
||||
* Used for reputation system in Bisq 2.
|
||||
* <a href="http://localhost:8082/api/v1/account-age/get-date/dd75a7175c7c83fe9a4729e36b85f5fbc44e29ae">Request with hash</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@Path("/account-age")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Tag(name = "Account age API")
|
||||
public class AccountAgeApi {
|
||||
private static final String DESC_HASH = "The hash of the account age witness as hex string";
|
||||
private final ServiceNode serviceNode;
|
||||
|
||||
public AccountAgeApi(@Context Application application) {
|
||||
serviceNode = ((DaoNodeRestApiApplication) application).getServiceNode();
|
||||
}
|
||||
|
||||
@Operation(description = "Request the account age date")
|
||||
@ApiResponse(responseCode = "200", description = "The account age date",
|
||||
content = {@Content(mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(allOf = ProofOfBurnDto.class))}
|
||||
)
|
||||
@GET
|
||||
@Path("get-date/{hash}")
|
||||
public Long getDate(@Parameter(description = DESC_HASH)
|
||||
@PathParam("hash")
|
||||
String hash) {
|
||||
return checkNotNull(serviceNode.getAccountAgeWitnessService()).getWitnessByHashAsHex(hash)
|
||||
.map(AccountAgeWitness::getDate)
|
||||
.orElse(-1L);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.daonode.endpoints;
|
||||
|
||||
import bisq.core.dao.governance.bond.reputation.BondedReputationRepository;
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
|
||||
import bisq.common.util.Hex;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import bisq.daonode.DaoNodeRestApiApplication;
|
||||
import bisq.daonode.ServiceNode;
|
||||
import bisq.daonode.dto.BondedReputationDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.Application;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* Endpoint for getting the bonded reputation data from a given block height.
|
||||
* Used for reputation system in Bisq 2.
|
||||
* <a href="http://localhost:8082/api/v1/bonded-reputation/get-bonded-reputation/0">Request with block height 0</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@Path("/bonded-reputation")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Bonded reputation API")
|
||||
public class BondedReputationApi {
|
||||
private static final String DESC_BLOCK_HEIGHT = "The block height from which we request the bonded reputation data";
|
||||
private final BondedReputationRepository bondedReputationRepository;
|
||||
private final DaoStateService daoStateService;
|
||||
|
||||
public BondedReputationApi(@Context Application application) {
|
||||
ServiceNode serviceNode = ((DaoNodeRestApiApplication) application).getServiceNode();
|
||||
daoStateService = serviceNode.getDaoStateService();
|
||||
bondedReputationRepository = serviceNode.getBondedReputationRepository();
|
||||
}
|
||||
|
||||
@Operation(description = "Request the bonded reputation data")
|
||||
@ApiResponse(responseCode = "200", description = "The bonded reputation data",
|
||||
content = {@Content(mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(allOf = BondedReputationDto.class))}
|
||||
)
|
||||
@GET
|
||||
@Path("get-bonded-reputation/{block-height}")
|
||||
public List<BondedReputationDto> getBondedReputation(@Parameter(description = DESC_BLOCK_HEIGHT)
|
||||
@PathParam("block-height")
|
||||
int fromBlockHeight) {
|
||||
return bondedReputationRepository.getActiveBonds().stream()
|
||||
.map(bondedReputation -> {
|
||||
Optional<Tx> optionalTx = daoStateService.getTx(bondedReputation.getLockupTxId());
|
||||
if (optionalTx.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Tx tx = optionalTx.get();
|
||||
int blockHeight = tx.getBlockHeight();
|
||||
if (blockHeight >= fromBlockHeight) {
|
||||
return new BondedReputationDto(bondedReputation.getAmount(),
|
||||
tx.getTime(),
|
||||
Hex.encode(bondedReputation.getBondedAsset().getHash()),
|
||||
blockHeight,
|
||||
bondedReputation.getLockTime()
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -17,11 +17,10 @@
|
||||
|
||||
package bisq.daonode.endpoints;
|
||||
|
||||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
|
||||
|
||||
import bisq.common.util.Hex;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -31,8 +30,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import bisq.daonode.DaoNode;
|
||||
import bisq.daonode.DaoNodeRestApiApplication;
|
||||
import bisq.daonode.ServiceNode;
|
||||
import bisq.daonode.dto.ProofOfBurnDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@ -48,16 +47,21 @@ import jakarta.ws.rs.core.Application;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* Endpoint for getting the proof of burn data from a given block height.
|
||||
* Used for reputation system in Bisq 2.
|
||||
* <a href="http://localhost:8082/api/v1/proof-of-burn/get-proof-of-burn/0">Request with block height 0</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@Path("/proof-of-burn")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = "Proof of burn API")
|
||||
public class ProofOfBurnApi {
|
||||
private static final String DESC_BLOCK_HEIGHT = "The block height from which we request the proof of burn data";
|
||||
private final DaoNode daoNode;
|
||||
private final ServiceNode serviceNode;
|
||||
|
||||
public ProofOfBurnApi(@Context Application application) {
|
||||
daoNode = ((DaoNodeRestApiApplication) application).getDaoNode();
|
||||
serviceNode = ((DaoNodeRestApiApplication) application).getServiceNode();
|
||||
}
|
||||
|
||||
@Operation(description = "Request the proof of burn data")
|
||||
@ -69,23 +73,13 @@ public class ProofOfBurnApi {
|
||||
@Path("get-proof-of-burn/{block-height}")
|
||||
public List<ProofOfBurnDto> getProofOfBurn(@Parameter(description = DESC_BLOCK_HEIGHT)
|
||||
@PathParam("block-height")
|
||||
int blockHeight) {
|
||||
return checkNotNull(daoNode.getDaoStateService()).getProofOfBurnTxs().stream()
|
||||
.filter(tx -> tx.getBlockHeight() >= blockHeight)
|
||||
.map(tx -> new ProofOfBurnDto(tx.getId(),
|
||||
tx.getBurntBsq(),
|
||||
tx.getBlockHeight(),
|
||||
int fromBlockHeight) {
|
||||
return checkNotNull(serviceNode.getDaoStateService()).getProofOfBurnTxs().stream()
|
||||
.filter(tx -> tx.getBlockHeight() >= fromBlockHeight)
|
||||
.map(tx -> new ProofOfBurnDto(tx.getBurntBsq(),
|
||||
tx.getTime(),
|
||||
getHash(tx)))
|
||||
Hex.encode(ProofOfBurnService.getHashFromOpReturnData(tx)),
|
||||
tx.getBlockHeight()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// We strip out the version bytes
|
||||
private String getHash(Tx tx) {
|
||||
byte[] opReturnData = tx.getLastTxOutput().getOpReturnData();
|
||||
if (opReturnData == null) {
|
||||
return "";
|
||||
}
|
||||
return Hex.encode(Arrays.copyOfRange(opReturnData, 2, 22));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.daonode.endpoints;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import bisq.daonode.DaoNodeRestApiApplication;
|
||||
import bisq.daonode.ServiceNode;
|
||||
import bisq.daonode.dto.ProofOfBurnDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.Application;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* Endpoint for getting the signed witness date from a given hash as hex string.
|
||||
* Used for reputation system in Bisq 2.
|
||||
* <a href="http://localhost:8082/api/v1/signed-witness/get-date/dd75a7175c7c83fe9a4729e36b85f5fbc44e29ae">Request with hash</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@Path("/signed-witness")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Tag(name = "Signed witness API")
|
||||
public class SignedWitnessApi {
|
||||
private static final String DESC_HASH = "The hash of the signed account age witness as hex string";
|
||||
private final ServiceNode serviceNode;
|
||||
|
||||
public SignedWitnessApi(@Context Application application) {
|
||||
serviceNode = ((DaoNodeRestApiApplication) application).getServiceNode();
|
||||
}
|
||||
|
||||
@Operation(description = "Request the signed witness date")
|
||||
@ApiResponse(responseCode = "200", description = "The signed witness date",
|
||||
content = {@Content(mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(allOf = ProofOfBurnDto.class))}
|
||||
)
|
||||
@GET
|
||||
@Path("get-date/{hash}")
|
||||
public Long getDate(@Parameter(description = DESC_HASH)
|
||||
@PathParam("hash")
|
||||
String hash) {
|
||||
AccountAgeWitnessService accountAgeWitnessService = checkNotNull(serviceNode.getAccountAgeWitnessService());
|
||||
return accountAgeWitnessService.getWitnessByHashAsHex(hash)
|
||||
.map(accountAgeWitnessService::getWitnessSignDate)
|
||||
.orElse(-1L);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
package bisq.desktop.main.account.content.fiataccounts;
|
||||
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.TitledGroupBg;
|
||||
import bisq.desktop.components.paymentmethods.AchTransferForm;
|
||||
import bisq.desktop.components.paymentmethods.AdvancedCashForm;
|
||||
@ -100,6 +101,7 @@ import bisq.desktop.util.validation.UpholdValidator;
|
||||
import bisq.desktop.util.validation.WeChatPayValidator;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.account.witness.AccountAgeWitnessUtils;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
import bisq.core.payment.AmazonGiftCardAccount;
|
||||
@ -120,6 +122,8 @@ import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.util.Hex;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Tuple3;
|
||||
import bisq.common.util.Utilities;
|
||||
@ -135,7 +139,10 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
@ -157,6 +164,7 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
private final BICValidator bicValidator;
|
||||
private final CapitualValidator capitualValidator;
|
||||
private final LengthValidator inputValidator;
|
||||
private final KeyRing keyRing;
|
||||
private final UpholdValidator upholdValidator;
|
||||
private final MoneyBeamValidator moneyBeamValidator;
|
||||
private final PopmoneyValidator popmoneyValidator;
|
||||
@ -208,12 +216,14 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
AdvancedCashValidator advancedCashValidator,
|
||||
TransferwiseValidator transferwiseValidator,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
KeyRing keyRing,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter) {
|
||||
super(model, accountAgeWitnessService);
|
||||
|
||||
this.bicValidator = bicValidator;
|
||||
this.capitualValidator = capitualValidator;
|
||||
this.inputValidator = inputValidator;
|
||||
this.keyRing = keyRing;
|
||||
this.inputValidator.setMaxLength(100); // restrict general field entry length
|
||||
this.inputValidator.setMinLength(2);
|
||||
this.upholdValidator = upholdValidator;
|
||||
@ -296,9 +306,9 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
}
|
||||
|
||||
new Popup().information(Res.get(limitsInfoKey,
|
||||
initialLimit,
|
||||
formatter.formatCoinWithCode(maxTradeLimitSecondMonth),
|
||||
formatter.formatCoinWithCode(maxTradeLimitAsCoin)))
|
||||
initialLimit,
|
||||
formatter.formatCoinWithCode(maxTradeLimitSecondMonth),
|
||||
formatter.formatCoinWithCode(maxTradeLimitAsCoin)))
|
||||
.width(700)
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.actionButtonText(Res.get("shared.iUnderstand"))
|
||||
@ -490,6 +500,7 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
Res.get("shared.deleteAccount"),
|
||||
Res.get("shared.cancel")
|
||||
);
|
||||
|
||||
Button updateButton = tuple.first;
|
||||
updateButton.setOnAction(event -> onUpdateAccount(paymentMethodForm.getPaymentAccount()));
|
||||
Button deleteAccountButton = tuple.second;
|
||||
@ -497,10 +508,69 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
Button cancelButton = tuple.third;
|
||||
cancelButton.setOnAction(event -> onCancelSelectedAccount(paymentMethodForm.getPaymentAccount()));
|
||||
GridPane.setRowSpan(accountTitledGroupBg, paymentMethodForm.getRowSpan());
|
||||
|
||||
Button exportAccountAgeButton = new AutoTooltipButton(Res.get("account.fiat.exportAccountAge"));
|
||||
exportAccountAgeButton.setOnAction(event -> onExportAccountAgeForBisq2(paymentMethodForm.getPaymentAccount()));
|
||||
exportAccountAgeButton.setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(exportAccountAgeButton, Priority.ALWAYS);
|
||||
|
||||
Button signedWitnessButton = new AutoTooltipButton(Res.get("account.fiat.signedWitness"));
|
||||
signedWitnessButton.setOnAction(event -> onExportSignedWitnessForBisq2(paymentMethodForm.getPaymentAccount()));
|
||||
signedWitnessButton.setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(signedWitnessButton, Priority.ALWAYS);
|
||||
|
||||
|
||||
HBox hBox = (HBox) cancelButton.getParent();
|
||||
hBox.getChildren().addAll(exportAccountAgeButton, signedWitnessButton);
|
||||
|
||||
model.onSelectAccount(current);
|
||||
}
|
||||
}
|
||||
|
||||
private void onExportAccountAgeForBisq2(PaymentAccount paymentAccount) {
|
||||
String prefix = "BISQ2_ACCOUNT_AGE:";
|
||||
try {
|
||||
String profileId = getProfileIdFromClipBoard(prefix);
|
||||
AccountAgeWitnessUtils.signAccountAgeAndBisq2ProfileId(accountAgeWitnessService, paymentAccount, keyRing, profileId)
|
||||
.ifPresent(json -> {
|
||||
Utilities.copyToClipboard(prefix + json);
|
||||
new Popup().information(Res.get("account.fiat.exportAccountAge.popup", json))
|
||||
.width(900).show();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
String error = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
|
||||
new Popup().warning(error).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void onExportSignedWitnessForBisq2(PaymentAccount paymentAccount) {
|
||||
String prefix = "BISQ2_SIGNED_WITNESS:";
|
||||
try {
|
||||
String profileId = getProfileIdFromClipBoard(prefix);
|
||||
AccountAgeWitnessUtils.signSignedWitnessAndBisq2ProfileId(accountAgeWitnessService, paymentAccount, keyRing, profileId)
|
||||
.ifPresent(json -> {
|
||||
Utilities.copyToClipboard(prefix + json);
|
||||
new Popup().information(Res.get("account.fiat.signedWitness.popup", json))
|
||||
.width(900).show();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
String error = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
|
||||
new Popup().warning(error).show();
|
||||
}
|
||||
}
|
||||
|
||||
private String getProfileIdFromClipBoard(String prefix) {
|
||||
String clipboardText = Clipboard.getSystemClipboard().getString();
|
||||
if (clipboardText != null && clipboardText.startsWith(prefix)) {
|
||||
String profileId = clipboardText.replace(prefix, "");
|
||||
if (profileId.length() == 40) {
|
||||
Hex.decode(profileId);
|
||||
return profileId;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Clipboard text not in expected format. " + clipboardText);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
|
@ -58,7 +58,7 @@ class MyProofOfBurnListItem {
|
||||
amount = proofOfBurnService.getAmount(tx);
|
||||
amountAsString = bsqFormatter.formatCoinWithCode(Coin.valueOf(amount));
|
||||
txId = tx.getId();
|
||||
hashAsHex = Utilities.bytesAsHexString(proofOfBurnService.getHashFromOpReturnData(tx));
|
||||
hashAsHex = Utilities.bytesAsHexString(ProofOfBurnService.getHashFromOpReturnData(tx));
|
||||
pubKey = Utilities.bytesAsHexString(proofOfBurnService.getPubKey(txId));
|
||||
} else {
|
||||
amount = 0;
|
||||
|
@ -45,7 +45,7 @@ class ProofOfBurnListItem {
|
||||
amount = proofOfBurnService.getAmount(tx);
|
||||
amountAsString = bsqFormatter.formatCoinWithCode(Coin.valueOf(amount));
|
||||
txId = tx.getId();
|
||||
hashAsHex = Utilities.bytesAsHexString(proofOfBurnService.getHashFromOpReturnData(tx));
|
||||
hashAsHex = Utilities.bytesAsHexString(ProofOfBurnService.getHashFromOpReturnData(tx));
|
||||
pubKey = Utilities.bytesAsHexString(proofOfBurnService.getPubKey(txId));
|
||||
date = new Date(tx.getTime());
|
||||
dateAsString = DisplayUtils.formatDateTime(date);
|
||||
|
@ -843,7 +843,9 @@ public class GUIUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean canCreateOrTakeOfferOrShowPopup(User user, Navigation navigation, @Nullable TradeCurrency currency) {
|
||||
public static boolean canCreateOrTakeOfferOrShowPopup(User user,
|
||||
Navigation navigation,
|
||||
@Nullable TradeCurrency currency) {
|
||||
if (currency != null && currency.getCode().equals("BSQ")) {
|
||||
return true;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user