diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 7a74c6b2a7..070baff870 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -393,7 +393,7 @@ func (v2 *Handlers) TealDryrun(ctx echo.Context) error { var dr DryrunRequest var gdr generated.DryrunRequest - err := decode(protocol.JSONHandle, data, &gdr) + err := decode(protocol.JSONStrictHandle, data, &gdr) if err == nil { dr, err = DryrunRequestFromGenerated(&gdr) if err != nil { diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index da98d6549a..b5930e28a0 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -18,6 +18,7 @@ package test import ( "bytes" + "encoding/json" "errors" "io" "net/http" @@ -27,16 +28,20 @@ import ( "github.com/labstack/echo/v4" "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" + "github.com/algorand/go-algorand/crypto" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" generatedV2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util/execpool" ) func setupTestForMethodGet(t *testing.T) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { @@ -105,6 +110,101 @@ func TestGetBlock(t *testing.T) { getBlockTest(t, 0, "bad format", 400) } +func TestGetBlockJsonEncoding(t *testing.T) { + t.Parallel() + + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t) + defer releasefunc() + + l := handler.Node.Ledger() + + genBlk, err := l.Block(0) + require.NoError(t, err) + + // make an app call txn with eval delta + lsig := transactions.LogicSig{Logic: retOneProgram} // int 1 + program := logic.Program(lsig.Logic) + lhash := crypto.HashObj(&program) + var sender basics.Address + copy(sender[:], lhash[:]) + stx := transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: transactions.Header{ + Sender: sender, + Fee: basics.MicroAlgos{Raw: 1000}, + GenesisID: genBlk.GenesisID(), + GenesisHash: genBlk.GenesisHash(), + FirstValid: 1, + LastValid: 10, + }, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 1, + OnCompletion: transactions.ClearStateOC, + }, + }, + Lsig: lsig, + } + ad := transactions.ApplyData{ + EvalDelta: basics.EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 1: {"key": basics.ValueDelta{Action: 1}}, + }, + }, + } + + // put it into a block + backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) + defer backlogPool.Shutdown() + + totals, err := l.Totals(l.Latest()) + require.NoError(t, err) + totalRewardUnits := totals.RewardUnits() + poolBal, err := l.Lookup(l.Latest(), poolAddr) + require.NoError(t, err) + + var blk bookkeeping.Block + blk.BlockHeader = bookkeeping.BlockHeader{ + GenesisID: genBlk.GenesisID(), + GenesisHash: genBlk.GenesisHash(), + Round: l.Latest() + 1, + Branch: genBlk.Hash(), + TimeStamp: 0, + RewardsState: genBlk.NextRewardsState(l.Latest()+1, proto, poolBal.MicroAlgos, totalRewardUnits), + UpgradeState: genBlk.UpgradeState, + } + + blk.BlockHeader.TxnCounter = genBlk.TxnCounter + + blk.RewardsPool = genBlk.RewardsPool + blk.FeeSink = genBlk.FeeSink + blk.CurrentProtocol = genBlk.CurrentProtocol + blk.TimeStamp = genBlk.TimeStamp + 1 + + txib, err := blk.EncodeSignedTxn(stx, ad) + blk.Payset = append(blk.Payset, txib) + blk.BlockHeader.TxnCounter++ + blk.TxnRoot, err = blk.PaysetCommit() + require.NoError(t, err) + + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + + // fetch the block and ensure it can be properly decoded with the standard JSON decoder + format := "json" + err = handler.GetBlock(c, 1, generatedV2.GetBlockParams{Format: &format}) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + data := rec.Body.Bytes() + + response := struct { + Block bookkeeping.Block `codec:"block"` + }{} + + err = json.Unmarshal(data, &response) + require.NoError(t, err) +} + func TestGetSupply(t *testing.T) { t.Parallel() diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index b5962cf4c4..28d35c8577 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -32,6 +32,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" @@ -188,6 +189,7 @@ var sinkAddr = basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb var poolAddr = basics.Address{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} var genesisHash = crypto.Digest{0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe} var genesisID = "testingid" +var retOneProgram = []byte{2, 0x20, 1, 1, 0x22} var proto = config.Consensus[protocol.ConsensusCurrentVersion] @@ -256,7 +258,15 @@ func testingenv(t testing.TB, numAccounts, numTxs int, offlineAccounts bool) (*d genesis[poolAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) - bootstrap := data.MakeGenesisBalances(genesis, poolAddr, sinkAddr) + program := logic.Program(retOneProgram) + lhash := crypto.HashObj(&program) + var addr basics.Address + copy(addr[:], lhash[:]) + ad := basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) + ad.AppLocalStates = map[basics.AppIndex]basics.AppLocalState{1: {}} + genesis[addr] = ad + + bootstrap := data.MakeGenesisBalances(genesis, sinkAddr, poolAddr) // generate test transactions const inMem = true diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 6788890622..e4b2dd24c4 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -186,7 +186,7 @@ func getCodecHandle(formatPtr *string) (codec.Handle, string, error) { switch format { case "json": - return protocol.JSONHandle, "application/json", nil + return protocol.JSONStrictHandle, "application/json", nil case "msgpack": fallthrough case "msgp": diff --git a/protocol/codec_test.go b/protocol/codec_test.go index 221e14ceba..14fef6187c 100644 --- a/protocol/codec_test.go +++ b/protocol/codec_test.go @@ -18,10 +18,10 @@ package protocol import ( "reflect" - "strings" "testing" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-codec/codec" "github.com/stretchr/testify/require" ) @@ -167,21 +167,35 @@ func TestEncodeJSON(t *testing.T) { v.Map[1] = []string{"test1"} nonStrict := EncodeJSON(&v) - strings.Contains(string(nonStrict), `0:`) - strings.Contains(string(nonStrict), `1:`) + require.Contains(t, string(nonStrict), `0:`) + require.Contains(t, string(nonStrict), `1:`) strict := EncodeJSONStrict(&v) - strings.Contains(string(strict), `"0":`) - strings.Contains(string(strict), `"1":`) + require.Contains(t, string(strict), `"0":`) + require.Contains(t, string(strict), `"1":`) var nsv mp err := DecodeJSON(nonStrict, &nsv) require.NoError(t, err) var sv mp - err = DecodeJSON(nonStrict, &sv) + err = DecodeJSON(strict, &sv) require.NoError(t, err) require.True(t, reflect.DeepEqual(v, nsv)) require.True(t, reflect.DeepEqual(v, sv)) + + decodeJSONStrict := func(b []byte, objptr interface{}) error { + dec := codec.NewDecoderBytes(b, JSONStrictHandle) + return dec.Decode(objptr) + } + + nsv = mp{} + decodeJSONStrict(nonStrict, &nsv) + + sv = mp{} + decodeJSONStrict(strict, &sv) + + require.True(t, reflect.DeepEqual(v, nsv)) + require.True(t, reflect.DeepEqual(v, sv)) } diff --git a/test/scripts/e2e_subs/serial/rest-proof-endpoint.sh b/test/scripts/e2e_subs/serial/rest-proof-endpoint.sh index ba87561efc..d0faaee5d2 100755 --- a/test/scripts/e2e_subs/serial/rest-proof-endpoint.sh +++ b/test/scripts/e2e_subs/serial/rest-proof-endpoint.sh @@ -17,9 +17,7 @@ while [[ "${NUM_TRANSACTIONS}" != "1" ]]; do # check if the transaction was all alone in the round call_and_verify "Checking block" "/v2/blocks/${ROUND}" 200 'txns' - #TODO: The check with jq can be re-enabled after fixing JSONStrictHandle. - #NUM_TRANSACTIONS=$(cat "${TEMPDIR}/curl_out.txt" | jq '.block.txns | length') - NUM_TRANSACTIONS=$(cat "${TEMPDIR}/curl_out.txt" | grep type | wc -l | tr -d ' ') + NUM_TRANSACTIONS=$(cat "${TEMPDIR}/curl_out.txt" | jq '.block.txns | length') done call_and_verify "The proof should not be null." "/v2/blocks/${ROUND}/transactions/${TXID}/proof" 200 '"proof":""'