Skip to content

Commit

Permalink
api: massage protojson's output to use hex rather than base64
Browse files Browse the repository at this point in the history
Fixes #1354.
  • Loading branch information
mvdan committed Sep 16, 2024
1 parent 43b6518 commit adab3c2
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 2 deletions.
49 changes: 49 additions & 0 deletions api/helpers.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package api

import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"reflect"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -71,9 +74,55 @@ func protoTxAsJSON(tx []byte) []byte {
if err != nil {
panic(err)
}
// protojson follows protobuf's json mapping behavior,
// which requires bytes to be encoded as base64:
// https://protobuf.dev/programming-guides/proto3/#json
//
// We want hex rather than base64 for consistency with our REST API
// protojson does not expose any option to configure its behavior,
// and as the Go types are code generated, we cannot use our HexBytes type.
//
// Do a bit of a hack: walk the protobuf value with reflection,
// find all []byte values, and search-and-replace their base64 encoding with hex
// in the protojson bytes. This can be slow if we have many byte slices,
// but in general the protobuf message will be small so there will be few.
bytesValues := collectBytesValues(nil, reflect.ValueOf(&ptx))
for _, bv := range bytesValues {
asBase64 := base64.StdEncoding.AppendEncode(nil, bv)
asHex := hex.AppendEncode(nil, bv)
println(string(asBase64), string(asHex))
asJSON = bytes.Replace(asJSON, asBase64, asHex, 1)
}
return asJSON
}

var typBytes = reflect.TypeFor[[]byte]()

func collectBytesValues(result [][]byte, val reflect.Value) [][]byte {
typ := val.Type()
if typ == typBytes {
return append(result, val.Bytes())
}
switch typ.Kind() {
case reflect.Pointer, reflect.Interface:
if !val.IsNil() {
result = collectBytesValues(result, val.Elem())
}
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
if !typ.Field(i).IsExported() {
continue
}
result = collectBytesValues(result, val.Field(i))
}
case reflect.Slice, reflect.Array:
for i := 0; i < val.Len(); i++ {
result = collectBytesValues(result, val.Index(i))
}
}
return result
}

// isTransactionType checks if the given transaction is of the given type.
// t is expected to be a pointer to a protobuf transaction message.
func isTransactionType[T any](signedTxBytes []byte) (bool, error) {
Expand Down
4 changes: 2 additions & 2 deletions api/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ func TestProtoTxAsJSON(t *testing.T) {
"setProcess": {
"txtype": "SET_PROCESS_CENSUS",
"nonce": 1,
"processId": "sx3/YYFNq5DWw6m2XWyQgwSA5Lda0y50eUICAAAAAAA=",
"censusRoot": "zUU9BcTLBCnuXuGu/tAW9VO4AmtM7VsMNSkFv6U8foE=",
"processId": "b31dff61814dab90d6c3a9b65d6c90830480e4b75ad32e747942020000000000",
"censusRoot": "cd453d05c4cb0429ee5ee1aefed016f553b8026b4ced5b0c352905bfa53c7e81",
"censusURI": "ipfs://bafybeicyfukarcryrvy5oe37ligmxwf55sbfiojori4t25wencma4ymxfa",
"censusSize": "1000"
}
Expand Down

0 comments on commit adab3c2

Please sign in to comment.