Use lockup txid to track confiscation

1. Every bond is defined by its lockup transaction. To make it easy to
track which bonds are confiscated it's easier to track them by only one
txid instead of using a map with a mix of lockup txoutputs and unlock
txoutputs.

To check if a txoutput has been confiscated it has to be checked against
the originiating lockup txid.

2. Minor fixes of naming lockedup -> lockup and comments.
This commit is contained in:
sqrrm 2018-11-17 22:56:20 +01:00
parent aa27d443a4
commit e815c4b105
No known key found for this signature in database
GPG Key ID: 45235F9EF87089EC
4 changed files with 51 additions and 66 deletions

View File

@ -1473,7 +1473,7 @@ message BsqState {
map<string, BaseTxOutput> unspent_tx_output_map = 4; map<string, BaseTxOutput> unspent_tx_output_map = 4;
map<string, BaseTxOutput> non_bsq_tx_output_map = 5; map<string, BaseTxOutput> non_bsq_tx_output_map = 5;
map<string, Issuance> issuance_map = 6; map<string, Issuance> issuance_map = 6;
map<string, BaseTxOutput> confiscated_tx_output_map = 7; repeated string confiscated_lockup_tx_list = 7;
map<string, SpentInfo> spent_info_map = 8; map<string, SpentInfo> spent_info_map = 8;
repeated ParamChange param_change_list = 9; repeated ParamChange param_change_list = 9;
repeated EvaluatedProposal evaluated_proposal_list = 10; repeated EvaluatedProposal evaluated_proposal_list = 10;

View File

@ -615,14 +615,14 @@ public class BsqWalletService extends WalletService implements DaoStateListener
// Unlock bond tx // Unlock bond tx
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public Transaction getPreparedUnlockTx(TxOutput lockedTxOutput) throws AddressFormatException { public Transaction getPreparedUnlockTx(TxOutput lockupTxOutput) throws AddressFormatException {
Transaction tx = new Transaction(params); Transaction tx = new Transaction(params);
// Unlocking means spending the full value of the locked txOutput to another txOutput with the same value // Unlocking means spending the full value of the locked txOutput to another txOutput with the same value
Coin amountToUnlock = Coin.valueOf(lockedTxOutput.getValue()); Coin amountToUnlock = Coin.valueOf(lockupTxOutput.getValue());
checkArgument(Restrictions.isAboveDust(amountToUnlock), "The amount is too low (dust limit)."); checkArgument(Restrictions.isAboveDust(amountToUnlock), "The amount is too low (dust limit).");
Transaction lockupTx = getTransaction(lockedTxOutput.getTxId()); Transaction lockupTx = getTransaction(lockupTxOutput.getTxId());
checkNotNull(lockupTx, "lockupTx must not be null"); checkNotNull(lockupTx, "lockupTx must not be null");
TransactionOutPoint outPoint = new TransactionOutPoint(params, lockedTxOutput.getIndex(), lockupTx); TransactionOutPoint outPoint = new TransactionOutPoint(params, lockupTxOutput.getIndex(), lockupTx);
// Input is not signed yet so we use new byte[]{} // Input is not signed yet so we use new byte[]{}
tx.addInput(new TransactionInput(params, tx, new byte[]{}, outPoint, amountToUnlock)); tx.addInput(new TransactionInput(params, tx, new byte[]{}, outPoint, amountToUnlock));
tx.addOutput(new TransactionOutput(params, tx, amountToUnlock, getUnusedAddress())); tx.addOutput(new TransactionOutput(params, tx, amountToUnlock, getUnusedAddress()));

View File

@ -110,8 +110,8 @@ public class DaoStateService implements DaoSetupService {
daoState.getUnspentTxOutputMap().clear(); daoState.getUnspentTxOutputMap().clear();
daoState.getUnspentTxOutputMap().putAll(snapshot.getUnspentTxOutputMap()); daoState.getUnspentTxOutputMap().putAll(snapshot.getUnspentTxOutputMap());
daoState.getConfiscatedTxOutputMap().clear(); daoState.getConfiscatedLockupTxList().clear();
daoState.getConfiscatedTxOutputMap().putAll(snapshot.getConfiscatedTxOutputMap()); daoState.getConfiscatedLockupTxList().addAll(snapshot.getConfiscatedLockupTxList());
daoState.getIssuanceMap().clear(); daoState.getIssuanceMap().clear();
daoState.getIssuanceMap().putAll(snapshot.getIssuanceMap()); daoState.getIssuanceMap().putAll(snapshot.getIssuanceMap());
@ -668,6 +668,7 @@ public class DaoStateService implements DaoSetupService {
// Returns amount of all LOCKUP txOutputs (they might have been unlocking or unlocked in the meantime) // Returns amount of all LOCKUP txOutputs (they might have been unlocking or unlocked in the meantime)
public long getTotalAmountOfLockupTxOutputs() { public long getTotalAmountOfLockupTxOutputs() {
return getLockupTxOutputs().stream() return getLockupTxOutputs().stream()
.filter(txOutput -> !isConfiscatedLockupTxOutput(txOutput.getTxId()))
.mapToLong(TxOutput::getValue) .mapToLong(TxOutput::getValue)
.sum(); .sum();
} }
@ -679,6 +680,11 @@ public class DaoStateService implements DaoSetupService {
// Unlock // Unlock
public boolean isUnlockOutput(TxOutputKey key) {
Optional<TxOutput> opTxOutput = getUnspentTxOutput(key);
return opTxOutput.isPresent() && isUnlockOutput(opTxOutput.get());
}
public boolean isUnlockOutput(TxOutput txOutput) { public boolean isUnlockOutput(TxOutput txOutput) {
return txOutput.getTxOutputType() == TxOutputType.UNLOCK_OUTPUT; return txOutput.getTxOutputType() == TxOutputType.UNLOCK_OUTPUT;
} }
@ -693,6 +699,7 @@ public class DaoStateService implements DaoSetupService {
public long getTotalAmountOfUnLockingTxOutputs() { public long getTotalAmountOfUnLockingTxOutputs() {
return getUnspentUnlockingTxOutputsStream() return getUnspentUnlockingTxOutputsStream()
.filter(txOutput -> !isConfiscatedUnlockTxOutput(txOutput.getTxId()))
.mapToLong(TxOutput::getValue) .mapToLong(TxOutput::getValue)
.sum(); .sum();
} }
@ -713,6 +720,10 @@ public class DaoStateService implements DaoSetupService {
!isLockTimeOverForUnlockTxOutput(unlockTxOutput); !isLockTimeOverForUnlockTxOutput(unlockTxOutput);
} }
public Optional<Tx> getLockupTxFromUnlockTxId(String unlockTxId) {
return getTx(unlockTxId).flatMap(tx -> getTx(tx.getTxInputs().get(0).getConnectedTxOutputTxId()));
}
// Unlocked // Unlocked
public Optional<Integer> getUnlockBlockHeight(String txId) { public Optional<Integer> getUnlockBlockHeight(String txId) {
return getTx(txId).map(Tx::getUnlockBlockHeight); return getTx(txId).map(Tx::getUnlockBlockHeight);
@ -728,23 +739,10 @@ public class DaoStateService implements DaoSetupService {
// We don't care here about the unspent state // We don't care here about the unspent state
public Stream<TxOutput> getUnlockedTxOutputsStream() { public Stream<TxOutput> getUnlockedTxOutputsStream() {
return getTxOutputsByTxOutputType(TxOutputType.UNLOCK_OUTPUT).stream() return getTxOutputsByTxOutputType(TxOutputType.UNLOCK_OUTPUT).stream()
.filter(txOutput -> !isConfiscatedUnlockTxOutput(txOutput.getTxId()))
.filter(this::isLockTimeOverForUnlockTxOutput); .filter(this::isLockTimeOverForUnlockTxOutput);
} }
// TODO SQ
/*public boolean isSpentByUnlockTx(TxOutput txOutput) {
log.error("txOutput " + txOutput.getTxId());
boolean present = getSpentInfo(txOutput)
.map(spentInfo -> {
log.error("spentInfo " + spentInfo);
return getTx(spentInfo.getTxId());
})
.filter(Optional::isPresent)
.isPresent();
log.error("isSpentByUnlockTx present={}", present);
return present;
}*/
public long getTotalAmountOfUnLockedTxOutputs() { public long getTotalAmountOfUnLockedTxOutputs() {
return getUnlockedTxOutputsStream() return getUnlockedTxOutputsStream()
.mapToLong(TxOutput::getValue) .mapToLong(TxOutput::getValue)
@ -752,10 +750,11 @@ public class DaoStateService implements DaoSetupService {
} }
public long getTotalAmountOfConfiscatedTxOutputs() { public long getTotalAmountOfConfiscatedTxOutputs() {
return daoState.getConfiscatedTxOutputMap() return daoState.getConfiscatedLockupTxList()
.values()
.stream() .stream()
.mapToLong(TxOutput::getValue) .map(txId -> getTx(txId))
.filter(tx -> tx.isPresent())
.mapToLong(tx -> tx.get().getLockupOutput().getValue())
.sum(); .sum();
} }
@ -766,7 +765,7 @@ public class DaoStateService implements DaoSetupService {
TxOutput lockupTxOutput = optionalTxOutput.get(); TxOutput lockupTxOutput = optionalTxOutput.get();
if (isUnspent(lockupTxOutput.getKey())) { if (isUnspent(lockupTxOutput.getKey())) {
log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey()); log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey());
confiscateBond(lockupTxOutput); doConfiscateBond(lockupTxId);
} else { } else {
// We lookup for the unlock tx which need to be still in unlocking state // We lookup for the unlock tx which need to be still in unlocking state
Optional<SpentInfo> optionalSpentInfo = getSpentInfo(lockupTxOutput); Optional<SpentInfo> optionalSpentInfo = getSpentInfo(lockupTxOutput);
@ -775,9 +774,7 @@ public class DaoStateService implements DaoSetupService {
if (isUnlockingAndUnspent(unlockTxId)) { if (isUnlockingAndUnspent(unlockTxId)) {
// We found the unlock tx is still not spend // We found the unlock tx is still not spend
log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey()); log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey());
getUnspentUnlockingTxOutputsStream() doConfiscateBond(lockupTxId);
.filter(txOutput -> txOutput.getTxId().equals(unlockTxId))
.forEach(this::confiscateBond);
} else { } else {
// We could be more radical here and confiscate the output if it is unspent but lock time is over, // We could be more radical here and confiscate the output if it is unspent but lock time is over,
// but its probably better to stick to the rules that confiscation can only happen before lock time // but its probably better to stick to the rules that confiscation can only happen before lock time
@ -791,44 +788,33 @@ public class DaoStateService implements DaoSetupService {
} }
} }
private void confiscateBond(TxOutput txOutput) { private void doConfiscateBond(String lockupTxId) {
// Can be txOutput from lockup or unlock tx log.warn("TxId {} added to confiscatedLockupTxIdList.", lockupTxId);
log.warn("txOutput {} added to confiscatedTxOutputMap.", txOutput.getKey()); daoState.getConfiscatedLockupTxList().add(lockupTxId);
daoState.getConfiscatedTxOutputMap().put(txOutput.getKey(), txOutput);
} }
public boolean isConfiscated(TxOutputKey txOutputKey) { public boolean isConfiscated(TxOutputKey txOutputKey) {
return daoState.getConfiscatedTxOutputMap().containsKey(txOutputKey); if (isLockupOutput(txOutputKey))
return isConfiscatedLockupTxOutput(txOutputKey.getTxId());
else if (isUnlockOutput(txOutputKey))
return isConfiscatedUnlockTxOutput(txOutputKey.getTxId());
return false;
}
public boolean isConfiscated(String lockupTxId) {
return daoState.getConfiscatedLockupTxList().contains(lockupTxId);
} }
public boolean isConfiscatedLockupTxOutput(String lockupTxId) { public boolean isConfiscatedLockupTxOutput(String lockupTxId) {
return getLockupTxOutput(lockupTxId) return isConfiscated(lockupTxId);
.map(lockupTxIdxOutput -> isConfiscated(lockupTxIdxOutput.getKey()))
.orElse(false);
} }
public boolean isConfiscatedUnlockTxOutput(String unlockTxId) { public boolean isConfiscatedUnlockTxOutput(String unlockTxId) {
return getUnlockTxOutputs().stream() return getLockupTxFromUnlockTxId(unlockTxId).
.filter(unlockTxOutput -> unlockTxOutput.getTxId().equals(unlockTxId)) map(lockupTx -> isConfiscated(lockupTx.getId())).
.map(unlockTxOutput -> isConfiscated(unlockTxOutput.getKey())) orElse(false);
.findAny()
.orElse(false);
} }
//TODO do we want to check for both txs?
/* public boolean isConfiscatedUnlockTxOutput(String lockupTxId) {
boolean present = getLockupTxOutput(lockupTxId)
.flatMap(lockupTxIdxOutput -> getSpentInfo(lockupTxIdxOutput)
.map(SpentInfo::getTxId)
.flatMap(this::getTx)
.map(unlockTx -> unlockTx.getTxOutputs().get(0)))
.filter(firstTxOutput -> isConfiscated(firstTxOutput.getKey()))
.isPresent();
log.error("lockupTxId {}, {}", lockupTxId, present);
//log.error("daoState.getConfiscatedTxOutputMap() {}", daoState.getConfiscatedTxOutputMap());
return present;
}*/
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Param // Param

View File

@ -40,6 +40,7 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Getter; import lombok.Getter;
@ -75,7 +76,7 @@ public class DaoState implements PersistablePayload {
@Getter @Getter
private final LinkedList<Cycle> cycles; private final LinkedList<Cycle> cycles;
// Those maps represent mutual data which can get changed at parsing a transaction // These maps represent mutual data which can get changed at parsing a transaction
@Getter @Getter
private final Map<TxOutputKey, TxOutput> unspentTxOutputMap; private final Map<TxOutputKey, TxOutput> unspentTxOutputMap;
@Getter @Getter
@ -83,9 +84,9 @@ public class DaoState implements PersistablePayload {
@Getter @Getter
private final Map<TxOutputKey, SpentInfo> spentInfoMap; private final Map<TxOutputKey, SpentInfo> spentInfoMap;
// Those maps are related to state change triggered by voting // These maps are related to state change triggered by voting
@Getter @Getter
private final Map<TxOutputKey, TxOutput> confiscatedTxOutputMap; private final List<String> confiscatedLockupTxList;
@Getter @Getter
private final Map<String, Issuance> issuanceMap; // key is txId private final Map<String, Issuance> issuanceMap; // key is txId
@Getter @Getter
@ -112,7 +113,7 @@ public class DaoState implements PersistablePayload {
new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashMap<>(),
new HashMap<>(), new ArrayList<>(),
new HashMap<>(), new HashMap<>(),
new ArrayList<>(), new ArrayList<>(),
new ArrayList<>(), new ArrayList<>(),
@ -131,7 +132,7 @@ public class DaoState implements PersistablePayload {
Map<TxOutputKey, TxOutput> unspentTxOutputMap, Map<TxOutputKey, TxOutput> unspentTxOutputMap,
Map<TxOutputKey, TxOutput> nonBsqTxOutputMap, Map<TxOutputKey, TxOutput> nonBsqTxOutputMap,
Map<TxOutputKey, SpentInfo> spentInfoMap, Map<TxOutputKey, SpentInfo> spentInfoMap,
Map<TxOutputKey, TxOutput> confiscatedTxOutputMap, List<String> confiscatedLockupTxList,
Map<String, Issuance> issuanceMap, Map<String, Issuance> issuanceMap,
List<ParamChange> paramChangeList, List<ParamChange> paramChangeList,
List<EvaluatedProposal> evaluatedProposalList, List<EvaluatedProposal> evaluatedProposalList,
@ -144,7 +145,7 @@ public class DaoState implements PersistablePayload {
this.nonBsqTxOutputMap = nonBsqTxOutputMap; this.nonBsqTxOutputMap = nonBsqTxOutputMap;
this.spentInfoMap = spentInfoMap; this.spentInfoMap = spentInfoMap;
this.confiscatedTxOutputMap = confiscatedTxOutputMap; this.confiscatedLockupTxList = confiscatedLockupTxList;
this.issuanceMap = issuanceMap; this.issuanceMap = issuanceMap;
this.paramChangeList = paramChangeList; this.paramChangeList = paramChangeList;
this.evaluatedProposalList = evaluatedProposalList; this.evaluatedProposalList = evaluatedProposalList;
@ -167,8 +168,7 @@ public class DaoState implements PersistablePayload {
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toProtoMessage()))) .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toProtoMessage())))
.putAllSpentInfoMap(spentInfoMap.entrySet().stream() .putAllSpentInfoMap(spentInfoMap.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toString(), entry -> entry.getValue().toProtoMessage()))) .collect(Collectors.toMap(e -> e.getKey().toString(), entry -> entry.getValue().toProtoMessage())))
.putAllUnspentTxOutputMap(confiscatedTxOutputMap.entrySet().stream() .addAllConfiscatedLockupTxList(confiscatedLockupTxList)
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toProtoMessage())))
.putAllIssuanceMap(issuanceMap.entrySet().stream() .putAllIssuanceMap(issuanceMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toProtoMessage()))) .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toProtoMessage())))
.addAllParamChangeList(paramChangeList.stream().map(ParamChange::toProtoMessage).collect(Collectors.toList())) .addAllParamChangeList(paramChangeList.stream().map(ParamChange::toProtoMessage).collect(Collectors.toList()))
@ -189,8 +189,7 @@ public class DaoState implements PersistablePayload {
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue()))); .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
Map<TxOutputKey, SpentInfo> spentInfoMap = proto.getSpentInfoMapMap().entrySet().stream() Map<TxOutputKey, SpentInfo> spentInfoMap = proto.getSpentInfoMapMap().entrySet().stream()
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> SpentInfo.fromProto(e.getValue()))); .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> SpentInfo.fromProto(e.getValue())));
Map<TxOutputKey, TxOutput> confiscatedTxOutputMap = proto.getConfiscatedTxOutputMapMap().entrySet().stream() List<String> confiscatedLockupTxList = proto.getConfiscatedLockupTxListList();
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
Map<String, Issuance> issuanceMap = proto.getIssuanceMapMap().entrySet().stream() Map<String, Issuance> issuanceMap = proto.getIssuanceMapMap().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue()))); .collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue())));
List<ParamChange> paramChangeList = proto.getParamChangeListList().stream() List<ParamChange> paramChangeList = proto.getParamChangeListList().stream()
@ -205,7 +204,7 @@ public class DaoState implements PersistablePayload {
unspentTxOutputMap, unspentTxOutputMap,
nonBsqTxOutputMap, nonBsqTxOutputMap,
spentInfoMap, spentInfoMap,
confiscatedTxOutputMap, confiscatedLockupTxList,
issuanceMap, issuanceMap,
paramChangeList, paramChangeList,
evaluatedProposalList, evaluatedProposalList,