txcatcher 0.2.8 → 0.2.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2e24688c0b712c8752c126180e4ccdc7508bb51d83273c0ee95975ee683c45b
4
- data.tar.gz: 0fc259dc40b77ff6f3ad51d31a389b6ad6c290e50e00fdd29b45dc9c0aff8706
3
+ metadata.gz: 38bad27a4120b50cb51ce9071239676b1219f1177ab977090b1e57a192b8ac66
4
+ data.tar.gz: 187e7f7905bee995a8ac341d899d7d7cf00efbd05b5f32696f82f456dc8d024d
5
5
  SHA512:
6
- metadata.gz: fa710896fdaa6ff9a504e73168eef31eec3a69ba4b583817c7dce9132f8c1924e1579437a47162a8c1a5643dc366d54ef08cc9b41e102d6e0f738d291c9c3855
7
- data.tar.gz: 0cd0b9f730fc45229c7a9547d19a4299b2a103549c7687e0b144f9e042e2ccdbb9d82960de72de154eb95c55f494c2e44ae202c1939d64f3522eef7f176e41b5
6
+ metadata.gz: fe46bcb16a750f4a03aa77f0034fac4fc8e072ce7960b8ce3a4ad82ea361b2a5464908b7d2ca2e0a6b5a9bccf98447a018e968b90b6b6eced1c20d64ecf5ba12
7
+ data.tar.gz: 97cbcab71ce183f75675e074a474af8824a9757242d4a1e5e02e6135b54950ae9c3e1e6a5d5bc47272951faf7488d97ab3ff2d5d4bcec3e5e0d068737280e2cd
data/Gemfile.lock CHANGED
@@ -93,4 +93,4 @@ DEPENDENCIES
93
93
  sqlite3
94
94
 
95
95
  BUNDLED WITH
96
- 1.17.2
96
+ 2.1.4
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.8
1
+ 0.2.14
@@ -106,11 +106,14 @@ module TxCatcher
106
106
  undetected_transactions_ids.each { |txid| Transaction.create_from_rpc(txid) }
107
107
  Transaction.where(txid: existing_transactions_ids).update(block_height: height)
108
108
  })
109
- # Update RBF transactions and deposits if a transaction a with lower fee (no associated deposit) got
110
- # accidentally confirmed.
111
- TxCatcher::Transaction.where(block_height: height).exclude(rbf_next_transaction_id: nil).each do |t|
112
- t.force_deposit_association_on_rbf!
113
- end
109
+ # Update RBF transactions and deposits if a transaction a with lower fee
110
+ # (no associated deposit) got accidentally confirmed.
111
+ @queue["hashblock"] << (Proc.new {
112
+ TxCatcher::Transaction.where(block_height: height).exclude(rbf_next_transaction_id: nil).each do |t|
113
+ t.force_deposit_association_on_rbf!
114
+ end
115
+ })
116
+
114
117
  end
115
118
 
116
119
  end # class Catcher
@@ -51,7 +51,7 @@ module TxCatcher
51
51
 
52
52
  def clean_transactions(n)
53
53
  transactions = Transaction.order(Sequel.asc(:created_at))
54
- transactions.where(Sequel.~(protected: true)) if Config.protected_transactions
54
+ transactions = transactions.where(Sequel.~(protected: true)) if Config.protected_transactions
55
55
 
56
56
  t = n/100
57
57
  t = 1 if t == 0
@@ -105,15 +105,15 @@ module TxCatcher
105
105
  "#{File.dirname(ConfigFile.path)}/#{db_config[:db_path]}"
106
106
  end
107
107
  else
108
- db_config[:db_name]
108
+ db_config[:db_path] || db_config[:db_name]
109
109
  end
110
110
 
111
111
  TxCatcher.db_connection = Sequel.connect(
112
112
  "#{db_config[:adapter]}://" +
113
113
  "#{db_config[:user]}#{(":" if db_config[:user])}" +
114
114
  "#{db_config[:password]}#{("@" if db_config[:user] || db_config[:password])}" +
115
- "#{db_config[:host]}#{(":" if db_config[:port])}" +
116
- "#{db_config[:port]}#{("/" if db_config[:host] || db_config[:port])}" +
115
+ "#{db_config[:host] || "localhost"}" +
116
+ ":#{db_config[:port]}#{("/" if db_config[:host] || db_config[:port])}" +
117
117
  "#{db_name}"
118
118
  )
119
119
  end
@@ -30,7 +30,7 @@ module TxCatcher
30
30
 
31
31
  private
32
32
 
33
- def report_to_stdout(message, log_level, data: nil, timestamp: timestamp, newline: "\n")
33
+ def report_to_stdout(message, log_level, data: nil, timestamp: false, newline: "\n")
34
34
  $stdout.print prepare_message(message, timestamp: timestamp)
35
35
  $stdout.print "\n additional data: #{data.to_s}" if data
36
36
  if LOG_LEVELS[log_level] >= LOG_LEVELS[:error]
@@ -55,7 +55,7 @@ module TxCatcher
55
55
  end
56
56
  end
57
57
 
58
- def report_to_sentry(e, log_level, data: nil, timestamp: timestamp, newline: true)
58
+ def report_to_sentry(e, log_level, data: nil, timestamp: false, newline: true)
59
59
  return unless TxCatcher::Config["logger"]["sentry_dsn"]
60
60
  data ||= {}
61
61
  data.merge!(environment: Config["environment"], host: Config["host"], currency: Config["currency"])
@@ -180,7 +180,7 @@ module TxCatcher
180
180
  end
181
181
 
182
182
  # Sometimes, even though an RBF transaction with higher fee was broadcasted,
183
- # miners accept one the lower-fee transactions instead. However, in txcatcher database, the
183
+ # miners accept the lower-fee transaction instead. However, in txcatcher database, the
184
184
  # deposits are already associated with the latest transaction. In this case,
185
185
  # we need to find the deposits in the DB set their transaction_id field to current transaction id.
186
186
  def force_deposit_association_on_rbf!
@@ -189,8 +189,11 @@ module TxCatcher
189
189
  tx = tx.rbf_next_transaction
190
190
  end
191
191
  tx.deposits.each do |d|
192
- d.update(transaction_id: self.id)
193
- end
192
+ d.rbf_transaction_ids.delete(self.id)
193
+ d.rbf_transaction_ids.push(d.transaction_id)
194
+ d.transaction_id = self.id
195
+ d.save
196
+ end if tx
194
197
  end
195
198
 
196
199
  def to_json
@@ -222,6 +225,7 @@ module TxCatcher
222
225
  def validate
223
226
  super
224
227
  validates_unique :txid
228
+ #errors.add(:base, "Duplicate transaction listed as RBF") if self.rbf_previous_transaction&.txid && self&.txid && self.rbf_previous_transaction&.txid == self&.txid
225
229
  errors.add(:base, "No outputs for this transaction") if !self.rbf? && self.deposits.empty?
226
230
  end
227
231
 
@@ -23,10 +23,10 @@ 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)
28
26
  elsif path.start_with? "/tx/send"
29
27
  broadcast_tx(params["rawtx"])
28
+ elsif path.start_with? "/tx/"
29
+ tx(path)
30
30
  elsif path.start_with? "/feerate"
31
31
  feerate(params["blocks_target"] || 2)
32
32
  elsif path == "/" || path.empty?
data/spec/catcher_spec.rb CHANGED
@@ -8,78 +8,118 @@ require_relative '../lib/txcatcher/catcher'
8
8
 
9
9
  RSpec.describe TxCatcher::Catcher do
10
10
 
11
- before(:all) do
12
- @tx_sock = ZMQ::Context.create.socket(ZMQ::PUB)
13
- @block_sock = ZMQ::Context.create.socket(ZMQ::PUB)
11
+ # None of the unit tests here should require a running bitcoind
12
+ describe "(unit tests that require bitcoind to test)" do
14
13
 
15
- @tx_sock.bind("ipc:///tmp/bitcoind_test.rawtx")
16
- @block_sock.bind("ipc:///tmp/bitcoind_test.hashblock")
14
+ before(:all) do
15
+ @tx_sock = ZMQ::Context.create.socket(ZMQ::PUB)
16
+ @block_sock = ZMQ::Context.create.socket(ZMQ::PUB)
17
17
 
18
- @hextx = File.read(File.dirname(__FILE__) + "/fixtures/transaction.txt").strip
19
- @rawtx = unhexlify(File.read(File.dirname(__FILE__) + "/fixtures/transaction.txt"))
20
- @catcher = TxCatcher::Catcher.new(name: "bitcoind_test")
21
- sleep 1
22
- end
18
+ @tx_sock.bind("ipc:///tmp/bitcoind_test.rawtx")
19
+ @block_sock.bind("ipc:///tmp/bitcoind_test.hashblock")
23
20
 
24
- after(:all) do
25
- @catcher.close_all_connections
26
- @tx_sock.unbind("ipc:///tmp/bitcoind_test.rawtx")
27
- @block_sock.unbind('ipc:///tmp/bitcoind_test.hashblock')
28
- end
21
+ @hextx = File.read(File.dirname(__FILE__) + "/fixtures/transaction.txt").strip
22
+ @rawtx = unhexlify(File.read(File.dirname(__FILE__) + "/fixtures/transaction.txt"))
23
+ @catcher = TxCatcher::Catcher.new(name: "bitcoind_test")
24
+ sleep 1
25
+ end
29
26
 
30
- after(:each) do
31
- @catcher.sockets["rawtx"][:last_message] = nil
32
- @catcher.sockets["hashblock"][:last_message] = nil
33
- end
27
+ after(:all) do
28
+ @catcher.close_all_connections
29
+ @tx_sock.unbind("ipc:///tmp/bitcoind_test.rawtx")
30
+ @block_sock.unbind('ipc:///tmp/bitcoind_test.hashblock')
31
+ end
34
32
 
35
- it "creates a new transaction in the DB" do
36
- @tx_sock.send_strings(['rawtx', @rawtx])
37
- i = 0
38
- until (tx = TxCatcher::Transaction.last) || i > 3
39
- sleep 1
40
- i+=1
33
+ after(:each) do
34
+ @catcher.sockets["rawtx"][:last_message] = nil
35
+ @catcher.sockets["hashblock"][:last_message] = nil
41
36
  end
42
- expect(tx.hex).to eq(@hextx)
43
- end
44
37
 
45
- it "updates transactions block height upon receiving a new block " do
46
- transaction = TxCatcher::Transaction.create(hex: @hextx)
47
- expect(TxCatcher.rpc_node).to receive(:getblock).at_least(:once).and_return({ "height" => TxCatcher.current_block_height + 1, "tx" => [transaction.txid]})
38
+ it "creates a new transaction in the DB" do
39
+ @tx_sock.send_strings(['rawtx', @rawtx])
40
+ i = 0
41
+ until (tx = TxCatcher::Transaction.last) || i > 3
42
+ sleep 1
43
+ i+=1
44
+ end
45
+ expect(tx.hex).to eq(@hextx)
46
+ end
48
47
 
49
- @block_sock.send_strings(["hashblock", 'hello'])
48
+ it "updates transactions block height upon receiving a new block " do
49
+ transaction = TxCatcher::Transaction.create(hex: @hextx)
50
+ expect(TxCatcher.rpc_node).to receive(:getblock).at_least(:once).and_return({ "height" => TxCatcher.current_block_height + 1, "tx" => [transaction.txid]})
50
51
 
51
- i = 0
52
- begin
53
- sleep 1 and i += 1
54
- end until @catcher.sockets["hashblock"][:last_message] || i > 3
52
+ @block_sock.send_strings(["hashblock", 'hello'])
55
53
 
56
- expect(transaction.reload.block_height).to eq(TxCatcher.current_block_height)
57
- end
54
+ i = 0
55
+ begin
56
+ sleep 1 and i += 1
57
+ end until @catcher.sockets["hashblock"][:last_message] || i > 3
58
+
59
+ expect(transaction.reload.block_height).to eq(TxCatcher.current_block_height)
60
+ end
61
+
62
+ it "ignores validation errors" do
63
+ tx = eval File.read(File.dirname(__FILE__) + "/fixtures/transaction_decoded_no_outputs.txt")
64
+ expect(TxCatcher.rpc_node).to receive(:decoderawtransaction).at_least(:once).and_return(tx)
65
+ @tx_sock.send_strings(["rawtx", @rawtx])
58
66
 
59
- it "ignores validation errors" do
60
- tx = eval File.read(File.dirname(__FILE__) + "/fixtures/transaction_decoded_no_outputs.txt")
61
- expect(TxCatcher.rpc_node).to receive(:decoderawtransaction).at_least(:once).and_return(tx)
62
- @tx_sock.send_strings(["rawtx", @rawtx])
67
+ i = 0
68
+ begin
69
+ sleep 1 and i += 1
70
+ end until @catcher.sockets["rawtx"][:last_message] || i > 3
63
71
 
64
- i = 0
65
- begin
66
- sleep 1 and i += 1
67
- end until @catcher.sockets["rawtx"][:last_message] || i > 3
72
+ expect(File.exists?(ERRFILE)).to be_falsey
73
+ end
74
+
75
+ it "logs all other errors" do
76
+ sleep 1
77
+ expect(TxCatcher.rpc_node).to receive(:decoderawtransaction).at_least(:once).and_raise(StandardError)
78
+ @tx_sock.send_strings(["rawtx", @rawtx])
79
+
80
+ i = 0
81
+ begin
82
+ sleep 1 and i += 1
83
+ end until @catcher.sockets["rawtx"][:last_message] || i > 3
84
+ expect(File.read(ERRFILE)).not_to be_empty
85
+ end
68
86
 
69
- expect(File.exists?(ERRFILE)).to be_falsey
70
87
  end
71
88
 
72
- it "logs all other errors" do
89
+
90
+ it "updates deposits association with an RBF tx if a transaction with lower fee gets confirmed" do
91
+
92
+ class TxCatcher::Transaction
93
+ def assign_tx_hash(h)
94
+ @tx_hash = h
95
+ end
96
+ end
97
+
98
+ class CatcherStub < TxCatcher::Catcher
99
+ def listen_to_zeromq_channels(channel);end
100
+ end
101
+
102
+ hextx = File.read(File.dirname(__FILE__) + "/fixtures/transaction.txt").strip
103
+ tx1 = TxCatcher::Transaction.create(hex: hextx)
104
+ deposits = tx1.deposits
105
+ rbf_tx_hash = tx1.tx_hash
106
+ rbf_tx_hash["txid"] = "rbftxid1"
107
+ rbf_tx_hash["locktime"] = "1"
108
+ tx2 = TxCatcher::Transaction.new(hex: hextx)
109
+ tx2.assign_tx_hash(rbf_tx_hash)
110
+ tx2.save
111
+
112
+ catcher = CatcherStub.new(name: "bitcoind_test")
113
+ allow(TxCatcher.rpc_node).to receive(:getblock).and_return({ "tx" => [tx1.txid], "height" => 123 })
114
+ catcher.send(:handle_hashblock, "new block")
73
115
  sleep 1
74
- expect(TxCatcher.rpc_node).to receive(:decoderawtransaction).at_least(:once).and_raise(StandardError)
75
- @tx_sock.send_strings(["rawtx", @rawtx])
76
-
77
- i = 0
78
- begin
79
- sleep 1 and i += 1
80
- end until @catcher.sockets["rawtx"][:last_message] || i > 3
81
- expect(File.read(ERRFILE)).not_to be_empty
82
- end
83
116
 
117
+ deposits.each do |d|
118
+ expect(d.reload.transaction_id).to eq(tx1.id)
119
+ end
120
+
121
+ expect(tx1.reload.deposits.map(&:id)).to eq(deposits.map(&:id))
122
+ expect(tx2.reload.deposits.map(&:id)).to eq([])
123
+ end
84
124
 
85
125
  end
@@ -82,11 +82,15 @@ RSpec.describe TxCatcher::Transaction do
82
82
  rbf_tx.assign_tx_hash(rbf_tx_hash)
83
83
  rbf_tx.save
84
84
 
85
+ expect(rbf_tx.reload.deposits.size).to eq(2)
86
+ expect(@transaction.reload.deposits.size).to eq(0)
87
+
85
88
  @transaction.reload.force_deposit_association_on_rbf!
86
89
  expect(rbf_tx.reload.deposits).to be_empty
87
90
  expect(@transaction.reload.deposits.map(&:id)).to eq(deposit_ids)
91
+
88
92
  @transaction.deposits.each do |d|
89
- expect(d.rbf_transaction_ids).to eq([@transaction.id])
93
+ expect(d.rbf_transaction_ids).to eq([rbf_tx.id])
90
94
  end
91
95
 
92
96
  end
@@ -96,7 +100,7 @@ RSpec.describe TxCatcher::Transaction do
96
100
  it "updates block height by searching if tx is included in one of the previous blocks" do
97
101
  expect(TxCatcher.rpc_node).to receive(:getblockhash).exactly(10).times.and_return("blockhash123")
98
102
  expect(TxCatcher.rpc_node).to receive(:getblock).exactly(10).times.and_return({ "height" => "123", "tx" => [@transaction.txid], "hash" => "blockhash123"})
99
- @transaction.check_block_height!
103
+ @transaction.update_block_height!
100
104
  expect(@transaction.block_height).to eq(123)
101
105
  end
102
106
 
data/txcatcher.gemspec CHANGED
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "txcatcher".freeze
3
- s.version = "0.2.8"
3
+ s.version = "0.2.14"
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]
7
7
  s.authors = ["Roman Snitko".freeze]
8
8
  s.date = "2018-10-02"
9
- s.description = "Ccurrently, the only job of this gem is to collect all new Bitcoin/Litecoin transactions, store them in a DB, index addresses.".freeze
9
+ s.description = "Currently, the only job of this gem is to collect all new Bitcoin/Litecoin transactions, store them in a DB, index addresses.".freeze
10
10
  s.email = "roman.snitko@gmail.com".freeze
11
11
  s.executables = ["txcatcher".freeze, "txcatcher-monitor".freeze]
12
12
  s.extra_rdoc_files = [
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.8
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Snitko
@@ -136,7 +136,7 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
- description: Ccurrently, the only job of this gem is to collect all new Bitcoin/Litecoin
139
+ description: Currently, the only job of this gem is to collect all new Bitcoin/Litecoin
140
140
  transactions, store them in a DB, index addresses.
141
141
  email: roman.snitko@gmail.com
142
142
  executables:
@@ -206,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
206
  - !ruby/object:Gem::Version
207
207
  version: '0'
208
208
  requirements: []
209
- rubygems_version: 3.0.3
209
+ rubygems_version: 3.1.2
210
210
  signing_key:
211
211
  specification_version: 4
212
212
  summary: An lightweight version of Bitpay's Insight, allows to check Bitcoin/Litecoin