From f06f298c534a0e3a96b85d5b81930d5e7f683a7e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 9 Oct 2023 16:08:53 -0400 Subject: [PATCH] Marshal blocks and transactions inside API calls --- api/common_args_responses.go | 6 ++-- vms/avm/service.go | 58 ++++++++++++++++++++-------------- vms/avm/service_test.go | 57 +++++++++++++++------------------ vms/platformvm/service.go | 52 +++++++++++++++--------------- vms/platformvm/service_test.go | 42 ++++++++++++++---------- 5 files changed, 116 insertions(+), 99 deletions(-) diff --git a/api/common_args_responses.go b/api/common_args_responses.go index e81496022706..73624d529d9b 100644 --- a/api/common_args_responses.go +++ b/api/common_args_responses.go @@ -4,6 +4,8 @@ package api import ( + stdjson "encoding/json" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/json" @@ -75,7 +77,7 @@ type GetBlockByHeightArgs struct { // GetBlockResponse is the response object for the GetBlock API. type GetBlockResponse struct { - Block interface{} `json:"block"` + Block stdjson.RawMessage `json:"block"` // If GetBlockResponse.Encoding is formatting.Hex, GetBlockResponse.Block is // the string representation of the block under hex encoding. // If GetBlockResponse.Encoding is formatting.JSON, GetBlockResponse.Block @@ -105,7 +107,7 @@ type GetTxReply struct { // the tx under hex encoding. // If [GetTxArgs.Encoding] is [JSON], [Tx] is the actual tx, which will be // returned as JSON to the caller. - Tx interface{} `json:"tx"` + Tx stdjson.RawMessage `json:"tx"` Encoding formatting.Encoding `json:"encoding"` } diff --git a/vms/avm/service.go b/vms/avm/service.go index 6a9b522c7ea6..92d3547ea5ea 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -9,6 +9,8 @@ import ( "math" "net/http" + stdjson "encoding/json" + "go.uber.org/zap" "github.com/ava-labs/avalanchego/api" @@ -79,6 +81,7 @@ func (s *Service) GetBlock(_ *http.Request, args *api.GetBlockArgs, reply *api.G } reply.Encoding = args.Encoding + var result any if args.Encoding == formatting.JSON { block.InitCtx(s.vm.ctx) for _, tx := range block.Txs() { @@ -92,16 +95,16 @@ func (s *Service) GetBlock(_ *http.Request, args *api.GetBlockArgs, reply *api.G return err } } - reply.Block = block - return nil - } - - reply.Block, err = formatting.Encode(args.Encoding, block.Bytes()) - if err != nil { - return fmt.Errorf("couldn't encode block %s as string: %w", args.BlockID, err) + result = block + } else { + result, err = formatting.Encode(args.Encoding, block.Bytes()) + if err != nil { + return fmt.Errorf("couldn't encode block %s as string: %w", args.BlockID, err) + } } - return nil + reply.Block, err = stdjson.Marshal(result) + return err } // GetBlockByHeight returns the block at the given height. @@ -130,6 +133,7 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightAr return fmt.Errorf("couldn't get block with id %s: %w", blockID, err) } + var result any if args.Encoding == formatting.JSON { block.InitCtx(s.vm.ctx) for _, tx := range block.Txs() { @@ -143,16 +147,16 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightAr return err } } - reply.Block = block - return nil - } - - reply.Block, err = formatting.Encode(args.Encoding, block.Bytes()) - if err != nil { - return fmt.Errorf("couldn't encode block %s as string: %w", blockID, err) + result = block + } else { + result, err = formatting.Encode(args.Encoding, block.Bytes()) + if err != nil { + return fmt.Errorf("couldn't encode block %s as string: %w", blockID, err) + } } - return nil + reply.Block, err = stdjson.Marshal(result) + return err } // GetHeight returns the height of the last accepted block. @@ -320,23 +324,29 @@ func (s *Service) GetTx(_ *http.Request, args *api.GetTxArgs, reply *api.GetTxRe if err != nil { return err } - reply.Encoding = args.Encoding + + var result any if args.Encoding == formatting.JSON { - reply.Tx = tx - return tx.Unsigned.Visit(&txInit{ + err := tx.Unsigned.Visit(&txInit{ tx: tx, ctx: s.vm.ctx, typeToFxIndex: s.vm.typeToFxIndex, fxs: s.vm.fxs, }) + if err != nil { + return err + } + result = tx + } else { + result, err = formatting.Encode(args.Encoding, tx.Bytes()) + if err != nil { + return fmt.Errorf("couldn't encode tx as string: %w", err) + } } - reply.Tx, err = formatting.Encode(args.Encoding, tx.Bytes()) - if err != nil { - return fmt.Errorf("couldn't encode tx as string: %w", err) - } - return nil + reply.Tx, err = stdjson.Marshal(result) + return err } // GetUTXOs gets all utxos for passed in addresses diff --git a/vms/avm/service_test.go b/vms/avm/service_test.go index 1a97bad62d47..a0bd93f01861 100644 --- a/vms/avm/service_test.go +++ b/vms/avm/service_test.go @@ -481,9 +481,14 @@ func TestServiceGetTx(t *testing.T) { reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ - TxID: txID, + TxID: txID, + Encoding: formatting.Hex, }, &reply)) - txBytes, err := formatting.Decode(reply.Encoding, reply.Tx.(string)) + + var txStr string + require.NoError(stdjson.Unmarshal(reply.Tx, &txStr)) + + txBytes, err := formatting.Decode(reply.Encoding, txStr) require.NoError(err) require.Equal(env.genesisTx.Bytes(), txBytes) } @@ -507,9 +512,7 @@ func TestServiceGetTxJSON_BaseTx(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) require.Contains(jsonString, `"memo":"0x0102030405060708"`) require.Contains(jsonString, `"inputs":[{"txID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","outputIndex":2,"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","input":{"amount":50000,"signatureIndices":[0]}}]`) require.Contains(jsonString, `"outputs":[{"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","output":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"amount":49000,"locktime":0,"threshold":1}}]`) @@ -534,9 +537,7 @@ func TestServiceGetTxJSON_ExportTx(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) require.Contains(jsonString, `"inputs":[{"txID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","outputIndex":2,"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","input":{"amount":50000,"signatureIndices":[0]}}]`) require.Contains(jsonString, `"exportedOutputs":[{"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","output":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"amount":49000,"locktime":0,"threshold":1}}]}`) } @@ -566,9 +567,7 @@ func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) // contains the address in the right format require.Contains(jsonString, `"outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"groupID":1,"locktime":0,"threshold":1},{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"groupID":2,"locktime":0,"threshold":1}]}`) @@ -605,9 +604,7 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) // assert memo and payload are in hex require.Contains(jsonString, `"memo":"0x"`) require.Contains(jsonString, `"payload":"0x68656c6c6f"`) @@ -651,9 +648,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) // contains the address in the right format require.Contains(jsonString, `"outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) @@ -693,9 +688,7 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) // ensure memo is in hex require.Contains(jsonString, `"memo":"0x"`) @@ -741,9 +734,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) // contains the address in the right format require.Contains(jsonString, `"mintOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) @@ -784,9 +775,7 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) // ensure memo is in hex require.Contains(jsonString, `"memo":"0x"`) @@ -831,9 +820,7 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonTxBytes, err := stdjson.Marshal(reply.Tx) - require.NoError(err) - jsonString := string(jsonTxBytes) + jsonString := string(reply.Tx) // contains the address in the right format require.Contains(jsonString, `"mintOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) @@ -2111,7 +2098,11 @@ func TestServiceGetBlock(t *testing.T) { return } require.Equal(tt.encoding, reply.Encoding) - require.Equal(expected, reply.Block) + + expectedJSON, err := stdjson.Marshal(expected) + require.NoError(err) + + require.Equal(stdjson.RawMessage(expectedJSON), reply.Block) }) } } @@ -2313,7 +2304,11 @@ func TestServiceGetBlockByHeight(t *testing.T) { return } require.Equal(tt.encoding, reply.Encoding) - require.Equal(expected, reply.Block) + + expectedJSON, err := stdjson.Marshal(expected) + require.NoError(err) + + require.Equal(stdjson.RawMessage(expectedJSON), reply.Block) }) } } diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index b917b5e4a5e7..79003c471a85 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -2142,7 +2142,6 @@ func (s *Service) IssueTx(_ *http.Request, args *api.FormattedTx, response *api. return nil } -// GetTx gets a tx func (s *Service) GetTx(_ *http.Request, args *api.GetTxArgs, response *api.GetTxReply) error { s.vm.ctx.Log.Debug("API called", zap.String("service", "platform"), @@ -2153,20 +2152,21 @@ func (s *Service) GetTx(_ *http.Request, args *api.GetTxArgs, response *api.GetT if err != nil { return fmt.Errorf("couldn't get tx: %w", err) } - txBytes := tx.Bytes() response.Encoding = args.Encoding + var result any if args.Encoding == formatting.JSON { tx.Unsigned.InitCtx(s.vm.ctx) - response.Tx = tx - return nil + result = tx + } else { + result, err = formatting.Encode(args.Encoding, tx.Bytes()) + if err != nil { + return fmt.Errorf("couldn't encode tx as %s: %w", args.Encoding, err) + } } - response.Tx, err = formatting.Encode(args.Encoding, txBytes) - if err != nil { - return fmt.Errorf("couldn't encode tx as %s: %w", args.Encoding, err) - } - return nil + response.Tx, err = stdjson.Marshal(result) + return err } type GetTxStatusArgs struct { @@ -2638,18 +2638,19 @@ func (s *Service) GetBlock(_ *http.Request, args *api.GetBlockArgs, response *ap } response.Encoding = args.Encoding + var result any if args.Encoding == formatting.JSON { block.InitCtx(s.vm.ctx) - response.Block = block - return nil - } - - response.Block, err = formatting.Encode(args.Encoding, block.Bytes()) - if err != nil { - return fmt.Errorf("couldn't encode block %s as %s: %w", args.BlockID, args.Encoding, err) + result = block + } else { + result, err = formatting.Encode(args.Encoding, block.Bytes()) + if err != nil { + return fmt.Errorf("couldn't encode block %s as %s: %w", args.BlockID, args.Encoding, err) + } } - return nil + response.Block, err = stdjson.Marshal(result) + return err } // GetBlockByHeight returns the block at the given height. @@ -2676,18 +2677,19 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightAr } response.Encoding = args.Encoding + var result any if args.Encoding == formatting.JSON { block.InitCtx(s.vm.ctx) - response.Block = block - return nil - } - - response.Block, err = formatting.Encode(args.Encoding, block.Bytes()) - if err != nil { - return fmt.Errorf("couldn't encode block %s as %s: %w", blockID, args.Encoding, err) + result = block + } else { + result, err = formatting.Encode(args.Encoding, block.Bytes()) + if err != nil { + return fmt.Errorf("couldn't encode block %s as %s: %w", blockID, args.Encoding, err) + } } - return nil + response.Block, err = stdjson.Marshal(result) + return err } func (s *Service) getAPIUptime(staker *state.Staker) (*json.Float32, error) { diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index 0a12c1e6464b..1e7468cc6fba 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -269,9 +269,9 @@ func TestGetTx(t *testing.T) { func(service *Service) (*txs.Tx, error) { return service.vm.txBuilder.NewCreateChainTx( // Test GetTx works for standard blocks testSubnet1.ID(), - nil, + []byte{}, constants.AVMID, - nil, + []ids.ID{}, "chain name", []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, keys[0].PublicKey().Address(), // change addr @@ -356,14 +356,17 @@ func TestGetTx(t *testing.T) { switch encoding { case formatting.Hex: // we're always guaranteed a string for hex encodings. - responseTxBytes, err := formatting.Decode(response.Encoding, response.Tx.(string)) + var txStr string + require.NoError(stdjson.Unmarshal(response.Tx, &txStr)) + responseTxBytes, err := formatting.Decode(response.Encoding, txStr) require.NoError(err) require.Equal(tx.Bytes(), responseTxBytes) case formatting.JSON: - require.IsType((*txs.Tx)(nil), response.Tx) - responseTx := response.Tx.(*txs.Tx) - require.Equal(tx.ID(), responseTx.ID()) + tx.Unsigned.InitCtx(service.vm.ctx) + expectedTxJSON, err := stdjson.Marshal(tx) + require.NoError(err) + require.Equal(expectedTxJSON, []byte(response.Tx)) } require.NoError(service.vm.Shutdown(context.Background())) @@ -737,9 +740,9 @@ func TestGetBlock(t *testing.T) { // Make a block an accept it, then check we can get it. tx, err := service.vm.txBuilder.NewCreateChainTx( // Test GetTx works for standard blocks testSubnet1.ID(), - nil, + []byte{}, constants.AVMID, - nil, + []ids.ID{}, "chain name", []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, keys[0].PublicKey().Address(), // change addr @@ -771,15 +774,16 @@ func TestGetBlock(t *testing.T) { switch { case test.encoding == formatting.JSON: - require.IsType((*block.BanffStandardBlock)(nil), response.Block) - responseBlock := response.Block.(*block.BanffStandardBlock) - require.Equal(statelessBlock.ID(), responseBlock.ID()) - - _, err = stdjson.Marshal(response) + statelessBlock.InitCtx(service.vm.ctx) + expectedBlockJSON, err := stdjson.Marshal(statelessBlock) require.NoError(err) + require.Equal(expectedBlockJSON, []byte(response.Block)) default: - decoded, _ := formatting.Decode(response.Encoding, response.Block.(string)) - require.Equal(blk.Bytes(), decoded) + var blockStr string + require.NoError(stdjson.Unmarshal(response.Block, &blockStr)) + responseBlockBytes, err := formatting.Decode(response.Encoding, blockStr) + require.NoError(err) + require.Equal(blk.Bytes(), responseBlockBytes) } require.Equal(test.encoding, response.Encoding) @@ -998,11 +1002,15 @@ func TestServiceGetBlockByHeight(t *testing.T) { reply := &api.GetBlockResponse{} err := service.GetBlockByHeight(nil, args, reply) require.ErrorIs(err, tt.expectedErr) - if tt.expectedErr == nil { + if tt.expectedErr != nil { return } require.Equal(tt.encoding, reply.Encoding) - require.Equal(expected, reply.Block) + + expectedJSON, err := stdjson.Marshal(expected) + require.NoError(err) + + require.Equal(stdjson.RawMessage(expectedJSON), reply.Block) }) } }