Detailed changes
@@ -220,6 +220,7 @@ end
repo = PendingTransactionRepo.new(
"pending_#{CONFIG[:electrum][:currency]}_transactions",
+ ignored_key: "ignored_#{CONFIG[:electrum][:currency]}_transactions",
customer_address_template: lambda { |customer_id|
"jmp_customer_#{CONFIG[:electrum][:currency]}_addresses-#{customer_id}"
}
@@ -237,7 +238,7 @@ done = repo.map { |pending, customer_id|
next unless pending.confirmations >= CONFIG[:required_confirmations]
if pending.outgoing?
- # This is a send, not a receive, do not record it
+ repo.mark_ignored(pending)
repo.remove_transaction(pending)
next
end
@@ -26,9 +26,10 @@ class PendingTransactionRepo
end
end
- def initialize(key, customer_address_template: nil)
+ def initialize(key, customer_address_template: nil, ignored_key: nil)
@key = key
@customer_address_template = customer_address_template
+ @ignored_key = ignored_key
# Default handler allows all exceptions to be rethrown
@error_handler = ->(*){}
@@ -83,6 +84,32 @@ class PendingTransactionRepo
end
end
+ class IgnoredTransactionFilter
+ def initialize(redis, key)
+ @redis = redis
+ @key = key
+ end
+
+ def filter_chunk(txids_and_customer_ids)
+ # There's an smismember command that was added to the rubygem in
+ # v4.5 but prod is currently v4.2.5 and I don't want to have to
+ # deal with that. So for now this sucks a little, using pipelining
+ # instead.
+ # Better than N full round-trip requests, but not as good as
+ # smismember probably
+ results = @redis.pipelined {
+ txids_and_customer_ids.each do |(txid, _customer_id)|
+ @redis.sismember(@key, txid)
+ end
+ }
+
+ # Now filter by the results correspondingly
+ # Because it's in order, we pull each true or false off results and
+ # if it's true then we don't need to process the item further
+ txids_and_customer_ids.reject { |_v| results.shift }
+ end
+ end
+
class WrongCustomerFilter
def initialize(redis, template)
@redis = redis
@@ -111,6 +138,7 @@ class PendingTransactionRepo
def filter(txids_and_customer_ids)
@filters ||= [
+ IgnoredTransactionFilter.new(redis, @ignored_key),
WrongCustomerFilter.new(redis, @customer_address_template),
ExistingTransactionFilter.new(database)
]
@@ -144,4 +172,16 @@ class PendingTransactionRepo
def remove_transaction(pending)
redis.hdel(@key, pending.txid)
end
+
+ # We use this to record transactions we've decided not to care about ever
+ # again. These get filtered out during the processing _before_ it gets to
+ # electrum or the user code, so effectively they just don't exist anymore.
+ #
+ # If electrum was more efficient this would be unnecessary because we'd
+ # just ask electrum about the thing and we could decide we don't care
+ # organically. But since it's a bit sensitive, this effectively serves as a
+ # cache of "things we'd end up ignoring anyway"
+ def mark_ignored(pending)
+ redis.sadd(@ignored_key, pending.txid)
+ end
end
@@ -180,6 +180,23 @@ class TestPendingTransactionRepo < Minitest::Test
assert_mock repo.electrum
end
+ def test_mark_ignored
+ repo = PendingTransactionRepo.new("key", ignored_key: "ig")
+ repo.setup_mocks
+
+ pending = PendingTransactionRepo::PendingTransaction.new(
+ FakeElectrumTransaction.new("tx", 6, 0.5),
+ "addr"
+ )
+
+ repo.redis.expect(:sadd, nil, ["ig", "tx/addr"])
+
+ repo.mark_ignored(pending)
+
+ assert_mock repo.redis
+ assert_mock repo.electrum
+ end
+
def test_chunking
repo = PendingTransactionRepo.new("key")
repo.setup_mocks
@@ -273,6 +290,39 @@ class TestPendingTransactionRepo < Minitest::Test
assert_mock db_mock
end
+ def test_ignored_transaction_filter
+ redis = Object.new
+
+ def redis.pipelined
+ @stuff = []
+ yield
+ @stuff
+ end
+
+ def redis.sismember(key, txid)
+ raise unless key == "key"
+
+ @stuff << ["two/a", "three/b"].include?(txid)
+ end
+
+ filter = PendingTransactionRepo::IgnoredTransactionFilter.new(
+ redis, "key"
+ )
+
+ remaining = filter.filter_chunk([
+ ["one/a", "1234"],
+ ["two/a", "1234"],
+ ["three/b", "4321"],
+ ["four/c", "2323"]
+ ])
+
+ assert_equal(
+ [["one/a", "1234"], ["four/c", "2323"]],
+ remaining,
+ "should only include unfiltered results"
+ )
+ end
+
def test_wrong_customer_filter
redis = Object.new