bitcoinrb 0.3.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +5 -3
  4. data/README.md +17 -6
  5. data/bitcoinrb.gemspec +7 -7
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +34 -11
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_filter.rb +14 -0
  10. data/lib/bitcoin/block_header.rb +2 -0
  11. data/lib/bitcoin/chain_params.rb +9 -8
  12. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  13. data/lib/bitcoin/chainparams/signet.yml +39 -0
  14. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  15. data/lib/bitcoin/constants.rb +45 -12
  16. data/lib/bitcoin/descriptor.rb +1 -1
  17. data/lib/bitcoin/errors.rb +19 -0
  18. data/lib/bitcoin/ext.rb +5 -0
  19. data/lib/bitcoin/ext/ecdsa.rb +31 -0
  20. data/lib/bitcoin/ext/json_parser.rb +46 -0
  21. data/lib/bitcoin/ext_key.rb +50 -19
  22. data/lib/bitcoin/key.rb +46 -29
  23. data/lib/bitcoin/key_path.rb +12 -5
  24. data/lib/bitcoin/message.rb +7 -0
  25. data/lib/bitcoin/message/base.rb +1 -0
  26. data/lib/bitcoin/message/cf_parser.rb +16 -0
  27. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  28. data/lib/bitcoin/message/cfheaders.rb +40 -0
  29. data/lib/bitcoin/message/cfilter.rb +35 -0
  30. data/lib/bitcoin/message/fee_filter.rb +1 -1
  31. data/lib/bitcoin/message/filter_load.rb +3 -3
  32. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  33. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  34. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  35. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  36. data/lib/bitcoin/message/inventory.rb +1 -1
  37. data/lib/bitcoin/message/merkle_block.rb +1 -1
  38. data/lib/bitcoin/message/network_addr.rb +3 -3
  39. data/lib/bitcoin/message/ping.rb +1 -1
  40. data/lib/bitcoin/message/pong.rb +1 -1
  41. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  42. data/lib/bitcoin/message/version.rb +7 -0
  43. data/lib/bitcoin/mnemonic.rb +7 -7
  44. data/lib/bitcoin/network/peer.rb +9 -4
  45. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  46. data/lib/bitcoin/node/cli.rb +14 -10
  47. data/lib/bitcoin/node/configuration.rb +3 -1
  48. data/lib/bitcoin/node/spv.rb +9 -1
  49. data/lib/bitcoin/opcodes.rb +14 -1
  50. data/lib/bitcoin/out_point.rb +7 -0
  51. data/lib/bitcoin/payment_code.rb +92 -0
  52. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  53. data/lib/bitcoin/psbt/input.rb +8 -17
  54. data/lib/bitcoin/psbt/output.rb +1 -1
  55. data/lib/bitcoin/psbt/tx.rb +11 -16
  56. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  57. data/lib/bitcoin/rpc/request_handler.rb +2 -2
  58. data/lib/bitcoin/script/script.rb +68 -28
  59. data/lib/bitcoin/script/script_error.rb +27 -1
  60. data/lib/bitcoin/script/script_interpreter.rb +164 -67
  61. data/lib/bitcoin/script/tx_checker.rb +64 -14
  62. data/lib/bitcoin/secp256k1.rb +1 -0
  63. data/lib/bitcoin/secp256k1/native.rb +138 -25
  64. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  65. data/lib/bitcoin/secp256k1/ruby.rb +82 -54
  66. data/lib/bitcoin/sighash_generator.rb +156 -0
  67. data/lib/bitcoin/slip39/sss.rb +5 -2
  68. data/lib/bitcoin/store.rb +2 -1
  69. data/lib/bitcoin/store/chain_entry.rb +1 -0
  70. data/lib/bitcoin/store/db/level_db.rb +2 -2
  71. data/lib/bitcoin/store/utxo_db.rb +226 -0
  72. data/lib/bitcoin/tx.rb +17 -88
  73. data/lib/bitcoin/tx_in.rb +4 -5
  74. data/lib/bitcoin/tx_out.rb +2 -3
  75. data/lib/bitcoin/util.rb +43 -6
  76. data/lib/bitcoin/version.rb +1 -1
  77. data/lib/bitcoin/wallet.rb +1 -0
  78. data/lib/bitcoin/wallet/account.rb +2 -1
  79. data/lib/bitcoin/wallet/base.rb +3 -3
  80. data/lib/bitcoin/wallet/db.rb +1 -1
  81. data/lib/bitcoin/wallet/master_key.rb +1 -0
  82. data/lib/bitcoin/wallet/utxo.rb +37 -0
  83. metadata +50 -32
@@ -37,13 +37,12 @@ module Bitcoin
37
37
  hash, index = buf.read(36).unpack('a32V')
38
38
  i.out_point = OutPoint.new(hash.bth, index)
39
39
  sig_length = Bitcoin.unpack_var_int_from_io(buf)
40
- sig = buf.read(sig_length)
41
- if i.coinbase?
42
- i.script_sig.chunks[0] = sig
40
+ if sig_length == 0
41
+ i.script_sig = Bitcoin::Script.new
43
42
  else
44
- i.script_sig = Script.parse_from_payload(sig)
43
+ i.script_sig = Script.parse_from_payload(buf.read(sig_length))
45
44
  end
46
- i.sequence = buf.read(4).unpack('V').first
45
+ i.sequence = buf.read(4).unpack1('V')
47
46
  i
48
47
  end
49
48
 
@@ -18,14 +18,13 @@ module Bitcoin
18
18
 
19
19
  def self.parse_from_payload(payload)
20
20
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
21
- value = buf.read(8).unpack('q').first
21
+ value = buf.read(8).unpack1('q')
22
22
  script_size = Bitcoin.unpack_var_int_from_io(buf)
23
23
  new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
24
24
  end
25
25
 
26
26
  def to_payload
27
- s = script_pubkey.to_payload
28
- [value].pack('Q') << Bitcoin.pack_var_int(s.length) << s
27
+ [value].pack('Q') << script_pubkey.to_payload(true)
29
28
  end
30
29
 
31
30
  def to_empty_payload
@@ -33,7 +33,7 @@ module Bitcoin
33
33
 
34
34
  # @return an integer for a valid payload, otherwise nil
35
35
  def unpack_var_int(payload)
36
- case payload.unpack('C').first
36
+ case payload.unpack1('C')
37
37
  when 0xfd
38
38
  payload.unpack('xva*')
39
39
  when 0xfe
@@ -47,14 +47,14 @@ module Bitcoin
47
47
 
48
48
  # @return an integer for a valid payload, otherwise nil
49
49
  def unpack_var_int_from_io(buf)
50
- uchar = buf.read(1)&.unpack('C')&.first
50
+ uchar = buf.read(1)&.unpack1('C')
51
51
  case uchar
52
52
  when 0xfd
53
- buf.read(2)&.unpack('v')&.first
53
+ buf.read(2)&.unpack1('v')
54
54
  when 0xfe
55
- buf.read(4)&.unpack('V')&.first
55
+ buf.read(4)&.unpack1('V')
56
56
  when 0xff
57
- buf.read(8)&.unpack('Q')&.first
57
+ buf.read(8)&.unpack1('Q')
58
58
  else
59
59
  uchar
60
60
  end
@@ -79,7 +79,16 @@ module Bitcoin
79
79
 
80
80
  # byte convert to the sequence of bits packed eight in a byte with the least significant bit first.
81
81
  def byte_to_bit(byte)
82
- byte.unpack('b*').first
82
+ byte.unpack1('b*')
83
+ end
84
+
85
+ # padding zero to the left of binary string until bytesize.
86
+ # @param [String] binary string
87
+ # @param [Integer] bytesize total bytesize.
88
+ # @return [String] padded binary string.
89
+ def padding_zero(binary, bytesize)
90
+ return binary unless binary.bytesize < bytesize
91
+ ('00' * (bytesize - binary.bytesize)).htb + binary
83
92
  end
84
93
 
85
94
  # generate sha256-ripemd160 hash for value
@@ -87,6 +96,15 @@ module Bitcoin
87
96
  Digest::RMD160.hexdigest(Digest::SHA256.digest(hex.htb))
88
97
  end
89
98
 
99
+ # Generate tagged hash value.
100
+ # @param [String] tag tag value.
101
+ # @param [String] msg the message to be hashed.
102
+ # @return [String] the hash value with binary format.
103
+ def tagged_hash(tag, msg)
104
+ tag_hash = Digest::SHA256.digest(tag)
105
+ Digest::SHA256.digest(tag_hash + tag_hash + msg)
106
+ end
107
+
90
108
  # encode Base58 check address.
91
109
  # @param [String] hex the address payload.
92
110
  # @param [String] addr_version the address version for P2PKH and P2SH.
@@ -119,6 +137,25 @@ module Bitcoin
119
137
  OpenSSL::HMAC.digest(DIGEST_NAME_SHA256, key, data)
120
138
  end
121
139
 
140
+ # check whether +addr+ is valid address.
141
+ # @param [String] addr an address
142
+ # @return [Boolean] if valid address return true, otherwise false.
143
+ def valid_address?(addr)
144
+ begin
145
+ Bitcoin::Script.parse_from_addr(addr)
146
+ true
147
+ rescue Exception
148
+ false
149
+ end
150
+ end
151
+
122
152
  end
123
153
 
154
+ module HexConverter
155
+
156
+ def to_hex
157
+ to_payload.bth
158
+ end
159
+
160
+ end
124
161
  end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.3.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -4,5 +4,6 @@ module Bitcoin
4
4
  autoload :Account, 'bitcoin/wallet/account'
5
5
  autoload :DB, 'bitcoin/wallet/db'
6
6
  autoload :MasterKey, 'bitcoin/wallet/master_key'
7
+ autoload :Utxo, 'bitcoin/wallet/utxo'
7
8
  end
8
9
  end
@@ -3,6 +3,7 @@ module Bitcoin
3
3
 
4
4
  # the account in BIP-44
5
5
  class Account
6
+ include Bitcoin::HexConverter
6
7
 
7
8
  PURPOSE_TYPE = {legacy: 44, nested_witness: 49, native_segwit: 84}
8
9
 
@@ -42,7 +43,7 @@ module Bitcoin
42
43
 
43
44
  def to_payload
44
45
  payload = account_key.to_payload
45
- payload << Bitcoin.pack_var_string(name.unpack('H*').first.htb)
46
+ payload << Bitcoin.pack_var_string(name.unpack1('H*').htb)
46
47
  payload << [purpose, index, receive_depth, change_depth, lookahead].pack('I*')
47
48
  payload
48
49
  end
@@ -1,4 +1,4 @@
1
- require 'leveldb'
1
+ require 'leveldb-native'
2
2
  module Bitcoin
3
3
  module Wallet
4
4
 
@@ -20,14 +20,14 @@ module Bitcoin
20
20
  # @param [String] wallet_id new wallet id.
21
21
  # @param [String] path_prefix wallet file path prefix.
22
22
  # @return [Bitcoin::Wallet::Base] the wallet
23
- def self.create(wallet_id = 1, path_prefix = default_path_prefix)
23
+ def self.create(wallet_id = 1, path_prefix = default_path_prefix, purpose = Account::PURPOSE_TYPE[:native_segwit])
24
24
  raise ArgumentError, "wallet_id : #{wallet_id} already exist." if self.exist?(wallet_id, path_prefix)
25
25
  w = self.new(wallet_id, path_prefix)
26
26
  # generate seed
27
27
  raise RuntimeError, 'the seed already exist.' if w.db.registered_master?
28
28
  master = Bitcoin::Wallet::MasterKey.generate
29
29
  w.db.register_master_key(master)
30
- w.create_account('Default')
30
+ w.create_account(purpose, 'Default')
31
31
  w
32
32
  end
33
33
 
@@ -15,7 +15,7 @@ module Bitcoin
15
15
 
16
16
  def initialize(path = "#{Bitcoin.base_dir}/db/wallet")
17
17
  FileUtils.mkdir_p(path)
18
- @level_db = ::LevelDB::DB.new(path)
18
+ @level_db = ::LevelDBNative::DB.new(path)
19
19
  end
20
20
 
21
21
  # close database
@@ -3,6 +3,7 @@ module Bitcoin
3
3
 
4
4
  # HD Wallet master seed
5
5
  class MasterKey
6
+ include Bitcoin::HexConverter
6
7
  extend Bitcoin::Util
7
8
  include Bitcoin::Util
8
9
  include Bitcoin::KeyPath
@@ -0,0 +1,37 @@
1
+ module Bitcoin
2
+ module Wallet
3
+ class Utxo
4
+ attr_reader :tx_hash
5
+ attr_reader :index
6
+ attr_reader :block_height
7
+ attr_reader :value
8
+ attr_reader :script_pubkey
9
+
10
+ def initialize(tx_hash, index, value, script_pubkey, block_height = nil)
11
+ @tx_hash = tx_hash
12
+ @index = index
13
+ @block_height = block_height
14
+ @value = value
15
+ @script_pubkey = script_pubkey
16
+ end
17
+
18
+ def self.parse_from_payload(payload)
19
+ return nil if payload.nil?
20
+
21
+ tx_hash, index, block_height, value, payload = payload.unpack('H64VVQa*')
22
+
23
+ buf = StringIO.new(payload)
24
+ script_size = Bitcoin.unpack_var_int_from_io(buf)
25
+ script_pubkey = Bitcoin::Script.parse_from_payload(buf.read(script_size));
26
+ new(tx_hash, index, value, script_pubkey, block_height == 0 ? nil : block_height )
27
+ end
28
+
29
+ def to_payload
30
+ payload = [tx_hash, index, block_height.nil? ? 0 : block_height, value].pack('H64VVQ')
31
+ s = script_pubkey.to_payload
32
+ payload << Bitcoin.pack_var_int(s.length) << s
33
+ payload
34
+ end
35
+ end
36
+ end
37
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoinrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-30 00:00:00.000000000 Z
11
+ date: 2021-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -136,20 +136,6 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: rest-client
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :runtime
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
139
  - !ruby/object:Gem::Dependency
154
140
  name: iniparse
155
141
  requirement: !ruby/object:Gem::Requirement
@@ -193,35 +179,35 @@ dependencies:
193
179
  - !ruby/object:Gem::Version
194
180
  version: 3.8.5
195
181
  - !ruby/object:Gem::Dependency
196
- name: scrypt
182
+ name: json_pure
197
183
  requirement: !ruby/object:Gem::Requirement
198
184
  requirements:
199
185
  - - ">="
200
186
  - !ruby/object:Gem::Version
201
- version: '0'
187
+ version: 2.3.1
202
188
  type: :runtime
203
189
  prerelease: false
204
190
  version_requirements: !ruby/object:Gem::Requirement
205
191
  requirements:
206
192
  - - ">="
207
193
  - !ruby/object:Gem::Version
208
- version: '0'
194
+ version: 2.3.1
209
195
  - !ruby/object:Gem::Dependency
210
- name: activesupport
196
+ name: bip-schnorr
211
197
  requirement: !ruby/object:Gem::Requirement
212
198
  requirements:
213
- - - "~>"
199
+ - - ">="
214
200
  - !ruby/object:Gem::Version
215
- version: 5.2.3
201
+ version: 0.3.2
216
202
  type: :runtime
217
203
  prerelease: false
218
204
  version_requirements: !ruby/object:Gem::Requirement
219
205
  requirements:
220
- - - "~>"
206
+ - - ">="
221
207
  - !ruby/object:Gem::Version
222
- version: 5.2.3
208
+ version: 0.3.2
223
209
  - !ruby/object:Gem::Dependency
224
- name: leveldb-ruby
210
+ name: leveldb-native
225
211
  requirement: !ruby/object:Gem::Requirement
226
212
  requirements:
227
213
  - - ">="
@@ -252,16 +238,16 @@ dependencies:
252
238
  name: rake
253
239
  requirement: !ruby/object:Gem::Requirement
254
240
  requirements:
255
- - - "~>"
241
+ - - ">="
256
242
  - !ruby/object:Gem::Version
257
- version: '10.0'
243
+ version: 12.3.3
258
244
  type: :development
259
245
  prerelease: false
260
246
  version_requirements: !ruby/object:Gem::Requirement
261
247
  requirements:
262
- - - "~>"
248
+ - - ">="
263
249
  - !ruby/object:Gem::Version
264
- version: '10.0'
250
+ version: 12.3.3
265
251
  - !ruby/object:Gem::Dependency
266
252
  name: rspec
267
253
  requirement: !ruby/object:Gem::Requirement
@@ -290,7 +276,21 @@ dependencies:
290
276
  - - ">="
291
277
  - !ruby/object:Gem::Version
292
278
  version: '0'
293
- description: "[WIP]The implementation of Bitcoin Protocol for Ruby."
279
+ - !ruby/object:Gem::Dependency
280
+ name: webmock
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - ">="
284
+ - !ruby/object:Gem::Version
285
+ version: 3.11.1
286
+ type: :development
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - ">="
291
+ - !ruby/object:Gem::Version
292
+ version: 3.11.1
293
+ description: The implementation of Bitcoin Protocol for Ruby.
294
294
  email:
295
295
  - azuchi@chaintope.com
296
296
  executables:
@@ -317,6 +317,7 @@ files:
317
317
  - exe/bitcoinrbd
318
318
  - lib/bitcoin.rb
319
319
  - lib/bitcoin/base58.rb
320
+ - lib/bitcoin/bip85_entropy.rb
320
321
  - lib/bitcoin/bit_stream.rb
321
322
  - lib/bitcoin/block.rb
322
323
  - lib/bitcoin/block_filter.rb
@@ -325,9 +326,14 @@ files:
325
326
  - lib/bitcoin/chain_params.rb
326
327
  - lib/bitcoin/chainparams/mainnet.yml
327
328
  - lib/bitcoin/chainparams/regtest.yml
329
+ - lib/bitcoin/chainparams/signet.yml
328
330
  - lib/bitcoin/chainparams/testnet.yml
329
331
  - lib/bitcoin/constants.rb
330
332
  - lib/bitcoin/descriptor.rb
333
+ - lib/bitcoin/errors.rb
334
+ - lib/bitcoin/ext.rb
335
+ - lib/bitcoin/ext/ecdsa.rb
336
+ - lib/bitcoin/ext/json_parser.rb
331
337
  - lib/bitcoin/ext_key.rb
332
338
  - lib/bitcoin/gcs_filter.rb
333
339
  - lib/bitcoin/key.rb
@@ -341,6 +347,10 @@ files:
341
347
  - lib/bitcoin/message/block_transaction_request.rb
342
348
  - lib/bitcoin/message/block_transactions.rb
343
349
  - lib/bitcoin/message/block_txn.rb
350
+ - lib/bitcoin/message/cf_parser.rb
351
+ - lib/bitcoin/message/cfcheckpt.rb
352
+ - lib/bitcoin/message/cfheaders.rb
353
+ - lib/bitcoin/message/cfilter.rb
344
354
  - lib/bitcoin/message/cmpct_block.rb
345
355
  - lib/bitcoin/message/error.rb
346
356
  - lib/bitcoin/message/fee_filter.rb
@@ -350,6 +360,9 @@ files:
350
360
  - lib/bitcoin/message/get_addr.rb
351
361
  - lib/bitcoin/message/get_block_txn.rb
352
362
  - lib/bitcoin/message/get_blocks.rb
363
+ - lib/bitcoin/message/get_cfcheckpt.rb
364
+ - lib/bitcoin/message/get_cfheaders.rb
365
+ - lib/bitcoin/message/get_cfilters.rb
353
366
  - lib/bitcoin/message/get_data.rb
354
367
  - lib/bitcoin/message/get_headers.rb
355
368
  - lib/bitcoin/message/header_and_short_ids.rb
@@ -391,6 +404,7 @@ files:
391
404
  - lib/bitcoin/node/spv.rb
392
405
  - lib/bitcoin/opcodes.rb
393
406
  - lib/bitcoin/out_point.rb
407
+ - lib/bitcoin/payment_code.rb
394
408
  - lib/bitcoin/payments.rb
395
409
  - lib/bitcoin/payments/output.pb.rb
396
410
  - lib/bitcoin/payments/payment.pb.rb
@@ -416,7 +430,9 @@ files:
416
430
  - lib/bitcoin/script_witness.rb
417
431
  - lib/bitcoin/secp256k1.rb
418
432
  - lib/bitcoin/secp256k1/native.rb
433
+ - lib/bitcoin/secp256k1/rfc6979.rb
419
434
  - lib/bitcoin/secp256k1/ruby.rb
435
+ - lib/bitcoin/sighash_generator.rb
420
436
  - lib/bitcoin/slip39.rb
421
437
  - lib/bitcoin/slip39/share.rb
422
438
  - lib/bitcoin/slip39/sss.rb
@@ -426,6 +442,7 @@ files:
426
442
  - lib/bitcoin/store/db.rb
427
443
  - lib/bitcoin/store/db/level_db.rb
428
444
  - lib/bitcoin/store/spv_chain.rb
445
+ - lib/bitcoin/store/utxo_db.rb
429
446
  - lib/bitcoin/tx.rb
430
447
  - lib/bitcoin/tx_in.rb
431
448
  - lib/bitcoin/tx_out.rb
@@ -437,6 +454,7 @@ files:
437
454
  - lib/bitcoin/wallet/base.rb
438
455
  - lib/bitcoin/wallet/db.rb
439
456
  - lib/bitcoin/wallet/master_key.rb
457
+ - lib/bitcoin/wallet/utxo.rb
440
458
  - lib/openassets.rb
441
459
  - lib/openassets/marker_output.rb
442
460
  - lib/openassets/payload.rb
@@ -460,8 +478,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
460
478
  - !ruby/object:Gem::Version
461
479
  version: '0'
462
480
  requirements: []
463
- rubygems_version: 3.0.3
481
+ rubygems_version: 3.2.3
464
482
  signing_key:
465
483
  specification_version: 4
466
- summary: "[WIP]The implementation of Bitcoin Protocol for Ruby."
484
+ summary: The implementation of Bitcoin Protocol for Ruby.
467
485
  test_files: []