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> non_bsq_tx_output_map = 5;
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;
repeated ParamChange param_change_list = 9;
repeated EvaluatedProposal evaluated_proposal_list = 10;

View File

@ -615,14 +615,14 @@ public class BsqWalletService extends WalletService implements DaoStateListener
// Unlock bond tx
///////////////////////////////////////////////////////////////////////////////////////////
public Transaction getPreparedUnlockTx(TxOutput lockedTxOutput) throws AddressFormatException {
public Transaction getPreparedUnlockTx(TxOutput lockupTxOutput) throws AddressFormatException {
Transaction tx = new Transaction(params);
// 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).");
Transaction lockupTx = getTransaction(lockedTxOutput.getTxId());
Transaction lockupTx = getTransaction(lockupTxOutput.getTxId());
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[]{}
tx.addInput(new TransactionInput(params, tx, new byte[]{}, outPoint, amountToUnlock));
tx.addOutput(new TransactionOutput(params, tx, amountToUnlock, getUnusedAddress()));

View File

@ -110,8 +110,8 @@ public class DaoStateService implements DaoSetupService {
daoState.getUnspentTxOutputMap().clear();
daoState.getUnspentTxOutputMap().putAll(snapshot.getUnspentTxOutputMap());
daoState.getConfiscatedTxOutputMap().clear();
daoState.getConfiscatedTxOutputMap().putAll(snapshot.getConfiscatedTxOutputMap());
daoState.getConfiscatedLockupTxList().clear();
daoState.getConfiscatedLockupTxList().addAll(snapshot.getConfiscatedLockupTxList());
daoState.getIssuanceMap().clear();
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)
public long getTotalAmountOfLockupTxOutputs() {
return getLockupTxOutputs().stream()
.filter(txOutput -> !isConfiscatedLockupTxOutput(txOutput.getTxId()))
.mapToLong(TxOutput::getValue)
.sum();
}
@ -679,6 +680,11 @@ public class DaoStateService implements DaoSetupService {
// Unlock
public boolean isUnlockOutput(TxOutputKey key) {
Optional<TxOutput> opTxOutput = getUnspentTxOutput(key);
return opTxOutput.isPresent() && isUnlockOutput(opTxOutput.get());
}
public boolean isUnlockOutput(TxOutput txOutput) {
return txOutput.getTxOutputType() == TxOutputType.UNLOCK_OUTPUT;
}
@ -693,6 +699,7 @@ public class DaoStateService implements DaoSetupService {
public long getTotalAmountOfUnLockingTxOutputs() {
return getUnspentUnlockingTxOutputsStream()
.filter(txOutput -> !isConfiscatedUnlockTxOutput(txOutput.getTxId()))
.mapToLong(TxOutput::getValue)
.sum();
}
@ -713,6 +720,10 @@ public class DaoStateService implements DaoSetupService {
!isLockTimeOverForUnlockTxOutput(unlockTxOutput);
}
public Optional<Tx> getLockupTxFromUnlockTxId(String unlockTxId) {
return getTx(unlockTxId).flatMap(tx -> getTx(tx.getTxInputs().get(0).getConnectedTxOutputTxId()));
}
// Unlocked
public Optional<Integer> getUnlockBlockHeight(String txId) {
return getTx(txId).map(Tx::getUnlockBlockHeight);
@ -728,23 +739,10 @@ public class DaoStateService implements DaoSetupService {
// We don't care here about the unspent state
public Stream<TxOutput> getUnlockedTxOutputsStream() {
return getTxOutputsByTxOutputType(TxOutputType.UNLOCK_OUTPUT).stream()
.filter(txOutput -> !isConfiscatedUnlockTxOutput(txOutput.getTxId()))
.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() {
return getUnlockedTxOutputsStream()
.mapToLong(TxOutput::getValue)
@ -752,10 +750,11 @@ public class DaoStateService implements DaoSetupService {
}
public long getTotalAmountOfConfiscatedTxOutputs() {
return daoState.getConfiscatedTxOutputMap()
.values()
return daoState.getConfiscatedLockupTxList()
.stream()
.mapToLong(TxOutput::getValue)
.map(txId -> getTx(txId))
.filter(tx -> tx.isPresent())
.mapToLong(tx -> tx.get().getLockupOutput().getValue())
.sum();
}
@ -766,7 +765,7 @@ public class DaoStateService implements DaoSetupService {
TxOutput lockupTxOutput = optionalTxOutput.get();
if (isUnspent(lockupTxOutput.getKey())) {
log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey());
confiscateBond(lockupTxOutput);
doConfiscateBond(lockupTxId);
} else {
// We lookup for the unlock tx which need to be still in unlocking state
Optional<SpentInfo> optionalSpentInfo = getSpentInfo(lockupTxOutput);
@ -775,9 +774,7 @@ public class DaoStateService implements DaoSetupService {
if (isUnlockingAndUnspent(unlockTxId)) {
// We found the unlock tx is still not spend
log.warn("lockupTxOutput {} is still unspent. We confiscate it.", lockupTxOutput.getKey());
getUnspentUnlockingTxOutputsStream()
.filter(txOutput -> txOutput.getTxId().equals(unlockTxId))
.forEach(this::confiscateBond);
doConfiscateBond(lockupTxId);
} else {
// 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
@ -791,44 +788,33 @@ public class DaoStateService implements DaoSetupService {
}
}
private void confiscateBond(TxOutput txOutput) {
// Can be txOutput from lockup or unlock tx
log.warn("txOutput {} added to confiscatedTxOutputMap.", txOutput.getKey());
daoState.getConfiscatedTxOutputMap().put(txOutput.getKey(), txOutput);
private void doConfiscateBond(String lockupTxId) {
log.warn("TxId {} added to confiscatedLockupTxIdList.", lockupTxId);
daoState.getConfiscatedLockupTxList().add(lockupTxId);
}
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) {
return getLockupTxOutput(lockupTxId)
.map(lockupTxIdxOutput -> isConfiscated(lockupTxIdxOutput.getKey()))
.orElse(false);
return isConfiscated(lockupTxId);
}
public boolean isConfiscatedUnlockTxOutput(String unlockTxId) {
return getUnlockTxOutputs().stream()
.filter(unlockTxOutput -> unlockTxOutput.getTxId().equals(unlockTxId))
.map(unlockTxOutput -> isConfiscated(unlockTxOutput.getKey()))
.findAny()
.orElse(false);
return getLockupTxFromUnlockTxId(unlockTxId).
map(lockupTx -> isConfiscated(lockupTx.getId())).
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

View File

@ -40,6 +40,7 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
@ -75,7 +76,7 @@ public class DaoState implements PersistablePayload {
@Getter
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
private final Map<TxOutputKey, TxOutput> unspentTxOutputMap;
@Getter
@ -83,9 +84,9 @@ public class DaoState implements PersistablePayload {
@Getter
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
private final Map<TxOutputKey, TxOutput> confiscatedTxOutputMap;
private final List<String> confiscatedLockupTxList;
@Getter
private final Map<String, Issuance> issuanceMap; // key is txId
@Getter
@ -112,7 +113,7 @@ public class DaoState implements PersistablePayload {
new HashMap<>(),
new HashMap<>(),
new HashMap<>(),
new HashMap<>(),
new ArrayList<>(),
new HashMap<>(),
new ArrayList<>(),
new ArrayList<>(),
@ -131,7 +132,7 @@ public class DaoState implements PersistablePayload {
Map<TxOutputKey, TxOutput> unspentTxOutputMap,
Map<TxOutputKey, TxOutput> nonBsqTxOutputMap,
Map<TxOutputKey, SpentInfo> spentInfoMap,
Map<TxOutputKey, TxOutput> confiscatedTxOutputMap,
List<String> confiscatedLockupTxList,
Map<String, Issuance> issuanceMap,
List<ParamChange> paramChangeList,
List<EvaluatedProposal> evaluatedProposalList,
@ -144,7 +145,7 @@ public class DaoState implements PersistablePayload {
this.nonBsqTxOutputMap = nonBsqTxOutputMap;
this.spentInfoMap = spentInfoMap;
this.confiscatedTxOutputMap = confiscatedTxOutputMap;
this.confiscatedLockupTxList = confiscatedLockupTxList;
this.issuanceMap = issuanceMap;
this.paramChangeList = paramChangeList;
this.evaluatedProposalList = evaluatedProposalList;
@ -167,8 +168,7 @@ public class DaoState implements PersistablePayload {
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toProtoMessage())))
.putAllSpentInfoMap(spentInfoMap.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toString(), entry -> entry.getValue().toProtoMessage())))
.putAllUnspentTxOutputMap(confiscatedTxOutputMap.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toProtoMessage())))
.addAllConfiscatedLockupTxList(confiscatedLockupTxList)
.putAllIssuanceMap(issuanceMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toProtoMessage())))
.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())));
Map<TxOutputKey, SpentInfo> spentInfoMap = proto.getSpentInfoMapMap().entrySet().stream()
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> SpentInfo.fromProto(e.getValue())));
Map<TxOutputKey, TxOutput> confiscatedTxOutputMap = proto.getConfiscatedTxOutputMapMap().entrySet().stream()
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
List<String> confiscatedLockupTxList = proto.getConfiscatedLockupTxListList();
Map<String, Issuance> issuanceMap = proto.getIssuanceMapMap().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue())));
List<ParamChange> paramChangeList = proto.getParamChangeListList().stream()
@ -205,7 +204,7 @@ public class DaoState implements PersistablePayload {
unspentTxOutputMap,
nonBsqTxOutputMap,
spentInfoMap,
confiscatedTxOutputMap,
confiscatedLockupTxList,
issuanceMap,
paramChangeList,
evaluatedProposalList,