diff --git a/history/src/servers/history/run.ts b/history/src/servers/history/run.ts index 4c4c0d7..ae52e6d 100644 --- a/history/src/servers/history/run.ts +++ b/history/src/servers/history/run.ts @@ -25,7 +25,9 @@ const listPrices = wrapAsyncToRunInSpan({ fnName: "listPrices", fn: async ( { request }: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData<{ priceHistory: Tick[] }>, + callback: grpc.sendUnaryData<{ + priceHistory: Array + }>, ) => { const { currency, range } = request const priceHistory = await History.getPriceHistory({ currency, range }) @@ -40,7 +42,17 @@ const listPrices = wrapAsyncToRunInSpan({ }) } - return callback(null, { priceHistory }) + // ENG-317 / Phase A: populate both `price` (deprecated float32) and + // `price_v2` (double) on every Tick from the same source value. The + // wire will quantise `price` through float32; `price_v2` preserves + // the full float64. See realtime/run.ts and the proto for the full + // rollout plan. + const priceHistoryWithDouble = priceHistory.map((t) => ({ + ...t, + price_v2: t.price, + })) + + return callback(null, { priceHistory: priceHistoryWithDouble }) }, }) diff --git a/history/src/servers/protos/price_history.proto b/history/src/servers/protos/price_history.proto index 41df871..cde86d3 100644 --- a/history/src/servers/protos/price_history.proto +++ b/history/src/servers/protos/price_history.proto @@ -22,5 +22,10 @@ message PriceHistoryResponse { message Tick { uint64 timestamp = 1; - float price = 2; + // ENG-317 / Phase A of float→double rollout. See price.proto for the full + // rationale. Servers MUST populate both fields; clients SHOULD prefer + // `price_v2` and fall back to `price` when the server has not yet been + // upgraded. `float price = 2` will be removed in Phase B. + float price = 2 [deprecated = true]; + double price_v2 = 3; } diff --git a/realtime/src/servers/protos/price.proto b/realtime/src/servers/protos/price.proto index c8cb45e..8c36d92 100644 --- a/realtime/src/servers/protos/price.proto +++ b/realtime/src/servers/protos/price.proto @@ -6,7 +6,21 @@ service PriceFeed { } message PriceResponse { - float price = 1; + // ENG-317 / Phase A of float→double rollout. + // + // `price` (float32) is lossy for non-USD/non-BTC currencies — the JMD + // round-trip exhibited ~J$0.80 of drift, see ENG-316 / lnflash/flash#282. + // It is preserved here at its original tag for wire compatibility with + // pre-ENG-317 clients and servers, and is marked deprecated. + // + // Servers MUST populate both `price` and `price_v2` for the duration of + // Phase A. Clients SHOULD prefer `price_v2` and fall back to `price` when + // talking to a server that has not yet been upgraded. + // + // Phase B (future PR) will remove `float price = 1` once all clients are + // confirmed upgraded in prod. + float price = 1 [deprecated = true]; + double price_v2 = 3; } message PriceQuery { diff --git a/realtime/src/servers/realtime/run.ts b/realtime/src/servers/realtime/run.ts index 1125d76..5f7c7e2 100644 --- a/realtime/src/servers/realtime/run.ts +++ b/realtime/src/servers/realtime/run.ts @@ -28,7 +28,7 @@ const getPrice = wrapAsyncToRunInSpan({ fnName: "getPrice", fn: async ( { request }: grpc.ServerUnaryCall<{ currency: string }, unknown>, - callback: grpc.sendUnaryData<{ price: Price }>, + callback: grpc.sendUnaryData<{ price: Price; price_v2: Price }>, ) => { const currency = request.currency const price = await Realtime.getPrice(currency) @@ -40,7 +40,13 @@ const getPrice = wrapAsyncToRunInSpan({ }) } - return callback(null, { price }) + // ENG-317 / Phase A: populate both the legacy float field (`price`) and + // the new double field (`price_v2`) from the same source value. The + // float wire encoding will quantise `price` to ~24 bits of mantissa; + // `price_v2` carries the full float64 we computed. Clients prefer + // `price_v2` and fall back to `price` only when talking to a server + // that pre-dates this change. Phase B will drop `price` entirely. + return callback(null, { price, price_v2: price }) }, })