mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Implements bonded reputation
Still WIP with many areas...
This commit is contained in:
parent
15b8f02c67
commit
7bf5c819cd
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
77
core/src/main/java/bisq/core/dao/governance/bond/Bond.java
Normal file
77
core/src/main/java/bisq/core/dao/governance/bond/Bond.java
Normal 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}";
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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}";
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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=Тир гарантийного депозита:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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"
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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())) :
|
||||
"-";
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user