txcatcher 0.1.0
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 +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +116 -0
- data/LICENSE.txt +20 -0
- data/README.md +11 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/bin/txcatcher +9 -0
- data/db/migrations/001_create_transactions.rb +11 -0
- data/db/migrations/002_create_addresses.rb +9 -0
- data/db/migrations/003_create_deposits.rb +10 -0
- data/db/schema.rb +35 -0
- data/lib/tasks/db.rake +52 -0
- data/lib/txcatcher/bitcoin_rpc.rb +30 -0
- data/lib/txcatcher/catcher.rb +103 -0
- data/lib/txcatcher/cleaner.rb +65 -0
- data/lib/txcatcher/config.rb +20 -0
- data/lib/txcatcher/initializer.rb +148 -0
- data/lib/txcatcher/models/address.rb +13 -0
- data/lib/txcatcher/models/deposit.rb +23 -0
- data/lib/txcatcher/models/transaction.rb +55 -0
- data/lib/txcatcher/server.rb +74 -0
- data/lib/txcatcher/utils/hash_string_to_sym_keys.rb +24 -0
- data/lib/txcatcher.rb +21 -0
- data/spec/catcher_spec.rb +96 -0
- data/spec/cleaner_spec.rb +60 -0
- data/spec/config/config.yml +22 -0
- data/spec/config/txcatcher_test.db +0 -0
- data/spec/fixtures/transaction.txt +1 -0
- data/spec/fixtures/transaction_decoded_no_outputs.txt +1 -0
- data/spec/models/address_spec.rb +5 -0
- data/spec/models/transaction_spec.rb +26 -0
- data/spec/spec_helper.rb +46 -0
- data/templates/config.yml +23 -0
- metadata +167 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 19e98ad3469e868a632b24ac5b511e557be28c9c
|
|
4
|
+
data.tar.gz: 83b80df4f5cd3b2367adfbc26e9782184c8901f7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6e5e8b687a4feb36016a0ebe7f8e07cb0c604dc9add1d31b6ce0ca412fa871f7222f45f102cef733017276277b301a52dd9f3de56a06deaa7581ef2eb1819cb5
|
|
7
|
+
data.tar.gz: 8436da5751fdf20ce4ef8d45d0d58842d3f807e73ae8fe0b83412c919b41e1bccb47d2c780e07c5cc9d6ae3f083953e195ca73c8bc1dd8bbf2b370126a1f3efc
|
data/.document
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
addressable (2.4.0)
|
|
5
|
+
async-rack (0.5.1)
|
|
6
|
+
rack (~> 1.1)
|
|
7
|
+
builder (3.2.3)
|
|
8
|
+
descendants_tracker (0.0.4)
|
|
9
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
10
|
+
diff-lcs (1.3)
|
|
11
|
+
einhorn (0.7.4)
|
|
12
|
+
em-synchrony (1.0.6)
|
|
13
|
+
eventmachine (>= 1.0.0.beta.1)
|
|
14
|
+
em-websocket (0.3.8)
|
|
15
|
+
addressable (>= 2.1.1)
|
|
16
|
+
eventmachine (>= 0.12.9)
|
|
17
|
+
eventmachine (1.2.5)
|
|
18
|
+
faraday (0.9.2)
|
|
19
|
+
multipart-post (>= 1.2, < 3)
|
|
20
|
+
ffi (1.9.18)
|
|
21
|
+
ffi-rzmq (2.0.5)
|
|
22
|
+
ffi-rzmq-core (>= 1.0.6)
|
|
23
|
+
ffi-rzmq-core (1.0.6)
|
|
24
|
+
ffi
|
|
25
|
+
git (1.3.0)
|
|
26
|
+
github_api (0.16.0)
|
|
27
|
+
addressable (~> 2.4.0)
|
|
28
|
+
descendants_tracker (~> 0.0.4)
|
|
29
|
+
faraday (~> 0.8, < 0.10)
|
|
30
|
+
hashie (>= 3.4)
|
|
31
|
+
mime-types (>= 1.16, < 3.0)
|
|
32
|
+
oauth2 (~> 1.0)
|
|
33
|
+
goliath (1.0.5)
|
|
34
|
+
async-rack
|
|
35
|
+
einhorn
|
|
36
|
+
em-synchrony (>= 1.0.0)
|
|
37
|
+
em-websocket (= 0.3.8)
|
|
38
|
+
eventmachine (>= 1.0.0.beta.4)
|
|
39
|
+
http_parser.rb (>= 0.6.0)
|
|
40
|
+
log4r
|
|
41
|
+
multi_json
|
|
42
|
+
rack (>= 1.2.2)
|
|
43
|
+
rack-contrib
|
|
44
|
+
rack-respond_to
|
|
45
|
+
hashie (3.5.6)
|
|
46
|
+
highline (1.7.8)
|
|
47
|
+
http_parser.rb (0.6.0)
|
|
48
|
+
jeweler (2.3.7)
|
|
49
|
+
builder
|
|
50
|
+
bundler (>= 1)
|
|
51
|
+
git (>= 1.2.5)
|
|
52
|
+
github_api (~> 0.16.0)
|
|
53
|
+
highline (>= 1.6.15)
|
|
54
|
+
nokogiri (>= 1.5.10)
|
|
55
|
+
psych (~> 2.2)
|
|
56
|
+
rake
|
|
57
|
+
rdoc
|
|
58
|
+
semver2
|
|
59
|
+
jwt (1.5.6)
|
|
60
|
+
log4r (1.1.10)
|
|
61
|
+
mime-types (2.99.3)
|
|
62
|
+
mini_portile2 (2.2.0)
|
|
63
|
+
multi_json (1.12.1)
|
|
64
|
+
multi_xml (0.6.0)
|
|
65
|
+
multipart-post (2.0.0)
|
|
66
|
+
nokogiri (1.8.0)
|
|
67
|
+
mini_portile2 (~> 2.2.0)
|
|
68
|
+
oauth2 (1.4.0)
|
|
69
|
+
faraday (>= 0.8, < 0.13)
|
|
70
|
+
jwt (~> 1.0)
|
|
71
|
+
multi_json (~> 1.3)
|
|
72
|
+
multi_xml (~> 0.5)
|
|
73
|
+
rack (>= 1.2, < 3)
|
|
74
|
+
psych (2.2.4)
|
|
75
|
+
rack (1.6.8)
|
|
76
|
+
rack-accept-media-types (0.9)
|
|
77
|
+
rack-contrib (1.5.0)
|
|
78
|
+
rack (~> 1.4)
|
|
79
|
+
rack-respond_to (0.9.8)
|
|
80
|
+
rack-accept-media-types (>= 0.6)
|
|
81
|
+
rake (12.0.0)
|
|
82
|
+
rdoc (5.1.0)
|
|
83
|
+
rspec (3.6.0)
|
|
84
|
+
rspec-core (~> 3.6.0)
|
|
85
|
+
rspec-expectations (~> 3.6.0)
|
|
86
|
+
rspec-mocks (~> 3.6.0)
|
|
87
|
+
rspec-core (3.6.0)
|
|
88
|
+
rspec-support (~> 3.6.0)
|
|
89
|
+
rspec-expectations (3.6.0)
|
|
90
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
91
|
+
rspec-support (~> 3.6.0)
|
|
92
|
+
rspec-mocks (3.6.0)
|
|
93
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
94
|
+
rspec-support (~> 3.6.0)
|
|
95
|
+
rspec-support (3.6.0)
|
|
96
|
+
satoshi-unit (0.2.2)
|
|
97
|
+
semver2 (3.4.2)
|
|
98
|
+
sequel (4.49.0)
|
|
99
|
+
sqlite3 (1.3.13)
|
|
100
|
+
thread_safe (0.3.6)
|
|
101
|
+
|
|
102
|
+
PLATFORMS
|
|
103
|
+
ruby
|
|
104
|
+
|
|
105
|
+
DEPENDENCIES
|
|
106
|
+
bundler
|
|
107
|
+
ffi-rzmq
|
|
108
|
+
goliath
|
|
109
|
+
jeweler
|
|
110
|
+
rspec
|
|
111
|
+
satoshi-unit
|
|
112
|
+
sequel
|
|
113
|
+
sqlite3
|
|
114
|
+
|
|
115
|
+
BUNDLED WITH
|
|
116
|
+
1.15.4
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2017 Roman Snitko
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
= txcatcher
|
|
2
|
+
|
|
3
|
+
Dependencies
|
|
4
|
+
------------
|
|
5
|
+
|
|
6
|
+
In order for bitcoin-core and litecoin-core to stream new transactions
|
|
7
|
+
and blocks, we need ZeroMQ. Ubuntu/Debian installation instructions are here:
|
|
8
|
+
|
|
9
|
+
http://zeromq.org/distro:debian
|
|
10
|
+
|
|
11
|
+
you can safely use a package weirdly located at download.opensuse.org.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'bundler'
|
|
5
|
+
begin
|
|
6
|
+
Bundler.setup(:default, :development)
|
|
7
|
+
rescue Bundler::BundlerError => e
|
|
8
|
+
$stderr.puts e.message
|
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
|
10
|
+
exit e.status_code
|
|
11
|
+
end
|
|
12
|
+
require 'rake'
|
|
13
|
+
|
|
14
|
+
require 'jeweler'
|
|
15
|
+
Jeweler::Tasks.new do |gem|
|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
|
17
|
+
gem.name = "txcatcher"
|
|
18
|
+
gem.homepage = "http://github.com/snitko/txcatcher"
|
|
19
|
+
gem.license = "MIT"
|
|
20
|
+
gem.summary = %Q{An lightweight version of Bitpay's Insight, allows to check Bitcoin/Litecoin addresses}
|
|
21
|
+
gem.description = %Q{Ccurrently, the only job of this gem is to collect all new Bitcoin/Litecoin transactions, store them in a DB, index addresses.}
|
|
22
|
+
gem.email = "roman.snitko@gmail.com"
|
|
23
|
+
gem.authors = ["Roman Snitko"]
|
|
24
|
+
# dependencies defined in Gemfile
|
|
25
|
+
end
|
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
|
27
|
+
|
|
28
|
+
Dir.glob('lib/tasks/*.rake').each { |r| load r }
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|
data/bin/txcatcher
ADDED
data/db/schema.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
change do
|
|
3
|
+
create_table(:addresses, :ignore_index_errors=>true) do
|
|
4
|
+
primary_key :id
|
|
5
|
+
String :address, :size=>255
|
|
6
|
+
Integer :received, :default=>0
|
|
7
|
+
|
|
8
|
+
index [:address]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
create_table(:deposits, :ignore_index_errors=>true) do
|
|
12
|
+
primary_key :id
|
|
13
|
+
Integer :amount, :null=>false
|
|
14
|
+
Integer :transaction_id
|
|
15
|
+
Integer :address_id
|
|
16
|
+
|
|
17
|
+
index [:address_id]
|
|
18
|
+
index [:transaction_id]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
create_table(:schema_info) do
|
|
22
|
+
Integer :version, :default=>0, :null=>false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
create_table(:transactions, :ignore_index_errors=>true) do
|
|
26
|
+
primary_key :id
|
|
27
|
+
String :txid, :size=>255
|
|
28
|
+
String :hex, :text=>true
|
|
29
|
+
Integer :amount
|
|
30
|
+
Integer :block_height
|
|
31
|
+
|
|
32
|
+
index [:txid]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/tasks/db.rake
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'sequel'
|
|
5
|
+
require_relative '../txcatcher/utils/hash_string_to_sym_keys'
|
|
6
|
+
require_relative '../txcatcher/config'
|
|
7
|
+
require_relative '../txcatcher/initializer'
|
|
8
|
+
|
|
9
|
+
Sequel.extension :migration
|
|
10
|
+
|
|
11
|
+
namespace :db do
|
|
12
|
+
|
|
13
|
+
task :environment do
|
|
14
|
+
include TxCatcher::Initializer
|
|
15
|
+
ConfigFile.set!
|
|
16
|
+
create_config_files
|
|
17
|
+
read_config_file
|
|
18
|
+
connect_to_db
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
desc "Migrates the database"
|
|
22
|
+
task :migrate, [:step] => :environment do |t, args|
|
|
23
|
+
target = args[:step] && (step = args[:step].to_i) > 0 ?
|
|
24
|
+
current_migration_version + step : nil
|
|
25
|
+
|
|
26
|
+
Sequel::Migrator.run(TxCatcher.db_connection, MIGRATIONS_ROOT, target: target)
|
|
27
|
+
puts "Migrated DB to version #{current_migration_version}"
|
|
28
|
+
dump_schema
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc "Rollbacks database migrations"
|
|
32
|
+
task :rollback, [:step] => :environment do |t, args|
|
|
33
|
+
target = args[:step] && (step = args[:step].to_i) > 0 ?
|
|
34
|
+
current_migration_version - step : current_migration_version - 1
|
|
35
|
+
|
|
36
|
+
Sequel::Migrator.run(TxCatcher.db_connection, MIGRATIONS_ROOT, target: target)
|
|
37
|
+
puts "Rolled back DB to version #{current_migration_version}"
|
|
38
|
+
dump_schema
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def current_migration_version
|
|
42
|
+
db = TxCatcher.db_connection
|
|
43
|
+
Sequel::Migrator.migrator_class(MIGRATIONS_ROOT).new(db, MIGRATIONS_ROOT, {}).current
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def dump_schema
|
|
47
|
+
TxCatcher.db_connection.extension :schema_dumper
|
|
48
|
+
open('db/schema.rb', 'w') do |f|
|
|
49
|
+
f.puts TxCatcher.db_connection.dump_schema_migration(same_db: false)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Credits for this file go to
|
|
2
|
+
# Mikica Ivosevic https://github.com/mikicaivosevic/bitcoin-rpc-ruby/
|
|
3
|
+
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
class BitcoinRPC
|
|
9
|
+
def initialize(service_url)
|
|
10
|
+
@uri = URI.parse(service_url)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def method_missing(name, *args)
|
|
14
|
+
post_body = { 'method' => name, 'params' => args, 'id' => 'jsonrpc' }.to_json
|
|
15
|
+
resp = JSON.parse( http_post_request(post_body) )
|
|
16
|
+
raise JSONRPCError, resp['error'].to_s + " (method: '#{name}')" if resp['error']
|
|
17
|
+
resp['result']
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def http_post_request(post_body)
|
|
21
|
+
http = Net::HTTP.new(@uri.host, @uri.port)
|
|
22
|
+
request = Net::HTTP::Post.new(@uri.request_uri)
|
|
23
|
+
request.basic_auth @uri.user, @uri.password
|
|
24
|
+
request.content_type = 'application/json'
|
|
25
|
+
request.body = post_body
|
|
26
|
+
http.request(request).body
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class JSONRPCError < RuntimeError; end
|
|
30
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module TxCatcher
|
|
2
|
+
|
|
3
|
+
class Catcher
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :sockets
|
|
6
|
+
|
|
7
|
+
def initialize(name:, socket: "ipc:///tmp/")
|
|
8
|
+
@queue = {}
|
|
9
|
+
@sockets = {}
|
|
10
|
+
|
|
11
|
+
{'rawtx' => "#{socket}#{name}.rawtx", 'hashblock' => "#{socket}#{name}.hashblock"}.each do |channel, address|
|
|
12
|
+
puts "Start listening on #{name} #{channel}... (#{address})"
|
|
13
|
+
listen_to_zeromq_message(channel: channel, address: address)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def hexlify(s)
|
|
20
|
+
a = []
|
|
21
|
+
s.each_byte do |b|
|
|
22
|
+
a << sprintf('%02X', b)
|
|
23
|
+
end
|
|
24
|
+
a.join
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def listen_to_zeromq_message(channel:, address:)
|
|
28
|
+
@queue[channel] = Queue.new
|
|
29
|
+
|
|
30
|
+
# This thread is responsible for actions after the messages from ZeroMQ is parsed,
|
|
31
|
+
# typically it's writing data to DB through the models. We start it
|
|
32
|
+
# before we start listening to any messages from ZeroMQ.
|
|
33
|
+
queue_thread = Thread.new do
|
|
34
|
+
loop do
|
|
35
|
+
puts "in #{channel} queue: #{@queue[channel].size}"
|
|
36
|
+
if @queue[channel].empty?
|
|
37
|
+
sleep 1
|
|
38
|
+
else
|
|
39
|
+
begin
|
|
40
|
+
@queue[channel].pop.call
|
|
41
|
+
rescue Sequel::ValidationFailed => e
|
|
42
|
+
$stdout.puts "[WARNING #{Time.now.to_s}] #{e.class} #{e.to_s}\n"
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
File.open(TxCatcher::Config.config_dir + "/error.log", "a") do |f|
|
|
45
|
+
f.puts "[ERROR #{Time.now.to_s}] #{e.class} #{e.to_s}\n #{e.backtrace.join("\n ")}\n\n"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Now we can start receiving messages from ZeroMQ.
|
|
53
|
+
# On every received message we call a handler method, which parses it
|
|
54
|
+
# appropriately (each ZeroMQ channel has its own handler method) and then
|
|
55
|
+
# adds additional tasks, such as writing to the DB, in the queue.
|
|
56
|
+
# They queue itself is handled in the thread created above.
|
|
57
|
+
key = "#{channel}#{address}"
|
|
58
|
+
handler_thread = Thread.new do
|
|
59
|
+
context = ZMQ::Context.new
|
|
60
|
+
socket = context.socket(ZMQ::SUB)
|
|
61
|
+
socket.setsockopt(ZMQ::SUBSCRIBE, channel)
|
|
62
|
+
socket.connect(address)
|
|
63
|
+
@sockets[key] = { object: socket }
|
|
64
|
+
loop do
|
|
65
|
+
topic = []
|
|
66
|
+
message = []
|
|
67
|
+
socket.recv_multipart(topic, message)
|
|
68
|
+
message
|
|
69
|
+
if message[1]
|
|
70
|
+
message_hex = hexlify(message[1].copy_out_string).downcase
|
|
71
|
+
@sockets[key][:last_message] = message_hex
|
|
72
|
+
send("handle_#{channel}", message_hex)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end # listen_to_zeromq_message
|
|
78
|
+
|
|
79
|
+
def handle_rawtx(txhex)
|
|
80
|
+
$stdout.print "received tx hex: #{txhex[0..50]}...\n"
|
|
81
|
+
|
|
82
|
+
@queue["rawtx"] << ( Proc.new {
|
|
83
|
+
tx = TxCatcher::Transaction.new(hex: txhex)
|
|
84
|
+
tx.save
|
|
85
|
+
$stdout.puts "tx #{tx.txid} saved (id: #{tx.id}), deposits (outputs):"
|
|
86
|
+
tx.deposits.each do |d|
|
|
87
|
+
$stdout.puts " id: #{d.id}, addr: #{d.address.address}, amount: #{Satoshi.new(d.amount, from_unit: :satoshi).to_btc}"
|
|
88
|
+
end
|
|
89
|
+
})
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def handle_hashblock(block_hex)
|
|
93
|
+
block_hash = TxCatcher.rpc_node.getblock(block_hex)
|
|
94
|
+
transactions = block_hash["tx"]
|
|
95
|
+
height = TxCatcher.current_block_height = block_hash["height"].to_i
|
|
96
|
+
$stdout.puts "Block #{height} mined, transactions received:\n #{transactions.join(" \n")}"
|
|
97
|
+
@queue["hashblock"] << ( Proc.new {
|
|
98
|
+
Transaction.where(txid: transactions).update(block_height: height)
|
|
99
|
+
})
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end # class Catcher
|
|
103
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
|
|
3
|
+
module TxCatcher
|
|
4
|
+
|
|
5
|
+
# Cleans DB so that its size doesn't go above Config.max_db_transactions_stored
|
|
6
|
+
class Cleaner
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
|
|
10
|
+
def stopped?
|
|
11
|
+
@@stopped
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start(run_once: false)
|
|
15
|
+
@@stopped = false
|
|
16
|
+
@@stop = false
|
|
17
|
+
Thread.new do
|
|
18
|
+
loop do
|
|
19
|
+
@@cleaning_counter = { transactions: 0, deposits: 0, addresses: 0 }
|
|
20
|
+
db_tx_count = TxCatcher::Transaction.last.id - TxCatcher::Transaction.first.id + 1
|
|
21
|
+
$stdout.print "Cleaning transactions in DB..."
|
|
22
|
+
$stdout.print "#{db_tx_count} now, needs to be below #{Config.max_db_transactions_stored}\n"
|
|
23
|
+
if db_tx_count > Config.max_db_transactions_stored
|
|
24
|
+
number_to_delete = (db_tx_count - Config.max_db_transactions_stored) + (Config.max_db_transactions_stored*0.1).round
|
|
25
|
+
clean_transactions(number_to_delete)
|
|
26
|
+
$stdout.puts "DB cleaned: #{@@cleaning_counter.to_s}"
|
|
27
|
+
else
|
|
28
|
+
$stdout.puts "Nothing to be cleaned from DB, size is below threshold."
|
|
29
|
+
end
|
|
30
|
+
if @stop || run_once
|
|
31
|
+
@@stopped = true
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
sleep Config.db_clean_period_seconds
|
|
35
|
+
end # loop
|
|
36
|
+
end # Thread
|
|
37
|
+
@@stopped
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def stop
|
|
41
|
+
@@stop = true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def clean_transactions(n)
|
|
45
|
+
transactions = Transaction.order("created_at ASC").limit(n).each do |t|
|
|
46
|
+
clean_deposits(t)
|
|
47
|
+
t.delete
|
|
48
|
+
@@cleaning_counter[:transactions] += 1
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def clean_deposits(transaction)
|
|
53
|
+
transaction.deposits.each do |d|
|
|
54
|
+
if d.address && d.address.deposits.count == 1
|
|
55
|
+
d.address.delete
|
|
56
|
+
@@cleaning_counter[:addresses] += 1
|
|
57
|
+
end
|
|
58
|
+
d.delete
|
|
59
|
+
@@cleaning_counter[:deposits] += 1
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end # class << self
|
|
64
|
+
end # Cleaner
|
|
65
|
+
end # TxCatcher
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
|
|
3
|
+
module TxCatcher
|
|
4
|
+
|
|
5
|
+
class << (Config = OpenStruct.new)
|
|
6
|
+
def [](key_chain)
|
|
7
|
+
key_chain = key_chain.to_s.split('.')
|
|
8
|
+
config = self.public_send(key_chain.shift)
|
|
9
|
+
key_chain.each do |key|
|
|
10
|
+
if config.kind_of?(Hash)
|
|
11
|
+
config = config[key] || config[key.to_sym]
|
|
12
|
+
else
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
config
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|