Implements bonded reputation

Still WIP with many areas...
This commit is contained in:
Manfred Karrer 2018-11-05 18:35:30 -05:00
parent 15b8f02c67
commit 7bf5c819cd
No known key found for this signature in database
GPG Key ID: 401250966A6B2C46
51 changed files with 1958 additions and 1058 deletions

View File

@ -943,7 +943,7 @@ message PersistableEnvelope {
MeritList merit_list = 23;
RemovedAssetList removed_asset_list = 24;
DaoStateStore dao_state_store = 25;
ReputationList reputation_list = 26;
MyReputationList my_reputation_list = 26;
}
}
@ -1546,12 +1546,12 @@ message Role {
string bonded_role_type = 4; // name of BondedRoleType enum
}
message Reputation {
message MyReputation {
bytes salt = 1;
}
message ReputationList {
repeated Reputation reputation = 1;
message MyReputationList {
repeated MyReputation my_reputation = 1;
}
message TempProposalPayload {

View File

@ -22,7 +22,7 @@ import bisq.core.dao.DaoSetup;
import bisq.core.dao.governance.asset.AssetService;
import bisq.core.dao.governance.ballot.BallotListService;
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
import bisq.core.dao.governance.bond.reputation.BondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
import bisq.core.dao.governance.myvote.MyVoteListService;
import bisq.core.dao.governance.proposal.MyProposalListService;
import bisq.core.filter.FilterManager;
@ -55,7 +55,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
BallotListService ballotListService,
MyBlindVoteListService myBlindVoteListService,
MyProposalListService myProposalListService,
BondedReputationService bondedReputationService,
MyReputationListService myReputationListService,
AssetService assetService,
@Named(DaoOptionKeys.DAO_ACTIVATED) boolean daoActivated) {
super(encryptionService,
@ -73,7 +73,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
persistedDataHosts.add(ballotListService);
persistedDataHosts.add(myBlindVoteListService);
persistedDataHosts.add(myProposalListService);
persistedDataHosts.add(bondedReputationService);
persistedDataHosts.add(myReputationListService);
persistedDataHosts.add(assetService);
}
}

View File

@ -24,11 +24,15 @@ import bisq.core.dao.governance.ballot.BallotListPresentation;
import bisq.core.dao.governance.ballot.BallotListService;
import bisq.core.dao.governance.blindvote.BlindVoteConsensus;
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
import bisq.core.dao.governance.bond.BondWithHash;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.BondedAsset;
import bisq.core.dao.governance.bond.lockup.LockupService;
import bisq.core.dao.governance.bond.lockup.LockupType;
import bisq.core.dao.governance.bond.reputation.BondedReputation;
import bisq.core.dao.governance.bond.reputation.BondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyBondedReputation;
import bisq.core.dao.governance.bond.reputation.MyBondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.dao.governance.bond.role.BondedRolesService;
import bisq.core.dao.governance.bond.unlock.UnlockService;
@ -85,7 +89,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@ -118,6 +122,8 @@ public class DaoFacade implements DaoSetupService {
private final RemoveAssetProposalFactory removeAssetProposalFactory;
private final BondedRolesService bondedRolesService;
private final BondedReputationService bondedReputationService;
private final MyReputationListService myReputationListService;
private final MyBondedReputationService myBondedReputationService;
private final LockupService lockupService;
private final UnlockService unlockService;
private final DaoStateStorageService daoStateStorageService;
@ -142,6 +148,8 @@ public class DaoFacade implements DaoSetupService {
RemoveAssetProposalFactory removeAssetProposalFactory,
BondedRolesService bondedRolesService,
BondedReputationService bondedReputationService,
MyReputationListService myReputationListService,
MyBondedReputationService myBondedReputationService,
LockupService lockupService,
UnlockService unlockService,
DaoStateStorageService daoStateStorageService) {
@ -162,6 +170,8 @@ public class DaoFacade implements DaoSetupService {
this.removeAssetProposalFactory = removeAssetProposalFactory;
this.bondedRolesService = bondedRolesService;
this.bondedReputationService = bondedReputationService;
this.myReputationListService = myReputationListService;
this.myBondedReputationService = myBondedReputationService;
this.lockupService = lockupService;
this.unlockService = unlockService;
this.daoStateStorageService = daoStateStorageService;
@ -282,12 +292,8 @@ public class DaoFacade implements DaoSetupService {
return removeAssetProposalFactory.createProposalWithTransaction(name, link, asset);
}
public Collection<BondedRole> getBondedRoles() {
return bondedRolesService.getBondedRoles();
}
public List<BondedReputation> getReputationList() {
return bondedReputationService.getReputationList();
public List<BondedRole> getBondedRoles() {
return bondedRolesService.getBonds();
}
// Show fee
@ -487,9 +493,9 @@ public class DaoFacade implements DaoSetupService {
// Use case: Bonding
///////////////////////////////////////////////////////////////////////////////////////////
public void publishLockupTx(Coin lockupAmount, int lockTime, LockupType lockupType, BondWithHash bondWithHash,
public void publishLockupTx(Coin lockupAmount, int lockTime, LockupType lockupType, BondedAsset bondedAsset,
Consumer<String> resultHandler, ExceptionHandler exceptionHandler) {
lockupService.publishLockupTx(lockupAmount, lockTime, lockupType, bondWithHash, resultHandler, exceptionHandler);
lockupService.publishLockupTx(lockupAmount, lockTime, lockupType, bondedAsset, resultHandler, exceptionHandler);
}
public void publishUnlockTx(String lockupTxId, Consumer<String> resultHandler,
@ -513,13 +519,21 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getLockTime(txId);
}
public List<Role> getActiveBondedRoles() {
return bondedRolesService.getActiveBondedRoles();
public List<BondedRole> getActiveBondedRoles() {
return bondedRolesService.getActiveBonds();
}
/*public List<BondedReputation> getValidBondedReputationList() {
return bondedReputationService.getValidBondedReputationList();
}*/
public List<Bond> getAllActiveBonds() {
List<BondedReputation> activeReputations = bondedReputationService.getActiveBonds();
List<BondedRole> activeRoles = bondedRolesService.getActiveBonds();
List<Bond> bonds = new ArrayList<>(activeReputations);
bonds.addAll(activeRoles);
return bonds;
}
public List<MyBondedReputation> getMyBondedReputations() {
return myBondedReputationService.getMyBondedReputations();
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -622,17 +636,6 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.isUnspent(key);
}
public Optional<Role> getBondedRoleFromHash(byte[] hash) {
return bondedRolesService.getBondedRoleFromHash(hash);
}
public Optional<BondedReputation> getBondedReputationFromHash(byte[] hash) {
return bondedReputationService.getBondedReputationFromHash(hash);
}
/*public boolean isUnlocking(String unlockTxId) {
return daoStateService.isUnlocking(unlockTxId);
}*/
public Coin getMinCompensationRequestAmount() {
return CompensationConsensus.getMinCompensationRequestAmount(daoStateService, periodService.getChainHeight());

View File

@ -28,6 +28,8 @@ import bisq.core.dao.governance.blindvote.storage.BlindVoteStorageService;
import bisq.core.dao.governance.blindvote.storage.BlindVoteStore;
import bisq.core.dao.governance.bond.lockup.LockupService;
import bisq.core.dao.governance.bond.reputation.BondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyBondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
import bisq.core.dao.governance.bond.role.BondedRolesService;
import bisq.core.dao.governance.bond.unlock.UnlockService;
import bisq.core.dao.governance.myvote.MyVoteListService;
@ -188,6 +190,8 @@ public class DaoModule extends AppModule {
bind(UnlockService.class).in(Singleton.class);
bind(BondedRolesService.class).in(Singleton.class);
bind(BondedReputationService.class).in(Singleton.class);
bind(MyReputationListService.class).in(Singleton.class);
bind(MyBondedReputationService.class).in(Singleton.class);
// Asset
bind(AssetService.class).in(Singleton.class);

View File

@ -20,6 +20,10 @@ package bisq.core.dao;
import bisq.core.dao.governance.ballot.BallotListService;
import bisq.core.dao.governance.blindvote.BlindVoteListService;
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
import bisq.core.dao.governance.bond.reputation.BondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyBondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
import bisq.core.dao.governance.bond.role.BondedRolesService;
import bisq.core.dao.governance.period.CycleService;
import bisq.core.dao.governance.proposal.ProposalService;
import bisq.core.dao.governance.voteresult.MissingDataRequestService;
@ -34,23 +38,16 @@ import bisq.common.handlers.ErrorMessageHandler;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
/**
* High level entry point for Dao domain.
* We initialize all main service classes here to be sure they are started.
*/
public class DaoSetup {
private final DaoStateService daoStateService;
private final CycleService cycleService;
private final ProposalService proposalService;
private final BallotListService ballotListService;
private final BlindVoteListService blindVoteListService;
private final MyBlindVoteListService myBlindVoteListService;
private final VoteRevealService voteRevealService;
private final VoteResultService voteResultService;
private final BsqNode bsqNode;
private final MissingDataRequestService missingDataRequestService;
private final DaoFacade daoFacade;
private final ExportJsonFilesService exportJsonFilesService;
private final List<DaoSetupService> daoSetupServices = new ArrayList<>();
@Inject
public DaoSetup(BsqNodeProvider bsqNodeProvider,
@ -63,52 +60,41 @@ public class DaoSetup {
VoteRevealService voteRevealService,
VoteResultService voteResultService,
MissingDataRequestService missingDataRequestService,
BondedReputationService bondedReputationService,
BondedRolesService bondedRolesService,
MyReputationListService myReputationListService,
MyBondedReputationService myBondedReputationService,
DaoFacade daoFacade,
ExportJsonFilesService exportJsonFilesService) {
this.daoStateService = daoStateService;
this.cycleService = cycleService;
this.proposalService = proposalService;
this.ballotListService = ballotListService;
this.blindVoteListService = blindVoteListService;
this.myBlindVoteListService = myBlindVoteListService;
this.voteRevealService = voteRevealService;
this.voteResultService = voteResultService;
this.missingDataRequestService = missingDataRequestService;
this.daoFacade = daoFacade;
this.exportJsonFilesService = exportJsonFilesService;
bsqNode = bsqNodeProvider.getBsqNode();
// We need to take care of order of execution.
daoSetupServices.add(daoStateService);
daoSetupServices.add(cycleService);
daoSetupServices.add(proposalService);
daoSetupServices.add(ballotListService);
daoSetupServices.add(blindVoteListService);
daoSetupServices.add(myBlindVoteListService);
daoSetupServices.add(voteRevealService);
daoSetupServices.add(voteResultService);
daoSetupServices.add(missingDataRequestService);
daoSetupServices.add(bondedReputationService);
daoSetupServices.add(bondedRolesService);
daoSetupServices.add(myReputationListService);
daoSetupServices.add(myBondedReputationService);
daoSetupServices.add(daoFacade);
daoSetupServices.add(exportJsonFilesService);
daoSetupServices.add(bsqNodeProvider.getBsqNode());
}
public void onAllServicesInitialized(ErrorMessageHandler errorMessageHandler) {
// We need to take care of order of execution. Let's keep both addListeners and start for all main classes even
// if they are not used to have a consistent startup sequence.
daoStateService.addListeners();
cycleService.addListeners();
proposalService.addListeners();
ballotListService.addListeners();
blindVoteListService.addListeners();
myBlindVoteListService.addListeners();
voteRevealService.addListeners();
voteResultService.addListeners();
missingDataRequestService.addListeners();
daoFacade.addListeners();
exportJsonFilesService.addListeners();
daoStateService.start();
cycleService.start();
proposalService.start();
ballotListService.start();
blindVoteListService.start();
myBlindVoteListService.start();
voteRevealService.start();
voteResultService.start();
missingDataRequestService.start();
daoFacade.start();
exportJsonFilesService.start();
bsqNode.setErrorMessageHandler(errorMessageHandler);
bsqNode.start();
daoSetupServices.forEach(daoSetupServices -> {
daoSetupServices.addListeners();
daoSetupServices.start();
});
}
public void shutDown() {

View File

@ -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.core.dao.governance.bond;
import bisq.core.locale.Res;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
/**
* Base class for BondedRole and BondedAsset. Holds the bond state of the bonded asset.
*/
@Getter
public abstract class Bond<T extends BondedAsset> {
@Getter
protected final T bondedAsset;
@Setter
@Nullable
protected String lockupTxId;
@Setter
@Nullable
protected String unlockTxId;
@Setter
protected BondState bondState = BondState.READY_FOR_LOCKUP;
@Setter
private long amount;
@Setter
private long lockupDate;
@Setter
private long unlockDate;
@Setter
private int lockTime;
public Bond(T bondedAsset) {
this.bondedAsset = bondedAsset;
}
public boolean isActive() {
return bondState != BondState.READY_FOR_LOCKUP &&
bondState != BondState.UNLOCKED;
}
public String getDisplayString() {
return Res.get("dao.bonding.info", lockupTxId, getBondedAsset().getDisplayString());
}
@Override
public String toString() {
return "Bond{" +
"\n bondedAsset=" + bondedAsset +
",\n lockupTxId='" + lockupTxId + '\'' +
",\n unlockTxId='" + unlockTxId + '\'' +
",\n bondState=" + bondState +
",\n amount=" + amount +
",\n lockupDate=" + lockupDate +
",\n unlockDate=" + unlockDate +
"\n}";
}
}

View File

@ -88,7 +88,7 @@ public class BondConsensus {
return Arrays.copyOfRange(opReturnData, 5, 25);
}
public static byte[] getHash(BondWithHash bondWithHash) {
return bondWithHash.getHash();
public static byte[] getHash(BondedAsset bondedAsset) {
return bondedAsset.getHash();
}
}

View File

@ -0,0 +1,247 @@
/*
* 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.core.dao.governance.bond;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoSetupService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.SpentInfo;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxType;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Manages bonds.
*/
@Slf4j
public abstract class BondService<T extends Bond, R extends BondedAsset> implements DaoStateListener, DaoSetupService {
protected final DaoStateService daoStateService;
protected final BsqWalletService bsqWalletService;
// This map is just for convenience. The data which are used to fill the map are stored in the DaoState (role, txs).
protected final Map<String, T> bondByUidMap = new HashMap<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public BondService(DaoStateService daoStateService, BsqWalletService bsqWalletService) {
this.daoStateService = daoStateService;
this.bsqWalletService = bsqWalletService;
daoStateService.addBsqStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoSetupService
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void addListeners() {
}
@Override
public void start() {
updateMap();
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onNewBlockHeight(int blockHeight) {
}
@Override
public void onParseTxsComplete(Block block) {
// TODO optimize to not re-write the whole map at each block
updateMap();
}
protected void updateMap() {
getBondedAssetStream().forEach(bondedAsset -> {
String uid = bondedAsset.getUid();
bondByUidMap.putIfAbsent(uid, createBond(bondedAsset));
T bond = bondByUidMap.get(uid);
daoStateService.getLockupTxOutputs().forEach(lockupTxOutput -> {
updateBond(bond, bondedAsset, lockupTxOutput);
});
});
updateBondStateFromUnconfirmedLockupTxs();
updateBondStateFromUnconfirmedUnlockTxs();
}
public void updateBond(T bond, R bondedAsset, TxOutput lockupTxOutput) {
// Lets see if we have a lock up tx.
String lockupTxId = lockupTxOutput.getTxId();
daoStateService.getTx(lockupTxId).ifPresent(lockupTx -> {
byte[] opReturnData = lockupTx.getLastTxOutput().getOpReturnData();
// We used the hash of th bonded bondedAsset object as our hash in OpReturn of the lock up tx to have a
// unique binding of the tx to the data object.
byte[] hash = BondConsensus.getHashFromOpReturnData(opReturnData);
Optional<R> candidate = findBondedAssetByHash(hash);
if (candidate.isPresent() && bondedAsset.equals(candidate.get())) {
bond.setBondState(BondState.LOCKUP_TX_CONFIRMED);
bond.setLockupTxId(lockupTx.getId());
// We use the tx time as we want to have a unique time for all users
bond.setLockupDate(lockupTx.getTime());
bond.setAmount(lockupTx.getLockedAmount());
bond.setLockTime(lockupTx.getLockTime());
if (!daoStateService.isUnspent(lockupTxOutput.getKey())) {
// Lockup is already spent (in unlock tx)
daoStateService.getSpentInfo(lockupTxOutput)
.map(SpentInfo::getTxId)
.flatMap(daoStateService::getTx)
.filter(unlockTx -> unlockTx.getTxType() == TxType.UNLOCK)
.ifPresent(unlockTx -> {
// cross check if it is in daoStateService.getUnlockTxOutputs() ?
String unlockTxId = unlockTx.getId();
bond.setUnlockTxId(unlockTxId);
bond.setBondState(BondState.UNLOCK_TX_CONFIRMED);
bond.setUnlockDate(unlockTx.getTime());
boolean unlocking = daoStateService.isUnlocking(unlockTxId);
if (unlocking) {
bond.setBondState(BondState.UNLOCKING);
} else {
bond.setBondState(BondState.UNLOCKED);
}
});
}
}
});
}
protected abstract T createBond(R bondedAsset);
@Override
public void onParseBlockChainComplete() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public List<T> getBonds() {
return new ArrayList<>(bondByUidMap.values());
}
public List<T> getActiveBonds() {
return bondByUidMap.values().stream()
.filter(T::isActive)
.collect(Collectors.toList());
}
public Optional<T> findBondByLockupTxId(String lockupTxId) {
return bondByUidMap.values().stream()
.filter(bond -> lockupTxId.equals(bond.getLockupTxId()))
.findAny();
}
public boolean wasBondedAssetAlreadyBonded(R bondedAsset) {
T bond = bondByUidMap.get(bondedAsset.getUid());
checkArgument(bond != null, "bond must not be null");
return bond.getLockupTxId() != null;
}
public Optional<R> findBondedAssetByHash(byte[] hash) {
return getBondedAssetStream()
.filter(bondedAsset -> Arrays.equals(bondedAsset.getHash(), hash))
.findAny();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateBondStateFromUnconfirmedLockupTxs() {
getBondedAssetStream().filter(this::isLockupTxUnconfirmed)
.map(bondedAsset -> bondByUidMap.get(bondedAsset.getUid()))
.filter(bond -> bond.getBondState() == BondState.READY_FOR_LOCKUP)
.forEach(bond -> bond.setBondState(BondState.LOCKUP_TX_PENDING));
}
private void updateBondStateFromUnconfirmedUnlockTxs() {
getBondedAssetStream().filter(this::isUnlockTxUnconfirmed)
.map(bondedAsset -> bondByUidMap.get(bondedAsset.getUid()))
.filter(bond -> bond.getBondState() == BondState.LOCKUP_TX_CONFIRMED)
.forEach(bond -> bond.setBondState(BondState.UNLOCK_TX_PENDING));
}
abstract protected Stream<R> getBondedAssetStream();
public boolean isLockupTxUnconfirmed(R bondedAsset) {
return getPendingWalletTransactionsStream()
.map(transaction -> transaction.getOutputs().get(transaction.getOutputs().size() - 1))
.filter(lastOutput -> lastOutput.getScriptPubKey().isOpReturn())
.map(lastOutput -> lastOutput.getScriptPubKey().getChunks())
.filter(chunks -> chunks.size() > 1)
.map(chunks -> chunks.get(1).data)
.anyMatch(data -> Arrays.equals(BondConsensus.getHashFromOpReturnData(data), bondedAsset.getHash()));
}
private boolean isUnlockTxUnconfirmed(R bondedAsset) {
return getPendingWalletTransactionsStream()
.filter(transaction -> transaction.getInputs().size() > 1)
.map(transaction -> transaction.getInputs().get(0))
.map(TransactionInput::getConnectedOutput)
.filter(Objects::nonNull)
.map(TransactionOutput::getParentTransaction)
.filter(Objects::nonNull)
.map(Transaction::getHashAsString)
.flatMap(lockupTxId -> daoStateService.getLockupOpReturnTxOutput(lockupTxId).stream())
.map(BaseTxOutput::getOpReturnData)
.anyMatch(data -> Arrays.equals(BondConsensus.getHashFromOpReturnData(data), bondedAsset.getHash()));
}
private Stream<Transaction> getPendingWalletTransactionsStream() {
return bsqWalletService.getWalletTransactions().stream()
.filter(transaction -> transaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING);
}
}

View File

@ -17,6 +17,13 @@
package bisq.core.dao.governance.bond;
public interface BondWithHash {
/**
* Interface of the bonded asset like the Role or Reputation.
*/
public interface BondedAsset {
byte[] getHash();
String getUid();
String getDisplayString();
}

View File

@ -26,9 +26,9 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.governance.bond.BondWithHash;
import bisq.core.dao.governance.bond.reputation.BondedReputationService;
import bisq.core.dao.governance.bond.reputation.Reputation;
import bisq.core.dao.governance.bond.BondedAsset;
import bisq.core.dao.governance.bond.reputation.MyReputation;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
import bisq.core.dao.governance.bond.role.BondedRolesService;
import bisq.core.dao.state.model.governance.Role;
@ -53,7 +53,7 @@ public class LockupService {
private final WalletsManager walletsManager;
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final BondedReputationService bondedReputationService;
private final MyReputationListService myReputationListService;
private final BondedRolesService bondedRolesService;
@ -65,30 +65,30 @@ public class LockupService {
public LockupService(WalletsManager walletsManager,
BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
BondedReputationService bondedReputationService,
MyReputationListService myReputationListService,
BondedRolesService bondedRolesService) {
this.walletsManager = walletsManager;
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.bondedReputationService = bondedReputationService;
this.myReputationListService = myReputationListService;
this.bondedRolesService = bondedRolesService;
}
public void publishLockupTx(Coin lockupAmount, int lockTime, LockupType lockupType, BondWithHash bondWithHash,
public void publishLockupTx(Coin lockupAmount, int lockTime, LockupType lockupType, BondedAsset bondedAsset,
Consumer<String> resultHandler, ExceptionHandler exceptionHandler) {
checkArgument(lockTime <= BondConsensus.getMaxLockTime() &&
lockTime >= BondConsensus.getMinLockTime(), "lockTime not in rage");
if (bondWithHash instanceof Role) {
Role role = (Role) bondWithHash;
if (bondedRolesService.wasRoleAlreadyBonded(role)) {
if (bondedAsset instanceof Role) {
Role role = (Role) bondedAsset;
if (bondedRolesService.wasBondedAssetAlreadyBonded(role)) {
exceptionHandler.handleException(new RuntimeException("The role has been used already for a lockup tx."));
return;
}
} else if (bondWithHash instanceof Reputation) {
bondedReputationService.addReputation((Reputation) bondWithHash);
} else if (bondedAsset instanceof MyReputation) {
myReputationListService.addReputation((MyReputation) bondedAsset);
}
byte[] hash = BondConsensus.getHash(bondWithHash);
byte[] hash = BondConsensus.getHash(bondedAsset);
try {
byte[] opReturnData = BondConsensus.getLockupOpReturnData(lockTime, lockupType, hash);
Transaction lockupTx = createLockupTx(lockupAmount, opReturnData);

View File

@ -17,54 +17,26 @@
package bisq.core.dao.governance.bond.reputation;
import bisq.core.locale.Res;
import bisq.core.dao.governance.bond.Bond;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
/**
* Wrapper for reputation which contains the mutable state of a bonded reputation. Only kept in memory.
*/
@Getter
public final class BondedReputation {
private final Reputation reputation;
@Nullable
@Setter
private String lockupTxId;
@Nullable
@Setter
private String unlockTxId;
@EqualsAndHashCode(callSuper = true)
public final class BondedReputation extends Bond<Reputation> {
public BondedReputation(Reputation reputation) {
this.reputation = reputation;
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public String getDisplayString() {
return Res.get("dao.bond.bondedReputation");
}
public boolean isLockedUp() {
return lockupTxId != null;
}
public boolean isUnlocked() {
return unlockTxId != null;
super(reputation);
}
@Override
public String toString() {
return "BondedReputation{" +
"\n reputation='" + reputation + '\'' +
",\n lockupTxId='" + lockupTxId + '\'' +
",\n unlockTxId='" + unlockTxId + '\'' +
"\n}";
"\n} " + super.toString();
}
}

View File

@ -17,162 +17,110 @@
package bisq.core.dao.governance.bond.reputation;
import bisq.core.app.BisqEnvironment;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.governance.bond.BondService;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.common.proto.persistable.PersistedDataHost;
import bisq.common.storage.Storage;
import bisq.core.dao.state.model.blockchain.TxOutput;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BondedReputationService implements PersistedDataHost, DaoStateListener {
public interface ReputationListChangeListener {
void onListChanged(List<Reputation> list);
}
private final DaoStateService daoStateService;
private final Storage<ReputationList> storage;
private final ReputationList reputationList = new ReputationList();
@Getter
private final List<ReputationListChangeListener> listeners = new CopyOnWriteArrayList<>();
public class BondedReputationService extends BondService<BondedReputation, Reputation> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public BondedReputationService(Storage<ReputationList> storage, DaoStateService daoStateService) {
this.storage = storage;
this.daoStateService = daoStateService;
public BondedReputationService(DaoStateService daoStateService, BsqWalletService bsqWalletService) {
super(daoStateService, bsqWalletService);
daoStateService.addBsqStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PersistedDataHost
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void readPersisted() {
if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) {
ReputationList persisted = storage.initAndGetPersisted(reputationList, 100);
if (persisted != null) {
reputationList.clear();
reputationList.addAll(persisted.getList());
listeners.forEach(l -> l.onListChanged(reputationList.getList()));
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onNewBlockHeight(int blockHeight) {
}
@Override
public void onParseTxsComplete(Block block) {
/* bondedReputationList.getList().forEach(bondedReputation -> {
daoStateService.getLockupTxOutputs().forEach(lockupTxOutput -> {
String lockupTxId = lockupTxOutput.getTxId();
daoStateService.getTx(lockupTxId)
.ifPresent(lockupTx -> {
byte[] opReturnData = lockupTx.getLastTxOutput().getOpReturnData();
byte[] hash = BondConsensus.getHashFromOpReturnData(opReturnData);
Optional<BondedReputation> candidate = getBondedReputationFromHash(hash);
if (candidate.isPresent() && bondedReputation.equals(candidate.get())) {
if (bondedReputation.getLockupTxId() == null) {
bondedReputation.setLockupTxId(lockupTxId);
persist();
}
if (!daoStateService.isUnspent(lockupTxOutput.getKey())) {
daoStateService.getSpentInfo(lockupTxOutput)
.map(SpentInfo::getTxId)
.map(daoStateService::getTx)
.map(Optional::get)
// TODO(sq): What if the tx is burnt and not unlocked, need to check on that
.filter(unlockTx -> unlockTx.getTxType() == TxType.UNLOCK)
.ifPresent(unlockTx -> {
if (bondedReputation.getUnlockTxId() == null) {
bondedReputation.setUnlockTxId(unlockTx.getId());
persist();
}
// TODO check lock time
});
}
}
});
});
});*/
}
@Override
public void onParseBlockChainComplete() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void start() {
public List<BondedReputation> getActiveBondedReputations() {
return bondByUidMap.values().stream()
.filter(e -> e.isActive())
.collect(Collectors.toList());
}
public void addListener(ReputationListChangeListener listener) {
listeners.add(listener);
public List<BondedReputation> getAllBondedReputations() {
return new ArrayList<>(bondByUidMap.values());
}
public void addReputation(Reputation reputation) {
if (!reputationList.contains(reputation)) {
reputationList.add(reputation);
persist();
}
public List<BondedReputation> getUnconfirmedBondedReputations() {
//TODO
/* Set<String> myWalletTransactionIds = bsqWalletService.getWalletTransactions().stream()
.map(Transaction::getHashAsString)
.collect(Collectors.toSet());
*/
return bondByUidMap.values().stream()
.filter(e -> e.isActive())
.collect(Collectors.toList());
}
public List<BondedReputation> getReputationList() {
return new ArrayList<>(); //bondedReputationList.getList();
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected BondedReputation createBond(Reputation reputation) {
return new BondedReputation(reputation);
}
public Optional<BondedReputation> getBondedReputationFromHash(byte[] hash) {
return Optional.empty();
/*
return bondedReputationList.getList().stream()
.filter(bondedReputation -> {
byte[] candidateHash = bondedReputation.getHash();
*//* log.error("getBondedReputationFromHash: equals?={}, hash={}, candidateHash={}\bondedReputation={}",
Arrays.equals(candidateHash, hash),
Utilities.bytesAsHexString(hash),
Utilities.bytesAsHexString(candidateHash),
bondedReputation.toString());*//*
return Arrays.equals(candidateHash, hash);
@Override
protected Stream<Reputation> getBondedAssetStream() {
return getBondedReputationStream().map(Bond::getBondedAsset);
}
@Override
protected void updateMap() {
bondByUidMap.clear();
getBondedReputationStream().forEach(bondedReputation -> {
bondByUidMap.put(bondedReputation.getBondedAsset().getUid(), bondedReputation);
});
}
private Stream<BondedReputation> getBondedReputationStream() {
return daoStateService.getLockupTxOutputs().stream()
.map(lockupTxOutput -> {
String txId = lockupTxOutput.getTxId();
// long time = daoStateService.getTx(txId).map(BaseTx::getTime).orElse(0L);
// lockupTxOutput is first output, but we need the data from the opReturn
Optional<TxOutput> optionalOpReturnTxOutput = daoStateService.getLockupOpReturnTxOutput(txId);
if (optionalOpReturnTxOutput.isPresent()) {
TxOutput opReturnTxOutput = optionalOpReturnTxOutput.get();
byte[] hash = BondConsensus.getHashFromOpReturnData(opReturnTxOutput.getOpReturnData());
Reputation reputation = new Reputation(hash);
BondedReputation bondedReputation = new BondedReputation(reputation);
updateBond(bondedReputation, reputation, lockupTxOutput);
return bondedReputation;
} else {
return null;
}
})
.findAny();*/
.filter(Objects::nonNull);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void persist() {
storage.queueUpForSave(20);
@Override
public Optional<Reputation> findBondedAssetByHash(byte[] hash) {
return Optional.of(new Reputation(hash));
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.core.dao.governance.bond.reputation;
import bisq.core.dao.governance.bond.Bond;
import lombok.EqualsAndHashCode;
import lombok.Getter;
/**
* Wrapper for reputation which contains the mutable state of a bonded reputation. Only kept in memory.
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public final class MyBondedReputation extends Bond<MyReputation> {
public MyBondedReputation(MyReputation myReputation) {
super(myReputation);
}
@Override
public String toString() {
return "MyBondedReputation{" +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.core.dao.governance.bond.reputation;
import bisq.core.dao.DaoSetupService;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.SpentInfo;
import bisq.core.dao.state.model.blockchain.TxType;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyBondedReputationService implements DaoSetupService {
private final DaoStateService daoStateService;
private final MyReputationListService myReputationListService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MyBondedReputationService(DaoStateService daoStateService,
MyReputationListService myReputationListService) {
this.daoStateService = daoStateService;
this.myReputationListService = myReputationListService;
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoSetupService
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void addListeners() {
}
@Override
public void start() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public List<MyBondedReputation> getMyBondedReputations() {
return myReputationListService.getMyReputationList().stream()
.map(myReputation -> getMyBondedReputation(myReputation).orElse(null))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private Optional<MyBondedReputation> getMyBondedReputation(MyReputation myReputation) {
return daoStateService.getLockupTxOutputs().stream()
.map(lockupTxOutput -> {
String lockupTxId = lockupTxOutput.getTxId();
return daoStateService.getTx(lockupTxId)
.map(lockupTx -> {
byte[] opReturnData = lockupTx.getLastTxOutput().getOpReturnData();
byte[] hash = BondConsensus.getHashFromOpReturnData(opReturnData);
if (Arrays.equals(hash, myReputation.getHash())) {
MyBondedReputation myBondedReputation = new MyBondedReputation(myReputation);
myBondedReputation.setLockTime(lockupTx.getLockTime());
myBondedReputation.setBondState(BondState.LOCKUP_TX_CONFIRMED);
myBondedReputation.setLockupTxId(lockupTx.getId());
// We use the tx time as we want to have a unique time for all users
myBondedReputation.setLockupDate(lockupTx.getTime());
myBondedReputation.setAmount(lockupTx.getLockedAmount());
if (!daoStateService.isUnspent(lockupTxOutput.getKey())) {
// Lockup is already spent (in unlock tx)
daoStateService.getSpentInfo(lockupTxOutput)
.map(SpentInfo::getTxId)
.flatMap(daoStateService::getTx)
.filter(unlockTx -> unlockTx.getTxType() == TxType.UNLOCK)
.ifPresent(unlockTx -> {
// cross check if it is in daoStateService.getUnlockTxOutputs() ?
String unlockTxId = unlockTx.getId();
myBondedReputation.setUnlockTxId(unlockTxId);
myBondedReputation.setBondState(BondState.UNLOCK_TX_CONFIRMED);
myBondedReputation.setUnlockDate(unlockTx.getTime());
boolean unlocking = daoStateService.isUnlocking(unlockTxId);
if (unlocking) {
myBondedReputation.setBondState(BondState.UNLOCKING);
} else {
myBondedReputation.setBondState(BondState.UNLOCKED);
}
});
}
return myBondedReputation;
} else {
return null;
}
})
.orElse(null);
})
.filter(Objects::nonNull)
.findAny();
}
}

View File

@ -0,0 +1,112 @@
/*
* 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.core.dao.governance.bond.reputation;
import bisq.core.dao.governance.bond.BondedAsset;
import bisq.common.crypto.Hash;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.Utilities;
import io.bisq.generated.protobuffer.PB;
import com.google.protobuf.ByteString;
import java.util.Arrays;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.concurrent.Immutable;
@Immutable
@Value
@Slf4j
public final class MyReputation implements PersistablePayload, NetworkPayload, BondedAsset {
private final byte[] salt;
private final transient byte[] hash; // not persisted as it is derived from salt. Stored for caching purpose only.
public MyReputation(byte[] salt) {
this.salt = salt;
this.hash = Hash.getSha256Ripemd160hash(salt);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public PB.MyReputation toProtoMessage() {
return PB.MyReputation.newBuilder().setSalt(ByteString.copyFrom(salt)).build();
}
public static MyReputation fromProto(PB.MyReputation proto) {
return new MyReputation(proto.getSalt().toByteArray());
}
///////////////////////////////////////////////////////////////////////////////////////////
// BondedAsset implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public byte[] getHash() {
return hash;
}
@Override
public String getDisplayString() {
return Utilities.bytesAsHexString(hash);
}
@Override
public String getUid() {
return Utilities.bytesAsHexString(hash);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MyReputation)) return false;
if (!super.equals(o)) return false;
MyReputation that = (MyReputation) o;
return Arrays.equals(hash, that.hash);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Arrays.hashCode(hash);
return result;
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public String toString() {
return "MyReputation{" +
"\n salt=" + Utilities.bytesAsHexString(salt) +
"\n hash=" + Utilities.bytesAsHexString(hash) +
"\n}";
}
}

View File

@ -28,16 +28,16 @@ import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
/**
* PersistableEnvelope wrapper for list of Reputations.
* PersistableEnvelope wrapper for list of MyReputations.
*/
@EqualsAndHashCode(callSuper = true)
public class ReputationList extends PersistableList<Reputation> {
public class MyReputationList extends PersistableList<MyReputation> {
public ReputationList(List<Reputation> list) {
public MyReputationList(List<MyReputation> list) {
super(list);
}
public ReputationList() {
public MyReputationList() {
super();
}
@ -48,26 +48,26 @@ public class ReputationList extends PersistableList<Reputation> {
@Override
public PB.PersistableEnvelope toProtoMessage() {
return PB.PersistableEnvelope.newBuilder().setReputationList(getBuilder()).build();
return PB.PersistableEnvelope.newBuilder().setMyReputationList(getBuilder()).build();
}
public PB.ReputationList.Builder getBuilder() {
return PB.ReputationList.newBuilder()
.addAllReputation(getList().stream()
.map(Reputation::toProtoMessage)
public PB.MyReputationList.Builder getBuilder() {
return PB.MyReputationList.newBuilder()
.addAllMyReputation(getList().stream()
.map(MyReputation::toProtoMessage)
.collect(Collectors.toList()));
}
public static ReputationList fromProto(PB.ReputationList proto) {
return new ReputationList(new ArrayList<>(proto.getReputationList().stream()
.map(Reputation::fromProto)
public static MyReputationList fromProto(PB.MyReputationList proto) {
return new MyReputationList(new ArrayList<>(proto.getMyReputationList().stream()
.map(MyReputation::fromProto)
.collect(Collectors.toList())));
}
@Override
public String toString() {
return "List of salts in ReputationList: " + getList().stream()
.map(Reputation::getSalt)
return "List of salts in MyReputationList: " + getList().stream()
.map(MyReputation::getSalt)
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,114 @@
/*
* 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.core.dao.governance.bond.reputation;
import bisq.core.app.BisqEnvironment;
import bisq.core.dao.DaoSetupService;
import bisq.common.proto.persistable.PersistedDataHost;
import bisq.common.storage.Storage;
import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyReputationListService implements PersistedDataHost, DaoSetupService {
public interface MyReputationListChangeListener {
void onListChanged(List<MyReputation> list);
}
private final Storage<MyReputationList> storage;
private final MyReputationList myReputationList = new MyReputationList();
@Getter
private final List<MyReputationListChangeListener> listeners = new CopyOnWriteArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MyReputationListService(Storage<MyReputationList> storage) {
this.storage = storage;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PersistedDataHost
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void readPersisted() {
if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) {
MyReputationList persisted = storage.initAndGetPersisted(myReputationList, 100);
if (persisted != null) {
myReputationList.clear();
myReputationList.addAll(persisted.getList());
listeners.forEach(l -> l.onListChanged(myReputationList.getList()));
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoSetupService
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void addListeners() {
}
@Override
public void start() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void addListener(MyReputationListChangeListener listener) {
listeners.add(listener);
}
public void addReputation(MyReputation reputation) {
if (!myReputationList.contains(reputation)) {
myReputationList.add(reputation);
persist();
}
}
public List<MyReputation> getMyReputationList() {
return myReputationList.getList();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void persist() {
storage.queueUpForSave(20);
}
}

View File

@ -17,60 +17,70 @@
package bisq.core.dao.governance.bond.reputation;
import bisq.core.dao.governance.bond.BondWithHash;
import bisq.core.dao.governance.bond.BondedAsset;
import bisq.common.crypto.Hash;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.Utilities;
import io.bisq.generated.protobuffer.PB;
import com.google.protobuf.ByteString;
import java.util.Arrays;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.concurrent.Immutable;
/**
* Reputation objects we found on the blockchain. We only know the hash of it.
*/
@Immutable
@Value
public final class Reputation implements PersistablePayload, NetworkPayload, BondWithHash {
private final byte[] salt;
@Slf4j
public final class Reputation implements BondedAsset {
private final byte[] hash;
public Reputation(byte[] salt) {
this.salt = salt;
public Reputation(byte[] hash) {
this.hash = hash;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public PB.Reputation toProtoMessage() {
PB.Reputation.Builder builder = PB.Reputation.newBuilder()
.setSalt(ByteString.copyFrom(salt));
return builder.build();
}
public static Reputation fromProto(PB.Reputation proto) {
return new Reputation(proto.getSalt().toByteArray());
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
// BondedAsset implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public byte[] getHash() {
return Hash.getSha256Ripemd160hash(salt);
return hash;
}
@Override
public String getDisplayString() {
return Utilities.bytesAsHexString(hash);
}
@Override
public String getUid() {
return Utilities.bytesAsHexString(hash);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Reputation)) return false;
if (!super.equals(o)) return false;
Reputation that = (Reputation) o;
return Arrays.equals(hash, that.hash);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Arrays.hashCode(hash);
return result;
}
@Override
public String toString() {
return "Reputation{" +
"\n salt=" + Utilities.bytesAsHexString(salt) +
"\n hash=" + Utilities.bytesAsHexString(hash) +
"\n}";
}
}

View File

@ -17,34 +17,26 @@
package bisq.core.dao.governance.bond.role;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.state.model.governance.Role;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
/**
* Wrapper for role which contains the mutable state of a bonded role. Only kept in memory.
*/
@Getter
public class BondedRole {
private final Role role;
@Setter
@Nullable
private String lockupTxId;
@Setter
@Nullable
private String unlockTxId;
@Setter
private long startDate;
@Setter
private long revokeDate;
@Setter
private BondState bondState = BondState.READY_FOR_LOCKUP;
@EqualsAndHashCode(callSuper = true)
public class BondedRole extends Bond<Role> {
BondedRole(Role role) {
this.role = role;
super(role);
}
@Override
public String toString() {
return "BondedRole{" +
"\n} " + super.toString();
}
}

View File

@ -18,33 +18,18 @@
package bisq.core.dao.governance.bond.role;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.BondWithHash;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.BondService;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.SpentInfo;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -52,19 +37,11 @@ import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Manages bonded roles if they got accepted by voting.
*/
@Slf4j
public class BondedRolesService implements DaoStateListener {
private final DaoStateService daoStateService;
private final BsqWalletService bsqWalletService;
// This map is just for convenience. The data which are used to fill the map are store din the DaoState (role, txs).
private final Map<String, BondedRole> bondedRoleByRoleUidMap = new HashMap<>();
public class BondedRolesService extends BondService<BondedRole, Role> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -72,78 +49,7 @@ public class BondedRolesService implements DaoStateListener {
@Inject
public BondedRolesService(DaoStateService daoStateService, BsqWalletService bsqWalletService) {
this.daoStateService = daoStateService;
this.bsqWalletService = bsqWalletService;
daoStateService.addBsqStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onNewBlockHeight(int blockHeight) {
}
@Override
public void onParseTxsComplete(Block block) {
// TODO optimize to not re-write the whole map at each block
getBondedRoleStream().forEach(bondedRole -> {
bondedRoleByRoleUidMap.putIfAbsent(bondedRole.getUid(), new BondedRole(bondedRole));
BondedRole bondedRoleState = bondedRoleByRoleUidMap.get(bondedRole.getUid());
// Lets see if we have a lock up tx.
daoStateService.getLockupTxOutputs().forEach(lockupTxOutput -> {
String lockupTxId = lockupTxOutput.getTxId();
// log.error("lockupTxId " + lockupTxId);
daoStateService.getTx(lockupTxId).ifPresent(lockupTx -> {
byte[] opReturnData = lockupTx.getLastTxOutput().getOpReturnData();
// We used the hash of th bonded role object as our hash in OpReturn of the lock up tx to have a
// unique binding of the tx to the data object.
byte[] hash = BondConsensus.getHashFromOpReturnData(opReturnData);
Optional<Role> candidate = getBondedRoleFromHash(hash);
if (candidate.isPresent() && bondedRole.equals(candidate.get())) {
bondedRoleState.setBondState(BondState.LOCKUP_TX_CONFIRMED);
bondedRoleState.setLockupTxId(lockupTxId);
// We use the tx time as we want to have a unique time for all users
bondedRoleState.setStartDate(lockupTx.getTime());
if (!daoStateService.isUnspent(lockupTxOutput.getKey())) {
// Lockup is already spent (in unlock tx)
daoStateService.getSpentInfo(lockupTxOutput)
.map(SpentInfo::getTxId)
.flatMap(daoStateService::getTx)
.filter(unlockTx -> unlockTx.getTxType() == TxType.UNLOCK)
.ifPresent(unlockTx -> {
// cross check if it is in daoStateService.getUnlockTxOutputs() ?
String unlockTxId = unlockTx.getId();
bondedRoleState.setUnlockTxId(unlockTxId);
bondedRoleState.setBondState(BondState.UNLOCK_TX_CONFIRMED);
bondedRoleState.setRevokeDate(unlockTx.getTime());
boolean unlocking = daoStateService.isUnlocking(unlockTxId);
if (unlocking) {
bondedRoleState.setBondState(BondState.UNLOCKING);
} else {
bondedRoleState.setBondState(BondState.UNLOCKED);
}
});
}
}
});
});
});
updateBondedRoleStateFromUnconfirmedLockupTxs();
updateBondedRoleStateFromUnconfirmedUnlockTxs();
}
@Override
public void onParseBlockChainComplete() {
super(daoStateService, bsqWalletService);
}
@ -151,44 +57,6 @@ public class BondedRolesService implements DaoStateListener {
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void start() {
}
public Collection<BondedRole> getBondedRoles() {
return bondedRoleByRoleUidMap.values();
}
// bonded roles which are active and can be confiscated
public List<Role> getActiveBondedRoles() {
//TODO
return getBondedRoleList();
}
public Optional<Role> getBondedRoleFromHash(byte[] hash) {
return getBondedRoleStream()
.filter(bondedRole -> Arrays.equals(bondedRole.getHash(), hash))
.findAny();
}
public Optional<BondedRole> getBondedRoleStateFromLockupTxId(String lockupTxId) {
return bondedRoleByRoleUidMap.values().stream()
.filter(bondedRoleState -> lockupTxId.equals(bondedRoleState.getLockupTxId()))
.findAny();
}
public Optional<BondedRoleType> getBondedRoleType(String lockUpTxId) {
return getBondedRoleStateFromLockupTxId(lockUpTxId)
.map(BondedRole::getRole)
.map(Role::getBondedRoleType);
}
public boolean wasRoleAlreadyBonded(Role role) {
BondedRole bondedRole = bondedRoleByRoleUidMap.get(role.getUid());
checkArgument(bondedRole != null, "bondedRole must not be null");
return bondedRole.getLockupTxId() != null;
}
public boolean isMyRole(Role role) {
Set<String> myWalletTransactionIds = bsqWalletService.getWalletTransactions().stream()
.map(Transaction::getHashAsString)
@ -199,59 +67,25 @@ public class BondedRolesService implements DaoStateListener {
.anyMatch(myWalletTransactionIds::contains);
}
public Optional<BondedRoleType> getBondedRoleType(String lockUpTxId) {
return findBondByLockupTxId(lockUpTxId)
.map(Bond::getBondedAsset)
.map(Role::getBondedRoleType);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
private List<Role> getBondedRoleList() {
return getBondedRoleStream().collect(Collectors.toList());
@Override
protected BondedRole createBond(Role role) {
return new BondedRole(role);
}
private void updateBondedRoleStateFromUnconfirmedLockupTxs() {
getBondedRoleStream().filter(this::isLockupTxUnconfirmed)
.map(role -> bondedRoleByRoleUidMap.get(role.getUid()))
.filter(bondedRole -> bondedRole.getBondState() == BondState.READY_FOR_LOCKUP)
.forEach(bondedRole -> bondedRole.setBondState(BondState.LOCKUP_TX_PENDING));
}
private void updateBondedRoleStateFromUnconfirmedUnlockTxs() {
getBondedRoleStream().filter(this::isUnlockTxUnconfirmed)
.map(role -> bondedRoleByRoleUidMap.get(role.getUid()))
.filter(bondedRole -> bondedRole.getBondState() == BondState.LOCKUP_TX_CONFIRMED)
.forEach(bondedRole -> bondedRole.setBondState(BondState.UNLOCK_TX_PENDING));
}
private boolean isLockupTxUnconfirmed(BondWithHash bondWithHash) {
return bsqWalletService.getWalletTransactions().stream()
.filter(transaction -> transaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING)
.map(transaction -> transaction.getOutputs().get(transaction.getOutputs().size() - 1))
.filter(lastOutput -> lastOutput.getScriptPubKey().isOpReturn())
.map(lastOutput -> lastOutput.getScriptPubKey().getChunks())
.filter(chunks -> chunks.size() > 1)
.map(chunks -> chunks.get(1).data)
.anyMatch(data -> Arrays.equals(BondConsensus.getHashFromOpReturnData(data), bondWithHash.getHash()));
}
private boolean isUnlockTxUnconfirmed(BondWithHash bondWithHash) {
return bsqWalletService.getWalletTransactions().stream()
.filter(transaction -> transaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING)
.filter(transaction -> transaction.getInputs().size() > 1)
.map(transaction -> transaction.getInputs().get(0))
.map(TransactionInput::getConnectedOutput)
.filter(Objects::nonNull)
.map(TransactionOutput::getParentTransaction)
.filter(Objects::nonNull)
.map(Transaction::getHashAsString)
.flatMap(lockupTxId -> daoStateService.getLockupOpReturnTxOutput(lockupTxId).stream())
.map(BaseTxOutput::getOpReturnData)
.anyMatch(data -> Arrays.equals(BondConsensus.getHashFromOpReturnData(data), bondWithHash.getHash()));
}
private Stream<Role> getBondedRoleStream() {
return daoStateService.getEvaluatedProposalList().stream()
.filter(evaluatedProposal -> evaluatedProposal.getProposal() instanceof RoleProposal)
.map(e -> ((RoleProposal) e.getProposal()).getRole());
@Override
protected Stream<Role> getBondedAssetStream() {
return getBondedRoleProposalStream().map(RoleProposal::getRole);
}
private Stream<RoleProposal> getBondedRoleProposalStream() {

View File

@ -141,8 +141,8 @@ public class TxParser {
boolean bsqOutputFound = txOutputParser.isBsqOutputFound();
long burntBsq = remainingInputValue + burntBondValue;
boolean hasBurntBSQ = burntBsq > 0;
if (hasBurntBSQ)
boolean hasBurntBsq = burntBsq > 0;
if (hasBurntBsq)
tempTx.setBurntFee(burntBsq);
@ -152,14 +152,14 @@ public class TxParser {
applyTxTypeAndTxOutputType(blockHeight, tempTx, remainingInputValue);
TxType txType = evaluateTxType(tempTx, optionalOpReturnType, hasBurntBSQ, unLockInputValid);
TxType txType = evaluateTxType(tempTx, optionalOpReturnType, hasBurntBsq, unLockInputValid);
tempTx.setTxType(txType);
if (isTxInvalid(tempTx, bsqOutputFound, hasBurntBond)) {
tempTx.setTxType(TxType.INVALID);
txOutputParser.invalidateUTXOCandidates();
if (hasBurntBSQ) {
if (hasBurntBsq) {
log.warn("We have destroyed BSQ because of an invalid tx. Burned BSQ={}. tx={}",
burntBsq / 100D, tempTx);
}

View File

@ -640,6 +640,12 @@ public class DaoStateService implements DaoSetupService {
return getTxOutputsByTxOutputType(TxOutputType.UNLOCK_OUTPUT);
}
public Set<TxOutput> getUnspentLockUpTxOutputs() {
return getTxOutputsByTxOutputType(TxOutputType.LOCKUP_OUTPUT).stream()
.filter(txOutput -> isUnspent(txOutput.getKey()))
.collect(Collectors.toSet());
}
public Optional<TxOutput> getLockupTxOutput(String txId) {
return getTx(txId).flatMap(tx -> tx.getTxOutputs().stream()
.filter(this::isLockupOutput)

View File

@ -135,14 +135,23 @@ public final class Tx extends BaseTx implements PersistablePayload, ImmutableDao
}
// The lockTime is stored in the first output of the LOCKUP tx.
public int getLockTime() {
return txOutputs.get(0).getLockTime();
// TODO MK: Still get confused that we have the lock time stored at a non opReturn output
return getLockupOutput().getLockTime();
}
public long getLockedAmount() {
return getLockupOutput().getValue();
}
// The lockTime is stored in the first output of the LOCKUP tx.
private TxOutput getLockupOutput() {
return txOutputs.get(0);
}
// The unlockBlockHeight is stored in the first output of the UNLOCK tx.
public int getUnlockBlockHeight() {
return txOutputs.get(0).getUnlockBlockHeight();
return getLockupOutput().getUnlockBlockHeight();
}
@Override

View File

@ -17,7 +17,7 @@
package bisq.core.dao.state.model.governance;
import bisq.core.dao.governance.bond.BondWithHash;
import bisq.core.dao.governance.bond.BondedAsset;
import bisq.core.dao.state.model.ImmutableDaoStateModel;
import bisq.core.locale.Res;
@ -44,7 +44,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@Slf4j
@Value
public final class Role implements PersistablePayload, NetworkPayload, BondWithHash, ImmutableDaoStateModel {
public final class Role implements PersistablePayload, NetworkPayload, BondedAsset, ImmutableDaoStateModel {
private final String uid;
private final String name;
private final String link;
@ -99,7 +99,7 @@ public final class Role implements PersistablePayload, NetworkPayload, BondWithH
///////////////////////////////////////////////////////////////////////////////////////////
// BondWithHash implementation
// BondedAsset implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
@ -109,15 +109,16 @@ public final class Role implements PersistablePayload, NetworkPayload, BondWithH
return Hash.getSha256Ripemd160hash(bytes);
}
@Override
public String getDisplayString() {
return Res.get("dao.bond.bondedRoleType." + bondedRoleType.name()) + ": " + name;
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public String getDisplayString() {
return Res.get("dao.bond.bondedRoleType." + bondedRoleType.name()) + ": " + name;
}
// We use only the immutable data
// bondedRoleType must not be used directly for hashCode or equals as it delivers the Object.hashCode (internal address)!
// The equals and hashCode methods cannot be overwritten in Enums.

View File

@ -23,6 +23,7 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.governance.asset.RemovedAssetsList;
import bisq.core.dao.governance.blindvote.MyBlindVoteList;
import bisq.core.dao.governance.blindvote.storage.BlindVoteStore;
import bisq.core.dao.governance.bond.reputation.MyReputationList;
import bisq.core.dao.governance.myvote.MyVoteList;
import bisq.core.dao.governance.proposal.MyProposalList;
import bisq.core.dao.governance.proposal.storage.appendonly.ProposalStore;
@ -132,6 +133,9 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
return RemovedAssetsList.fromProto(proto.getRemovedAssetList());
case DAO_STATE_STORE:
return DaoStateStore.fromProto(proto.getDaoStateStore());
case MY_REPUTATION_LIST:
return MyReputationList.fromProto(proto.getMyReputationList());
default:
throw new ProtobufferRuntimeException("Unknown proto message case(PB.PersistableEnvelope). " +
"messageCase=" + proto.getMessageCase() + "; proto raw data=" + proto.toString());

View File

@ -23,7 +23,7 @@ import bisq.core.dao.DaoOptionKeys;
import bisq.core.dao.governance.asset.AssetService;
import bisq.core.dao.governance.ballot.BallotListService;
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
import bisq.core.dao.governance.bond.reputation.BondedReputationService;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
import bisq.core.dao.governance.myvote.MyVoteListService;
import bisq.core.dao.governance.proposal.MyProposalListService;
import bisq.core.offer.OpenOfferManager;
@ -67,7 +67,7 @@ public class CorePersistedDataHost {
persistedDataHosts.add(injector.getInstance(MyBlindVoteListService.class));
persistedDataHosts.add(injector.getInstance(MyVoteListService.class));
persistedDataHosts.add(injector.getInstance(MyProposalListService.class));
persistedDataHosts.add(injector.getInstance(BondedReputationService.class));
persistedDataHosts.add(injector.getInstance(MyReputationListService.class));
persistedDataHosts.add(injector.getInstance(AssetService.class));
}
return persistedDataHosts;

View File

@ -1310,7 +1310,8 @@ dao.results.votes.table.header.vote=Vote
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Bonded reputation
dao.bonding.menuItem.bonds=Bonds
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup a bond for reputation
dao.bonding.reputation.list.header=My reputation bonds
dao.bonding.lock.amount=Amount of BSQ to lockup
dao.bonding.lock.time=Unlock time in blocks
dao.bonding.lock.salt=Salt
@ -1323,13 +1324,18 @@ dao.bonding.lock.sendFunds.headline=Confirm lockup transaction
dao.bonding.lock.sendFunds.details=Lockup amount: {0}\nLockup time: {1} block(s)\n\nAre you sure you want to proceed?
dao.bonding.unlock.time=Lock time
dao.bonding.unlock.unlock=Unlock
dao.bonding.unlock.type=Type
dao.bonding.unlock.hash=Hash
dao.bonding.unlock.salt=Salt
dao.bonding.unlock.reputation=Reputation
dao.bonding.unlock.sendTx.headline=Confirm unlock transaction
dao.bonding.unlock.sendTx.details=Unlock amount: {0}\nLockup time: {1} block(s)\n\nAre you sure you want to proceed?
dao.bonding.dashboard.bondsHeadline=Bonded BSQ
dao.bonding.dashboard.lockupAmount=Lockup funds
dao.bonding.dashboard.unlockingAmount=Unlocking funds (wait until lock time is over)
dao.bonding.bonds.table.lockupTxId=Lockup tx ID
dao.bonding.info=Lockup Tx ID: {0} / {1}
dao.bonding.reputation.salt.info=Salt: {0}
# suppress inspection "UnusedProperty"
dao.bond.lockupType.BONDED_ROLE=Bonded role
@ -1390,9 +1396,12 @@ dao.bond.table.column.header.lockupTxId=Lockup Tx ID
dao.bond.table.column.header.revokeDate=Revoked
dao.bond.table.column.header.unlockTxId=Unlock Tx ID
dao.bond.table.column.header.bondState=Bond state
dao.bond.table.column.header.lockTime=Lock time
dao.bond.table.column.header.lockupDate=Lockup date
dao.bond.table.button.lockup=Lockup
dao.bond.table.button.revoke=Revoke
dao.bond.table.button.unlock=Unlock
dao.bond.table.notBonded=Not bonded yet
dao.bond.table.lockedUp=Bond locked up
dao.bond.table.unlocking=Bond unlocking

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Stimme
dao.bonding.menuItem.bondedRoles=Gekoppelte Rollen
dao.bonding.menuItem.reputation=BSQ sperren
dao.bonding.menuItem.bonds=BSQ entsperren
dao.bonding.lock.lockBSQ=BSQ sperren
dao.bonding.reputation.header=BSQ sperren
dao.bonding.lock.amount=Betrag von BSQ zu sperren:
dao.bonding.lock.time=Entsperrung-Zeit in Blöcken:
dao.bonding.lock.type=Art der Kopplung:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Ψήφισε
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=otar
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=رأی
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Szavazás
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Votar
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Votează
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Голосование
dao.bonding.menuItem.bondedRoles=Обеспеченные роли
dao.bonding.menuItem.reputation=Запереть BSQ
dao.bonding.menuItem.bonds=Разблокировать BSQ
dao.bonding.lock.lockBSQ=Запереть BSQ
dao.bonding.reputation.header=Запереть BSQ
dao.bonding.lock.amount=Запереть BSQ на сумму:
dao.bonding.lock.time=Срок разблокировки в блоках:
dao.bonding.lock.type=Тир гарантийного депозита:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Glasaj
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=โหวต
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=Bỏ phiếu
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -1124,7 +1124,7 @@ dao.results.votes.table.header.vote=投票
dao.bonding.menuItem.bondedRoles=Bonded roles
dao.bonding.menuItem.reputation=Lockup BSQ
dao.bonding.menuItem.bonds=Unlock BSQ
dao.bonding.lock.lockBSQ=Lockup BSQ
dao.bonding.reputation.header=Lockup BSQ
dao.bonding.lock.amount=Amount of BSQ to lockup:
dao.bonding.lock.time=Unlock time in blocks:
dao.bonding.lock.type=Type of bond:

View File

@ -29,7 +29,7 @@ import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.bonding.bonds.BondsView;
import bisq.desktop.main.dao.bonding.dashboard.BondingDashboardView;
import bisq.desktop.main.dao.bonding.reputation.ReputationView;
import bisq.desktop.main.dao.bonding.reputation.MyBondedReputationView;
import bisq.desktop.main.dao.bonding.roles.BondedRolesView;
import bisq.core.locale.Res;
@ -86,7 +86,7 @@ public class BondingView extends ActivatableViewAndModel {
bondedRoles = new MenuItem(navigation, toggleGroup, Res.get("dao.bonding.menuItem.bondedRoles"),
BondedRolesView.class, AwesomeIcon.SHIELD, baseNavPath);
reputation = new MenuItem(navigation, toggleGroup, Res.get("dao.bonding.menuItem.reputation"),
ReputationView.class, AwesomeIcon.LOCK, baseNavPath);
MyBondedReputationView.class, AwesomeIcon.LOCK, baseNavPath);
bonds = new MenuItem(navigation, toggleGroup, Res.get("dao.bonding.menuItem.bonds"),
BondsView.class, AwesomeIcon.UNLOCK, baseNavPath);
@ -131,7 +131,7 @@ public class BondingView extends ActivatableViewAndModel {
if (view instanceof BondingDashboardView) dashboard.setSelected(true);
else if (view instanceof BondedRolesView) bondedRoles.setSelected(true);
else if (view instanceof ReputationView) reputation.setSelected(true);
else if (view instanceof MyBondedReputationView) reputation.setSelected(true);
else if (view instanceof BondsView) bonds.setSelected(true);
}
}

View File

@ -26,9 +26,9 @@ import bisq.desktop.util.GUIUtil;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.BondWithHash;
import bisq.core.dao.governance.bond.BondedAsset;
import bisq.core.dao.governance.bond.lockup.LockupType;
import bisq.core.dao.governance.bond.reputation.Reputation;
import bisq.core.dao.governance.bond.reputation.MyReputation;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.Role;
@ -70,7 +70,7 @@ public class BondingViewUtils {
this.bsqFormatter = bsqFormatter;
}
private void lockupBond(BondWithHash bondWithHash, Coin lockupAmount, int lockupTime, LockupType lockupType,
private void lockupBond(BondedAsset bondedAsset, Coin lockupAmount, int lockupTime, LockupType lockupType,
Consumer<String> resultHandler) {
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
if (!DevEnv.isDevMode()) {
@ -80,22 +80,22 @@ public class BondingViewUtils {
lockupTime
))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> publishLockupTx(bondWithHash, lockupAmount, lockupTime, lockupType, resultHandler))
.onAction(() -> publishLockupTx(bondedAsset, lockupAmount, lockupTime, lockupType, resultHandler))
.closeButtonText(Res.get("shared.cancel"))
.show();
} else {
publishLockupTx(bondWithHash, lockupAmount, lockupTime, lockupType, resultHandler);
publishLockupTx(bondedAsset, lockupAmount, lockupTime, lockupType, resultHandler);
}
} else {
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
}
}
private void publishLockupTx(BondWithHash bondWithHash, Coin lockupAmount, int lockupTime, LockupType lockupType, Consumer<String> resultHandler) {
private void publishLockupTx(BondedAsset bondedAsset, Coin lockupAmount, int lockupTime, LockupType lockupType, Consumer<String> resultHandler) {
daoFacade.publishLockupTx(lockupAmount,
lockupTime,
lockupType,
bondWithHash,
bondedAsset,
txId -> {
if (!DevEnv.isDevMode())
new Popup<>().feedback(Res.get("dao.tx.published.success")).show();
@ -115,8 +115,8 @@ public class BondingViewUtils {
}
public void lockupBondForReputation(Coin lockupAmount, int lockupTime, byte[] salt, Consumer<String> resultHandler) {
Reputation reputation = new Reputation(salt);
lockupBond(reputation, lockupAmount, lockupTime, LockupType.REPUTATION, resultHandler);
MyReputation myReputation = new MyReputation(salt);
lockupBond(myReputation, lockupAmount, lockupTime, LockupType.REPUTATION, resultHandler);
}
public void unLock(String lockupTxId, Consumer<String> resultHandler) {

View File

@ -18,135 +18,118 @@
package bisq.desktop.main.dao.bonding.bonds;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.TxConfidenceListItem;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.main.dao.bonding.BondingViewUtils;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.governance.bond.lockup.LockupType;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.role.BondedRolesService;
import bisq.core.dao.state.model.blockchain.BaseTxOutput;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.locale.Res;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.util.BsqFormatter;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javafx.scene.control.Button;
import java.util.Date;
import java.util.Optional;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@EqualsAndHashCode(callSuper = true)
@EqualsAndHashCode
@Data
@Slf4j
class BondListItem extends TxConfidenceListItem {
private final BtcWalletService btcWalletService;
class BondListItem implements DaoStateListener {
private final Bond bond;
private final DaoFacade daoFacade;
private final BsqFormatter bsqFormatter;
private final BondedRolesService bondedRolesService;
private final Date date;
private final BondingViewUtils bondingViewUtils;
private final BsqFormatter bsqFormatter;
private final String info;
private final String txId;
private final String amount;
private final String lockTime;
private Coin amount = Coin.ZERO;
private int lockTime;
private AutoTooltipButton button;
@Getter
private Button button;
private TxConfidenceIndicator txConfidenceIndicator;
private TxConfidenceListener txConfidenceListener;
private boolean issuanceTx;
BondListItem(Transaction transaction,
BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
BondListItem(Bond bond,
DaoFacade daoFacade,
BondedRolesService bondedRolesService,
Date date,
BondingViewUtils bondingViewUtils,
BsqFormatter bsqFormatter) {
super(transaction, bsqWalletService);
this.btcWalletService = btcWalletService;
this.bond = bond;
this.daoFacade = daoFacade;
this.bondedRolesService = bondedRolesService;
this.date = date;
this.bondingViewUtils = bondingViewUtils;
this.bsqFormatter = bsqFormatter;
checkNotNull(transaction, "transaction must not be null as we only have list items from transactions " +
"which are available in the wallet");
daoFacade.getLockupTxOutput(transaction.getHashAsString())
.ifPresent(out -> amount = Coin.valueOf(out.getValue()));
Optional<Integer> opLockTime = daoFacade.getLockTime(transaction.getHashAsString());
lockTime = opLockTime.orElse(-1);
info = bond.getBondedAsset().getDisplayString();
txId = bond.getLockupTxId();
amount = bsqFormatter.formatCoin(Coin.valueOf(bond.getAmount()));
lockTime = bsqFormatter.formatDateTime(new Date(bond.getLockupDate()));
button = new AutoTooltipButton();
button.setMinWidth(70);
button.updateText(Res.get("dao.bonding.unlock.unlock"));
button.setVisible(true);
button.setManaged(true);
}
public boolean isLockupAndUnspent() {
boolean isLocked;
Optional<BondedRole> optionalBondedRoleState = bondedRolesService.getBondedRoleStateFromLockupTxId(txId);
if (optionalBondedRoleState.isPresent()) {
BondedRole bondedRole = optionalBondedRoleState.get();
//TODO
//isLocked = bondedRole.getLockupTxId() != null && bondedRole.getUnlockTxId() == null;
//log.error("isLocked {}, tx={}",isLocked,bondedRole.getLockupTxId());
} else {
//TODO get reputation
isLocked = true;
}
return /*isLocked && */!isSpent() && getTxType() == TxType.LOCKUP;
}
private boolean isSpent() {
Optional<TxOutput> optionalTxOutput = daoFacade.getLockupTxOutput(txId);
return optionalTxOutput.map(txOutput -> !daoFacade.isUnspent(txOutput.getKey()))
.orElse(true);
}
public TxType getTxType() {
return daoFacade.getTx(txId)
.flatMap(tx -> daoFacade.getOptionalTxType(tx.getId()))
.orElse(confirmations == 0 ? TxType.UNVERIFIED : TxType.UNDEFINED_TX_TYPE);
}
private Optional<LockupType> getOptionalLockupType() {
return getOpReturnData()
.flatMap(BondConsensus::getLockupType);
}
private Optional<byte[]> getOpReturnData() {
return daoFacade.getLockupOpReturnTxOutput(txId).map(BaseTxOutput::getOpReturnData);
}
public String getInfo() {
Optional<BondedRoleType> optionalRoleType = bondedRolesService.getBondedRoleType(txId);
if (optionalRoleType.isPresent()) {
return optionalRoleType.get().getDisplayString();
} else {
Optional<LockupType> optionalLockupType = getOptionalLockupType();
if (optionalLockupType.isPresent()) {
LockupType lockupType = optionalLockupType.get();
if (lockupType == LockupType.REPUTATION)
return Res.get("dao.bonding.unlock.reputation");
// label = new Label();
/*
daoFacade.addBsqStateListener(this);
button.setOnAction(e -> {
if (bondedRole.getBondState() == BondState.READY_FOR_LOCKUP) {
bondingViewUtils.lockupBondForBondedRole(role,
txId -> {
bondedRole.setLockupTxId(txId);
bondedRole.setBondState(BondState.LOCKUP_TX_PENDING);
update();
button.setDisable(true);
});
} else if (bondedRole.getBondState() == BondState.LOCKUP_TX_CONFIRMED) {
bondingViewUtils.unLock(bondedRole.getLockupTxId(),
txId -> {
bondedRole.setUnlockTxId(txId);
bondedRole.setBondState(BondState.UNLOCK_TX_PENDING);
update();
button.setDisable(true);
});
}
}
return Res.get("shared.na");
});*/
}
private void update() {
/* label.setText(Res.get("dao.bond.bondState." + bondedRole.getBondState().name()));
boolean showLockup = bondedRole.getBondState() == BondState.READY_FOR_LOCKUP;
boolean showRevoke = bondedRole.getBondState() == BondState.LOCKUP_TX_CONFIRMED;
if (showLockup)
button.updateText(Res.get("dao.bond.table.button.lockup"));
else if (showRevoke)
button.updateText(Res.get("dao.bond.table.button.revoke"));
boolean showButton = isMyRole && (showLockup || showRevoke);
button.setVisible(showButton);
button.setManaged(showButton);*/
}
public void cleanup() {
// daoFacade.removeBsqStateListener(this);
// button.setOnAction(null);
}
// DaoStateListener
@Override
public void onNewBlockHeight(int blockHeight) {
}
@Override
public void onParseTxsComplete(Block block) {
update();
}
@Override
public void onParseBlockChainComplete() {
}
}

View File

@ -28,12 +28,10 @@ import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.role.BondedRolesService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.BsqFormatter;
@ -61,12 +59,9 @@ import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@ -75,7 +70,6 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
private TableView<BondListItem> tableView;
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final BsqFormatter bsqFormatter;
private final BsqBalanceUtil bsqBalanceUtil;
private final BsqValidator bsqValidator;
@ -87,7 +81,6 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
private int gridRow = 0;
private final ObservableList<BondListItem> observableList = FXCollections.observableArrayList();
private final FilteredList<BondListItem> lockupTxs = new FilteredList<>(observableList);
private ListChangeListener<Transaction> walletBsqTransactionsListener;
private ChangeListener<Number> walletChainHeightListener;
@ -99,7 +92,6 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
@Inject
private BondsView(BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
BsqFormatter bsqFormatter,
BsqBalanceUtil bsqBalanceUtil,
BsqValidator bsqValidator,
@ -108,7 +100,6 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
DaoFacade daoFacade,
Preferences preferences) {
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.bsqFormatter = bsqFormatter;
this.bsqBalanceUtil = bsqBalanceUtil;
this.bsqValidator = bsqValidator;
@ -124,14 +115,8 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
tableView = new TableView<>();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPrefHeight(300);
addTxIdColumn();
addInfoColumn();
addAmountColumn();
addLockTimeColumn();
addUnlockColumn();
addColumns();
lockupTxs.setPredicate(BondListItem::isLockupAndUnspent);
walletBsqTransactionsListener = change -> updateList();
walletChainHeightListener = (observable, oldValue, newValue) -> updateList();
@ -157,9 +142,9 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
bsqWalletService.getWalletTransactions().addListener(walletBsqTransactionsListener);
bsqWalletService.addBsqBalanceListener(this);
btcWalletService.getChainHeightProperty().addListener(walletChainHeightListener);
bsqWalletService.getChainHeightProperty().addListener(walletChainHeightListener);
tableView.setItems(lockupTxs);
tableView.setItems(observableList);
daoFacade.addBsqStateListener(this);
@ -171,13 +156,12 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
bsqBalanceUtil.deactivate();
bsqWalletService.removeBsqBalanceListener(this);
lockupTxs.predicateProperty().unbind();
bsqWalletService.getWalletTransactions().removeListener(walletBsqTransactionsListener);
bsqWalletService.removeBsqBalanceListener(this);
btcWalletService.getChainHeightProperty().removeListener(walletChainHeightListener);
bsqWalletService.getChainHeightProperty().removeListener(walletChainHeightListener);
daoFacade.removeBsqStateListener(this);
observableList.forEach(BondListItem::cleanup);
// observableList.forEach(BondListItem::cleanup);
}
@ -224,22 +208,17 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
}
private void updateList() {
observableList.forEach(BondListItem::cleanup);
// copy list to avoid ConcurrentModificationException
final List<Transaction> walletTransactions = new ArrayList<>(bsqWalletService.getWalletTransactions());
List<BondListItem> items = walletTransactions.stream()
.map(transaction -> {
return new BondListItem(transaction,
bsqWalletService,
btcWalletService,
List<BondListItem> items = daoFacade.getAllActiveBonds().stream()
.map(bond -> {
return new BondListItem(bond,
daoFacade,
bondedRolesService,
transaction.getUpdateTime(),
bondingViewUtils,
bsqFormatter);
})
.collect(Collectors.toList());
observableList.setAll(items);
GUIUtil.setFitToRowsForTableView(tableView, 37, 28, 2, 10);
}
@ -247,9 +226,85 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void addTxIdColumn() {
TableColumn<BondListItem, BondListItem> column = new AutoTooltipTableColumn<>(Res.get("shared.txId"));
private void addColumns() {
TableColumn<BondListItem, BondListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.bonding.unlock.hash"));
column.setMinWidth(160);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getInfo());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", "BSQ"));
column.setMinWidth(120);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAmount());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bonding.unlock.time"));
column.setMinWidth(140);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLockTime());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bonding.bonds.table.lockupTxId"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(60);
column.setCellFactory(
@ -282,135 +337,36 @@ public class BondsView extends ActivatableView<GridPane, Void> implements BsqBal
}
});
tableView.getColumns().add(column);
}
private void addInfoColumn() {
TableColumn<BondListItem, BondListItem> column =
new AutoTooltipTableColumn<>(Res.get("dao.bonding.unlock.type"));
column.setMinWidth(160);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
column = new TableColumn<>();
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getInfo());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
}
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
Button button;
private void addAmountColumn() {
TableColumn<BondListItem, BondListItem> column =
new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", "BSQ"));
column.setMinWidth(120);
column.setMaxWidth(column.getMinWidth());
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
TxType txType = item.getTxType();
setText(item.getConfirmations() > 0 && txType.ordinal() > TxType.INVALID.ordinal() ?
bsqFormatter.formatCoin(item.getAmount()) :
Res.get("shared.na"));
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
}
private void addLockTimeColumn() {
TableColumn<BondListItem, BondListItem> column =
new AutoTooltipTableColumn<>(Res.get("dao.bonding.unlock.time"));
column.setMinWidth(120);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
TxType txType = item.getTxType();
setText(item.getConfirmations() > 0 && txType.ordinal() > TxType.INVALID.ordinal() ?
Integer.toString(item.getLockTime()) :
Res.get("shared.na"));
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
}
private void addUnlockColumn() {
TableColumn<BondListItem, BondListItem> unlockColumn = new TableColumn<>();
unlockColumn.setMinWidth(130);
unlockColumn.setMaxWidth(unlockColumn.getMinWidth());
unlockColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
unlockColumn.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
Button button;
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = item.getButton();
button.setOnAction(e -> bondingViewUtils.unLock(item.getTxId(), txId -> {
//TODO
button.setDisable(true);
}));
setGraphic(button);
if (item != null && !empty) {
if (button == null) {
button = item.getButton();
setGraphic(button);
}
} else {
setGraphic(null);
if (button != null)
button = null;
}
}
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
};
}
};
}
});
unlockColumn.setComparator(Comparator.comparing(BondListItem::getConfirmations));
tableView.getColumns().add(unlockColumn);
});
tableView.getColumns().add(column);
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.desktop.main.dao.bonding.reputation;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.main.dao.bonding.BondingViewUtils;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.reputation.MyBondedReputation;
import bisq.core.dao.governance.bond.reputation.MyReputation;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.locale.Res;
import bisq.core.util.BsqFormatter;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import javafx.scene.control.Label;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@EqualsAndHashCode
@Data
@Slf4j
class MyBondedReputationListItem implements DaoStateListener {
private final MyBondedReputation myBondedReputation;
private final DaoFacade daoFacade;
private final BsqFormatter bsqFormatter;
private final String hash, salt;
private final String txId;
private final String amount;
private final String lockupDate;
private final String lockTime;
@Getter
private final AutoTooltipButton button;
@Getter
private final Label stateLabel;
MyBondedReputationListItem(MyBondedReputation myBondedReputation,
DaoFacade daoFacade,
BondingViewUtils bondingViewUtils,
BsqFormatter bsqFormatter) {
this.myBondedReputation = myBondedReputation;
this.daoFacade = daoFacade;
this.bsqFormatter = bsqFormatter;
MyReputation myReputation = myBondedReputation.getBondedAsset();
hash = Utilities.bytesAsHexString(myReputation.getHash());
salt = Utilities.bytesAsHexString(myReputation.getSalt());
txId = myBondedReputation.getLockupTxId();
amount = bsqFormatter.formatCoin(Coin.valueOf(myBondedReputation.getAmount()));
lockupDate = bsqFormatter.formatDateTime(new Date(myBondedReputation.getLockupDate()));
lockTime = Integer.toString(myBondedReputation.getLockTime());
daoFacade.addBsqStateListener(this);
button = new AutoTooltipButton();
stateLabel = new Label();
button.setOnAction(e -> {
if (myBondedReputation.getBondState() == BondState.LOCKUP_TX_CONFIRMED) {
bondingViewUtils.unLock(myBondedReputation.getLockupTxId(),
txId -> {
myBondedReputation.setUnlockTxId(txId);
myBondedReputation.setBondState(BondState.UNLOCK_TX_PENDING);
update();
button.setDisable(true);
});
}
});
update();
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onNewBlockHeight(int blockHeight) {
}
@Override
public void onParseTxsComplete(Block block) {
update();
}
@Override
public void onParseBlockChainComplete() {
}
void cleanup() {
daoFacade.removeBsqStateListener(this);
button.setOnAction(null);
}
private void update() {
stateLabel.setText(Res.get("dao.bond.bondState." + myBondedReputation.getBondState().name()));
boolean showButton = myBondedReputation.getBondState() == BondState.LOCKUP_TX_CONFIRMED;
if (showButton)
button.updateText(Res.get("dao.bond.table.button.unlock"));
button.setVisible(showButton);
button.setManaged(showButton);
}
}

View File

@ -21,7 +21,7 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.bonding.reputation.ReputationView"
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.bonding.reputation.MyBondedReputationView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"

View File

@ -0,0 +1,554 @@
/*
* 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.desktop.main.dao.bonding.reputation;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.bonding.BondingViewUtils;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.BsqFormatter;
import bisq.core.util.validation.HexStringValidator;
import bisq.core.util.validation.IntegerValidator;
import bisq.common.crypto.Hash;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import com.google.common.base.Charsets;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.util.Callback;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class MyBondedReputationView extends ActivatableView<GridPane, Void> implements DaoStateListener, BsqBalanceListener {
private final BsqWalletService bsqWalletService;
private final BsqFormatter bsqFormatter;
private final BondingViewUtils bondingViewUtils;
private final HexStringValidator hexStringValidator;
private final BsqValidator bsqValidator;
private final DaoFacade daoFacade;
private final Preferences preferences;
private final IntegerValidator timeInputTextFieldValidator;
private int gridRow = 0;
private InputTextField amountInputTextField, timeInputTextField, saltInputTextField;
private Button lockupButton;
private TableView<MyBondedReputationListItem> tableView;
private ChangeListener<Boolean> amountFocusOutListener, timeFocusOutListener, saltFocusOutListener;
private ChangeListener<String> amountInputTextFieldListener, timeInputTextFieldListener, saltInputTextFieldListener;
private final ObservableList<MyBondedReputationListItem> observableList = FXCollections.observableArrayList();
private ListChangeListener<Transaction> walletBsqTransactionsListener;
private ChangeListener<Number> walletChainHeightListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private MyBondedReputationView(BsqWalletService bsqWalletService,
BsqFormatter bsqFormatter,
BondingViewUtils bondingViewUtils,
HexStringValidator hexStringValidator,
BsqValidator bsqValidator,
DaoFacade daoFacade,
Preferences preferences) {
this.bsqWalletService = bsqWalletService;
this.bsqFormatter = bsqFormatter;
this.bondingViewUtils = bondingViewUtils;
this.hexStringValidator = hexStringValidator;
this.bsqValidator = bsqValidator;
this.daoFacade = daoFacade;
this.preferences = preferences;
timeInputTextFieldValidator = new IntegerValidator();
timeInputTextFieldValidator.setMinValue(BondConsensus.getMinLockTime());
timeInputTextFieldValidator.setMaxValue(BondConsensus.getMaxLockTime());
}
@Override
public void initialize() {
int columnSpan = 3;
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 3, Res.get("dao.bonding.reputation.header"));
GridPane.setColumnSpan(titledGroupBg, columnSpan);
amountInputTextField = addInputTextField(root, gridRow, Res.get("dao.bonding.lock.amount"),
Layout.FIRST_ROW_DISTANCE);
amountInputTextField.setValidator(bsqValidator);
GridPane.setColumnSpan(amountInputTextField, columnSpan);
timeInputTextField = FormBuilder.addInputTextField(root, ++gridRow, Res.get("dao.bonding.lock.time"));
GridPane.setColumnSpan(timeInputTextField, columnSpan);
timeInputTextField.setValidator(timeInputTextFieldValidator);
saltInputTextField = FormBuilder.addInputTextField(root, ++gridRow, Res.get("dao.bonding.lock.salt"));
GridPane.setColumnSpan(saltInputTextField, columnSpan);
saltInputTextField.setValidator(hexStringValidator);
lockupButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.bonding.lock.lockupButton"));
createTableView(columnSpan);
createListeners();
}
@Override
protected void activate() {
amountInputTextField.textProperty().addListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().addListener(amountFocusOutListener);
timeInputTextField.textProperty().addListener(timeInputTextFieldListener);
timeInputTextField.focusedProperty().addListener(timeFocusOutListener);
saltInputTextField.textProperty().addListener(saltInputTextFieldListener);
saltInputTextField.focusedProperty().addListener(saltFocusOutListener);
bsqWalletService.getWalletTransactions().addListener(walletBsqTransactionsListener);
bsqWalletService.addBsqBalanceListener(this);
bsqWalletService.getChainHeightProperty().addListener(walletChainHeightListener);
lockupButton.setOnAction((event) -> {
Coin lockupAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
int lockupTime = Integer.parseInt(timeInputTextField.getText());
byte[] salt = Utilities.decodeFromHex(saltInputTextField.getText());
bondingViewUtils.lockupBondForReputation(lockupAmount,
lockupTime,
salt,
txId -> {
});
amountInputTextField.setText("");
timeInputTextField.setText("");
setNewRandomSalt();
});
daoFacade.addBsqStateListener(this);
amountInputTextField.resetValidation();
timeInputTextField.resetValidation();
setNewRandomSalt();
tableView.setItems(observableList);
onUpdateBalances();
updateList();
}
@Override
protected void deactivate() {
observableList.forEach(MyBondedReputationListItem::cleanup);
amountInputTextField.textProperty().removeListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().removeListener(amountFocusOutListener);
timeInputTextField.textProperty().removeListener(timeInputTextFieldListener);
timeInputTextField.focusedProperty().removeListener(timeFocusOutListener);
saltInputTextField.textProperty().removeListener(saltInputTextFieldListener);
saltInputTextField.focusedProperty().removeListener(saltFocusOutListener);
bsqWalletService.getWalletTransactions().removeListener(walletBsqTransactionsListener);
bsqWalletService.addBsqBalanceListener(this);
bsqWalletService.getChainHeightProperty().removeListener(walletChainHeightListener);
lockupButton.setOnAction(null);
daoFacade.removeBsqStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onNewBlockHeight(int blockHeight) {
}
@Override
public void onParseTxsComplete(Block block) {
updateList();
}
@Override
public void onParseBlockChainComplete() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqBalanceListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onUpdateBalances(Coin confirmedBalance,
Coin availableNonBsqBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
bsqValidator.setAvailableBalance(confirmedBalance);
updateButtonState();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void createListeners() {
amountFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
onUpdateBalances();
}
};
timeFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
onUpdateBalances();
}
};
saltFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
onUpdateBalances();
}
};
amountInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
timeInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
saltInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
walletBsqTransactionsListener = change -> updateList();
walletChainHeightListener = (observable, oldValue, newValue) -> updateList();
}
private void createTableView(int columnSpan) {
TitledGroupBg titledGroupBg2 = addTitledGroupBg(root, ++gridRow, 2, Res.get("dao.bonding.reputation.list.header"), 20);
GridPane.setColumnSpan(titledGroupBg2, columnSpan);
tableView = new TableView<>();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
createColumns();
VBox vBox = new VBox();
vBox.setSpacing(10);
GridPane.setRowIndex(vBox, ++gridRow);
GridPane.setColumnSpan(vBox, columnSpan);
GridPane.setMargin(vBox, new Insets(50, -10, 5, -10));
vBox.getChildren().addAll(tableView);
root.getChildren().add(vBox);
}
private void setNewRandomSalt() {
byte[] randomBytes = UUID.randomUUID().toString().getBytes(Charsets.UTF_8);
byte[] hashOfRandomBytes = Hash.getSha256Ripemd160hash(randomBytes);
saltInputTextField.setText(Utilities.bytesAsHexString(hashOfRandomBytes));
saltInputTextField.resetValidation();
}
private void onUpdateBalances() {
onUpdateBalances(bsqWalletService.getAvailableBalance(),
bsqWalletService.getAvailableNonBsqBalance(),
bsqWalletService.getUnverifiedBalance(),
bsqWalletService.getLockedForVotingBalance(),
bsqWalletService.getLockupBondsBalance(),
bsqWalletService.getUnlockingBondsBalance());
}
private void updateButtonState() {
boolean isValid = bsqValidator.validate(amountInputTextField.getText()).isValid &&
timeInputTextFieldValidator.validate(timeInputTextField.getText()).isValid &&
hexStringValidator.validate(saltInputTextField.getText()).isValid;
lockupButton.setDisable(!isValid);
}
private void openTxInBlockExplorer(MyBondedReputationListItem item) {
if (item.getTxId() != null)
GUIUtil.openWebPage(preferences.getBsqBlockChainExplorer().txUrl + item.getTxId());
}
private void updateList() {
List<MyBondedReputationListItem> items = daoFacade.getMyBondedReputations().stream()
.map(myBondedReputation -> {
return new MyBondedReputationListItem(myBondedReputation, daoFacade, bondingViewUtils, bsqFormatter);
})
.sorted(Comparator.comparing(MyBondedReputationListItem::getLockupDate).reversed())
.collect(Collectors.toList());
observableList.setAll(items);
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 4);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void createColumns() {
TableColumn<MyBondedReputationListItem, MyBondedReputationListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", "BSQ"));
column.setMinWidth(120);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAmount());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.header.lockTime"));
column.setMinWidth(60);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLockTime());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.header.bondState"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(120);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
Label label;
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (label == null) {
label = item.getStateLabel();
setGraphic(label);
}
} else {
setGraphic(null);
if (label != null)
label = null;
}
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.header.lockupDate"));
column.setMinWidth(140);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLockupDate());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bonding.bonds.table.lockupTxId"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
//noinspection Duplicates
if (item != null && !empty) {
String transactionId = item.getTxId();
hyperlinkWithIcon = new HyperlinkWithIcon(transactionId, AwesomeIcon.EXTERNAL_LINK);
hyperlinkWithIcon.setOnAction(event -> openTxInBlockExplorer(item));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId)));
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bonding.unlock.salt"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getSalt());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bonding.unlock.hash"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getHash());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new TableColumn<>();
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(60);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyBondedReputationListItem, MyBondedReputationListItem> call(TableColumn<MyBondedReputationListItem,
MyBondedReputationListItem> column) {
return new TableCell<>() {
Button button;
@Override
public void updateItem(final MyBondedReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = item.getButton();
setGraphic(button);
}
} else {
setGraphic(null);
if (button != null)
button = null;
}
}
};
}
});
tableView.getColumns().add(column);
}
}

View File

@ -1,228 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.bonding.reputation;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.bonding.BondingViewUtils;
import bisq.desktop.main.dao.wallet.BsqBalanceUtil;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.locale.Res;
import bisq.core.util.BsqFormatter;
import bisq.core.util.validation.HexStringValidator;
import bisq.core.util.validation.IntegerValidator;
import bisq.common.crypto.Hash;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import com.google.common.base.Charsets;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.beans.value.ChangeListener;
import java.util.UUID;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class ReputationView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
private final BsqWalletService bsqWalletService;
private final BsqFormatter bsqFormatter;
private final BsqBalanceUtil bsqBalanceUtil;
private final BondingViewUtils bondingViewUtils;
private final HexStringValidator hexStringValidator;
private final BsqValidator bsqValidator;
private final IntegerValidator timeInputTextFieldValidator;
private int gridRow = 0;
private InputTextField amountInputTextField, timeInputTextField, saltInputTextField;
private Button lockupButton;
private ChangeListener<Boolean> amountFocusOutListener, timeFocusOutListener, saltFocusOutListener;
private ChangeListener<String> amountInputTextFieldListener, timeInputTextFieldListener, saltInputTextFieldListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ReputationView(BsqWalletService bsqWalletService,
BsqFormatter bsqFormatter,
BsqBalanceUtil bsqBalanceUtil,
BondingViewUtils bondingViewUtils,
HexStringValidator hexStringValidator,
BsqValidator bsqValidator) {
this.bsqWalletService = bsqWalletService;
this.bsqFormatter = bsqFormatter;
this.bsqBalanceUtil = bsqBalanceUtil;
this.bondingViewUtils = bondingViewUtils;
this.hexStringValidator = hexStringValidator;
this.bsqValidator = bsqValidator;
timeInputTextFieldValidator = new IntegerValidator();
timeInputTextFieldValidator.setMinValue(BondConsensus.getMinLockTime());
timeInputTextFieldValidator.setMaxValue(BondConsensus.getMaxLockTime());
}
@Override
public void initialize() {
gridRow = bsqBalanceUtil.addGroup(root, gridRow);
int columnSpan = 3;
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.bonding.lock.lockBSQ"),
Layout.GROUP_DISTANCE);
GridPane.setColumnSpan(titledGroupBg, columnSpan);
amountInputTextField = addInputTextField(root, gridRow, Res.get("dao.bonding.lock.amount"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE);
//amountInputTextField.setPromptText(Res.get("dao.bonding.lock.setAmount", bsqFormatter.formatCoinWithCode(Restrictions.getMinNonDustOutput())));
amountInputTextField.setValidator(bsqValidator);
GridPane.setColumnSpan(amountInputTextField, columnSpan);
timeInputTextField = FormBuilder.addInputTextField(root, ++gridRow, Res.get("dao.bonding.lock.time"));
saltInputTextField = FormBuilder.addInputTextField(root, ++gridRow, Res.get("dao.bonding.lock.salt"));
GridPane.setColumnSpan(saltInputTextField, columnSpan);
saltInputTextField.setValidator(hexStringValidator);
/* timeInputTextField.setPromptText(Res.get("dao.bonding.lock.setTime",
String.valueOf(BondConsensus.getMinLockTime()), String.valueOf(BondConsensus.getMaxLockTime())));*/
timeInputTextField.setValidator(timeInputTextFieldValidator);
GridPane.setColumnSpan(timeInputTextField, columnSpan);
lockupButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.bonding.lock.lockupButton"));
lockupButton.setOnAction((event) -> {
Coin lockupAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
int lockupTime = Integer.parseInt(timeInputTextField.getText());
byte[] salt = Utilities.decodeFromHex(saltInputTextField.getText());
bondingViewUtils.lockupBondForReputation(lockupAmount,
lockupTime,
salt,
txId -> {
amountInputTextField.setText("");
timeInputTextField.setText("");
});
});
amountFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
onUpdateBalances();
}
};
timeFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
onUpdateBalances();
}
};
saltFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
onUpdateBalances();
}
};
amountInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
timeInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
saltInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
}
@Override
protected void activate() {
bsqBalanceUtil.activate();
amountInputTextField.textProperty().addListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().addListener(amountFocusOutListener);
timeInputTextField.textProperty().addListener(timeInputTextFieldListener);
timeInputTextField.focusedProperty().addListener(timeFocusOutListener);
saltInputTextField.textProperty().addListener(saltInputTextFieldListener);
saltInputTextField.focusedProperty().addListener(saltFocusOutListener);
bsqWalletService.addBsqBalanceListener(this);
byte[] randomBytes = UUID.randomUUID().toString().getBytes(Charsets.UTF_8);
byte[] hashOfRandomBytes = Hash.getSha256Ripemd160hash(randomBytes);
saltInputTextField.setText(Utilities.bytesAsHexString(hashOfRandomBytes));
onUpdateBalances();
}
@Override
protected void deactivate() {
bsqBalanceUtil.deactivate();
amountInputTextField.textProperty().removeListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().removeListener(amountFocusOutListener);
timeInputTextField.textProperty().removeListener(timeInputTextFieldListener);
timeInputTextField.focusedProperty().removeListener(timeFocusOutListener);
saltInputTextField.textProperty().removeListener(saltInputTextFieldListener);
saltInputTextField.focusedProperty().removeListener(saltFocusOutListener);
bsqWalletService.removeBsqBalanceListener(this);
}
@Override
public void onUpdateBalances(Coin confirmedBalance,
Coin availableNonBsqBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
bsqValidator.setAvailableBalance(confirmedBalance);
boolean isValid = bsqValidator.validate(amountInputTextField.getText()).isValid;
lockupButton.setDisable(!isValid);
}
private void onUpdateBalances() {
onUpdateBalances(bsqWalletService.getAvailableBalance(),
bsqWalletService.getAvailableNonBsqBalance(),
bsqWalletService.getUnverifiedBalance(),
bsqWalletService.getLockedForVotingBalance(),
bsqWalletService.getLockupBondsBalance(),
bsqWalletService.getUnlockingBondsBalance());
}
private void updateButtonState() {
boolean isValid = bsqValidator.validate(amountInputTextField.getText()).isValid &&
timeInputTextFieldValidator.validate(timeInputTextField.getText()).isValid &&
hexStringValidator.validate(saltInputTextField.getText()).isValid;
lockupButton.setDisable(!isValid);
}
}

View File

@ -63,8 +63,8 @@ class BondedRolesListItem implements DaoStateListener {
this.bondingViewUtils = bondingViewUtils;
this.bsqFormatter = bsqFormatter;
role = bondedRole.getRole();
isMyRole = daoFacade.isMyRole(bondedRole.getRole());
role = bondedRole.getBondedAsset();
isMyRole = daoFacade.isMyRole(role);
daoFacade.addBsqStateListener(this);
@ -96,14 +96,14 @@ class BondedRolesListItem implements DaoStateListener {
}
public String getStartDate() {
return bondedRole.getStartDate() > 0 ?
bsqFormatter.formatDateTime(new Date(bondedRole.getStartDate())) :
return bondedRole.getLockupDate() > 0 ?
bsqFormatter.formatDateTime(new Date(bondedRole.getLockupDate())) :
"-";
}
public String getRevokeDate() {
return bondedRole.getRevokeDate() > 0 ?
bsqFormatter.formatDateTime(new Date(bondedRole.getRevokeDate())) :
return bondedRole.getUnlockDate() > 0 ?
bsqFormatter.formatDateTime(new Date(bondedRole.getUnlockDate())) :
"-";
}

View File

@ -154,7 +154,7 @@ public class BondedRolesView extends ActivatableView<GridPane, Void> implements
private void updateList() {
observableList.forEach(BondedRolesListItem::cleanup);
observableList.setAll(daoFacade.getBondedRoles().stream()
.map(bondedRole -> new BondedRolesListItem(bondedRole, daoFacade, bondingViewUtils, bsqFormatter))
.map(bond -> new BondedRolesListItem(bond, daoFacade, bondingViewUtils, bsqFormatter))
.collect(Collectors.toList()));
}
@ -175,13 +175,12 @@ public class BondedRolesView extends ActivatableView<GridPane, Void> implements
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<TableColumn<BondedRolesListItem, BondedRolesListItem>, TableCell<BondedRolesListItem,
BondedRolesListItem>>() {
new Callback<>() {
@Override
public TableCell<BondedRolesListItem, BondedRolesListItem> call(TableColumn<BondedRolesListItem,
BondedRolesListItem> column) {
return new TableCell<BondedRolesListItem, BondedRolesListItem>() {
return new TableCell<>() {
@Override
public void updateItem(final BondedRolesListItem item, boolean empty) {
super.updateItem(item, empty);
@ -199,13 +198,12 @@ public class BondedRolesView extends ActivatableView<GridPane, Void> implements
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(60);
column.setCellFactory(
new Callback<TableColumn<BondedRolesListItem, BondedRolesListItem>, TableCell<BondedRolesListItem,
BondedRolesListItem>>() {
new Callback<>() {
@Override
public TableCell<BondedRolesListItem, BondedRolesListItem> call(TableColumn<BondedRolesListItem,
BondedRolesListItem> column) {
return new TableCell<BondedRolesListItem, BondedRolesListItem>() {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
@ -232,13 +230,12 @@ public class BondedRolesView extends ActivatableView<GridPane, Void> implements
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<TableColumn<BondedRolesListItem, BondedRolesListItem>, TableCell<BondedRolesListItem,
BondedRolesListItem>>() {
new Callback<>() {
@Override
public TableCell<BondedRolesListItem, BondedRolesListItem> call(TableColumn<BondedRolesListItem,
BondedRolesListItem> column) {
return new TableCell<BondedRolesListItem, BondedRolesListItem>() {
return new TableCell<>() {
private Hyperlink hyperlink;
@Override
@ -265,7 +262,7 @@ public class BondedRolesView extends ActivatableView<GridPane, Void> implements
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.header.startDate"));
/* column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.header.startDate"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(120);
column.setCellFactory(
@ -395,18 +392,17 @@ public class BondedRolesView extends ActivatableView<GridPane, Void> implements
};
}
});
tableView.getColumns().add(column);
tableView.getColumns().add(column);*/
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.header.bondState"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(120);
column.setCellFactory(
new Callback<TableColumn<BondedRolesListItem, BondedRolesListItem>, TableCell<BondedRolesListItem,
BondedRolesListItem>>() {
new Callback<>() {
@Override
public TableCell<BondedRolesListItem, BondedRolesListItem> call(TableColumn<BondedRolesListItem,
BondedRolesListItem> column) {
return new TableCell<BondedRolesListItem, BondedRolesListItem>() {
return new TableCell<>() {
Label label;
@Override

View File

@ -27,6 +27,7 @@ import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.BaseCurrencyNetwork;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.governance.proposal.ProposalType;
import bisq.core.dao.governance.proposal.param.ChangeParamInputValidator;
@ -111,7 +112,7 @@ public class ProposalDisplay {
@Nullable
public ComboBox<Param> paramComboBox;
@Nullable
public ComboBox<Role> confiscateBondComboBox;
public ComboBox<Bond> confiscateBondComboBox;
@Nullable
public ComboBox<BondedRoleType> bondedRoleTypeComboBox;
@Nullable
@ -299,19 +300,20 @@ public class ProposalDisplay {
break;
case CONFISCATE_BOND:
confiscateBondComboBox = FormBuilder.<Role>addComboBox(gridPane, ++gridRow,
confiscateBondComboBox = FormBuilder.<Bond>addComboBox(gridPane, ++gridRow,
Res.get("dao.proposal.display.confiscateBondComboBox.label"));
comboBoxValueTextFieldIndex = gridRow;
checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null");
confiscateBondComboBox.setItems(FXCollections.observableArrayList(daoFacade.getActiveBondedRoles()));
confiscateBondComboBox.setItems(FXCollections.observableArrayList(daoFacade.getAllActiveBonds()));
confiscateBondComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Role role) {
return role != null ? role.getDisplayString() : "";
public String toString(Bond bond) {
return bond != null ? bond.getDisplayString() : "";
}
@Override
public Role fromString(String string) {
public Bond fromString(String string) {
return null;
}
});
@ -481,11 +483,13 @@ public class ProposalDisplay {
ConfiscateBondProposal confiscateBondProposal = (ConfiscateBondProposal) proposal;
checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null");
daoFacade.getBondedRoleFromHash(confiscateBondProposal.getHash())
//TODO
/* daoFacade.getBondedRoleFromHash(confiscateBondProposal.getHash())
.ifPresent(bondedRole -> {
confiscateBondComboBox.getSelectionModel().select(bondedRole);
comboBoxValueTextField.setText(confiscateBondComboBox.getConverter().toString(bondedRole));
});
});*/
} else if (proposal instanceof GenericProposal) {
// do nothing
} else if (proposal instanceof RemoveAssetProposal) {

View File

@ -304,10 +304,12 @@ public class MakeProposalView extends ActivatableView<GridPane, Void> implements
case CONFISCATE_BOND:
checkNotNull(proposalDisplay.confiscateBondComboBox,
"proposalDisplay.confiscateBondComboBox must not be null");
role = proposalDisplay.confiscateBondComboBox.getSelectionModel().getSelectedItem();
//TODO
/* role = proposalDisplay.confiscateBondComboBox.getSelectionModel().getSelectedItem();
return daoFacade.getConfiscateBondProposalWithTransaction(proposalDisplay.nameTextField.getText(),
proposalDisplay.linkInputTextField.getText(),
role.getHash());
role.getHash());*/
case GENERIC:
return daoFacade.getGenericProposalWithTransaction(proposalDisplay.nameTextField.getText(),
proposalDisplay.linkInputTextField.getText());