utxoracle 0.0.2 → 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 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