txcatcher 0.2.6 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +27 -27
- data/VERSION +1 -1
- data/lib/txcatcher/bitcoin_rpc.rb +19 -0
- data/lib/txcatcher/catcher.rb +12 -16
- data/lib/txcatcher/initializer.rb +1 -0
- data/lib/txcatcher/logger.rb +19 -11
- data/lib/txcatcher/models/transaction.rb +80 -40
- data/lib/txcatcher/server.rb +6 -6
- data/spec/models/transaction_spec.rb +11 -7
- data/txcatcher.gemspec +1 -2
- metadata +1 -2
- data/db/schema.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2e24688c0b712c8752c126180e4ccdc7508bb51d83273c0ee95975ee683c45b
|
4
|
+
data.tar.gz: 0fc259dc40b77ff6f3ad51d31a389b6ad6c290e50e00fdd29b45dc9c0aff8706
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa710896fdaa6ff9a504e73168eef31eec3a69ba4b583817c7dce9132f8c1924e1579437a47162a8c1a5643dc366d54ef08cc9b41e102d6e0f738d291c9c3855
|
7
|
+
data.tar.gz: 0cd0b9f730fc45229c7a9547d19a4299b2a103549c7687e0b144f9e042e2ccdbb9d82960de72de154eb95c55f494c2e44ae202c1939d64f3522eef7f176e41b5
|
data/Gemfile.lock
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
addressable (2.
|
5
|
-
public_suffix (>= 2.0.2, <
|
4
|
+
addressable (2.7.0)
|
5
|
+
public_suffix (>= 2.0.2, < 5.0)
|
6
6
|
async-rack (0.5.1)
|
7
7
|
rack (~> 1.1)
|
8
8
|
aws-eventstream (1.0.3)
|
9
|
-
aws-partitions (1.
|
10
|
-
aws-sdk-core (3.
|
9
|
+
aws-partitions (1.269.0)
|
10
|
+
aws-sdk-core (3.89.1)
|
11
11
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
12
|
-
aws-partitions (~> 1.0)
|
12
|
+
aws-partitions (~> 1, >= 1.239.0)
|
13
13
|
aws-sigv4 (~> 1.1)
|
14
14
|
jmespath (~> 1.0)
|
15
|
-
aws-sdk-ses (1.
|
16
|
-
aws-sdk-core (~> 3, >= 3.
|
15
|
+
aws-sdk-ses (1.27.0)
|
16
|
+
aws-sdk-core (~> 3, >= 3.71.0)
|
17
17
|
aws-sigv4 (~> 1.1)
|
18
18
|
aws-sigv4 (1.1.0)
|
19
19
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
@@ -26,9 +26,9 @@ GEM
|
|
26
26
|
addressable (>= 2.1.1)
|
27
27
|
eventmachine (>= 0.12.9)
|
28
28
|
eventmachine (1.2.7)
|
29
|
-
faraday (0.
|
29
|
+
faraday (1.0.0)
|
30
30
|
multipart-post (>= 1.2, < 3)
|
31
|
-
ffi (1.
|
31
|
+
ffi (1.12.1)
|
32
32
|
ffi-rzmq (2.0.7)
|
33
33
|
ffi-rzmq-core (>= 1.0.7)
|
34
34
|
ffi-rzmq-core (1.0.7)
|
@@ -48,32 +48,32 @@ GEM
|
|
48
48
|
http_parser.rb (0.6.0)
|
49
49
|
jmespath (1.4.0)
|
50
50
|
log4r (1.1.10)
|
51
|
-
multi_json (1.
|
51
|
+
multi_json (1.14.1)
|
52
52
|
multipart-post (2.1.1)
|
53
|
-
public_suffix (
|
54
|
-
rack (1.6.
|
53
|
+
public_suffix (4.0.3)
|
54
|
+
rack (1.6.12)
|
55
55
|
rack-accept-media-types (0.9)
|
56
56
|
rack-contrib (1.8.0)
|
57
57
|
rack (~> 1.4)
|
58
58
|
rack-respond_to (0.9.8)
|
59
59
|
rack-accept-media-types (>= 0.6)
|
60
|
-
rspec (3.
|
61
|
-
rspec-core (~> 3.
|
62
|
-
rspec-expectations (~> 3.
|
63
|
-
rspec-mocks (~> 3.
|
64
|
-
rspec-core (3.
|
65
|
-
rspec-support (~> 3.
|
66
|
-
rspec-expectations (3.
|
60
|
+
rspec (3.9.0)
|
61
|
+
rspec-core (~> 3.9.0)
|
62
|
+
rspec-expectations (~> 3.9.0)
|
63
|
+
rspec-mocks (~> 3.9.0)
|
64
|
+
rspec-core (3.9.1)
|
65
|
+
rspec-support (~> 3.9.1)
|
66
|
+
rspec-expectations (3.9.0)
|
67
67
|
diff-lcs (>= 1.2.0, < 2.0)
|
68
|
-
rspec-support (~> 3.
|
69
|
-
rspec-mocks (3.
|
68
|
+
rspec-support (~> 3.9.0)
|
69
|
+
rspec-mocks (3.9.1)
|
70
70
|
diff-lcs (>= 1.2.0, < 2.0)
|
71
|
-
rspec-support (~> 3.
|
72
|
-
rspec-support (3.
|
73
|
-
sentry-raven (
|
74
|
-
faraday (>= 0.7.6
|
75
|
-
sequel (5.
|
76
|
-
sqlite3 (1.4.
|
71
|
+
rspec-support (~> 3.9.0)
|
72
|
+
rspec-support (3.9.2)
|
73
|
+
sentry-raven (1.1.0)
|
74
|
+
faraday (>= 0.7.6)
|
75
|
+
sequel (5.28.0)
|
76
|
+
sqlite3 (1.4.2)
|
77
77
|
|
78
78
|
PLATFORMS
|
79
79
|
ruby
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.8
|
@@ -7,10 +7,29 @@ require 'json'
|
|
7
7
|
|
8
8
|
class BitcoinRPC
|
9
9
|
|
10
|
+
class NoTxIndexErorr < StandardError;end
|
11
|
+
|
10
12
|
def initialize(service_url)
|
11
13
|
@uri = URI.parse(service_url)
|
12
14
|
end
|
13
15
|
|
16
|
+
def txindex_enabled?
|
17
|
+
return @tx_index_enabled unless @tx_index_enabled.nil?
|
18
|
+
begin
|
19
|
+
txid = self.get_block_transactions(self.getblockcount-1000)["tx"].first
|
20
|
+
self.getrawtransaction(txid, 1)
|
21
|
+
TxCatcher::LOGGER.report "Pruning is off, -txindex enabled, can perform RPC requests to check block_height for transactions, that's much more reliable!"
|
22
|
+
return @tx_index_enabled = true
|
23
|
+
rescue BitcoinRPC::JSONRPCError => e
|
24
|
+
if e.message.include?("pruned data") || e.message.include?("-txindex")
|
25
|
+
TxCatcher::LOGGER.report "WARNING: Pruning is ON, will NOT be able to use RPC requests to check block_height for transactions!", :warn
|
26
|
+
return @tx_index_enabled = false
|
27
|
+
else
|
28
|
+
raise e
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
14
33
|
def method_missing(name, *args)
|
15
34
|
post_body = { 'method' => name, 'params' => args, 'id' => 'jsonrpc' }.to_json
|
16
35
|
resp = JSON.parse( http_post_request(post_body) )
|
data/lib/txcatcher/catcher.rb
CHANGED
@@ -81,36 +81,32 @@ module TxCatcher
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def handle_rawtx(txhex)
|
84
|
-
LOGGER.report "received tx hex: #{txhex[0..50]}..."
|
85
84
|
@queue["rawtx"] << ( Proc.new {
|
86
85
|
tx = TxCatcher::Transaction.new(hex: txhex)
|
86
|
+
tx.assign_transaction_attrs
|
87
87
|
begin
|
88
|
-
LOGGER.report "tx #{tx.txid} caught (id: #{tx.id}), deposits (outputs):"
|
89
88
|
tx.save
|
90
89
|
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
|
90
|
+
if !tx.errors[:txid] || !tx.errors[:txid].include?("is already taken")
|
94
91
|
raise e
|
95
92
|
end
|
96
93
|
end
|
97
|
-
tx.deposits.each do |d|
|
98
|
-
LOGGER.report " id: #{d.id}, addr: #{d.address.address}, amount: #{CryptoUnit.new(Config["currency"], d.amount, from_unit: :primary).to_standart}"
|
99
|
-
end
|
100
94
|
})
|
101
95
|
end
|
102
96
|
|
103
97
|
def handle_hashblock(block_hex)
|
104
|
-
block_hash
|
105
|
-
|
106
|
-
height
|
107
|
-
LOGGER.report "*** Block #{height} mined, transactions received:\n #{
|
98
|
+
block_hash = TxCatcher.rpc_node.getblock(block_hex)
|
99
|
+
block_transactions_ids = block_hash["tx"]
|
100
|
+
height = TxCatcher.current_block_height = block_hash["height"].to_i
|
101
|
+
LOGGER.report "*** Block #{height} mined, transactions received:\n #{block_transactions_ids.join("\n\s\s")}"
|
108
102
|
@queue["hashblock"] << ( Proc.new {
|
109
|
-
existing_transactions
|
110
|
-
|
111
|
-
|
103
|
+
existing_transactions = Transaction.where(txid: block_transactions_ids)
|
104
|
+
existing_transactions_ids = existing_transactions.map(&:txid)
|
105
|
+
undetected_transactions_ids = block_transactions_ids - existing_transactions_ids
|
106
|
+
undetected_transactions_ids.each { |txid| Transaction.create_from_rpc(txid) }
|
107
|
+
Transaction.where(txid: existing_transactions_ids).update(block_height: height)
|
112
108
|
})
|
113
|
-
# Update RBF transactions and deposits if a transaction with lower fee (no associated deposit) got
|
109
|
+
# Update RBF transactions and deposits if a transaction a with lower fee (no associated deposit) got
|
114
110
|
# accidentally confirmed.
|
115
111
|
TxCatcher::Transaction.where(block_height: height).exclude(rbf_next_transaction_id: nil).each do |t|
|
116
112
|
t.force_deposit_association_on_rbf!
|
@@ -123,6 +123,7 @@ module TxCatcher
|
|
123
123
|
n = TxCatcher::Config.rpcnode
|
124
124
|
print "Checking #{n["name"]} RPC connection... "
|
125
125
|
TxCatcher.rpc_node = BitcoinRPC.new("http://#{n["user"]}:#{n["password"]}@#{n["host"]}:#{n["port"]}")
|
126
|
+
TxCatcher.rpc_node.txindex_enabled?
|
126
127
|
|
127
128
|
i = 0 # try to connect to RPC 100 times before exiting with error
|
128
129
|
until TxCatcher.current_block_height
|
data/lib/txcatcher/logger.rb
CHANGED
@@ -20,34 +20,42 @@ module TxCatcher
|
|
20
20
|
end
|
21
21
|
|
22
22
|
|
23
|
-
def report(message, log_level=:info, data: nil, timestamp: false)
|
23
|
+
def report(message, log_level=:info, data: nil, timestamp: false, newline: "\n")
|
24
24
|
@reporters.each do |out|
|
25
25
|
if LOG_LEVELS[log_level] >= LOG_LEVELS[Config["logger"]["#{out}_level"].to_sym]
|
26
|
-
send("report_to_#{out}", message, log_level, data: data, timestamp: timestamp)
|
26
|
+
send("report_to_#{out}", message, log_level, data: data, timestamp: timestamp, newline: newline)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
def report_to_stdout(message, log_level, data: nil, timestamp: timestamp)
|
34
|
-
$stdout.print prepare_message(message, timestamp: timestamp)
|
35
|
-
$stdout.print " additional data: #{data.to_s}
|
36
|
-
|
33
|
+
def report_to_stdout(message, log_level, data: nil, timestamp: timestamp, newline: "\n")
|
34
|
+
$stdout.print prepare_message(message, timestamp: timestamp)
|
35
|
+
$stdout.print "\n additional data: #{data.to_s}" if data
|
36
|
+
if LOG_LEVELS[log_level] >= LOG_LEVELS[:error]
|
37
|
+
$stdout.print(@error_log_delimiter)
|
38
|
+
elsif newline
|
39
|
+
$stdout.print newline
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
|
-
def report_to_logfile(message, log_level, data: nil, timestamp: true) # always gonna be forcing timestamp to be true here
|
43
|
+
def report_to_logfile(message, log_level, data: nil, timestamp: true, newline: true) # always gonna be forcing timestamp to be true here
|
40
44
|
fn = LOG_LEVELS[log_level] >= LOG_LEVELS[:error] ? @error_log_file_name : @log_file_name
|
41
45
|
fn = TxCatcher::Config.config_dir + "/#{fn}"
|
42
46
|
|
43
47
|
File.open(fn, "a") do |f|
|
44
|
-
f.print "#{prepare_message(message, timestamp: true)}
|
45
|
-
f.print " additional data: #{data.to_s}\n" if data
|
46
|
-
|
48
|
+
f.print "#{prepare_message(message, timestamp: true)}"
|
49
|
+
f.print "\n additional data: #{data.to_s}\n" if data
|
50
|
+
if LOG_LEVELS[log_level] >= LOG_LEVELS[:error]
|
51
|
+
f.print(@error_log_delimiter)
|
52
|
+
elsif newline
|
53
|
+
f.print newline
|
54
|
+
end
|
47
55
|
end
|
48
56
|
end
|
49
57
|
|
50
|
-
def report_to_sentry(e, log_level, data: nil, timestamp: timestamp)
|
58
|
+
def report_to_sentry(e, log_level, data: nil, timestamp: timestamp, newline: true)
|
51
59
|
return unless TxCatcher::Config["logger"]["sentry_dsn"]
|
52
60
|
data ||= {}
|
53
61
|
data.merge!(environment: Config["environment"], host: Config["host"], currency: Config["currency"])
|
@@ -5,23 +5,36 @@ module TxCatcher
|
|
5
5
|
plugin :validation_helpers
|
6
6
|
one_to_many :deposits
|
7
7
|
|
8
|
-
|
8
|
+
attr_accessor :manual_rpc_request
|
9
|
+
|
10
|
+
def self.find_or_create_from_rpc(txid)
|
9
11
|
if tx = self.where(txid: txid).first
|
12
|
+
tx.update_block_height!
|
10
13
|
tx
|
11
14
|
else
|
12
|
-
self.
|
15
|
+
self.create_from_rpc(txid)
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
16
|
-
def self.
|
17
|
-
|
18
|
-
|
19
|
-
tx = self.new(hex:
|
19
|
+
def self.create_from_rpc(txid)
|
20
|
+
raise BitcoinRPC::NoTxIndexErorr, "Cannot create transaction from RPC request, (txid: #{txid}) please use -txindex and don't use pruning" unless TxCatcher.rpc_node.txindex_enabled?
|
21
|
+
if tx_from_rpc = TxCatcher.rpc_node.getrawtransaction(txid, 1)
|
22
|
+
tx = self.new(hex: tx_from_rpc["hex"], txid: txid)
|
23
|
+
tx.manual_rpc_request = true
|
24
|
+
tx.update_block_height(confirmations: tx_from_rpc["confirmations"])
|
20
25
|
tx.save
|
21
26
|
tx
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
30
|
+
def log_the_catch!
|
31
|
+
manual_rpc_request_caption = (self.manual_rpc_request ? " (fetched via a manual RPC request) " : " ")
|
32
|
+
LOGGER.report "tx #{self.txid} caught#{manual_rpc_request_caption}and saved to DB (id: #{self.id}), deposits (outputs):"
|
33
|
+
self.deposits.each do |d|
|
34
|
+
LOGGER.report " id: #{d.id}, addr: #{d.address.address}, amount: #{CryptoUnit.new(Config["currency"], d.amount, from_unit: :primary).to_standart}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
25
38
|
# Updates only those transactions that have changed
|
26
39
|
def self.update_all(transactions)
|
27
40
|
transactions_to_update = transactions.select { |t| !t.column_changes.empty? }
|
@@ -57,13 +70,15 @@ module TxCatcher
|
|
57
70
|
d.rbf_transaction_ids = d.rbf_transaction_ids.uniq
|
58
71
|
end
|
59
72
|
d.save
|
60
|
-
self.rbf_previous_transaction&.update(rbf_next_transaction_id: self.id)
|
61
73
|
end
|
74
|
+
self.rbf_previous_transaction&.update(rbf_next_transaction_id: self.id)
|
75
|
+
self.log_the_catch!
|
62
76
|
end
|
63
77
|
|
64
78
|
def tx_hash
|
65
|
-
@tx_hash ||=
|
79
|
+
@tx_hash ||= TxCatcher.rpc_node.decoderawtransaction(self.hex)
|
66
80
|
end
|
81
|
+
alias :parse_transaction :tx_hash
|
67
82
|
|
68
83
|
def confirmations
|
69
84
|
if self.block_height
|
@@ -73,19 +88,48 @@ module TxCatcher
|
|
73
88
|
end
|
74
89
|
end
|
75
90
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
91
|
+
def update_block_height(confirmations: nil)
|
92
|
+
return false if self.block_height
|
93
|
+
if TxCatcher.rpc_node.txindex_enabled? || !confirmations.nil?
|
94
|
+
update_block_height_from_rpc(confirmations: confirmations)
|
95
|
+
else
|
96
|
+
self.update_block_height_from_latest_blocks
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
81
100
|
|
101
|
+
def update_block_height!(confirmations: nil)
|
102
|
+
return false if self.block_height
|
103
|
+
self.update_block_height(confirmations: confirmations)
|
104
|
+
self.save if self.column_changed?(:block_height)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Checks the last n blocks to see if current transaction has been included in any of them,
|
108
|
+
# This is for cases when -txindex is not enabled and you can't make an RPC query for a particular
|
109
|
+
# txid, which would be more reliable.
|
110
|
+
def update_block_height_from_latest_blocks
|
111
|
+
blocks = TxCatcher.rpc_node.get_blocks(blocks_to_check_for_inclusion_if_unconfirmed) unless blocks
|
82
112
|
for block in blocks.values do
|
83
113
|
if block["tx"] && block["tx"].include?(self.txid)
|
84
114
|
LOGGER.report "tx #{self.txid} block height updated to #{block["height"]}"
|
85
|
-
self.
|
115
|
+
self.block_height = block["height"].to_i
|
86
116
|
return block["height"].to_i
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Directly queries the RPC, fetches transaction confirmations number and calculates
|
122
|
+
# the block_height. Of confirmations number is provided, doesn't do the RPC request
|
123
|
+
# (used in Transaction.create_from_rpc).
|
124
|
+
def update_block_height_from_rpc(confirmations: nil)
|
125
|
+
begin
|
126
|
+
confirmations ||= TxCatcher.rpc_node.getrawtransaction(self.txid, 1)["confirmations"]
|
127
|
+
self.block_height = confirmations && confirmations > 0 ? TxCatcher.current_block_height - confirmations + 1 : nil
|
128
|
+
rescue BitcoinRPC::JSONRPCError => e
|
129
|
+
if e.message.include?("No such mempool or blockchain transaction") && self.rbf?
|
130
|
+
LOGGER.report "tx #{self.txid} is an RBF transcation with a lower fee, bitcoin RPC says it's not in the mempool anymore. No need to check for confirmations", :warn
|
87
131
|
else
|
88
|
-
|
132
|
+
raise e
|
89
133
|
end
|
90
134
|
end
|
91
135
|
end
|
@@ -128,7 +172,7 @@ module TxCatcher
|
|
128
172
|
end
|
129
173
|
|
130
174
|
def input_hexes
|
131
|
-
@input_hexes ||= self.tx_hash["vin"].map { |input| input["scriptSig"]["hex"] }.compact.sort
|
175
|
+
@input_hexes ||= self.tx_hash["vin"].select { |input| !input["scriptSig"].nil? }.map { |input| input["scriptSig"]["hex"] }.compact.sort
|
132
176
|
end
|
133
177
|
|
134
178
|
def output_addresses
|
@@ -150,34 +194,30 @@ module TxCatcher
|
|
150
194
|
end
|
151
195
|
|
152
196
|
def to_json
|
153
|
-
#self.tx_hash.to_json
|
154
197
|
self.tx_hash.merge(confirmations: self.confirmations, block_height: self.block_height).to_json
|
155
198
|
end
|
156
199
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
200
|
+
def assign_transaction_attrs
|
201
|
+
self.txid = self.tx_hash["txid"] unless self.txid
|
202
|
+
self.block_height = self.tx_hash["block_height"] unless self.block_height
|
203
|
+
# In order to be able to identify RBF - those are normally transactions with
|
204
|
+
# identical inputs and outputs - we hash inputs and outputs that hash serves
|
205
|
+
# as an identifier that we store in our DB and thus can search all
|
206
|
+
# previous transactions which the current transaction might be an RBF transaction to.
|
207
|
+
#
|
208
|
+
# A few comments:
|
209
|
+
#
|
210
|
+
# 1. Although an RBF transaction may techinically have different outputs as per
|
211
|
+
# protocol specification, it is true in most cases that outputs will also be
|
212
|
+
# the same (that's how most wallets implement RBF). Thus,
|
213
|
+
# we're also incorporating outputs into the hashed value.
|
214
|
+
#
|
215
|
+
# 2. For inputs, we're using input hexes, because pruned bitcoin-core
|
216
|
+
# doesn't provide addresses.
|
217
|
+
self.inputs_outputs_hash ||= Digest::SHA256.hexdigest((self.input_hexes + self.output_addresses).join(""))
|
218
|
+
end
|
162
219
|
|
163
|
-
|
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(""))
|
180
|
-
end
|
220
|
+
private
|
181
221
|
|
182
222
|
def validate
|
183
223
|
super
|
data/lib/txcatcher/server.rb
CHANGED
@@ -50,7 +50,7 @@ module TxCatcher
|
|
50
50
|
deposits = deposits.map do |d|
|
51
51
|
t = d.transaction
|
52
52
|
t.update(protected: true) unless t.protected
|
53
|
-
t.
|
53
|
+
t.update_block_height
|
54
54
|
result = {
|
55
55
|
txid: t.txid,
|
56
56
|
amount: d.amount_in_btc,
|
@@ -73,8 +73,8 @@ module TxCatcher
|
|
73
73
|
addr = path.last
|
74
74
|
|
75
75
|
address = Address.find_or_create(address: addr)
|
76
|
-
return [200, {}, "{}"] unless address.deposits.empty?
|
77
76
|
deposits = Deposit.where(address_id: address.id).limit(params["limit"] || 100).eager(:transaction)
|
77
|
+
return [200, {}, "{}"] if address.deposits.empty?
|
78
78
|
|
79
79
|
transactions = deposits.map { |d| d.transaction }
|
80
80
|
transactions.sort! { |t1,t2| t2.created_at <=> t1.created_at }
|
@@ -82,7 +82,7 @@ module TxCatcher
|
|
82
82
|
transactions.each do |t|
|
83
83
|
# If we see a transaction with 0 confirmations, let's check if it got any news confirmations
|
84
84
|
# by querying Bitcoind RPC.
|
85
|
-
t.
|
85
|
+
t.update_block_height_from_rpc if t.confirmations == 0
|
86
86
|
# If still not confirmed, let's make it protected so it's not deleted during cleanup.
|
87
87
|
t.protected = true if t.confirmations == 0
|
88
88
|
end
|
@@ -109,10 +109,10 @@ module TxCatcher
|
|
109
109
|
def tx(path)
|
110
110
|
path = path.sub(/\?.*/, '').split("/").delete_if { |i| i.empty? }
|
111
111
|
txid = path.last
|
112
|
-
tx = Transaction.
|
113
|
-
tx = Transaction.where(txid: txid).first
|
112
|
+
tx = Transaction.find_or_create_from_rpc(txid)
|
114
113
|
|
115
|
-
if tx && !tx.deposits.empty?
|
114
|
+
if tx && (!tx.deposits.empty? || tx.rbf?)
|
115
|
+
tx.update_block_height! if tx.block_height.nil?
|
116
116
|
return [200, {}, tx.to_json]
|
117
117
|
else
|
118
118
|
return [404, {}, "Transaction not found"]
|
@@ -32,13 +32,6 @@ RSpec.describe TxCatcher::Transaction do
|
|
32
32
|
expect(TxCatcher::Transaction.where(txid: transaction.txid).count).to eq(1)
|
33
33
|
end
|
34
34
|
|
35
|
-
it "updates block height by making manual requests to RPC and searching if tx is included in one of the previous blocks" do
|
36
|
-
expect(TxCatcher.rpc_node).to receive(:getblockhash).exactly(10).times.and_return("blockhash123")
|
37
|
-
expect(TxCatcher.rpc_node).to receive(:getblock).exactly(10).times.and_return({ "height" => "123", "tx" => [@transaction.txid], "hash" => "blockhash123"})
|
38
|
-
@transaction.check_block_height!
|
39
|
-
expect(@transaction.block_height).to eq(123)
|
40
|
-
end
|
41
|
-
|
42
35
|
it "updates multiple records at once" do
|
43
36
|
transaction1 = TxCatcher::Transaction.create(hex: @hextx, txid: "123")
|
44
37
|
transaction2 = TxCatcher::Transaction.create(hex: @hextx, txid: "1234")
|
@@ -98,4 +91,15 @@ RSpec.describe TxCatcher::Transaction do
|
|
98
91
|
|
99
92
|
end
|
100
93
|
|
94
|
+
describe "updating block height" do
|
95
|
+
|
96
|
+
it "updates block height by searching if tx is included in one of the previous blocks" do
|
97
|
+
expect(TxCatcher.rpc_node).to receive(:getblockhash).exactly(10).times.and_return("blockhash123")
|
98
|
+
expect(TxCatcher.rpc_node).to receive(:getblock).exactly(10).times.and_return({ "height" => "123", "tx" => [@transaction.txid], "hash" => "blockhash123"})
|
99
|
+
@transaction.check_block_height!
|
100
|
+
expect(@transaction.block_height).to eq(123)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
101
105
|
end
|
data/txcatcher.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "txcatcher".freeze
|
3
|
-
s.version = "0.2.
|
3
|
+
s.version = "0.2.8"
|
4
4
|
|
5
5
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
6
6
|
s.require_paths = ["lib".freeze]
|
@@ -29,7 +29,6 @@ Gem::Specification.new do |s|
|
|
29
29
|
"db/migrations/003_create_deposits.rb",
|
30
30
|
"db/migrations/004_add_timestamps_to_transactions.rb",
|
31
31
|
"db/migrations/005_add_protected_flag_to_transactions.rb",
|
32
|
-
"db/schema.rb",
|
33
32
|
"lib/tasks/db.rake",
|
34
33
|
"lib/txcatcher.rb",
|
35
34
|
"lib/txcatcher/bitcoin_rpc.rb",
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: txcatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roman Snitko
|
@@ -162,7 +162,6 @@ files:
|
|
162
162
|
- db/migrations/003_create_deposits.rb
|
163
163
|
- db/migrations/004_add_timestamps_to_transactions.rb
|
164
164
|
- db/migrations/005_add_protected_flag_to_transactions.rb
|
165
|
-
- db/schema.rb
|
166
165
|
- lib/tasks/db.rake
|
167
166
|
- lib/txcatcher.rb
|
168
167
|
- lib/txcatcher/bitcoin_rpc.rb
|
data/db/schema.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
Sequel.migration do
|
2
|
-
change do
|
3
|
-
create_table(:addresses) do
|
4
|
-
String :address, :size=>255, :null=>false
|
5
|
-
|
6
|
-
primary_key [:address]
|
7
|
-
end
|
8
|
-
|
9
|
-
create_table(:deposits, :ignore_index_errors=>true) do
|
10
|
-
primary_key :id
|
11
|
-
Bignum :amount, :null=>false
|
12
|
-
Integer :transaction_id
|
13
|
-
Integer :address_id
|
14
|
-
|
15
|
-
index [:address_id]
|
16
|
-
index [:transaction_id]
|
17
|
-
end
|
18
|
-
|
19
|
-
create_table(:schema_info) do
|
20
|
-
Integer :version, :default=>0, :null=>false
|
21
|
-
end
|
22
|
-
|
23
|
-
create_table(:transactions) do
|
24
|
-
String :txid, :size=>255, :null=>false
|
25
|
-
DateTime :created_at
|
26
|
-
TrueClass :protected, :default=>false
|
27
|
-
|
28
|
-
primary_key [:txid]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|