utxoracle 0.0.2 → 0.1.0

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: 966c4c18a2b785a539acbfd09a9b491b24d5e83b6b606e5db05f4f3997c3f13c
4
- data.tar.gz: 6089c1e572294e3b0a2a0c5f1e842f5b52449d78001522fe1f5c6c3db1c3382e
3
+ metadata.gz: 27c98fbe262713835893d7590648c0e566ac35cf837d5c1b6514961b54c99324
4
+ data.tar.gz: 8cf05e364850de7f330a4c833640d58c69b8a0c048aaec4fbaa8f4dbe54b529c
5
5
  SHA512:
6
- metadata.gz: 9cbde112559f59680e49d3fbef85dee93660b2ca03f3c8c73c45d07565ec6f90871e00de939ff530ce0a63f53e8fad3ef0c0b8e338e60d05aae9628fb72af971
7
- data.tar.gz: 592eaf022a3bdfd81856cb9dfe1a6ee822daaf71d43bd824f7dd39e88d6b192cf17bb6d7d42f1a27368de5417ff8bbbbaa33c0a1e908e5b492427ae94a91a709
6
+ metadata.gz: 932541e0474fbdbe9515629cfa7f4f4b35a1a04b4f993fc21542b063f46f1273bf66b77cc91c457301af2f81fe95bd782ec0c31bd16d8060b4509f870e1806a6
7
+ data.tar.gz: dcabd03dcf97198240167c5ad9449345340c85f49b39f31e80a77cab3ba4a5c68f02736a8d09c988246af34ea2a35238795a3a7b4b3cd4bcdad38ef4a36310e8
data/.gitignore CHANGED
@@ -57,3 +57,4 @@ build-iPhoneSimulator/
57
57
 
58
58
  /Gemfile.lock
59
59
  .rspec_status
60
+ .DS_Store
data/README.md CHANGED
@@ -44,29 +44,58 @@ All examples below assume that the gem has been required.
44
44
  require 'utxoracle'
45
45
  ```
46
46
 
47
-
48
47
  ### Fetching price
49
48
 
50
49
  #### Using a specific bitcoin node
50
+
51
+ `bitcoin.conf` would look like:
52
+ ```txt
53
+ server=1
54
+ rpcuser=aUser
55
+ rpcpassword=aPassword
56
+ ```
51
57
  ```ruby
52
58
  provider = Utxoracle::Node.new("aUser", "aPassword", "127.0.0.1", 8332)
59
+ oracle = Utxoracle::Oracle.new(provider, log = false)
60
+ oracle.price("2023-10-30")
61
+ 34840
53
62
  ```
54
63
 
55
64
  #### Using mempool.space node
56
- ```ruby
57
- provider = Utxoracle::Mempool.new
58
- ```
59
65
 
60
- ####
61
66
  ```ruby
62
- oracle = Utxoracle::Oracle.new(provider)
67
+ # Mempool will throttle you without an enterprise license
68
+ provider = Utxoracle::Mempool.new
69
+ oracle = Utxoracle::Oracle.new(provider, log = false)
63
70
  oracle.price("2023-10-30")
64
71
  34840
65
72
  ```
66
73
 
74
+ ### Command line usage
75
+ ```bash
76
+ $ ./bin/run aUser aPassword 127.0.0.1 8332 2023-10-10
77
+ Reading all blocks on 2023-10-12 00:00:00 -0400...
78
+ This will take a few minutes (~144 blocks)...
79
+ Height Time(utc) Completion %
80
+ 811769 00:18:31 1.25
81
+ 811770 00:22:48 1.53
82
+ 811771 00:24:24 1.67
83
+ 811772 00:33:32 2.29
84
+ 811773 00:38:14 2.64
85
+ 811774 01:06:40 4.58
86
+ 811775 01:09:07 4.79
87
+ ...
88
+ 811931 23:41:09 98.68
89
+ 811932 23:44:01 98.89
90
+ 811933 23:48:40 99.17
91
+ blocks_on_this_day: 165
92
+ Price Estimate: $27,045
93
+ ```
94
+
95
+
67
96
  ## Development
68
97
 
69
- After checking out the repo, run `bundle i` to install dependencies.
98
+ After checking out the repo, run `bundle install` to install dependencies.
70
99
 
71
100
  To install this gem onto your local machine, run `bundle exec rake install`.
72
101
 
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This is a copy of bin/run but with timing code.
5
+ # Usage:
6
+ #
7
+ # Basic wall clock measurement:
8
+ # $ ./bin/benchmarks/run aUser aPassword 127.0.0.1 8332 2023-10-01
9
+ #
10
+ # With StackProf measurement:
11
+ # ./bin/benchmarks/run aUser aPassword 127.0.0.1 8332 2023-10-01 true
12
+
13
+ require 'bundler/setup'
14
+ require 'utxoracle'
15
+ require 'benchmark'
16
+
17
+ user = ARGV[0]
18
+ password = ARGV[1]
19
+ ip = ARGV[2]
20
+ port = ARGV[3]
21
+ date = ARGV[4]
22
+ profile = ARGV[5]
23
+
24
+ if profile
25
+ require 'stackprof'
26
+ # See https://github.com/tmm1/stackprof#sampling: for :cpu, :wall, :object
27
+ StackProf.run(mode: :wall, out: 'tmp/stackprof-wall.dump', raw: true) do
28
+ provider = Utxoracle::Node.new(user, password, ip, port)
29
+ oracle = Utxoracle::Oracle.new(provider, log = false)
30
+ puts oracle.price(date)
31
+ end
32
+ else
33
+ time = Benchmark.measure do
34
+ provider = Utxoracle::Node.new(user, password, ip, port)
35
+ oracle = Utxoracle::Oracle.new(provider, log = false)
36
+ puts oracle.price(date)
37
+ end
38
+ puts time
39
+ end
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This is a copy of bin/run_range but with timing code.
5
+ # Usage:
6
+ #
7
+ # ./bin/benchmarks/run_range aUser aPassword 127.0.0.1 8332 2023-10-01 2023-10-05
8
+ #
9
+ # Note: Too long of a range will crash the program.
10
+ # Bitcoin Core has limits:
11
+ # https://github.com/bitcoin/bitcoin/blob/c0ddd32bf629bb48426b0651def497ca1a78e6b1/src/httpserver.h#L12-L14
12
+ # for threads, queue and timeout.
13
+ #
14
+ # With default settings, 20 days works fine.
15
+ #
16
+ # To run for a full month, I've found the following settings to work fine:
17
+ #
18
+ # rpcworkqueue=32
19
+ # rpcthreads=16
20
+ # rpctimeout=60
21
+ # Database cache 1024 MiB
22
+
23
+ require 'bundler/setup'
24
+ require 'utxoracle'
25
+ require 'benchmark'
26
+
27
+ user = ARGV[0]
28
+ password = ARGV[1]
29
+ ip = ARGV[2]
30
+ port = ARGV[3]
31
+ start_date = ARGV[4]
32
+ end_date = ARGV[5]
33
+
34
+ time = Benchmark.measure do
35
+ provider = Utxoracle::Node.new(user, password, ip, port)
36
+ batch_oracle = Utxoracle::BatchOracle.new(provider)
37
+ batch_oracle.prices(start_date, end_date)
38
+ end
39
+
40
+ puts time.real
data/bin/run CHANGED
@@ -4,12 +4,12 @@
4
4
  require 'bundler/setup'
5
5
  require 'utxoracle'
6
6
 
7
- user = ARGV[0]
8
- password = ARGV[1]
9
- ip = ARGV[2]
10
- port = ARGV[3]
11
- date = ARGV[4]
7
+ user = ARGV[0]
8
+ password = ARGV[1]
9
+ ip = ARGV[2]
10
+ port = ARGV[3]
11
+ date = ARGV[4]
12
12
 
13
13
  provider = Utxoracle::Node.new(user, password, ip, port)
14
- oracle = Utxoracle::Oracle.new(provider, true)
14
+ oracle = Utxoracle::Oracle.new(provider, log = true)
15
15
  oracle.price(date)
data/bin/run_range ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'utxoracle'
6
+
7
+ user = ARGV[0]
8
+ password = ARGV[1]
9
+ ip = ARGV[2]
10
+ port = ARGV[3]
11
+ start_date = ARGV[4]
12
+ end_date = ARGV[5]
13
+
14
+ provider = Utxoracle::Node.new(user, password, ip, port)
15
+ batch_oracle = Utxoracle::BatchOracle.new(provider)
16
+ batch_oracle.prices(start_date, end_date)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Utxoracle
4
+ class BatchOracle
5
+ def initialize(provider, log = false)
6
+ @provider = provider
7
+ @log = log
8
+ end
9
+
10
+ def prices(start_date, end_date)
11
+ formatted_start_date = Time.parse start_date.tr('\n', '')
12
+ formatted_end_date = Time.parse end_date.tr('\n', '')
13
+ ret = {}
14
+
15
+ date = formatted_start_date
16
+ threads = []
17
+
18
+ while date < formatted_end_date
19
+ # Bind `date` to thread scope: https://ruby-doc.org/core-2.0.0/Thread.html#method-c-new
20
+ threads << Thread.new(date) do |t_date|
21
+ oracle = Utxoracle::Oracle.new(@provider, log = @log)
22
+ date_str = t_date.to_s[0..9]
23
+ ret[date_str] = oracle.price(date_str)
24
+ end
25
+
26
+ date += Utxoracle::Oracle::SECONDS_PER_DAY
27
+ end
28
+ threads.each(&:join)
29
+
30
+ puts ret
31
+ ret
32
+ end
33
+ end
34
+ end
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
- require_relative 'rpc'
3
4
 
4
5
  module Utxoracle
5
6
  class Oracle
6
- SECONDS_IN_A_DAY = 60 * 60 * 24
7
+ SECONDS_PER_DAY = 86_400
7
8
  FOUR_HOURS = 14_400
8
9
  MAINNET_PORT = 8332
9
10
  TESTNET_PORT = 18_332
@@ -25,7 +26,7 @@ module Utxoracle
25
26
  end
26
27
 
27
28
  if price_estimate = @cache[requested_date]
28
- puts "price_estimate is #{price_estimate}" if @log
29
+ puts "Price Estimate: $#{price_estimate.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}" if @log
29
30
  return price_estimate
30
31
  end
31
32
 
@@ -52,7 +53,7 @@ module Utxoracle
52
53
  latest_utc_midnight = Time.new(latest_year, latest_month, latest_day).utc
53
54
 
54
55
  latest_time_in_seconds = blockheader['time']
55
- yesterday_seconds = latest_time_in_seconds - SECONDS_IN_A_DAY
56
+ yesterday_seconds = latest_time_in_seconds - SECONDS_PER_DAY
56
57
  latest_price_day = Time.at(yesterday_seconds).utc
57
58
  latest_price_date = latest_price_day.utc.strftime('%Y-%m-%d')
58
59
 
@@ -60,7 +61,7 @@ module Utxoracle
60
61
  price_day_date_utc = @requested_date
61
62
 
62
63
  seconds_since_price_day = latest_time_in_seconds - price_day_seconds
63
- blocks_ago_estimate = (144 * seconds_since_price_day.to_f / SECONDS_IN_A_DAY.to_f).round
64
+ blocks_ago_estimate = (144 * seconds_since_price_day.to_f / SECONDS_PER_DAY.to_f).round
64
65
  price_day_block_estimate = (block_count - blocks_ago_estimate).to_i
65
66
 
66
67
  block_hash_b = @provider.getblockhash(price_day_block_estimate)
@@ -68,7 +69,7 @@ module Utxoracle
68
69
  time_in_seconds = block_header['time']
69
70
 
70
71
  seconds_difference = time_in_seconds - price_day_seconds
71
- block_jump_estimate = (144.0 * seconds_difference / SECONDS_IN_A_DAY).round
72
+ block_jump_estimate = (144.0 * seconds_difference / SECONDS_PER_DAY).round
72
73
 
73
74
  last_estimate = 0
74
75
  last_last_estimate = 0
@@ -82,7 +83,7 @@ module Utxoracle
82
83
 
83
84
  time_in_seconds = block_header['time']
84
85
  seconds_difference = time_in_seconds - price_day_seconds
85
- block_jump_estimate = (144.0 * seconds_difference / SECONDS_IN_A_DAY).round
86
+ block_jump_estimate = (144.0 * seconds_difference / SECONDS_PER_DAY).round
86
87
  end
87
88
 
88
89
  if time_in_seconds > price_day_seconds
@@ -261,7 +262,7 @@ module Utxoracle
261
262
  w2 = a2 / (a1 + a2)
262
263
  price_estimate = (w1 * btc_in_usd_best + w2 * btc_in_usd_2nd).to_i
263
264
 
264
- puts "price_estimate is #{price_estimate}" if @log
265
+ puts "Price Estimate: $#{price_estimate.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}" if @log
265
266
 
266
267
  price_estimate
267
268
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Utxoracle
2
4
  class Provider
3
5
  def init
@@ -1,10 +1,10 @@
1
1
  require_relative '../provider'
2
- require_relative '../rpc'
2
+ require 'cleanrpc'
3
3
 
4
4
  module Utxoracle
5
5
  class Node < Provider
6
6
  def initialize(rpcuser, rpcpassword, ip, port)
7
- @rpc = Rpc.new("http://#{rpcuser}:#{rpcpassword}@#{ip}:#{port}")
7
+ @rpc = Cleanrpc::Rpc.new("http://#{rpcuser}:#{rpcpassword}@#{ip}:#{port}")
8
8
  end
9
9
 
10
10
  def getblockcount
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Utxoracle
4
- VERSION = '0.0.2'
4
+ VERSION = '0.1.0'
5
5
  end
data/lib/utxoracle.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'utxoracle/rpc'
4
3
  require_relative 'utxoracle/version'
4
+ require_relative 'utxoracle/batch_oracle'
5
5
  require_relative 'utxoracle/oracle'
6
6
  require_relative 'utxoracle/providers/mempool'
7
7
  require_relative 'utxoracle/providers/node'
data/utxoracle.gemspec CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.bindir = 'bin'
22
22
  spec.require_path = 'lib'
23
23
 
24
+ spec.add_dependency 'cleanrpc', '~> 0.0.2'
24
25
  spec.add_dependency 'typhoeus', '~> 1.4.0'
25
26
 
26
27
  spec.add_development_dependency 'pry'
@@ -30,4 +31,5 @@ Gem::Specification.new do |spec|
30
31
  spec.add_development_dependency 'rubocop-rspec'
31
32
  spec.add_development_dependency 'simplecov'
32
33
  spec.add_development_dependency 'simplecov-console'
34
+ spec.add_development_dependency 'stackprof'
33
35
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: utxoracle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Gardner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-03 00:00:00.000000000 Z
11
+ date: 2023-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cleanrpc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.2
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: typhoeus
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +136,20 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: stackprof
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
125
153
  description: Offline price oracle for Bitcoin. Offers programmable data providers
126
154
  (Bitcoin node, mempool, etc). Can be used from commandline, or integrated into existing
127
155
  ruby stacks.
@@ -136,19 +164,21 @@ files:
136
164
  - LICENSE
137
165
  - README.md
138
166
  - Rakefile
167
+ - bin/benchmarks/run
168
+ - bin/benchmarks/run_range
139
169
  - bin/console
140
170
  - bin/run
171
+ - bin/run_range
141
172
  - lib/utxoracle.rb
173
+ - lib/utxoracle/batch_oracle.rb
142
174
  - lib/utxoracle/oracle.rb
143
175
  - lib/utxoracle/provider.rb
144
176
  - lib/utxoracle/providers/mempool.rb
145
177
  - lib/utxoracle/providers/node.rb
146
178
  - lib/utxoracle/request.rb
147
- - lib/utxoracle/rpc.rb
148
179
  - lib/utxoracle/version.rb
149
180
  - spec/spec_helper.rb
150
181
  - spec/utxoracle/oracle_spec.rb
151
- - spec/utxoracle/rpc_spec.rb
152
182
  - spec/utxoracle_spec.rb
153
183
  - utxoracle.gemspec
154
184
  homepage: https://github.com/Carolina-Bitcoin-Project/UTXOracle
data/lib/utxoracle/rpc.rb DELETED
@@ -1,30 +0,0 @@
1
- require 'net/http'
2
- require 'uri'
3
- require 'json'
4
-
5
- module Utxoracle
6
- class Rpc
7
- def initialize(service_url)
8
- @uri = URI.parse(service_url)
9
- end
10
-
11
- def method_missing(name, *args)
12
- post_body = { 'method' => name, 'params' => args, 'id' => 'jsonrpc' }.to_json
13
- resp = JSON.parse(http_post_request(post_body))
14
- raise JSONRPCError, resp['error'] if resp['error']
15
-
16
- resp['result']
17
- end
18
-
19
- def http_post_request(post_body)
20
- http = Net::HTTP.new(@uri.host, @uri.port)
21
- request = Net::HTTP::Post.new(@uri.request_uri)
22
- request.basic_auth @uri.user, @uri.password
23
- request.content_type = 'application/json'
24
- request.body = post_body
25
- http.request(request).body
26
- end
27
-
28
- class JSONRPCError < RuntimeError; end
29
- end
30
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe Utxoracle::Rpc do
6
- let(:rpc) do
7
- described_class.new('http://foo:bar@127.0.0.1:8332')
8
- end
9
-
10
- describe '.new' do
11
- it 'creates an instance of Rpc with given uri' do
12
- expect(rpc.class).to eq(Utxoracle::Rpc)
13
- end
14
- end
15
-
16
- it 'exposes http request interface' do
17
- allow(rpc).to receive(:http_post_request).and_return(true)
18
- expect(rpc.http_post_request('')).to eq true
19
- end
20
-
21
- it 'forwards methods over http' do
22
- allow(rpc).to receive(:http_post_request).and_return(
23
- "{\"result\":814521,\"error\":null,\"id\":\"jsonrpc\"}\n"
24
- )
25
- expect(rpc.getblockcount).to eq 814_521
26
- end
27
-
28
- it 'returns error from http endpoint when indicated' do
29
- expect(rpc).to receive(:http_post_request).and_return(
30
- "{\"result\":814521,\"error\":\"test error\",\"id\":\"jsonrpc\"}\n"
31
- )
32
- expect { rpc.test_rpc_call }.to raise_error(Utxoracle::Rpc::JSONRPCError)
33
- end
34
- end