utxoracle 0.0.1 → 0.0.3

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: 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