mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
- Change entrySet to CopyOnWriteArraySet to avoid potential ConcurrentModificationExceptions
- Make entrySet final - Avoind unneeded wrapping - Remove visibility of entrySet - Add getAddressEntriesAsListImmutable method. This is the only access for the hashSet so we ensure it cannot be changed from outside. - Remove stream() method - Remove unused return types - Improve some stream structures - Renaming, improve comments
This commit is contained in:
parent
e197b4ce25
commit
fa987d1461
3 changed files with 94 additions and 95 deletions
|
@ -29,28 +29,26 @@ import org.bitcoinj.wallet.Wallet;
|
|||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The List supporting our persistence solution.
|
||||
* The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the
|
||||
* associated protobuf message.
|
||||
*/
|
||||
@ToString
|
||||
@Slf4j
|
||||
public final class AddressEntryList implements UserThreadMappedPersistableEnvelope, PersistedDataHost {
|
||||
transient private Storage<AddressEntryList> storage;
|
||||
transient private Wallet wallet;
|
||||
@Getter
|
||||
private Set<AddressEntry> entrySet;
|
||||
private final Set<AddressEntry> entrySet = new CopyOnWriteArraySet<>();
|
||||
|
||||
@Inject
|
||||
public AddressEntryList(Storage<AddressEntryList> storage) {
|
||||
|
@ -60,8 +58,10 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
|
|||
@Override
|
||||
public void readPersisted() {
|
||||
AddressEntryList persisted = storage.initAndGetPersisted(this, 50);
|
||||
if (persisted != null)
|
||||
entrySet = new HashSet<>(persisted.getEntrySet());
|
||||
if (persisted != null) {
|
||||
entrySet.clear();
|
||||
entrySet.addAll(persisted.entrySet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -70,21 +70,21 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private AddressEntryList(Set<AddressEntry> entrySet) {
|
||||
this.entrySet = entrySet;
|
||||
this.entrySet.addAll(entrySet);
|
||||
}
|
||||
|
||||
public static AddressEntryList fromProto(protobuf.AddressEntryList proto) {
|
||||
return new AddressEntryList(new HashSet<>(proto.getAddressEntryList().stream().map(AddressEntry::fromProto).collect(Collectors.toList())));
|
||||
Set<AddressEntry> entrySet = proto.getAddressEntryList().stream()
|
||||
.map(AddressEntry::fromProto)
|
||||
.collect(Collectors.toSet());
|
||||
return new AddressEntryList(entrySet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
// We clone list as we got ConcurrentModificationExceptions
|
||||
List<AddressEntry> clone = new ArrayList<>(entrySet);
|
||||
List<protobuf.AddressEntry> addressEntries = clone.stream()
|
||||
Set<protobuf.AddressEntry> addressEntries = entrySet.stream()
|
||||
.map(AddressEntry::toProtoMessage)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
.collect(Collectors.toSet());
|
||||
return protobuf.PersistableEnvelope.newBuilder()
|
||||
.setAddressEntryList(protobuf.AddressEntryList.newBuilder()
|
||||
.addAllAddressEntry(addressEntries))
|
||||
|
@ -99,7 +99,7 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
|
|||
public void onWalletReady(Wallet wallet) {
|
||||
this.wallet = wallet;
|
||||
|
||||
if (entrySet != null) {
|
||||
if (!entrySet.isEmpty()) {
|
||||
entrySet.forEach(addressEntry -> {
|
||||
DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash());
|
||||
if (keyFromPubHash != null) {
|
||||
|
@ -109,8 +109,7 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
|
|||
}
|
||||
});
|
||||
} else {
|
||||
entrySet = new HashSet<>();
|
||||
add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR));
|
||||
entrySet.add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR));
|
||||
|
||||
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
|
||||
// IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to
|
||||
|
@ -118,73 +117,51 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
|
|||
if (wallet.getBalance().isPositive()) {
|
||||
wallet.getIssuedReceiveAddresses().forEach(address -> {
|
||||
log.info("Create AddressEntry for IssuedReceiveAddress. address={}", address.toString());
|
||||
add(new AddressEntry((DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()), AddressEntry.Context.AVAILABLE));
|
||||
entrySet.add(new AddressEntry((DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()), AddressEntry.Context.AVAILABLE));
|
||||
});
|
||||
}
|
||||
persist();
|
||||
|
||||
}
|
||||
persist();
|
||||
|
||||
// We add those listeners to get notified about potential new transactions and
|
||||
// add an address entry list in case it does not exist yet. This is mainly needed for restore from seed words
|
||||
// but can help as well in case the addressEntry list would miss an address where the wallet was received
|
||||
// funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the
|
||||
// wallet details window).
|
||||
wallet.addCoinsReceivedEventListener((w, tx, prevBalance, newBalance) -> {
|
||||
updateList(tx);
|
||||
wallet.addCoinsReceivedEventListener((wallet1, tx, prevBalance, newBalance) -> {
|
||||
updateEntrySet(tx);
|
||||
});
|
||||
wallet.addCoinsSentEventListener((w, tx, prevBalance, newBalance) -> {
|
||||
updateList(tx);
|
||||
wallet.addCoinsSentEventListener((wallet1, tx, prevBalance, newBalance) -> {
|
||||
updateEntrySet(tx);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateList(Transaction tx) {
|
||||
tx.getOutputs().stream()
|
||||
.filter(output -> output.isMine(wallet))
|
||||
.map(output -> output.getAddressFromP2PKHScript(wallet.getNetworkParameters()))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(address -> !listContainsEntryWithAddress(address.toBase58()))
|
||||
.map(address -> (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()))
|
||||
.filter(Objects::nonNull)
|
||||
.map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE))
|
||||
.forEach(this::add);
|
||||
public ImmutableList<AddressEntry> getAddressEntriesAsListImmutable() {
|
||||
return ImmutableList.copyOf(entrySet);
|
||||
}
|
||||
|
||||
private boolean listContainsEntryWithAddress(String addressString) {
|
||||
return entrySet.stream().anyMatch(addressEntry -> Objects.equals(addressEntry.getAddressString(), addressString));
|
||||
}
|
||||
|
||||
private boolean add(AddressEntry addressEntry) {
|
||||
return entrySet.add(addressEntry);
|
||||
}
|
||||
|
||||
private boolean remove(AddressEntry addressEntry) {
|
||||
return entrySet.remove(addressEntry);
|
||||
}
|
||||
|
||||
public AddressEntry addAddressEntry(AddressEntry addressEntry) {
|
||||
boolean changed = add(addressEntry);
|
||||
if (changed)
|
||||
public void addAddressEntry(AddressEntry addressEntry) {
|
||||
boolean replacedEntryOnAdd = entrySet.add(addressEntry);
|
||||
if (replacedEntryOnAdd)
|
||||
persist();
|
||||
return addressEntry;
|
||||
}
|
||||
|
||||
public void swapTradeToSavings(String offerId) {
|
||||
entrySet.stream().filter(addressEntry -> offerId.equals(addressEntry.getOfferId()))
|
||||
.findAny().ifPresent(this::swapToAvailable);
|
||||
}
|
||||
|
||||
public void swapToAvailable(AddressEntry addressEntry) {
|
||||
boolean changed1 = remove(addressEntry);
|
||||
boolean changed2 = add(new AddressEntry(addressEntry.getKeyPair(), AddressEntry.Context.AVAILABLE));
|
||||
if (changed1 || changed2)
|
||||
boolean hadRemovedEntry = entrySet.remove(addressEntry);
|
||||
boolean replacedEntryOnAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(), AddressEntry.Context.AVAILABLE));
|
||||
if (hadRemovedEntry || replacedEntryOnAdd) {
|
||||
persist();
|
||||
}
|
||||
}
|
||||
|
||||
public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressEntry, AddressEntry.Context context, String offerId) {
|
||||
boolean changed1 = remove(addressEntry);
|
||||
public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressEntry,
|
||||
AddressEntry.Context context,
|
||||
String offerId) {
|
||||
boolean hadRemovedEntry = entrySet.remove(addressEntry);
|
||||
final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId);
|
||||
boolean changed2 = add(newAddressEntry);
|
||||
if (changed1 || changed2)
|
||||
boolean replacedEntryOnAdd = entrySet.add(newAddressEntry);
|
||||
if (hadRemovedEntry || replacedEntryOnAdd)
|
||||
persist();
|
||||
|
||||
return newAddressEntry;
|
||||
|
@ -194,7 +171,25 @@ public final class AddressEntryList implements UserThreadMappedPersistableEnvelo
|
|||
storage.queueUpForSave(50);
|
||||
}
|
||||
|
||||
public Stream<AddressEntry> stream() {
|
||||
return entrySet.stream();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void updateEntrySet(Transaction tx) {
|
||||
tx.getOutputs().stream()
|
||||
.filter(output -> output.isMine(wallet))
|
||||
.map(output -> output.getAddressFromP2PKHScript(wallet.getNetworkParameters()))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(address -> !isAddressInEntries(address.toBase58()))
|
||||
.map(address -> (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()))
|
||||
.filter(Objects::nonNull)
|
||||
.map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE))
|
||||
.forEach(this::addAddressEntry);
|
||||
}
|
||||
|
||||
private boolean isAddressInEntries(String addressString) {
|
||||
return entrySet.stream()
|
||||
.anyMatch(addressEntry -> addressString.equals(addressEntry.getAddressString()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ import javax.inject.Inject;
|
|||
import javax.inject.Named;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Service;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -498,7 +497,7 @@ public class WalletsSetup {
|
|||
}
|
||||
|
||||
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
|
||||
return ImmutableList.copyOf(addressEntryList.getList()).stream()
|
||||
return addressEntryList.getAddressEntriesAsListImmutable().stream()
|
||||
.filter(addressEntry -> addressEntry.getContext() == context)
|
||||
.map(AddressEntry::getAddress)
|
||||
.collect(Collectors.toSet());
|
||||
|
|
|
@ -47,7 +47,6 @@ import org.bitcoinj.wallet.Wallet;
|
|||
import javax.inject.Inject;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
|
||||
|
@ -108,8 +107,8 @@ public class BtcWalletService extends WalletService {
|
|||
void decryptWallet(@NotNull KeyParameter key) {
|
||||
super.decryptWallet(key);
|
||||
|
||||
addressEntryList.stream().forEach(e -> {
|
||||
final DeterministicKey keyPair = e.getKeyPair();
|
||||
addressEntryList.getAddressEntriesAsListImmutable().stream().forEach(e -> {
|
||||
DeterministicKey keyPair = e.getKeyPair();
|
||||
if (keyPair.isEncrypted())
|
||||
e.setDeterministicKey(keyPair.decrypt(key));
|
||||
});
|
||||
|
@ -119,8 +118,8 @@ public class BtcWalletService extends WalletService {
|
|||
@Override
|
||||
void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) {
|
||||
super.encryptWallet(keyCrypterScrypt, key);
|
||||
addressEntryList.stream().forEach(e -> {
|
||||
final DeterministicKey keyPair = e.getKeyPair();
|
||||
addressEntryList.getAddressEntriesAsListImmutable().stream().forEach(e -> {
|
||||
DeterministicKey keyPair = e.getKeyPair();
|
||||
if (keyPair.isEncrypted())
|
||||
e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key));
|
||||
});
|
||||
|
@ -157,12 +156,18 @@ public class BtcWalletService extends WalletService {
|
|||
// Proposal txs
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData)
|
||||
public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount,
|
||||
Address issuanceAddress,
|
||||
Transaction feeTx,
|
||||
byte[] opReturnData)
|
||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
|
||||
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
|
||||
}
|
||||
|
||||
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData)
|
||||
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount,
|
||||
Address issuanceAddress,
|
||||
Transaction feeTx,
|
||||
byte[] opReturnData)
|
||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
|
||||
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
|
||||
}
|
||||
|
@ -292,7 +297,8 @@ public class BtcWalletService extends WalletService {
|
|||
return completePreparedBsqTxWithBtcFee(preparedTx, opReturnData);
|
||||
}
|
||||
|
||||
private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx, byte[] opReturnData) throws InsufficientMoneyException, TransactionVerificationException, WalletException {
|
||||
private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx,
|
||||
byte[] opReturnData) throws InsufficientMoneyException, TransactionVerificationException, WalletException {
|
||||
// Remember index for first BTC input
|
||||
int indexOfBtcFirstInput = preparedTx.getInputs().size();
|
||||
|
||||
|
@ -306,7 +312,8 @@ public class BtcWalletService extends WalletService {
|
|||
return tx;
|
||||
}
|
||||
|
||||
private Transaction addInputsForMinerFee(Transaction preparedTx, byte[] opReturnData) throws InsufficientMoneyException {
|
||||
private Transaction addInputsForMinerFee(Transaction preparedTx,
|
||||
byte[] opReturnData) throws InsufficientMoneyException {
|
||||
// safety check counter to avoid endless loops
|
||||
int counter = 0;
|
||||
// estimated size of input sig
|
||||
|
@ -421,7 +428,9 @@ public class BtcWalletService extends WalletService {
|
|||
return completePreparedBsqTx(preparedBsqTx, isSendTx, null);
|
||||
}
|
||||
|
||||
public Transaction completePreparedBsqTx(Transaction preparedBsqTx, boolean useCustomTxFee, @Nullable byte[] opReturnData) throws
|
||||
public Transaction completePreparedBsqTx(Transaction preparedBsqTx,
|
||||
boolean useCustomTxFee,
|
||||
@Nullable byte[] opReturnData) throws
|
||||
TransactionVerificationException, WalletException, InsufficientMoneyException {
|
||||
|
||||
// preparedBsqTx has following structure:
|
||||
|
@ -545,7 +554,8 @@ public class BtcWalletService extends WalletService {
|
|||
// AddressEntry
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Optional<AddressEntry> getAddressEntry(String offerId, @SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
|
||||
public Optional<AddressEntry> getAddressEntry(String offerId,
|
||||
@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
.filter(e -> context == e.getContext())
|
||||
|
@ -592,17 +602,14 @@ public class BtcWalletService extends WalletService {
|
|||
return getOrCreateAddressEntry(context, addressEntry);
|
||||
}
|
||||
|
||||
public AddressEntry getNewAddressEntry(String offerId, AddressEntry.Context context) {
|
||||
public void getNewAddressEntry(String offerId, AddressEntry.Context context) {
|
||||
AddressEntry entry = new AddressEntry(wallet.freshReceiveKey(), context, offerId);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public AddressEntry recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
|
||||
var available = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
|
||||
if (!available.isPresent())
|
||||
return null;
|
||||
return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId);
|
||||
public void recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
|
||||
findAddressEntry(address, AddressEntry.Context.AVAILABLE).ifPresent(addressEntry ->
|
||||
addressEntryList.swapAvailableToAddressEntryWithOfferId(addressEntry, context, offerId));
|
||||
}
|
||||
|
||||
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
|
||||
|
@ -655,19 +662,17 @@ public class BtcWalletService extends WalletService {
|
|||
}
|
||||
|
||||
public List<AddressEntry> getAddressEntryListAsImmutableList() {
|
||||
return ImmutableList.copyOf(addressEntryList.getList());
|
||||
return addressEntryList.getAddressEntriesAsListImmutable();
|
||||
}
|
||||
|
||||
public void swapTradeEntryToAvailableEntry(String offerId, AddressEntry.Context context) {
|
||||
Optional<AddressEntry> addressEntryOptional = getAddressEntryListAsImmutableList().stream()
|
||||
getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
addressEntryOptional.ifPresent(e -> {
|
||||
.forEach(e -> {
|
||||
log.info("swap addressEntry with address {} and offerId {} from context {} to available",
|
||||
e.getAddressString(), e.getOfferId(), context);
|
||||
addressEntryList.swapToAvailable(e);
|
||||
saveAddressEntryList();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue