honeymaker 0.9.0 → 0.9.2

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: 12080b0cbcae6a52a2680efde8f4131b30f4ebcf70647c1c297ac88217c459ba
4
- data.tar.gz: c475a4e5da6e8f7ac2e0b5f3eaa84e12192eec504876871028935a6b3be05cae
3
+ metadata.gz: ac4aba6f18266cfffed7dfe3939a60198cb6bdb7361818db8f25f435e19d92f9
4
+ data.tar.gz: e0e398092c55d7483ac386a94846fd5eff4abf115feb9dffd59a85c59462da37
5
5
  SHA512:
6
- metadata.gz: c6c9bffcfa55f7f096a4128c2b09119e06d11e5be58bc04dde162a693600595e7662334034d38a049b7faaa2f9535976668b22890564bf6ddcf82f8f1c1ade2e
7
- data.tar.gz: eedbdfab0c9fa12e8b4ae86362eafcd90b4d8319caace49bdb642405ca99a90836af69a6c8a50d9a13e555f6f1be4008175fb7e019927d8198060d7835a3754c
6
+ metadata.gz: aabbbc8fdd2c884c85770fc21a5862fe05c014860775f44e362e59c7db7c841cf92e74494af5028abdf10f4af70d88dc5fe13cc4fdc1838e359dc44f6f5c6dd5
7
+ data.tar.gz: a6cb39b92b07b6a5d04bb9b5d3adc14f11f7ea045b628c3b7dc09dd7501ddc0b9268bd3b477d8f2aac5072cfd379afe527738899c706b3d60ace82edd91073ad
@@ -237,8 +237,25 @@ module Honeymaker
237
237
  end
238
238
  end
239
239
 
240
+ # Kraken rejects any request whose nonce is <= the last nonce it saw for the
241
+ # same API key. We protect against in-process collisions (microsecond bursts,
242
+ # backward clock corrections) by tracking the last issued nonce per API key
243
+ # under a mutex. Cross-process reuse of the same API key (other workers,
244
+ # other containers, other tools) is not protected here — affected users
245
+ # should increase the Nonce Window on their Kraken API key.
246
+ @@nonce_mutex = Mutex.new
247
+ @@last_nonces = {}
248
+
249
+ def self.reset_nonce_state!
250
+ @@nonce_mutex.synchronize { @@last_nonces.clear }
251
+ end
252
+
240
253
  def nonce
241
- (Time.now.utc.to_f * 1_000_000).to_i
254
+ key = @api_key ? Digest::SHA256.hexdigest(@api_key) : :__no_api_key__
255
+ @@nonce_mutex.synchronize do
256
+ candidate = (Time.now.utc.to_f * 1_000_000).to_i
257
+ @@last_nonces[key] = [candidate, (@@last_nonces[key] || 0) + 1].max
258
+ end
242
259
  end
243
260
 
244
261
  def private_headers(path, body)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Honeymaker
4
- VERSION = "0.9.0"
4
+ VERSION = "0.9.2"
5
5
  end
@@ -4,6 +4,7 @@ require "test_helper"
4
4
 
5
5
  class Honeymaker::Clients::KrakenTest < Minitest::Test
6
6
  def setup
7
+ Honeymaker::Clients::Kraken.reset_nonce_state! if Honeymaker::Clients::Kraken.respond_to?(:reset_nonce_state!)
7
8
  @client = Honeymaker::Clients::Kraken.new(
8
9
  api_key: "test_key",
9
10
  api_secret: Base64.strict_encode64("test_secret_key_1234567890123456")
@@ -74,8 +75,65 @@ class Honeymaker::Clients::KrakenTest < Minitest::Test
74
75
  refute headers.key?(:"API-Key")
75
76
  end
76
77
 
78
+ def test_reset_nonce_state_is_available_for_test_isolation
79
+ assert_respond_to Honeymaker::Clients::Kraken, :reset_nonce_state!
80
+ end
81
+
82
+ def test_nonce_is_strictly_increasing_in_a_tight_loop
83
+ nonces = Array.new(10_000) { @client.send(:nonce) }
84
+
85
+ assert_strictly_increasing nonces
86
+ end
87
+
88
+ def test_nonce_is_strictly_increasing_when_clock_is_frozen
89
+ frozen_time = Time.utc(2026, 1, 1, 12, 0, 0)
90
+ Time.stubs(:now).returns(frozen_time)
91
+
92
+ nonces = Array.new(3) { @client.send(:nonce) }
93
+
94
+ assert_strictly_increasing nonces
95
+ end
96
+
97
+ def test_nonce_is_strictly_increasing_across_clients_with_the_same_api_key
98
+ frozen_time = Time.utc(2026, 1, 1, 12, 0, 0)
99
+ Time.stubs(:now).returns(frozen_time)
100
+ other_client = Honeymaker::Clients::Kraken.new(
101
+ api_key: "test_key",
102
+ api_secret: Base64.strict_encode64("test_secret_key_1234567890123456")
103
+ )
104
+
105
+ nonces = [
106
+ @client.send(:nonce),
107
+ other_client.send(:nonce),
108
+ @client.send(:nonce),
109
+ other_client.send(:nonce)
110
+ ]
111
+
112
+ assert_strictly_increasing nonces
113
+ end
114
+
115
+ def test_nonce_sequences_are_independent_for_different_api_keys
116
+ frozen_time = Time.utc(2026, 1, 1, 12, 0, 0)
117
+ Time.stubs(:now).returns(frozen_time)
118
+ first_key_client = Honeymaker::Clients::Kraken.new(api_key: "first_key", api_secret: @client.api_secret)
119
+ second_key_client = Honeymaker::Clients::Kraken.new(api_key: "second_key", api_secret: @client.api_secret)
120
+
121
+ first_key_first_nonce = first_key_client.send(:nonce)
122
+ first_key_second_nonce = first_key_client.send(:nonce)
123
+ second_key_first_nonce = second_key_client.send(:nonce)
124
+
125
+ assert_operator first_key_second_nonce, :>, first_key_first_nonce
126
+ assert_equal first_key_first_nonce, second_key_first_nonce
127
+ end
128
+
77
129
  private
78
130
 
131
+ def assert_strictly_increasing(values)
132
+ values.each_cons(2) do |previous, current|
133
+ assert_operator current, :>, previous
134
+ end
135
+ end
136
+
79
137
  def stub_connection(method, body)
80
138
  response = stub(body: body)
81
139
  connection = stub
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeymaker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Deltabadger