utxoracle 0.0.1 → 0.0.3

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: e06742e7448c182f3f2399e200c50e0563f3d8672433d942028cbc7c1827f60b
4
- data.tar.gz: 33f2c9d1d7e496664c84a682e3b0d09a0971f152f7f351b59d7cdeb0baa48bba
3
+ metadata.gz: 2ac6ef650a940eb64d180a28f6d556acca9310a42d199fa4bceb59e909cb6b2e
4
+ data.tar.gz: '0792d9c0e1867d62fcc507a0574c9338577b8468399d26b34124092525dddd8d'
5
5
  SHA512:
6
- metadata.gz: b64d8799da84d9ee041e61548adcf889067bd0bcef7b5331d59e43aca4bec7a4075d6bdd0773939132d9b8dec4b428e60a05be9bf9b720ecfec74b40b805e74f
7
- data.tar.gz: caeb20a9d134903c29cccfd3bd03697a1bcf5b56452762ea383850b9423a4bc056d7ac77e4d18ed6f455d7765e21935421172ccd2ab3ca733298545792a03628
6
+ metadata.gz: 528c646c4201da999ca64ef7ea2ddd723a27e4663d7a253a0bc58472af5efc44d17ae28c4482b0bb271c7d87ed4269c165aa7355c6f0364b49cbdf1672b7e4e6
7
+ data.tar.gz: a34764d738c7fc67d09d57a9bf142642c2087233fe0fbb9110327ad2153b8d5c737924fab4cb120386f798b4ad4f819102d19f809a41b59b213797f7396c414d
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
@@ -1,7 +1,17 @@
1
- # UTXOracle.rb
2
- Ruby implementation of https://utxo.live/oracle/.
1
+ [![Gem Version](https://badge.fury.io/rb/utxoracle.svg)](https://badge.fury.io/rb/utxoracle)
2
+
3
+ # Utxoracle
4
+ Offline price oracle for Bitcoin written in Ruby.
5
+
6
+ In September 2023 [SteveSimple](https://twitter.com/SteveSimple) & [DanielLHinton](https://twitter.com/DanielLHinton) [dropped](https://twitter.com/SteveSimple/status/1704864674431332503) https://utxo.live/oracle/, the first method to derive historical USD price of bitcoin based purely on UTXO set.
7
+
8
+ Needless to say, a lot of bitcoiners found this VERY cool. We're in a unique phase of history; transitioning from the Dollar Network to a Bitcoin Standard. Pulling out these patterns sparks joy.
9
+
10
+ The purpose of releasing Utxoracle as a Gem is multi-fold:
11
+ 1. Make the tool available as a [Gem](https://rubygems.org/gems/utxoracle) to the Ruby community.
12
+ 2. Provide a flexible API, where folks can extend this for other use cases (currencies, language, etc).
13
+ 3. Provide a _modular_ provider model, so folks can pull data from mempool.space or other RPCs.
3
14
 
4
- (Original implementation by [SteveSimple](https://twitter.com/SteveSimple) & [DanielLHinton](https://twitter.com/DanielLHinton) who discovered and built the first offline bitcoin price oracle.)
5
15
 
6
16
  ## Table of contents
7
17
 
@@ -34,29 +44,58 @@ All examples below assume that the gem has been required.
34
44
  require 'utxoracle'
35
45
  ```
36
46
 
37
-
38
47
  ### Fetching price
39
48
 
40
- #### Using a raw bitcoin node
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
+ ```
41
57
  ```ruby
42
- provider = Utxoracle::RawBitcoinNode.new("aUser", "aPassword", "127.0.0.1", 8332)
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
43
62
  ```
44
63
 
45
64
  #### Using mempool.space node
46
- ```ruby
47
- provider = Utxoracle::MempoolDotSpace.new
48
- ```
49
65
 
50
- ####
51
66
  ```ruby
52
- 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)
53
70
  oracle.price("2023-10-30")
54
71
  34840
55
72
  ```
56
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
+
57
96
  ## Development
58
97
 
59
- After checking out the repo, run `bundle i` to install dependencies.
98
+ After checking out the repo, run `bundle install` to install dependencies.
60
99
 
61
100
  To install this gem onto your local machine, run `bundle exec rake install`.
62
101
 
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
  require 'rubocop/rake_task'
3
3
  require 'rspec/core/rake_task'
4
4
 
data/bin/run ADDED
@@ -0,0 +1,27 @@
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
+ date = ARGV[4]
12
+ profile = ARGV[5]
13
+
14
+
15
+ if profile
16
+ require 'stackprof'
17
+ # See https://github.com/tmm1/stackprof#sampling: for :cpu, :wall, :object
18
+ StackProf.run(mode: :wall, out: 'tmp/stackprof-wall.dump', raw: true) do
19
+ provider = Utxoracle::Node.new(user, password, ip, port)
20
+ oracle = Utxoracle::Oracle.new(provider, log = true)
21
+ oracle.price(date)
22
+ end
23
+ else
24
+ provider = Utxoracle::Node.new(user, password, ip, port)
25
+ oracle = Utxoracle::Oracle.new(provider, log = true)
26
+ oracle.price(date)
27
+ end
@@ -1,6 +1,4 @@
1
1
  require 'time'
2
- require_relative 'rpc'
3
- require_relative 'providers/raw_bitcoin_node'
4
2
 
5
3
  module Utxoracle
6
4
  class Oracle
@@ -19,12 +17,14 @@ module Utxoracle
19
17
 
20
18
  def price(requested_date)
21
19
  unless validate_date(requested_date)
22
- puts "Invalid date.\nEarliest available: 2020-07-26.\nLatest available #{Date.today}.\nFormat: YYYY-MM-DD."
23
- return
20
+ if @log
21
+ puts "Invalid date.\nEarliest available: 2020-07-26.\nLatest available #{Date.today}.\nFormat: YYYY-MM-DD."
22
+ end
23
+ return -1
24
24
  end
25
25
 
26
26
  if price_estimate = @cache[requested_date]
27
- puts "price_estimate is #{price_estimate}"
27
+ puts "Price Estimate: $#{price_estimate.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}" if @log
28
28
  return price_estimate
29
29
  end
30
30
 
@@ -124,7 +124,7 @@ module Utxoracle
124
124
 
125
125
  puts("Reading all blocks on #{price_day_date_utc}...") if @log
126
126
  puts('This will take a few minutes (~144 blocks)...') if @log
127
- puts("Height\tTime(utc)\t Completion %") if @log
127
+ puts("Height\tTime(utc)\tCompletion %") if @log
128
128
 
129
129
  block_height = price_day_block_estimate
130
130
  block_hash_b = @provider.getblockhash(block_height)
@@ -143,8 +143,8 @@ module Utxoracle
143
143
  while target == day
144
144
  blocks_on_this_day += 1
145
145
 
146
- progress_estimate = 100.0 * (hour + minute / 60) / 24.0
147
- puts("#{block_height}\t#{time.strftime('%H:%M:%S')}\t#{progress_estimate}") if @log
146
+ progress_estimate = 100.0 * (hour + minute / 60.0) / 24.0
147
+ puts("#{block_height}\t#{time.strftime('%H:%M:%S')}\t#{progress_estimate.round(2)}") if @log
148
148
 
149
149
  for tx in block_b['tx']
150
150
  outputs = tx['vout']
@@ -260,7 +260,7 @@ module Utxoracle
260
260
  w2 = a2 / (a1 + a2)
261
261
  price_estimate = (w1 * btc_in_usd_best + w2 * btc_in_usd_2nd).to_i
262
262
 
263
- puts "price_estimate is #{price_estimate}" if @log
263
+ puts "Price Estimate: $#{price_estimate.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}" if @log
264
264
 
265
265
  price_estimate
266
266
  end
@@ -347,6 +347,8 @@ module Utxoracle
347
347
  (Date.parse(date) < Date.today)
348
348
 
349
349
  valid_format && valid_range
350
+ rescue Date::Error
351
+ false
350
352
  end
351
353
  end
352
354
  end
@@ -5,7 +5,7 @@ require_relative '../request'
5
5
  # and throttles the number of requests the oracle takes to compute.
6
6
  # Running this requires an Enterprise license.
7
7
  module Utxoracle
8
- class MempoolDotSpace < Provider
8
+ class Mempool < Provider
9
9
  def initialize; end
10
10
 
11
11
  def getblockcount
@@ -1,10 +1,10 @@
1
1
  require_relative '../provider'
2
- require_relative '../rpc'
2
+ require 'cleanrpc'
3
3
 
4
4
  module Utxoracle
5
- class RawBitcoinNode < Provider
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.1'
4
+ VERSION = '0.0.3'
5
5
  end
data/lib/utxoracle.rb CHANGED
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'utxoracle/rpc'
4
3
  require_relative 'utxoracle/version'
5
4
  require_relative 'utxoracle/oracle'
6
- require_relative 'utxoracle/providers/mempool_dot_space'
7
- require_relative 'utxoracle/providers/raw_bitcoin_node'
5
+ require_relative 'utxoracle/providers/mempool'
6
+ require_relative 'utxoracle/providers/node'
8
7
 
9
8
  module Utxoracle
10
9
  end
@@ -2,20 +2,38 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- # RSpec.describe Utxoracle::Oracle do
6
- # let(:oracle) do
7
- # described_class.new('aUser', 'aPassword', '127.0.0.1', '8332')
8
- # end
9
- #
10
- # describe '.new' do
11
- # it 'initialized an Rpc instance' do
12
- # expect(Utxoracle::Rpc).to receive(:new)
13
- # described_class.new('aUser', 'aPassword', '127.0.0.1', '8332')
14
- # expect(oracle.class).to eq Utxoracle::Oracle
15
- # end
16
- # end
17
- #
18
- # # TODO: - test cache
19
- # # TODO - test provider interface
20
- # # TODO - test `price` return type
21
- # end
5
+ RSpec.describe Utxoracle::Oracle do
6
+ describe '.new' do
7
+ it 'requires provider and takes optional log config' do
8
+ provider = Utxoracle::Mempool
9
+ Utxoracle::Oracle.new(provider, log = true)
10
+ end
11
+ end
12
+
13
+ describe 'date validation' do
14
+ it 'returns -1 for garbage date inputs' do
15
+ provider = Utxoracle::Mempool
16
+ oracle = Utxoracle::Oracle.new(provider)
17
+ price = oracle.price('garbage')
18
+ expect(price).to be(-1)
19
+ end
20
+
21
+ it 'returns -1 for dates before 2020-07-26' do
22
+ provider = Utxoracle::Mempool
23
+ oracle = Utxoracle::Oracle.new(provider)
24
+ price = oracle.price('2015-01-01')
25
+ expect(price).to be(-1)
26
+ end
27
+
28
+ it 'returns -1 for dates in the future' do
29
+ provider = Utxoracle::Mempool
30
+ oracle = Utxoracle::Oracle.new(provider)
31
+ price = oracle.price('3000-01-01')
32
+ expect(price).to be(-1)
33
+ end
34
+ end
35
+
36
+ # TODO: - test cache
37
+ # TODO - test provider interface
38
+ # TODO - test `price` return type
39
+ end
data/utxoracle.gemspec CHANGED
@@ -11,17 +11,18 @@ Gem::Specification.new do |spec|
11
11
  spec.platform = Gem::Platform::RUBY
12
12
  spec.authors = ['Keith Gardner']
13
13
 
14
- spec.summary = 'Interface for UTXOracle.'
15
- spec.description = 'Object oriented design for interacting with UTXOracle.'
14
+ spec.summary = 'Offline price oracle for Bitcoin.'
15
+ spec.description = 'Offline price oracle for Bitcoin. Offers programmable data providers (Bitcoin node, mempool, etc). Can be used from commandline, or integrated into existing ruby stacks.'
16
16
  spec.homepage = 'https://github.com/Carolina-Bitcoin-Project/UTXOracle'
17
17
  spec.license = 'MIT'
18
18
  spec.required_ruby_version = '>= 3.1.0'
19
19
 
20
20
  spec.files = `git ls-files`.split("\n")
21
- spec.bindir = 'exe'
21
+ spec.bindir = 'bin'
22
22
  spec.require_path = 'lib'
23
23
 
24
- spec.add_dependency 'typhoeus'
24
+ spec.add_dependency 'typhoeus', '~> 1.4.0'
25
+ spec.add_dependency 'cleanrpc', '~> 0.0.2'
25
26
 
26
27
  spec.add_development_dependency 'pry'
27
28
  spec.add_development_dependency 'rake'
@@ -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,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: utxoracle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Gardner
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-01 00:00:00.000000000 Z
11
+ date: 2023-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 1.4.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 1.4.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: cleanrpc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.2
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pry
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -122,32 +136,45 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
- description: Object oriented design for interacting with UTXOracle.
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'
153
+ description: Offline price oracle for Bitcoin. Offers programmable data providers
154
+ (Bitcoin node, mempool, etc). Can be used from commandline, or integrated into existing
155
+ ruby stacks.
126
156
  email:
127
157
  executables: []
128
158
  extensions: []
129
159
  extra_rdoc_files: []
130
160
  files:
131
161
  - ".gitignore"
132
- - ".rspec"
133
- - ".rspec_status"
134
162
  - ".ruby-version"
135
163
  - Gemfile
136
164
  - LICENSE
137
165
  - README.md
138
166
  - Rakefile
139
167
  - bin/console
168
+ - bin/run
140
169
  - lib/utxoracle.rb
141
170
  - lib/utxoracle/oracle.rb
142
171
  - lib/utxoracle/provider.rb
143
- - lib/utxoracle/providers/mempool_dot_space.rb
144
- - lib/utxoracle/providers/raw_bitcoin_node.rb
172
+ - lib/utxoracle/providers/mempool.rb
173
+ - lib/utxoracle/providers/node.rb
145
174
  - lib/utxoracle/request.rb
146
- - lib/utxoracle/rpc.rb
147
175
  - lib/utxoracle/version.rb
148
176
  - spec/spec_helper.rb
149
177
  - spec/utxoracle/oracle_spec.rb
150
- - spec/utxoracle/rpc_spec.rb
151
178
  - spec/utxoracle_spec.rb
152
179
  - utxoracle.gemspec
153
180
  homepage: https://github.com/Carolina-Bitcoin-Project/UTXOracle
@@ -169,8 +196,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
196
  - !ruby/object:Gem::Version
170
197
  version: '0'
171
198
  requirements: []
172
- rubygems_version: 3.4.10
199
+ rubygems_version: 3.4.21
173
200
  signing_key:
174
201
  specification_version: 4
175
- summary: Interface for UTXOracle.
202
+ summary: Offline price oracle for Bitcoin.
176
203
  test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rspec_status DELETED
@@ -1,8 +0,0 @@
1
- example_id | status | run_time |
2
- -------------------------------------- | ------ | --------------- |
3
- ./spec/utxoracle/oracle_spec.rb[1:1:1] | failed | 0.00966 seconds |
4
- ./spec/utxoracle/rpc_spec.rb[1:1:1] | passed | 0.00025 seconds |
5
- ./spec/utxoracle/rpc_spec.rb[1:2] | passed | 0.01109 seconds |
6
- ./spec/utxoracle/rpc_spec.rb[1:3] | passed | 0.00028 seconds |
7
- ./spec/utxoracle/rpc_spec.rb[1:4] | passed | 0.00214 seconds |
8
- ./spec/utxoracle_spec.rb[1:1] | passed | 0.00127 seconds |
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,29 +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
- describe '.new' do
10
- it 'creates an instance of Rpc with given uri' do
11
- expect(rpc.class).to eq(Utxoracle::Rpc)
12
- end
13
- end
14
-
15
- it 'exposes http request interface' do
16
- allow(rpc).to receive(:http_post_request).and_return(true)
17
- expect(rpc.http_post_request('')).to eq true
18
- end
19
-
20
- it 'forwards methods over http' do
21
- allow(rpc).to receive(:http_post_request).and_return("{\"result\":814521,\"error\":null,\"id\":\"jsonrpc\"}\n")
22
- expect(rpc.getblockcount).to eq 814_521
23
- end
24
-
25
- it 'returns error from http endpoint when indicated' do
26
- expect(rpc).to receive(:http_post_request).and_return("{\"result\":814521,\"error\":\"test error\",\"id\":\"jsonrpc\"}\n")
27
- expect { rpc.test_rpc_call }.to raise_error(Utxoracle::Rpc::JSONRPCError)
28
- end
29
- end