selectRandomCoins.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. /**
  2. * """
  3. Grab wallet coins with a certain probability, while also paying attention
  4. to obvious linkages and possible linkages.
  5. Returns list of list of coins (bucketed by obvious linkage).
  6. """
  7. */
  8. export default (wallet, fraction, eligible) => {
  9. # First, we want to bucket coins together when they have obvious linkage.
  10. # Coins that are linked together should be spent together.
  11. # Currently, just look at address.
  12. addr_coins = eligible
  13. random.shuffle(addr_coins)
  14. # While fusing we want to pay attention to semi-correlations among coins.
  15. # When we fuse semi-linked coins, it increases the linkage. So we try to
  16. # avoid doing that (but rarely, we just do it anyway :D).
  17. # Currently, we just look at all txids touched by the address.
  18. # (TODO this is a disruption vector: someone can spam multiple fusions'
  19. # output addrs with massive dust transactions (2900 outputs in 100 kB)
  20. # that make the plugin think that all those addresses are linked.)
  21. result_txids = set()
  22. result = []
  23. num_coins = 0
  24. for addr, acoins in addr_coins:
  25. if num_coins >= DEFAULT_MAX_COINS:
  26. break
  27. elif num_coins + len(acoins) > DEFAULT_MAX_COINS:
  28. continue
  29. # For each bucket, we give a separate chance of joining.
  30. if random.random() > fraction:
  31. continue
  32. # Semi-linkage check:
  33. # We consider all txids involving the address, historical and current.
  34. ctxids = {txid for txid, height in wallet.get_address_history(addr)}
  35. collisions = ctxids.intersection(result_txids)
  36. # Note each collision gives a separate chance of discarding this bucket.
  37. if random.random() > KEEP_LINKED_PROBABILITY**len(collisions):
  38. continue
  39. # OK, no problems: let's include this bucket.
  40. num_coins += len(acoins)
  41. result.append(acoins)
  42. result_txids.update(ctxids)
  43. if not result:
  44. # nothing was selected, just try grabbing first nonempty bucket
  45. try:
  46. res = next(coins for addr,coins in addr_coins if coins)
  47. result = [res]
  48. except StopIteration:
  49. # all eligible buckets were cleared.
  50. pass
  51. return result
  52. }