跳至主要内容

Bitfinex 放貸機器人開發心得(二):難搞的 nonce 大魔王

· 閱讀時間約 5 分鐘

2026 年 1 月底開始,log 裡一直出現 Bitfinex 回的 nonce: small。前後修了兩次才真的修好,API key 也因此換了兩把。中間最誇張的一版,是把 nonce 直接往後加 180 天。

nonce 是什麼​

用 API key 呼叫 Bitfinex 需要登入的 API(查錢包、掛單、撤單),每次都要附一個 nonce,一個只能越來越大的數字。Bitfinex 會記住每把 key 收過的最大 nonce,下一次送來的如果沒有比它大,就回:

nonce: small

一般的做法是拿現在的時間(微秒)當 nonce,時間只會往前走,自然一直變大。

症狀​

當初本來就規劃了一套方便 debug 的 log,結果 log 裡一直在報 nonce 的錯誤。

git 紀錄裡能看到的是:

  • 1 月 5 日先加了「保證遞增」:同一個 client 連續產生的 nonce,下一個一定比上一個大
  • 1 月 24 日的 commit 裡有一條「nonce: small 不要算成斷線」,代表那時候這個錯誤會被當成斷線,讓 bot 把自己切成離線

試過什麼​

第一次修從 1 月 24 日改到 2 月 6 日,大約兩週,做法是遇到 nonce: small 就把之後的 nonce 往後加一段時間(offset)。這幾版都是 AI 自己往上加的:

  1. 1 月 24 日:每錯一次往後加 5 秒(無效)
  2. 2 月 2 日 10:11:改成每次加 60 秒,commit 寫「5 次重試最多可以往前跳 5 分鐘」(無效)
  3. 2 月 2 日 10:26:一啟動就先往後加 5 分鐘,理由是「容器重啟後 server 可能還記得上次比較大的 nonce」(無效)
  4. 2 月 2 日 10:32:起始改 10 分鐘,每次加 2 分鐘,同一天後來又改成每次加 10 分鐘(無效)
  5. 2 月 3 日:起始 7 天,每次加 1 小時(無效)
  6. 2 月 6 日:起始 180 天,每次加 1 天,而且把 offset 存成檔案,重啟後接著用(無效)

到這邊,送給 Bitfinex 的 nonce 已經是半年後的時間了。

為什麼越加越糟​

nonce 只能往上不能往下。Bitfinex 記住的那個最大值,一旦被推到 180 天後,之後這把 key 送的 nonce 都要比它大才會過。程式改回正常用現在時間當 nonce,就會一直被擋,所以這把 key 只能換掉。

第一次出事的時候只有我自己在用 bot,換 key 還好;第二次已經有請朋友加入測試,只好麻煩他也換一把。

而且如果同時有兩個計數器在用同一把 key,加 offset 根本救不了:A 往前跳,B 就變成比較小的那個被擋;B 再往前跳,又換 A 被擋。每次被擋就再加,數字只會一直往上疊。

當時 commit 裡猜的原因是「多個元件搶同一個 client」和「重啟後 server 記得舊的 nonce」,然後就繼續加 offset,沒有去查到底是誰在搶。

真正的原因​

第二次修是 4 月 7 日到 10 日,4 天:

  1. 4 月 7 日:拿掉 offset,改成官方 Go SDK 的做法。啟動時用「現在的秒數 × 1,000,000」當起點,之後每次 +1;送成功才存檔,每把 key 各存一個檔
  2. 4 月 9~10 日:找到根因。程式裡有兩個地方各自快取了 client(排程器管理一份,多用戶的 client 工廠又一份),同一把 key 會被建出兩個 client,各自有自己的 nonce 計數器
  3. 同時把產生 nonce 的 4 條路徑(一般 API、WebSocket 登入、查使用者資訊、查 key 權限)統一用同一個計數器
  4. nonce: small 從自動重試的清單拿掉,它不是網路問題,重試只會一直撞
  5. 同一個 client 的請求改成排隊一個一個送。同時送出去的請求,nonce 大的那個可能先到,小的那個晚到就被擋。一輪大約 11 個請求、每個 200~400 毫秒,排隊之後一輪 3~4 秒,反而比之前一直重試快

1~2 月那時候是不是同一個原因,現在從 git 紀錄已經分不出來了。那時候的程式還有另外一支高頻模式的執行檔,也會自己建 client。

之後又出現兩次​

修好之後,「同一把 key 建出第二個 client」這件事又發生過兩次:

  • 5 月 19 日:寫了一個一次性的對帳工具,直接呼叫 client 工廠建 client,沒走排程器的快取。bot 還在跑的時候執行它,Bitfinex 就開始擋所有請求,網頁掛了大約 5 分鐘,重啟才好。後來把那個工具整個刪掉,在 client 工廠的說明寫上只有哪兩個地方可以呼叫它
  • 8 月 21 日:改存 API key 的流程時,AI 寫了「存 key 失敗就刷新快取」,等於又會為同一把 key 建第二個 client。這次在 codex 審查時被抓到,沒有上線

老實說我對後端這種 nonce 機制完全不熟,這種 bug 沒辦法靠經驗處理(其實也懶得看 code 了),只能靠 AI 去嘗試和解決。但也許是因為本來就有設計 log 系統方便排查問題,這個 bug 雖然嚴重,但沒有修太久。