txcatcher 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/txcatcher.rb +1 -0
- data/lib/txcatcher/catcher.rb +17 -2
- data/lib/txcatcher/models/address.rb +16 -0
- data/lib/txcatcher/models/deposit.rb +6 -0
- data/lib/txcatcher/models/transaction.rb +98 -9
- data/lib/txcatcher/server.rb +27 -7
- data/spec/cleaner_spec.rb +5 -3
- data/spec/logger_spec.rb +1 -1
- data/spec/models/transaction_spec.rb +50 -2
- data/spec/spec_helper.rb +1 -0
- data/txcatcher.gemspec +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f29602463f10ef91c91d32eef97b9a587f9005b7e63c1d4d5fb13585299f3c82
|
|
4
|
+
data.tar.gz: 0b698f2d3528a4ba5395828079904fdf67574d0bdea8660c7583092cf1f903e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e1a21f9973c1cf4703228958522ffe1d4dd7e7a641aa3cdcbda0df8d825552833bfe7e3d926672d3de8565ab5b85d049fe61fcb70b5bf748e41124a9e1d74ec
|
|
7
|
+
data.tar.gz: 5b9613759bd60fc5a36c2b7a8d7647cc81400928224eaf099abaae66ea4f2b72489d267f1e01df22eee5d49efb7830a7b3609fc07a02b5c0d6c4c618d70b6bfd
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.6
|
data/lib/txcatcher.rb
CHANGED
data/lib/txcatcher/catcher.rb
CHANGED
|
@@ -84,8 +84,16 @@ module TxCatcher
|
|
|
84
84
|
LOGGER.report "received tx hex: #{txhex[0..50]}..."
|
|
85
85
|
@queue["rawtx"] << ( Proc.new {
|
|
86
86
|
tx = TxCatcher::Transaction.new(hex: txhex)
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
begin
|
|
88
|
+
LOGGER.report "tx #{tx.txid} caught (id: #{tx.id}), deposits (outputs):"
|
|
89
|
+
tx.save
|
|
90
|
+
rescue Sequel::ValidationFailed => e
|
|
91
|
+
if tx.errors[:txid].include?("is already taken")
|
|
92
|
+
LOGGER.report " it's already in DB, no need to save it!"
|
|
93
|
+
else
|
|
94
|
+
raise e
|
|
95
|
+
end
|
|
96
|
+
end
|
|
89
97
|
tx.deposits.each do |d|
|
|
90
98
|
LOGGER.report " id: #{d.id}, addr: #{d.address.address}, amount: #{CryptoUnit.new(Config["currency"], d.amount, from_unit: :primary).to_standart}"
|
|
91
99
|
end
|
|
@@ -98,8 +106,15 @@ module TxCatcher
|
|
|
98
106
|
height = TxCatcher.current_block_height = block_hash["height"].to_i
|
|
99
107
|
LOGGER.report "*** Block #{height} mined, transactions received:\n #{transactions.join(" \n")}"
|
|
100
108
|
@queue["hashblock"] << ( Proc.new {
|
|
109
|
+
existing_transactions = Transaction.where(txid: transactions).map(&:txid)
|
|
110
|
+
LOGGER.report "*** Block #{height} mined, transactions received:\n #{transactions.join(" \n")}"
|
|
101
111
|
Transaction.where(txid: transactions).update(block_height: height)
|
|
102
112
|
})
|
|
113
|
+
# Update RBF transactions and deposits if a transaction with lower fee (no associated deposit) got
|
|
114
|
+
# accidentally confirmed.
|
|
115
|
+
TxCatcher::Transaction.where(block_height: height).exclude(rbf_next_transaction_id: nil).each do |t|
|
|
116
|
+
t.force_deposit_association_on_rbf!
|
|
117
|
+
end
|
|
103
118
|
end
|
|
104
119
|
|
|
105
120
|
end # class Catcher
|
|
@@ -7,6 +7,22 @@ module TxCatcher
|
|
|
7
7
|
CryptoUnit.new(Config["currency"], self.received, from_unit: :primary).to_standart
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
def self.find_or_catch_and_create(a)
|
|
11
|
+
# Even if there are not transactions to this address yet, we still create it,
|
|
12
|
+
# because calling this method means someone is interested in this address and we need
|
|
13
|
+
# to track it and maybe force catching it not just through mempool and ZeroMQ (see catcher.rb),
|
|
14
|
+
# but also directly through querying RPC (slow, but at least we won't miss it).
|
|
15
|
+
Address.find_or_create(address: a)
|
|
16
|
+
|
|
17
|
+
if addr&.deposits.empty? && addr.created_at < (Time.now - 3600)
|
|
18
|
+
# The address is in the DB, which means someone has been checking it,
|
|
19
|
+
# but no deposits were associated with it and it's been more than 1 hour
|
|
20
|
+
# since someone first got interested. Let's query the RPC directly, see if there were any transactions to this
|
|
21
|
+
# address.
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
10
26
|
end
|
|
11
27
|
|
|
12
28
|
|
|
@@ -6,6 +6,12 @@ module TxCatcher
|
|
|
6
6
|
|
|
7
7
|
attr_accessor :address_string
|
|
8
8
|
|
|
9
|
+
plugin :serialization, :json, :rbf_transaction_ids
|
|
10
|
+
|
|
11
|
+
def before_save
|
|
12
|
+
self.rbf_transaction_ids
|
|
13
|
+
end
|
|
14
|
+
|
|
9
15
|
def before_save
|
|
10
16
|
if @address_string
|
|
11
17
|
self.address = Address.find_or_create(address: @address_string)
|
|
@@ -5,6 +5,23 @@ module TxCatcher
|
|
|
5
5
|
plugin :validation_helpers
|
|
6
6
|
one_to_many :deposits
|
|
7
7
|
|
|
8
|
+
def self.find_or_catch(txid)
|
|
9
|
+
if tx = self.where(txid: txid).first
|
|
10
|
+
tx
|
|
11
|
+
else
|
|
12
|
+
self.catch(txid)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.catch(txid)
|
|
17
|
+
if txhex = TxCatcher.rpc_node.getrawtransaction(txid)
|
|
18
|
+
LOGGER.report "received tx hex: #{txhex[0..50]}... (fetched via manual RPC request)"
|
|
19
|
+
tx = self.new(hex: txhex)
|
|
20
|
+
tx.save
|
|
21
|
+
tx
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
8
25
|
# Updates only those transactions that have changed
|
|
9
26
|
def self.update_all(transactions)
|
|
10
27
|
transactions_to_update = transactions.select { |t| !t.column_changes.empty? }
|
|
@@ -13,14 +30,15 @@ module TxCatcher
|
|
|
13
30
|
end
|
|
14
31
|
|
|
15
32
|
def before_validation
|
|
16
|
-
return if !self.new? || !self.deposits.empty?
|
|
17
|
-
parse_transaction
|
|
33
|
+
return if !self.new? || (!self.deposits.empty? && !self.rbf?)
|
|
18
34
|
assign_transaction_attrs
|
|
19
|
-
|
|
35
|
+
self.tx_hash["vout"].uniq { |out| out["n"] }.each do |out|
|
|
20
36
|
amount = CryptoUnit.new(Config["currency"], out["value"], from_unit: :standart).to_i if out["value"]
|
|
21
37
|
address = out["scriptPubKey"]["addresses"]&.first
|
|
22
38
|
# Do not create a new deposit unless it actually makes sense to create one
|
|
23
|
-
if
|
|
39
|
+
if rbf?
|
|
40
|
+
self.rbf_previous_transaction.deposits.each { |d| self.deposits << d }
|
|
41
|
+
elsif address && amount && amount > 0
|
|
24
42
|
self.deposits << Deposit.new(amount: amount, address_string: address)
|
|
25
43
|
end
|
|
26
44
|
end
|
|
@@ -32,13 +50,19 @@ module TxCatcher
|
|
|
32
50
|
|
|
33
51
|
def after_create
|
|
34
52
|
self.deposits.each do |d|
|
|
35
|
-
d.
|
|
53
|
+
d.transaction_id = self.id
|
|
54
|
+
if self.rbf?
|
|
55
|
+
d.rbf_transaction_ids ||= []
|
|
56
|
+
d.rbf_transaction_ids.push(self.rbf_previous_transaction.id)
|
|
57
|
+
d.rbf_transaction_ids = d.rbf_transaction_ids.uniq
|
|
58
|
+
end
|
|
36
59
|
d.save
|
|
60
|
+
self.rbf_previous_transaction&.update(rbf_next_transaction_id: self.id)
|
|
37
61
|
end
|
|
38
62
|
end
|
|
39
63
|
|
|
40
64
|
def tx_hash
|
|
41
|
-
@tx_hash
|
|
65
|
+
@tx_hash ||= parse_transaction
|
|
42
66
|
end
|
|
43
67
|
|
|
44
68
|
def confirmations
|
|
@@ -80,20 +104,85 @@ module TxCatcher
|
|
|
80
104
|
blocks_to_check
|
|
81
105
|
end
|
|
82
106
|
|
|
107
|
+
def rbf?
|
|
108
|
+
return true if self.rbf_previous_transaction_id
|
|
109
|
+
# 1. Find transactions that are like this one (inputs, outputs).
|
|
110
|
+
previous_unmarked_transactions = Transaction.where(inputs_outputs_hash: self.inputs_outputs_hash, block_height: nil, rbf_next_transaction_id: nil)
|
|
111
|
+
.exclude(id: self.id)
|
|
112
|
+
.order(Sequel.desc(:created_at)).eager(:deposits).to_a.select { |t| !t.deposits.empty? }
|
|
113
|
+
unless previous_unmarked_transactions.empty?
|
|
114
|
+
@rbf_previous_transaction = previous_unmarked_transactions.first
|
|
115
|
+
self.rbf_previous_transaction_id = @rbf_previous_transaction.id
|
|
116
|
+
true
|
|
117
|
+
else
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def rbf_previous_transaction
|
|
123
|
+
@rbf_previous_transaction ||= Transaction.where(id: self.rbf_previous_transaction_id).first
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def rbf_next_transaction
|
|
127
|
+
@rbf_next_transaction ||= Transaction.where(id: self.rbf_next_transaction_id).first
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def input_hexes
|
|
131
|
+
@input_hexes ||= self.tx_hash["vin"].map { |input| input["scriptSig"]["hex"] }.compact.sort
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def output_addresses
|
|
135
|
+
@output_addresses ||= self.tx_hash["vout"].map { |output| output["scriptPubKey"]["addresses"]&.join(",") }.compact.sort
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Sometimes, even though an RBF transaction with higher fee was broadcasted,
|
|
139
|
+
# miners accept one the lower-fee transactions instead. However, in txcatcher database, the
|
|
140
|
+
# deposits are already associated with the latest transaction. In this case,
|
|
141
|
+
# we need to find the deposits in the DB set their transaction_id field to current transaction id.
|
|
142
|
+
def force_deposit_association_on_rbf!
|
|
143
|
+
tx = self
|
|
144
|
+
while tx && tx.deposits.empty? do
|
|
145
|
+
tx = tx.rbf_next_transaction
|
|
146
|
+
end
|
|
147
|
+
tx.deposits.each do |d|
|
|
148
|
+
d.update(transaction_id: self.id)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def to_json
|
|
153
|
+
#self.tx_hash.to_json
|
|
154
|
+
self.tx_hash.merge(confirmations: self.confirmations, block_height: self.block_height).to_json
|
|
155
|
+
end
|
|
156
|
+
|
|
83
157
|
private
|
|
84
158
|
|
|
85
159
|
def parse_transaction
|
|
86
|
-
|
|
160
|
+
TxCatcher.rpc_node.decoderawtransaction(self.hex)
|
|
87
161
|
end
|
|
88
162
|
|
|
89
163
|
def assign_transaction_attrs
|
|
90
|
-
self.txid =
|
|
164
|
+
self.txid = self.tx_hash["txid"] unless self.txid
|
|
165
|
+
# In order to be able to identify RBF - those are normally transactions with
|
|
166
|
+
# identical inputs and outputs - we hash inputs and outputs that hash serves
|
|
167
|
+
# as an identifier that we store in our DB and thus can search all
|
|
168
|
+
# previous transactions which the current transaction might be an RBF transaction to.
|
|
169
|
+
#
|
|
170
|
+
# A few comments:
|
|
171
|
+
#
|
|
172
|
+
# 1. Although an RBF transaction may techinically have different outputs as per
|
|
173
|
+
# protocol specification, it is true in most cases that outputs will also be
|
|
174
|
+
# the same (that's how most wallets implement RBF). Thus,
|
|
175
|
+
# we're also incorporating outputs into the hashed value.
|
|
176
|
+
#
|
|
177
|
+
# 2. For inputs, we're using input hexes, because pruned bitcoin-core
|
|
178
|
+
# doesn't provide addresses.
|
|
179
|
+
self.inputs_outputs_hash ||= Digest::SHA256.hexdigest((self.input_hexes + self.output_addresses).join(""))
|
|
91
180
|
end
|
|
92
181
|
|
|
93
182
|
def validate
|
|
94
183
|
super
|
|
95
184
|
validates_unique :txid
|
|
96
|
-
errors.add(:base, "No outputs for this
|
|
185
|
+
errors.add(:base, "No outputs for this transaction") if !self.rbf? && self.deposits.empty?
|
|
97
186
|
end
|
|
98
187
|
|
|
99
188
|
end
|
data/lib/txcatcher/server.rb
CHANGED
|
@@ -23,6 +23,8 @@ module TxCatcher
|
|
|
23
23
|
utxo(path)
|
|
24
24
|
elsif path.start_with? "/addr/"
|
|
25
25
|
address(path)
|
|
26
|
+
elsif path.start_with? "/tx/"
|
|
27
|
+
tx(path)
|
|
26
28
|
elsif path.start_with? "/tx/send"
|
|
27
29
|
broadcast_tx(params["rawtx"])
|
|
28
30
|
elsif path.start_with? "/feerate"
|
|
@@ -39,8 +41,8 @@ module TxCatcher
|
|
|
39
41
|
path = path.sub(/\?.*/, '').split("/").delete_if { |i| i.empty? }
|
|
40
42
|
addr = path.last
|
|
41
43
|
|
|
42
|
-
address = Address.
|
|
43
|
-
|
|
44
|
+
address = Address.find_or_create(address: addr)
|
|
45
|
+
unless address.deposits.empty?
|
|
44
46
|
deposits = Deposit.where(address_id: address.id)
|
|
45
47
|
deposits_count = deposits.count
|
|
46
48
|
deposits = deposits.eager(:transaction).limit(params["limit"] || 100)
|
|
@@ -49,14 +51,15 @@ module TxCatcher
|
|
|
49
51
|
t = d.transaction
|
|
50
52
|
t.update(protected: true) unless t.protected
|
|
51
53
|
t.check_block_height!(dont_save: true)
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
+
result = {
|
|
54
55
|
txid: t.txid,
|
|
55
56
|
amount: d.amount_in_btc,
|
|
56
57
|
satoshis: d.amount,
|
|
57
58
|
confirmations: t.confirmations,
|
|
58
|
-
block_height: t.block_height
|
|
59
|
+
block_height: t.block_height,
|
|
59
60
|
}
|
|
61
|
+
result.merge!({ rbf: "yes", rbf_previous_txid: t.rbf_previous_transaction.txid }) if t.rbf?
|
|
62
|
+
result
|
|
60
63
|
end
|
|
61
64
|
return [200, {}, { address: address.address, received: address.received, deposits_count: deposits_count, deposits_shown: deposits.size, deposits: deposits }.to_json]
|
|
62
65
|
else
|
|
@@ -69,8 +72,8 @@ module TxCatcher
|
|
|
69
72
|
path.pop
|
|
70
73
|
addr = path.last
|
|
71
74
|
|
|
72
|
-
address = Address.
|
|
73
|
-
return [200, {}, "{}"] unless address
|
|
75
|
+
address = Address.find_or_create(address: addr)
|
|
76
|
+
return [200, {}, "{}"] unless address.deposits.empty?
|
|
74
77
|
deposits = Deposit.where(address_id: address.id).limit(params["limit"] || 100).eager(:transaction)
|
|
75
78
|
|
|
76
79
|
transactions = deposits.map { |d| d.transaction }
|
|
@@ -91,6 +94,10 @@ module TxCatcher
|
|
|
91
94
|
outs.map! do |out|
|
|
92
95
|
out["confirmations"] = t.confirmations || 0
|
|
93
96
|
out["txid"] = t.txid
|
|
97
|
+
if t.rbf?
|
|
98
|
+
out["rbf"] = "yes"
|
|
99
|
+
out["rbf_previous_txid"] = t.rbf_previous_transaction.txid
|
|
100
|
+
end
|
|
94
101
|
out
|
|
95
102
|
end
|
|
96
103
|
outs
|
|
@@ -99,6 +106,19 @@ module TxCatcher
|
|
|
99
106
|
return [200, {}, utxos.to_json]
|
|
100
107
|
end
|
|
101
108
|
|
|
109
|
+
def tx(path)
|
|
110
|
+
path = path.sub(/\?.*/, '').split("/").delete_if { |i| i.empty? }
|
|
111
|
+
txid = path.last
|
|
112
|
+
tx = Transaction.find_or_catch(txid)
|
|
113
|
+
tx = Transaction.where(txid: txid).first
|
|
114
|
+
|
|
115
|
+
if tx && !tx.deposits.empty?
|
|
116
|
+
return [200, {}, tx.to_json]
|
|
117
|
+
else
|
|
118
|
+
return [404, {}, "Transaction not found"]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
102
122
|
def broadcast_tx(txhex)
|
|
103
123
|
TxCatcher.rpc_node.sendrawtransaction(txhex)
|
|
104
124
|
tx = TxCatcher.rpc_node.decoderawtransaction(txhex)
|
data/spec/cleaner_spec.rb
CHANGED
|
@@ -9,7 +9,7 @@ require_relative '../lib/txcatcher/cleaner'
|
|
|
9
9
|
RSpec.describe TxCatcher::Cleaner do
|
|
10
10
|
|
|
11
11
|
before(:each) do
|
|
12
|
-
allow(TxCatcher.rpc_node).to receive(:decoderawtransaction).and_return({ "vout" => []})
|
|
12
|
+
# allow(TxCatcher.rpc_node).to receive(:decoderawtransaction).and_return({ "vout" => []})
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
it "doesn't clean anything if transaction count is below threshold" do
|
|
@@ -43,7 +43,7 @@ RSpec.describe TxCatcher::Cleaner do
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
it "protects checked transactions" do
|
|
46
|
-
protected_txs = create_transactions(3, { protected: true })
|
|
46
|
+
protected_txs = create_transactions(3, { protected: true, prefix: "protected" })
|
|
47
47
|
regular_txs = create_transactions(15)
|
|
48
48
|
clean_transactions
|
|
49
49
|
expect(TxCatcher::Transaction.count).to eq(12)
|
|
@@ -53,9 +53,11 @@ RSpec.describe TxCatcher::Cleaner do
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def create_transactions(n, attrs={})
|
|
56
|
+
prefix = attrs.delete(:prefix)
|
|
56
57
|
(1..n).to_a.map do |i|
|
|
57
58
|
d = TxCatcher::Deposit.new(address_string: "addr#{i}", amount: 0)
|
|
58
|
-
tx = TxCatcher::Transaction.new(attrs)
|
|
59
|
+
tx = TxCatcher::Transaction.new(attrs.merge(hex: File.read(File.dirname(__FILE__) + "/fixtures/transaction.txt").strip))
|
|
60
|
+
tx.txid = "#{prefix}_tx#{i}"
|
|
59
61
|
tx.deposits << d
|
|
60
62
|
tx.save
|
|
61
63
|
tx
|
data/spec/logger_spec.rb
CHANGED
|
@@ -49,7 +49,7 @@ RSpec.describe TxCatcher::LOGGER do
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
it "converts Exception into a text for logging" do
|
|
52
|
-
expect($stdout).to receive(:print).with("StandardError\n[no backtrace]\n")
|
|
52
|
+
expect($stdout).to receive(:print).with("StandardError - StandardError\n[no backtrace]\n")
|
|
53
53
|
expect($stdout).to receive(:print).with("\n\n")
|
|
54
54
|
TxCatcher::LOGGER.report StandardError.new, :error
|
|
55
55
|
end
|
|
@@ -3,8 +3,8 @@ require_relative '../spec_helper'
|
|
|
3
3
|
RSpec.describe TxCatcher::Transaction do
|
|
4
4
|
|
|
5
5
|
class TxCatcher::Transaction
|
|
6
|
-
def
|
|
7
|
-
|
|
6
|
+
def assign_tx_hash(h)
|
|
7
|
+
@tx_hash = h
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -50,4 +50,52 @@ RSpec.describe TxCatcher::Transaction do
|
|
|
50
50
|
).to eq([transaction2.id, transaction3.id])
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
it "handles an RBF transaction" do
|
|
54
|
+
deposit_ids = @transaction.deposits.map(&:id)
|
|
55
|
+
rbf_tx = TxCatcher::Transaction.new(hex: @hextx)
|
|
56
|
+
rbf_tx_hash = @transaction.tx_hash
|
|
57
|
+
rbf_tx_hash["txid"] = "rbftxid1"
|
|
58
|
+
rbf_tx_hash["locktime"] = "1"
|
|
59
|
+
rbf_tx.assign_tx_hash(rbf_tx_hash)
|
|
60
|
+
rbf_tx.save
|
|
61
|
+
expect(@transaction.reload.rbf_next_transaction_id).to eq(rbf_tx.id)
|
|
62
|
+
expect(rbf_tx.rbf_previous_transaction_id).to eq(@transaction.id)
|
|
63
|
+
expect(@transaction.deposits).to be_empty
|
|
64
|
+
expect(rbf_tx.reload.deposits.map(&:id)).to eq(deposit_ids)
|
|
65
|
+
rbf_tx.deposits.each do |d|
|
|
66
|
+
expect(d.rbf_transaction_ids).to eq([@transaction.id])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
rbf_tx2 = TxCatcher::Transaction.new(hex: @hextx)
|
|
70
|
+
rbf_tx_hash["txid"] = "rbftxid2"
|
|
71
|
+
rbf_tx_hash["locktime"] = "2"
|
|
72
|
+
rbf_tx2.assign_tx_hash(rbf_tx_hash)
|
|
73
|
+
rbf_tx2.save
|
|
74
|
+
expect(rbf_tx.reload.rbf_next_transaction_id).to eq(rbf_tx2.id)
|
|
75
|
+
expect(rbf_tx2.rbf_previous_transaction_id).to eq(rbf_tx.id)
|
|
76
|
+
expect(rbf_tx.deposits).to be_empty
|
|
77
|
+
expect(rbf_tx2.reload.deposits.map(&:id)).to eq(deposit_ids)
|
|
78
|
+
rbf_tx2.deposits.each do |d|
|
|
79
|
+
expect(d.rbf_transaction_ids).to eq([@transaction.id, rbf_tx.id])
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "forces deposit association with itself when confirmed, but another RBF transaction is associated with the deposit" do
|
|
84
|
+
deposit_ids = @transaction.deposits.map(&:id)
|
|
85
|
+
rbf_tx = TxCatcher::Transaction.new(hex: @hextx)
|
|
86
|
+
rbf_tx_hash = @transaction.tx_hash
|
|
87
|
+
rbf_tx_hash["txid"] = "rbftxid1"
|
|
88
|
+
rbf_tx_hash["locktime"] = "1"
|
|
89
|
+
rbf_tx.assign_tx_hash(rbf_tx_hash)
|
|
90
|
+
rbf_tx.save
|
|
91
|
+
|
|
92
|
+
@transaction.reload.force_deposit_association_on_rbf!
|
|
93
|
+
expect(rbf_tx.reload.deposits).to be_empty
|
|
94
|
+
expect(@transaction.reload.deposits.map(&:id)).to eq(deposit_ids)
|
|
95
|
+
@transaction.deposits.each do |d|
|
|
96
|
+
expect(d.rbf_transaction_ids).to eq([@transaction.id])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
|
|
53
101
|
end
|
data/spec/spec_helper.rb
CHANGED
data/txcatcher.gemspec
CHANGED