From f97d3b5c0b4080b702c7710322e9d7b695e8a2d6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 28 Apr 2022 21:50:01 -0400 Subject: [PATCH 01/30] Add an unlimited global storage mechanism - Boxes. At least three four left to consider, as well as extensive testing. 1. It has no LRU cache for the reads and writes. I expect sqlite to do a fine job caching kv pairs, and there's not savings from skipping msgpack serialization. But sqlite has a page cache, not a row cache, so maybe it's worth it. 2. There is no loop around the sql read with round number test. 3. There's is no locking of au.accountsMu. This is clearly wrong, need to learn more about the rules that govern when the other lookup routines are _not_ taking lock. 4. Need to think through limits on total boxes read/written, and how boxes can be "named" for access by new apps. --- cmd/tealdbg/localLedger.go | 4 + config/consensus.go | 15 +- daemon/algod/api/server/v2/dryrun.go | 4 + data/basics/userBalance.go | 16 + data/transactions/application.go | 23 + data/transactions/application_test.go | 9 +- data/transactions/logic/README.md | 4 + data/transactions/logic/TEAL_opcodes.md | 32 + data/transactions/logic/assembler_test.go | 13 +- data/transactions/logic/box.go | 121 ++ data/transactions/logic/box_test.go | 142 ++ data/transactions/logic/doc.go | 7 +- data/transactions/logic/doc_test.go | 4 +- data/transactions/logic/eval.go | 71 +- data/transactions/logic/evalAppTxn_test.go | 16 +- data/transactions/logic/evalCrypto_test.go | 12 +- data/transactions/logic/evalStateful_test.go | 52 +- data/transactions/logic/eval_test.go | 23 +- data/transactions/logic/fields_test.go | 6 +- data/transactions/logic/langspec.json | 41 + data/transactions/logic/ledger_test.go | 102 +- data/transactions/logic/opcodes.go | 9 + data/transactions/logic/teal.tmLanguage.json | 2 +- data/transactions/msgp_gen.go | 1705 +++++++++++------ data/transactions/msgp_gen_test.go | 60 + data/txntest/txn.go | 18 +- ledger/accountdb.go | 109 +- ledger/accountdb_test.go | 20 +- ledger/acctupdates.go | 145 +- ledger/acctupdates_test.go | 6 +- ledger/apply/payment.go | 9 + ledger/catchpointtracker_test.go | 2 +- ledger/evalindexer.go | 4 + ledger/internal/appcow.go | 25 +- ledger/internal/appcow_test.go | 96 +- ledger/internal/applications.go | 268 ++- ledger/internal/applications_test.go | 359 ---- ledger/internal/apptxn_test.go | 223 ++- ledger/internal/boxtxn_test.go | 187 ++ ledger/internal/cow.go | 18 +- ledger/internal/cow_test.go | 6 +- ledger/internal/double_test.go | 25 +- ledger/internal/eval.go | 17 +- ledger/internal/eval_blackbox_test.go | 4 +- ledger/internal/eval_test.go | 8 + .../prefetcher/prefetcher_alignment_test.go | 3 + ledger/ledger.go | 8 + ledger/ledgercore/accountdata.go | 15 +- ledger/ledgercore/statedelta.go | 4 + ledger/tracker.go | 3 +- 50 files changed, 2742 insertions(+), 1333 deletions(-) create mode 100644 data/transactions/logic/box.go create mode 100644 data/transactions/logic/box_test.go delete mode 100644 ledger/internal/applications_test.go create mode 100644 ledger/internal/boxtxn_test.go diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go index 50d3f6aae4..6ddae7ffe3 100644 --- a/cmd/tealdbg/localLedger.go +++ b/cmd/tealdbg/localLedger.go @@ -317,6 +317,10 @@ func (l *localLedger) LookupApplication(rnd basics.Round, addr basics.Address, a return result, nil } +func (l *localLedger) LookupKv(rnd basics.Round, name string) (*string, error) { + return nil, fmt.Errorf("boxes not implemented in debugger") +} + func (l *localLedger) LookupWithoutRewards(rnd basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, error) { ad := l.balances[addr] // Clear RewardsBase since tealdbg has no idea about rewards level so the underlying calculation with reward will fail. diff --git a/config/consensus.go b/config/consensus.go index 07aa9ba84f..ebbd69a785 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -332,6 +332,15 @@ type ConsensusParams struct { // []byte values stored in LocalState or GlobalState key/value stores SchemaBytesMinBalance uint64 + // MBR per box created + BoxFlatMinBalance uint64 + + // MBR per byte in a box + BoxByteMinBalance uint64 + + // Number of box references allowed + MaxAppBoxReferences uint64 + // maximum number of total key/value pairs allowed by a given // LocalStateSchema (and therefore allowed in LocalState) MaxLocalSchemaEntries uint64 @@ -1146,11 +1155,15 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 7 vFuture.MinInnerApplVersion = 4 - vFuture.UnifyInnerTxIDs = true vFuture.EnableSHA256TxnCommitmentHeader = true + // Boxes (unlimited global storage) + vFuture.BoxFlatMinBalance = 2500 + vFuture.BoxByteMinBalance = 400 + vFuture.MaxAppBoxReferences = 8 + Consensus[protocol.ConsensusFuture] = vFuture } diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index d3b85de39a..894622a995 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -326,6 +326,10 @@ func (dl *dryrunLedger) LookupAsset(rnd basics.Round, addr basics.Address, aidx return result, nil } +func (dl *dryrunLedger) LookupKv(rnd basics.Round, key string) (*string, error) { + return nil, fmt.Errorf("boxes not implemented in dry run") +} + func (dl *dryrunLedger) GetCreatorForRound(rnd basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { switch ctype { case basics.AssetCreatable: diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 36d83cf955..a1a554bbb3 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -223,6 +223,12 @@ type AccountData struct { // TotalExtraAppPages stores the extra length in pages (MaxAppProgramLen bytes per page) // requested for app program by this account TotalExtraAppPages uint32 `codec:"teap"` + + // Boxes is all of the boxes associated with this account. (This account must be an app account) + Boxes map[string][]byte + + // TotalBoxBytes stores the sum of all len(keys) and len(values) of Boxes + TotalBoxBytes uint64 } // AppLocalState stores the LocalState associated with an application. It also @@ -469,6 +475,7 @@ func (u AccountData) MinBalance(proto *config.ConsensusParams) (res MicroAlgos) u.TotalAppSchema, uint64(len(u.AppParams)), uint64(len(u.AppLocalStates)), uint64(u.TotalExtraAppPages), + uint64(len(u.Boxes)), u.TotalBoxBytes, ) } @@ -481,6 +488,7 @@ func MinBalance( totalAppSchema StateSchema, totalAppParams uint64, totalAppLocalStates uint64, totalExtraAppPages uint64, + totalBoxes uint64, totalBoxBytes uint64, ) (res MicroAlgos) { var min uint64 @@ -508,6 +516,14 @@ func MinBalance( extraAppProgramLenCost := MulSaturate(proto.AppFlatParamsMinBalance, totalExtraAppPages) min = AddSaturate(min, extraAppProgramLenCost) + // Base MinBalance for each created box + boxBaseCost := MulSaturate(proto.BoxFlatMinBalance, totalBoxes) + min = AddSaturate(min, boxBaseCost) + + // Per byte MinBalance for boxes + boxByteCost := MulSaturate(proto.BoxByteMinBalance, totalBoxBytes) + min = AddSaturate(min, boxByteCost) + res.Raw = min return res } diff --git a/data/transactions/application.go b/data/transactions/application.go index 0db7e72b90..353323f4e6 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -46,6 +46,12 @@ const ( // can contain. Its value is verified against consensus parameters in // TestEncodedAppTxnAllocationBounds encodedMaxForeignAssets = 32 + + // encodedMaxBoxes sets the allocation bound for the maximum + // number of Boxes that a transaction decoded off of the wire + // can contain. Its value is verified against consensus parameters in + // TestEncodedAppTxnAllocationBounds + encodedMaxBoxes = 32 ) // OnCompletion is an enum representing some layer 1 side effect that an @@ -115,6 +121,12 @@ type ApplicationCallTxnFields struct { // by the executing ApprovalProgram or ClearStateProgram. ForeignApps []basics.AppIndex `codec:"apfa,allocbound=encodedMaxForeignApps"` + // Boxes are the boxes that can be accessed by this transaction (and others + // in the same group). The Index in the boxRef is the slot of ForeignApps + // that the name is asscoiated with (shifted by 1, so 0 indicates "current + // app") + Boxes []BoxRef `codec:"apbx,allocbound=encodedMaxBoxes"` + // ForeignAssets are asset IDs for assets whose AssetParams // (and since v4, Holdings) may be read by the executing // ApprovalProgram or ClearStateProgram. @@ -155,6 +167,14 @@ type ApplicationCallTxnFields struct { // method below! } +// BoxRef names a box by the slot +type BoxRef struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Index uint64 `codec:"i"` + Name string `codec:"n"` +} + // Empty indicates whether or not all the fields in the // ApplicationCallTxnFields are zeroed out func (ac *ApplicationCallTxnFields) Empty() bool { @@ -176,6 +196,9 @@ func (ac *ApplicationCallTxnFields) Empty() bool { if ac.ForeignAssets != nil { return false } + if ac.Boxes != nil { + return false + } if ac.LocalStateSchema != (basics.StateSchema{}) { return false } diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go index a076e4b8d0..64777185c7 100644 --- a/data/transactions/application_test.go +++ b/data/transactions/application_test.go @@ -33,7 +33,7 @@ func TestApplicationCallFieldsNotChanged(t *testing.T) { af := ApplicationCallTxnFields{} s := reflect.ValueOf(&af).Elem() - if s.NumField() != 12 { + if s.NumField() != 13 { t.Errorf("You added or removed a field from transactions.ApplicationCallTxnFields. " + "Please ensure you have updated the Empty() method and then " + "fix this test") @@ -76,6 +76,10 @@ func TestApplicationCallFieldsEmpty(t *testing.T) { a.False(ac.Empty()) ac.LocalStateSchema = basics.StateSchema{} + ac.Boxes = make([]BoxRef, 1) + a.False(ac.Empty()) + + ac.Boxes = nil ac.GlobalStateSchema = basics.StateSchema{NumUint: 1} a.False(ac.Empty()) @@ -115,6 +119,9 @@ func TestEncodedAppTxnAllocationBounds(t *testing.T) { if proto.MaxAppTxnForeignAssets > encodedMaxForeignAssets { require.Failf(t, "proto.MaxAppTxnForeignAssets > encodedMaxForeignAssets", "protocol version = %s", protoVer) } + if proto.MaxAppBoxReferences > encodedMaxBoxes { + require.Failf(t, "proto.MaxAppBoxReferences > encodedMaxBoxes", "protocol version = %s", protoVer) + } } } diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 44d68bd1ee..e7e0e4177c 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -609,6 +609,10 @@ Account fields used in the `acct_params_get` opcode. | `app_params_get f` | X is field F from app A. Y is 1 if A exists, else 0 | | `acct_params_get f` | X is field F from account A. Y is 1 if A owns positive algos, else 0 | | `log` | write A to log state of the current application | +| `box_create` | make a box | +| `box_extract` | read from a box | +| `box_replace` | write to a box | +| `box_del` | delete a box | ### Inner Transactions diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index de7c8d8bdd..dc197e5ec1 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1330,6 +1330,38 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Availability: v6 - Mode: Application +## box_create + +- Opcode: 0xb9 +- Stack: ..., A: uint64, B: []byte → ... +- make a box +- Availability: v7 +- Mode: Application + +## box_extract + +- Opcode: 0xba +- Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte +- read from a box +- Availability: v7 +- Mode: Application + +## box_replace + +- Opcode: 0xbb +- Stack: ..., A: []byte, B: uint64, C: []byte → ... +- write to a box +- Availability: v7 +- Mode: Application + +## box_del + +- Opcode: 0xbc +- Stack: ..., A: []byte → ... +- delete a box +- Availability: v7 +- Mode: Application + ## txnas f - Opcode: 0xc0 {uint8 transaction field index} diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 8c719bbfe0..7bb63a31f6 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -361,6 +361,13 @@ pushint 1 gitxnas 0 Logs ` +const boxNonsense = ` + box_create + box_extract + box_replace + box_del +` + const v7Nonsense = v6Nonsense + ` base64_decode URLEncoding json_ref JSONUint64 @@ -383,11 +390,13 @@ dup bn256_scalar_mul dup bn256_pairing -` +` + boxNonsense const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" -const v7Compiled = v6Compiled + "5c005d018120af060180070123456789abcd4949050198800301234549498480030123454999499a499b" +const v7Compiled = v6Compiled + "5c005d018120af060180070123456789abcd4949050198800301234549498480030123454999499a499b" + boxCompiled + +const boxCompiled = "b9babbbc" var nonsense = map[uint64]string{ 1: v1Nonsense, diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go new file mode 100644 index 0000000000..6439758e2e --- /dev/null +++ b/data/transactions/logic/box.go @@ -0,0 +1,121 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic + +import ( + "errors" + "fmt" +) + +func opBoxCreate(cx *EvalContext) error { + last := len(cx.stack) - 1 // name + prev := last - 1 // size + + name := string(cx.stack[last].Bytes) + size := cx.stack[prev].Uint + + // This is questionable! We need to think about how boxes can be made during + // the txgroup that constructs the app. The app won't be funded at create + // time, but supposing someone uses the "trampoline" technique to fund it in + // a later txn, if an even later txn invokes it, can it create any boxes? + if !cx.availableBox(name) { + return fmt.Errorf("invalid Box reference %v", name) + } + err := cx.Ledger.NewBox(cx.appID, name, size) + if err != nil { + return err + } + + cx.stack = cx.stack[:prev] + return nil +} + +func (cx *EvalContext) availableBox(name string) bool { + if available, ok := cx.available.boxes[cx.appID]; ok { + for _, n := range available { + if name == n { + return true + } + } + } + return false +} + +func opBoxExtract(cx *EvalContext) error { + last := len(cx.stack) - 1 // length + prev := last - 1 // start + pprev := prev - 1 // name + + name := string(cx.stack[pprev].Bytes) + start := cx.stack[prev].Uint + length := cx.stack[last].Uint + + if !cx.availableBox(name) { + return fmt.Errorf("invalid Box reference %v", name) + } + box, err := cx.Ledger.GetBox(cx.appID, name) + if err != nil { + return err + } + + end := start + length + if start > uint64(len(box)) || end > uint64(len(box)) { + return errors.New("extract range beyond box") + } + + cx.stack[pprev].Bytes = []byte(box[start:end]) + cx.stack = cx.stack[:prev] + return nil +} + +func opBoxReplace(cx *EvalContext) error { + last := len(cx.stack) - 1 // replacement + prev := last - 1 // start + pprev := prev - 1 // name + + name := string(cx.stack[pprev].Bytes) + start := cx.stack[prev].Uint + replacement := cx.stack[last].Bytes + + if !cx.availableBox(name) { + return fmt.Errorf("invalid Box reference %v", name) + } + box, err := cx.Ledger.GetBox(cx.appID, name) + if err != nil { + return err + } + + end := start + uint64(len(replacement)) + if start > uint64(len(box)) || end > uint64(len(box)) { + return errors.New("replace range beyond box") + } + clone := []byte(box) + copy(clone[start:end], replacement) + cx.stack = cx.stack[:pprev] + return cx.Ledger.SetBox(cx.appID, name, string(clone)) +} + +func opBoxDel(cx *EvalContext) error { + last := len(cx.stack) - 1 // name + name := string(cx.stack[last].Bytes) + + if !cx.availableBox(name) { + return fmt.Errorf("invalid Box reference %v", name) + } + cx.stack = cx.stack[:last] + return cx.Ledger.DelBox(cx.appID, name) +} diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go new file mode 100644 index 0000000000..011337df7d --- /dev/null +++ b/data/transactions/logic/box_test.go @@ -0,0 +1,142 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic_test + +import ( + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestBoxNewDel(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ep, txn, ledger := logic.MakeSampleEnv() + + ledger.NewApp(txn.Sender, 888, basics.AppParams{}) + logic.TestApp(t, "int 24; byte 0x11; box_create; int 1", ep) + logic.TestApp(t, "int 24; byte 0x12; box_create; int 24; byte 0x12; box_create; int 1", ep, + "already exists") + logic.TestApp(t, "int 24; byte 0x13; box_create; int 24; byte 0x14; box_create; int 1", ep) + + logic.TestApp(t, "int 24; byte 0x15; box_create; byte 0x15; box_del; int 1", ep) + logic.TestApp(t, "int 24; byte 0x17; box_del; int 1", ep, "no such box") + logic.TestApp(t, "int 24; byte 0x18; box_create; byte 0x18; box_del; byte 0x18; box_del; int 1", ep, + "no such box") +} + +func TestBoxReadWrite(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ep, txn, ledger := logic.MakeSampleEnv() + + ledger.NewApp(txn.Sender, 888, basics.AppParams{}) + // extract some bytes until past the end, confirm the begin as zeros, and + // when it fails. + logic.TestApp(t, `int 4; byte 0x11; box_create; + byte 0x11; int 1; int 2; box_extract; + byte 0x0000; ==`, ep) + logic.TestApp(t, `int 4; byte 0x12; box_create; + byte 0x12; int 1; int 3; box_extract; + byte 0x000000; ==`, ep) + logic.TestApp(t, `int 4; byte 0x13; box_create; + byte 0x13; int 1; int 4; box_extract; + byte 0x00000000; ==`, ep, "extract range") + logic.TestApp(t, `int 4; byte 0x14; box_create; + byte 0x14; int 0; int 4; box_extract; + byte 0x00000000; ==`, ep) + + // Replace some bytes until past the end, confirm when it fails. + logic.TestApp(t, `int 4; byte 0x15; box_create; + byte 0x15; int 1; byte 0x3031; box_replace; + byte 0x15; int 0; int 4; box_extract; + byte 0x00303100; ==`, ep) + logic.TestApp(t, `int 4; byte 0x16; box_create; + byte 0x16; int 1; byte 0x303132; box_replace; + byte 0x16; int 0; int 4; box_extract; + byte 0x00303132; ==`, ep) + logic.TestApp(t, `int 4; byte 0x17; box_create; + byte 0x17; int 1; byte 0x30313233; box_replace; + byte 0x17; int 0; int 4; box_extract; + byte 0x0030313233; ==`, ep, "replace range") +} + +func TestBoxAcrossTxns(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ledger := logic.NewLedger(nil) + ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) + // After creation in first txn, second one can read it (though it's empty) + logic.TestApps(t, []string{ + `int 64; byte "self"; box_create; int 1`, + `byte "self"; int 10; int 4; box_extract; byte 0x00000000; ==`, + }, nil, 7, ledger) + // after creation, modification, the third can read it + logic.TestApps(t, []string{ + `int 64; byte "self"; box_create; int 1`, + `byte "self"; int 2; byte "hi"; box_replace; int 1`, + `byte "self"; int 1; int 4; box_extract; byte 0x00686900; ==`, // "\0hi\0" + }, nil, 7, ledger) +} + +func TestBoxAvailability(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ledger := logic.NewLedger(nil) + ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) + + // B is not available (recall that "self" is set up by MakeSampleEnv, in TestApps) + logic.TestApps(t, []string{ + `int 64; byte "self"; box_create; int 1`, + `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, + }, nil, 7, ledger, logic.NewExpect(1, "invalid Box reference B")) + + // B is available if indexed by 0 in tx[1].Boxes + group := logic.MakeSampleTxnGroup(logic.MakeSampleTxn(), txntest.Txn{ + Type: "appl", + ApplicationID: 10000, + Boxes: []transactions.BoxRef{{Index: 0, Name: "B"}}, + }.SignedTxn()) + group[0].Txn.Type = protocol.ApplicationCallTx + logic.TestApps(t, []string{ + `int 64; byte "self"; box_create; int 1`, + `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, + }, group, 7, ledger, logic.NewExpect(1, "no such app")) + + // B is available if listed by appId in tx[1].Boxes + group = logic.MakeSampleTxnGroup(logic.MakeSampleTxn(), txntest.Txn{ + Type: "appl", + ApplicationID: 10000, + ForeignApps: []basics.AppIndex{10000}, + Boxes: []transactions.BoxRef{{Index: 1, Name: "B"}}, + }.SignedTxn()) + group[0].Txn.Type = protocol.ApplicationCallTx + logic.TestApps(t, []string{ + `int 64; byte "self"; box_create; int 1`, + `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, + }, group, 7, ledger, logic.NewExpect(1, "no such app")) + +} diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index a8badee03f..33c2cf13fb 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -188,6 +188,11 @@ var opDocByName = map[string]string{ "itxn_next": "begin preparation of a new inner transaction in the same transaction group", "itxn_field": "set field F of the current inner transaction to A", "itxn_submit": "execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.", + + "box_create": "make a box", + "box_extract": "read from a box", + "box_replace": "write to a box", + "box_del": "delete a box", } // OpDoc returns a description of the op @@ -331,7 +336,7 @@ var OpGroups = map[string][]string{ "Byte Array Logic": {"b|", "b&", "b^", "b~"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, - "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log"}, + "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log", "box_create", "box_extract", "box_replace", "box_del"}, "Inner Transactions": {"itxn_begin", "itxn_next", "itxn_field", "itxn_submit", "itxn", "itxna", "itxnas", "gitxn", "gitxna", "gitxnas"}, } diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index f270d91629..bdfdc05a51 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -33,7 +33,9 @@ func TestOpDocs(t *testing.T) { opsSeen[op.Name] = false } for name := range opDocByName { - assert.Contains(t, opsSeen, name, "opDocByName contains strange opcode %#v", name) + if _, ok := opsSeen[name]; !ok { // avoid assert.Contains: printing opsSeen is waste + assert.Fail(t, "opDocByName contains strange opcode", "%#v", name) + } opsSeen[name] = true } for op, seen := range opsSeen { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index f55b61bc93..d02df5c587 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -199,7 +199,7 @@ type LedgerForLogic interface { AccountData(addr basics.Address) (ledgercore.AccountData, error) Authorizer(addr basics.Address) (basics.Address, error) Round() basics.Round - LatestTimestamp() int64 + PrevTimestamp() int64 AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error) AssetParams(aidx basics.AssetIndex) (basics.AssetParams, basics.Address, error) @@ -214,15 +214,22 @@ type LedgerForLogic interface { SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error DelGlobal(appIdx basics.AppIndex, key string) error + NewBox(appIdx basics.AppIndex, key string, size uint64) error + GetBox(appIdx basics.AppIndex, key string) (string, error) + SetBox(appIdx basics.AppIndex, key string, value string) error + DelBox(appIdx basics.AppIndex, key string) error + Perform(gi int, ep *EvalParams) error Counter() uint64 } -// resources contains a list of apps and assets. It's used to track the apps and -// assets created by a txgroup, for "free" access. +// resources contains a catalog of available resources. It's used to track the +// apps, assets, and boxes that are available to a transaction, outside the +// direct foreign array mechanism. type resources struct { - asas []basics.AssetIndex - apps []basics.AppIndex + asas []basics.AssetIndex + apps []basics.AppIndex + boxes map[basics.AppIndex][]string } // EvalParams contains data that comes into condition evaluation. @@ -261,9 +268,12 @@ type EvalParams struct { // Total allowable inner txns in a group transaction (nil before inner pooling enabled) pooledAllowedInners *int - // created contains resources that may be used for "created" - they need not be in - // a foreign array. They remain empty until createdResourcesVersion. - created *resources + // available contains resources that may be used even though they are not + // necessarily directly in the txn's "static arrays". Apps and ASAs go in if + // the app or asa was created earlier in the txgroup (empty until + // createdResourcesVersion). Boxes go in when the ep is created, to share + // availability across all txns in the group. + available *resources // Caching these here means the hashes can be shared across the TxnGroup // (and inners, because the cache is shared with the inner EvalParams) @@ -290,9 +300,32 @@ func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.Sign // NewEvalParams creates an EvalParams to use while evaluating a top-level txgroup func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses) *EvalParams { apps := 0 + var allBoxes map[basics.AppIndex][]string for _, tx := range txgroup { if tx.Txn.Type == protocol.ApplicationCallTx { apps++ + if allBoxes == nil { + allBoxes = make(map[basics.AppIndex][]string) + } + for _, br := range tx.Txn.Boxes { + var app basics.AppIndex + if br.Index == 0 { + // 0 is the "current app". Ignore if this is a create, else use ApplicationID + if tx.Txn.ApplicationID == 0 { + continue + } + app = tx.Txn.ApplicationID + } else { + // Bounds check will already have been done by WellFormed + if br.Index > uint64(len(tx.Txn.ForeignApps)) { + return nil + } + app = tx.Txn.ForeignApps[br.Index-1] // shift for the 0=this convention + } + appBoxes := allBoxes[app] + appBoxes = append(appBoxes, br.Name) + allBoxes[app] = appBoxes + } } } @@ -331,7 +364,7 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens FeeCredit: &credit, PooledApplicationBudget: pooledApplicationBudget, pooledAllowedInners: pooledAllowedInners, - created: &resources{}, + available: &resources{boxes: allBoxes}, appAddrCache: make(map[basics.AppIndex]basics.Address), } } @@ -386,7 +419,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) PooledApplicationBudget: caller.PooledApplicationBudget, pooledAllowedInners: caller.pooledAllowedInners, Ledger: caller.Ledger, - created: caller.created, + available: caller.available, appAddrCache: caller.appAddrCache, caller: caller, } @@ -437,17 +470,17 @@ func (ep *EvalParams) log() logging.Logger { // package. For example, after a acfg transaction is processed, the AD created // by the acfg is added to the EvalParams this way. func (ep *EvalParams) RecordAD(gi int, ad transactions.ApplyData) { - if ep.created == nil { + if ep.available == nil { // This is a simplified ep. It won't be used for app evaluation, and // shares the TxnGroup memory with the caller. Don't touch anything! return } ep.TxnGroup[gi].ApplyData = ad if aid := ad.ConfigAsset; aid != 0 { - ep.created.asas = append(ep.created.asas, aid) + ep.available.asas = append(ep.available.asas, aid) } if aid := ad.ApplicationID; aid != 0 { - ep.created.apps = append(ep.created.apps, aid) + ep.available.apps = append(ep.available.apps, aid) } } @@ -2831,7 +2864,7 @@ func (cx *EvalContext) getRound() uint64 { } func (cx *EvalContext) getLatestTimestamp() (uint64, error) { - ts := cx.Ledger.LatestTimestamp() + ts := cx.Ledger.PrevTimestamp() if ts < 0 { return 0, fmt.Errorf("latest timestamp %d < 0", ts) } @@ -3550,7 +3583,7 @@ func (cx *EvalContext) accountReference(account stackValue) (basics.Address, uin invalidIndex := uint64(len(cx.txn.Txn.Accounts) + 1) // Allow an address for an app that was created in group if err != nil && cx.version >= createdResourcesVersion { - for _, appID := range cx.created.apps { + for _, appID := range cx.available.apps { createdAddress := cx.getApplicationAddress(appID) if addr == createdAddress { return addr, invalidIndex, nil @@ -3906,7 +3939,7 @@ func appReference(cx *EvalContext, ref uint64, foreign bool) (basics.AppIndex, e } // or was created in group if cx.version >= createdResourcesVersion { - for _, appID := range cx.created.apps { + for _, appID := range cx.available.apps { if appID == basics.AppIndex(ref) { return appID, nil } @@ -3945,7 +3978,7 @@ func asaReference(cx *EvalContext, ref uint64, foreign bool) (basics.AssetIndex, } // or was created in group if cx.version >= createdResourcesVersion { - for _, assetID := range cx.created.asas { + for _, assetID := range cx.available.asas { if assetID == basics.AssetIndex(ref) { return assetID, nil } @@ -4234,7 +4267,7 @@ func (cx *EvalContext) availableAsset(sv stackValue) (basics.AssetIndex, error) } // or was created in group if cx.version >= createdResourcesVersion { - for _, assetID := range cx.created.asas { + for _, assetID := range cx.available.asas { if assetID == aid { return aid, nil } @@ -4262,7 +4295,7 @@ func (cx *EvalContext) availableApp(sv stackValue) (basics.AppIndex, error) { } // or was created in group if cx.version >= createdResourcesVersion { - for _, appID := range cx.created.apps { + for _, appID := range cx.available.apps { if appID == aid { return aid, nil } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index f1ae3c40df..1315399d1a 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -253,7 +253,7 @@ func TestRekeyPay(t *testing.T) { TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep) // Note that the Sender would fail min balance check if we did it here. // It seems proper to wait until end of txn though. - // See explanation in logicLedger's Perform() + // See explanation in cowRoundState's Perform() } func TestRekeyBack(t *testing.T) { @@ -496,7 +496,7 @@ func TestNumInnerPooled(t *testing.T) { tx := txntest.Txn{ Type: protocol.ApplicationCallTx, }.SignedTxn() - ledger := MakeLedger(nil) + ledger := NewLedger(nil) ledger.NewApp(tx.Txn.Receiver, 888, basics.AppParams{}) ledger.NewAccount(appAddr(888), 1000000) short := pay + ";int 1" @@ -1705,6 +1705,11 @@ int 1 ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified + // Whenever MakeSampleEnv() is changed to create a different + // transaction, we must reverse those changes here, so that the + // historic test is correct. + parentTx.Boxes = nil + parentTx.ApplicationID = parentAppID parentTx.ForeignApps = []basics.AppIndex{ childAppID, @@ -2026,6 +2031,11 @@ int 1 ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified + // Whenever MakeSampleEnv() is changed to create a different + // transaction, we must reverse those changes here, so that the + // historic test is correct. + parentTx.Boxes = nil + parentTx.ApplicationID = parentAppID parentTx.ForeignApps = []basics.AppIndex{ childAppID, @@ -2496,7 +2506,7 @@ func TestNumInnerDeep(t *testing.T) { ForeignApps: []basics.AppIndex{basics.AppIndex(222)}, }.SignedTxnWithAD() require.Equal(t, 888, int(tx.Txn.ApplicationID)) - ledger := MakeLedger(nil) + ledger := NewLedger(nil) pay3 := TestProg(t, pay+pay+pay+"int 1;", AssemblerMaxVersion).Program ledger.NewApp(tx.Txn.Receiver, 222, basics.AppParams{ diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go index 84a88f6e40..2adb2d9669 100644 --- a/data/transactions/logic/evalCrypto_test.go +++ b/data/transactions/logic/evalCrypto_test.go @@ -41,8 +41,8 @@ import ( func TestKeccak256(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + /* pip install sha3 import sha3 @@ -58,8 +58,8 @@ byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567 func TestSHA3_256(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + /* pip install hashlib import hashlib @@ -74,8 +74,8 @@ byte 0xd757297405c5c89f7ceca368ee76c2f1893ee24f654e60032e65fb53b01aae10 func TestSHA512_256(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + /* pip cryptography from cryptography.hazmat.backends import default_backend @@ -95,8 +95,8 @@ byte 0x98D2C31612EA500279B6753E5F6E780CA63EBA8274049664DAD66A2565ED1D2A func TestEd25519verify(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + var s crypto.Seed crypto.RandBytes(s[:]) c := crypto.GenerateSignatureSecrets(s) @@ -137,8 +137,8 @@ ed25519verify`, pkStr), v) func TestEd25519VerifyBare(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + var s crypto.Seed crypto.RandBytes(s[:]) c := crypto.GenerateSignatureSecrets(s) @@ -483,6 +483,7 @@ ecdsa_verify Secp256r1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.Encod // test compatibility with ethereum signatures func TestEcdsaEthAddress(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() /* pip install eth-keys pycryptodome @@ -512,6 +513,7 @@ byte 0x5ce9454909639d2d17a3f753ce7d93fa0b9ab12e // addr func TestEcdsaCostVariation(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // Doesn't matter if the actual verify returns true or false. Just confirm the cost depends on curve. source := ` diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index e861883a37..ee47b0233c 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -51,7 +51,7 @@ func makeSampleEnv() (*EvalParams, *transactions.Transaction, *Ledger) { func makeSampleEnvWithVersion(version uint64) (*EvalParams, *transactions.Transaction, *Ledger) { ep := defaultEvalParamsWithVersion(nil, version) ep.TxnGroup = transactions.WrapSignedTxnsWithAD(makeSampleTxnGroup(makeSampleTxn())) - ledger := MakeLedger(map[basics.Address]uint64{}) + ledger := NewLedger(nil) ep.Ledger = ledger return ep, &ep.TxnGroup[0].Txn, ledger } @@ -327,7 +327,7 @@ func TestBalance(t *testing.T) { testApp(t, text, ep) } -func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, version uint64, ledger LedgerForLogic, +func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, version uint64, ledger *Ledger, expected ...Expect) { t.Helper() codes := make([][]byte, len(programs)) @@ -347,8 +347,9 @@ func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, } ep := NewEvalParams(transactions.WrapSignedTxnsWithAD(txgroup), makeTestProtoV(version), &transactions.SpecialAddresses{}) if ledger == nil { - ledger = MakeLedger(nil) + ledger = NewLedger(nil) } + ledger.Reset() ep.Ledger = ledger testAppsBytes(t, codes, ep, expected...) } @@ -358,11 +359,15 @@ func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ... require.Equal(t, len(programs), len(ep.TxnGroup)) for i := range ep.TxnGroup { if programs[i] != nil { + appId := ep.TxnGroup[i].Txn.ApplicationID + if appId == 0 { + appId = basics.AppIndex(888) + } if len(expected) > 0 && expected[0].l == i { - testAppFull(t, programs[i], i, basics.AppIndex(888), ep, expected[0].s) + testAppFull(t, programs[i], i, appId, ep, expected[0].s) break // Stop after first failure } else { - testAppFull(t, programs[i], i, basics.AppIndex(888), ep) + testAppFull(t, programs[i], i, appId, ep) } } } @@ -422,7 +427,7 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep * // the best way to be concise about all sorts of tests. if ep.Ledger == nil { - ep.Ledger = MakeLedger(nil) + ep.Ledger = NewLedger(nil) } pass, err := EvalApp(program, gi, aid, ep) @@ -497,7 +502,7 @@ func TestAppCheckOptedIn(t *testing.T) { pre := defaultEvalParamsWithVersion(&txn, directRefEnabledVersion-1) pre.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Receiver: 1, txn.Txn.Sender: 1, @@ -693,7 +698,6 @@ int 0 func TestAppReadGlobalState(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() text := `int 0 @@ -921,7 +925,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) pre := defaultEvalParamsWithVersion(&txn, directRefEnabledVersion-1) require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) now := defaultEvalParamsWithVersion(&txn, version) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1235,7 +1239,7 @@ intc_1 err := CheckContract(ops.Program, ep) require.NoError(t, err) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1289,7 +1293,7 @@ func TestAppLocalStateReadWrite(t *testing.T) { txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep := defaultEvalParams(&txn) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1638,7 +1642,7 @@ int 0x77 txn.Txn.ApplicationID = 100 txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} ep := defaultEvalParams(&txn) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1777,7 +1781,7 @@ byte "myval" txn.Txn.ApplicationID = 100 txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID, 101} ep := defaultEvalParams(&txn) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1822,7 +1826,7 @@ int 7 txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep := defaultEvalParams(&txn) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1868,7 +1872,7 @@ int 1 txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep := defaultEvalParams(&txn) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -2034,7 +2038,7 @@ int 1 txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep := defaultEvalParams(&txn) - ledger := MakeLedger( + ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -2194,6 +2198,7 @@ int 1 func TestEnumFieldErrors(t *testing.T) { partitiontest.PartitionTest(t) + // t.Parallel() NO! manipulates globalFieldSpecs source := `txn Amount` origSpec := txnFieldSpecs[Amount] @@ -2268,13 +2273,13 @@ assert func TestReturnTypes(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // Ensure all opcodes return values they are supposed to according to the OpSpecs table - t.Parallel() typeToArg := map[StackType]string{ StackUint64: "int 1\n", StackAny: "int 1\n", - StackBytes: "byte 0x33343536\n", + StackBytes: "byte 0x33343536\n", // Which is the string "3456" } ep, tx, ledger := makeSampleEnv() @@ -2317,7 +2322,7 @@ func TestReturnTypes(t *testing.T) { ledger.NewAccount(appAddr(1), 1000000) // We try to form a snippet that will test every opcode, by sandwiching it - // between arguments that correspond to the opcodes input types, and then + // between arguments that correspond to the opcode's input types, and then // check to see if the proper output types end up on the stack. But many // opcodes require more specific inputs than a constant string or the number // 1 for ints. Defaults are also supplied for immediate arguments. For @@ -2392,6 +2397,13 @@ func TestReturnTypes(t *testing.T) { "bn256_add": true, "bn256_scalar_mul": true, "bn256_pairing": true, + + // It's too annoying to set things up for them to work in this context. + // Tested in box_test.go + "box_create": true, + "box_extract": true, + "box_replace": true, + "box_del": true, } byName := OpsByName[LogicVersion] @@ -2565,7 +2577,7 @@ func TestPooledAppCallsVerifyOp(t *testing.T) { pop int 1` - ledger := MakeLedger(nil) + ledger := NewLedger(nil) call := transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}} // Simulate test with 2 grouped txn testApps(t, []string{source, ""}, []transactions.SignedTxn{call, call}, LogicVersion, ledger, diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 9e54e7b291..16d20cdbd2 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -154,7 +154,7 @@ func (ep *EvalParams) reset() { for i := range ep.TxnGroup { ep.TxnGroup[i].ApplyData = transactions.ApplyData{} } - ep.created = &resources{} + ep.available = &resources{} ep.appAddrCache = make(map[basics.AppIndex]basics.Address) ep.Trace = &strings.Builder{} } @@ -413,22 +413,22 @@ func TestTLHC(t *testing.T) { func TestU64Math(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + testAccepts(t, "int 0x1234567812345678; int 0x100000000; /; int 0x12345678; ==", 1) } func TestItob(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + testAccepts(t, "byte 0x1234567812345678; int 0x1234567812345678; itob; ==", 1) } func TestBtoi(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + testAccepts(t, "int 0x1234567812345678; byte 0x1234567812345678; btoi; ==", 1) testAccepts(t, "int 0x34567812345678; byte 0x34567812345678; btoi; ==", 1) testAccepts(t, "int 0x567812345678; byte 0x567812345678; btoi; ==", 1) @@ -1011,7 +1011,7 @@ func TestGlobal(t *testing.T) { require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) require.Len(t, globalFieldSpecs, int(invalidGlobalField)) - ledger := MakeLedger(nil) + ledger := NewLedger(nil) addr, err := basics.UnmarshalChecksumAddress(testAddr) require.NoError(t, err) ledger.NewApp(addr, 888, basics.AppParams{}) @@ -1517,6 +1517,7 @@ func makeSampleTxn() transactions.SignedTxn { txn.Txn.AssetFrozen = true txn.Txn.ForeignAssets = []basics.AssetIndex{55, 77} txn.Txn.ForeignApps = []basics.AppIndex{56, 100, 111} // 100 must be 2nd, 111 must be present + txn.Txn.Boxes = []transactions.BoxRef{{Index: 0, Name: "self"}, {Index: 2, Name: "hundy"}} txn.Txn.GlobalStateSchema = basics.StateSchema{NumUint: 3, NumByteSlice: 0} txn.Txn.LocalStateSchema = basics.StateSchema{NumUint: 1, NumByteSlice: 2} return txn @@ -1702,7 +1703,7 @@ func TestGaid(t *testing.T) { txgroup[0] = targetTxn ep := defaultEvalParams(nil) ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup) - ep.Ledger = MakeLedger(nil) + ep.Ledger = NewLedger(nil) // should fail when no creatable was created _, err := EvalApp(check0.Program, 1, 888, ep) @@ -2472,9 +2473,9 @@ int 1`, } if testCase.errContains != "" { - testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil), Expect{testCase.errTxn, testCase.errContains}) + testApps(t, sources, txgroup, LogicVersion, nil, Expect{testCase.errTxn, testCase.errContains}) } else { - testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil)) + testApps(t, sources, txgroup, LogicVersion, nil) } }) } @@ -2580,7 +2581,7 @@ int 1 txgroup[j].Txn.Type = protocol.ApplicationCallTx } - testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil)) + testApps(t, sources, txgroup, LogicVersion, nil) } const testCompareProgramText = `int 35 @@ -4485,7 +4486,7 @@ func TestLog(t *testing.T) { t.Parallel() var txn transactions.SignedTxn txn.Txn.Type = protocol.ApplicationCallTx - ledger := MakeLedger(nil) + ledger := NewLedger(nil) ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) ep := defaultEvalParams(&txn) ep.Proto = makeTestProtoV(LogicVersion) @@ -4825,7 +4826,7 @@ func TestOpJSONRef(t *testing.T) { Type: protocol.ApplicationCallTx, }, } - ledger := MakeLedger(nil) + ledger := NewLedger(nil) ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) ep := defaultEvalParams(&txn) ep.Proto = proto diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 4a8128c87e..b3281ed082 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -41,7 +41,7 @@ func TestGlobalFieldsVersions(t *testing.T) { } require.Greater(t, len(fields), 1) - ledger := MakeLedger(nil) + ledger := NewLedger(nil) for _, field := range fields { text := fmt.Sprintf("global %s", field.field.String()) // check assembler fails if version before introduction @@ -101,7 +101,7 @@ func TestTxnFieldVersions(t *testing.T) { } txnaVersion := uint64(appsEnabledVersion) - ledger := MakeLedger(nil) + ledger := NewLedger(nil) txn := makeSampleTxn() // We'll reject too early if we have a nonzero RekeyTo, because that // field must be zero for every txn in the group if this is an old @@ -190,7 +190,7 @@ func TestTxnEffectsAvailable(t *testing.T) { ep.TxnGroup[1].Lsig.Logic = ops.Program _, err := EvalSignature(1, ep) require.Error(t, err) - ep.Ledger = MakeLedger(nil) + ep.Ledger = NewLedger(nil) _, err = EvalApp(ops.Program, 1, 888, ep) if v < txnEffectsVersion { require.Error(t, err, source) diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 1d978db8ec..7e39716479 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -2148,6 +2148,47 @@ "Inner Transactions" ] }, + { + "Opcode": 185, + "Name": "box_create", + "Args": "UB", + "Size": 1, + "Doc": "make a box", + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 186, + "Name": "box_extract", + "Args": "BUU", + "Returns": "B", + "Size": 1, + "Doc": "read from a box", + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 187, + "Name": "box_replace", + "Args": "BUB", + "Size": 1, + "Doc": "write to a box", + "Groups": [ + "State Access" + ] + }, + { + "Opcode": 188, + "Name": "box_del", + "Args": "B", + "Size": 1, + "Doc": "delete a box", + "Groups": [ + "State Access" + ] + }, { "Opcode": 192, "Name": "txnas", diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 5df6102e17..0db9ad1685 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -36,7 +36,7 @@ type balanceRecord struct { mods map[basics.AppIndex]map[string]basics.ValueDelta } -func makeBalanceRecord(addr basics.Address, balance uint64) balanceRecord { +func newBalanceRecord(addr basics.Address, balance uint64) balanceRecord { br := balanceRecord{ addr: addr, balance: balance, @@ -52,6 +52,8 @@ func makeBalanceRecord(addr basics.Address, balance uint64) balanceRecord { type appParams struct { basics.AppParams Creator basics.Address + + boxes map[string]string } type asaParams struct { @@ -68,8 +70,8 @@ type Ledger struct { rnd basics.Round } -// MakeLedger constructs a Ledger with the given balances. -func MakeLedger(balances map[basics.Address]uint64) *Ledger { +// NewLedger constructs a Ledger with the given balances. +func NewLedger(balances map[basics.Address]uint64) *Ledger { l := new(Ledger) l.balances = make(map[basics.Address]balanceRecord) for addr, balance := range balances { @@ -88,11 +90,15 @@ func (l *Ledger) Reset() { br.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) l.balances[addr] = br } + for id, app := range l.applications { + app.boxes = nil + l.applications[id] = app + } } // NewAccount adds a new account with a given balance to the Ledger. func (l *Ledger) NewAccount(addr basics.Address, balance uint64) { - l.balances[addr] = makeBalanceRecord(addr, balance) + l.balances[addr] = newBalanceRecord(addr, balance) } // NewApp add a new AVM app to the Ledger. In most uses, it only sets up the id @@ -117,7 +123,7 @@ func (l *Ledger) NewAsset(creator basics.Address, assetID basics.AssetIndex, par } br, ok := l.balances[creator] if !ok { - br = makeBalanceRecord(creator, 0) + br = newBalanceRecord(creator, 0) } br.holdings[assetID] = basics.AssetHolding{Amount: params.Total, Frozen: params.DefaultFrozen} l.balances[creator] = br @@ -144,7 +150,7 @@ func (l *Ledger) Counter() uint64 { func (l *Ledger) NewHolding(addr basics.Address, assetID uint64, amount uint64, frozen bool) { br, ok := l.balances[addr] if !ok { - br = makeBalanceRecord(addr, 0) + br = newBalanceRecord(addr, 0) } br.holdings[basics.AssetIndex(assetID)] = basics.AssetHolding{Amount: amount, Frozen: frozen} l.balances[addr] = br @@ -153,7 +159,7 @@ func (l *Ledger) NewHolding(addr basics.Address, assetID uint64, amount uint64, // NewLocals essentially "opts in" an address to an app id. func (l *Ledger) NewLocals(addr basics.Address, appID uint64) { if _, ok := l.balances[addr]; !ok { - l.balances[addr] = makeBalanceRecord(addr, 0) + l.balances[addr] = newBalanceRecord(addr, 0) } l.balances[addr].locals[basics.AppIndex(appID)] = basics.TealKeyValue{} } @@ -186,14 +192,9 @@ func (l *Ledger) Rekey(addr basics.Address, auth basics.Address) { } } -// Round gives the current Round of the test ledger, which is random but consistent -func (l *Ledger) Round() basics.Round { - return l.round() -} - // LatestTimestamp gives a uint64, chosen randomly. It should // probably increase monotonically, but no tests care yet. -func (l *Ledger) LatestTimestamp() int64 { +func (l *Ledger) PrevTimestamp() int64 { return int64(rand.Uint32() + 1) } @@ -330,6 +331,69 @@ func (l *Ledger) DelGlobal(appIdx basics.AppIndex, key string) error { return nil } +func (l *Ledger) NewBox(appIdx basics.AppIndex, key string, size uint64) error { + params, ok := l.applications[appIdx] + if !ok { + return fmt.Errorf("no such app %d", appIdx) + } + if params.boxes == nil { + params.boxes = make(map[string]string) + } + if _, ok = params.boxes[key]; ok { + return fmt.Errorf("box already exists %#v", key) + } + params.boxes[key] = string(make([]byte, size)) + l.applications[appIdx] = params + return nil +} + +func (l *Ledger) GetBox(appIdx basics.AppIndex, key string) (string, error) { + params, ok := l.applications[appIdx] + if !ok { + return "", fmt.Errorf("no such app %d", appIdx) + } + if params.boxes == nil { + return "", fmt.Errorf("no such box %#v", key) + } + if box, ok := params.boxes[key]; ok { + return box, nil + } + return "", fmt.Errorf("no such box %#v", key) +} + +func (l *Ledger) SetBox(appIdx basics.AppIndex, key string, value string) error { + params, ok := l.applications[appIdx] + if !ok { + return fmt.Errorf("no such app %d", appIdx) + } + if params.boxes == nil { + return fmt.Errorf("no such box %#v", key) + } + if box, ok := params.boxes[key]; ok { + if len(box) != len(value) { + return fmt.Errorf("wrong box size %#v %d != %d", key, len(box), len(value)) + } + params.boxes[key] = value + return nil + } + return fmt.Errorf("no such box %#v", key) +} + +func (l *Ledger) DelBox(appIdx basics.AppIndex, key string) error { + params, ok := l.applications[appIdx] + if !ok { + return fmt.Errorf("no such app %d", appIdx) + } + if params.boxes == nil { + return fmt.Errorf("boxes %#v", key) + } + if _, ok := params.boxes[key]; !ok { + return fmt.Errorf("no such box %#v", key) + } + delete(params.boxes, key) + return nil +} + // GetLocal returns the current value bound to a local key, taking // into account mods caused by earlier executions. func (l *Ledger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) { @@ -458,11 +522,11 @@ func (l *Ledger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Addr func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) error { fbr, ok := l.balances[from] if !ok { - fbr = makeBalanceRecord(from, 0) + fbr = newBalanceRecord(from, 0) } tbr, ok := l.balances[to] if !ok { - tbr = makeBalanceRecord(to, 0) + tbr = newBalanceRecord(to, 0) } if fbr.balance < amount { return fmt.Errorf("insufficient balance") @@ -524,7 +588,7 @@ func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFi fbr, ok := l.balances[from] if !ok { - fbr = makeBalanceRecord(from, 0) + fbr = newBalanceRecord(from, 0) } fholding, ok := fbr.holdings[aid] if !ok { @@ -545,7 +609,7 @@ func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFi } tbr, ok := l.balances[to] if !ok { - tbr = makeBalanceRecord(to, 0) + tbr = newBalanceRecord(to, 0) } tholding, ok := tbr.holdings[aid] if !ok && amount > 0 { @@ -573,7 +637,7 @@ func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFi if !close.IsZero() && fholding.Amount > 0 { cbr, ok := l.balances[close] if !ok { - cbr = makeBalanceRecord(close, 0) + cbr = newBalanceRecord(close, 0) } cholding, ok := cbr.holdings[aid] if !ok { @@ -800,7 +864,7 @@ func (l *Ledger) DelKey(addr basics.Address, aidx basics.AppIndex, global bool, return nil } -func (l *Ledger) round() basics.Round { +func (l *Ledger) Round() basics.Round { if l.rnd == basics.Round(0) { l.rnd = basics.Round(rand.Uint32() + 1) } diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 1d26d437a0..9952002ee1 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -68,6 +68,9 @@ const appAddressAvailableVersion = 7 const fidoVersion = 7 // base64, json, secp256r1 const pairingVersion = 7 // bn256 opcodes. will add bls12-381, and unify the available opcodes.// experimental- +// Unlimited Global Storage opcodes +const boxVersion = 7 // box_* + type linearCost struct { baseCost int chunkCost int @@ -578,6 +581,12 @@ var OpSpecs = []OpSpec{ {0xb7, "gitxn", opGitxn, proto(":a"), 6, immediates("t", "f").field("f", &TxnFields).only(modeApp).assembler(asmGitxn)}, {0xb8, "gitxna", opGitxna, proto(":a"), 6, immediates("t", "f", "i").field("f", &TxnArrayFields).only(modeApp)}, + // Unlimited Global Storage - Boxes + {0xb9, "box_create", opBoxCreate, proto("ib:"), boxVersion, only(modeApp)}, + {0xba, "box_extract", opBoxExtract, proto("bii:b"), boxVersion, only(modeApp)}, + {0xbb, "box_replace", opBoxReplace, proto("bib:"), boxVersion, only(modeApp)}, + {0xbc, "box_del", opBoxDel, proto("b:"), boxVersion, only(modeApp)}, + // Dynamic indexing {0xc0, "txnas", opTxnas, proto("i:a"), 5, field("f", &TxnArrayFields)}, {0xc1, "gtxnas", opGtxnas, proto("i:a"), 5, immediates("t", "f").field("f", &TxnArrayFields)}, diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 5219dbcd45..e5e27b8894 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -72,7 +72,7 @@ }, { "name": "keyword.other.unit.teal", - "match": "^(acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|log|min_balance)\\b" + "match": "^(acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|box_create|box_del|box_extract|box_replace|log|min_balance)\\b" }, { "name": "keyword.operator.teal", diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 299d4da243..55d787b00d 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -53,6 +53,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// BoxRef +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // CompactCertTxnFields // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -178,56 +186,60 @@ import ( func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0005Len := uint32(11) - var zb0005Mask uint16 /* 12 bits */ + zb0006Len := uint32(12) + var zb0006Mask uint16 /* 13 bits */ if len((*z).ApplicationArgs) == 0 { - zb0005Len-- - zb0005Mask |= 0x2 + zb0006Len-- + zb0006Mask |= 0x2 } if (*z).OnCompletion == 0 { - zb0005Len-- - zb0005Mask |= 0x4 + zb0006Len-- + zb0006Mask |= 0x4 } if len((*z).ApprovalProgram) == 0 { - zb0005Len-- - zb0005Mask |= 0x8 + zb0006Len-- + zb0006Mask |= 0x8 } if len((*z).ForeignAssets) == 0 { - zb0005Len-- - zb0005Mask |= 0x10 + zb0006Len-- + zb0006Mask |= 0x10 } if len((*z).Accounts) == 0 { - zb0005Len-- - zb0005Mask |= 0x20 + zb0006Len-- + zb0006Mask |= 0x20 + } + if len((*z).Boxes) == 0 { + zb0006Len-- + zb0006Mask |= 0x40 } if (*z).ExtraProgramPages == 0 { - zb0005Len-- - zb0005Mask |= 0x40 + zb0006Len-- + zb0006Mask |= 0x80 } if len((*z).ForeignApps) == 0 { - zb0005Len-- - zb0005Mask |= 0x80 + zb0006Len-- + zb0006Mask |= 0x100 } if (*z).GlobalStateSchema.MsgIsZero() { - zb0005Len-- - zb0005Mask |= 0x100 + zb0006Len-- + zb0006Mask |= 0x200 } if (*z).ApplicationID.MsgIsZero() { - zb0005Len-- - zb0005Mask |= 0x200 + zb0006Len-- + zb0006Mask |= 0x400 } if (*z).LocalStateSchema.MsgIsZero() { - zb0005Len-- - zb0005Mask |= 0x400 + zb0006Len-- + zb0006Mask |= 0x800 } if len((*z).ClearStateProgram) == 0 { - zb0005Len-- - zb0005Mask |= 0x800 + zb0006Len-- + zb0006Mask |= 0x1000 } - // variable map header, size zb0005Len - o = append(o, 0x80|uint8(zb0005Len)) - if zb0005Len != 0 { - if (zb0005Mask & 0x2) == 0 { // if not empty + // variable map header, size zb0006Len + o = append(o, 0x80|uint8(zb0006Len)) + if zb0006Len != 0 { + if (zb0006Mask & 0x2) == 0 { // if not empty // string "apaa" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x61) if (*z).ApplicationArgs == nil { @@ -239,17 +251,17 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendBytes(o, (*z).ApplicationArgs[zb0001]) } } - if (zb0005Mask & 0x4) == 0 { // if not empty + if (zb0006Mask & 0x4) == 0 { // if not empty // string "apan" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x6e) o = msgp.AppendUint64(o, uint64((*z).OnCompletion)) } - if (zb0005Mask & 0x8) == 0 { // if not empty + if (zb0006Mask & 0x8) == 0 { // if not empty // string "apap" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x70) o = msgp.AppendBytes(o, (*z).ApprovalProgram) } - if (zb0005Mask & 0x10) == 0 { // if not empty + if (zb0006Mask & 0x10) == 0 { // if not empty // string "apas" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x73) if (*z).ForeignAssets == nil { @@ -257,11 +269,11 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).ForeignAssets))) } - for zb0004 := range (*z).ForeignAssets { - o = (*z).ForeignAssets[zb0004].MarshalMsg(o) + for zb0005 := range (*z).ForeignAssets { + o = (*z).ForeignAssets[zb0005].MarshalMsg(o) } } - if (zb0005Mask & 0x20) == 0 { // if not empty + if (zb0006Mask & 0x20) == 0 { // if not empty // string "apat" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x74) if (*z).Accounts == nil { @@ -273,12 +285,46 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { o = (*z).Accounts[zb0002].MarshalMsg(o) } } - if (zb0005Mask & 0x40) == 0 { // if not empty + if (zb0006Mask & 0x40) == 0 { // if not empty + // string "apbx" + o = append(o, 0xa4, 0x61, 0x70, 0x62, 0x78) + if (*z).Boxes == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).Boxes))) + } + for zb0004 := range (*z).Boxes { + // omitempty: check for empty values + zb0007Len := uint32(2) + var zb0007Mask uint8 /* 3 bits */ + if (*z).Boxes[zb0004].Index == 0 { + zb0007Len-- + zb0007Mask |= 0x2 + } + if (*z).Boxes[zb0004].Name == "" { + zb0007Len-- + zb0007Mask |= 0x4 + } + // variable map header, size zb0007Len + o = append(o, 0x80|uint8(zb0007Len)) + if (zb0007Mask & 0x2) == 0 { // if not empty + // string "i" + o = append(o, 0xa1, 0x69) + o = msgp.AppendUint64(o, (*z).Boxes[zb0004].Index) + } + if (zb0007Mask & 0x4) == 0 { // if not empty + // string "n" + o = append(o, 0xa1, 0x6e) + o = msgp.AppendString(o, (*z).Boxes[zb0004].Name) + } + } + } + if (zb0006Mask & 0x80) == 0 { // if not empty // string "apep" o = append(o, 0xa4, 0x61, 0x70, 0x65, 0x70) o = msgp.AppendUint32(o, (*z).ExtraProgramPages) } - if (zb0005Mask & 0x80) == 0 { // if not empty + if (zb0006Mask & 0x100) == 0 { // if not empty // string "apfa" o = append(o, 0xa4, 0x61, 0x70, 0x66, 0x61) if (*z).ForeignApps == nil { @@ -290,22 +336,22 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { o = (*z).ForeignApps[zb0003].MarshalMsg(o) } } - if (zb0005Mask & 0x100) == 0 { // if not empty + if (zb0006Mask & 0x200) == 0 { // if not empty // string "apgs" o = append(o, 0xa4, 0x61, 0x70, 0x67, 0x73) o = (*z).GlobalStateSchema.MarshalMsg(o) } - if (zb0005Mask & 0x200) == 0 { // if not empty + if (zb0006Mask & 0x400) == 0 { // if not empty // string "apid" o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) o = (*z).ApplicationID.MarshalMsg(o) } - if (zb0005Mask & 0x400) == 0 { // if not empty + if (zb0006Mask & 0x800) == 0 { // if not empty // string "apls" o = append(o, 0xa4, 0x61, 0x70, 0x6c, 0x73) o = (*z).LocalStateSchema.MarshalMsg(o) } - if (zb0005Mask & 0x800) == 0 { // if not empty + if (zb0006Mask & 0x1000) == 0 { // if not empty // string "apsu" o = append(o, 0xa4, 0x61, 0x70, 0x73, 0x75) o = msgp.AppendBytes(o, (*z).ClearStateProgram) @@ -323,55 +369,55 @@ func (_ *ApplicationCallTxnFields) CanMarshalMsg(z interface{}) bool { func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field - var zb0005 int - var zb0006 bool - zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0006 int + var zb0007 bool + zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0005 > 0 { - zb0005-- + if zb0006 > 0 { + zb0006-- bts, err = (*z).ApplicationID.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationID") return } } - if zb0005 > 0 { - zb0005-- + if zb0006 > 0 { + zb0006-- { - var zb0007 uint64 - zb0007, bts, err = msgp.ReadUint64Bytes(bts) + var zb0008 uint64 + zb0008, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OnCompletion") return } - (*z).OnCompletion = OnCompletion(zb0007) + (*z).OnCompletion = OnCompletion(zb0008) } } - if zb0005 > 0 { - zb0005-- - var zb0008 int - var zb0009 bool - zb0008, zb0009, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0006 > 0 { + zb0006-- + var zb0009 int + var zb0010 bool + zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0008 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0008), uint64(encodedMaxApplicationArgs)) + if zb0009 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0009), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0009 { + if zb0010 { (*z).ApplicationArgs = nil - } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0008 { - (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0008] + } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0009 { + (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0009] } else { - (*z).ApplicationArgs = make([][]byte, zb0008) + (*z).ApplicationArgs = make([][]byte, zb0009) } for zb0001 := range (*z).ApplicationArgs { (*z).ApplicationArgs[zb0001], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationArgs[zb0001]) @@ -381,26 +427,26 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } } } - if zb0005 > 0 { - zb0005-- - var zb0010 int - var zb0011 bool - zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0006 > 0 { + zb0006-- + var zb0011 int + var zb0012 bool + zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0010 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0010), uint64(encodedMaxAccounts)) + if zb0011 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0011), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0011 { + if zb0012 { (*z).Accounts = nil - } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0010 { - (*z).Accounts = ((*z).Accounts)[:zb0010] + } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0011 { + (*z).Accounts = ((*z).Accounts)[:zb0011] } else { - (*z).Accounts = make([]basics.Address, zb0010) + (*z).Accounts = make([]basics.Address, zb0011) } for zb0002 := range (*z).Accounts { bts, err = (*z).Accounts[zb0002].UnmarshalMsg(bts) @@ -410,26 +456,26 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } } } - if zb0005 > 0 { - zb0005-- - var zb0012 int - var zb0013 bool - zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0006 > 0 { + zb0006-- + var zb0013 int + var zb0014 bool + zb0013, zb0014, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0012 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0012), uint64(encodedMaxForeignApps)) + if zb0013 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0013), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0013 { + if zb0014 { (*z).ForeignApps = nil - } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0012 { - (*z).ForeignApps = ((*z).ForeignApps)[:zb0012] + } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0013 { + (*z).ForeignApps = ((*z).ForeignApps)[:zb0013] } else { - (*z).ForeignApps = make([]basics.AppIndex, zb0012) + (*z).ForeignApps = make([]basics.AppIndex, zb0013) } for zb0003 := range (*z).ForeignApps { bts, err = (*z).ForeignApps[zb0003].UnmarshalMsg(bts) @@ -439,61 +485,154 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } } } - if zb0005 > 0 { - zb0005-- - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0006 > 0 { + zb0006-- + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes") + return + } + if zb0015 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0015), uint64(encodedMaxBoxes)) + err = msgp.WrapError(err, "struct-from-array", "Boxes") + return + } + if zb0016 { + (*z).Boxes = nil + } else if (*z).Boxes != nil && cap((*z).Boxes) >= zb0015 { + (*z).Boxes = ((*z).Boxes)[:zb0015] + } else { + (*z).Boxes = make([]BoxRef, zb0015) + } + for zb0004 := range (*z).Boxes { + var zb0017 int + var zb0018 bool + zb0017, zb0018, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + return + } + if zb0017 > 0 { + zb0017-- + (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array", "Index") + return + } + } + if zb0017 > 0 { + zb0017-- + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array", "Name") + return + } + } + if zb0017 > 0 { + err = msgp.ErrTooManyArrayFields(zb0017) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + return + } + if zb0018 { + (*z).Boxes[zb0004] = BoxRef{} + } + for zb0017 > 0 { + zb0017-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + return + } + switch string(field) { + case "i": + (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "Index") + return + } + case "n": + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "Name") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004) + return + } + } + } + } + } + } + if zb0006 > 0 { + zb0006-- + var zb0019 int + var zb0020 bool + zb0019, zb0020, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") return } - if zb0014 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0014), uint64(encodedMaxForeignAssets)) + if zb0019 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0019), uint64(encodedMaxForeignAssets)) err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") return } - if zb0015 { + if zb0020 { (*z).ForeignAssets = nil - } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0014 { - (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0014] + } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0019 { + (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0019] } else { - (*z).ForeignAssets = make([]basics.AssetIndex, zb0014) + (*z).ForeignAssets = make([]basics.AssetIndex, zb0019) } - for zb0004 := range (*z).ForeignAssets { - bts, err = (*z).ForeignAssets[zb0004].UnmarshalMsg(bts) + for zb0005 := range (*z).ForeignAssets { + bts, err = (*z).ForeignAssets[zb0005].UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0004) + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0005) return } } } - if zb0005 > 0 { - zb0005-- + if zb0006 > 0 { + zb0006-- bts, err = (*z).LocalStateSchema.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LocalStateSchema") return } } - if zb0005 > 0 { - zb0005-- + if zb0006 > 0 { + zb0006-- bts, err = (*z).GlobalStateSchema.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GlobalStateSchema") return } } - if zb0005 > 0 { - zb0005-- - var zb0016 int - zb0016, err = msgp.ReadBytesBytesHeader(bts) + if zb0006 > 0 { + zb0006-- + var zb0021 int + zb0021, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApprovalProgram") return } - if zb0016 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0016), uint64(config.MaxAvailableAppProgramLen)) + if zb0021 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0021), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApprovalProgram) @@ -502,16 +641,16 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error return } } - if zb0005 > 0 { - zb0005-- - var zb0017 int - zb0017, err = msgp.ReadBytesBytesHeader(bts) + if zb0006 > 0 { + zb0006-- + var zb0022 int + zb0022, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ClearStateProgram") return } - if zb0017 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0017), uint64(config.MaxAvailableAppProgramLen)) + if zb0022 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0022), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ClearStateProgram) @@ -520,16 +659,16 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error return } } - if zb0005 > 0 { - zb0005-- + if zb0006 > 0 { + zb0006-- (*z).ExtraProgramPages, bts, err = msgp.ReadUint32Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExtraProgramPages") return } } - if zb0005 > 0 { - err = msgp.ErrTooManyArrayFields(zb0005) + if zb0006 > 0 { + err = msgp.ErrTooManyArrayFields(zb0006) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -540,11 +679,11 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error err = msgp.WrapError(err) return } - if zb0006 { + if zb0007 { (*z) = ApplicationCallTxnFields{} } - for zb0005 > 0 { - zb0005-- + for zb0006 > 0 { + zb0006-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -559,33 +698,33 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } case "apan": { - var zb0018 uint64 - zb0018, bts, err = msgp.ReadUint64Bytes(bts) + var zb0023 uint64 + zb0023, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "OnCompletion") return } - (*z).OnCompletion = OnCompletion(zb0018) + (*z).OnCompletion = OnCompletion(zb0023) } case "apaa": - var zb0019 int - var zb0020 bool - zb0019, zb0020, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0024 int + var zb0025 bool + zb0024, zb0025, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0019 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0019), uint64(encodedMaxApplicationArgs)) + if zb0024 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0024), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0020 { + if zb0025 { (*z).ApplicationArgs = nil - } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0019 { - (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0019] + } else if (*z).ApplicationArgs != nil && cap((*z).ApplicationArgs) >= zb0024 { + (*z).ApplicationArgs = ((*z).ApplicationArgs)[:zb0024] } else { - (*z).ApplicationArgs = make([][]byte, zb0019) + (*z).ApplicationArgs = make([][]byte, zb0024) } for zb0001 := range (*z).ApplicationArgs { (*z).ApplicationArgs[zb0001], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationArgs[zb0001]) @@ -595,24 +734,24 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } } case "apat": - var zb0021 int - var zb0022 bool - zb0021, zb0022, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0026 int + var zb0027 bool + zb0026, zb0027, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Accounts") return } - if zb0021 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0021), uint64(encodedMaxAccounts)) + if zb0026 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0026), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "Accounts") return } - if zb0022 { + if zb0027 { (*z).Accounts = nil - } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0021 { - (*z).Accounts = ((*z).Accounts)[:zb0021] + } else if (*z).Accounts != nil && cap((*z).Accounts) >= zb0026 { + (*z).Accounts = ((*z).Accounts)[:zb0026] } else { - (*z).Accounts = make([]basics.Address, zb0021) + (*z).Accounts = make([]basics.Address, zb0026) } for zb0002 := range (*z).Accounts { bts, err = (*z).Accounts[zb0002].UnmarshalMsg(bts) @@ -622,24 +761,24 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } } case "apfa": - var zb0023 int - var zb0024 bool - zb0023, zb0024, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0028 int + var zb0029 bool + zb0028, zb0029, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ForeignApps") return } - if zb0023 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0023), uint64(encodedMaxForeignApps)) + if zb0028 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0028), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "ForeignApps") return } - if zb0024 { + if zb0029 { (*z).ForeignApps = nil - } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0023 { - (*z).ForeignApps = ((*z).ForeignApps)[:zb0023] + } else if (*z).ForeignApps != nil && cap((*z).ForeignApps) >= zb0028 { + (*z).ForeignApps = ((*z).ForeignApps)[:zb0028] } else { - (*z).ForeignApps = make([]basics.AppIndex, zb0023) + (*z).ForeignApps = make([]basics.AppIndex, zb0028) } for zb0003 := range (*z).ForeignApps { bts, err = (*z).ForeignApps[zb0003].UnmarshalMsg(bts) @@ -648,30 +787,121 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error return } } + case "apbx": + var zb0030 int + var zb0031 bool + zb0030, zb0031, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes") + return + } + if zb0030 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0030), uint64(encodedMaxBoxes)) + err = msgp.WrapError(err, "Boxes") + return + } + if zb0031 { + (*z).Boxes = nil + } else if (*z).Boxes != nil && cap((*z).Boxes) >= zb0030 { + (*z).Boxes = ((*z).Boxes)[:zb0030] + } else { + (*z).Boxes = make([]BoxRef, zb0030) + } + for zb0004 := range (*z).Boxes { + var zb0032 int + var zb0033 bool + zb0032, zb0033, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0032, zb0033, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004) + return + } + if zb0032 > 0 { + zb0032-- + (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array", "Index") + return + } + } + if zb0032 > 0 { + zb0032-- + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array", "Name") + return + } + } + if zb0032 > 0 { + err = msgp.ErrTooManyArrayFields(zb0032) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004) + return + } + if zb0033 { + (*z).Boxes[zb0004] = BoxRef{} + } + for zb0032 > 0 { + zb0032-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004) + return + } + switch string(field) { + case "i": + (*z).Boxes[zb0004].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004, "Index") + return + } + case "n": + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004, "Name") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0004) + return + } + } + } + } + } case "apas": - var zb0025 int - var zb0026 bool - zb0025, zb0026, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0034 int + var zb0035 bool + zb0034, zb0035, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ForeignAssets") return } - if zb0025 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0025), uint64(encodedMaxForeignAssets)) + if zb0034 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0034), uint64(encodedMaxForeignAssets)) err = msgp.WrapError(err, "ForeignAssets") return } - if zb0026 { + if zb0035 { (*z).ForeignAssets = nil - } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0025 { - (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0025] + } else if (*z).ForeignAssets != nil && cap((*z).ForeignAssets) >= zb0034 { + (*z).ForeignAssets = ((*z).ForeignAssets)[:zb0034] } else { - (*z).ForeignAssets = make([]basics.AssetIndex, zb0025) + (*z).ForeignAssets = make([]basics.AssetIndex, zb0034) } - for zb0004 := range (*z).ForeignAssets { - bts, err = (*z).ForeignAssets[zb0004].UnmarshalMsg(bts) + for zb0005 := range (*z).ForeignAssets { + bts, err = (*z).ForeignAssets[zb0005].UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "ForeignAssets", zb0004) + err = msgp.WrapError(err, "ForeignAssets", zb0005) return } } @@ -688,14 +918,14 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error return } case "apap": - var zb0027 int - zb0027, err = msgp.ReadBytesBytesHeader(bts) + var zb0036 int + zb0036, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ApprovalProgram") return } - if zb0027 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0027), uint64(config.MaxAvailableAppProgramLen)) + if zb0036 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0036), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApprovalProgram) @@ -704,14 +934,14 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error return } case "apsu": - var zb0028 int - zb0028, err = msgp.ReadBytesBytesHeader(bts) + var zb0037 int + zb0037, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ClearStateProgram") return } - if zb0028 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0028), uint64(config.MaxAvailableAppProgramLen)) + if zb0037 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0037), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ClearStateProgram) @@ -758,8 +988,12 @@ func (z *ApplicationCallTxnFields) Msgsize() (s int) { s += (*z).ForeignApps[zb0003].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0004 := range (*z).ForeignAssets { - s += (*z).ForeignAssets[zb0004].Msgsize() + for zb0004 := range (*z).Boxes { + s += 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).Boxes[zb0004].Name) + } + s += 5 + msgp.ArrayHeaderSize + for zb0005 := range (*z).ForeignAssets { + s += (*z).ForeignAssets[zb0005].Msgsize() } s += 5 + (*z).LocalStateSchema.Msgsize() + 5 + (*z).GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ClearStateProgram) + 5 + msgp.Uint32Size return @@ -767,7 +1001,7 @@ func (z *ApplicationCallTxnFields) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *ApplicationCallTxnFields) MsgIsZero() bool { - return ((*z).ApplicationID.MsgIsZero()) && ((*z).OnCompletion == 0) && (len((*z).ApplicationArgs) == 0) && (len((*z).Accounts) == 0) && (len((*z).ForeignApps) == 0) && (len((*z).ForeignAssets) == 0) && ((*z).LocalStateSchema.MsgIsZero()) && ((*z).GlobalStateSchema.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).ExtraProgramPages == 0) + return ((*z).ApplicationID.MsgIsZero()) && ((*z).OnCompletion == 0) && (len((*z).ApplicationArgs) == 0) && (len((*z).Accounts) == 0) && (len((*z).ForeignApps) == 0) && (len((*z).Boxes) == 0) && (len((*z).ForeignAssets) == 0) && ((*z).LocalStateSchema.MsgIsZero()) && ((*z).GlobalStateSchema.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).ExtraProgramPages == 0) } // MarshalMsg implements msgp.Marshaler @@ -1430,7 +1664,154 @@ func (z *AssetTransferTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) zb0001-- bts, err = (*z).AssetCloseTo.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "AssetCloseTo") + err = msgp.WrapError(err, "struct-from-array", "AssetCloseTo") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = AssetTransferTxnFields{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "xaid": + bts, err = (*z).XferAsset.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "XferAsset") + return + } + case "aamt": + (*z).AssetAmount, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "AssetAmount") + return + } + case "asnd": + bts, err = (*z).AssetSender.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "AssetSender") + return + } + case "arcv": + bts, err = (*z).AssetReceiver.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "AssetReceiver") + return + } + case "aclose": + bts, err = (*z).AssetCloseTo.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "AssetCloseTo") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *AssetTransferTxnFields) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*AssetTransferTxnFields) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *AssetTransferTxnFields) Msgsize() (s int) { + s = 1 + 5 + (*z).XferAsset.Msgsize() + 5 + msgp.Uint64Size + 5 + (*z).AssetSender.Msgsize() + 5 + (*z).AssetReceiver.Msgsize() + 7 + (*z).AssetCloseTo.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *AssetTransferTxnFields) MsgIsZero() bool { + return ((*z).XferAsset.MsgIsZero()) && ((*z).AssetAmount == 0) && ((*z).AssetSender.MsgIsZero()) && ((*z).AssetReceiver.MsgIsZero()) && ((*z).AssetCloseTo.MsgIsZero()) +} + +// MarshalMsg implements msgp.Marshaler +func (z *BoxRef) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).Index == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Name == "" { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "i" + o = append(o, 0xa1, 0x69) + o = msgp.AppendUint64(o, (*z).Index) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "n" + o = append(o, 0xa1, 0x6e) + o = msgp.AppendString(o, (*z).Name) + } + } + return +} + +func (_ *BoxRef) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*BoxRef) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *BoxRef) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Index") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Name") return } } @@ -1447,7 +1828,7 @@ func (z *AssetTransferTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) return } if zb0002 { - (*z) = AssetTransferTxnFields{} + (*z) = BoxRef{} } for zb0001 > 0 { zb0001-- @@ -1457,34 +1838,16 @@ func (z *AssetTransferTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) return } switch string(field) { - case "xaid": - bts, err = (*z).XferAsset.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "XferAsset") - return - } - case "aamt": - (*z).AssetAmount, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "AssetAmount") - return - } - case "asnd": - bts, err = (*z).AssetSender.UnmarshalMsg(bts) + case "i": + (*z).Index, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "AssetSender") - return - } - case "arcv": - bts, err = (*z).AssetReceiver.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "AssetReceiver") + err = msgp.WrapError(err, "Index") return } - case "aclose": - bts, err = (*z).AssetCloseTo.UnmarshalMsg(bts) + case "n": + (*z).Name, bts, err = msgp.ReadStringBytes(bts) if err != nil { - err = msgp.WrapError(err, "AssetCloseTo") + err = msgp.WrapError(err, "Name") return } default: @@ -1500,20 +1863,20 @@ func (z *AssetTransferTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) return } -func (_ *AssetTransferTxnFields) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*AssetTransferTxnFields) +func (_ *BoxRef) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*BoxRef) return ok } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *AssetTransferTxnFields) Msgsize() (s int) { - s = 1 + 5 + (*z).XferAsset.Msgsize() + 5 + msgp.Uint64Size + 5 + (*z).AssetSender.Msgsize() + 5 + (*z).AssetReceiver.Msgsize() + 7 + (*z).AssetCloseTo.Msgsize() +func (z *BoxRef) Msgsize() (s int) { + s = 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).Name) return } // MsgIsZero returns whether this is a zero value -func (z *AssetTransferTxnFields) MsgIsZero() bool { - return ((*z).XferAsset.MsgIsZero()) && ((*z).AssetAmount == 0) && ((*z).AssetSender.MsgIsZero()) && ((*z).AssetReceiver.MsgIsZero()) && ((*z).AssetCloseTo.MsgIsZero()) +func (z *BoxRef) MsgIsZero() bool { + return ((*z).Index == 0) && ((*z).Name == "") } // MarshalMsg implements msgp.Marshaler @@ -4200,212 +4563,216 @@ func (z *SignedTxnWithAD) MsgIsZero() bool { func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0006Len := uint32(45) - var zb0006Mask uint64 /* 54 bits */ + zb0007Len := uint32(46) + var zb0007Mask uint64 /* 55 bits */ if (*z).AssetTransferTxnFields.AssetAmount == 0 { - zb0006Len-- - zb0006Mask |= 0x200 + zb0007Len-- + zb0007Mask |= 0x200 } if (*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x400 + zb0007Len-- + zb0007Mask |= 0x400 } if (*z).AssetFreezeTxnFields.AssetFrozen == false { - zb0006Len-- - zb0006Mask |= 0x800 + zb0007Len-- + zb0007Mask |= 0x800 } if (*z).PaymentTxnFields.Amount.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x1000 + zb0007Len-- + zb0007Mask |= 0x1000 } if len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0 { - zb0006Len-- - zb0006Mask |= 0x2000 + zb0007Len-- + zb0007Mask |= 0x2000 } if (*z).ApplicationCallTxnFields.OnCompletion == 0 { - zb0006Len-- - zb0006Mask |= 0x4000 + zb0007Len-- + zb0007Mask |= 0x4000 } if len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0 { - zb0006Len-- - zb0006Mask |= 0x8000 + zb0007Len-- + zb0007Mask |= 0x8000 } if (*z).AssetConfigTxnFields.AssetParams.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x10000 + zb0007Len-- + zb0007Mask |= 0x10000 } if len((*z).ApplicationCallTxnFields.ForeignAssets) == 0 { - zb0006Len-- - zb0006Mask |= 0x20000 + zb0007Len-- + zb0007Mask |= 0x20000 } if len((*z).ApplicationCallTxnFields.Accounts) == 0 { - zb0006Len-- - zb0006Mask |= 0x40000 + zb0007Len-- + zb0007Mask |= 0x40000 + } + if len((*z).ApplicationCallTxnFields.Boxes) == 0 { + zb0007Len-- + zb0007Mask |= 0x80000 } if (*z).ApplicationCallTxnFields.ExtraProgramPages == 0 { - zb0006Len-- - zb0006Mask |= 0x80000 + zb0007Len-- + zb0007Mask |= 0x100000 } if len((*z).ApplicationCallTxnFields.ForeignApps) == 0 { - zb0006Len-- - zb0006Mask |= 0x100000 + zb0007Len-- + zb0007Mask |= 0x200000 } if (*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x200000 + zb0007Len-- + zb0007Mask |= 0x400000 } if (*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x400000 + zb0007Len-- + zb0007Mask |= 0x800000 } if (*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x800000 + zb0007Len-- + zb0007Mask |= 0x1000000 } if len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0 { - zb0006Len-- - zb0006Mask |= 0x1000000 + zb0007Len-- + zb0007Mask |= 0x2000000 } if (*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x2000000 + zb0007Len-- + zb0007Mask |= 0x4000000 } if (*z).AssetTransferTxnFields.AssetSender.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x4000000 + zb0007Len-- + zb0007Mask |= 0x8000000 } if (*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x8000000 + zb0007Len-- + zb0007Mask |= 0x10000000 } if (*z).CompactCertTxnFields.Cert.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x10000000 + zb0007Len-- + zb0007Mask |= 0x20000000 } if (*z).CompactCertTxnFields.CertRound.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x20000000 + zb0007Len-- + zb0007Mask |= 0x40000000 } if (*z).CompactCertTxnFields.CertType.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x40000000 + zb0007Len-- + zb0007Mask |= 0x80000000 } if (*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x80000000 + zb0007Len-- + zb0007Mask |= 0x100000000 } if (*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x100000000 + zb0007Len-- + zb0007Mask |= 0x200000000 } if (*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x200000000 + zb0007Len-- + zb0007Mask |= 0x400000000 } if (*z).Header.Fee.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x400000000 + zb0007Len-- + zb0007Mask |= 0x800000000 } if (*z).Header.FirstValid.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x800000000 + zb0007Len-- + zb0007Mask |= 0x1000000000 } if (*z).Header.GenesisID == "" { - zb0006Len-- - zb0006Mask |= 0x1000000000 + zb0007Len-- + zb0007Mask |= 0x2000000000 } if (*z).Header.GenesisHash.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x2000000000 + zb0007Len-- + zb0007Mask |= 0x4000000000 } if (*z).Header.Group.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x4000000000 + zb0007Len-- + zb0007Mask |= 0x8000000000 } if (*z).Header.LastValid.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x8000000000 + zb0007Len-- + zb0007Mask |= 0x10000000000 } if (*z).Header.Lease == ([32]byte{}) { - zb0006Len-- - zb0006Mask |= 0x10000000000 + zb0007Len-- + zb0007Mask |= 0x20000000000 } if (*z).KeyregTxnFields.Nonparticipation == false { - zb0006Len-- - zb0006Mask |= 0x20000000000 + zb0007Len-- + zb0007Mask |= 0x40000000000 } if len((*z).Header.Note) == 0 { - zb0006Len-- - zb0006Mask |= 0x40000000000 + zb0007Len-- + zb0007Mask |= 0x80000000000 } if (*z).PaymentTxnFields.Receiver.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x80000000000 + zb0007Len-- + zb0007Mask |= 0x100000000000 } if (*z).Header.RekeyTo.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x100000000000 + zb0007Len-- + zb0007Mask |= 0x200000000000 } if (*z).KeyregTxnFields.SelectionPK.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x200000000000 + zb0007Len-- + zb0007Mask |= 0x400000000000 } if (*z).Header.Sender.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x400000000000 + zb0007Len-- + zb0007Mask |= 0x800000000000 } if (*z).KeyregTxnFields.StateProofPK.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x800000000000 + zb0007Len-- + zb0007Mask |= 0x1000000000000 } if (*z).Type.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x1000000000000 + zb0007Len-- + zb0007Mask |= 0x2000000000000 } if (*z).KeyregTxnFields.VoteFirst.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x2000000000000 + zb0007Len-- + zb0007Mask |= 0x4000000000000 } if (*z).KeyregTxnFields.VoteKeyDilution == 0 { - zb0006Len-- - zb0006Mask |= 0x4000000000000 + zb0007Len-- + zb0007Mask |= 0x8000000000000 } if (*z).KeyregTxnFields.VotePK.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x8000000000000 + zb0007Len-- + zb0007Mask |= 0x10000000000000 } if (*z).KeyregTxnFields.VoteLast.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x10000000000000 + zb0007Len-- + zb0007Mask |= 0x20000000000000 } if (*z).AssetTransferTxnFields.XferAsset.MsgIsZero() { - zb0006Len-- - zb0006Mask |= 0x20000000000000 + zb0007Len-- + zb0007Mask |= 0x40000000000000 } - // variable map header, size zb0006Len - o = msgp.AppendMapHeader(o, zb0006Len) - if zb0006Len != 0 { - if (zb0006Mask & 0x200) == 0 { // if not empty + // variable map header, size zb0007Len + o = msgp.AppendMapHeader(o, zb0007Len) + if zb0007Len != 0 { + if (zb0007Mask & 0x200) == 0 { // if not empty // string "aamt" o = append(o, 0xa4, 0x61, 0x61, 0x6d, 0x74) o = msgp.AppendUint64(o, (*z).AssetTransferTxnFields.AssetAmount) } - if (zb0006Mask & 0x400) == 0 { // if not empty + if (zb0007Mask & 0x400) == 0 { // if not empty // string "aclose" o = append(o, 0xa6, 0x61, 0x63, 0x6c, 0x6f, 0x73, 0x65) o = (*z).AssetTransferTxnFields.AssetCloseTo.MarshalMsg(o) } - if (zb0006Mask & 0x800) == 0 { // if not empty + if (zb0007Mask & 0x800) == 0 { // if not empty // string "afrz" o = append(o, 0xa4, 0x61, 0x66, 0x72, 0x7a) o = msgp.AppendBool(o, (*z).AssetFreezeTxnFields.AssetFrozen) } - if (zb0006Mask & 0x1000) == 0 { // if not empty + if (zb0007Mask & 0x1000) == 0 { // if not empty // string "amt" o = append(o, 0xa3, 0x61, 0x6d, 0x74) o = (*z).PaymentTxnFields.Amount.MarshalMsg(o) } - if (zb0006Mask & 0x2000) == 0 { // if not empty + if (zb0007Mask & 0x2000) == 0 { // if not empty // string "apaa" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x61) if (*z).ApplicationCallTxnFields.ApplicationArgs == nil { @@ -4417,22 +4784,22 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002]) } } - if (zb0006Mask & 0x4000) == 0 { // if not empty + if (zb0007Mask & 0x4000) == 0 { // if not empty // string "apan" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x6e) o = msgp.AppendUint64(o, uint64((*z).ApplicationCallTxnFields.OnCompletion)) } - if (zb0006Mask & 0x8000) == 0 { // if not empty + if (zb0007Mask & 0x8000) == 0 { // if not empty // string "apap" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x70) o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ApprovalProgram) } - if (zb0006Mask & 0x10000) == 0 { // if not empty + if (zb0007Mask & 0x10000) == 0 { // if not empty // string "apar" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x72) o = (*z).AssetConfigTxnFields.AssetParams.MarshalMsg(o) } - if (zb0006Mask & 0x20000) == 0 { // if not empty + if (zb0007Mask & 0x20000) == 0 { // if not empty // string "apas" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x73) if (*z).ApplicationCallTxnFields.ForeignAssets == nil { @@ -4440,11 +4807,11 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { } else { o = msgp.AppendArrayHeader(o, uint32(len((*z).ApplicationCallTxnFields.ForeignAssets))) } - for zb0005 := range (*z).ApplicationCallTxnFields.ForeignAssets { - o = (*z).ApplicationCallTxnFields.ForeignAssets[zb0005].MarshalMsg(o) + for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { + o = (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].MarshalMsg(o) } } - if (zb0006Mask & 0x40000) == 0 { // if not empty + if (zb0007Mask & 0x40000) == 0 { // if not empty // string "apat" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x74) if (*z).ApplicationCallTxnFields.Accounts == nil { @@ -4456,12 +4823,46 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).ApplicationCallTxnFields.Accounts[zb0003].MarshalMsg(o) } } - if (zb0006Mask & 0x80000) == 0 { // if not empty + if (zb0007Mask & 0x80000) == 0 { // if not empty + // string "apbx" + o = append(o, 0xa4, 0x61, 0x70, 0x62, 0x78) + if (*z).ApplicationCallTxnFields.Boxes == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).ApplicationCallTxnFields.Boxes))) + } + for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { + // omitempty: check for empty values + zb0008Len := uint32(2) + var zb0008Mask uint8 /* 3 bits */ + if (*z).ApplicationCallTxnFields.Boxes[zb0005].Index == 0 { + zb0008Len-- + zb0008Mask |= 0x2 + } + if (*z).ApplicationCallTxnFields.Boxes[zb0005].Name == "" { + zb0008Len-- + zb0008Mask |= 0x4 + } + // variable map header, size zb0008Len + o = append(o, 0x80|uint8(zb0008Len)) + if (zb0008Mask & 0x2) == 0 { // if not empty + // string "i" + o = append(o, 0xa1, 0x69) + o = msgp.AppendUint64(o, (*z).ApplicationCallTxnFields.Boxes[zb0005].Index) + } + if (zb0008Mask & 0x4) == 0 { // if not empty + // string "n" + o = append(o, 0xa1, 0x6e) + o = msgp.AppendString(o, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + } + } + } + if (zb0007Mask & 0x100000) == 0 { // if not empty // string "apep" o = append(o, 0xa4, 0x61, 0x70, 0x65, 0x70) o = msgp.AppendUint32(o, (*z).ApplicationCallTxnFields.ExtraProgramPages) } - if (zb0006Mask & 0x100000) == 0 { // if not empty + if (zb0007Mask & 0x200000) == 0 { // if not empty // string "apfa" o = append(o, 0xa4, 0x61, 0x70, 0x66, 0x61) if (*z).ApplicationCallTxnFields.ForeignApps == nil { @@ -4473,167 +4874,167 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).ApplicationCallTxnFields.ForeignApps[zb0004].MarshalMsg(o) } } - if (zb0006Mask & 0x200000) == 0 { // if not empty + if (zb0007Mask & 0x400000) == 0 { // if not empty // string "apgs" o = append(o, 0xa4, 0x61, 0x70, 0x67, 0x73) o = (*z).ApplicationCallTxnFields.GlobalStateSchema.MarshalMsg(o) } - if (zb0006Mask & 0x400000) == 0 { // if not empty + if (zb0007Mask & 0x800000) == 0 { // if not empty // string "apid" o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) o = (*z).ApplicationCallTxnFields.ApplicationID.MarshalMsg(o) } - if (zb0006Mask & 0x800000) == 0 { // if not empty + if (zb0007Mask & 0x1000000) == 0 { // if not empty // string "apls" o = append(o, 0xa4, 0x61, 0x70, 0x6c, 0x73) o = (*z).ApplicationCallTxnFields.LocalStateSchema.MarshalMsg(o) } - if (zb0006Mask & 0x1000000) == 0 { // if not empty + if (zb0007Mask & 0x2000000) == 0 { // if not empty // string "apsu" o = append(o, 0xa4, 0x61, 0x70, 0x73, 0x75) o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ClearStateProgram) } - if (zb0006Mask & 0x2000000) == 0 { // if not empty + if (zb0007Mask & 0x4000000) == 0 { // if not empty // string "arcv" o = append(o, 0xa4, 0x61, 0x72, 0x63, 0x76) o = (*z).AssetTransferTxnFields.AssetReceiver.MarshalMsg(o) } - if (zb0006Mask & 0x4000000) == 0 { // if not empty + if (zb0007Mask & 0x8000000) == 0 { // if not empty // string "asnd" o = append(o, 0xa4, 0x61, 0x73, 0x6e, 0x64) o = (*z).AssetTransferTxnFields.AssetSender.MarshalMsg(o) } - if (zb0006Mask & 0x8000000) == 0 { // if not empty + if (zb0007Mask & 0x10000000) == 0 { // if not empty // string "caid" o = append(o, 0xa4, 0x63, 0x61, 0x69, 0x64) o = (*z).AssetConfigTxnFields.ConfigAsset.MarshalMsg(o) } - if (zb0006Mask & 0x10000000) == 0 { // if not empty + if (zb0007Mask & 0x20000000) == 0 { // if not empty // string "cert" o = append(o, 0xa4, 0x63, 0x65, 0x72, 0x74) o = (*z).CompactCertTxnFields.Cert.MarshalMsg(o) } - if (zb0006Mask & 0x20000000) == 0 { // if not empty + if (zb0007Mask & 0x40000000) == 0 { // if not empty // string "certrnd" o = append(o, 0xa7, 0x63, 0x65, 0x72, 0x74, 0x72, 0x6e, 0x64) o = (*z).CompactCertTxnFields.CertRound.MarshalMsg(o) } - if (zb0006Mask & 0x40000000) == 0 { // if not empty + if (zb0007Mask & 0x80000000) == 0 { // if not empty // string "certtype" o = append(o, 0xa8, 0x63, 0x65, 0x72, 0x74, 0x74, 0x79, 0x70, 0x65) o = (*z).CompactCertTxnFields.CertType.MarshalMsg(o) } - if (zb0006Mask & 0x80000000) == 0 { // if not empty + if (zb0007Mask & 0x100000000) == 0 { // if not empty // string "close" o = append(o, 0xa5, 0x63, 0x6c, 0x6f, 0x73, 0x65) o = (*z).PaymentTxnFields.CloseRemainderTo.MarshalMsg(o) } - if (zb0006Mask & 0x100000000) == 0 { // if not empty + if (zb0007Mask & 0x200000000) == 0 { // if not empty // string "fadd" o = append(o, 0xa4, 0x66, 0x61, 0x64, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAccount.MarshalMsg(o) } - if (zb0006Mask & 0x200000000) == 0 { // if not empty + if (zb0007Mask & 0x400000000) == 0 { // if not empty // string "faid" o = append(o, 0xa4, 0x66, 0x61, 0x69, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAsset.MarshalMsg(o) } - if (zb0006Mask & 0x400000000) == 0 { // if not empty + if (zb0007Mask & 0x800000000) == 0 { // if not empty // string "fee" o = append(o, 0xa3, 0x66, 0x65, 0x65) o = (*z).Header.Fee.MarshalMsg(o) } - if (zb0006Mask & 0x800000000) == 0 { // if not empty + if (zb0007Mask & 0x1000000000) == 0 { // if not empty // string "fv" o = append(o, 0xa2, 0x66, 0x76) o = (*z).Header.FirstValid.MarshalMsg(o) } - if (zb0006Mask & 0x1000000000) == 0 { // if not empty + if (zb0007Mask & 0x2000000000) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).Header.GenesisID) } - if (zb0006Mask & 0x2000000000) == 0 { // if not empty + if (zb0007Mask & 0x4000000000) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).Header.GenesisHash.MarshalMsg(o) } - if (zb0006Mask & 0x4000000000) == 0 { // if not empty + if (zb0007Mask & 0x8000000000) == 0 { // if not empty // string "grp" o = append(o, 0xa3, 0x67, 0x72, 0x70) o = (*z).Header.Group.MarshalMsg(o) } - if (zb0006Mask & 0x8000000000) == 0 { // if not empty + if (zb0007Mask & 0x10000000000) == 0 { // if not empty // string "lv" o = append(o, 0xa2, 0x6c, 0x76) o = (*z).Header.LastValid.MarshalMsg(o) } - if (zb0006Mask & 0x10000000000) == 0 { // if not empty + if (zb0007Mask & 0x20000000000) == 0 { // if not empty // string "lx" o = append(o, 0xa2, 0x6c, 0x78) o = msgp.AppendBytes(o, ((*z).Header.Lease)[:]) } - if (zb0006Mask & 0x20000000000) == 0 { // if not empty + if (zb0007Mask & 0x40000000000) == 0 { // if not empty // string "nonpart" o = append(o, 0xa7, 0x6e, 0x6f, 0x6e, 0x70, 0x61, 0x72, 0x74) o = msgp.AppendBool(o, (*z).KeyregTxnFields.Nonparticipation) } - if (zb0006Mask & 0x40000000000) == 0 { // if not empty + if (zb0007Mask & 0x80000000000) == 0 { // if not empty // string "note" o = append(o, 0xa4, 0x6e, 0x6f, 0x74, 0x65) o = msgp.AppendBytes(o, (*z).Header.Note) } - if (zb0006Mask & 0x80000000000) == 0 { // if not empty + if (zb0007Mask & 0x100000000000) == 0 { // if not empty // string "rcv" o = append(o, 0xa3, 0x72, 0x63, 0x76) o = (*z).PaymentTxnFields.Receiver.MarshalMsg(o) } - if (zb0006Mask & 0x100000000000) == 0 { // if not empty + if (zb0007Mask & 0x200000000000) == 0 { // if not empty // string "rekey" o = append(o, 0xa5, 0x72, 0x65, 0x6b, 0x65, 0x79) o = (*z).Header.RekeyTo.MarshalMsg(o) } - if (zb0006Mask & 0x200000000000) == 0 { // if not empty + if (zb0007Mask & 0x400000000000) == 0 { // if not empty // string "selkey" o = append(o, 0xa6, 0x73, 0x65, 0x6c, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.SelectionPK.MarshalMsg(o) } - if (zb0006Mask & 0x400000000000) == 0 { // if not empty + if (zb0007Mask & 0x800000000000) == 0 { // if not empty // string "snd" o = append(o, 0xa3, 0x73, 0x6e, 0x64) o = (*z).Header.Sender.MarshalMsg(o) } - if (zb0006Mask & 0x800000000000) == 0 { // if not empty + if (zb0007Mask & 0x1000000000000) == 0 { // if not empty // string "sprfkey" o = append(o, 0xa7, 0x73, 0x70, 0x72, 0x66, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.StateProofPK.MarshalMsg(o) } - if (zb0006Mask & 0x1000000000000) == 0 { // if not empty + if (zb0007Mask & 0x2000000000000) == 0 { // if not empty // string "type" o = append(o, 0xa4, 0x74, 0x79, 0x70, 0x65) o = (*z).Type.MarshalMsg(o) } - if (zb0006Mask & 0x2000000000000) == 0 { // if not empty + if (zb0007Mask & 0x4000000000000) == 0 { // if not empty // string "votefst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x66, 0x73, 0x74) o = (*z).KeyregTxnFields.VoteFirst.MarshalMsg(o) } - if (zb0006Mask & 0x4000000000000) == 0 { // if not empty + if (zb0007Mask & 0x8000000000000) == 0 { // if not empty // string "votekd" o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x64) o = msgp.AppendUint64(o, (*z).KeyregTxnFields.VoteKeyDilution) } - if (zb0006Mask & 0x8000000000000) == 0 { // if not empty + if (zb0007Mask & 0x10000000000000) == 0 { // if not empty // string "votekey" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.VotePK.MarshalMsg(o) } - if (zb0006Mask & 0x10000000000000) == 0 { // if not empty + if (zb0007Mask & 0x20000000000000) == 0 { // if not empty // string "votelst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6c, 0x73, 0x74) o = (*z).KeyregTxnFields.VoteLast.MarshalMsg(o) } - if (zb0006Mask & 0x20000000000000) == 0 { // if not empty + if (zb0007Mask & 0x40000000000000) == 0 { // if not empty // string "xaid" o = append(o, 0xa4, 0x78, 0x61, 0x69, 0x64) o = (*z).AssetTransferTxnFields.XferAsset.MarshalMsg(o) @@ -4651,65 +5052,65 @@ func (_ *Transaction) CanMarshalMsg(z interface{}) bool { func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field - var zb0006 int - var zb0007 bool - zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0007, zb0008, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Type.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Type") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Header.Sender.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Sender") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Header.Fee.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Fee") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Header.FirstValid.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FirstValid") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Header.LastValid.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LastValid") return } } - if zb0006 > 0 { - zb0006-- - var zb0008 int - zb0008, err = msgp.ReadBytesBytesHeader(bts) + if zb0007 > 0 { + zb0007-- + var zb0009 int + zb0009, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Note") return } - if zb0008 > config.MaxTxnNoteBytes { - err = msgp.ErrOverflow(uint64(zb0008), uint64(config.MaxTxnNoteBytes)) + if zb0009 > config.MaxTxnNoteBytes { + err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxTxnNoteBytes)) return } (*z).Header.Note, bts, err = msgp.ReadBytesBytes(bts, (*z).Header.Note) @@ -4718,246 +5119,246 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).Header.GenesisID, bts, err = msgp.ReadStringBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisID") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Header.GenesisHash.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisHash") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Header.Group.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Group") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = msgp.ReadExactBytes(bts, ((*z).Header.Lease)[:]) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Lease") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).Header.RekeyTo.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RekeyTo") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).KeyregTxnFields.VotePK.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VotePK") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).KeyregTxnFields.SelectionPK.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "SelectionPK") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).KeyregTxnFields.StateProofPK.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofPK") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).KeyregTxnFields.VoteFirst.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VoteFirst") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).KeyregTxnFields.VoteLast.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VoteLast") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).KeyregTxnFields.VoteKeyDilution, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "VoteKeyDilution") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).KeyregTxnFields.Nonparticipation, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Nonparticipation") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).PaymentTxnFields.Receiver.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Receiver") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).PaymentTxnFields.Amount.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Amount") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).PaymentTxnFields.CloseRemainderTo.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CloseRemainderTo") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetConfigTxnFields.ConfigAsset.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ConfigAsset") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetConfigTxnFields.AssetParams.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetParams") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetTransferTxnFields.XferAsset.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "XferAsset") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).AssetTransferTxnFields.AssetAmount, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetAmount") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetTransferTxnFields.AssetSender.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetSender") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetTransferTxnFields.AssetReceiver.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetReceiver") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetTransferTxnFields.AssetCloseTo.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetCloseTo") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetFreezeTxnFields.FreezeAccount.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FreezeAccount") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).AssetFreezeTxnFields.FreezeAsset.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FreezeAsset") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).AssetFreezeTxnFields.AssetFrozen, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetFrozen") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).ApplicationCallTxnFields.ApplicationID.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationID") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- { - var zb0009 uint64 - zb0009, bts, err = msgp.ReadUint64Bytes(bts) + var zb0010 uint64 + zb0010, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OnCompletion") return } - (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0009) + (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0010) } } - if zb0006 > 0 { - zb0006-- - var zb0010 int - var zb0011 bool - zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0011 int + var zb0012 bool + zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0010 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0010), uint64(encodedMaxApplicationArgs)) + if zb0011 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0011), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "struct-from-array", "ApplicationArgs") return } - if zb0011 { + if zb0012 { (*z).ApplicationCallTxnFields.ApplicationArgs = nil - } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0010 { - (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0010] + } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0011 { + (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0011] } else { - (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0010) + (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0011) } for zb0002 := range (*z).ApplicationCallTxnFields.ApplicationArgs { (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002]) @@ -4967,26 +5368,26 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } } } - if zb0006 > 0 { - zb0006-- - var zb0012 int - var zb0013 bool - zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0013 int + var zb0014 bool + zb0013, zb0014, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0012 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0012), uint64(encodedMaxAccounts)) + if zb0013 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0013), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "struct-from-array", "Accounts") return } - if zb0013 { + if zb0014 { (*z).ApplicationCallTxnFields.Accounts = nil - } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0012 { - (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0012] + } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0013 { + (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0013] } else { - (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0012) + (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0013) } for zb0003 := range (*z).ApplicationCallTxnFields.Accounts { bts, err = (*z).ApplicationCallTxnFields.Accounts[zb0003].UnmarshalMsg(bts) @@ -4996,26 +5397,26 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } } } - if zb0006 > 0 { - zb0006-- - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0014 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0014), uint64(encodedMaxForeignApps)) + if zb0015 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0015), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "struct-from-array", "ForeignApps") return } - if zb0015 { + if zb0016 { (*z).ApplicationCallTxnFields.ForeignApps = nil - } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0014 { - (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0014] + } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0015 { + (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0015] } else { - (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0014) + (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0015) } for zb0004 := range (*z).ApplicationCallTxnFields.ForeignApps { bts, err = (*z).ApplicationCallTxnFields.ForeignApps[zb0004].UnmarshalMsg(bts) @@ -5025,61 +5426,154 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } } } - if zb0006 > 0 { - zb0006-- - var zb0016 int - var zb0017 bool - zb0016, zb0017, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0007 > 0 { + zb0007-- + var zb0017 int + var zb0018 bool + zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes") + return + } + if zb0017 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0017), uint64(encodedMaxBoxes)) + err = msgp.WrapError(err, "struct-from-array", "Boxes") + return + } + if zb0018 { + (*z).ApplicationCallTxnFields.Boxes = nil + } else if (*z).ApplicationCallTxnFields.Boxes != nil && cap((*z).ApplicationCallTxnFields.Boxes) >= zb0017 { + (*z).ApplicationCallTxnFields.Boxes = ((*z).ApplicationCallTxnFields.Boxes)[:zb0017] + } else { + (*z).ApplicationCallTxnFields.Boxes = make([]BoxRef, zb0017) + } + for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { + var zb0019 int + var zb0020 bool + zb0019, zb0020, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0019, zb0020, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + return + } + if zb0019 > 0 { + zb0019-- + (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array", "Index") + return + } + } + if zb0019 > 0 { + zb0019-- + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array", "Name") + return + } + } + if zb0019 > 0 { + err = msgp.ErrTooManyArrayFields(zb0019) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + return + } + if zb0020 { + (*z).ApplicationCallTxnFields.Boxes[zb0005] = BoxRef{} + } + for zb0019 > 0 { + zb0019-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + return + } + switch string(field) { + case "i": + (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "Index") + return + } + case "n": + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "Name") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005) + return + } + } + } + } + } + } + if zb0007 > 0 { + zb0007-- + var zb0021 int + var zb0022 bool + zb0021, zb0022, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") return } - if zb0016 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0016), uint64(encodedMaxForeignAssets)) + if zb0021 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0021), uint64(encodedMaxForeignAssets)) err = msgp.WrapError(err, "struct-from-array", "ForeignAssets") return } - if zb0017 { + if zb0022 { (*z).ApplicationCallTxnFields.ForeignAssets = nil - } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0016 { - (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0016] + } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0021 { + (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0021] } else { - (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0016) + (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0021) } - for zb0005 := range (*z).ApplicationCallTxnFields.ForeignAssets { - bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0005].UnmarshalMsg(bts) + for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { + bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0005) + err = msgp.WrapError(err, "struct-from-array", "ForeignAssets", zb0006) return } } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).ApplicationCallTxnFields.LocalStateSchema.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "LocalStateSchema") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).ApplicationCallTxnFields.GlobalStateSchema.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GlobalStateSchema") return } } - if zb0006 > 0 { - zb0006-- - var zb0018 int - zb0018, err = msgp.ReadBytesBytesHeader(bts) + if zb0007 > 0 { + zb0007-- + var zb0023 int + zb0023, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ApprovalProgram") return } - if zb0018 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0018), uint64(config.MaxAvailableAppProgramLen)) + if zb0023 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0023), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApprovalProgram) @@ -5088,16 +5582,16 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } - if zb0006 > 0 { - zb0006-- - var zb0019 int - zb0019, err = msgp.ReadBytesBytesHeader(bts) + if zb0007 > 0 { + zb0007-- + var zb0024 int + zb0024, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ClearStateProgram") return } - if zb0019 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0019), uint64(config.MaxAvailableAppProgramLen)) + if zb0024 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0024), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ClearStateProgram) @@ -5106,40 +5600,40 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- (*z).ApplicationCallTxnFields.ExtraProgramPages, bts, err = msgp.ReadUint32Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExtraProgramPages") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).CompactCertTxnFields.CertRound.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CertRound") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).CompactCertTxnFields.CertType.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CertType") return } } - if zb0006 > 0 { - zb0006-- + if zb0007 > 0 { + zb0007-- bts, err = (*z).CompactCertTxnFields.Cert.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Cert") return } } - if zb0006 > 0 { - err = msgp.ErrTooManyArrayFields(zb0006) + if zb0007 > 0 { + err = msgp.ErrTooManyArrayFields(zb0007) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -5150,11 +5644,11 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err) return } - if zb0007 { + if zb0008 { (*z) = Transaction{} } - for zb0006 > 0 { - zb0006-- + for zb0007 > 0 { + zb0007-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -5192,14 +5686,14 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "note": - var zb0020 int - zb0020, err = msgp.ReadBytesBytesHeader(bts) + var zb0025 int + zb0025, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "Note") return } - if zb0020 > config.MaxTxnNoteBytes { - err = msgp.ErrOverflow(uint64(zb0020), uint64(config.MaxTxnNoteBytes)) + if zb0025 > config.MaxTxnNoteBytes { + err = msgp.ErrOverflow(uint64(zb0025), uint64(config.MaxTxnNoteBytes)) return } (*z).Header.Note, bts, err = msgp.ReadBytesBytes(bts, (*z).Header.Note) @@ -5365,33 +5859,33 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } case "apan": { - var zb0021 uint64 - zb0021, bts, err = msgp.ReadUint64Bytes(bts) + var zb0026 uint64 + zb0026, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "OnCompletion") return } - (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0021) + (*z).ApplicationCallTxnFields.OnCompletion = OnCompletion(zb0026) } case "apaa": - var zb0022 int - var zb0023 bool - zb0022, zb0023, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0027 int + var zb0028 bool + zb0027, zb0028, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0022 > encodedMaxApplicationArgs { - err = msgp.ErrOverflow(uint64(zb0022), uint64(encodedMaxApplicationArgs)) + if zb0027 > encodedMaxApplicationArgs { + err = msgp.ErrOverflow(uint64(zb0027), uint64(encodedMaxApplicationArgs)) err = msgp.WrapError(err, "ApplicationArgs") return } - if zb0023 { + if zb0028 { (*z).ApplicationCallTxnFields.ApplicationArgs = nil - } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0022 { - (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0022] + } else if (*z).ApplicationCallTxnFields.ApplicationArgs != nil && cap((*z).ApplicationCallTxnFields.ApplicationArgs) >= zb0027 { + (*z).ApplicationCallTxnFields.ApplicationArgs = ((*z).ApplicationCallTxnFields.ApplicationArgs)[:zb0027] } else { - (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0022) + (*z).ApplicationCallTxnFields.ApplicationArgs = make([][]byte, zb0027) } for zb0002 := range (*z).ApplicationCallTxnFields.ApplicationArgs { (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002], bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002]) @@ -5401,24 +5895,24 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } } case "apat": - var zb0024 int - var zb0025 bool - zb0024, zb0025, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0029 int + var zb0030 bool + zb0029, zb0030, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Accounts") return } - if zb0024 > encodedMaxAccounts { - err = msgp.ErrOverflow(uint64(zb0024), uint64(encodedMaxAccounts)) + if zb0029 > encodedMaxAccounts { + err = msgp.ErrOverflow(uint64(zb0029), uint64(encodedMaxAccounts)) err = msgp.WrapError(err, "Accounts") return } - if zb0025 { + if zb0030 { (*z).ApplicationCallTxnFields.Accounts = nil - } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0024 { - (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0024] + } else if (*z).ApplicationCallTxnFields.Accounts != nil && cap((*z).ApplicationCallTxnFields.Accounts) >= zb0029 { + (*z).ApplicationCallTxnFields.Accounts = ((*z).ApplicationCallTxnFields.Accounts)[:zb0029] } else { - (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0024) + (*z).ApplicationCallTxnFields.Accounts = make([]basics.Address, zb0029) } for zb0003 := range (*z).ApplicationCallTxnFields.Accounts { bts, err = (*z).ApplicationCallTxnFields.Accounts[zb0003].UnmarshalMsg(bts) @@ -5428,24 +5922,24 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } } case "apfa": - var zb0026 int - var zb0027 bool - zb0026, zb0027, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0031 int + var zb0032 bool + zb0031, zb0032, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ForeignApps") return } - if zb0026 > encodedMaxForeignApps { - err = msgp.ErrOverflow(uint64(zb0026), uint64(encodedMaxForeignApps)) + if zb0031 > encodedMaxForeignApps { + err = msgp.ErrOverflow(uint64(zb0031), uint64(encodedMaxForeignApps)) err = msgp.WrapError(err, "ForeignApps") return } - if zb0027 { + if zb0032 { (*z).ApplicationCallTxnFields.ForeignApps = nil - } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0026 { - (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0026] + } else if (*z).ApplicationCallTxnFields.ForeignApps != nil && cap((*z).ApplicationCallTxnFields.ForeignApps) >= zb0031 { + (*z).ApplicationCallTxnFields.ForeignApps = ((*z).ApplicationCallTxnFields.ForeignApps)[:zb0031] } else { - (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0026) + (*z).ApplicationCallTxnFields.ForeignApps = make([]basics.AppIndex, zb0031) } for zb0004 := range (*z).ApplicationCallTxnFields.ForeignApps { bts, err = (*z).ApplicationCallTxnFields.ForeignApps[zb0004].UnmarshalMsg(bts) @@ -5454,30 +5948,121 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + case "apbx": + var zb0033 int + var zb0034 bool + zb0033, zb0034, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes") + return + } + if zb0033 > encodedMaxBoxes { + err = msgp.ErrOverflow(uint64(zb0033), uint64(encodedMaxBoxes)) + err = msgp.WrapError(err, "Boxes") + return + } + if zb0034 { + (*z).ApplicationCallTxnFields.Boxes = nil + } else if (*z).ApplicationCallTxnFields.Boxes != nil && cap((*z).ApplicationCallTxnFields.Boxes) >= zb0033 { + (*z).ApplicationCallTxnFields.Boxes = ((*z).ApplicationCallTxnFields.Boxes)[:zb0033] + } else { + (*z).ApplicationCallTxnFields.Boxes = make([]BoxRef, zb0033) + } + for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { + var zb0035 int + var zb0036 bool + zb0035, zb0036, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0035, zb0036, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005) + return + } + if zb0035 > 0 { + zb0035-- + (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array", "Index") + return + } + } + if zb0035 > 0 { + zb0035-- + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array", "Name") + return + } + } + if zb0035 > 0 { + err = msgp.ErrTooManyArrayFields(zb0035) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005) + return + } + if zb0036 { + (*z).ApplicationCallTxnFields.Boxes[zb0005] = BoxRef{} + } + for zb0035 > 0 { + zb0035-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005) + return + } + switch string(field) { + case "i": + (*z).ApplicationCallTxnFields.Boxes[zb0005].Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005, "Index") + return + } + case "n": + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005, "Name") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err, "Boxes", zb0005) + return + } + } + } + } + } case "apas": - var zb0028 int - var zb0029 bool - zb0028, zb0029, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0037 int + var zb0038 bool + zb0037, zb0038, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ForeignAssets") return } - if zb0028 > encodedMaxForeignAssets { - err = msgp.ErrOverflow(uint64(zb0028), uint64(encodedMaxForeignAssets)) + if zb0037 > encodedMaxForeignAssets { + err = msgp.ErrOverflow(uint64(zb0037), uint64(encodedMaxForeignAssets)) err = msgp.WrapError(err, "ForeignAssets") return } - if zb0029 { + if zb0038 { (*z).ApplicationCallTxnFields.ForeignAssets = nil - } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0028 { - (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0028] + } else if (*z).ApplicationCallTxnFields.ForeignAssets != nil && cap((*z).ApplicationCallTxnFields.ForeignAssets) >= zb0037 { + (*z).ApplicationCallTxnFields.ForeignAssets = ((*z).ApplicationCallTxnFields.ForeignAssets)[:zb0037] } else { - (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0028) + (*z).ApplicationCallTxnFields.ForeignAssets = make([]basics.AssetIndex, zb0037) } - for zb0005 := range (*z).ApplicationCallTxnFields.ForeignAssets { - bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0005].UnmarshalMsg(bts) + for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { + bts, err = (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "ForeignAssets", zb0005) + err = msgp.WrapError(err, "ForeignAssets", zb0006) return } } @@ -5494,14 +6079,14 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "apap": - var zb0030 int - zb0030, err = msgp.ReadBytesBytesHeader(bts) + var zb0039 int + zb0039, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ApprovalProgram") return } - if zb0030 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0030), uint64(config.MaxAvailableAppProgramLen)) + if zb0039 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0039), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ApprovalProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ApprovalProgram) @@ -5510,14 +6095,14 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "apsu": - var zb0031 int - zb0031, err = msgp.ReadBytesBytesHeader(bts) + var zb0040 int + zb0040, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "ClearStateProgram") return } - if zb0031 > config.MaxAvailableAppProgramLen { - err = msgp.ErrOverflow(uint64(zb0031), uint64(config.MaxAvailableAppProgramLen)) + if zb0040 > config.MaxAvailableAppProgramLen { + err = msgp.ErrOverflow(uint64(zb0040), uint64(config.MaxAvailableAppProgramLen)) return } (*z).ApplicationCallTxnFields.ClearStateProgram, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.ClearStateProgram) @@ -5582,8 +6167,12 @@ func (z *Transaction) Msgsize() (s int) { s += (*z).ApplicationCallTxnFields.ForeignApps[zb0004].Msgsize() } s += 5 + msgp.ArrayHeaderSize - for zb0005 := range (*z).ApplicationCallTxnFields.ForeignAssets { - s += (*z).ApplicationCallTxnFields.ForeignAssets[zb0005].Msgsize() + for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { + s += 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + } + s += 5 + msgp.ArrayHeaderSize + for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { + s += (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].Msgsize() } s += 5 + (*z).ApplicationCallTxnFields.LocalStateSchema.Msgsize() + 5 + (*z).ApplicationCallTxnFields.GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ClearStateProgram) + 5 + msgp.Uint32Size + 8 + (*z).CompactCertTxnFields.CertRound.Msgsize() + 9 + (*z).CompactCertTxnFields.CertType.Msgsize() + 5 + (*z).CompactCertTxnFields.Cert.Msgsize() return @@ -5591,7 +6180,7 @@ func (z *Transaction) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *Transaction) MsgIsZero() bool { - return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).CompactCertTxnFields.CertRound.MsgIsZero()) && ((*z).CompactCertTxnFields.CertType.MsgIsZero()) && ((*z).CompactCertTxnFields.Cert.MsgIsZero()) + return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.Boxes) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).CompactCertTxnFields.CertRound.MsgIsZero()) && ((*z).CompactCertTxnFields.CertType.MsgIsZero()) && ((*z).CompactCertTxnFields.Cert.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler diff --git a/data/transactions/msgp_gen_test.go b/data/transactions/msgp_gen_test.go index daf552f982..bfc997627b 100644 --- a/data/transactions/msgp_gen_test.go +++ b/data/transactions/msgp_gen_test.go @@ -314,6 +314,66 @@ func BenchmarkUnmarshalAssetTransferTxnFields(b *testing.B) { } } +func TestMarshalUnmarshalBoxRef(t *testing.T) { + partitiontest.PartitionTest(t) + v := BoxRef{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingBoxRef(t *testing.T) { + protocol.RunEncodingTest(t, &BoxRef{}) +} + +func BenchmarkMarshalMsgBoxRef(b *testing.B) { + v := BoxRef{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgBoxRef(b *testing.B) { + v := BoxRef{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalBoxRef(b *testing.B) { + v := BoxRef{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalCompactCertTxnFields(t *testing.T) { partitiontest.PartitionTest(t) v := CompactCertTxnFields{} diff --git a/data/txntest/txn.go b/data/txntest/txn.go index c137b171ff..ddbdc29f0d 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -94,6 +94,7 @@ type Txn struct { Accounts []basics.Address ForeignApps []basics.AppIndex ForeignAssets []basics.AssetIndex + Boxes []transactions.BoxRef LocalStateSchema basics.StateSchema GlobalStateSchema basics.StateSchema ApprovalProgram interface{} // string, nil, or []bytes if already compiled @@ -107,10 +108,20 @@ type Txn struct { // Noted returns a new Txn with the given note field. func (tx *Txn) Noted(note string) *Txn { - copy := &Txn{} - *copy = *tx + copy := *tx copy.Note = []byte(note) - return copy + return © +} + +// Args returns a new Txn with the given strings as app args +func (tx *Txn) Args(strings ...string) *Txn { + copy := *tx + bytes := make([][]byte, len(strings)) + for i, s := range strings { + bytes[i] = []byte(s) + } + copy.ApplicationArgs = bytes + return © } // FillDefaults populates some obvious defaults from config params, @@ -234,6 +245,7 @@ func (tx Txn) Txn() transactions.Transaction { Accounts: tx.Accounts, ForeignApps: tx.ForeignApps, ForeignAssets: tx.ForeignAssets, + Boxes: tx.Boxes, LocalStateSchema: tx.LocalStateSchema, GlobalStateSchema: tx.GlobalStateSchema, ApprovalProgram: assemble(tx.ApprovalProgram), diff --git a/ledger/accountdb.go b/ledger/accountdb.go index a438f578d4..f2509489b8 100644 --- a/ledger/accountdb.go +++ b/ledger/accountdb.go @@ -44,6 +44,7 @@ type accountsDbQueries struct { lookupStmt *sql.Stmt lookupResourcesStmt *sql.Stmt lookupAllResourcesStmt *sql.Stmt + lookupKvPairStmt *sql.Stmt lookupCreatorStmt *sql.Stmt deleteStoredCatchpoint *sql.Stmt insertStoredCatchpoint *sql.Stmt @@ -71,6 +72,9 @@ var accountsSchema = []string{ `CREATE TABLE IF NOT EXISTS accountbase ( address blob primary key, data blob)`, + `CREATE TABLE IF NOT EXISTS kvstore ( + key blob primary key, + value blob)`, `CREATE TABLE IF NOT EXISTS assetcreators ( asset integer primary key, creator blob)`, @@ -1872,6 +1876,11 @@ func accountsInitDbQueries(r db.Queryable, w db.Queryable) (*accountsDbQueries, return nil, err } + qs.lookupKvPairStmt, err = r.Prepare("SELECT value FROM kvstore WHERE key = ?") + if err != nil { + return nil, err + } + qs.lookupCreatorStmt, err = r.Prepare("SELECT rnd, creator FROM acctrounds LEFT JOIN assetcreators ON asset = ? AND ctype = ? WHERE id='acctbase'") if err != nil { return nil, err @@ -1952,6 +1961,22 @@ func (qs *accountsDbQueries) listCreatables(maxIdx basics.CreatableIndex, maxRes return } +func (qs *accountsDbQueries) lookupKvPair(key string) (value string, ok bool, err error) { + err = db.Retry(func() error { + err := qs.lookupKvPairStmt.QueryRow(key).Scan(&value) + if err != nil { + if err == sql.ErrNoRows { + return nil // value and ok remain zero values + } + return err + } + // value has been set by Scan + ok = true + return nil + }) + return +} + func (qs *accountsDbQueries) lookupCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (addr basics.Address, ok bool, dbRound basics.Round, err error) { err = db.Retry(func() error { var buf []byte @@ -2197,6 +2222,7 @@ func (qs *accountsDbQueries) close() { &qs.lookupStmt, &qs.lookupResourcesStmt, &qs.lookupAllResourcesStmt, + &qs.lookupKvPairStmt, &qs.lookupCreatorStmt, &qs.deleteStoredCatchpoint, &qs.insertStoredCatchpoint, @@ -2297,6 +2323,9 @@ type accountsWriter interface { deleteResource(addrid int64, aidx basics.CreatableIndex) (rowsAffected int64, err error) updateResource(addrid int64, aidx basics.CreatableIndex, data resourcesData) (rowsAffected int64, err error) + upsertKvPair(key string, value string) error + deleteKvPair(key string) error + insertCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType, creator []byte) (rowid int64, err error) deleteCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType) (rowsAffected int64, err error) @@ -2307,6 +2336,7 @@ type accountsSQLWriter struct { insertCreatableIdxStmt, deleteCreatableIdxStmt *sql.Stmt deleteByRowIDStmt, insertStmt, updateStmt *sql.Stmt deleteResourceStmt, insertResourceStmt, updateResourceStmt *sql.Stmt + deleteKvPairStmt, upsertKvPairStmt *sql.Stmt } func (w *accountsSQLWriter) close() { @@ -2334,6 +2364,14 @@ func (w *accountsSQLWriter) close() { w.updateResourceStmt.Close() w.updateResourceStmt = nil } + if w.deleteKvPairStmt != nil { + w.deleteKvPairStmt.Close() + w.deleteKvPairStmt = nil + } + if w.upsertKvPairStmt != nil { + w.upsertKvPairStmt.Close() + w.upsertKvPairStmt = nil + } if w.insertCreatableIdxStmt != nil { w.insertCreatableIdxStmt.Close() w.insertCreatableIdxStmt = nil @@ -2344,7 +2382,7 @@ func (w *accountsSQLWriter) close() { } } -func makeAccountsSQLWriter(tx *sql.Tx, hasAccounts bool, hasResources bool, hasCreatables bool) (w *accountsSQLWriter, err error) { +func makeAccountsSQLWriter(tx *sql.Tx, hasAccounts, hasResources, hasKvPairs, hasCreatables bool) (w *accountsSQLWriter, err error) { w = new(accountsSQLWriter) if hasAccounts { @@ -2381,6 +2419,18 @@ func makeAccountsSQLWriter(tx *sql.Tx, hasAccounts bool, hasResources bool, hasC } } + if hasKvPairs { + w.upsertKvPairStmt, err = tx.Prepare("INSERT INTO kvstore (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value") + if err != nil { + return + } + + w.deleteKvPairStmt, err = tx.Prepare("DELETE FROM kvstore WHERE key=?") + if err != nil { + return + } + } + if hasCreatables { w.insertCreatableIdxStmt, err = tx.Prepare("INSERT INTO assetcreators (asset, creator, ctype) VALUES (?, ?, ?)") if err != nil { @@ -2449,6 +2499,24 @@ func (w accountsSQLWriter) updateResource(addrid int64, aidx basics.CreatableInd return } +func (w accountsSQLWriter) upsertKvPair(key string, value string) error { + result, err := w.upsertKvPairStmt.Exec(key, value) + if err != nil { + return err + } + _, err = result.LastInsertId() + return err +} + +func (w accountsSQLWriter) deleteKvPair(key string) error { + result, err := w.deleteKvPairStmt.Exec(key) + if err != nil { + return err + } + _, err = result.RowsAffected() + return err +} + func (w accountsSQLWriter) insertCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType, creator []byte) (rowid int64, err error) { result, err := w.insertCreatableIdxStmt.Exec(cidx, creator, ctype) if err != nil { @@ -2470,29 +2538,29 @@ func (w accountsSQLWriter) deleteCreatable(cidx basics.CreatableIndex, ctype bas // accountsNewRound is a convenience wrapper for accountsNewRoundImpl func accountsNewRound( tx *sql.Tx, - updates compactAccountDeltas, resources compactResourcesDeltas, creatables map[basics.CreatableIndex]ledgercore.ModifiedCreatable, + updates compactAccountDeltas, resources compactResourcesDeltas, kvPairs map[string]modifiedValue, creatables map[basics.CreatableIndex]ledgercore.ModifiedCreatable, proto config.ConsensusParams, lastUpdateRound basics.Round, ) (updatedAccounts []persistedAccountData, updatedResources map[basics.Address][]persistedResourcesData, err error) { hasAccounts := updates.len() > 0 hasResources := resources.len() > 0 + hasKvPairs := len(kvPairs) > 0 hasCreatables := len(creatables) > 0 - writer, err := makeAccountsSQLWriter(tx, hasAccounts, hasResources, hasCreatables) + writer, err := makeAccountsSQLWriter(tx, hasAccounts, hasResources, hasKvPairs, hasCreatables) if err != nil { return } defer writer.close() - return accountsNewRoundImpl(writer, updates, resources, creatables, proto, lastUpdateRound) + return accountsNewRoundImpl(writer, updates, resources, kvPairs, creatables, proto, lastUpdateRound) } // accountsNewRoundImpl updates the accountbase and assetcreators tables by applying the provided deltas to the accounts / creatables. // The function returns a persistedAccountData for the modified accounts which can be stored in the base cache. func accountsNewRoundImpl( writer accountsWriter, - updates compactAccountDeltas, resources compactResourcesDeltas, creatables map[basics.CreatableIndex]ledgercore.ModifiedCreatable, + updates compactAccountDeltas, resources compactResourcesDeltas, kvPairs map[string]modifiedValue, creatables map[basics.CreatableIndex]ledgercore.ModifiedCreatable, proto config.ConsensusParams, lastUpdateRound basics.Round, ) (updatedAccounts []persistedAccountData, updatedResources map[basics.Address][]persistedResourcesData, err error) { - updatedAccounts = make([]persistedAccountData, updates.len()) updatedAccountIdx := 0 newAddressesRowIDs := make(map[basics.Address]int64) @@ -2690,16 +2758,25 @@ func accountsNewRoundImpl( } } - if len(creatables) > 0 { - for cidx, cdelta := range creatables { - if cdelta.Created { - _, err = writer.insertCreatable(cidx, cdelta.Ctype, cdelta.Creator[:]) - } else { - _, err = writer.deleteCreatable(cidx, cdelta.Ctype) - } - if err != nil { - return - } + for key, value := range kvPairs { + if value.data != nil { + err = writer.upsertKvPair(key, *value.data) + } else { + err = writer.deleteKvPair(key) + } + if err != nil { + return + } + } + + for cidx, cdelta := range creatables { + if cdelta.Created { + _, err = writer.insertCreatable(cidx, cdelta.Ctype, cdelta.Creator[:]) + } else { + _, err = writer.deleteCreatable(cidx, cdelta.Ctype) + } + if err != nil { + return } } diff --git a/ledger/accountdb_test.go b/ledger/accountdb_test.go index b771d1532f..8a6382ccac 100644 --- a/ledger/accountdb_test.go +++ b/ledger/accountdb_test.go @@ -269,7 +269,7 @@ func TestAccountDBRound(t *testing.T) { err = accountsPutTotals(tx, totals, false) require.NoError(t, err) - updatedAccts, updatesResources, err := accountsNewRound(tx, updatesCnt, resourceUpdatesCnt, ctbsWithDeletes, proto, basics.Round(i)) + updatedAccts, updatesResources, err := accountsNewRound(tx, updatesCnt, resourceUpdatesCnt, nil, ctbsWithDeletes, proto, basics.Round(i)) require.NoError(t, err) require.Equal(t, updatesCnt.len(), len(updatedAccts)) numResUpdates := 0 @@ -393,7 +393,7 @@ func TestAccountDBInMemoryAcct(t *testing.T) { err = outResourcesDeltas.resourcesLoadOld(tx, knownAddresses) require.NoError(t, err) - updatedAccts, updatesResources, err := accountsNewRound(tx, outAccountDeltas, outResourcesDeltas, nil, proto, basics.Round(lastRound)) + updatedAccts, updatesResources, err := accountsNewRound(tx, outAccountDeltas, outResourcesDeltas, nil, nil, proto, basics.Round(lastRound)) require.NoError(t, err) require.Equal(t, 1, len(updatedAccts)) // we store empty even for deleted accounts require.Equal(t, @@ -2185,6 +2185,8 @@ type mockAccountWriter struct { rowids map[int64]basics.Address resources map[mockResourcesKey]ledgercore.AccountResource + kvStore map[string]string + lastRowid int64 availRowIds []int64 } @@ -2375,6 +2377,16 @@ func (m *mockAccountWriter) updateResource(addrid int64, aidx basics.CreatableIn return 1, nil } +func (m *mockAccountWriter) upsertKvPair(key string, value string) error { + m.kvStore[key] = value + return nil +} + +func (m *mockAccountWriter) deleteKvPair(key string) error { + delete(m.kvStore, key) + return nil +} + func (m *mockAccountWriter) insertCreatable(cidx basics.CreatableIndex, ctype basics.CreatableType, creator []byte) (rowid int64, err error) { return 0, fmt.Errorf("insertCreatable: not implemented") } @@ -2619,7 +2631,7 @@ func TestAccountUnorderedUpdates(t *testing.T) { a := require.New(t) mock2 := mock.clone() updatedAccounts, updatedResources, err := accountsNewRoundImpl( - &mock2, acctVariant, resVariant, nil, config.ConsensusParams{}, latestRound, + &mock2, acctVariant, resVariant, nil, nil, config.ConsensusParams{}, latestRound, ) a.NoError(err) a.Equal(3, len(updatedAccounts)) @@ -2701,7 +2713,7 @@ func TestAccountsNewRoundDeletedResourceEntries(t *testing.T) { a.Equal(2, resDeltas.len()) // (addr1, aidx) found updatedAccounts, updatedResources, err := accountsNewRoundImpl( - &mock, acctDeltas, resDeltas, nil, config.ConsensusParams{}, latestRound, + &mock, acctDeltas, resDeltas, nil, nil, config.ConsensusParams{}, latestRound, ) a.NoError(err) a.Equal(3, len(updatedAccounts)) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 7ace8c55d0..41e111fd1c 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -125,6 +125,20 @@ type modifiedResource struct { ndeltas int } +// A modifiedValue represents the value that has been modified since the +// persistent state stored in the account DB (i.e., in the range of rounds +// covered by the accountUpdates tracker). +type modifiedValue struct { + // data stores the most recent value (nil == deleted) + data *string + + // ndelta keeps track of how many times the key for this value appears in + // accountUpdates.deltas. This is used to evict modifiedValue entries when + // all changes to a key have been reflected in the kv table, and no + // outstanding modifications remain. + ndeltas int +} + type accountUpdates struct { // Connection to the database. dbs db.Pair @@ -147,6 +161,13 @@ type accountUpdates struct { // address&resource that appears in deltas. resources resourcesUpdates + // kvDeltas stores kvPair updates for every round after dbRound. + kvDeltas []map[string]*string + + // kvStore has the most recent kv pairs for every write/del that appears in + // deltas. + kvStore map[string]modifiedValue + // creatableDeltas stores creatable updates for every round after dbRound. creatableDeltas []map[basics.CreatableIndex]ledgercore.ModifiedCreatable @@ -298,6 +319,68 @@ func (au *accountUpdates) LookupResource(rnd basics.Round, addr basics.Address, return au.lookupResource(rnd, addr, aidx, ctype, true /* take lock */) } +func (au *accountUpdates) LookupKv(rnd basics.Round, key string) (*string, error) { + // TODO: Learn how locking discipline works in lookupResource / + // LookupWithoutRewards, particularly the optional lock taking. + + // TODO lookupResource / lookupWithoutRewards uses a loop and round checking + // here. It _looks_ like that only loops in case of programming error? + // Maybe it has to do with outstanding writes that haven't hit db yet. Is + // that possible, since we don't trim delta until it happens? + currentDbRound := au.cachedDBRound + currentDeltaLen := len(au.deltas) + offset, err := au.roundOffset(rnd) + if err != nil { + return nil, err + } + + // check if we have this key in `kvStore`, as that means the change we + // care about is in kvDeltas (and maybe just kvStore itself) + mval, indeltas := au.kvStore[key] + if indeltas { + // Check if this is the most recent round, in which case, we can + // use a cache of the most recent kvStore state + if offset == uint64(len(au.kvDeltas)) { + return mval.data, nil + } + + // the key is in the deltas, but we don't know if it appears in the + // delta range of [0..offset], so we'll need to check. Walk deltas + // backwards so later updates take priority. + for i := offset - 1; i > 0; i-- { + mval, ok := au.kvDeltas[i][key] + if ok { + return mval, nil + } + } + } else { + // we know that the key in not in kvDeltas - so there is no point in scanning it. + // we've going to fall back to search in the database, but before doing so, we should + // update the rnd so that it would point to the end of the known delta range. + // ( that would give us the best validity range ) + rnd = currentDbRound + basics.Round(currentDeltaLen) + // TODO: THIS IS POINTLESS FOT KV's current interface. I don't know + // how the validity window is used yet. -jj + } + + // OTHER LOOKUPS USE "base" caches here. + + // No updates of this account in kvDeltas; use on-disk DB. The check in + // roundOffset() made sure the round is exactly the one present in the + // on-disk DB. As an optimization, we avoid creating a separate + // transaction here, and directly use a prepared SQL query against the + // database. + + persistedValue, ok, err := au.accountsq.lookupKvPair(key) + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + return &persistedValue, nil +} + // LookupWithoutRewards returns the account data for a given address at a given round. func (au *accountUpdates) LookupWithoutRewards(rnd basics.Round, addr basics.Address) (data ledgercore.AccountData, validThrough basics.Round, err error) { data, validThrough, _, _, err = au.lookupWithoutRewards(rnd, addr, true /* take lock*/) @@ -723,6 +806,10 @@ func (aul *accountUpdatesLedgerEvaluator) LookupAsset(rnd basics.Round, addr bas return ledgercore.AssetResource{AssetParams: r.AssetParams, AssetHolding: r.AssetHolding}, err } +func (aul *accountUpdatesLedgerEvaluator) LookupKv(rnd basics.Round, key string) (*string, error) { + panic("not implemented") +} + // GetCreatorForRound returns the asset/app creator for a given asset/app index at a given round func (aul *accountUpdatesLedgerEvaluator) GetCreatorForRound(rnd basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType) (creator basics.Address, ok bool, err error) { return aul.au.getCreatorForRound(rnd, cidx, ctype, false /* don't sync */) @@ -782,9 +869,11 @@ func (au *accountUpdates) initializeFromDisk(l ledgerForTracker, lastBalancesRou au.versions = []protocol.ConsensusVersion{hdr.CurrentProtocol} au.deltas = nil + au.kvDeltas = nil au.creatableDeltas = nil au.accounts = make(map[basics.Address]modifiedAccount) - au.resources = resourcesUpdates(make(map[accountCreatable]modifiedResource)) + au.resources = make(resourcesUpdates) + au.kvStore = make(map[string]modifiedValue) au.creatables = make(map[basics.CreatableIndex]ledgercore.ModifiedCreatable) au.deltasAccum = []int{0} @@ -809,6 +898,7 @@ func (au *accountUpdates) newBlockImpl(blk bookkeeping.Block, delta ledgercore.S au.deltas = append(au.deltas, delta.Accts) au.versions = append(au.versions, blk.CurrentProtocol) au.creatableDeltas = append(au.creatableDeltas, delta.Creatables) + au.kvDeltas = append(au.kvDeltas, delta.KvMods) au.deltasAccum = append(au.deltasAccum, delta.Accts.Len()+au.deltasAccum[len(au.deltasAccum)-1]) au.baseAccounts.flushPendingWrites() @@ -844,6 +934,13 @@ func (au *accountUpdates) newBlockImpl(blk bookkeeping.Block, delta ledgercore.S au.resources.set(key, mres) } + for k, v := range delta.KvMods { + mvalue := au.kvStore[k] + mvalue.ndeltas++ + mvalue.data = v + au.kvStore[k] = mvalue + } + for cidx, cdelta := range delta.Creatables { mcreat := au.creatables[cidx] mcreat.Creator = cdelta.Creator @@ -1094,7 +1191,7 @@ func (au *accountUpdates) lookupLatest(addr basics.Address) (data basics.Account } } -// lookupWithRewards returns the online account data for a given address at a given round. +// lookupOnlineAccountData returns the online account data for a given address at a given round. func (au *accountUpdates) lookupOnlineAccountData(rnd basics.Round, addr basics.Address) (data basics.OnlineAccountData, err error) { au.accountsMu.RLock() needUnlock := true @@ -1485,9 +1582,11 @@ func (au *accountUpdates) prepareCommit(dcc *deferredCommitContext) error { // create a copy of the deltas, round totals and protos for the range we're going to flush. dcc.deltas = make([]ledgercore.AccountDeltas, offset) + kvDeltas := make([]map[string]*string, offset) creatableDeltas := make([]map[basics.CreatableIndex]ledgercore.ModifiedCreatable, offset) dcc.roundTotals = au.roundTotals[offset] copy(dcc.deltas, au.deltas[:offset]) + copy(kvDeltas, au.kvDeltas[:offset]) copy(creatableDeltas, au.creatableDeltas[:offset]) // verify version correctness : all the entries in the au.versions[1:offset+1] should have the *same* version, and the committedUpTo should be enforcing that. @@ -1512,6 +1611,7 @@ func (au *accountUpdates) prepareCommit(dcc *deferredCommitContext) error { // being updated multiple times. When that happen, we can safely omit the intermediate updates. dcc.compactAccountDeltas = makeCompactAccountDeltas(dcc.deltas, dcc.oldBase, setUpdateRound, au.baseAccounts) dcc.compactResourcesDeltas = makeCompactResourceDeltas(dcc.deltas, dcc.oldBase, setUpdateRound, au.baseAccounts, au.baseResources) + dcc.compactKvDeltas = compactKvDeltas(kvDeltas) dcc.compactCreatableDeltas = compactCreatableDeltas(creatableDeltas) au.accountsMu.RUnlock() @@ -1525,8 +1625,8 @@ func (au *accountUpdates) prepareCommit(dcc *deferredCommitContext) error { return nil } -// commitRound closure is called within the same transaction for all trackers -// it receives current offset and dbRound +// commitRound is called within the same transaction for all trackers it +// receives current offset and dbRound func (au *accountUpdates) commitRound(ctx context.Context, tx *sql.Tx, dcc *deferredCommitContext) (err error) { offset := dcc.offset dbRound := dcc.oldBase @@ -1578,7 +1678,7 @@ func (au *accountUpdates) commitRound(ctx context.Context, tx *sql.Tx, dcc *defe // the updates of the actual account data is done last since the accountsNewRound would modify the compactDeltas old values // so that we can update the base account back. - dcc.updatedPersistedAccounts, dcc.updatedPersistedResources, err = accountsNewRound(tx, dcc.compactAccountDeltas, dcc.compactResourcesDeltas, dcc.compactCreatableDeltas, dcc.genesisProto, dbRound+basics.Round(offset)) + dcc.updatedPersistedAccounts, dcc.updatedPersistedResources, err = accountsNewRound(tx, dcc.compactAccountDeltas, dcc.compactResourcesDeltas, dcc.compactKvDeltas, dcc.compactCreatableDeltas, dcc.genesisProto, dbRound+basics.Round(offset)) if err != nil { return err } @@ -1656,6 +1756,19 @@ func (au *accountUpdates) postCommit(ctx context.Context, dcc *deferredCommitCon } } + for key, out := range dcc.compactKvDeltas { + cnt := out.ndeltas + mval := au.kvStore[key] + if cnt > mval.ndeltas { + au.log.Panicf("inconsistency: flushed %d changes to key %d, but au.kvStore had %d", cnt, key, mval.ndeltas) + } else if cnt == mval.ndeltas { + delete(au.kvStore, key) + } else { + mval.ndeltas -= cnt + au.kvStore[key] = mval + } + } + for cidx, modCrt := range dcc.compactCreatableDeltas { cnt := modCrt.Ndeltas mcreat, ok := au.creatables[cidx] @@ -1677,6 +1790,7 @@ func (au *accountUpdates) postCommit(ctx context.Context, dcc *deferredCommitCon au.deltasAccum = au.deltasAccum[offset:] au.versions = au.versions[offset:] au.roundTotals = au.roundTotals[offset:] + au.kvDeltas = au.kvDeltas[offset:] au.creatableDeltas = au.creatableDeltas[offset:] au.cachedDBRound = newBase @@ -1708,6 +1822,27 @@ func (au *accountUpdates) postCommit(ctx context.Context, dcc *deferredCommitCon func (au *accountUpdates) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } +// compactKvDeltas takes an array of kv deltas (one array entry per round), and +// compacts the array into a single map that contains all the +// changes. Intermediate changes are eliminated. It counts the number of +// changes per round by specifying it in the ndeltas field of the modifiedKv. +func compactKvDeltas(kvDeltas []map[string]*string) map[string]modifiedValue { + if len(kvDeltas) == 0 { + return nil + } + outKvDeltas := make(map[string]modifiedValue) + for _, roundKv := range kvDeltas { + for key, value := range roundKv { + prev := outKvDeltas[key] // prev may be the zero value. that's correct. + outKvDeltas[key] = modifiedValue{ + data: value, + ndeltas: prev.ndeltas + 1, + } + } + } + return outKvDeltas +} + // compactCreatableDeltas takes an array of creatables map deltas ( one array entry per round ), and compact the array into a single // map that contains all the deltas changes. While doing that, the function eliminate any intermediate changes. // It counts the number of changes per round by specifying it in the ndeltas field of the modifiedCreatable. diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index d2faf6ec7c..4c973f9fc1 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -1150,7 +1150,7 @@ func TestListCreatables(t *testing.T) { // sync with the database var updates compactAccountDeltas var resUpdates compactResourcesDeltas - _, _, err = accountsNewRound(tx, updates, resUpdates, ctbsWithDeletes, proto, basics.Round(1)) + _, _, err = accountsNewRound(tx, updates, resUpdates, nil, ctbsWithDeletes, proto, basics.Round(1)) require.NoError(t, err) // nothing left in cache au.creatables = make(map[basics.CreatableIndex]ledgercore.ModifiedCreatable) @@ -1166,7 +1166,7 @@ func TestListCreatables(t *testing.T) { // ******* Results are obtained from the database and from the cache ******* // ******* Deletes are in the database and in the cache ******* // sync with the database. This has deletes synced to the database. - _, _, err = accountsNewRound(tx, updates, resUpdates, au.creatables, proto, basics.Round(1)) + _, _, err = accountsNewRound(tx, updates, resUpdates, nil, au.creatables, proto, basics.Round(1)) require.NoError(t, err) // get new creatables in the cache. There will be deletes in the cache from the previous batch. au.creatables = randomCreatableSampling(3, ctbsList, randomCtbs, @@ -1253,7 +1253,7 @@ func BenchmarkLargeMerkleTrieRebuild(b *testing.B) { } err := ml.dbs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - _, _, err = accountsNewRound(tx, updates, compactResourcesDeltas{}, nil, proto, basics.Round(1)) + _, _, err = accountsNewRound(tx, updates, compactResourcesDeltas{}, nil, nil, proto, basics.Round(1)) return }) require.NoError(b, err) diff --git a/ledger/apply/payment.go b/ledger/apply/payment.go index 9c1f0f94e3..c86f791e5a 100644 --- a/ledger/apply/payment.go +++ b/ledger/apply/payment.go @@ -99,6 +99,15 @@ func Payment(payment transactions.PaymentTxnFields, header transactions.Header, return fmt.Errorf("cannot close: %d outstanding applications opted in. Please opt out or clear them", totalAppLocalStates) } + // Confirm that there is no box-related state in the account + if rec.TotalBoxes > 0 { + return fmt.Errorf("cannot close: %d outstanding boxes", rec.TotalBoxes) + } + if rec.TotalBoxBytes > 0 { + // This should be impossible because every box byte comes from the existence of a box. + return fmt.Errorf("cannot close: %d outstanding box bytes", rec.TotalBoxBytes) + } + // Can't have created apps remaining either totalAppParams := rec.TotalAppParams if totalAppParams > 0 { diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index eb812937f7..654804fd46 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -372,7 +372,7 @@ func BenchmarkLargeCatchpointWriting(b *testing.B) { i++ } - _, _, err = accountsNewRound(tx, updates, compactResourcesDeltas{}, nil, proto, basics.Round(1)) + _, _, err = accountsNewRound(tx, updates, compactResourcesDeltas{}, nil, nil, proto, basics.Round(1)) if err != nil { return } diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go index a27d6118ba..5be31bd603 100644 --- a/ledger/evalindexer.go +++ b/ledger/evalindexer.go @@ -142,6 +142,10 @@ func (l indexerLedgerConnector) lookupResource(round basics.Round, address basic return accountResourceMap[address][Creatable{aidx, ctype}], nil } +func (l indexerLedgerConnector) LookupKv(rnd basics.Round, key string) (*string, error) { + panic("not implemented") +} + // GetCreatorForRound is part of LedgerForEvaluator interface. func (l indexerLedgerConnector) GetCreatorForRound(_ basics.Round, cindex basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { var foundAddress FoundAddress diff --git a/ledger/internal/appcow.go b/ledger/internal/appcow.go index 503ddd06bf..a04405db05 100644 --- a/ledger/internal/appcow.go +++ b/ledger/internal/appcow.go @@ -284,11 +284,6 @@ func (cb *roundCowState) DeallocateApp(addr basics.Address, aidx basics.AppIndex return nil } -// GetKey looks for a key in {addr, aidx, global} storage -func (cb *roundCowState) GetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) { - return cb.getKey(addr, aidx, global, key, accountIdx) -} - // getKey looks for a key in {addr, aidx, global} storage // This is hierarchical lookup: if the key not in this cow cache, then request parent and all way down to ledger func (cb *roundCowState) getKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) { @@ -339,8 +334,8 @@ func (cb *roundCowState) getKey(addr basics.Address, aidx basics.AppIndex, globa return cb.lookupParent.getKey(addr, aidx, global, key, accountIdx) } -// SetKey creates a new key-value in {addr, aidx, global} storage -func (cb *roundCowState) SetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, value basics.TealValue, accountIdx uint64) error { +// setKey creates a new key-value in {addr, aidx, global} storage +func (cb *roundCowState) setKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, value basics.TealValue, accountIdx uint64) error { // Enforce maximum key length if len(key) > cb.proto.MaxAppKeyLen { return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), cb.proto.MaxAppKeyLen) @@ -368,7 +363,7 @@ func (cb *roundCowState) SetKey(addr basics.Address, aidx basics.AppIndex, globa } // Fetch the old value + presence so we know how to update - oldValue, oldOk, err := cb.GetKey(addr, aidx, global, key, accountIdx) + oldValue, oldOk, err := cb.getKey(addr, aidx, global, key, accountIdx) if err != nil { return err } @@ -398,8 +393,8 @@ func (cb *roundCowState) SetKey(addr basics.Address, aidx basics.AppIndex, globa return lsd.checkCounts() } -// DelKey removes a key from {addr, aidx, global} storage -func (cb *roundCowState) DelKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) error { +// delKey removes a key from {addr, aidx, global} storage +func (cb *roundCowState) delKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) error { // Check that account has allocated storage allocated, err := cb.allocated(addr, aidx, global) if err != nil { @@ -411,7 +406,7 @@ func (cb *roundCowState) DelKey(addr basics.Address, aidx basics.AppIndex, globa } // Fetch the old value + presence so we know how to update counts - oldValue, oldOk, err := cb.GetKey(addr, aidx, global, key, accountIdx) + oldValue, oldOk, err := cb.getKey(addr, aidx, global, key, accountIdx) if err != nil { return err } @@ -461,7 +456,7 @@ func MakeDebugBalances(l LedgerForCowBase, round basics.Round, proto protocol.Co func (cb *roundCowState) StatefulEval(gi int, params *logic.EvalParams, aidx basics.AppIndex, program []byte) (pass bool, evalDelta transactions.EvalDelta, err error) { // Make a child cow to eval our program in calf := cb.child(1) - params.Ledger = newLogicLedger(calf) + params.Ledger = calf // Eval the program pass, cx, err := logic.EvalContract(program, gi, aidx, params) @@ -487,7 +482,7 @@ func (cb *roundCowState) StatefulEval(gi int, params *logic.EvalParams, aidx bas // changes from this app and any inner called apps. Instead, we now keep // the EvalDelta built as we go, in app evaluation. So just use it. if cb.proto.LogicSigVersion < 6 { - evalDelta, err = calf.BuildEvalDelta(aidx, ¶ms.TxnGroup[gi].Txn) + evalDelta, err = calf.buildEvalDelta(aidx, ¶ms.TxnGroup[gi].Txn) if err != nil { return false, transactions.EvalDelta{}, err } @@ -502,9 +497,9 @@ func (cb *roundCowState) StatefulEval(gi int, params *logic.EvalParams, aidx bas return pass, evalDelta, nil } -// BuildEvalDelta creates an EvalDelta by converting internal sdeltas +// buildEvalDelta creates an EvalDelta by converting internal sdeltas // into the (Global|Local)Delta fields. -func (cb *roundCowState) BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) { +func (cb *roundCowState) buildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) { // sdeltas foundGlobal := false for addr, smod := range cb.sdeltas { diff --git a/ledger/internal/appcow_test.go b/ledger/internal/appcow_test.go index 07669d61e3..2ccc0b0e06 100644 --- a/ledger/internal/appcow_test.go +++ b/ledger/internal/appcow_test.go @@ -87,7 +87,11 @@ func (ml *emptyLedger) getKey(addr basics.Address, aidx basics.AppIndex, global return basics.TealValue{}, false, nil } -func (ml *emptyLedger) txnCounter() uint64 { +func (ml *emptyLedger) kvGet(key string) (string, bool, error) { + return "", false, nil +} + +func (ml *emptyLedger) Counter() uint64 { return 0 } @@ -283,7 +287,7 @@ func TestCowStorage(t *testing.T) { actuallyAllocated := st.allocated(aapp) rkey := allKeys[rand.Intn(len(allKeys))] rval := allValues[rand.Intn(len(allValues))] - err := cow.SetKey(addr, sptr.aidx, sptr.global, rkey, rval, 0) + err := cow.setKey(addr, sptr.aidx, sptr.global, rkey, rval, 0) if actuallyAllocated { require.NoError(t, err) err = st.set(aapp, rkey, rval) @@ -298,7 +302,7 @@ func TestCowStorage(t *testing.T) { if rand.Float32() < 0.25 { actuallyAllocated := st.allocated(aapp) rkey := allKeys[rand.Intn(len(allKeys))] - err := cow.DelKey(addr, sptr.aidx, sptr.global, rkey, 0) + err := cow.delKey(addr, sptr.aidx, sptr.global, rkey, 0) if actuallyAllocated { require.NoError(t, err) err = st.del(aapp, rkey) @@ -340,7 +344,7 @@ func TestCowStorage(t *testing.T) { tval, tok, err := st.get(aapp, key) require.NoError(t, err) - cval, cok, err := cow.GetKey(addr, sptr.aidx, sptr.global, key, 0) + cval, cok, err := cow.getKey(addr, sptr.aidx, sptr.global, key, 0) require.NoError(t, err) require.Equal(t, tok, cok) require.Equal(t, tval, cval) @@ -379,29 +383,29 @@ func TestCowBuildDelta(t *testing.T) { cow := roundCowState{} cow.sdeltas = make(map[basics.Address]map[storagePtr]*storageDelta) txn := transactions.Transaction{} - ed, err := cow.BuildEvalDelta(aidx, &txn) + ed, err := cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Empty(ed) cow.sdeltas[creator] = make(map[storagePtr]*storageDelta) - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Empty(ed) // check global delta cow.sdeltas[creator][storagePtr{aidx, true}] = &storageDelta{} - ed, err = cow.BuildEvalDelta(1, &txn) + ed, err = cow.buildEvalDelta(1, &txn) a.Error(err) a.Contains(err.Error(), "found storage delta for different app") a.Empty(ed) cow.sdeltas[creator][storagePtr{aidx, true}] = &storageDelta{} - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal(transactions.EvalDelta{GlobalDelta: basics.StateDelta{}}, ed) cow.sdeltas[creator][storagePtr{aidx + 1, true}] = &storageDelta{} - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.Error(err) a.Contains(err.Error(), "found storage delta for different app") a.Empty(ed) @@ -409,7 +413,7 @@ func TestCowBuildDelta(t *testing.T) { delete(cow.sdeltas[creator], storagePtr{aidx + 1, true}) cow.sdeltas[sender] = make(map[storagePtr]*storageDelta) cow.sdeltas[sender][storagePtr{aidx, true}] = &storageDelta{} - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.Error(err) a.Contains(err.Error(), "found more than one global delta") a.Empty(ed) @@ -418,7 +422,7 @@ func TestCowBuildDelta(t *testing.T) { delete(cow.sdeltas[sender], storagePtr{aidx, true}) cow.sdeltas[sender][storagePtr{aidx, false}] = &storageDelta{} - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.Error(err) a.Contains(err.Error(), "invalid Account reference ") a.Empty(ed) @@ -428,7 +432,7 @@ func TestCowBuildDelta(t *testing.T) { cow.mods.Hdr = &bookkeeping.BlockHeader{ UpgradeState: bookkeeping.UpgradeState{CurrentProtocol: protocol.ConsensusV25}, } - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( transactions.EvalDelta{ @@ -441,7 +445,7 @@ func TestCowBuildDelta(t *testing.T) { // check v27 behavior for empty deltas cow.mods.Hdr = nil cow.proto = config.Consensus[protocol.ConsensusCurrentVersion] - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( transactions.EvalDelta{ @@ -464,7 +468,7 @@ func TestCowBuildDelta(t *testing.T) { }, }, } - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( transactions.EvalDelta{ @@ -504,7 +508,7 @@ func TestCowBuildDelta(t *testing.T) { }, } - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( transactions.EvalDelta{ @@ -538,7 +542,7 @@ func TestCowBuildDelta(t *testing.T) { }, }, } - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( transactions.EvalDelta{ @@ -569,7 +573,7 @@ func TestCowBuildDelta(t *testing.T) { }, accountIdx: 1, } - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( transactions.EvalDelta{ @@ -597,7 +601,7 @@ func TestCowBuildDelta(t *testing.T) { }, accountIdx: 1, } - ed, err = cow.BuildEvalDelta(aidx, &txn) + ed, err = cow.buildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( transactions.EvalDelta{ @@ -1063,8 +1067,8 @@ func TestCowGetters(t *testing.T) { ts := int64(11223344) c.mods.PrevTimestamp = ts - a.Equal(round, c.round()) - a.Equal(ts, c.prevTimestamp()) + a.Equal(round, c.Round()) + a.Equal(ts, c.PrevTimestamp()) } func TestCowGet(t *testing.T) { @@ -1104,7 +1108,7 @@ func TestCowGetKey(t *testing.T) { c.sdeltas = map[basics.Address]map[storagePtr]*storageDelta{ addr: {storagePtr{aidx, true}: &storageDelta{action: deallocAction}}, } - _, ok, err := c.GetKey(addr, aidx, true, "gkey", 0) + _, ok, err := c.getKey(addr, aidx, true, "gkey", 0) a.Error(err) a.False(ok) a.Contains(err.Error(), "cannot fetch key") @@ -1112,10 +1116,10 @@ func TestCowGetKey(t *testing.T) { c.sdeltas = map[basics.Address]map[storagePtr]*storageDelta{ addr: {storagePtr{aidx, true}: &storageDelta{action: allocAction}}, } - _, ok, err = c.GetKey(addr, aidx, true, "gkey", 0) + _, ok, err = c.getKey(addr, aidx, true, "gkey", 0) a.NoError(err) a.False(ok) - _, ok, err = c.GetKey(addr, aidx, true, "gkey", 0) + _, ok, err = c.getKey(addr, aidx, true, "gkey", 0) a.NoError(err) a.False(ok) @@ -1128,7 +1132,7 @@ func TestCowGetKey(t *testing.T) { }, }, } - _, ok, err = c.GetKey(addr, aidx, true, "gkey", 0) + _, ok, err = c.getKey(addr, aidx, true, "gkey", 0) a.NoError(err) a.False(ok) @@ -1140,7 +1144,7 @@ func TestCowGetKey(t *testing.T) { }, }, } - val, ok, err := c.GetKey(addr, aidx, true, "gkey", 0) + val, ok, err := c.getKey(addr, aidx, true, "gkey", 0) a.NoError(err) a.True(ok) a.Equal(tv, val) @@ -1155,14 +1159,14 @@ func TestCowGetKey(t *testing.T) { }, } - val, ok, err = c.GetKey(addr, aidx, false, "lkey", 0) + val, ok, err = c.getKey(addr, aidx, false, "lkey", 0) a.NoError(err) a.True(ok) a.Equal(tv, val) // ensure other requests go down to roundCowParent - a.Panics(func() { c.GetKey(ledgertesting.RandomAddress(), aidx, false, "lkey", 0) }) - a.Panics(func() { c.GetKey(addr, aidx+1, false, "lkey", 0) }) + a.Panics(func() { c.getKey(ledgertesting.RandomAddress(), aidx, false, "lkey", 0) }) + a.Panics(func() { c.getKey(addr, aidx+1, false, "lkey", 0) }) } func TestCowSetKey(t *testing.T) { @@ -1179,14 +1183,14 @@ func TestCowSetKey(t *testing.T) { key := strings.Repeat("key", 100) val := "val" tv := basics.TealValue{Type: basics.TealBytesType, Bytes: val} - err := c.SetKey(addr, aidx, true, key, tv, 0) + err := c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "key too long") key = "key" val = strings.Repeat("val", 100) tv = basics.TealValue{Type: basics.TealBytesType, Bytes: val} - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "value too long") @@ -1195,7 +1199,7 @@ func TestCowSetKey(t *testing.T) { c.sdeltas = map[basics.Address]map[storagePtr]*storageDelta{ addr: {storagePtr{aidx, true}: &storageDelta{action: deallocAction}}, } - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "cannot set key") @@ -1211,13 +1215,13 @@ func TestCowSetKey(t *testing.T) { }, }, } - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "exceeds schema bytes") counts = basics.StateSchema{NumUint: 1} maxCounts = basics.StateSchema{NumByteSlice: 1} - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "exceeds schema integer") @@ -1232,12 +1236,12 @@ func TestCowSetKey(t *testing.T) { }, }, } - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.NoError(err) counts = basics.StateSchema{NumUint: 1} maxCounts = basics.StateSchema{NumByteSlice: 1, NumUint: 1} - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.NoError(err) // check local @@ -1252,12 +1256,12 @@ func TestCowSetKey(t *testing.T) { }, }, } - err = c.SetKey(addr1, aidx, false, key, tv, 0) + err = c.setKey(addr1, aidx, false, key, tv, 0) a.NoError(err) // ensure other requests go down to roundCowParent - a.Panics(func() { c.SetKey(ledgertesting.RandomAddress(), aidx, false, key, tv, 0) }) - a.Panics(func() { c.SetKey(addr, aidx+1, false, key, tv, 0) }) + a.Panics(func() { c.setKey(ledgertesting.RandomAddress(), aidx, false, key, tv, 0) }) + a.Panics(func() { c.setKey(addr, aidx+1, false, key, tv, 0) }) } func TestCowSetKeyVFuture(t *testing.T) { @@ -1276,21 +1280,21 @@ func TestCowSetKeyVFuture(t *testing.T) { key := strings.Repeat("key", 100) val := "val" tv := basics.TealValue{Type: basics.TealBytesType, Bytes: val} - err := c.SetKey(addr, aidx, true, key, tv, 0) + err := c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "key too long") key = "key" val = strings.Repeat("val", 100) tv = basics.TealValue{Type: basics.TealBytesType, Bytes: val} - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "value too long") key = strings.Repeat("k", protoF.MaxAppKeyLen) val = strings.Repeat("v", protoF.MaxAppSumKeyValueLens-len(key)+1) tv = basics.TealValue{Type: basics.TealBytesType, Bytes: val} - err = c.SetKey(addr, aidx, true, key, tv, 0) + err = c.setKey(addr, aidx, true, key, tv, 0) a.Error(err) a.Contains(err.Error(), "key/value total too long") } @@ -1358,7 +1362,7 @@ func TestCowDelKey(t *testing.T) { c.sdeltas = map[basics.Address]map[storagePtr]*storageDelta{ addr: {storagePtr{aidx, true}: &storageDelta{action: deallocAction}}, } - err := c.DelKey(addr, aidx, true, key, 0) + err := c.delKey(addr, aidx, true, key, 0) a.Error(err) a.Contains(err.Error(), "cannot del key") @@ -1374,7 +1378,7 @@ func TestCowDelKey(t *testing.T) { }, }, } - err = c.DelKey(addr, aidx, true, key, 0) + err = c.delKey(addr, aidx, true, key, 0) a.NoError(err) c.sdeltas = map[basics.Address]map[storagePtr]*storageDelta{ @@ -1387,10 +1391,10 @@ func TestCowDelKey(t *testing.T) { }, }, } - err = c.DelKey(addr, aidx, false, key, 0) + err = c.delKey(addr, aidx, false, key, 0) a.NoError(err) // ensure other requests go down to roundCowParent - a.Panics(func() { c.DelKey(ledgertesting.RandomAddress(), aidx, false, key, 0) }) - a.Panics(func() { c.DelKey(addr, aidx+1, false, key, 0) }) + a.Panics(func() { c.delKey(ledgertesting.RandomAddress(), aidx, false, key, 0) }) + a.Panics(func() { c.delKey(addr, aidx+1, false, key, 0) }) } diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 8c56331dd1..fb71220d73 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -20,52 +20,21 @@ import ( "fmt" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" ) -type logicLedger struct { - cow cowForLogicLedger -} +/* This file adds functions to roundCowState that make it more palatable for use + outside of the ledger package. The LedgerForLogic interface expects them. */ -type cowForLogicLedger interface { - Get(addr basics.Address, withPendingRewards bool) (ledgercore.AccountData, error) - GetAppParams(addr basics.Address, aidx basics.AppIndex) (basics.AppParams, bool, error) - GetAssetParams(addr basics.Address, aidx basics.AssetIndex) (basics.AssetParams, bool, error) - GetAssetHolding(addr basics.Address, aidx basics.AssetIndex) (basics.AssetHolding, bool, error) - GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) - GetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) - BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (transactions.EvalDelta, error) - - SetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, value basics.TealValue, accountIdx uint64) error - DelKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) error - - round() basics.Round - prevTimestamp() int64 - allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) - txnCounter() uint64 - incTxnCount() +func (cs *roundCowState) AccountData(addr basics.Address) (ledgercore.AccountData, error) { + return cs.Get(addr, true) } -func newLogicLedger(cow cowForLogicLedger) *logicLedger { - return &logicLedger{ - cow: cow, - } -} - -func (al *logicLedger) AccountData(addr basics.Address) (ledgercore.AccountData, error) { - record, err := al.cow.Get(addr, true) - if err != nil { - return ledgercore.AccountData{}, err - } - return record, nil -} - -func (al *logicLedger) Authorizer(addr basics.Address) (basics.Address, error) { - record, err := al.cow.Get(addr, false) // pending rewards unneeded +func (cs *roundCowState) Authorizer(addr basics.Address) (basics.Address, error) { + record, err := cs.Get(addr, false) // pending rewards unneeded if err != nil { return basics.Address{}, err } @@ -75,25 +44,24 @@ func (al *logicLedger) Authorizer(addr basics.Address) (basics.Address, error) { return addr, nil } -func (al *logicLedger) AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error) { +func (cs *roundCowState) AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error) { // Fetch the requested balance record - holding, ok, err := al.cow.GetAssetHolding(addr, assetIdx) + holding, ok, err := cs.GetAssetHolding(addr, assetIdx) if err != nil { return basics.AssetHolding{}, err } // Ensure we have the requested holding if !ok { - err = fmt.Errorf("account %s has not opted in to asset %d", addr.String(), assetIdx) - return basics.AssetHolding{}, err + return basics.AssetHolding{}, fmt.Errorf("account %s has not opted in to asset %d", addr, assetIdx) } return holding, nil } -func (al *logicLedger) AssetParams(assetIdx basics.AssetIndex) (basics.AssetParams, basics.Address, error) { +func (cs *roundCowState) AssetParams(assetIdx basics.AssetIndex) (basics.AssetParams, basics.Address, error) { // Find asset creator - creator, ok, err := al.cow.GetCreator(basics.CreatableIndex(assetIdx), basics.AssetCreatable) + creator, ok, err := cs.GetCreator(basics.CreatableIndex(assetIdx), basics.AssetCreatable) if err != nil { return basics.AssetParams{}, creator, err } @@ -104,23 +72,22 @@ func (al *logicLedger) AssetParams(assetIdx basics.AssetIndex) (basics.AssetPara } // Fetch the requested balance record - params, ok, err := al.cow.GetAssetParams(creator, assetIdx) + params, ok, err := cs.GetAssetParams(creator, assetIdx) if err != nil { return basics.AssetParams{}, creator, err } // Ensure account created the requested asset if !ok { - err = fmt.Errorf("account %s has not created asset %d", creator, assetIdx) - return basics.AssetParams{}, creator, err + return basics.AssetParams{}, creator, fmt.Errorf("account %s has not created asset %d", creator, assetIdx) } return params, creator, nil } -func (al *logicLedger) AppParams(appIdx basics.AppIndex) (basics.AppParams, basics.Address, error) { +func (cs *roundCowState) AppParams(appIdx basics.AppIndex) (basics.AppParams, basics.Address, error) { // Find app creator - creator, ok, err := al.cow.GetCreator(basics.CreatableIndex(appIdx), basics.AppCreatable) + creator, ok, err := cs.GetCreator(basics.CreatableIndex(appIdx), basics.AppCreatable) if err != nil { return basics.AppParams{}, creator, err } @@ -131,47 +98,38 @@ func (al *logicLedger) AppParams(appIdx basics.AppIndex) (basics.AppParams, basi } // Fetch the requested balance record - params, ok, err := al.cow.GetAppParams(creator, appIdx) + params, ok, err := cs.GetAppParams(creator, appIdx) if err != nil { return basics.AppParams{}, creator, err } // Ensure account created the requested app if !ok { - err = fmt.Errorf("account %s has not created app %d", creator, appIdx) - return basics.AppParams{}, creator, err + return basics.AppParams{}, creator, fmt.Errorf("account %s has not created app %d", creator, appIdx) } return params, creator, nil } -func (al *logicLedger) Round() basics.Round { - return al.cow.round() +func (cs *roundCowState) OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) { + return cs.allocated(addr, appIdx, false) } -func (al *logicLedger) LatestTimestamp() int64 { - return al.cow.prevTimestamp() +func (cs *roundCowState) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) { + return cs.getKey(addr, appIdx, false, key, accountIdx) } -func (al *logicLedger) OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) { - return al.cow.allocated(addr, appIdx, false) +func (cs *roundCowState) SetLocal(addr basics.Address, appIdx basics.AppIndex, key string, value basics.TealValue, accountIdx uint64) error { + return cs.setKey(addr, appIdx, false, key, value, accountIdx) } -func (al *logicLedger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) { - return al.cow.GetKey(addr, appIdx, false, key, accountIdx) +func (cs *roundCowState) DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error { + return cs.delKey(addr, appIdx, false, key, accountIdx) } -func (al *logicLedger) SetLocal(addr basics.Address, appIdx basics.AppIndex, key string, value basics.TealValue, accountIdx uint64) error { - return al.cow.SetKey(addr, appIdx, false, key, value, accountIdx) -} - -func (al *logicLedger) DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error { - return al.cow.DelKey(addr, appIdx, false, key, accountIdx) -} - -func (al *logicLedger) fetchAppCreator(appIdx basics.AppIndex) (basics.Address, error) { +func (cs *roundCowState) fetchAppCreator(appIdx basics.AppIndex) (basics.Address, error) { // Fetch the application creator - addr, ok, err := al.cow.GetCreator(basics.CreatableIndex(appIdx), basics.AppCreatable) + addr, ok, err := cs.GetCreator(basics.CreatableIndex(appIdx), basics.AppCreatable) if err != nil { return basics.Address{}, err @@ -182,52 +140,163 @@ func (al *logicLedger) fetchAppCreator(appIdx basics.AppIndex) (basics.Address, return addr, nil } -func (al *logicLedger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue, bool, error) { - addr, err := al.fetchAppCreator(appIdx) +func (cs *roundCowState) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue, bool, error) { + creator, err := cs.fetchAppCreator(appIdx) if err != nil { return basics.TealValue{}, false, err } - return al.cow.GetKey(addr, appIdx, true, key, 0) + return cs.getKey(creator, appIdx, true, key, 0) } -func (al *logicLedger) SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error { - creator, err := al.fetchAppCreator(appIdx) +func (cs *roundCowState) SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error { + creator, err := cs.fetchAppCreator(appIdx) if err != nil { return err } - return al.cow.SetKey(creator, appIdx, true, key, value, 0) + return cs.setKey(creator, appIdx, true, key, value, 0) } -func (al *logicLedger) DelGlobal(appIdx basics.AppIndex, key string) error { - creator, err := al.fetchAppCreator(appIdx) +func (cs *roundCowState) DelGlobal(appIdx basics.AppIndex, key string) error { + creator, err := cs.fetchAppCreator(appIdx) if err != nil { return err } - return al.cow.DelKey(creator, appIdx, true, key, 0) + return cs.delKey(creator, appIdx, true, key, 0) +} + +func makeBoxKey(appIdx basics.AppIndex, key string) string { + // Reconsider this for something faster. Maybe msgpack encoding of array + // ["bk",appIdx,key]? + return fmt.Sprintf("bk:%d:%s", appIdx, key) +} + +func (cs *roundCowState) kvGet(key string) (string, bool, error) { + value, ok := cs.mods.KvMods[key] + if !ok { + return cs.lookupParent.kvGet(key) + } + if value == nil { + return "", false, nil + } + // If value is nil, it's a marker for a local deletion + return *value, true, nil } -func (al *logicLedger) balances() (apply.Balances, error) { - balances, ok := al.cow.(apply.Balances) +func (cb *roundCowBase) kvGet(key string) (string, bool, error) { + value, ok := cb.kvStore[key] if !ok { - return nil, fmt.Errorf("cannot get a Balances object from %v", al) + v, err := cb.l.LookupKv(cb.rnd, key) + if err != nil { + return "", false, err + } + value = v + } + // If value is nil, it caches a lookup that returned nothing. + if value == nil { + return "", false, nil } - return balances, nil + return *value, true, nil } -func (al *logicLedger) Perform(gi int, ep *logic.EvalParams) error { - txn := &ep.TxnGroup[gi] - balances, err := al.balances() +func (cs *roundCowState) kvPut(key string, value string) error { + cs.mods.KvMods[key] = &value + return nil +} + +func (cs *roundCowState) kvDel(key string) error { + cs.mods.KvMods[key] = nil + return nil +} + +func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) error { + // Use same limit on key length as for global/local storage + if len(key) > cs.proto.MaxAppKeyLen { + return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), cs.proto.MaxAppKeyLen) + } + + fullKey := makeBoxKey(appIdx, key) + _, ok, err := cs.kvGet(fullKey) + if err != nil { + return err + } + if ok { + return fmt.Errorf("book %s exists for %d", key, appIdx) + } + + // TODO: Choose and enforce a max size + + record, err := cs.Get(appIdx.Address(), false) + if err != nil { + return err + } + record.TotalBoxes = basics.AddSaturate(record.TotalBoxes, 1) + record.TotalBoxBytes = basics.AddSaturate(record.TotalBoxBytes, uint64(len(key))+size) + cs.Put(appIdx.Address(), record) + + value := string(make([]byte, size)) + return cs.kvPut(fullKey, value) +} + +func (cs *roundCowState) GetBox(appIdx basics.AppIndex, key string) (string, error) { + fullKey := makeBoxKey(appIdx, key) + value, ok, err := cs.kvGet(fullKey) + if err != nil { + return "", err + } + if !ok { + return "", fmt.Errorf("book %s does not exist for %d", key, appIdx) + } + return value, nil +} + +func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value string) error { + fullKey := makeBoxKey(appIdx, key) + old, ok, err := cs.kvGet(fullKey) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("book %s does not exist for %d", key, appIdx) + } + if len(old) != len(value) { + return fmt.Errorf("book %s is wrong size old:%d != new:%d", + key, len(old), len(value)) + } + return cs.kvPut(fullKey, value) +} + +func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string) error { + fullKey := makeBoxKey(appIdx, key) + + value, ok, err := cs.kvGet(fullKey) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("book %s does not exist for %d", key, appIdx) + } + + record, err := cs.Get(appIdx.Address(), false) if err != nil { return err } + record.TotalBoxes = basics.SubSaturate(record.TotalBoxes, 1) + record.TotalBoxBytes = basics.SubSaturate(record.TotalBoxBytes, uint64(len(key)+len(value))) + cs.Put(appIdx.Address(), record) + + return cs.kvDel(fullKey) +} + +func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) error { + txn := &ep.TxnGroup[gi] // move fee to pool - err = balances.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) + err := cs.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) if err != nil { return err } - err = apply.Rekey(balances, &txn.Txn) + err = apply.Rekey(cs, &txn.Txn) if err != nil { return err } @@ -241,29 +310,29 @@ func (al *logicLedger) Perform(gi int, ep *logic.EvalParams) error { // ahead of processing, we'd have to do ours *after* so that we'd // use the next id. So either way, this would seem backwards at // first glance. - al.cow.incTxnCount() + cs.incTxnCount() switch txn.Txn.Type { case protocol.PaymentTx: - err = apply.Payment(txn.Txn.PaymentTxnFields, txn.Txn.Header, balances, *ep.Specials, &txn.ApplyData) + err = apply.Payment(txn.Txn.PaymentTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) case protocol.KeyRegistrationTx: - err = apply.Keyreg(txn.Txn.KeyregTxnFields, txn.Txn.Header, balances, *ep.Specials, &txn.ApplyData, - al.Round()) + err = apply.Keyreg(txn.Txn.KeyregTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData, + cs.Round()) case protocol.AssetConfigTx: - err = apply.AssetConfig(txn.Txn.AssetConfigTxnFields, txn.Txn.Header, balances, *ep.Specials, &txn.ApplyData, - al.cow.txnCounter()) + err = apply.AssetConfig(txn.Txn.AssetConfigTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData, + cs.Counter()) case protocol.AssetTransferTx: - err = apply.AssetTransfer(txn.Txn.AssetTransferTxnFields, txn.Txn.Header, balances, *ep.Specials, &txn.ApplyData) + err = apply.AssetTransfer(txn.Txn.AssetTransferTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) case protocol.AssetFreezeTx: - err = apply.AssetFreeze(txn.Txn.AssetFreezeTxnFields, txn.Txn.Header, balances, *ep.Specials, &txn.ApplyData) + err = apply.AssetFreeze(txn.Txn.AssetFreezeTxnFields, txn.Txn.Header, cs, *ep.Specials, &txn.ApplyData) case protocol.ApplicationCallTx: - err = apply.ApplicationCall(txn.Txn.ApplicationCallTxnFields, txn.Txn.Header, balances, &txn.ApplyData, - gi, ep, al.cow.txnCounter()) + err = apply.ApplicationCall(txn.Txn.ApplicationCallTxnFields, txn.Txn.Header, cs, &txn.ApplyData, + gi, ep, cs.Counter()) default: err = fmt.Errorf("%s tx in AVM", txn.Txn.Type) @@ -279,9 +348,4 @@ func (al *logicLedger) Perform(gi int, ep *logic.EvalParams) error { // all changed accounts in modifiedAccounts(). return nil - -} - -func (al *logicLedger) Counter() uint64 { - return al.cow.txnCounter() } diff --git a/ledger/internal/applications_test.go b/ledger/internal/applications_test.go deleted file mode 100644 index 2b336970d8..0000000000 --- a/ledger/internal/applications_test.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package internal - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/ledger/ledgercore" - ledgertesting "github.com/algorand/go-algorand/ledger/testing" - "github.com/algorand/go-algorand/test/partitiontest" -) - -type creatableLocator struct { - cidx basics.CreatableIndex - ctype basics.CreatableType -} -type storeLocator struct { - addr basics.Address - aidx basics.AppIndex - global bool -} -type mockCowForLogicLedger struct { - rnd basics.Round - ts int64 - cr map[creatableLocator]basics.Address - brs map[basics.Address]basics.AccountData - stores map[storeLocator]basics.TealKeyValue - txc uint64 -} - -func (c *mockCowForLogicLedger) Get(addr basics.Address, withPendingRewards bool) (ledgercore.AccountData, error) { - acct, err := c.getAccount(addr, withPendingRewards) - return ledgercore.ToAccountData(acct), err -} - -func (c *mockCowForLogicLedger) getAccount(addr basics.Address, withPendingRewards bool) (basics.AccountData, error) { - br, ok := c.brs[addr] - if !ok { - return basics.AccountData{}, fmt.Errorf("addr %s not in mock cow", addr.String()) - } - return br, nil -} - -func (c *mockCowForLogicLedger) MinBalance(addr basics.Address, proto *config.ConsensusParams) (res basics.MicroAlgos, err error) { - br, ok := c.brs[addr] - if !ok { - return basics.MicroAlgos{}, fmt.Errorf("addr %s not in mock cow", addr.String()) - } - return br.MinBalance(proto), nil -} - -func (c *mockCowForLogicLedger) GetAppParams(addr basics.Address, aidx basics.AppIndex) (ret basics.AppParams, ok bool, err error) { - acct, err := c.getAccount(addr, false) - if err != nil { - return - } - ret, ok = acct.AppParams[aidx] - return -} -func (c *mockCowForLogicLedger) GetAssetParams(addr basics.Address, aidx basics.AssetIndex) (ret basics.AssetParams, ok bool, err error) { - acct, err := c.getAccount(addr, false) - if err != nil { - return - } - ret, ok = acct.AssetParams[aidx] - return -} -func (c *mockCowForLogicLedger) GetAssetHolding(addr basics.Address, aidx basics.AssetIndex) (ret basics.AssetHolding, ok bool, err error) { - acct, err := c.getAccount(addr, false) - if err != nil { - return - } - ret, ok = acct.Assets[aidx] - return -} - -func (c *mockCowForLogicLedger) GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { - addr, found := c.cr[creatableLocator{cidx, ctype}] - return addr, found, nil -} - -func (c *mockCowForLogicLedger) GetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) { - kv, ok := c.stores[storeLocator{addr, aidx, global}] - if !ok { - return basics.TealValue{}, false, fmt.Errorf("no store for (%s %d %v) in mock cow", addr.String(), aidx, global) - } - tv, found := kv[key] - return tv, found, nil -} - -func (c *mockCowForLogicLedger) BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) { - return transactions.EvalDelta{}, nil -} - -func (c *mockCowForLogicLedger) SetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, value basics.TealValue, accountIdx uint64) error { - kv, ok := c.stores[storeLocator{addr, aidx, global}] - if !ok { - return fmt.Errorf("no store for (%s %d %v) in mock cow", addr.String(), aidx, global) - } - kv[key] = value - c.stores[storeLocator{addr, aidx, global}] = kv - return nil -} - -func (c *mockCowForLogicLedger) DelKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) error { - kv, ok := c.stores[storeLocator{addr, aidx, global}] - if !ok { - return fmt.Errorf("no store for (%s %d %v) in mock cow", addr.String(), aidx, global) - } - delete(kv, key) - c.stores[storeLocator{addr, aidx, global}] = kv - return nil -} - -func (c *mockCowForLogicLedger) round() basics.Round { - return c.rnd -} - -func (c *mockCowForLogicLedger) prevTimestamp() int64 { - return c.ts -} - -func (c *mockCowForLogicLedger) allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) { - _, found := c.stores[storeLocator{addr, aidx, global}] - return found, nil -} - -func (c *mockCowForLogicLedger) incTxnCount() { - c.txc++ -} - -func (c *mockCowForLogicLedger) txnCounter() uint64 { - return c.txc -} - -func newCowMock(creatables []modsData) *mockCowForLogicLedger { - var m mockCowForLogicLedger - m.cr = make(map[creatableLocator]basics.Address, len(creatables)) - for _, e := range creatables { - m.cr[creatableLocator{e.cidx, e.ctype}] = e.addr - } - return &m -} - -func TestLogicLedgerMake(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - c := &mockCowForLogicLedger{} - l := newLogicLedger(c) - a.NotNil(l) - a.Equal(c, l.cow) -} - -func TestLogicLedgerBalances(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - c := newCowMock(nil) - l := newLogicLedger(c) - a.NotNil(l) - - addr1 := ledgertesting.RandomAddress() - ble := basics.MicroAlgos{Raw: 100} - c.brs = map[basics.Address]basics.AccountData{addr1: {MicroAlgos: ble}} - acct, err := l.AccountData(addr1) - a.NoError(err) - a.Equal(ble, acct.MicroAlgos) -} - -func TestLogicLedgerGetters(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - addr := ledgertesting.RandomAddress() - aidx := basics.AppIndex(1) - c := newCowMock([]modsData{{addr, basics.CreatableIndex(aidx), basics.AppCreatable}}) - l := newLogicLedger(c) - a.NotNil(l) - - round := basics.Round(1234) - c.rnd = round - ts := int64(11223344) - c.ts = ts - - addr1 := ledgertesting.RandomAddress() - c.stores = map[storeLocator]basics.TealKeyValue{{addr1, aidx, false}: {}} - a.Equal(round, l.Round()) - a.Equal(ts, l.LatestTimestamp()) - a.True(l.OptedIn(addr1, aidx)) - a.False(l.OptedIn(addr, aidx)) -} - -func TestLogicLedgerAsset(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - addr := ledgertesting.RandomAddress() - addr1 := ledgertesting.RandomAddress() - aidx := basics.AppIndex(1) - assetIdx := basics.AssetIndex(2) - c := newCowMock([]modsData{ - {addr, basics.CreatableIndex(aidx), basics.AppCreatable}, - {addr1, basics.CreatableIndex(assetIdx), basics.AssetCreatable}, - }) - l := newLogicLedger(c) - a.NotNil(l) - - _, _, err := l.AssetParams(basics.AssetIndex(aidx)) - a.Error(err) - a.Contains(err.Error(), fmt.Sprintf("asset %d does not exist", aidx)) - - c.brs = map[basics.Address]basics.AccountData{ - addr1: {AssetParams: map[basics.AssetIndex]basics.AssetParams{assetIdx: {Total: 1000}}}, - } - - ap, creator, err := l.AssetParams(assetIdx) - a.NoError(err) - a.Equal(addr1, creator) - a.Equal(uint64(1000), ap.Total) - - _, err = l.AssetHolding(addr1, assetIdx) - a.Error(err) - a.Contains(err.Error(), "has not opted in to asset") - - c.brs = map[basics.Address]basics.AccountData{ - addr1: { - AssetParams: map[basics.AssetIndex]basics.AssetParams{assetIdx: {Total: 1000}}, - Assets: map[basics.AssetIndex]basics.AssetHolding{assetIdx: {Amount: 99}}, - }, - } - - ah, err := l.AssetHolding(addr1, assetIdx) - a.NoError(err) - a.Equal(uint64(99), ah.Amount) -} - -func TestLogicLedgerGetKey(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - addr := ledgertesting.RandomAddress() - addr1 := ledgertesting.RandomAddress() - aidx := basics.AppIndex(1) - assetIdx := basics.AssetIndex(2) - c := newCowMock([]modsData{ - {addr, basics.CreatableIndex(aidx), basics.AppCreatable}, - {addr1, basics.CreatableIndex(assetIdx), basics.AssetCreatable}, - }) - l := newLogicLedger(c) - a.NotNil(l) - - _, ok, err := l.GetGlobal(basics.AppIndex(assetIdx), "gkey") - a.Error(err) - a.False(ok) - a.Contains(err.Error(), fmt.Sprintf("app %d does not exist", assetIdx)) - - tv := basics.TealValue{Type: basics.TealUintType, Uint: 1} - c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx + 1, true}: {"gkey": tv}} - val, ok, err := l.GetGlobal(aidx, "gkey") - a.Error(err) - a.False(ok) - a.Contains(err.Error(), fmt.Sprintf("no store for (%s %d %v) in mock cow", addr, aidx, true)) - - c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, true}: {"gkey": tv}} - val, ok, err = l.GetGlobal(aidx, "gkey") - a.NoError(err) - a.True(ok) - a.Equal(tv, val) - - // check local - c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, false}: {"lkey": tv}} - val, ok, err = l.GetLocal(addr, aidx, "lkey", 0) - a.NoError(err) - a.True(ok) - a.Equal(tv, val) -} - -func TestLogicLedgerSetKey(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - addr := ledgertesting.RandomAddress() - aidx := basics.AppIndex(1) - c := newCowMock([]modsData{ - {addr, basics.CreatableIndex(aidx), basics.AppCreatable}, - }) - l := newLogicLedger(c) - a.NotNil(l) - - tv := basics.TealValue{Type: basics.TealUintType, Uint: 1} - err := l.SetGlobal(aidx, "gkey", tv) - a.Error(err) - a.Contains(err.Error(), fmt.Sprintf("no store for (%s %d %v) in mock cow", addr, aidx, true)) - - tv2 := basics.TealValue{Type: basics.TealUintType, Uint: 2} - c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, true}: {"gkey": tv}} - err = l.SetGlobal(aidx, "gkey", tv2) - a.NoError(err) - - // check local - c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, false}: {"lkey": tv}} - err = l.SetLocal(addr, aidx, "lkey", tv2, 0) - a.NoError(err) -} - -func TestLogicLedgerDelKey(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - addr := ledgertesting.RandomAddress() - aidx := basics.AppIndex(1) - c := newCowMock([]modsData{ - {addr, basics.CreatableIndex(aidx), basics.AppCreatable}, - }) - l := newLogicLedger(c) - a.NotNil(l) - - err := l.DelGlobal(aidx, "gkey") - a.Error(err) - a.Contains(err.Error(), fmt.Sprintf("no store for (%s %d %v) in mock cow", addr, aidx, true)) - - tv := basics.TealValue{Type: basics.TealUintType, Uint: 1} - c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, true}: {"gkey": tv}} - err = l.DelGlobal(aidx, "gkey") - a.NoError(err) - - addr1 := ledgertesting.RandomAddress() - c.stores = map[storeLocator]basics.TealKeyValue{{addr1, aidx, false}: {"lkey": tv}} - err = l.DelLocal(addr1, aidx, "lkey", 0) - a.NoError(err) -} diff --git a/ledger/internal/apptxn_test.go b/ledger/internal/apptxn_test.go index 4e2b64c1a3..732f3b5348 100644 --- a/ledger/internal/apptxn_test.go +++ b/ledger/internal/apptxn_test.go @@ -53,17 +53,16 @@ func main(source string) string { // TestPayAction ensures a pay in teal affects balances func TestPayAction(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() - genesisInitState, addrs, _ := ledgertesting.Genesis(10) - - l, err := ledger.OpenLedger(logging.TestingLog(t), "", true, genesisInitState, config.GetDefaultLocal()) - require.NoError(t, err) - defer l.Close() + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + // Inner txns start in v30 + testConsensusRange(t, 30, 0, func(t *testing.T, ver int) { + dl := NewDoubleLedger(t, genBalances, consensusByNumber[ver]) + defer dl.Close() - create := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + ai := dl.fundedApp(addrs[0], 200000, // account min balance, plus fees + main(` itxn_begin int pay itxn_field TypeEnum @@ -72,129 +71,111 @@ func TestPayAction(t *testing.T) { txn Accounts 1 itxn_field Receiver itxn_submit -`), - } + `)) - ai := basics.AppIndex(1) - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: ai.Address(), - Amount: 200000, // account min balance, plus fees - } + require.Equal(t, ai, basics.AppIndex(1)) - payout1 := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: ai, - Accounts: []basics.Address{addrs[1]}, // pay self - } - - eval := nextBlock(t, l) - txns(t, l, eval, &create, &fund, &payout1) - vb := endBlock(t, l, eval) - - // AD contains expected appIndex - require.Equal(t, ai, vb.Block().Payset[0].ApplyData.ApplicationID) + payout1 := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: ai, + Accounts: []basics.Address{addrs[1]}, // pay self + } - ad0 := micros(t, l, addrs[0]) - ad1 := micros(t, l, addrs[1]) - app := micros(t, l, ai.Address()) + dl.fullBlock(&payout1) - genAccounts := genesisInitState.Accounts - // create(1000) and fund(1000 + 200000) - require.Equal(t, uint64(202000), genAccounts[addrs[0]].MicroAlgos.Raw-ad0) - // paid 5000, but 1000 fee - require.Equal(t, uint64(4000), ad1-genAccounts[addrs[1]].MicroAlgos.Raw) - // app still has 194000 (paid out 5000, and paid fee to do it) - require.Equal(t, uint64(194000), app) + ad0 := micros(dl.t, dl.generator, addrs[0]) + ad1 := micros(dl.t, dl.generator, addrs[1]) + app := micros(dl.t, dl.generator, ai.Address()) - // Build up Residue in RewardsState so it's ready to pay - for i := 1; i < 10; i++ { - eval = nextBlock(t, l) - endBlock(t, l, eval) - } + genAccounts := genBalances.Balances + // create(1000) and fund(1000 + 200000) + require.Equal(t, uint64(202000), genAccounts[addrs[0]].MicroAlgos.Raw-ad0) + // paid 5000, but 1000 fee + require.Equal(t, uint64(4000), ad1-genAccounts[addrs[1]].MicroAlgos.Raw) + // app still has 194000 (paid out 5000, and paid fee to do it) + require.Equal(t, uint64(194000), app) - eval = nextBlock(t, l) - payout2 := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApplicationID: ai, - Accounts: []basics.Address{addrs[2]}, // pay other - } - txn(t, l, eval, &payout2) - // confirm that modifiedAccounts can see account in inner txn - vb = endBlock(t, l, eval) + // Build up Residue in RewardsState so it's ready to pay + for i := 1; i < 10; i++ { + dl.fullBlock() + } - deltas := vb.Delta() - require.Contains(t, deltas.Accts.ModifiedAccounts(), addrs[2]) - - payInBlock := vb.Block().Payset[0] - rewards := payInBlock.ApplyData.SenderRewards.Raw - require.Greater(t, rewards, uint64(2000)) // some biggish number - inners := payInBlock.ApplyData.EvalDelta.InnerTxns - require.Len(t, inners, 1) - - // addr[2] is going to get the same rewards as addr[1], who - // originally sent the top-level txn. Both had their algo balance - // touched and has very nearly the same balance. - require.Equal(t, rewards, inners[0].ReceiverRewards.Raw) - // app gets none, because it has less than 1A - require.Equal(t, uint64(0), inners[0].SenderRewards.Raw) - - ad1 = micros(t, l, addrs[1]) - ad2 := micros(t, l, addrs[2]) - app = micros(t, l, ai.Address()) - - // paid 5000, in first payout (only), but paid 1000 fee in each payout txn - require.Equal(t, rewards+3000, ad1-genAccounts[addrs[1]].MicroAlgos.Raw) - // app still has 188000 (paid out 10000, and paid 2k fees to do it) - // no rewards because owns less than an algo - require.Equal(t, uint64(200000)-10000-2000, app) - - // paid 5000 by payout2, never paid any fees, got same rewards - require.Equal(t, rewards+uint64(5000), ad2-genAccounts[addrs[2]].MicroAlgos.Raw) - - // Now fund the app account much more, so we can confirm it gets rewards. - tenkalgos := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: ai.Address(), - Amount: 10 * 1000 * 1000000, // account min balance, plus fees - } - eval = nextBlock(t, l) - txn(t, l, eval, &tenkalgos) - endBlock(t, l, eval) - beforepay := micros(t, l, ai.Address()) + payout2 := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: ai, + Accounts: []basics.Address{addrs[2]}, // pay other + } + vb := dl.fullBlock(&payout2) + // confirm that modifiedAccounts can see account in inner txn + + deltas := vb.Delta() + require.Contains(t, deltas.Accts.ModifiedAccounts(), addrs[2]) + + payInBlock := vb.Block().Payset[0] + rewards := payInBlock.ApplyData.SenderRewards.Raw + require.Greater(t, rewards, uint64(2000)) // some biggish number + inners := payInBlock.ApplyData.EvalDelta.InnerTxns + require.Len(t, inners, 1) + + // addr[2] is going to get the same rewards as addr[1], who + // originally sent the top-level txn. Both had their algo balance + // touched and has very nearly the same balance. + require.Equal(t, rewards, inners[0].ReceiverRewards.Raw) + // app gets none, because it has less than 1A + require.Equal(t, uint64(0), inners[0].SenderRewards.Raw) + + ad1 = micros(dl.t, dl.validator, addrs[1]) + ad2 := micros(dl.t, dl.validator, addrs[2]) + app = micros(dl.t, dl.validator, ai.Address()) + + // paid 5000, in first payout (only), but paid 1000 fee in each payout txn + require.Equal(t, rewards+3000, ad1-genAccounts[addrs[1]].MicroAlgos.Raw) + // app still has 188000 (paid out 10000, and paid 2k fees to do it) + // no rewards because owns less than an algo + require.Equal(t, uint64(200000)-10000-2000, app) + + // paid 5000 by payout2, never paid any fees, got same rewards + require.Equal(t, rewards+uint64(5000), ad2-genAccounts[addrs[2]].MicroAlgos.Raw) + + // Now fund the app account much more, so we can confirm it gets rewards. + tenkalgos := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: ai.Address(), + Amount: 10 * 1000 * 1000000, // account min balance, plus fees + } + dl.fullBlock(&tenkalgos) + beforepay := micros(dl.t, dl.validator, ai.Address()) - // Build up Residue in RewardsState so it's ready to pay again - for i := 1; i < 10; i++ { - eval = nextBlock(t, l) - endBlock(t, l, eval) - } - eval = nextBlock(t, l) - txn(t, l, eval, payout2.Noted("2")) - vb = endBlock(t, l, eval) + // Build up Residue in RewardsState so it's ready to pay again + for i := 1; i < 10; i++ { + dl.fullBlock() + } + vb = dl.fullBlock(payout2.Noted("2")) - afterpay := micros(t, l, ai.Address()) + afterpay := micros(dl.t, dl.validator, ai.Address()) - payInBlock = vb.Block().Payset[0] - inners = payInBlock.ApplyData.EvalDelta.InnerTxns - require.Len(t, inners, 1) + payInBlock = vb.Block().Payset[0] + inners = payInBlock.ApplyData.EvalDelta.InnerTxns + require.Len(t, inners, 1) - appreward := inners[0].SenderRewards.Raw - require.Greater(t, appreward, uint64(1000)) + appreward := inners[0].SenderRewards.Raw + require.Greater(t, appreward, uint64(1000)) - require.Equal(t, beforepay+appreward-5000-1000, afterpay) + require.Equal(t, beforepay+appreward-5000-1000, afterpay) + }) } // TestAxferAction ensures axfers in teal have the intended effects func TestAxferAction(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genesisInitState, addrs, _ := ledgertesting.Genesis(10) - l, err := ledger.OpenLedger(logging.TestingLog(t), "", true, genesisInitState, config.GetDefaultLocal()) + l, err := ledger.OpenLedger(logging.TestingLog(t), t.Name(), true, genesisInitState, config.GetDefaultLocal()) require.NoError(t, err) defer l.Close() @@ -413,6 +394,7 @@ func newTestLedgerFull(t testing.TB, balances bookkeeping.GenesisBalances, cv pr // TestClawbackAction ensures an app address can act as clawback address. func TestClawbackAction(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -498,6 +480,7 @@ func TestClawbackAction(t *testing.T) { // TestRekeyAction ensures an app can transact for a rekeyed account func TestRekeyAction(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -602,6 +585,7 @@ skipclose: // properly removes the app as an authorizer for the account func TestRekeyActionCloseAccount(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -677,6 +661,7 @@ func TestRekeyActionCloseAccount(t *testing.T) { // TestDuplicatePayAction shows two pays with same parameters can be done as inner tarnsactions func TestDuplicatePayAction(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -752,6 +737,7 @@ func TestDuplicatePayAction(t *testing.T) { // TestInnerTxCount ensures that inner transactions increment the TxnCounter func TestInnerTxnCount(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -800,6 +786,7 @@ func TestInnerTxnCount(t *testing.T) { // TestAcfgAction ensures assets can be created and configured in teal func TestAcfgAction(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -976,6 +963,7 @@ submit: itxn_submit // we can know, so it helps exercise txncounter changes. func TestAsaDuringInit(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -1029,6 +1017,7 @@ func TestAsaDuringInit(t *testing.T) { func TestRekey(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -1080,6 +1069,7 @@ func TestRekey(t *testing.T) { func TestNote(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -1128,6 +1118,7 @@ func TestNote(t *testing.T) { func TestKeyreg(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -1228,6 +1219,7 @@ nonpart: func TestInnerAppCall(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -1295,6 +1287,7 @@ func TestInnerAppCall(t *testing.T) { // the changes expected when invoked. func TestInnerAppManipulate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -2420,6 +2413,7 @@ func BenchmarkMaximumCallStackDepth(b *testing.B) { // TestInnerClearState ensures inner ClearState performs close out properly, even if rejects. func TestInnerClearState(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -2508,6 +2502,7 @@ itxn_submit // allowed to use more than 700 (MaxAppProgramCost) func TestInnerClearStateBadCallee(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -2609,6 +2604,7 @@ skip: // be called with less than 700 (MaxAppProgramCost)) OpcodeBudget. func TestInnerClearStateBadCaller(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -2730,6 +2726,7 @@ itxn_submit // v30, but not in vFuture. (Test should add v31 after it exists.) func TestClearStateInnerPay(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() tests := []struct { consensus protocol.ConsensusVersion @@ -2842,6 +2839,7 @@ itxn_submit // calls when using inners. func TestGlobalChangesAcrossApps(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) @@ -2950,6 +2948,7 @@ check: // calls when using inners. func TestLocalChangesAcrossApps(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() l := newTestLedger(t, genBalances) diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go new file mode 100644 index 0000000000..aef62d4d75 --- /dev/null +++ b/ledger/internal/boxtxn_test.go @@ -0,0 +1,187 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package internal_test + +import ( + "bytes" + "fmt" + "testing" + "time" + + "github.com/algorand/go-algorand/data/txntest" + ledgertesting "github.com/algorand/go-algorand/ledger/testing" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/test/partitiontest" +) + +var appSource = main(` + txn ApplicationArgs 0 + byte "create" // create box named arg[1] + == + bz del + int 24 + txn NumAppArgs + int 2 + == + bnz default + pop // get rid of 24 + txn ApplicationArgs 2 + btoi + default: + txn ApplicationArgs 1 + box_create + b end + del: // delete box arg[1] + txn ApplicationArgs 0 + byte "delete" + == + bz set + txn ApplicationArgs 1 + box_del + b end + set: // put arg[1] at start of box arg[0] + txn ApplicationArgs 0 + byte "set" + == + bz test + txn ApplicationArgs 1 + int 0 + txn ApplicationArgs 2 + box_replace + b end + test: // fail unless arg[2] is the prefix of box arg[1] + txn ApplicationArgs 0 + byte "check" + == + bz bad + txn ApplicationArgs 1 + int 0 + txn ApplicationArgs 2 + len + box_extract + txn ApplicationArgs 2 + == + assert + b end + bad: + err +`) + +// TestBoxCreate tests MBR changes around allocation, deallocation +func TestBoxCreate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + // boxes begin in 33 + testConsensusRange(t, 33, 0, func(t *testing.T, ver int) { + dl := NewDoubleLedger(t, genBalances, consensusByNumber[ver]) + defer dl.Close() + + // increment for a size 24 box with 4 letter name + const mbr = 2500 + 28*400 + + appIndex := dl.fundedApp(addrs[0], 100_000+3*mbr, appSource) + + call := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: appIndex, + } + + adam := call.Args("create", "adam") + dl.txn(adam) + dl.txn(adam.Args("check", "adam", "\x00\x00")) + dl.txgroup("exists", adam.Noted("one"), adam.Noted("two")) + bobo := call.Args("create", "bobo") + dl.txn(bobo) + dl.txgroup("exists", bobo.Noted("one"), bobo.Noted("two")) + + dl.beginBlock() + chaz := call.Args("create", "chaz") + dl.txn(chaz) + dl.txn(chaz.Noted("again"), "exists") + dl.endBlock() + + // new block + dl.txn(chaz.Noted("again"), "exists") + dl.txn(call.Args("create", "dogg"), "below min") + dl.txn(call.Args("delete", "chaz")) + dl.txn(call.Args("delete", "chaz"), "does not exist") + dl.txn(call.Args("create", "dogg")) + dl.txn(call.Args("delete", "bobo")) + empty := call.Args("create", "") + dl.txn(empty) + + }) + +} + +// TestBoxRW tests reading writing boxes in consecutive transactions +func TestBoxRW(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + // boxes begin in 33 + testConsensusRange(t, 33, 0, func(t *testing.T, ver int) { + dl := NewDoubleLedger(t, genBalances, consensusByNumber[ver]) + defer dl.Close() + + var bufNewLogger bytes.Buffer + log := logging.NewLogger() + log.SetOutput(&bufNewLogger) + + appIndex := dl.fundedApp(addrs[0], 1_000_000, appSource) + call := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: appIndex, + } + + dl.txn(call.Args("create", "x", "\x10")) // 16 + dl.txn(call.Args("set", "x", "ABCDEFGHIJ")) // 10 long + dl.txn(call.Args("check", "x", "ABCDE")) + dl.txn(call.Args("check", "x", "ABCDEFGHIJ")) + dl.txn(call.Args("check", "x", "ABCDEFGHIJ\x00")) + + dl.txn(call.Args("delete", "x")) + dl.txn(call.Args("check", "x", "ABC"), "x does not exist") + dl.txn(call.Args("create", "x", "\x08")) + dl.txn(call.Args("check", "x", "\x00")) // it was cleared + dl.txn(call.Args("set", "x", "ABCDEFGHIJ"), "replace range") + dl.txn(call.Args("check", "x", "\x00")) // still clear + dl.txn(call.Args("set", "x", "ABCDEFGH")) + dl.txn(call.Args("check", "x", "ABCDEFGH\x00"), "extract range") + dl.txn(call.Args("check", "x", "ABCDEFGH")) + dl.txn(call.Args("set", "x", "ABCDEFGHI"), "replace range") + + // Advance more than 320 rounds, ensure box is still there + for i := 0; i < 330; i++ { + dl.fullBlock() + } + time.Sleep(5 * time.Second) // balancesFlushInterval, so commit happens + dl.fullBlock(call.Args("check", "x", "ABCDEFGH")) + time.Sleep(100 * time.Millisecond) // give commit time to run, and prune au caches + dl.fullBlock(call.Args("check", "x", "ABCDEFGH")) + + fmt.Printf("LOG %s\n", bufNewLogger.String()) + + dl.txn(call.Args("create", "yy")) + dl.txn(call.Args("create", "zzz")) + }) +} diff --git a/ledger/internal/cow.go b/ledger/internal/cow.go index f371bade7f..075f948f47 100644 --- a/ledger/internal/cow.go +++ b/ledger/internal/cow.go @@ -51,7 +51,7 @@ type roundCowParent interface { lookupAssetHolding(addr basics.Address, aidx basics.AssetIndex, cacheOnly bool) (ledgercore.AssetHoldingDelta, bool, error) checkDup(basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error - txnCounter() uint64 + Counter() uint64 getCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) compactCertNext() basics.Round blockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) @@ -61,6 +61,8 @@ type roundCowParent interface { getStorageLimits(addr basics.Address, aidx basics.AppIndex, global bool) (basics.StateSchema, error) allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) getKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) + + kvGet(key string) (string, bool, error) } type roundCowState struct { @@ -75,7 +77,7 @@ type roundCowState struct { // storage deltas populated as side effects of AppCall transaction // 1. Opt-in/Close actions (see Allocate/Deallocate) - // 2. Stateful TEAL evaluation (see SetKey/DelKey) + // 2. Stateful TEAL evaluation (see setKey/delKey) // must be incorporated into mods.accts before passing deltas forward sdeltas map[basics.Address]map[storagePtr]*storageDelta @@ -132,11 +134,11 @@ func (cb *roundCowState) rewardsLevel() uint64 { return cb.mods.Hdr.RewardsLevel } -func (cb *roundCowState) round() basics.Round { +func (cb *roundCowState) Round() basics.Round { return cb.mods.Hdr.Round } -func (cb *roundCowState) prevTimestamp() int64 { +func (cb *roundCowState) PrevTimestamp() int64 { return cb.mods.PrevTimestamp } @@ -212,8 +214,8 @@ func (cb *roundCowState) checkDup(firstValid, lastValid basics.Round, txid trans return cb.lookupParent.checkDup(firstValid, lastValid, txid, txl) } -func (cb *roundCowState) txnCounter() uint64 { - return cb.lookupParent.txnCounter() + cb.txnCount +func (cb *roundCowState) Counter() uint64 { + return cb.lookupParent.Counter() + cb.txnCount } func (cb *roundCowState) compactCertNext() basics.Round { @@ -288,6 +290,10 @@ func (cb *roundCowState) commitToParent() { } } cb.commitParent.mods.CompactCertNext = cb.mods.CompactCertNext + + for key, value := range cb.mods.KvMods { + cb.commitParent.mods.KvMods[key] = value + } } func (cb *roundCowState) modifiedAccounts() []basics.Address { diff --git a/ledger/internal/cow_test.go b/ledger/internal/cow_test.go index 9c5ffcafbf..d903ace37e 100644 --- a/ledger/internal/cow_test.go +++ b/ledger/internal/cow_test.go @@ -85,7 +85,11 @@ func (ml *mockLedger) getKey(addr basics.Address, aidx basics.AppIndex, global b return basics.TealValue{}, false, nil } -func (ml *mockLedger) txnCounter() uint64 { +func (ml *mockLedger) kvGet(key string) (string, bool, error) { + return "", false, nil +} + +func (ml *mockLedger) Counter() uint64 { return 0 } diff --git a/ledger/internal/double_test.go b/ledger/internal/double_test.go index 84f1f092b7..408f5370f5 100644 --- a/ledger/internal/double_test.go +++ b/ledger/internal/double_test.go @@ -19,6 +19,7 @@ package internal_test import ( "testing" + "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/txntest" @@ -110,11 +111,33 @@ func (dl *DoubleLedger) fullBlock(txs ...*txntest.Txn) *ledgercore.ValidatedBloc func (dl *DoubleLedger) endBlock() *ledgercore.ValidatedBlock { vb := endBlock(dl.t, dl.generator, dl.eval) - checkBlock(dl.t, dl.validator, vb) + if dl.validator != nil { // Allows setting to nil while debugging, to simplify + checkBlock(dl.t, dl.validator, vb) + } dl.eval = nil // Ensure it's not used again return vb } +func (dl *DoubleLedger) fundedApp(sender basics.Address, amount uint64, source string) basics.AppIndex { + createapp := txntest.Txn{ + Type: "appl", + Sender: sender, + ApprovalProgram: source, + } + vb := dl.fullBlock(&createapp) + appIndex := vb.Block().Payset[0].ApplyData.ApplicationID + + fund := txntest.Txn{ + Type: "pay", + Sender: sender, + Receiver: appIndex.Address(), + Amount: amount, + } + + dl.txn(&fund) + return appIndex +} + func checkBlock(t *testing.T, checkLedger *ledger.Ledger, vb *ledgercore.ValidatedBlock) { bl := vb.Block() msg := bl.MarshalMsg(nil) diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go index 20c6f5d5ab..c848daeca3 100644 --- a/ledger/internal/eval.go +++ b/ledger/internal/eval.go @@ -45,6 +45,7 @@ type LedgerForCowBase interface { LookupWithoutRewards(basics.Round, basics.Address) (ledgercore.AccountData, basics.Round, error) LookupAsset(basics.Round, basics.Address, basics.AssetIndex) (ledgercore.AssetResource, error) LookupApplication(basics.Round, basics.Address, basics.AppIndex) (ledgercore.AppResource, error) + LookupKv(basics.Round, string) (*string, error) GetCreatorForRound(basics.Round, basics.CreatableIndex, basics.CreatableType) (basics.Address, bool, error) } @@ -132,6 +133,9 @@ type roundCowBase struct { // Similar cache for asset/app creators. creators map[creatable]foundAddress + + // Similar cache for kv entries. A nil entry means ledger has no such pair + kvStore map[string]*string } func makeRoundCowBase(l LedgerForCowBase, rnd basics.Round, txnCount uint64, compactCertNextRnd basics.Round, proto config.ConsensusParams) *roundCowBase { @@ -147,6 +151,7 @@ func makeRoundCowBase(l LedgerForCowBase, rnd basics.Round, txnCount uint64, com appLocalStates: make(map[ledgercore.AccountApp]cachedAppLocalState), assets: make(map[ledgercore.AccountAsset]cachedAssetHolding), creators: make(map[creatable]foundAddress), + kvStore: make(map[string]*string), } } @@ -320,7 +325,7 @@ func (x *roundCowBase) checkDup(firstValid, lastValid basics.Round, txid transac return x.l.CheckDup(x.proto, x.rnd+1, firstValid, lastValid, txid, txl) } -func (x *roundCowBase) txnCounter() uint64 { +func (x *roundCowBase) Counter() uint64 { return x.txnCount } @@ -1082,7 +1087,7 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * } // Apply the transaction, updating the cow balances - applyData, err := eval.applyTransaction(txn.Txn, cow, evalParams, gi, cow.txnCounter()) + applyData, err := eval.applyTransaction(txn.Txn, cow, evalParams, gi, cow.Counter()) if err != nil { return fmt.Errorf("transaction %v: %v", txid, err) } @@ -1145,7 +1150,7 @@ func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, balanc err = apply.Payment(tx.PaymentTxnFields, tx.Header, balances, eval.specials, &ad) case protocol.KeyRegistrationTx: - err = apply.Keyreg(tx.KeyregTxnFields, tx.Header, balances, eval.specials, &ad, balances.round()) + err = apply.Keyreg(tx.KeyregTxnFields, tx.Header, balances, eval.specials, &ad, balances.Round()) case protocol.AssetConfigTx: err = apply.AssetConfig(tx.AssetConfigTxnFields, tx.Header, balances, eval.specials, &ad, ctr) @@ -1223,7 +1228,7 @@ func (eval *BlockEvaluator) compactCertVotersAndTotal() (root crypto.GenericDige // TestingTxnCounter - the method returns the current evaluator transaction counter. The method is used for testing purposes only. func (eval *BlockEvaluator) TestingTxnCounter() uint64 { - return eval.state.txnCounter() + return eval.state.Counter() } // Call "endOfBlock" after all the block's rewards and transactions are processed. @@ -1236,7 +1241,7 @@ func (eval *BlockEvaluator) endOfBlock() error { } if eval.proto.TxnCounter { - eval.block.TxnCounter = eval.state.txnCounter() + eval.block.TxnCounter = eval.state.Counter() } else { eval.block.TxnCounter = 0 } @@ -1281,7 +1286,7 @@ func (eval *BlockEvaluator) endOfBlock() error { var expectedTxnCount uint64 if eval.proto.TxnCounter { - expectedTxnCount = eval.state.txnCounter() + expectedTxnCount = eval.state.Counter() } if eval.block.TxnCounter != expectedTxnCount { return fmt.Errorf("txn count wrong: %d != %d", eval.block.TxnCounter, expectedTxnCount) diff --git a/ledger/internal/eval_blackbox_test.go b/ledger/internal/eval_blackbox_test.go index 79d22d1898..5880535d4a 100644 --- a/ledger/internal/eval_blackbox_test.go +++ b/ledger/internal/eval_blackbox_test.go @@ -914,7 +914,9 @@ func testConsensusRange(t *testing.T, start, stop int, test func(t *testing.T, v } else { version = fmt.Sprintf("v%d", i) } - t.Run(fmt.Sprintf("cv=%s", version), func(t *testing.T) { test(t, i) }) + t.Run(fmt.Sprintf("cv=%s", version), func(t *testing.T) { + test(t, i) + }) } } diff --git a/ledger/internal/eval_test.go b/ledger/internal/eval_test.go index b8bb438c2b..b9c5390eae 100644 --- a/ledger/internal/eval_test.go +++ b/ledger/internal/eval_test.go @@ -558,6 +558,10 @@ func (ledger *evalTestLedger) LookupAsset(rnd basics.Round, addr basics.Address, return res, nil } +func (ledger *evalTestLedger) LookupKv(rnd basics.Round, key string) (*string, error) { + panic("unimplemented") +} + // GenesisHash returns the genesis hash for this ledger. func (ledger *evalTestLedger) GenesisHash() crypto.Digest { return ledger.genesisHash @@ -736,6 +740,10 @@ func (l *testCowBaseLedger) LookupAsset(rnd basics.Round, addr basics.Address, a return ledgercore.AssetResource{}, errors.New("not implemented") } +func (l *testCowBaseLedger) LookupKv(rnd basics.Round, key string) (*string, error) { + return nil, errors.New("not implemented") +} + func (l *testCowBaseLedger) GetCreatorForRound(_ basics.Round, cindex basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { res := l.creators[0] l.creators = l.creators[1:] diff --git a/ledger/internal/prefetcher/prefetcher_alignment_test.go b/ledger/internal/prefetcher/prefetcher_alignment_test.go index ba14fbe03f..31e1147b68 100644 --- a/ledger/internal/prefetcher/prefetcher_alignment_test.go +++ b/ledger/internal/prefetcher/prefetcher_alignment_test.go @@ -140,6 +140,9 @@ func (l *prefetcherAlignmentTestLedger) LookupAsset(rnd basics.Round, addr basic return l.assets[addr][aidx], nil } +func (l *prefetcherAlignmentTestLedger) LookupKv(rnd basics.Round, key string) (*string, error) { + panic("not implemented") +} func (l *prefetcherAlignmentTestLedger) GetCreatorForRound(_ basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { l.mu.Lock() if l.requestedCreators == nil { diff --git a/ledger/ledger.go b/ledger/ledger.go index a48385ff0c..392d2af924 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -521,6 +521,14 @@ func (l *Ledger) lookupResource(rnd basics.Round, addr basics.Address, aidx basi return res, nil } +// LookupKv loads a KV pair from the accounts update +func (l *Ledger) LookupKv(rnd basics.Round, key string) (*string, error) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + + return l.accts.LookupKv(rnd, key) +} + // LookupAgreement returns account data used by agreement. func (l *Ledger) LookupAgreement(rnd basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { l.trackerMu.RLock() diff --git a/ledger/ledgercore/accountdata.go b/ledger/ledgercore/accountdata.go index 5f141cdbb6..5db9ac41f0 100644 --- a/ledger/ledgercore/accountdata.go +++ b/ledger/ledgercore/accountdata.go @@ -42,12 +42,14 @@ type AccountBaseData struct { RewardedMicroAlgos basics.MicroAlgos AuthAddr basics.Address - TotalAppSchema basics.StateSchema - TotalExtraAppPages uint32 - TotalAppParams uint64 - TotalAppLocalStates uint64 - TotalAssetParams uint64 - TotalAssets uint64 + TotalAppSchema basics.StateSchema // Totals across created globals, and opted in locals. + TotalExtraAppPages uint32 // Total number of extra pages across all created apps + TotalAppParams uint64 // Total number of apps this account has created + TotalAppLocalStates uint64 // Total number of apps this account is opted into. + TotalAssetParams uint64 // Total number of assets created by this account + TotalAssets uint64 // Total of asset creations and optins (i.e. number of holdings) + TotalBoxes uint64 // Total number of boxes associated to this account + TotalBoxBytes uint64 // Total bytes for this account's boxes. keys _and_ values count } // VotingData holds participation information @@ -136,6 +138,7 @@ func (u AccountData) MinBalance(proto *config.ConsensusParams) (res basics.Micro u.TotalAppSchema, uint64(u.TotalAppParams), uint64(u.TotalAppLocalStates), uint64(u.TotalExtraAppPages), + u.TotalBoxes, u.TotalBoxBytes, ) } diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index 7bea4e8330..ed7b606d15 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -76,6 +76,9 @@ type StateDelta struct { // modified new accounts Accts AccountDeltas + // modified kv pairs (nil == delete) + KvMods map[string]*string + // new Txids for the txtail and TxnCounter, mapped to txn.LastValid Txids map[transactions.Txid]basics.Round @@ -176,6 +179,7 @@ type AccountDeltas struct { func MakeStateDelta(hdr *bookkeeping.BlockHeader, prevTimestamp int64, hint int, compactCertNext basics.Round) StateDelta { return StateDelta{ Accts: MakeAccountDeltas(hint), + KvMods: make(map[string]*string), Txids: make(map[transactions.Txid]basics.Round, hint), Txleases: make(map[Txlease]basics.Round, hint), // asset or application creation are considered as rare events so do not pre-allocate space for them diff --git a/ledger/tracker.go b/ledger/tracker.go index ca3bf894d2..172755c555 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -208,7 +208,7 @@ type deferredCommitRange struct { enableGeneratingCatchpointFiles bool } -// deferredCommitContext is used in order to syncornize the persistence of a given deferredCommitRange. +// deferredCommitContext is used in order to synchronize the persistence of a given deferredCommitRange. // prepareCommit, commitRound and postCommit are all using it to exchange data. type deferredCommitContext struct { deferredCommitRange @@ -222,6 +222,7 @@ type deferredCommitContext struct { roundTotals ledgercore.AccountTotals compactAccountDeltas compactAccountDeltas compactResourcesDeltas compactResourcesDeltas + compactKvDeltas map[string]modifiedValue compactCreatableDeltas map[basics.CreatableIndex]ledgercore.ModifiedCreatable updatedPersistedAccounts []persistedAccountData From 455447f751886e3d7dee5c142be4e7ee8ae3b537 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 18 May 2022 22:42:00 -0400 Subject: [PATCH 02/30] Fixup tests that require complete evalparams --- .../transactions/logic/backwardCompat_test.go | 2 +- data/transactions/logic/box_test.go | 65 ++--- data/transactions/logic/debugger_test.go | 4 +- data/transactions/logic/eval.go | 15 +- data/transactions/logic/evalAppTxn_test.go | 11 +- data/transactions/logic/evalCrypto_test.go | 20 +- data/transactions/logic/evalStateful_test.go | 137 +++++----- data/transactions/logic/eval_test.go | 234 ++++++++++-------- data/transactions/logic/export_test.go | 1 + data/transactions/logic/fields_test.go | 4 +- 10 files changed, 256 insertions(+), 237 deletions(-) diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index e2503e8878..936223fdda 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -480,7 +480,7 @@ func TestBackwardCompatAssemble(t *testing.T) { for v := uint64(2); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { - testLogic(t, source, v, defaultEvalParams(nil)) + testLogic(t, source, v, defaultEvalParams()) }) } } diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 011337df7d..df8db69db4 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -34,15 +34,20 @@ func TestBoxNewDel(t *testing.T) { ep, txn, ledger := logic.MakeSampleEnv() ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - logic.TestApp(t, "int 24; byte 0x11; box_create; int 1", ep) - logic.TestApp(t, "int 24; byte 0x12; box_create; int 24; byte 0x12; box_create; int 1", ep, - "already exists") - logic.TestApp(t, "int 24; byte 0x13; box_create; int 24; byte 0x14; box_create; int 1", ep) - - logic.TestApp(t, "int 24; byte 0x15; box_create; byte 0x15; box_del; int 1", ep) - logic.TestApp(t, "int 24; byte 0x17; box_del; int 1", ep, "no such box") - logic.TestApp(t, "int 24; byte 0x18; box_create; byte 0x18; box_del; byte 0x18; box_del; int 1", ep, - "no such box") + logic.TestApp(t, `int 24; byte "self"; box_create; int 1`, ep) + ledger.DelBox(888, "self") + logic.TestApp(t, `int 24; byte "self"; box_create; int 1`, ep) + ledger.DelBox(888, "self") + logic.TestApp(t, `int 24; byte "self"; box_create; int 24; byte "self"; box_create; int 1`, ep, + `already exists`) + ledger.DelBox(888, "self") + logic.TestApp(t, `int 24; byte "self"; box_create; int 24; byte "other"; box_create; int 1`, ep) + ledger.DelBox(888, "self") + + logic.TestApp(t, `int 24; byte "self"; box_create; byte "self"; box_del; int 1`, ep) + logic.TestApp(t, `int 24; byte "self"; box_del; int 1`, ep, `no such box`) + logic.TestApp(t, `int 24; byte "self"; box_create; byte "self"; box_del; byte "self"; box_del; int 1`, ep, + `no such box`) } func TestBoxReadWrite(t *testing.T) { @@ -54,32 +59,28 @@ func TestBoxReadWrite(t *testing.T) { ledger.NewApp(txn.Sender, 888, basics.AppParams{}) // extract some bytes until past the end, confirm the begin as zeros, and // when it fails. - logic.TestApp(t, `int 4; byte 0x11; box_create; - byte 0x11; int 1; int 2; box_extract; - byte 0x0000; ==`, ep) - logic.TestApp(t, `int 4; byte 0x12; box_create; - byte 0x12; int 1; int 3; box_extract; - byte 0x000000; ==`, ep) - logic.TestApp(t, `int 4; byte 0x13; box_create; - byte 0x13; int 1; int 4; box_extract; + logic.TestApp(t, `int 4; byte "self"; box_create; + byte "self"; int 1; int 2; box_extract; + byte 0x0000; ==; assert; + byte "self"; int 1; int 3; box_extract; + byte 0x000000; ==; assert; + byte "self"; int 0; int 4; box_extract; + byte 0x00000000; ==; assert; + int 1`, ep) + + logic.TestApp(t, `byte "self"; int 1; int 4; box_extract; byte 0x00000000; ==`, ep, "extract range") - logic.TestApp(t, `int 4; byte 0x14; box_create; - byte 0x14; int 0; int 4; box_extract; - byte 0x00000000; ==`, ep) // Replace some bytes until past the end, confirm when it fails. - logic.TestApp(t, `int 4; byte 0x15; box_create; - byte 0x15; int 1; byte 0x3031; box_replace; - byte 0x15; int 0; int 4; box_extract; - byte 0x00303100; ==`, ep) - logic.TestApp(t, `int 4; byte 0x16; box_create; - byte 0x16; int 1; byte 0x303132; box_replace; - byte 0x16; int 0; int 4; box_extract; - byte 0x00303132; ==`, ep) - logic.TestApp(t, `int 4; byte 0x17; box_create; - byte 0x17; int 1; byte 0x30313233; box_replace; - byte 0x17; int 0; int 4; box_extract; - byte 0x0030313233; ==`, ep, "replace range") + logic.TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace; + byte "self"; int 0; int 4; box_extract; + byte 0x00303100; ==`, ep) + logic.TestApp(t, `byte "self"; int 1; byte 0x303132; box_replace; + byte "self"; int 0; int 4; box_extract; + byte 0x00303132; ==`, ep) + logic.TestApp(t, `byte "self"; int 1; byte 0x30313233; box_replace; + byte "self"; int 0; int 4; box_extract; + byte 0x0030313233; ==`, ep, "replace range") } func TestBoxAcrossTxns(t *testing.T) { diff --git a/data/transactions/logic/debugger_test.go b/data/transactions/logic/debugger_test.go index 060b953fcd..5ee41c6236 100644 --- a/data/transactions/logic/debugger_test.go +++ b/data/transactions/logic/debugger_test.go @@ -113,7 +113,7 @@ func TestDebuggerHook(t *testing.T) { partitiontest.PartitionTest(t) testDbg := testDbgHook{} - ep := defaultEvalParams(nil) + ep := defaultEvalParams() ep.Debugger = &testDbg testLogic(t, testProgram, AssemblerMaxVersion, ep) @@ -223,7 +223,7 @@ func TestCallStackUpdate(t *testing.T) { } testDbg := testDbgHook{} - ep := defaultEvalParams(nil) + ep := defaultEvalParams() ep.Debugger = &testDbg testLogic(t, testCallStackProgram, AssemblerMaxVersion, ep) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index d02df5c587..7af898b9ba 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -304,7 +304,7 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens for _, tx := range txgroup { if tx.Txn.Type == protocol.ApplicationCallTx { apps++ - if allBoxes == nil { + if allBoxes == nil && len(tx.Txn.Boxes) > 0 { allBoxes = make(map[basics.AppIndex][]string) } for _, br := range tx.Txn.Boxes { @@ -316,10 +316,9 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens } app = tx.Txn.ApplicationID } else { - // Bounds check will already have been done by WellFormed - if br.Index > uint64(len(tx.Txn.ForeignApps)) { - return nil - } + // Bounds check will already have been done by + // WellFormed. For testing purposes, it's better to panic + // now than after returning a nil. app = tx.Txn.ForeignApps[br.Index-1] // shift for the 0=this convention } appBoxes := allBoxes[app] @@ -371,8 +370,7 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens // feeCredit returns the extra fee supplied in this top-level txgroup compared // to required minfee. It can make assumptions about overflow because the group -// is known OK according to TxnGroupBatchVerify. (In essence the group is -// "WellFormed") +// is known OK according to TxnGroupBatchVerify. (The group is "WellFormed") func feeCredit(txgroup []transactions.SignedTxnWithAD, minFee uint64) uint64 { minFeeCount := uint64(0) feesPaid := uint64(0) @@ -384,8 +382,7 @@ func feeCredit(txgroup []transactions.SignedTxnWithAD, minFee uint64) uint64 { } // Overflow is impossible, because TxnGroupBatchVerify checked. feeNeeded := minFee * minFeeCount - - return feesPaid - feeNeeded + return basics.SubSaturate(feesPaid, feeNeeded) } // NewInnerEvalParams creates an EvalParams to be used while evaluating an inner group txgroup diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 1315399d1a..45e2f6652e 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -782,6 +782,8 @@ func TestInnerGroup(t *testing.T) { partitiontest.PartitionTest(t) ep, tx, ledger := MakeSampleEnv() + ep.FeeCredit = nil // default sample env starts at 401 + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) // Need both fees and both payments ledger.NewAccount(appAddr(888), 999+2*MakeTestProto().MinTxnFee) @@ -802,6 +804,8 @@ func TestInnerFeePooling(t *testing.T) { partitiontest.PartitionTest(t) ep, tx, ledger := MakeSampleEnv() + ep.FeeCredit = nil // default sample env starts at 401 + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(appAddr(888), 50_000) pay := ` @@ -1708,7 +1712,9 @@ int 1 // Whenever MakeSampleEnv() is changed to create a different // transaction, we must reverse those changes here, so that the // historic test is correct. + parentTx.Type = protocol.PaymentTx parentTx.Boxes = nil + ep.FeeCredit = nil // else inner's fee will change parentTx.ApplicationID = parentAppID parentTx.ForeignApps = []basics.AppIndex{ @@ -2026,7 +2032,7 @@ int 1 for _, unified := range []bool{true, false} { t.Run(fmt.Sprintf("unified=%t", unified), func(t *testing.T) { - t.Parallel() + // t.Parallel() NO! unified variable is actually shared ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified @@ -2034,7 +2040,9 @@ int 1 // Whenever MakeSampleEnv() is changed to create a different // transaction, we must reverse those changes here, so that the // historic test is correct. + parentTx.Type = protocol.PaymentTx parentTx.Boxes = nil + ep.FeeCredit = nil // else inner's fee will change parentTx.ApplicationID = parentAppID parentTx.ForeignApps = []basics.AppIndex{ @@ -2887,6 +2895,7 @@ done: func TestInfiniteRecursion(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, tx, ledger := MakeSampleEnv() source := ` diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go index 2adb2d9669..a66420958d 100644 --- a/data/transactions/logic/evalCrypto_test.go +++ b/data/transactions/logic/evalCrypto_test.go @@ -119,18 +119,18 @@ ed25519verify`, pkStr), v) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program txn.Lsig.Args = [][]byte{data[:], sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(&txn)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn)) // short sig will fail txn.Lsig.Args[1] = sig[1:] - testLogicBytes(t, ops.Program, defaultEvalParams(&txn), "invalid signature") + testLogicBytes(t, ops.Program, defaultEvalParams(txn), "invalid signature") // flip a bit and it should not pass msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" data1, err := hex.DecodeString(msg1) require.NoError(t, err) txn.Lsig.Args = [][]byte{data1, sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(&txn), "REJECT") + testLogicBytes(t, ops.Program, defaultEvalParams(txn), "REJECT") }) } } @@ -159,18 +159,18 @@ ed25519verify_bare`, pkStr), v) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program txn.Lsig.Args = [][]byte{data[:], sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(&txn)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn)) // short sig will fail txn.Lsig.Args[1] = sig[1:] - testLogicBytes(t, ops.Program, defaultEvalParams(&txn), "invalid signature") + testLogicBytes(t, ops.Program, defaultEvalParams(txn), "invalid signature") // flip a bit and it should not pass msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" data1, err := hex.DecodeString(msg1) require.NoError(t, err) txn.Lsig.Args = [][]byte{data1, sig[:]} - testLogicBytes(t, ops.Program, defaultEvalParams(&txn), "REJECT") + testLogicBytes(t, ops.Program, defaultEvalParams(txn), "REJECT") }) } } @@ -365,7 +365,7 @@ ecdsa_verify Secp256k1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.Encod ops := testProg(t, source, 5) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - pass, err := EvalSignature(0, defaultEvalParamsWithVersion(&txn, 5)) + pass, err := EvalSignature(0, defaultEvalParamsWithVersion(5, txn)) require.NoError(t, err) require.True(t, pass) } @@ -475,7 +475,7 @@ ecdsa_verify Secp256r1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.Encod ops := testProg(t, source, fidoVersion) var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - pass, err := EvalSignature(0, defaultEvalParamsWithVersion(&txn, fidoVersion)) + pass, err := EvalSignature(0, defaultEvalParamsWithVersion(fidoVersion, txn)) require.NoError(t, err) require.True(t, pass) } @@ -614,7 +614,7 @@ ed25519verify`, pkStr), AssemblerMaxVersion) var txn transactions.SignedTxn txn.Lsig.Logic = programs[i] txn.Lsig.Args = [][]byte{data[i][:], signatures[i][:]} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) pass, err := EvalSignature(0, ep) if !pass { b.Log(hex.EncodeToString(programs[i])) @@ -699,7 +699,7 @@ func benchmarkEcdsa(b *testing.B, source string, curve EcdsaCurve) { var txn transactions.SignedTxn txn.Lsig.Logic = data[i].programs txn.Lsig.Args = [][]byte{data[i].msg[:], data[i].r, data[i].s, data[i].x, data[i].y, data[i].pk, {uint8(data[i].v)}} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) pass, err := EvalSignature(0, ep) if !pass { b.Log(hex.EncodeToString(data[i].programs)) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index ee47b0233c..8b1c674632 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -49,16 +49,33 @@ func makeSampleEnv() (*EvalParams, *transactions.Transaction, *Ledger) { } func makeSampleEnvWithVersion(version uint64) (*EvalParams, *transactions.Transaction, *Ledger) { - ep := defaultEvalParamsWithVersion(nil, version) - ep.TxnGroup = transactions.WrapSignedTxnsWithAD(makeSampleTxnGroup(makeSampleTxn())) + // We'd usually like an app in the group, so that the ep created is + // "complete". But to keep as many old tests working as possible, if + // version < appsEnabledVersion, don't put an appl txn in it. + firstTxn := makeSampleTxn() + if version >= appsEnabledVersion { + firstTxn.Txn.Type = protocol.ApplicationCallTx + } + ep := defaultEvalParamsWithVersion(version, makeSampleTxnGroup(firstTxn)...) + ledger := NewLedger(nil) + ep.Ledger = ledger + return ep, &ep.TxnGroup[0].Txn, ledger +} + +// makeSampleAppEnv is as close to makeSampleEnv as possible, except the type of +// the sample txn is set to "appl", so a full EvalParams for apps will be made +func makeSampleAppEnv() (*EvalParams, *transactions.Transaction, *Ledger) { + appTxn := makeSampleTxn() + appTxn.Txn.Type = protocol.ApplicationCallTx + ep := defaultEvalParamsWithVersion(LogicVersion, makeSampleTxnGroup(appTxn)...) ledger := NewLedger(nil) ep.Ledger = ledger return ep, &ep.TxnGroup[0].Txn, ledger } func makeOldAndNewEnv(version uint64) (*EvalParams, *EvalParams, *Ledger) { - new, _, sharedLedger := makeSampleEnv() - old, _, _ := makeSampleEnvWithVersion(version) + new, _, sharedLedger := makeSampleEnvWithVersion(version) + old, _, _ := makeSampleEnvWithVersion(version - 1) old.Ledger = sharedLedger return old, new, sharedLedger } @@ -237,8 +254,8 @@ log // check err opcode work in both modes source := "err" - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(nil), "err opcode executed") - testApp(t, source, defaultEvalParams(nil), "err opcode executed") + testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "err opcode executed") + testApp(t, source, defaultEvalParams(), "err opcode executed") // check that ed25519verify and arg is not allowed in stateful mode between v2-v4 disallowedV4 := []string{ @@ -251,7 +268,7 @@ log } for _, source := range disallowedV4 { ops := testProg(t, source, 4) - testAppBytes(t, ops.Program, defaultEvalParams(nil), + testAppBytes(t, ops.Program, defaultEvalParams(), "not allowed in current mode", "not allowed in current mode") } @@ -265,7 +282,7 @@ log } for _, source := range disallowed { ops := testProg(t, source, AssemblerMaxVersion) - testAppBytes(t, ops.Program, defaultEvalParams(nil), + testAppBytes(t, ops.Program, defaultEvalParams(), "not allowed in current mode", "not allowed in current mode") } @@ -288,7 +305,7 @@ log } for _, source := range statefulOpcodeCalls { - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(nil), + testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "not allowed in current mode", "not allowed in current mode") } @@ -383,7 +400,7 @@ func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...stri t.Helper() ep.reset() aid := ep.TxnGroup[0].Txn.ApplicationID - if aid == basics.AppIndex(0) { + if aid == 0 { aid = basics.AppIndex(888) } return testAppFull(t, program, 0, aid, ep, problems...) @@ -492,24 +509,13 @@ func TestMinBalance(t *testing.T) { func TestAppCheckOptedIn(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - txn := makeSampleTxn() - txgroup := makeSampleTxnGroup(txn) - now := defaultEvalParams(&txn) - now.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup) - pre := defaultEvalParamsWithVersion(&txn, directRefEnabledVersion-1) - pre.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup) + pre, now, ledger := makeOldAndNewEnv(directRefEnabledVersion) - ledger := NewLedger( - map[basics.Address]uint64{ - txn.Txn.Receiver: 1, - txn.Txn.Sender: 1, - }, - ) - now.Ledger = ledger - pre.Ledger = ledger + txn := pre.TxnGroup[0] + ledger.NewAccount(txn.Txn.Receiver, 1) + ledger.NewAccount(txn.Txn.Sender, 1) testApp(t, "int 2; int 100; app_opted_in; int 1; ==", now, "invalid Account reference") // Receiver is not opted in @@ -565,7 +571,7 @@ exit: int 1 ==` - pre, now, ledger := makeOldAndNewEnv(directRefEnabledVersion - 1) + pre, now, ledger := makeOldAndNewEnv(directRefEnabledVersion) ledger.NewAccount(now.TxnGroup[0].Txn.Receiver, 1) testApp(t, text, now, "invalid Account reference") @@ -723,7 +729,7 @@ byte 0x414c474f == && ` - pre, now, ledger := makeOldAndNewEnv(directRefEnabledVersion - 1) + pre, now, ledger := makeOldAndNewEnv(directRefEnabledVersion) ledger.NewAccount(now.TxnGroup[0].Txn.Sender, 1) now.TxnGroup[0].Txn.ApplicationID = 100 @@ -773,13 +779,13 @@ int 4141 now.TxnGroup[0].Txn.ApplicationID = 0 now.TxnGroup[0].Txn.ForeignApps = []basics.AppIndex{100} - testAppFull(t, testProg(t, text, LogicVersion).Program, 0, 100, now) + testAppFull(t, testProg(t, text, directRefEnabledVersion).Program, 0, 100, now) // Direct reference to the current app also works now.TxnGroup[0].Txn.ForeignApps = []basics.AppIndex{} - testAppFull(t, testProg(t, strings.Replace(text, "int 1 // ForeignApps index", "int 100", -1), LogicVersion).Program, + testAppFull(t, testProg(t, strings.Replace(text, "int 1 // ForeignApps index", "int 100", -1), directRefEnabledVersion).Program, 0, 100, now) - testAppFull(t, testProg(t, strings.Replace(text, "int 1 // ForeignApps index", "global CurrentApplicationID", -1), LogicVersion).Program, + testAppFull(t, testProg(t, strings.Replace(text, "int 1 // ForeignApps index", "global CurrentApplicationID", -1), directRefEnabledVersion).Program, 0, 100, now) } @@ -922,9 +928,9 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) } txn := makeSampleTxn() - pre := defaultEvalParamsWithVersion(&txn, directRefEnabledVersion-1) + pre := defaultEvalParamsWithVersion(directRefEnabledVersion-1, txn) require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) - now := defaultEvalParamsWithVersion(&txn, version) + now := defaultEvalParamsWithVersion(version, txn) ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, @@ -1233,9 +1239,10 @@ intc_1 ops := testProg(t, source, AssemblerMaxVersion) - txn := makeSampleTxn() + var txn transactions.SignedTxn + txn.Txn.Type = protocol.ApplicationCallTx txn.Txn.ApplicationID = 100 - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) err := CheckContract(ops.Program, ep) require.NoError(t, err) @@ -1292,7 +1299,7 @@ func TestAppLocalStateReadWrite(t *testing.T) { txn := makeSampleTxn() txn.Txn.ApplicationID = 100 - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, @@ -1638,10 +1645,10 @@ int 0x77 == && ` - txn := makeSampleTxn() + txn := makeSampleAppl() txn.Txn.ApplicationID = 100 txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, @@ -1777,24 +1784,19 @@ ok2: byte "myval" == ` - txn := makeSampleTxn() - txn.Txn.ApplicationID = 100 - txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID, 101} - ep := defaultEvalParams(&txn) - ledger := NewLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - ep.Ledger = ledger - ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) + + ep, txn, ledger := makeSampleEnv() + txn.ApplicationID = 100 + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID, 101} + ledger.NewAccount(txn.Sender, 1) + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep, "no such app") require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) - ledger.NewApp(txn.Txn.Receiver, 101, basics.AppParams{}) - ledger.NewApp(txn.Txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 + ledger.NewApp(txn.Receiver, 101, basics.AppParams{}) + ledger.NewApp(txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} ledger.NewGlobal(101, "mykey", algoValue) @@ -1825,7 +1827,7 @@ int 7 ` txn := makeSampleTxn() txn.Txn.ApplicationID = 100 - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, @@ -1840,7 +1842,6 @@ int 7 func TestAppGlobalDelete(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() // check write/delete/read @@ -1869,16 +1870,10 @@ err ok: int 1 ` - txn := makeSampleTxn() - txn.Txn.ApplicationID = 100 - ep := defaultEvalParams(&txn) - ledger := NewLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - ep.Ledger = ledger - ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) + ep, txn, ledger := makeSampleEnv() + ledger.NewAccount(txn.Sender, 1) + txn.ApplicationID = 100 + ledger.NewApp(txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 2) @@ -1899,7 +1894,7 @@ byte 0x414c474f app_global_get_ex == // two zeros ` - ep.TxnGroup[0].Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} + txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} delta = testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 1) vd := delta.GlobalDelta["ALGO"] @@ -2000,7 +1995,6 @@ int 1 func TestAppLocalDelete(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() // check write/delete/read @@ -2037,7 +2031,7 @@ int 1 ` txn := makeSampleTxn() txn.Txn.ApplicationID = 100 - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) ledger := NewLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, @@ -2209,8 +2203,8 @@ func TestEnumFieldErrors(t *testing.T) { txnFieldSpecs[Amount] = origSpec }() - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(nil), "Amount expected field type is []byte but got uint64") - testApp(t, source, defaultEvalParams(nil), "Amount expected field type is []byte but got uint64") + testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "Amount expected field type is []byte but got uint64") + testApp(t, source, defaultEvalParams(), "Amount expected field type is []byte but got uint64") source = `global MinTxnFee` @@ -2222,8 +2216,8 @@ func TestEnumFieldErrors(t *testing.T) { globalFieldSpecs[MinTxnFee] = origMinTxnFs }() - testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(nil), "MinTxnFee expected field type is []byte but got uint64") - testApp(t, source, defaultEvalParams(nil), "MinTxnFee expected field type is []byte but got uint64") + testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(), "MinTxnFee expected field type is []byte but got uint64") + testApp(t, source, defaultEvalParams(), "MinTxnFee expected field type is []byte but got uint64") ep, tx, ledger := makeSampleEnv() ledger.NewAccount(tx.Sender, 1) @@ -2613,8 +2607,8 @@ func TestAppInfo(t *testing.T) { func TestBudget(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() - ep := defaultEvalParams(nil) source := ` global OpcodeBudget int 699 @@ -2624,11 +2618,12 @@ global OpcodeBudget int 695 == ` - testApp(t, source, ep) + testApp(t, source, defaultEvalParams()) } func TestSelfMutate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() ep, _, ledger := makeSampleEnv() diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 16d20cdbd2..ba325e9847 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -106,12 +106,12 @@ func makeTestProtoV(version uint64) *config.ConsensusParams { } } -func defaultEvalParams(txn *transactions.SignedTxn) *EvalParams { - return defaultEvalParamsWithVersion(txn, LogicVersion) +func defaultEvalParams(txns ...transactions.SignedTxn) *EvalParams { + return defaultEvalParamsWithVersion(LogicVersion, txns...) } -func benchmarkEvalParams(txn *transactions.SignedTxn) *EvalParams { - ep := defaultEvalParamsWithVersion(txn, LogicVersion) +func benchmarkEvalParams(txn transactions.SignedTxn) *EvalParams { + ep := defaultEvalParams(txn) ep.Trace = nil // Tracing would slow down benchmarks clone := *ep.Proto bigBudget := 1000 * 1000 // Allow long run times @@ -122,19 +122,18 @@ func benchmarkEvalParams(txn *transactions.SignedTxn) *EvalParams { return ep } -func defaultEvalParamsWithVersion(txn *transactions.SignedTxn, version uint64) *EvalParams { - var zero uint64 - ep := &EvalParams{ - Proto: makeTestProtoV(version), - TxnGroup: make([]transactions.SignedTxnWithAD, 1), - Specials: &transactions.SpecialAddresses{}, - Trace: &strings.Builder{}, - FeeCredit: &zero, +func defaultEvalParamsWithVersion(version uint64, txns ...transactions.SignedTxn) *EvalParams { + empty := false + if len(txns) == 0 { + empty = true + txns = []transactions.SignedTxn{{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}}} } - if txn != nil { - ep.TxnGroup[0].SignedTxn = *txn + ep := NewEvalParams(transactions.WrapSignedTxnsWithAD(txns), makeTestProtoV(version), &transactions.SpecialAddresses{}) + if empty { + // We made an app type in order to get a full ep, but that sets MinTealVersion=2 + ep.TxnGroup[0].Txn.Type = "" // set it back + ep.MinTealVersion = nil // will recalculate in eval() } - ep.reset() return ep } @@ -154,9 +153,15 @@ func (ep *EvalParams) reset() { for i := range ep.TxnGroup { ep.TxnGroup[i].ApplyData = transactions.ApplyData{} } - ep.available = &resources{} + if ep.available != nil { + ep.available.apps = nil + ep.available.asas = nil + // leave ep.available.boxes alone, as it is not affected by evaluations + } ep.appAddrCache = make(map[basics.AppIndex]basics.Address) - ep.Trace = &strings.Builder{} + if ep.Trace != nil { + ep.Trace = &strings.Builder{} + } } func TestTooManyArgs(t *testing.T) { @@ -170,7 +175,7 @@ func TestTooManyArgs(t *testing.T) { txn.Lsig.Logic = ops.Program args := [transactions.EvalMaxArgs + 1][]byte{} txn.Lsig.Args = args[:] - pass, err := EvalSignature(0, defaultEvalParams(&txn)) + pass, err := EvalSignature(0, defaultEvalParams(txn)) require.Error(t, err) require.False(t, pass) }) @@ -181,7 +186,7 @@ func TestEmptyProgram(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testLogicBytes(t, nil, defaultEvalParams(nil), "invalid", "invalid program (empty)") + testLogicBytes(t, nil, defaultEvalParams(), "invalid", "invalid program (empty)") } // TestMinTealVersionParamEval tests eval/check reading the MinTealVersion from the param @@ -189,7 +194,7 @@ func TestMinTealVersionParamEvalCheckSignature(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - params := defaultEvalParams(nil) + params := defaultEvalParams() version2 := uint64(rekeyingEnabledVersion) params.MinTealVersion = &version2 program := make([]byte, binary.MaxVarintLen64) @@ -254,7 +259,7 @@ func TestWrongProtoVersion(t *testing.T) { for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, "int 1", v) - ep := defaultEvalParamsWithVersion(nil, 0) + ep := defaultEvalParamsWithVersion(0) testAppBytes(t, ops.Program, ep, "LogicSig not supported", "LogicSig not supported") }) } @@ -285,7 +290,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) err := CheckSignature(0, ep) require.NoError(t, err) pass, err := EvalSignature(0, ep) @@ -346,7 +351,7 @@ func TestTLHC(t *testing.T) { txn.Lsig.Args = [][]byte{secret} txn.Txn.FirstValid = 999999 block := bookkeeping.Block{} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) err := CheckSignature(0, ep) if err != nil { t.Log(hex.EncodeToString(ops.Program)) @@ -363,7 +368,7 @@ func TestTLHC(t *testing.T) { txn.Txn.Receiver = a2 txn.Txn.CloseRemainderTo = a2 - ep = defaultEvalParams(&txn) + ep = defaultEvalParams(txn) pass, err = EvalSignature(0, ep) if !pass { t.Log(hex.EncodeToString(ops.Program)) @@ -375,7 +380,7 @@ func TestTLHC(t *testing.T) { txn.Txn.Receiver = a2 txn.Txn.CloseRemainderTo = a2 txn.Txn.FirstValid = 1 - ep = defaultEvalParams(&txn) + ep = defaultEvalParams(txn) pass, err = EvalSignature(0, ep) if pass { t.Log(hex.EncodeToString(ops.Program)) @@ -387,7 +392,7 @@ func TestTLHC(t *testing.T) { txn.Txn.Receiver = a1 txn.Txn.CloseRemainderTo = a1 txn.Txn.FirstValid = 999999 - ep = defaultEvalParams(&txn) + ep = defaultEvalParams(txn) pass, err = EvalSignature(0, ep) if !pass { t.Log(hex.EncodeToString(ops.Program)) @@ -399,7 +404,7 @@ func TestTLHC(t *testing.T) { // wrong answer txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849a")} block.BlockHeader.Round = 1 - ep = defaultEvalParams(&txn) + ep = defaultEvalParams(txn) pass, err = EvalSignature(0, ep) if pass { t.Log(hex.EncodeToString(ops.Program)) @@ -825,7 +830,7 @@ func TestTxnBadField(t *testing.T) { t.Parallel() program := []byte{0x01, 0x31, 0x7f} - testLogicBytes(t, program, defaultEvalParams(nil), "invalid txn field") + testLogicBytes(t, program, defaultEvalParams(), "invalid txn field") // TODO: Check should know the type stack was wrong // test txn does not accept ApplicationArgs and Accounts @@ -838,7 +843,7 @@ func TestTxnBadField(t *testing.T) { ops := testProg(t, source, AssemblerMaxVersion) require.Equal(t, txnaOpcode, ops.Program[1]) ops.Program[1] = txnOpcode - testLogicBytes(t, ops.Program, defaultEvalParams(nil), fmt.Sprintf("invalid txn field %d", field)) + testLogicBytes(t, ops.Program, defaultEvalParams(), fmt.Sprintf("invalid txn field %d", field)) } } @@ -847,7 +852,7 @@ func TestGtxnBadIndex(t *testing.T) { t.Parallel() program := []byte{0x01, 0x33, 0x1, 0x01} - testLogicBytes(t, program, defaultEvalParams(nil), "txn index 1") + testLogicBytes(t, program, defaultEvalParams(), "txn index 1") } func TestGtxnBadField(t *testing.T) { @@ -856,7 +861,7 @@ func TestGtxnBadField(t *testing.T) { t.Parallel() program := []byte{0x01, 0x33, 0x0, 127} // TODO: Check should know the type stack was wrong - testLogicBytes(t, program, defaultEvalParams(nil), "invalid txn field 127") + testLogicBytes(t, program, defaultEvalParams(), "invalid txn field 127") // test gtxn does not accept ApplicationArgs and Accounts txnOpcode := OpsByName[LogicVersion]["txn"].Opcode @@ -868,7 +873,7 @@ func TestGtxnBadField(t *testing.T) { ops := testProg(t, source, AssemblerMaxVersion) require.Equal(t, txnaOpcode, ops.Program[1]) ops.Program[1] = txnOpcode - testLogicBytes(t, ops.Program, defaultEvalParams(nil), fmt.Sprintf("invalid txn field %d", field)) + testLogicBytes(t, ops.Program, defaultEvalParams(), fmt.Sprintf("invalid txn field %d", field)) } } @@ -877,7 +882,7 @@ func TestGlobalBadField(t *testing.T) { t.Parallel() program := []byte{0x01, 0x32, 127} - testLogicBytes(t, program, defaultEvalParams(nil), "invalid global field") + testLogicBytes(t, program, defaultEvalParams(), "invalid global field") } func TestArg(t *testing.T) { @@ -900,7 +905,7 @@ func TestArg(t *testing.T) { []byte("aoeu4"), } ops := testProg(t, source, v) - testLogicBytes(t, ops.Program, defaultEvalParams(&txn)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn)) }) } } @@ -1030,7 +1035,7 @@ func TestGlobal(t *testing.T) { txn := transactions.SignedTxn{} txn.Txn.Group = crypto.Digest{0x07, 0x06} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) ep.Ledger = ledger testApp(t, tests[v].program, ep) }) @@ -1075,11 +1080,11 @@ int %s txn := transactions.SignedTxn{} txn.Txn.Type = tt if v < appsEnabledVersion && tt == protocol.ApplicationCallTx { - testLogicBytes(t, ops.Program, defaultEvalParams(&txn), + testLogicBytes(t, ops.Program, defaultEvalParams(txn), "program version must be", "program version must be") return } - testLogicBytes(t, ops.Program, defaultEvalParams(&txn)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn)) }) } }) @@ -1173,13 +1178,25 @@ txn VoteKeyDilution int 1 == && + txn Type -byte 0x706179 +byte "pay" +== +txn Type +byte "appl" == +|| + && + txn TypeEnum int 1 == +txn TypeEnum +int 6 +== +|| + && txn XferAsset int 10 @@ -1517,12 +1534,18 @@ func makeSampleTxn() transactions.SignedTxn { txn.Txn.AssetFrozen = true txn.Txn.ForeignAssets = []basics.AssetIndex{55, 77} txn.Txn.ForeignApps = []basics.AppIndex{56, 100, 111} // 100 must be 2nd, 111 must be present - txn.Txn.Boxes = []transactions.BoxRef{{Index: 0, Name: "self"}, {Index: 2, Name: "hundy"}} + txn.Txn.Boxes = []transactions.BoxRef{{Index: 0, Name: "self"}, {Index: 0, Name: "other"}} txn.Txn.GlobalStateSchema = basics.StateSchema{NumUint: 3, NumByteSlice: 0} txn.Txn.LocalStateSchema = basics.StateSchema{NumUint: 1, NumByteSlice: 2} return txn } +func makeSampleAppl() transactions.SignedTxn { + sample := makeSampleTxn() + sample.Txn.Type = protocol.ApplicationCallTx + return sample +} + // makeSampleTxnGroup creates a sample txn group. If less than two transactions // are supplied, samples are used. func makeSampleTxnGroup(txns ...transactions.SignedTxn) []transactions.SignedTxn { @@ -1584,6 +1607,9 @@ func TestTxn(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, source, v) txn := makeSampleTxn() + if v >= appsEnabledVersion { + txn.Txn.Type = protocol.ApplicationCallTx + } txn.Txn.ApprovalProgram = ops.Program txn.Txn.ClearStateProgram = clearOps.Program txn.Lsig.Logic = ops.Program @@ -1608,9 +1634,8 @@ func TestTxn(t *testing.T) { programHash[:], clearProgramHash[:], } - // Since we test GroupIndex ==3, we need to fake up such a group - ep := defaultEvalParams(nil) - ep.TxnGroup = transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{txn, txn, txn, txn}) + // Since we test GroupIndex ==3, we need a larger group + ep := defaultEvalParams(txn, txn, txn, txn) ep.TxnGroup[2].EvalDelta.Logs = []string{"x", "prefilled"} if v < txnEffectsVersion { testLogicFull(t, ops.Program, 3, ep) @@ -1694,15 +1719,11 @@ func TestGaid(t *testing.T) { t.Parallel() check0 := testProg(t, "gaid 0; int 100; ==", 4) - txn := makeSampleTxn() - txn.Txn.Type = protocol.ApplicationCallTx - txgroup := make([]transactions.SignedTxn, 3) - txgroup[1] = txn + appTxn := makeSampleTxn() + appTxn.Txn.Type = protocol.ApplicationCallTx targetTxn := makeSampleTxn() targetTxn.Txn.Type = protocol.AssetConfigTx - txgroup[0] = targetTxn - ep := defaultEvalParams(nil) - ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup) + ep := defaultEvalParams(targetTxn, appTxn, makeSampleTxn()) ep.Ledger = NewLedger(nil) // should fail when no creatable was created @@ -1860,8 +1881,7 @@ gtxn 0 Sender txn.Txn.SelectionPK[:], txn.Txn.Note, } - ep := defaultEvalParams(&txn) - ep.TxnGroup = transactions.WrapSignedTxnsWithAD(makeSampleTxnGroup(txn)) + ep := defaultEvalParams(makeSampleTxnGroup(txn)...) testLogic(t, source, v, ep) if v >= 3 { gtxnsProg := strings.ReplaceAll(source, "gtxn 0", "int 0; gtxns") @@ -1950,7 +1970,7 @@ txna ApplicationArgs 0 txn.Txn.Accounts = make([]basics.Address, 1) txn.Txn.Accounts[0] = txn.Txn.Sender txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) testLogicBytes(t, ops.Program, ep) // modify txn field @@ -1984,7 +2004,7 @@ txn Sender ops2 := testProg(t, source, AssemblerMaxVersion) var txn2 transactions.SignedTxn copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - ep2 := defaultEvalParams(&txn2) + ep2 := defaultEvalParams(txn2) testLogicBytes(t, ops2.Program, ep2) // check gtxna @@ -2025,7 +2045,7 @@ txn Sender ops3 := testProg(t, source, AssemblerMaxVersion) var txn3 transactions.SignedTxn copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - ep3 := defaultEvalParams(&txn3) + ep3 := defaultEvalParams(txn3) testLogicBytes(t, ops3.Program, ep3) } @@ -2044,10 +2064,10 @@ int 0 var txn transactions.SignedTxn txn.Txn.ApplicationArgs = make([][]byte, 1) txn.Txn.ApplicationArgs[0] = []byte("") - testLogicBytes(t, ops.Program, defaultEvalParams(&txn)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn)) txn.Txn.ApplicationArgs[0] = nil - testLogicBytes(t, ops.Program, defaultEvalParams(&txn)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn)) source2 := `txna Accounts 1 global ZeroAddress @@ -2058,10 +2078,10 @@ global ZeroAddress var txn2 transactions.SignedTxn txn2.Txn.Accounts = make([]basics.Address, 1) txn2.Txn.Accounts[0] = basics.Address{} - testLogicBytes(t, ops.Program, defaultEvalParams(&txn2)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn2)) txn2.Txn.Accounts = make([]basics.Address, 1) - testLogicBytes(t, ops.Program, defaultEvalParams(&txn2)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn2)) } func TestTxnas(t *testing.T) { @@ -2080,7 +2100,7 @@ txnas ApplicationArgs txn.Txn.Accounts = make([]basics.Address, 1) txn.Txn.Accounts[0] = txn.Txn.Sender txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) testLogicBytes(t, ops.Program, ep) // check special case: Account 0 == Sender @@ -2093,7 +2113,7 @@ txn Sender ops = testProg(t, source, AssemblerMaxVersion) var txn2 transactions.SignedTxn copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - testLogicBytes(t, ops.Program, defaultEvalParams(&txn2)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn2)) // check gtxnas source = `int 1 @@ -2113,7 +2133,7 @@ txn Sender ops = testProg(t, source, AssemblerMaxVersion) var txn3 transactions.SignedTxn copy(txn3.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) - testLogicBytes(t, ops.Program, defaultEvalParams(&txn3)) + testLogicBytes(t, ops.Program, defaultEvalParams(txn3)) // check gtxnsas source = `int 0 @@ -2670,19 +2690,19 @@ func TestSlowLogic(t *testing.T) { // v1overspend fails (on v1) ops := testProg(t, v1overspend, 1) // We should never Eval this after it fails Check(), but nice to see it also fails. - testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(nil, 1), + testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(1), "static cost", "dynamic cost") // v2overspend passes Check, even on v2 proto, because the old low cost is "grandfathered" ops = testProg(t, v2overspend, 1) - testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(nil, 2)) + testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(2)) // even the shorter, v2overspend, fails when compiled as v2 code ops = testProg(t, v2overspend, 2) - testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(nil, 2), + testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(2), "static cost", "dynamic cost") // in v4 cost is still 134, but only matters in Eval, not Check, so both fail there - ep4 := defaultEvalParamsWithVersion(nil, 4) + ep4 := defaultEvalParamsWithVersion(4) ops = testProg(t, v1overspend, 4) testLogicBytes(t, ops.Program, ep4, "dynamic cost") @@ -2707,7 +2727,7 @@ func TestStackUnderflow(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `int 1`, v) ops.Program = append(ops.Program, 0x08) // + - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "stack underflow") + testLogicBytes(t, ops.Program, defaultEvalParams(), "stack underflow") }) } } @@ -2720,7 +2740,7 @@ func TestWrongStackTypeRuntime(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `int 1`, v) ops.Program = append(ops.Program, 0x01, 0x15) // sha256, len - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "sha256 arg 0 wanted") + testLogicBytes(t, ops.Program, defaultEvalParams(), "sha256 arg 0 wanted") }) } } @@ -2733,7 +2753,7 @@ func TestEqMismatch(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `byte 0x1234; int 1`, v) ops.Program = append(ops.Program, 0x12) // == - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "cannot compare") + testLogicBytes(t, ops.Program, defaultEvalParams(), "cannot compare") // TODO: Check should know the type stack was wrong }) } @@ -2747,7 +2767,7 @@ func TestNeqMismatch(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `byte 0x1234; int 1`, v) ops.Program = append(ops.Program, 0x13) // != - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "cannot compare") + testLogicBytes(t, ops.Program, defaultEvalParams(), "cannot compare") }) } } @@ -2760,7 +2780,7 @@ func TestWrongStackTypeRuntime2(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { ops := testProg(t, `byte 0x1234; int 1`, v) ops.Program = append(ops.Program, 0x08) // + - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "+ arg 0 wanted") + testLogicBytes(t, ops.Program, defaultEvalParams(), "+ arg 0 wanted") }) } } @@ -2778,7 +2798,7 @@ func TestIllegalOp(t *testing.T) { break } } - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "illegal opcode", "illegal opcode") + testLogicBytes(t, ops.Program, defaultEvalParams(), "illegal opcode", "illegal opcode") }) } } @@ -2796,7 +2816,7 @@ int 1 `, v) // cut two last bytes - intc_1 and last byte of bnz ops.Program = ops.Program[:len(ops.Program)-2] - testLogicBytes(t, ops.Program, defaultEvalParams(nil), + testLogicBytes(t, ops.Program, defaultEvalParams(), "bnz program ends short", "bnz program ends short") }) } @@ -2811,7 +2831,7 @@ intc 0 intc 0 bnz done done:`, 2) - testLogicBytes(t, ops.Program, defaultEvalParams(nil)) + testLogicBytes(t, ops.Program, defaultEvalParams()) } func TestShortBytecblock(t *testing.T) { @@ -2826,7 +2846,7 @@ func TestShortBytecblock(t *testing.T) { for i := 2; i < len(fullops.Program); i++ { program := fullops.Program[:i] t.Run(hex.EncodeToString(program), func(t *testing.T) { - testLogicBytes(t, program, defaultEvalParams(nil), + testLogicBytes(t, program, defaultEvalParams(), "bytecblock", "bytecblock") }) } @@ -2848,7 +2868,7 @@ func TestShortBytecblock2(t *testing.T) { t.Run(src, func(t *testing.T) { program, err := hex.DecodeString(src) require.NoError(t, err) - testLogicBytes(t, program, defaultEvalParams(nil), "bytecblock", "bytecblock") + testLogicBytes(t, program, defaultEvalParams(), "bytecblock", "bytecblock") }) } } @@ -2885,7 +2905,7 @@ func TestPanic(t *testing.T) { break } } - params := defaultEvalParams(nil) + params := defaultEvalParams() params.logger = log params.TxnGroup[0].Lsig.Logic = ops.Program err := CheckSignature(0, params) @@ -2899,7 +2919,7 @@ func TestPanic(t *testing.T) { } var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - params = defaultEvalParams(&txn) + params = defaultEvalParams(txn) params.logger = log pass, err := EvalSignature(0, params) if pass { @@ -2925,7 +2945,7 @@ func TestProgramTooNew(t *testing.T) { t.Parallel() var program [12]byte vlen := binary.PutUvarint(program[:], evalMaxVersion+1) - testLogicBytes(t, program[:vlen], defaultEvalParams(nil), + testLogicBytes(t, program[:vlen], defaultEvalParams(), "greater than max supported", "greater than max supported") } @@ -2935,7 +2955,7 @@ func TestInvalidVersion(t *testing.T) { t.Parallel() program, err := hex.DecodeString("ffffffffffffffffffffffff") require.NoError(t, err) - testLogicBytes(t, program, defaultEvalParams(nil), "invalid version", "invalid version") + testLogicBytes(t, program, defaultEvalParams(), "invalid version", "invalid version") } func TestProgramProtoForbidden(t *testing.T) { @@ -2944,7 +2964,7 @@ func TestProgramProtoForbidden(t *testing.T) { t.Parallel() var program [12]byte vlen := binary.PutUvarint(program[:], evalMaxVersion) - ep := defaultEvalParams(nil) + ep := defaultEvalParams() ep.Proto = &config.ConsensusParams{ LogicSigVersion: evalMaxVersion - 1, } @@ -2969,16 +2989,16 @@ int 1`, v) require.Equal(t, ops.Program, canonicalProgramBytes) ops.Program[7] = 3 // clobber the branch offset to be in the middle of the bytecblock // Since Eval() doesn't know the jump is bad, we reject "by luck" - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "aligned", "REJECT") + testLogicBytes(t, ops.Program, defaultEvalParams(), "aligned", "REJECT") // back branches are checked differently, so test misaligned back branch ops.Program[6] = 0xff // Clobber the two bytes of offset with 0xff 0xff = -1 ops.Program[7] = 0xff // That jumps into the offset itself (pc + 3 -1) if v < backBranchEnabledVersion { - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "negative branch", "negative branch") + testLogicBytes(t, ops.Program, defaultEvalParams(), "negative branch", "negative branch") } else { // Again, if we were ever to Eval(), we would not know it's wrong. But we reject here "by luck" - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "back branch target", "REJECT") + testLogicBytes(t, ops.Program, defaultEvalParams(), "back branch target", "REJECT") } }) } @@ -3001,7 +3021,7 @@ int 1`, v) require.NoError(t, err) require.Equal(t, ops.Program, canonicalProgramBytes) ops.Program[7] = 200 // clobber the branch offset to be beyond the end of the program - testLogicBytes(t, ops.Program, defaultEvalParams(nil), + testLogicBytes(t, ops.Program, defaultEvalParams(), "outside of program", "outside of program") }) } @@ -3025,7 +3045,7 @@ int 1`, v) require.NoError(t, err) require.Equal(t, ops.Program, canonicalProgramBytes) ops.Program[6] = 0x70 // clobber hi byte of branch offset - testLogicBytes(t, ops.Program, defaultEvalParams(nil), "outside", "outside") + testLogicBytes(t, ops.Program, defaultEvalParams(), "outside", "outside") }) } branches := []string{ @@ -3045,7 +3065,7 @@ intc_1 require.NoError(t, err) ops.Program[7] = 0xf0 // clobber the branch offset - highly negative ops.Program[8] = 0xff // clobber the branch offset - testLogicBytes(t, ops.Program, defaultEvalParams(nil), + testLogicBytes(t, ops.Program, defaultEvalParams(), "outside of program", "outside of program") }) } @@ -3332,10 +3352,10 @@ func evalLoop(b *testing.B, runs int, program []byte) { for i := 0; i < runs; i++ { var txn transactions.SignedTxn txn.Lsig.Logic = program - pass, err := EvalSignature(0, benchmarkEvalParams(&txn)) + pass, err := EvalSignature(0, benchmarkEvalParams(txn)) if !pass { // rerun to trace it. tracing messes up timing too much - ep := benchmarkEvalParams(&txn) + ep := benchmarkEvalParams(txn) ep.Trace = &strings.Builder{} pass, err = EvalSignature(0, ep) b.Log(ep.Trace.String()) @@ -3557,7 +3577,7 @@ func BenchmarkCheckx5(b *testing.B) { for _, program := range programs { var txn transactions.SignedTxn txn.Lsig.Logic = program - err := CheckSignature(0, defaultEvalParams(&txn)) + err := CheckSignature(0, defaultEvalParams(txn)) if err != nil { require.NoError(b, err) } @@ -3581,16 +3601,16 @@ pop txn.Lsig.Logic = ops.Program txn.Txn.ApplicationArgs = [][]byte{[]byte("test")} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) testLogicBytes(t, ops.Program, ep) - ep = defaultEvalParamsWithVersion(&txn, 1) + ep = defaultEvalParamsWithVersion(1, txn) testLogicBytes(t, ops.Program, ep, "greater than protocol supported version 1", "greater than protocol supported version 1") // hack the version and fail on illegal opcode ops.Program[0] = 0x1 - ep = defaultEvalParamsWithVersion(&txn, 1) + ep = defaultEvalParamsWithVersion(1, txn) testLogicBytes(t, ops.Program, ep, "illegal opcode 0x36", "illegal opcode 0x36") // txna } @@ -3697,7 +3717,7 @@ func TestApplicationsDisallowOldTeal(t *testing.T) { txn := makeSampleTxn() txn.Txn.Type = protocol.ApplicationCallTx txn.Txn.RekeyTo = basics.Address{} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) for v := uint64(0); v < appsEnabledVersion; v++ { ops := testProg(t, source, v) @@ -3750,8 +3770,7 @@ func TestAnyRekeyToOrApplicationRaisesMinTealVersion(t *testing.T) { for ci, cse := range cases { t.Run(fmt.Sprintf("ci=%d", ci), func(t *testing.T) { - ep := defaultEvalParams(nil) - ep.TxnGroup = transactions.WrapSignedTxnsWithAD(cse.group) + ep := defaultEvalParams(cse.group...) // Computed MinTealVersion should be == validFromVersion calc := ComputeMinTealVersion(ep.TxnGroup) @@ -3814,7 +3833,7 @@ func TestAllowedOpcodesV2(t *testing.T) { "gtxn": true, } - ep := defaultEvalParams(nil) + ep := defaultEvalParams() cnt := 0 for _, spec := range OpSpecs { @@ -3867,7 +3886,7 @@ func TestAllowedOpcodesV3(t *testing.T) { "pushbytes": `pushbytes "stringsfail?"`, } - ep := defaultEvalParams(nil) + ep := defaultEvalParams() cnt := 0 for _, spec := range OpSpecs { @@ -3912,7 +3931,7 @@ func TestRekeyFailsOnOldVersion(t *testing.T) { ops := testProg(t, `int 1`, v) var txn transactions.SignedTxn txn.Txn.RekeyTo = basics.Address{1, 2, 3, 4} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) e := fmt.Sprintf("program version must be >= %d", rekeyingEnabledVersion) testLogicBytes(t, ops.Program, ep, e, e) }) @@ -3953,13 +3972,13 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval t.Helper() var txn transactions.SignedTxn txn.Lsig.Logic = ops.Program - ep := defaultEvalParamsWithVersion(&txn, lv) + ep := defaultEvalParamsWithVersion(lv, txn) err := CheckSignature(0, ep) if err != nil { t.Log(ep.Trace.String()) } require.NoError(t, err) - ep = defaultEvalParamsWithVersion(&txn, lv) + ep = defaultEvalParamsWithVersion(lv, txn) pass, err := EvalSignature(0, ep) ok := tester(pass, err) if !ok { @@ -4488,7 +4507,7 @@ func TestLog(t *testing.T) { txn.Txn.Type = protocol.ApplicationCallTx ledger := NewLedger(nil) ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) ep.Proto = makeTestProtoV(LogicVersion) ep.Ledger = ledger testCases := []struct { @@ -4601,6 +4620,7 @@ func TestPcDetails(t *testing.T) { t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { ops := testProg(t, test.source, LogicVersion) ep, _, _ := makeSampleEnv() + ep.Trace = &strings.Builder{} pass, cx, err := EvalContract(ops.Program, 0, 888, ep) require.Error(t, err) @@ -4820,16 +4840,12 @@ func TestHasDuplicateKeys(t *testing.T) { func TestOpJSONRef(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - proto := makeTestProtoV(LogicVersion) - txn := transactions.SignedTxn{ - Txn: transactions.Transaction{ - Type: protocol.ApplicationCallTx, - }, - } + + var txn transactions.SignedTxn + txn.Txn.Type = protocol.ApplicationCallTx ledger := NewLedger(nil) ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) - ep := defaultEvalParams(&txn) - ep.Proto = proto + ep := defaultEvalParams(txn) ep.Ledger = ledger testCases := []struct { source string diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 1a1a21ce2c..07ad613c58 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -32,6 +32,7 @@ func (ep *EvalParams) Reset() { var MakeSampleEnv = makeSampleEnv var MakeSampleEnvWithVersion = makeSampleEnvWithVersion +var MakeSampleAppEnv = makeSampleAppEnv var MakeSampleTxn = makeSampleTxn var MakeSampleTxnGroup = makeSampleTxnGroup var MakeTestProto = makeTestProto diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index b3281ed082..11025743e8 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -59,7 +59,7 @@ func TestGlobalFieldsVersions(t *testing.T) { if preLogicVersion < appsEnabledVersion { require.False(t, proto.Application) } - ep := defaultEvalParams(nil) + ep := defaultEvalParams() ep.Proto = proto ep.Ledger = ledger @@ -137,7 +137,7 @@ func TestTxnFieldVersions(t *testing.T) { if preLogicVersion < appsEnabledVersion { require.False(t, proto.Application) } - ep := defaultEvalParams(nil) + ep := defaultEvalParams() ep.Proto = proto ep.Ledger = ledger ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup) From 9e45d27dca8f598cf9b1375f2f7c09c107d01b45 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 18 May 2022 23:14:36 -0400 Subject: [PATCH 03/30] WellFormed check for Boxes Still need to think about the total size of boxes accessible to a txn, but that check must happen later, as it requires using state. Two options: A static limit on accessible stuff, or a dynamic limit on the sum of the size of boxes actually touched. Probbaly the static limit is better, since we may wish to prefetch. --- data/transactions/transaction.go | 19 ++++++--- data/transactions/transaction_test.go | 59 ++++++++++++++++++--------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 5c67f64dd2..16c46702bb 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -371,12 +371,8 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa // Ensure requested action is valid switch tx.OnCompletion { - case NoOpOC: - case OptInOC: - case CloseOutOC: - case ClearStateOC: - case UpdateApplicationOC: - case DeleteApplicationOC: + case NoOpOC, OptInOC, CloseOutOC, ClearStateOC, UpdateApplicationOC, DeleteApplicationOC: + /* ok */ default: return fmt.Errorf("invalid application OnCompletion") } @@ -464,6 +460,17 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa return fmt.Errorf("app programs too long. max total len %d bytes", pages*proto.MaxAppTotalProgramLen) } + if len(tx.Boxes) > int(proto.MaxAppBoxReferences) { + return fmt.Errorf("tx.Boxes too long, max number of box references is %d", proto.MaxAppBoxReferences) + } + + for i, br := range tx.Boxes { + // recall 0 is the current app so indexes are shifted, thus test is for greater than, not gte. + if br.Index > uint64(len(tx.ForeignApps)) { + return fmt.Errorf("tx.Boxes[%d].Index is %d. Exceeds len(tx.ForeignApps)", i, br.Index) + } + } + if tx.LocalStateSchema.NumEntries() > proto.MaxLocalSchemaEntries { return fmt.Errorf("tx.LocalStateSchema too large, max number of keys is %d", proto.MaxLocalSchemaEntries) } diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 4536fb122c..6f8d77f599 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -271,6 +271,7 @@ func TestWellFormedErrors(t *testing.T) { futureProto := config.Consensus[protocol.ConsensusFuture] protoV27 := config.Consensus[protocol.ConsensusV27] protoV28 := config.Consensus[protocol.ConsensusV28] + protoV32 := config.Consensus[protocol.ConsensusV32] addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA") require.NoError(t, err) v5 := []byte{0x05} @@ -282,7 +283,6 @@ func TestWellFormedErrors(t *testing.T) { } usecases := []struct { tx Transaction - spec SpecialAddresses proto config.ConsensusParams expectedError error }{ @@ -294,7 +294,6 @@ func TestWellFormedErrors(t *testing.T) { Fee: basics.MicroAlgos{Raw: 100}, }, }, - spec: specialAddr, proto: protoV27, expectedError: makeMinFeeErrorf("transaction had fee %d, which is less than the minimum %d", 100, curProto.MinTxnFee), }, @@ -306,7 +305,6 @@ func TestWellFormedErrors(t *testing.T) { Fee: basics.MicroAlgos{Raw: 100}, }, }, - spec: specialAddr, proto: curProto, }, { @@ -319,7 +317,6 @@ func TestWellFormedErrors(t *testing.T) { FirstValid: 105, }, }, - spec: specialAddr, proto: curProto, expectedError: fmt.Errorf("transaction invalid range (%d--%d)", 105, 100), }, @@ -337,7 +334,6 @@ func TestWellFormedErrors(t *testing.T) { ExtraProgramPages: 1, }, }, - spec: specialAddr, proto: protoV27, expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", protoV27.MaxExtraAppProgramPages), }, @@ -351,7 +347,6 @@ func TestWellFormedErrors(t *testing.T) { ClearStateProgram: []byte("Xjunk"), }, }, - spec: specialAddr, proto: protoV27, expectedError: fmt.Errorf("approval program too long. max len 1024 bytes"), }, @@ -365,7 +360,6 @@ func TestWellFormedErrors(t *testing.T) { ClearStateProgram: []byte("Xjunk"), }, }, - spec: specialAddr, proto: futureProto, }, { @@ -378,7 +372,6 @@ func TestWellFormedErrors(t *testing.T) { ClearStateProgram: []byte(strings.Repeat("X", 1025)), }, }, - spec: specialAddr, proto: futureProto, expectedError: fmt.Errorf("app programs too long. max total len 2048 bytes"), }, @@ -393,7 +386,6 @@ func TestWellFormedErrors(t *testing.T) { ExtraProgramPages: 1, }, }, - spec: specialAddr, proto: futureProto, }, { @@ -408,7 +400,6 @@ func TestWellFormedErrors(t *testing.T) { ExtraProgramPages: 1, }, }, - spec: specialAddr, proto: futureProto, expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"), }, @@ -426,7 +417,6 @@ func TestWellFormedErrors(t *testing.T) { ExtraProgramPages: 4, }, }, - spec: specialAddr, proto: futureProto, expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", futureProto.MaxExtraAppProgramPages), }, @@ -439,7 +429,6 @@ func TestWellFormedErrors(t *testing.T) { ForeignApps: []basics.AppIndex{10, 11}, }, }, - spec: specialAddr, proto: protoV27, }, { @@ -451,7 +440,6 @@ func TestWellFormedErrors(t *testing.T) { ForeignApps: []basics.AppIndex{10, 11, 12}, }, }, - spec: specialAddr, proto: protoV27, expectedError: fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is 2"), }, @@ -464,7 +452,6 @@ func TestWellFormedErrors(t *testing.T) { ForeignApps: []basics.AppIndex{10, 11, 12, 13, 14, 15, 16, 17}, }, }, - spec: specialAddr, proto: futureProto, }, { @@ -476,7 +463,6 @@ func TestWellFormedErrors(t *testing.T) { ForeignAssets: []basics.AssetIndex{14, 15, 16, 17, 18, 19, 20, 21, 22}, }, }, - spec: specialAddr, proto: futureProto, expectedError: fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is 8"), }, @@ -491,7 +477,6 @@ func TestWellFormedErrors(t *testing.T) { ForeignAssets: []basics.AssetIndex{14, 15, 16, 17}, }, }, - spec: specialAddr, proto: futureProto, expectedError: fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = 8"), }, @@ -507,7 +492,6 @@ func TestWellFormedErrors(t *testing.T) { OnCompletion: UpdateApplicationOC, }, }, - spec: specialAddr, proto: protoV28, expectedError: fmt.Errorf("app programs too long. max total len %d bytes", curProto.MaxAppProgramLen), }, @@ -523,7 +507,6 @@ func TestWellFormedErrors(t *testing.T) { OnCompletion: UpdateApplicationOC, }, }, - spec: specialAddr, proto: futureProto, }, { @@ -541,13 +524,49 @@ func TestWellFormedErrors(t *testing.T) { OnCompletion: UpdateApplicationOC, }, }, - spec: specialAddr, proto: protoV28, expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"), }, + { + tx: Transaction{ + Type: protocol.ApplicationCallTx, + Header: okHeader, + ApplicationCallTxnFields: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: "junk"}}, + }, + }, + proto: futureProto, + expectedError: fmt.Errorf("tx.Boxes[0].Index is 1. Exceeds len(tx.ForeignApps)"), + }, + { + tx: Transaction{ + Type: protocol.ApplicationCallTx, + Header: okHeader, + ApplicationCallTxnFields: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: "junk"}}, + ForeignApps: []basics.AppIndex{1}, + }, + }, + proto: futureProto, + }, + { + tx: Transaction{ + Type: protocol.ApplicationCallTx, + Header: okHeader, + ApplicationCallTxnFields: ApplicationCallTxnFields{ + ApplicationID: 1, + Boxes: []BoxRef{{Index: 1, Name: "junk"}}, + ForeignApps: []basics.AppIndex{1}, + }, + }, + proto: protoV32, + expectedError: fmt.Errorf("tx.Boxes too long, max number of box references is 0"), + }, } for _, usecase := range usecases { - err := usecase.tx.WellFormed(usecase.spec, usecase.proto) + err := usecase.tx.WellFormed(specialAddr, usecase.proto) require.Equal(t, usecase.expectedError, err) } } From 0139e545e821775cfee3f2183ea32d1e75bed23a Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Sat, 21 May 2022 21:43:06 -0400 Subject: [PATCH 04/30] Add --box argument to `goal app call` --- cmd/goal/application.go | 188 +++++++++++++++++++++++------------- cmd/goal/interact.go | 6 +- libgoal/transactions.go | 33 ++++--- shared/pingpong/accounts.go | 4 +- shared/pingpong/pingpong.go | 2 +- 5 files changed, 146 insertions(+), 87 deletions(-) diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 907a7b2910..bd6096dc3f 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -71,6 +71,7 @@ var ( // platform seems not so far-fetched? foreignApps []string foreignAssets []string + appBoxes []string // parse these as we do app args, with optional number and comma in front appStrAccounts []string appArgs []string @@ -97,8 +98,9 @@ func init() { appCmd.PersistentFlags().StringArrayVar(&appArgs, "app-arg", nil, "Args to encode for application transactions (all will be encoded to a byte slice). For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.") appCmd.PersistentFlags().StringSliceVar(&foreignApps, "foreign-app", nil, "Indexes of other apps whose global state is read in this transaction") appCmd.PersistentFlags().StringSliceVar(&foreignAssets, "foreign-asset", nil, "Indexes of assets whose parameters are read in this transaction") + appCmd.PersistentFlags().StringArrayVar(&appBoxes, "box", nil, "Boxes that may be accessed by this transaction. Use the same form as app-arg to name the box, preceded by an optional app-id and comma. No app-id indicates the box is accessible by the app being called.") appCmd.PersistentFlags().StringSliceVar(&appStrAccounts, "app-account", nil, "Accounts that may be accessed from application logic") - appCmd.PersistentFlags().StringVarP(&appInputFilename, "app-input", "i", "", "JSON file containing encoded arguments and inputs (mutually exclusive with app-arg-b64 and app-account)") + appCmd.PersistentFlags().StringVarP(&appInputFilename, "app-input", "i", "", "JSON file containing encoded arguments and inputs (mutually exclusive with app-arg, app-account, foreign-app, foreign-asset, and box)") appCmd.PersistentFlags().StringVar(&approvalProgFile, "approval-prog", "", "(Uncompiled) TEAL assembly program filename for approving/rejecting transactions") appCmd.PersistentFlags().StringVar(&clearProgFile, "clear-prog", "", "(Uncompiled) TEAL assembly program filename for updating application state when a user clears their local state") @@ -193,16 +195,59 @@ func init() { methodAppCmd.Flags().MarkHidden("app-arg") // nolint:errcheck } -type appCallArg struct { +type appCallBytes struct { Encoding string `codec:"encoding"` Value string `codec:"value"` } +func newAppCallBytes(arg string) appCallBytes { + parts := strings.SplitN(arg, ":", 2) + if len(parts) != 2 { + reportErrorf("all arguments and box names should be of the form 'encoding:value'") + } + return appCallBytes{ + Encoding: parts[0], + Value: parts[1], + } +} + type appCallInputs struct { - Accounts []string `codec:"accounts"` - ForeignApps []uint64 `codec:"foreignapps"` - ForeignAssets []uint64 `codec:"foreignassets"` - Args []appCallArg `codec:"args"` + Accounts []string `codec:"accounts"` + ForeignApps []uint64 `codec:"foreignapps"` + ForeignAssets []uint64 `codec:"foreignassets"` + Boxes []boxRef `codec:"boxes"` + Args []appCallBytes `codec:"args"` +} + +type boxRef struct { + appId uint64 `codec:"app"` + name appCallBytes `codec:"name"` +} + +// newBoxRef parses a command-line box ref, which is an optional appId, a comma, +// and then the same format as an app call arg. +func newBoxRef(arg string) boxRef { + parts := strings.SplitN(arg, ":", 2) + if len(parts) != 2 { + reportErrorf("box refs should be of the form '[,]encoding:value'") + } + encoding := parts[0] // tentative, may be , + value := parts[1] + parts = strings.SplitN(encoding, ",", 2) + appId := uint64(0) + if len(parts) == 2 { + // There was a comma in the part before the ":" + encoding = parts[1] + var err error + appId, err = strconv.ParseUint(parts[0], 10, 64) + if err != nil { + reportErrorf("Could not parse app id in box ref: %v", err) + } + } + return boxRef{ + appId: appId, + name: newAppCallBytes(encoding + ":" + value), + } } func stringsToUint64(strs []string) []uint64 { @@ -217,15 +262,15 @@ func stringsToUint64(strs []string) []uint64 { return out } -func getForeignAssets() []uint64 { - return stringsToUint64(foreignAssets) -} - -func getForeignApps() []uint64 { - return stringsToUint64(foreignApps) +func stringsToBoxRefs(strs []string) []boxRef { + out := make([]boxRef, len(strs)) + for i, brstr := range strs { + out[i] = newBoxRef(brstr) + } + return out } -func parseAppArg(arg appCallArg) (rawValue []byte, parseErr error) { +func (arg appCallBytes) raw() (rawValue []byte, parseErr error) { switch arg.Encoding { case "str", "string": rawValue = []byte(arg.Value) @@ -282,13 +327,44 @@ func parseAppArg(arg appCallArg) (rawValue []byte, parseErr error) { return } -func parseAppInputs(inputs appCallInputs) (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) { +func translateBoxRefs(input []boxRef, foreignApps []uint64) []transactions.BoxRef { + output := make([]transactions.BoxRef, len(input)) + for i, tbr := range input { + rawName, err := tbr.name.raw() + if err != nil { + reportErrorf("Could not decode box name %s: %v", tbr.name, err) + } + + index := uint64(0) + if tbr.appId != 0 { + found := false + for a, id := range foreignApps { + if tbr.appId == id { + index = uint64(a + 1) + found = true + break + } + } + if !found { + reportErrorf("Box ref with appId (%d) not in foreign-apps", tbr.appId) + } + } + output[i] = transactions.BoxRef{ + Index: index, + Name: string(rawName), + } + } + return output +} + +func parseAppInputs(inputs appCallInputs) (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) { accounts = inputs.Accounts foreignApps = inputs.ForeignApps foreignAssets = inputs.ForeignAssets + boxes = translateBoxRefs(inputs.Boxes, foreignApps) args = make([][]byte, len(inputs.Args)) for i, arg := range inputs.Args { - rawValue, err := parseAppArg(arg) + rawValue, err := arg.raw() if err != nil { reportErrorf("Could not decode input at index %d: %v", i, err) } @@ -297,7 +373,7 @@ func parseAppInputs(inputs appCallInputs) (args [][]byte, accounts []string, for return } -func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) { +func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) { var inputs appCallInputs f, err := os.Open(appInputFilename) if err != nil { @@ -313,49 +389,31 @@ func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint return parseAppInputs(inputs) } -// filterEmptyStrings filters out empty string parsed in by StringArrayVar -// this function is added to support abi argument parsing -// since parsing of `appArg` diverted from `StringSliceVar` to `StringArrayVar` -func filterEmptyStrings(strSlice []string) []string { - var newStrSlice []string - - for _, str := range strSlice { - if len(str) > 0 { - newStrSlice = append(newStrSlice, str) - } - } - return newStrSlice -} - -func getAppInputs() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) { - if (appArgs != nil || appStrAccounts != nil || foreignApps != nil) && appInputFilename != "" { - reportErrorf("Cannot specify both command-line arguments/accounts and JSON input filename") - } +func getAppInputs() (args [][]byte, accounts []string, apps []uint64, assets []uint64, boxes []transactions.BoxRef) { if appInputFilename != "" { + if appArgs != nil || appStrAccounts != nil || foreignApps != nil || foreignAssets != nil { + reportErrorf("Cannot specify both command-line arguments/resources and JSON input filename") + } return processAppInputFile() } - var encodedArgs []appCallArg + // we need to ignore empty strings from appArgs because app-arg was + // previously a StringSliceVar, which also does that, and some test depend + // on it. appArgs became `StringArrayVar` in order to support abi arguments + // which contain commas. - // we need to filter out empty strings from appArgs first, caused by change to `StringArrayVar` - newAppArgs := filterEmptyStrings(appArgs) - - for _, arg := range newAppArgs { - encodingValue := strings.SplitN(arg, ":", 2) - if len(encodingValue) != 2 { - reportErrorf("all arguments should be of the form 'encoding:value'") - } - encodedArg := appCallArg{ - Encoding: encodingValue[0], - Value: encodingValue[1], + var encodedArgs []appCallBytes + for _, arg := range appArgs { + if len(arg) > 0 { + encodedArgs = append(encodedArgs, newAppCallBytes(arg)) } - encodedArgs = append(encodedArgs, encodedArg) } inputs := appCallInputs{ Accounts: appStrAccounts, - ForeignApps: getForeignApps(), - ForeignAssets: getForeignAssets(), + ForeignApps: stringsToUint64(foreignApps), + ForeignAssets: stringsToUint64(foreignAssets), + Boxes: stringsToBoxRefs(appBoxes), Args: encodedArgs, } @@ -444,14 +502,14 @@ var createAppCmd = &cobra.Command{ // Parse transaction parameters approvalProg, clearProg := mustParseProgArgs() onCompletionEnum := mustParseOnCompletion(onCompletion) - appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() switch onCompletionEnum { case transactions.CloseOutOC, transactions.ClearStateOC: reportWarnf("'--on-completion %s' may be ill-formed for 'goal app create'", onCompletion) } - tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, extraPages) + tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, boxes, extraPages) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -524,9 +582,9 @@ var updateAppCmd = &cobra.Command{ // Parse transaction parameters approvalProg, clearProg := mustParseProgArgs() - appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() - tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, approvalProg, clearProg) + tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, approvalProg, clearProg) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -594,9 +652,9 @@ var optInAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() - tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets) + tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -664,9 +722,9 @@ var closeOutAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() - tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets) + tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -734,9 +792,9 @@ var clearAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() - tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets) + tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -804,9 +862,9 @@ var callAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() - tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets) + tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -874,9 +932,9 @@ var deleteAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() - tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets) + tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -1179,7 +1237,7 @@ var methodAppCmd = &cobra.Command{ dataDir, client := getDataDirAndClient() // Parse transaction parameters - appArgsParsed, appAccounts, foreignApps, foreignAssets := getAppInputs() + appArgsParsed, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs() if len(appArgsParsed) > 0 { reportErrorf("--arg and --app-arg are mutually exclusive, do not use --app-arg") } @@ -1296,7 +1354,7 @@ var methodAppCmd = &cobra.Command{ } appCallTxn, err := client.MakeUnsignedApplicationCallTx( - appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets, + appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets, boxes, onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, extraPages) if err != nil { diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go index 6b157a50da..8677f4738d 100644 --- a/cmd/goal/interact.go +++ b/cmd/goal/interact.go @@ -512,7 +512,7 @@ var appExecuteCmd = &cobra.Command{ var inputs appCallInputs for _, arg := range proc.Args { - var callArg appCallArg + var callArg appCallBytes callArg.Encoding = arg.Kind if !procFlags.Changed(arg.Name) && arg.Default != "" { @@ -564,7 +564,7 @@ var appExecuteCmd = &cobra.Command{ appArgs := make([][]byte, len(inputs.Args)) for i, arg := range inputs.Args { - rawValue, err := parseAppArg(arg) + rawValue, err := arg.raw() if err != nil { reportErrorf("Could not parse argument corresponding to '%s': %v", proc.Args[i].Name, err) } @@ -585,7 +585,7 @@ var appExecuteCmd = &cobra.Command{ localSchema = header.Query.Local.ToStateSchema() globalSchema = header.Query.Global.ToStateSchema() } - tx, err := client.MakeUnsignedApplicationCallTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, onCompletion, approvalProg, clearProg, globalSchema, localSchema, 0) + tx, err := client.MakeUnsignedApplicationCallTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, nil, onCompletion, approvalProg, clearProg, globalSchema, localSchema, 0) if err != nil { reportErrorf("Cannot create application txn: %v", err) } diff --git a/libgoal/transactions.go b/libgoal/transactions.go index bf704cc9ef..829d69d65f 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -21,7 +21,7 @@ import ( "fmt" "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" + v1 "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" @@ -410,50 +410,50 @@ func (c *Client) FillUnsignedTxTemplate(sender string, firstValid, lastValid, fe } // MakeUnsignedAppCreateTx makes a transaction for creating an application -func (c *Client) MakeUnsignedAppCreateTx(onComplete transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, extrapages uint32) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(0, appArgs, accounts, foreignApps, foreignAssets, onComplete, approvalProg, clearProg, globalSchema, localSchema, extrapages) +func (c *Client) MakeUnsignedAppCreateTx(onComplete transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, extrapages uint32) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(0, appArgs, accounts, foreignApps, foreignAssets, boxes, onComplete, approvalProg, clearProg, globalSchema, localSchema, extrapages) } // MakeUnsignedAppUpdateTx makes a transaction for updating an application's programs -func (c *Client) MakeUnsignedAppUpdateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, approvalProg []byte, clearProg []byte) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, transactions.UpdateApplicationOC, approvalProg, clearProg, emptySchema, emptySchema, 0) +func (c *Client) MakeUnsignedAppUpdateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, approvalProg []byte, clearProg []byte) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.UpdateApplicationOC, approvalProg, clearProg, emptySchema, emptySchema, 0) } // MakeUnsignedAppDeleteTx makes a transaction for deleting an application -func (c *Client) MakeUnsignedAppDeleteTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, transactions.DeleteApplicationOC, nil, nil, emptySchema, emptySchema, 0) +func (c *Client) MakeUnsignedAppDeleteTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.DeleteApplicationOC, nil, nil, emptySchema, emptySchema, 0) } // MakeUnsignedAppOptInTx makes a transaction for opting in to (allocating // some account-specific state for) an application -func (c *Client) MakeUnsignedAppOptInTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, transactions.OptInOC, nil, nil, emptySchema, emptySchema, 0) +func (c *Client) MakeUnsignedAppOptInTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.OptInOC, nil, nil, emptySchema, emptySchema, 0) } // MakeUnsignedAppCloseOutTx makes a transaction for closing out of // (deallocating all account-specific state for) an application -func (c *Client) MakeUnsignedAppCloseOutTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, transactions.CloseOutOC, nil, nil, emptySchema, emptySchema, 0) +func (c *Client) MakeUnsignedAppCloseOutTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.CloseOutOC, nil, nil, emptySchema, emptySchema, 0) } // MakeUnsignedAppClearStateTx makes a transaction for clearing out all // account-specific state for an application. It may not be rejected by the // application's logic. -func (c *Client) MakeUnsignedAppClearStateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, transactions.ClearStateOC, nil, nil, emptySchema, emptySchema, 0) +func (c *Client) MakeUnsignedAppClearStateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.ClearStateOC, nil, nil, emptySchema, emptySchema, 0) } // MakeUnsignedAppNoOpTx makes a transaction for interacting with an existing // application, potentially updating any account-specific local state and // global state associated with it. -func (c *Client) MakeUnsignedAppNoOpTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) (tx transactions.Transaction, err error) { - return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, transactions.NoOpOC, nil, nil, emptySchema, emptySchema, 0) +func (c *Client) MakeUnsignedAppNoOpTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) { + return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.NoOpOC, nil, nil, emptySchema, emptySchema, 0) } // MakeUnsignedApplicationCallTx is a helper for the above ApplicationCall // transaction constructors. A fully custom ApplicationCall transaction may // be constructed using this method. -func (c *Client) MakeUnsignedApplicationCallTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, onCompletion transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, extrapages uint32) (tx transactions.Transaction, err error) { +func (c *Client) MakeUnsignedApplicationCallTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, onCompletion transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, extrapages uint32) (tx transactions.Transaction, err error) { tx.Type = protocol.ApplicationCallTx tx.ApplicationID = basics.AppIndex(appIdx) tx.OnCompletion = onCompletion @@ -466,6 +466,7 @@ func (c *Client) MakeUnsignedApplicationCallTx(appIdx uint64, appArgs [][]byte, tx.ForeignApps = parseTxnForeignApps(foreignApps) tx.ForeignAssets = parseTxnForeignAssets(foreignAssets) + tx.Boxes = boxes tx.ApprovalProgram = approvalProg tx.ClearStateProgram = clearProg tx.LocalStateSchema = localSchema diff --git a/shared/pingpong/accounts.go b/shared/pingpong/accounts.go index fa8cfecdf0..62f51353c2 100644 --- a/shared/pingpong/accounts.go +++ b/shared/pingpong/accounts.go @@ -725,7 +725,7 @@ func (pps *WorkerState) prepareApps(accounts map[string]*pingPongAccount, client for i := begin; i < end; i++ { var tx transactions.Transaction - tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, prog, prog, globSchema, locSchema, nil, nil, nil, nil, 0) + tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, prog, prog, globSchema, locSchema, nil, nil, nil, nil, nil, 0) if err != nil { fmt.Printf("Cannot create app txn\n") panic(err) @@ -804,7 +804,7 @@ func (pps *WorkerState) prepareApps(accounts map[string]*pingPongAccount, client j := permAppIndices[i] aidx := aidxs[j] var tx transactions.Transaction - tx, err = client.MakeUnsignedAppOptInTx(aidx, nil, nil, nil, nil) + tx, err = client.MakeUnsignedAppOptInTx(aidx, nil, nil, nil, nil, nil) if err != nil { fmt.Printf("Cannot create app txn\n") panic(err) diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go index 5fcde03737..38dae5db02 100644 --- a/shared/pingpong/pingpong.go +++ b/shared/pingpong/pingpong.go @@ -998,7 +998,7 @@ func (pps *WorkerState) constructTxn(from, to string, fee, amt, aidx uint64, cli } accounts = accounts[1:] } - txn, err = client.MakeUnsignedAppNoOpTx(aidx, nil, accounts, nil, nil) + txn, err = client.MakeUnsignedAppNoOpTx(aidx, nil, accounts, nil, nil, nil) if err != nil { return } From efe16ed378b59ecadcdc4d3742ace0c51fdee3da Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 23 May 2022 14:26:38 -0400 Subject: [PATCH 05/30] Use 0 if needed as box ref index when caller specifies the app-id. --- cmd/goal/application.go | 8 ++++++++ data/transactions/logic/evalAppTxn_test.go | 4 ++-- ledger/acctupdates.go | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd/goal/application.go b/cmd/goal/application.go index bd6096dc3f..4b7600f6a8 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -345,6 +345,14 @@ func translateBoxRefs(input []boxRef, foreignApps []uint64) []transactions.BoxRe break } } + // Check appIdx after the foreignApps check. If the user actually + // put the appIdx in foreignApps, and then used the appIdx here + // (rather than 0), then maybe they really want to use it in the + // transaction as the full number. Though it's hard to see why. + if !found && tbr.appId == appIdx { + index = 0 + found = true + } if !found { reportErrorf("Box ref with appId (%d) not in foreign-apps", tbr.appId) } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 45e2f6652e..7616bc86d6 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -1704,7 +1704,7 @@ int 1 for _, unified := range []bool{true, false} { t.Run(fmt.Sprintf("unified=%t", unified), func(t *testing.T) { - t.Parallel() + // t.Parallel() NO! unified variable is actually shared ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified @@ -2170,7 +2170,7 @@ func TestInnerTxIDCaching(t *testing.T) { for _, unified := range []bool{true, false} { t.Run(fmt.Sprintf("unified=%t", unified), func(t *testing.T) { - t.Parallel() + // t.Parallel() NO! unified variable is actually shared ep, parentTx, ledger := MakeSampleEnv() ep.Proto.UnifyInnerTxIDs = unified diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 41e111fd1c..3203467573 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -359,7 +359,7 @@ func (au *accountUpdates) LookupKv(rnd basics.Round, key string) (*string, error // update the rnd so that it would point to the end of the known delta range. // ( that would give us the best validity range ) rnd = currentDbRound + basics.Round(currentDeltaLen) - // TODO: THIS IS POINTLESS FOT KV's current interface. I don't know + // TODO: THIS IS POINTLESS FOR KV's current interface. I don't know // how the validity window is used yet. -jj } From 9fcc59ab33b55a4e40c6e56dd3ecc59e06d65299 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 23 May 2022 19:11:56 -0400 Subject: [PATCH 06/30] Provide sort key for the new kvstore table --- scripts/dump_genesis.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/dump_genesis.sh b/scripts/dump_genesis.sh index 85615de5b4..347e8d7c8a 100755 --- a/scripts/dump_genesis.sh +++ b/scripts/dump_genesis.sh @@ -57,15 +57,18 @@ for LEDGER in $LEDGERS; do resources) SORT=addrid ;; + kvstore) + SORT=key + ;; *) echo "Unknown table $T" >&2 exit 1 ;; esac - echo ".schema $T" | sqlite3 $LEDGER + echo ".schema $T" | sqlite3 "$LEDGER" ( echo .headers on; - echo .mode insert $T; - echo "SELECT * FROM $T ORDER BY $SORT;" ) | sqlite3 $LEDGER + echo .mode insert "$T"; + echo "SELECT * FROM $T ORDER BY $SORT;" ) | sqlite3 "$LEDGER" done done From b142973a03b35b3255ece6cf726711ee8f9dcd0e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 10:45:44 -0400 Subject: [PATCH 07/30] make tests obey the box ref rules --- cmd/goal/application.go | 16 ++++----- data/transactions/logic/evalStateful_test.go | 10 +++--- ledger/internal/boxtxn_test.go | 35 +++++++++++++------ .../features/transactions/accountv2_test.go | 6 ++-- .../features/transactions/app_pages_test.go | 10 +++--- .../features/transactions/application_test.go | 2 +- 6 files changed, 47 insertions(+), 32 deletions(-) diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 4b7600f6a8..c29034e119 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -220,7 +220,7 @@ type appCallInputs struct { } type boxRef struct { - appId uint64 `codec:"app"` + appID uint64 `codec:"app"` name appCallBytes `codec:"name"` } @@ -234,18 +234,18 @@ func newBoxRef(arg string) boxRef { encoding := parts[0] // tentative, may be , value := parts[1] parts = strings.SplitN(encoding, ",", 2) - appId := uint64(0) + appID := uint64(0) if len(parts) == 2 { // There was a comma in the part before the ":" encoding = parts[1] var err error - appId, err = strconv.ParseUint(parts[0], 10, 64) + appID, err = strconv.ParseUint(parts[0], 10, 64) if err != nil { reportErrorf("Could not parse app id in box ref: %v", err) } } return boxRef{ - appId: appId, + appID: appID, name: newAppCallBytes(encoding + ":" + value), } } @@ -336,10 +336,10 @@ func translateBoxRefs(input []boxRef, foreignApps []uint64) []transactions.BoxRe } index := uint64(0) - if tbr.appId != 0 { + if tbr.appID != 0 { found := false for a, id := range foreignApps { - if tbr.appId == id { + if tbr.appID == id { index = uint64(a + 1) found = true break @@ -349,12 +349,12 @@ func translateBoxRefs(input []boxRef, foreignApps []uint64) []transactions.BoxRe // put the appIdx in foreignApps, and then used the appIdx here // (rather than 0), then maybe they really want to use it in the // transaction as the full number. Though it's hard to see why. - if !found && tbr.appId == appIdx { + if !found && tbr.appID == appIdx { index = 0 found = true } if !found { - reportErrorf("Box ref with appId (%d) not in foreign-apps", tbr.appId) + reportErrorf("Box ref with appId (%d) not in foreign-apps", tbr.appID) } } output[i] = transactions.BoxRef{ diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 8b1c674632..4b5c843dba 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -376,15 +376,15 @@ func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ... require.Equal(t, len(programs), len(ep.TxnGroup)) for i := range ep.TxnGroup { if programs[i] != nil { - appId := ep.TxnGroup[i].Txn.ApplicationID - if appId == 0 { - appId = basics.AppIndex(888) + appID := ep.TxnGroup[i].Txn.ApplicationID + if appID == 0 { + appID = basics.AppIndex(888) } if len(expected) > 0 && expected[0].l == i { - testAppFull(t, programs[i], i, appId, ep, expected[0].s) + testAppFull(t, programs[i], i, appID, ep, expected[0].s) break // Stop after first failure } else { - testAppFull(t, programs[i], i, appId, ep) + testAppFull(t, programs[i], i, appID, ep) } } } diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go index aef62d4d75..9920322d3c 100644 --- a/ledger/internal/boxtxn_test.go +++ b/ledger/internal/boxtxn_test.go @@ -18,14 +18,15 @@ package internal_test import ( "bytes" - "fmt" "testing" "time" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" ) var appSource = main(` @@ -104,27 +105,38 @@ func TestBoxCreate(t *testing.T) { } adam := call.Args("create", "adam") + dl.txn(adam, "invalid Box reference adam") + adam.Boxes = []transactions.BoxRef{{Index: 0, Name: "adam"}} dl.txn(adam) dl.txn(adam.Args("check", "adam", "\x00\x00")) dl.txgroup("exists", adam.Noted("one"), adam.Noted("two")) bobo := call.Args("create", "bobo") + dl.txn(bobo, "invalid Box reference bobo") + bobo.Boxes = []transactions.BoxRef{{Index: 0, Name: "bobo"}} dl.txn(bobo) dl.txgroup("exists", bobo.Noted("one"), bobo.Noted("two")) dl.beginBlock() chaz := call.Args("create", "chaz") + chaz.Boxes = []transactions.BoxRef{{Index: 0, Name: "chaz"}} dl.txn(chaz) dl.txn(chaz.Noted("again"), "exists") dl.endBlock() // new block dl.txn(chaz.Noted("again"), "exists") - dl.txn(call.Args("create", "dogg"), "below min") - dl.txn(call.Args("delete", "chaz")) - dl.txn(call.Args("delete", "chaz"), "does not exist") - dl.txn(call.Args("create", "dogg")) - dl.txn(call.Args("delete", "bobo")) + dogg := call.Args("create", "dogg") + dogg.Boxes = []transactions.BoxRef{{Index: 0, Name: "dogg"}} + dl.txn(dogg, "below min") + dl.txn(chaz.Args("delete", "chaz")) + dl.txn(chaz.Args("delete", "chaz").Noted("again"), "does not exist") + dl.txn(dogg) + dl.txn(bobo.Args("delete", "bobo")) + + // empty name is legal empty := call.Args("create", "") + dl.txn(empty, "invalid Box reference") + empty.Boxes = []transactions.BoxRef{{}} dl.txn(empty) }) @@ -151,6 +163,7 @@ func TestBoxRW(t *testing.T) { Type: "appl", Sender: addrs[0], ApplicationID: appIndex, + Boxes: []transactions.BoxRef{{Index: 0, Name: "x"}}, } dl.txn(call.Args("create", "x", "\x10")) // 16 @@ -179,9 +192,11 @@ func TestBoxRW(t *testing.T) { time.Sleep(100 * time.Millisecond) // give commit time to run, and prune au caches dl.fullBlock(call.Args("check", "x", "ABCDEFGH")) - fmt.Printf("LOG %s\n", bufNewLogger.String()) - - dl.txn(call.Args("create", "yy")) - dl.txn(call.Args("create", "zzz")) + dl.txn(call.Args("create", "yy"), "invalid Box reference yy") + withBr := call.Args("create", "yy") + withBr.Boxes = append(withBr.Boxes, transactions.BoxRef{Index: 1, Name: "yy"}) + require.Error(dl.t, withBr.Txn().WellFormed(transactions.SpecialAddresses{}, dl.generator.GenesisProto())) + withBr.Boxes[1].Index = 0 + dl.txn(withBr) }) } diff --git a/test/e2e-go/features/transactions/accountv2_test.go b/test/e2e-go/features/transactions/accountv2_test.go index 52b39c9eed..c689dfb553 100644 --- a/test/e2e-go/features/transactions/accountv2_test.go +++ b/test/e2e-go/features/transactions/accountv2_test.go @@ -159,7 +159,7 @@ int 1 // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) @@ -214,7 +214,7 @@ int 1 checkEvalDelta(t, &client, txnRound, txnRound+1, 1, 1) // call the app - tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil) + tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) @@ -293,7 +293,7 @@ int 1 a.Equal(creator, app.Params.Creator) // call the app - tx, err = client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil) + tx, err = client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) diff --git a/test/e2e-go/features/transactions/app_pages_test.go b/test/e2e-go/features/transactions/app_pages_test.go index 5ab2e4ad15..31ea8168c6 100644 --- a/test/e2e-go/features/transactions/app_pages_test.go +++ b/test/e2e-go/features/transactions/app_pages_test.go @@ -89,7 +89,7 @@ return // create app 1 with 1 extra page app1ExtraPages := uint32(1) - tx, err := client.MakeUnsignedAppCreateTx(transactions.NoOpOC, smallProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, app1ExtraPages) + tx, err := client.MakeUnsignedAppCreateTx(transactions.NoOpOC, smallProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, nil, app1ExtraPages) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -110,7 +110,7 @@ return a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages)) // update app 1 and ensure the extra page still works - tx, err = client.MakeUnsignedAppUpdateTx(app1ID, nil, nil, nil, nil, bigProgram, smallProgram) + tx, err = client.MakeUnsignedAppUpdateTx(app1ID, nil, nil, nil, nil, nil, bigProgram, smallProgram) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -130,7 +130,7 @@ return // create app 2 with 2 extra pages app2ExtraPages := uint32(2) - tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, bigProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, app2ExtraPages) + tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, bigProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, nil, app2ExtraPages) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -151,7 +151,7 @@ return a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages+app2ExtraPages)) // delete app 1 - tx, err = client.MakeUnsignedAppDeleteTx(app1ID, nil, nil, nil, nil) + tx, err = client.MakeUnsignedAppDeleteTx(app1ID, nil, nil, nil, nil, nil) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) @@ -170,7 +170,7 @@ return a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app2ExtraPages)) // delete app 2 - tx, err = client.MakeUnsignedAppDeleteTx(app2ID, nil, nil, nil, nil) + tx, err = client.MakeUnsignedAppDeleteTx(app2ID, nil, nil, nil, nil, nil) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx) a.NoError(err) diff --git a/test/e2e-go/features/transactions/application_test.go b/test/e2e-go/features/transactions/application_test.go index c9a4ac1315..8b851af0e6 100644 --- a/test/e2e-go/features/transactions/application_test.go +++ b/test/e2e-go/features/transactions/application_test.go @@ -97,7 +97,7 @@ log // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) From 9d78c31fa4c2c9b7cea80681b565d4370183e1d1 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 11:34:54 -0400 Subject: [PATCH 08/30] simplify closing prepared statements. now matches how queries are done, and the "shape" of teh type defintion --- ledger/accountdb.go | 51 ++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/ledger/accountdb.go b/ledger/accountdb.go index f2509489b8..5ce958031b 100644 --- a/ledger/accountdb.go +++ b/ledger/accountdb.go @@ -2340,46 +2340,21 @@ type accountsSQLWriter struct { } func (w *accountsSQLWriter) close() { - if w.deleteByRowIDStmt != nil { - w.deleteByRowIDStmt.Close() - w.deleteByRowIDStmt = nil + // Formatted to match the type definition above + preparedStmts := []**sql.Stmt{ + &w.insertCreatableIdxStmt, &w.deleteCreatableIdxStmt, + &w.deleteByRowIDStmt, &w.insertStmt, &w.updateStmt, + &w.deleteResourceStmt, &w.insertResourceStmt, &w.updateResourceStmt, + &w.deleteKvPairStmt, &w.upsertKvPairStmt, } - if w.insertStmt != nil { - w.insertStmt.Close() - w.insertStmt = nil - } - if w.updateStmt != nil { - w.updateStmt.Close() - w.updateStmt = nil - } - if w.deleteResourceStmt != nil { - w.deleteResourceStmt.Close() - w.deleteResourceStmt = nil - } - if w.insertResourceStmt != nil { - w.insertResourceStmt.Close() - w.insertResourceStmt = nil - } - if w.updateResourceStmt != nil { - w.updateResourceStmt.Close() - w.updateResourceStmt = nil - } - if w.deleteKvPairStmt != nil { - w.deleteKvPairStmt.Close() - w.deleteKvPairStmt = nil - } - if w.upsertKvPairStmt != nil { - w.upsertKvPairStmt.Close() - w.upsertKvPairStmt = nil - } - if w.insertCreatableIdxStmt != nil { - w.insertCreatableIdxStmt.Close() - w.insertCreatableIdxStmt = nil - } - if w.deleteCreatableIdxStmt != nil { - w.deleteCreatableIdxStmt.Close() - w.deleteCreatableIdxStmt = nil + + for _, stmt := range preparedStmts { + if (*stmt) != nil { + (*stmt).Close() + *stmt = nil + } } + } func makeAccountsSQLWriter(tx *sql.Tx, hasAccounts, hasResources, hasKvPairs, hasCreatables bool) (w *accountsSQLWriter, err error) { From 1fa7e55b9969d96b9b7f3abf2f9349b7bb39c522 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 11:34:54 -0400 Subject: [PATCH 09/30] test updates --- test/e2e-go/features/accountPerf/sixMillion_test.go | 6 +++--- test/e2e-go/restAPI/restClient_test.go | 4 ++-- test/e2e-go/upgrades/application_support_test.go | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/e2e-go/features/accountPerf/sixMillion_test.go b/test/e2e-go/features/accountPerf/sixMillion_test.go index 679d2728a2..ca12e1f55c 100644 --- a/test/e2e-go/features/accountPerf/sixMillion_test.go +++ b/test/e2e-go/features/accountPerf/sixMillion_test.go @@ -1155,7 +1155,7 @@ int 1 // create the app appTx, err = client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) require.NoError(t, err) note := make([]byte, 8) @@ -1182,7 +1182,7 @@ func makeOptInAppTransaction( tLife uint64, genesisHash crypto.Digest) (appTx transactions.Transaction) { - appTx, err := client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil) + appTx, err := client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil) require.NoError(t, err) appTx.Header = transactions.Header{ @@ -1288,7 +1288,7 @@ func callAppTransaction( tLife uint64, genesisHash crypto.Digest) (appTx transactions.Transaction) { - appTx, err := client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil) + appTx, err := client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil) require.NoError(t, err) appTx.Header = transactions.Header{ diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index dee64d399e..90eaff2705 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -998,7 +998,7 @@ return lc := basics.StateSchema{} // create app - appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0) + appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0) a.NoError(err) appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn) a.NoError(err) @@ -1022,7 +1022,7 @@ return a.NoError(err) // call app, which will issue an ASA create inner txn - appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(uint64(createdAppID), nil, nil, nil, nil) + appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(uint64(createdAppID), nil, nil, nil, nil, nil) a.NoError(err) appCallTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn) a.NoError(err) diff --git a/test/e2e-go/upgrades/application_support_test.go b/test/e2e-go/upgrades/application_support_test.go index 3667cc3cef..9d289f4b33 100644 --- a/test/e2e-go/upgrades/application_support_test.go +++ b/test/e2e-go/upgrades/application_support_test.go @@ -149,7 +149,7 @@ int 1 // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) @@ -236,7 +236,7 @@ int 1 a.Equal(uint64(1), value.Uint) // call the app - tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil) + tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) @@ -395,7 +395,7 @@ int 1 // create the app tx, err := client.MakeUnsignedAppCreateTx( - transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, 0) + transactions.OptInOC, approvalOps.Program, clearstateOps.Program, schema, schema, nil, nil, nil, nil, nil, 0) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, round, round+primaryNodeUnupgradedProtocol.DefaultUpgradeWaitRounds, fee, tx) a.NoError(err) @@ -491,7 +491,7 @@ int 1 a.Equal(uint64(1), value.Uint) // call the app - tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil) + tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil) a.NoError(err) tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx) a.NoError(err) From 4d24bf47112bf473c4faa96e0ad9e4b13fc8917b Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 14:40:26 -0400 Subject: [PATCH 10/30] Add required (but useless) allocbound. --- data/basics/userBalance.go | 10 +++++++++- data/basics/userBalance_test.go | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index a1a554bbb3..64eb3d288c 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -68,6 +68,14 @@ const ( // config.Consensus[protocol.ConsensusCurrentVersion].MaxLocalSchemaEntries and // config.Consensus[protocol.ConsensusCurrentVersion].MaxGlobalSchemaEntries EncodedMaxKeyValueEntries = 1024 + + // This number is not really meaningful because Boxes were introduced after + // the separation of ledgercore.AccountData and basics.AccountData. The + // constant exists only to satisfy the need for an allocbound on + // basics.AccountData, even though such structures are not used in protocol + // messages or serialized to the DB. It's made particularly small to ferret + // out any possible dependencies. + EncodedMaxBoxes = 4. ) func (s Status) String() string { @@ -225,7 +233,7 @@ type AccountData struct { TotalExtraAppPages uint32 `codec:"teap"` // Boxes is all of the boxes associated with this account. (This account must be an app account) - Boxes map[string][]byte + Boxes map[string][]byte `codec:"apbx,allocbound=EncodedMaxBoxes"` // TotalBoxBytes stores the sum of all len(keys) and len(values) of Boxes TotalBoxBytes uint64 diff --git a/data/basics/userBalance_test.go b/data/basics/userBalance_test.go index ddaf593152..d19388f742 100644 --- a/data/basics/userBalance_test.go +++ b/data/basics/userBalance_test.go @@ -248,6 +248,7 @@ func TestEncodedAccountAllocationBounds(t *testing.T) { if proto.MaxGlobalSchemaEntries > EncodedMaxKeyValueEntries { require.Failf(t, "proto.MaxGlobalSchemaEntries > encodedMaxKeyValueEntries", "protocol version = %s", protoVer) } + // There is no protocol limit to the number of Boxes per account, so that allocbound is not checked. } } From 1aa9d90d674bb52eca4a1168565b438032c3d2bd Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 16:32:28 -0400 Subject: [PATCH 11/30] Make "0 index" box refs on app calls available in group Gooing with an idea from Michael to help regularize how to make box refs available to the entire group if it is an app create, and a "0 index" box ref. At ep creation time, we don't know the appId, so it can't go into ep.available.boxes. But when we begin evaluating the txn, we do know the appID. So add it then. Doing the addition at the start of EvalContract, rather than in RecordAD allows the creation call itself to access the box - though that is incredibly unlikely to be useful, it seemed proper. (see boxtxn_test.go for explanation of how to take advantage of this.) --- data/transactions/logic/eval.go | 23 ++++-- data/transactions/logic/eval_test.go | 35 ++++++++- ledger/internal/boxtxn_test.go | 104 ++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 6 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 7af898b9ba..bc6b74f793 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -227,8 +227,12 @@ type LedgerForLogic interface { // apps, assets, and boxes that are available to a transaction, outside the // direct foreign array mechanism. type resources struct { - asas []basics.AssetIndex - apps []basics.AppIndex + asas []basics.AssetIndex + apps []basics.AppIndex + + // boxes are all of the top-level box refs from the txgroup. Most are added + // during NewEvalParams(). refs using 0 on an appl create are resolved and + // added when the appl executes. boxes map[basics.AppIndex][]string } @@ -312,6 +316,7 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens if br.Index == 0 { // 0 is the "current app". Ignore if this is a create, else use ApplicationID if tx.Txn.ApplicationID == 0 { + // When the create actually happens, and we learn the appID, we'll make it _available_. continue } app = tx.Txn.ApplicationID @@ -322,8 +327,7 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens app = tx.Txn.ForeignApps[br.Index-1] // shift for the 0=this convention } appBoxes := allBoxes[app] - appBoxes = append(appBoxes, br.Name) - allBoxes[app] = appBoxes + allBoxes[app] = append(appBoxes, br.Name) } } } @@ -655,6 +659,17 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam } } + // If this is a creation, make any "0 index" box refs available now that we + // have an appID. + if cx.txn.Txn.ApplicationID == 0 { + for _, br := range cx.txn.Txn.Boxes { + if br.Index == 0 { + appBoxes := cx.EvalParams.available.boxes[cx.appID] + cx.EvalParams.available.boxes[cx.appID] = append(appBoxes, br.Name) + } + } + } + if cx.Trace != nil && cx.caller != nil { fmt.Fprintf(cx.Trace, "--- enter %d %s %v\n", aid, cx.txn.Txn.OnCompletion, cx.txn.Txn.ApplicationArgs) } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index ba325e9847..9c17565eeb 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -156,7 +156,8 @@ func (ep *EvalParams) reset() { if ep.available != nil { ep.available.apps = nil ep.available.asas = nil - // leave ep.available.boxes alone, as it is not affected by evaluations + // reinitialize boxes because evaluation can add box refs for app creates. + ep.available.boxes = ep.recalcBoxRefs() } ep.appAddrCache = make(map[basics.AppIndex]basics.Address) if ep.Trace != nil { @@ -164,6 +165,38 @@ func (ep *EvalParams) reset() { } } +// reinitializeBoxRefs is nearly a copy of what happens in NewEvalParams, but is +// in _test.go because it never needs to happen in normal operation. +func (ep *EvalParams) recalcBoxRefs() map[basics.AppIndex][]string { + var allBoxes map[basics.AppIndex][]string + for _, tx := range ep.TxnGroup { + if tx.Txn.Type == protocol.ApplicationCallTx { + if allBoxes == nil && len(tx.Txn.Boxes) > 0 { + allBoxes = make(map[basics.AppIndex][]string) + } + for _, br := range tx.Txn.Boxes { + var app basics.AppIndex + if br.Index == 0 { + // 0 is the "current app". Ignore if this is a create, else use ApplicationID + if tx.Txn.ApplicationID == 0 { + // When the create actually happens, and we learn the appID, we'll make it _available_. + continue + } + app = tx.Txn.ApplicationID + } else { + // Bounds check will already have been done by + // WellFormed. For testing purposes, it's better to panic + // now than after returning a nil. + app = tx.Txn.ForeignApps[br.Index-1] // shift for the 0=this convention + } + appBoxes := allBoxes[app] + allBoxes[app] = append(appBoxes, br.Name) + } + } + } + return allBoxes +} + func TestTooManyArgs(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go index 9920322d3c..0cba0bad0a 100644 --- a/ledger/internal/boxtxn_test.go +++ b/ledger/internal/boxtxn_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" ledgertesting "github.com/algorand/go-algorand/ledger/testing" @@ -138,9 +139,110 @@ func TestBoxCreate(t *testing.T) { dl.txn(empty, "invalid Box reference") empty.Boxes = []transactions.BoxRef{{}} dl.txn(empty) - }) +} + +func TestBoxCreateAvailability(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + // boxes begin in 33 + testConsensusRange(t, 33, 0, func(t *testing.T, ver int) { + dl := NewDoubleLedger(t, genBalances, consensusByNumber[ver]) + defer dl.Close() + + accessInCreate := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: 0, // This is a create + Boxes: []transactions.BoxRef{{Index: 0, Name: "hello"}}, + ApprovalProgram: ` + int 10 + byte "hello" + box_create + int 1 +`, + } + + // We know box_create worked because we finished and checked MBR + dl.txn(&accessInCreate, "balance 0 below min") + + // But let's fund it and be sure. This is "psychic". We're going to fund + // the app address that we know the app will get. So this is a nice + // test, but unrealistic way to actual create a box. + psychic := basics.AppIndex(2) + dl.txn(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: psychic.Address(), + Amount: 108501, + }) + dl.txn(&accessInCreate) + // Now, a more realistic, though tricky, way to get a box created during + // the app's first txgroup in existence is to create it in tx0, and then + // in tx1 fund it using an inner tx, then invoke it with an inner + // transaction. During that invocation, the app will have access to the + // boxes supplied as "0 refs", since they were resolved to the app ID + // during creation. + + accessWhenCalled := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: 0, // This is a create + Boxes: []transactions.BoxRef{{Index: 0, Name: "hello"}}, + // Note that main() wraps the program so it does not run at creation time. + ApprovalProgram: main(` + int 10 + byte "hello" + box_create + byte "we did it" + log +`), + } + + trampoline := dl.fundedApp(addrs[0], 1_000_000, main(` + // Fund the app created in the txn behind me. + txn GroupIndex + int 1 + - + gtxns CreatedApplicationID + dup // copy for use when calling + dup // test copy + assert + app_params_get AppAddress + assert + + itxn_begin + itxn_field Receiver + int 500000 + itxn_field Amount + int pay + itxn_field TypeEnum + itxn_submit + + // Now invoke it, so it can intialize (and create the "hello" box) + itxn_begin + itxn_field ApplicationID + int appl + itxn_field TypeEnum + itxn_submit +`)) + + call := txntest.Txn{ + Sender: addrs[0], + Type: "appl", + ApplicationID: trampoline, + } + + dl.beginBlock() + dl.txgroup("", &accessWhenCalled, &call) + vb := dl.endBlock() + + // Make sure that we actually did it. + require.Equal(t, "we did it", vb.Block().Payset[1].ApplyData.EvalDelta.InnerTxns[1].EvalDelta.Logs[0]) + }) } // TestBoxRW tests reading writing boxes in consecutive transactions From 4e57665f79edfc3ef3f4e64a617c26270120c522 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 21:45:59 -0400 Subject: [PATCH 12/30] lints --- data/basics/userBalance.go | 4 ++-- data/transactions/logic/eval_test.go | 2 +- ledger/internal/applications.go | 10 ++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 64eb3d288c..7c8ddecf67 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -69,8 +69,8 @@ const ( // config.Consensus[protocol.ConsensusCurrentVersion].MaxGlobalSchemaEntries EncodedMaxKeyValueEntries = 1024 - // This number is not really meaningful because Boxes were introduced after - // the separation of ledgercore.AccountData and basics.AccountData. The + // EncodedMaxBoxes is not really meaningful because Boxes were introduced + // after the separation of AccountData in ledgercore and basics. The // constant exists only to satisfy the need for an allocbound on // basics.AccountData, even though such structures are not used in protocol // messages or serialized to the DB. It's made particularly small to ferret diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 9c17565eeb..86b7a6c80e 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -165,7 +165,7 @@ func (ep *EvalParams) reset() { } } -// reinitializeBoxRefs is nearly a copy of what happens in NewEvalParams, but is +// recalcBoxRefs is nearly a copy of what happens in NewEvalParams, but is // in _test.go because it never needs to happen in normal operation. func (ep *EvalParams) recalcBoxRefs() map[basics.AppIndex][]string { var allBoxes map[basics.AppIndex][]string diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index fb71220d73..4c707b6280 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -231,7 +231,10 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) } record.TotalBoxes = basics.AddSaturate(record.TotalBoxes, 1) record.TotalBoxBytes = basics.AddSaturate(record.TotalBoxBytes, uint64(len(key))+size) - cs.Put(appIdx.Address(), record) + err = cs.Put(appIdx.Address(), record) + if err != nil { + return err + } value := string(make([]byte, size)) return cs.kvPut(fullKey, value) @@ -282,7 +285,10 @@ func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string) error { } record.TotalBoxes = basics.SubSaturate(record.TotalBoxes, 1) record.TotalBoxBytes = basics.SubSaturate(record.TotalBoxBytes, uint64(len(key)+len(value))) - cs.Put(appIdx.Address(), record) + err = cs.Put(appIdx.Address(), record) + if err != nil { + return err + } return cs.kvDel(fullKey) } From 2b32b6a75ad7efd8cbd70d64ccc54dc0360c2235 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 22:02:12 -0400 Subject: [PATCH 13/30] Fixup after rebase master --- data/transactions/logic/evalCrypto_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go index a66420958d..29912566a2 100644 --- a/data/transactions/logic/evalCrypto_test.go +++ b/data/transactions/logic/evalCrypto_test.go @@ -822,7 +822,7 @@ func benchmarkBn256(b *testing.B, source string) { var txn transactions.SignedTxn txn.Lsig.Logic = data[i].programs txn.Lsig.Args = [][]byte{data[i].a, data[i].k, data[i].g1, data[i].g2} - ep := defaultEvalParams(&txn) + ep := defaultEvalParams(txn) pass, err := EvalSignature(0, ep) if !pass { b.Log(hex.EncodeToString(data[i].programs)) From b357b7cfceb4c8140be1e8fa4a44b8cbdcad9758 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 24 May 2022 22:10:07 -0400 Subject: [PATCH 14/30] Michael's refactor --- data/transactions/logic/eval_test.go | 37 +++------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 86b7a6c80e..dee4fdb6ac 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -157,7 +157,10 @@ func (ep *EvalParams) reset() { ep.available.apps = nil ep.available.asas = nil // reinitialize boxes because evaluation can add box refs for app creates. - ep.available.boxes = ep.recalcBoxRefs() + available := NewEvalParams(ep.TxnGroup, ep.Proto, ep.Specials).available + if available != nil { + ep.available.boxes = available.boxes + } } ep.appAddrCache = make(map[basics.AppIndex]basics.Address) if ep.Trace != nil { @@ -165,38 +168,6 @@ func (ep *EvalParams) reset() { } } -// recalcBoxRefs is nearly a copy of what happens in NewEvalParams, but is -// in _test.go because it never needs to happen in normal operation. -func (ep *EvalParams) recalcBoxRefs() map[basics.AppIndex][]string { - var allBoxes map[basics.AppIndex][]string - for _, tx := range ep.TxnGroup { - if tx.Txn.Type == protocol.ApplicationCallTx { - if allBoxes == nil && len(tx.Txn.Boxes) > 0 { - allBoxes = make(map[basics.AppIndex][]string) - } - for _, br := range tx.Txn.Boxes { - var app basics.AppIndex - if br.Index == 0 { - // 0 is the "current app". Ignore if this is a create, else use ApplicationID - if tx.Txn.ApplicationID == 0 { - // When the create actually happens, and we learn the appID, we'll make it _available_. - continue - } - app = tx.Txn.ApplicationID - } else { - // Bounds check will already have been done by - // WellFormed. For testing purposes, it's better to panic - // now than after returning a nil. - app = tx.Txn.ForeignApps[br.Index-1] // shift for the 0=this convention - } - appBoxes := allBoxes[app] - allBoxes[app] = append(appBoxes, br.Name) - } - } - } - return allBoxes -} - func TestTooManyArgs(t *testing.T) { partitiontest.PartitionTest(t) From 3d7c41ebf069f47a5f3d8dd5c14f154fe1fd3eea Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 25 May 2022 13:28:46 -0400 Subject: [PATCH 15/30] Avoid confusing addition of Boxes in basics.AccountData --- data/basics/userBalance.go | 14 +++----------- ledger/ledgercore/accountdata.go | 2 ++ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 7c8ddecf67..d5ba319655 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -68,14 +68,6 @@ const ( // config.Consensus[protocol.ConsensusCurrentVersion].MaxLocalSchemaEntries and // config.Consensus[protocol.ConsensusCurrentVersion].MaxGlobalSchemaEntries EncodedMaxKeyValueEntries = 1024 - - // EncodedMaxBoxes is not really meaningful because Boxes were introduced - // after the separation of AccountData in ledgercore and basics. The - // constant exists only to satisfy the need for an allocbound on - // basics.AccountData, even though such structures are not used in protocol - // messages or serialized to the DB. It's made particularly small to ferret - // out any possible dependencies. - EncodedMaxBoxes = 4. ) func (s Status) String() string { @@ -232,8 +224,8 @@ type AccountData struct { // requested for app program by this account TotalExtraAppPages uint32 `codec:"teap"` - // Boxes is all of the boxes associated with this account. (This account must be an app account) - Boxes map[string][]byte `codec:"apbx,allocbound=EncodedMaxBoxes"` + // Total number of boxes associated with this account, which implies it is an app account. + TotalBoxes uint64 // TotalBoxBytes stores the sum of all len(keys) and len(values) of Boxes TotalBoxBytes uint64 @@ -483,7 +475,7 @@ func (u AccountData) MinBalance(proto *config.ConsensusParams) (res MicroAlgos) u.TotalAppSchema, uint64(len(u.AppParams)), uint64(len(u.AppLocalStates)), uint64(u.TotalExtraAppPages), - uint64(len(u.Boxes)), u.TotalBoxBytes, + u.TotalBoxes, u.TotalBoxBytes, ) } diff --git a/ledger/ledgercore/accountdata.go b/ledger/ledgercore/accountdata.go index 5db9ac41f0..a47f811ed2 100644 --- a/ledger/ledgercore/accountdata.go +++ b/ledger/ledgercore/accountdata.go @@ -112,6 +112,8 @@ func AssignAccountData(a *basics.AccountData, acct AccountData) { a.AuthAddr = acct.AuthAddr a.TotalAppSchema = acct.TotalAppSchema a.TotalExtraAppPages = acct.TotalExtraAppPages + a.TotalBoxes = acct.TotalBoxes + a.TotalBoxBytes = acct.TotalBoxBytes } // WithUpdatedRewards calls basics account data WithUpdatedRewards From 1302b5a36538e7fe895f24fab9187a697dfd3d18 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 25 May 2022 14:24:36 -0400 Subject: [PATCH 16/30] make msgp --- data/basics/msgp_gen.go | 246 +++++++++++++++++++++++++++------------- 1 file changed, 169 insertions(+), 77 deletions(-) diff --git a/data/basics/msgp_gen.go b/data/basics/msgp_gen.go index 53bcc659f1..fb0c9ea4dc 100644 --- a/data/basics/msgp_gen.go +++ b/data/basics/msgp_gen.go @@ -201,85 +201,103 @@ import ( func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0009Len := uint32(17) - var zb0009Mask uint32 /* 18 bits */ - if (*z).MicroAlgos.MsgIsZero() { + zb0009Len := uint32(19) + var zb0009Mask uint32 /* 20 bits */ + if (*z).TotalBoxBytes == 0 { + zb0009Len-- + zb0009Mask |= 0x1 + } + if (*z).TotalBoxes == 0 { zb0009Len-- zb0009Mask |= 0x2 } + if (*z).MicroAlgos.MsgIsZero() { + zb0009Len-- + zb0009Mask |= 0x8 + } if len((*z).AssetParams) == 0 { zb0009Len-- - zb0009Mask |= 0x4 + zb0009Mask |= 0x10 } if len((*z).AppLocalStates) == 0 { zb0009Len-- - zb0009Mask |= 0x8 + zb0009Mask |= 0x20 } if len((*z).AppParams) == 0 { zb0009Len-- - zb0009Mask |= 0x10 + zb0009Mask |= 0x40 } if len((*z).Assets) == 0 { zb0009Len-- - zb0009Mask |= 0x20 + zb0009Mask |= 0x80 } if (*z).RewardsBase == 0 { zb0009Len-- - zb0009Mask |= 0x40 + zb0009Mask |= 0x100 } if (*z).RewardedMicroAlgos.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x80 + zb0009Mask |= 0x200 } if (*z).Status == 0 { zb0009Len-- - zb0009Mask |= 0x100 + zb0009Mask |= 0x400 } if (*z).SelectionID.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x200 + zb0009Mask |= 0x800 } if (*z).AuthAddr.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x400 + zb0009Mask |= 0x1000 } if (*z).StateProofID.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x800 + zb0009Mask |= 0x2000 } if (*z).TotalExtraAppPages == 0 { zb0009Len-- - zb0009Mask |= 0x1000 + zb0009Mask |= 0x4000 } if ((*z).TotalAppSchema.NumUint == 0) && ((*z).TotalAppSchema.NumByteSlice == 0) { zb0009Len-- - zb0009Mask |= 0x2000 + zb0009Mask |= 0x8000 } if (*z).VoteID.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x4000 + zb0009Mask |= 0x10000 } if (*z).VoteFirstValid == 0 { zb0009Len-- - zb0009Mask |= 0x8000 + zb0009Mask |= 0x20000 } if (*z).VoteKeyDilution == 0 { zb0009Len-- - zb0009Mask |= 0x10000 + zb0009Mask |= 0x40000 } if (*z).VoteLastValid == 0 { zb0009Len-- - zb0009Mask |= 0x20000 + zb0009Mask |= 0x80000 } // variable map header, size zb0009Len o = msgp.AppendMapHeader(o, zb0009Len) if zb0009Len != 0 { + if (zb0009Mask & 0x1) == 0 { // if not empty + // string "TotalBoxBytes" + o = append(o, 0xad, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73) + o = msgp.AppendUint64(o, (*z).TotalBoxBytes) + } if (zb0009Mask & 0x2) == 0 { // if not empty + // string "TotalBoxes" + o = append(o, 0xaa, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x65, 0x73) + o = msgp.AppendUint64(o, (*z).TotalBoxes) + } + if (zb0009Mask & 0x8) == 0 { // if not empty // string "algo" o = append(o, 0xa4, 0x61, 0x6c, 0x67, 0x6f) o = (*z).MicroAlgos.MarshalMsg(o) } - if (zb0009Mask & 0x4) == 0 { // if not empty + if (zb0009Mask & 0x10) == 0 { // if not empty // string "apar" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x72) if (*z).AssetParams == nil { @@ -299,7 +317,7 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0009Mask & 0x8) == 0 { // if not empty + if (zb0009Mask & 0x20) == 0 { // if not empty // string "appl" o = append(o, 0xa4, 0x61, 0x70, 0x70, 0x6c) if (*z).AppLocalStates == nil { @@ -319,7 +337,7 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = zb0006.MarshalMsg(o) } } - if (zb0009Mask & 0x10) == 0 { // if not empty + if (zb0009Mask & 0x40) == 0 { // if not empty // string "appp" o = append(o, 0xa4, 0x61, 0x70, 0x70, 0x70) if (*z).AppParams == nil { @@ -339,7 +357,7 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = zb0008.MarshalMsg(o) } } - if (zb0009Mask & 0x20) == 0 { // if not empty + if (zb0009Mask & 0x80) == 0 { // if not empty // string "asset" o = append(o, 0xa5, 0x61, 0x73, 0x73, 0x65, 0x74) if (*z).Assets == nil { @@ -383,42 +401,42 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { } } } - if (zb0009Mask & 0x40) == 0 { // if not empty + if (zb0009Mask & 0x100) == 0 { // if not empty // string "ebase" o = append(o, 0xa5, 0x65, 0x62, 0x61, 0x73, 0x65) o = msgp.AppendUint64(o, (*z).RewardsBase) } - if (zb0009Mask & 0x80) == 0 { // if not empty + if (zb0009Mask & 0x200) == 0 { // if not empty // string "ern" o = append(o, 0xa3, 0x65, 0x72, 0x6e) o = (*z).RewardedMicroAlgos.MarshalMsg(o) } - if (zb0009Mask & 0x100) == 0 { // if not empty + if (zb0009Mask & 0x400) == 0 { // if not empty // string "onl" o = append(o, 0xa3, 0x6f, 0x6e, 0x6c) o = msgp.AppendByte(o, byte((*z).Status)) } - if (zb0009Mask & 0x200) == 0 { // if not empty + if (zb0009Mask & 0x800) == 0 { // if not empty // string "sel" o = append(o, 0xa3, 0x73, 0x65, 0x6c) o = (*z).SelectionID.MarshalMsg(o) } - if (zb0009Mask & 0x400) == 0 { // if not empty + if (zb0009Mask & 0x1000) == 0 { // if not empty // string "spend" o = append(o, 0xa5, 0x73, 0x70, 0x65, 0x6e, 0x64) o = (*z).AuthAddr.MarshalMsg(o) } - if (zb0009Mask & 0x800) == 0 { // if not empty + if (zb0009Mask & 0x2000) == 0 { // if not empty // string "stprf" o = append(o, 0xa5, 0x73, 0x74, 0x70, 0x72, 0x66) o = (*z).StateProofID.MarshalMsg(o) } - if (zb0009Mask & 0x1000) == 0 { // if not empty + if (zb0009Mask & 0x4000) == 0 { // if not empty // string "teap" o = append(o, 0xa4, 0x74, 0x65, 0x61, 0x70) o = msgp.AppendUint32(o, (*z).TotalExtraAppPages) } - if (zb0009Mask & 0x2000) == 0 { // if not empty + if (zb0009Mask & 0x8000) == 0 { // if not empty // string "tsch" o = append(o, 0xa4, 0x74, 0x73, 0x63, 0x68) // omitempty: check for empty values @@ -445,22 +463,22 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).TotalAppSchema.NumUint) } } - if (zb0009Mask & 0x4000) == 0 { // if not empty + if (zb0009Mask & 0x10000) == 0 { // if not empty // string "vote" o = append(o, 0xa4, 0x76, 0x6f, 0x74, 0x65) o = (*z).VoteID.MarshalMsg(o) } - if (zb0009Mask & 0x8000) == 0 { // if not empty + if (zb0009Mask & 0x20000) == 0 { // if not empty // string "voteFst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x46, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).VoteFirstValid)) } - if (zb0009Mask & 0x10000) == 0 { // if not empty + if (zb0009Mask & 0x40000) == 0 { // if not empty // string "voteKD" o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x4b, 0x44) o = msgp.AppendUint64(o, (*z).VoteKeyDilution) } - if (zb0009Mask & 0x20000) == 0 { // if not empty + if (zb0009Mask & 0x80000) == 0 { // if not empty // string "voteLst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x4c, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).VoteLastValid)) @@ -875,6 +893,22 @@ func (z *AccountData) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + if zb0009 > 0 { + zb0009-- + (*z).TotalBoxes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "TotalBoxes") + return + } + } + if zb0009 > 0 { + zb0009-- + (*z).TotalBoxBytes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "TotalBoxBytes") + return + } + } if zb0009 > 0 { err = msgp.ErrTooManyArrayFields(zb0009) if err != nil { @@ -1252,6 +1286,18 @@ func (z *AccountData) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "TotalExtraAppPages") return } + case "TotalBoxes": + (*z).TotalBoxes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "TotalBoxes") + return + } + case "TotalBoxBytes": + (*z).TotalBoxBytes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "TotalBoxBytes") + return + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -1304,13 +1350,13 @@ func (z *AccountData) Msgsize() (s int) { s += 0 + zb0007.Msgsize() + zb0008.Msgsize() } } - s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + msgp.Uint32Size + s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + msgp.Uint32Size + 11 + msgp.Uint64Size + 14 + msgp.Uint64Size return } // MsgIsZero returns whether this is a zero value func (z *AccountData) MsgIsZero() bool { - return ((*z).Status == 0) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) && ((*z).RewardedMicroAlgos.MsgIsZero()) && ((*z).VoteID.MsgIsZero()) && ((*z).SelectionID.MsgIsZero()) && ((*z).StateProofID.MsgIsZero()) && ((*z).VoteFirstValid == 0) && ((*z).VoteLastValid == 0) && ((*z).VoteKeyDilution == 0) && (len((*z).AssetParams) == 0) && (len((*z).Assets) == 0) && ((*z).AuthAddr.MsgIsZero()) && (len((*z).AppLocalStates) == 0) && (len((*z).AppParams) == 0) && (((*z).TotalAppSchema.NumUint == 0) && ((*z).TotalAppSchema.NumByteSlice == 0)) && ((*z).TotalExtraAppPages == 0) + return ((*z).Status == 0) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) && ((*z).RewardedMicroAlgos.MsgIsZero()) && ((*z).VoteID.MsgIsZero()) && ((*z).SelectionID.MsgIsZero()) && ((*z).StateProofID.MsgIsZero()) && ((*z).VoteFirstValid == 0) && ((*z).VoteLastValid == 0) && ((*z).VoteKeyDilution == 0) && (len((*z).AssetParams) == 0) && (len((*z).Assets) == 0) && ((*z).AuthAddr.MsgIsZero()) && (len((*z).AppLocalStates) == 0) && (len((*z).AppParams) == 0) && (((*z).TotalAppSchema.NumUint == 0) && ((*z).TotalAppSchema.NumByteSlice == 0)) && ((*z).TotalExtraAppPages == 0) && ((*z).TotalBoxes == 0) && ((*z).TotalBoxBytes == 0) } // MarshalMsg implements msgp.Marshaler @@ -2899,94 +2945,112 @@ func (z *AssetParams) MsgIsZero() bool { func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0009Len := uint32(18) - var zb0009Mask uint32 /* 20 bits */ + zb0009Len := uint32(20) + var zb0009Mask uint32 /* 22 bits */ + if (*z).AccountData.TotalBoxBytes == 0 { + zb0009Len-- + zb0009Mask |= 0x1 + } + if (*z).AccountData.TotalBoxes == 0 { + zb0009Len-- + zb0009Mask |= 0x2 + } if (*z).Addr.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x4 + zb0009Mask |= 0x10 } if (*z).AccountData.MicroAlgos.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x8 + zb0009Mask |= 0x20 } if len((*z).AccountData.AssetParams) == 0 { zb0009Len-- - zb0009Mask |= 0x10 + zb0009Mask |= 0x40 } if len((*z).AccountData.AppLocalStates) == 0 { zb0009Len-- - zb0009Mask |= 0x20 + zb0009Mask |= 0x80 } if len((*z).AccountData.AppParams) == 0 { zb0009Len-- - zb0009Mask |= 0x40 + zb0009Mask |= 0x100 } if len((*z).AccountData.Assets) == 0 { zb0009Len-- - zb0009Mask |= 0x80 + zb0009Mask |= 0x200 } if (*z).AccountData.RewardsBase == 0 { zb0009Len-- - zb0009Mask |= 0x100 + zb0009Mask |= 0x400 } if (*z).AccountData.RewardedMicroAlgos.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x200 + zb0009Mask |= 0x800 } if (*z).AccountData.Status == 0 { zb0009Len-- - zb0009Mask |= 0x400 + zb0009Mask |= 0x1000 } if (*z).AccountData.SelectionID.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x800 + zb0009Mask |= 0x2000 } if (*z).AccountData.AuthAddr.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x1000 + zb0009Mask |= 0x4000 } if (*z).AccountData.StateProofID.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x2000 + zb0009Mask |= 0x8000 } if (*z).AccountData.TotalExtraAppPages == 0 { zb0009Len-- - zb0009Mask |= 0x4000 + zb0009Mask |= 0x10000 } if ((*z).AccountData.TotalAppSchema.NumUint == 0) && ((*z).AccountData.TotalAppSchema.NumByteSlice == 0) { zb0009Len-- - zb0009Mask |= 0x8000 + zb0009Mask |= 0x20000 } if (*z).AccountData.VoteID.MsgIsZero() { zb0009Len-- - zb0009Mask |= 0x10000 + zb0009Mask |= 0x40000 } if (*z).AccountData.VoteFirstValid == 0 { zb0009Len-- - zb0009Mask |= 0x20000 + zb0009Mask |= 0x80000 } if (*z).AccountData.VoteKeyDilution == 0 { zb0009Len-- - zb0009Mask |= 0x40000 + zb0009Mask |= 0x100000 } if (*z).AccountData.VoteLastValid == 0 { zb0009Len-- - zb0009Mask |= 0x80000 + zb0009Mask |= 0x200000 } // variable map header, size zb0009Len o = msgp.AppendMapHeader(o, zb0009Len) if zb0009Len != 0 { - if (zb0009Mask & 0x4) == 0 { // if not empty + if (zb0009Mask & 0x1) == 0 { // if not empty + // string "TotalBoxBytes" + o = append(o, 0xad, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73) + o = msgp.AppendUint64(o, (*z).AccountData.TotalBoxBytes) + } + if (zb0009Mask & 0x2) == 0 { // if not empty + // string "TotalBoxes" + o = append(o, 0xaa, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x65, 0x73) + o = msgp.AppendUint64(o, (*z).AccountData.TotalBoxes) + } + if (zb0009Mask & 0x10) == 0 { // if not empty // string "addr" o = append(o, 0xa4, 0x61, 0x64, 0x64, 0x72) o = (*z).Addr.MarshalMsg(o) } - if (zb0009Mask & 0x8) == 0 { // if not empty + if (zb0009Mask & 0x20) == 0 { // if not empty // string "algo" o = append(o, 0xa4, 0x61, 0x6c, 0x67, 0x6f) o = (*z).AccountData.MicroAlgos.MarshalMsg(o) } - if (zb0009Mask & 0x10) == 0 { // if not empty + if (zb0009Mask & 0x40) == 0 { // if not empty // string "apar" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x72) if (*z).AccountData.AssetParams == nil { @@ -3006,7 +3070,7 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0009Mask & 0x20) == 0 { // if not empty + if (zb0009Mask & 0x80) == 0 { // if not empty // string "appl" o = append(o, 0xa4, 0x61, 0x70, 0x70, 0x6c) if (*z).AccountData.AppLocalStates == nil { @@ -3026,7 +3090,7 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = zb0006.MarshalMsg(o) } } - if (zb0009Mask & 0x40) == 0 { // if not empty + if (zb0009Mask & 0x100) == 0 { // if not empty // string "appp" o = append(o, 0xa4, 0x61, 0x70, 0x70, 0x70) if (*z).AccountData.AppParams == nil { @@ -3046,7 +3110,7 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = zb0008.MarshalMsg(o) } } - if (zb0009Mask & 0x80) == 0 { // if not empty + if (zb0009Mask & 0x200) == 0 { // if not empty // string "asset" o = append(o, 0xa5, 0x61, 0x73, 0x73, 0x65, 0x74) if (*z).AccountData.Assets == nil { @@ -3090,42 +3154,42 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { } } } - if (zb0009Mask & 0x100) == 0 { // if not empty + if (zb0009Mask & 0x400) == 0 { // if not empty // string "ebase" o = append(o, 0xa5, 0x65, 0x62, 0x61, 0x73, 0x65) o = msgp.AppendUint64(o, (*z).AccountData.RewardsBase) } - if (zb0009Mask & 0x200) == 0 { // if not empty + if (zb0009Mask & 0x800) == 0 { // if not empty // string "ern" o = append(o, 0xa3, 0x65, 0x72, 0x6e) o = (*z).AccountData.RewardedMicroAlgos.MarshalMsg(o) } - if (zb0009Mask & 0x400) == 0 { // if not empty + if (zb0009Mask & 0x1000) == 0 { // if not empty // string "onl" o = append(o, 0xa3, 0x6f, 0x6e, 0x6c) o = msgp.AppendByte(o, byte((*z).AccountData.Status)) } - if (zb0009Mask & 0x800) == 0 { // if not empty + if (zb0009Mask & 0x2000) == 0 { // if not empty // string "sel" o = append(o, 0xa3, 0x73, 0x65, 0x6c) o = (*z).AccountData.SelectionID.MarshalMsg(o) } - if (zb0009Mask & 0x1000) == 0 { // if not empty + if (zb0009Mask & 0x4000) == 0 { // if not empty // string "spend" o = append(o, 0xa5, 0x73, 0x70, 0x65, 0x6e, 0x64) o = (*z).AccountData.AuthAddr.MarshalMsg(o) } - if (zb0009Mask & 0x2000) == 0 { // if not empty + if (zb0009Mask & 0x8000) == 0 { // if not empty // string "stprf" o = append(o, 0xa5, 0x73, 0x74, 0x70, 0x72, 0x66) o = (*z).AccountData.StateProofID.MarshalMsg(o) } - if (zb0009Mask & 0x4000) == 0 { // if not empty + if (zb0009Mask & 0x10000) == 0 { // if not empty // string "teap" o = append(o, 0xa4, 0x74, 0x65, 0x61, 0x70) o = msgp.AppendUint32(o, (*z).AccountData.TotalExtraAppPages) } - if (zb0009Mask & 0x8000) == 0 { // if not empty + if (zb0009Mask & 0x20000) == 0 { // if not empty // string "tsch" o = append(o, 0xa4, 0x74, 0x73, 0x63, 0x68) // omitempty: check for empty values @@ -3152,22 +3216,22 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).AccountData.TotalAppSchema.NumUint) } } - if (zb0009Mask & 0x10000) == 0 { // if not empty + if (zb0009Mask & 0x40000) == 0 { // if not empty // string "vote" o = append(o, 0xa4, 0x76, 0x6f, 0x74, 0x65) o = (*z).AccountData.VoteID.MarshalMsg(o) } - if (zb0009Mask & 0x20000) == 0 { // if not empty + if (zb0009Mask & 0x80000) == 0 { // if not empty // string "voteFst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x46, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).AccountData.VoteFirstValid)) } - if (zb0009Mask & 0x40000) == 0 { // if not empty + if (zb0009Mask & 0x100000) == 0 { // if not empty // string "voteKD" o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x4b, 0x44) o = msgp.AppendUint64(o, (*z).AccountData.VoteKeyDilution) } - if (zb0009Mask & 0x80000) == 0 { // if not empty + if (zb0009Mask & 0x200000) == 0 { // if not empty // string "voteLst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x4c, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).AccountData.VoteLastValid)) @@ -3590,6 +3654,22 @@ func (z *BalanceRecord) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + if zb0009 > 0 { + zb0009-- + (*z).AccountData.TotalBoxes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "TotalBoxes") + return + } + } + if zb0009 > 0 { + zb0009-- + (*z).AccountData.TotalBoxBytes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "TotalBoxBytes") + return + } + } if zb0009 > 0 { err = msgp.ErrTooManyArrayFields(zb0009) if err != nil { @@ -3973,6 +4053,18 @@ func (z *BalanceRecord) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "TotalExtraAppPages") return } + case "TotalBoxes": + (*z).AccountData.TotalBoxes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "TotalBoxes") + return + } + case "TotalBoxBytes": + (*z).AccountData.TotalBoxBytes, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "TotalBoxBytes") + return + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -4025,13 +4117,13 @@ func (z *BalanceRecord) Msgsize() (s int) { s += 0 + zb0007.Msgsize() + zb0008.Msgsize() } } - s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + msgp.Uint32Size + s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + msgp.Uint32Size + 11 + msgp.Uint64Size + 14 + msgp.Uint64Size return } // MsgIsZero returns whether this is a zero value func (z *BalanceRecord) MsgIsZero() bool { - return ((*z).Addr.MsgIsZero()) && ((*z).AccountData.Status == 0) && ((*z).AccountData.MicroAlgos.MsgIsZero()) && ((*z).AccountData.RewardsBase == 0) && ((*z).AccountData.RewardedMicroAlgos.MsgIsZero()) && ((*z).AccountData.VoteID.MsgIsZero()) && ((*z).AccountData.SelectionID.MsgIsZero()) && ((*z).AccountData.StateProofID.MsgIsZero()) && ((*z).AccountData.VoteFirstValid == 0) && ((*z).AccountData.VoteLastValid == 0) && ((*z).AccountData.VoteKeyDilution == 0) && (len((*z).AccountData.AssetParams) == 0) && (len((*z).AccountData.Assets) == 0) && ((*z).AccountData.AuthAddr.MsgIsZero()) && (len((*z).AccountData.AppLocalStates) == 0) && (len((*z).AccountData.AppParams) == 0) && (((*z).AccountData.TotalAppSchema.NumUint == 0) && ((*z).AccountData.TotalAppSchema.NumByteSlice == 0)) && ((*z).AccountData.TotalExtraAppPages == 0) + return ((*z).Addr.MsgIsZero()) && ((*z).AccountData.Status == 0) && ((*z).AccountData.MicroAlgos.MsgIsZero()) && ((*z).AccountData.RewardsBase == 0) && ((*z).AccountData.RewardedMicroAlgos.MsgIsZero()) && ((*z).AccountData.VoteID.MsgIsZero()) && ((*z).AccountData.SelectionID.MsgIsZero()) && ((*z).AccountData.StateProofID.MsgIsZero()) && ((*z).AccountData.VoteFirstValid == 0) && ((*z).AccountData.VoteLastValid == 0) && ((*z).AccountData.VoteKeyDilution == 0) && (len((*z).AccountData.AssetParams) == 0) && (len((*z).AccountData.Assets) == 0) && ((*z).AccountData.AuthAddr.MsgIsZero()) && (len((*z).AccountData.AppLocalStates) == 0) && (len((*z).AccountData.AppParams) == 0) && (((*z).AccountData.TotalAppSchema.NumUint == 0) && ((*z).AccountData.TotalAppSchema.NumByteSlice == 0)) && ((*z).AccountData.TotalExtraAppPages == 0) && ((*z).AccountData.TotalBoxes == 0) && ((*z).AccountData.TotalBoxBytes == 0) } // MarshalMsg implements msgp.Marshaler From 47e560df941ddd0e2ea5c8e30753cbc891680afb Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 26 May 2022 10:22:03 -0400 Subject: [PATCH 17/30] b64 encode the BoxRef names in a txn when marshaling to json --- data/basics/fields_test.go | 3 + data/transactions/json.go | 57 ++++++++++++ data/transactions/json_test.go | 91 +++++++++++++++++++ data/transactions/msgp_gen.go | 137 +++++++++++++++++++++++++++++ data/transactions/msgp_gen_test.go | 60 +++++++++++++ 5 files changed, 348 insertions(+) create mode 100644 data/transactions/json.go create mode 100644 data/transactions/json_test.go diff --git a/data/basics/fields_test.go b/data/basics/fields_test.go index 2fcabbd4d0..3bd181cc92 100644 --- a/data/basics/fields_test.go +++ b/data/basics/fields_test.go @@ -172,6 +172,9 @@ func TestBlockFields(t *testing.T) { typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addMapKey(), typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addValue().addField("Bytes"), typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("Logs").addValue(), + + // This exception is for a NEW string field. See transactions/json.go to see how we get b64 output. + typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("ApplicationCallTxnFields").addField("Boxes").addValue().addField("Name"), } seen := make(map[reflect.Type]bool) diff --git a/data/transactions/json.go b/data/transactions/json.go new file mode 100644 index 0000000000..98d1e69c9c --- /dev/null +++ b/data/transactions/json.go @@ -0,0 +1,57 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +/* + This file contains code to adjust the json serialization of + Transaction. Currently the only change from the standard serialization + provided by the codec package is to cause BoxRefs to be serialized with b64 + names, rather than the default for string, which would not properly encode + their binary nature in JSON. + + Had we done it from the beginning, it would be reasonable to do the same for + some other fields that are declared as string, but can contain arbitrary + binary data, such as AssetParams fields like unitname and url. We can't do + that now, to preserve the shape of our REST APIs. But perhaps we can do it + for a v3 REST API. + +*/ + +package transactions + +import "github.com/algorand/go-algorand/protocol" + +type boxRefStringly struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + Index uint64 `codec:"i"` + Name []byte `codec:"n"` +} + +// MarshalJSON makes it so BoxRefs emit b64 encoded names +func (br BoxRef) MarshalJSON() ([]byte, error) { + return protocol.EncodeJSON(boxRefStringly{ + Index: br.Index, + Name: []byte(br.Name), + }), nil +} + +// UnmarshalJSON makes it so BoxRefs read u64 names +func (br *BoxRef) UnmarshalJSON(data []byte) error { + var x boxRefStringly + protocol.DecodeJSON(data, &x) + br.Index = x.Index + br.Name = string(x.Name) + return nil +} diff --git a/data/transactions/json_test.go b/data/transactions/json_test.go new file mode 100644 index 0000000000..d480d0834b --- /dev/null +++ b/data/transactions/json_test.go @@ -0,0 +1,91 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package transactions_test + +import ( + "strings" + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/protocol" + "github.com/stretchr/testify/require" +) + +func decode(data string, v interface{}) error { + err := protocol.DecodeJSON([]byte(data), v) + if err != nil { + panic(err) + } + return err +} + +func compact(data []byte) string { + return strings.ReplaceAll(strings.ReplaceAll(string(data), " ", ""), "\n", "") +} + +// TestJsonMarshal ensures that BoxRef names are b64 encoded, since they may not be characters. +func TestJsonMarshal(t *testing.T) { + marshal := protocol.EncodeJSON(transactions.BoxRef{Index: 4, Name: "joe"}) + require.Equal(t, `{"i":4,"n":"am9l"}`, compact(marshal)) + + marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 0, Name: "joe"}) + require.Equal(t, `{"n":"am9l"}`, compact(marshal)) + + marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 1, Name: ""}) + require.Equal(t, `{"i":1}`, compact(marshal)) + + marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 0, Name: ""}) + require.Equal(t, `{}`, compact(marshal)) +} + +// TestJsonUnmarshal ensures that BoxRef unmarshaling expects b64 names +func TestJsonUnmarshal(t *testing.T) { + var br transactions.BoxRef + + decode(`{"i":4,"n":"am9l"}`, &br) + require.Equal(t, transactions.BoxRef{Index: 4, Name: "joe"}, br) + + decode(`{"n":"am9l"}`, &br) + require.Equal(t, transactions.BoxRef{Index: 0, Name: "joe"}, br) + + decode(`{"i":4}`, &br) + require.Equal(t, transactions.BoxRef{Index: 4, Name: ""}, br) + + decode(`{}`, &br) + require.Equal(t, transactions.BoxRef{Index: 0, Name: ""}, br) +} + +// TestTxnJson tests a few more things about how our Transactions get JSON +// encoded. These things could change without breaking the protocol, should stay +// the same for the sake of REST API compatibility. +func TestTxnJson(t *testing.T) { + txn := txntest.Txn{ + Sender: basics.Address{0x01, 0x02, 0x03}, + } + marshal := protocol.EncodeJSON(txn.Txn()) + require.Contains(t, compact(marshal), `"snd":"AEBA`) + + txn = txntest.Txn{ + Boxes: []transactions.BoxRef{{Index: 3, Name: "john"}}, + } + marshal = protocol.EncodeJSON(txn.Txn()) + require.Contains(t, compact(marshal), `"apbx":[{"i":3,"n":"am9obg=="}]`) + + marshal = protocol.EncodeJSON(txn.Txn()) +} diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 55d787b00d..727044ba64 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -181,6 +181,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// boxRefStringly +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // MarshalMsg implements msgp.Marshaler func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { @@ -6368,3 +6376,132 @@ func (z *Txid) Msgsize() int { func (z *Txid) MsgIsZero() bool { return ((*(crypto.Digest))(z)).MsgIsZero() } + +// MarshalMsg implements msgp.Marshaler +func (z *boxRefStringly) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).Index == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if len((*z).Name) == 0 { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "i" + o = append(o, 0xa1, 0x69) + o = msgp.AppendUint64(o, (*z).Index) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "n" + o = append(o, 0xa1, 0x6e) + o = msgp.AppendBytes(o, (*z).Name) + } + } + return +} + +func (_ *boxRefStringly) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*boxRefStringly) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *boxRefStringly) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Index") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Name) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Name") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = boxRefStringly{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "i": + (*z).Index, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Index") + return + } + case "n": + (*z).Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Name) + if err != nil { + err = msgp.WrapError(err, "Name") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *boxRefStringly) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*boxRefStringly) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *boxRefStringly) Msgsize() (s int) { + s = 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).Name) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *boxRefStringly) MsgIsZero() bool { + return ((*z).Index == 0) && (len((*z).Name) == 0) +} diff --git a/data/transactions/msgp_gen_test.go b/data/transactions/msgp_gen_test.go index bfc997627b..80fc9b755e 100644 --- a/data/transactions/msgp_gen_test.go +++ b/data/transactions/msgp_gen_test.go @@ -1093,3 +1093,63 @@ func BenchmarkUnmarshalTxGroup(b *testing.B) { } } } + +func TestMarshalUnmarshalboxRefStringly(t *testing.T) { + partitiontest.PartitionTest(t) + v := boxRefStringly{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingboxRefStringly(t *testing.T) { + protocol.RunEncodingTest(t, &boxRefStringly{}) +} + +func BenchmarkMarshalMsgboxRefStringly(b *testing.B) { + v := boxRefStringly{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgboxRefStringly(b *testing.B) { + v := boxRefStringly{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalboxRefStringly(b *testing.B) { + v := boxRefStringly{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} From beec0ec368235aad606447be19144c268b36dd5c Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 26 May 2022 12:49:41 -0400 Subject: [PATCH 18/30] preliminary algod box access API --- config/consensus.go | 4 + daemon/algod/api/algod.oas2.json | 102 ++++ daemon/algod/api/algod.oas3.yml | 115 +++++ daemon/algod/api/server/v2/errors.go | 1 + .../api/server/v2/generated/private/routes.go | 300 ++++++------ .../api/server/v2/generated/private/types.go | 13 + .../algod/api/server/v2/generated/routes.go | 445 ++++++++++-------- daemon/algod/api/server/v2/generated/types.go | 13 + daemon/algod/api/server/v2/handlers.go | 29 ++ .../server/v2/test/handlers_resources_test.go | 9 + data/transactions/logic/box.go | 9 + ledger/internal/applications.go | 20 +- 12 files changed, 699 insertions(+), 361 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index ebbd69a785..a86a7871f8 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -332,6 +332,9 @@ type ConsensusParams struct { // []byte values stored in LocalState or GlobalState key/value stores SchemaBytesMinBalance uint64 + // Maximum length of a box (does not include name length) + MaxBoxSize uint64 + // MBR per box created BoxFlatMinBalance uint64 @@ -1160,6 +1163,7 @@ func initConsensusProtocols() { vFuture.EnableSHA256TxnCommitmentHeader = true // Boxes (unlimited global storage) + vFuture.MaxBoxSize = 8096 vFuture.BoxFlatMinBalance = 2500 vFuture.BoxByteMinBalance = 400 vFuture.MaxAppBoxReferences = 8 diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 8e2c69e54c..ba11ccb17a 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1309,6 +1309,82 @@ } ] }, + "/v2/applications/{application-id}/boxes/{box-name}": { + "get": { + "description": "Given an application ID and box name, it returns box information including box name and base64 encoded content.", + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Get box information for a given application.", + "operationId": "GetApplicationBoxByName", + "parameters": [ + { + "type": "integer", + "description": "An application identifier", + "name": "application-id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "A box name", + "name": "box-name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "$ref": "#/responses/BoxResponse" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Box Not Found", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + }, + "parameters": [ + { + "type": "integer", + "name": "application-id", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "box-name", + "in": "path", + "required": true + } + ] + }, "/v2/assets/{asset-id}": { "get": { "description": "Given a asset ID, it returns asset information including creator, name, total supply and special addresses.", @@ -2406,6 +2482,26 @@ } } }, + "Box": { + "description": "Box name and its content.", + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "description": "\\[name\\] box name, base64 encoded", + "type": "string", + "format": "byte" + }, + "value": { + "description": "\\[value\\] box value, base64 encoded.", + "type": "string", + "format": "byte" + } + } + }, "Version": { "description": "algod version information.", "type": "object", @@ -3097,6 +3193,12 @@ "$ref": "#/definitions/Application" } }, + "BoxResponse": { + "description": "Box information", + "schema": { + "$ref": "#/definitions/Box" + } + }, "AssetResponse": { "description": "Asset information", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 0fa09dd559..c2b5699321 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -330,6 +330,16 @@ }, "description": "Encoded block object." }, + "BoxResponse": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Box" + } + } + }, + "description": "Box information" + }, "CatchpointAbortResponse": { "content": { "application/json": { @@ -1161,6 +1171,28 @@ ], "type": "object" }, + "Box": { + "description": "Box name and its content.", + "properties": { + "name": { + "description": "\\[name\\] box name, base64 encoded", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "value": { + "description": "\\[value\\] box value, base64 encoded.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, "BuildVersion": { "properties": { "branch": { @@ -2377,6 +2409,89 @@ "summary": "Get application information." } }, + "/v2/applications/{application-id}/boxes/{box-name}": { + "get": { + "description": "Given an application ID and box name, it returns box information including box name and base64 encoded content.", + "operationId": "GetApplicationBoxByName", + "parameters": [ + { + "description": "An application identifier", + "in": "path", + "name": "application-id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "A box name", + "in": "path", + "name": "box-name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Box" + } + } + }, + "description": "Box information" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Box Not Found" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Get box information for a given application." + } + }, "/v2/assets/{asset-id}": { "get": { "description": "Given a asset ID, it returns asset information including creator, name, total supply and special addresses.", diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go index 9966728dc8..0caf25b8e7 100644 --- a/daemon/algod/api/server/v2/errors.go +++ b/daemon/algod/api/server/v2/errors.go @@ -21,6 +21,7 @@ var ( errAssetDoesNotExist = "asset does not exist" errAccountAppDoesNotExist = "account application info not found" errAccountAssetDoesNotExist = "account asset info not found" + errBoxDoesNotExist = "box not found" errFailedLookingUpLedger = "failed to retrieve information from the ledger" errFailedLookingUpTransactionPool = "failed to retrieve information from the transaction pool" errFailedRetrievingNodeStatus = "failed retrieving node status" diff --git a/daemon/algod/api/server/v2/generated/private/routes.go b/daemon/algod/api/server/v2/generated/private/routes.go index 72eb4a7b19..44e8c39a57 100644 --- a/daemon/algod/api/server/v2/generated/private/routes.go +++ b/daemon/algod/api/server/v2/generated/private/routes.go @@ -311,155 +311,157 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AP//ozC1idO0AAA=", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/private/types.go b/daemon/algod/api/server/v2/generated/private/types.go index 17485641d5..8d6193d4a1 100644 --- a/daemon/algod/api/server/v2/generated/private/types.go +++ b/daemon/algod/api/server/v2/generated/private/types.go @@ -259,6 +259,16 @@ type AssetParams struct { UrlB64 *[]byte `json:"url-b64,omitempty"` } +// Box defines model for Box. +type Box struct { + + // \[name\] box name, base64 encoded + Name []byte `json:"name"` + + // \[value\] box value, base64 encoded. + Value []byte `json:"value"` +} + // BuildVersion defines model for BuildVersion. type BuildVersion struct { Branch string `json:"branch"` @@ -594,6 +604,9 @@ type BlockResponse struct { Cert *map[string]interface{} `json:"cert,omitempty"` } +// BoxResponse defines model for BoxResponse. +type BoxResponse Box + // CatchpointAbortResponse defines model for CatchpointAbortResponse. type CatchpointAbortResponse struct { diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go index fa62e543f5..35fcd6a0aa 100644 --- a/daemon/algod/api/server/v2/generated/routes.go +++ b/daemon/algod/api/server/v2/generated/routes.go @@ -32,6 +32,9 @@ type ServerInterface interface { // Get application information. // (GET /v2/applications/{application-id}) GetApplicationByID(ctx echo.Context, applicationId uint64) error + // Get box information for a given application. + // (GET /v2/applications/{application-id}/boxes/{box-name}) + GetApplicationBoxByName(ctx echo.Context, applicationId uint64, boxName string) error // Get asset information. // (GET /v2/assets/{asset-id}) GetAssetByID(ctx echo.Context, assetId uint64) error @@ -318,6 +321,44 @@ func (w *ServerInterfaceWrapper) GetApplicationByID(ctx echo.Context) error { return err } +// GetApplicationBoxByName converts echo context to params. +func (w *ServerInterfaceWrapper) GetApplicationBoxByName(ctx echo.Context) error { + + validQueryParams := map[string]bool{ + "pretty": true, + } + + // Check for unknown query parameters. + for name, _ := range ctx.QueryParams() { + if _, ok := validQueryParams[name]; !ok { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unknown parameter detected: %s", name)) + } + } + + var err error + // ------------- Path parameter "application-id" ------------- + var applicationId uint64 + + err = runtime.BindStyledParameter("simple", false, "application-id", ctx.Param("application-id"), &applicationId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter application-id: %s", err)) + } + + // ------------- Path parameter "box-name" ------------- + var boxName string + + err = runtime.BindStyledParameter("simple", false, "box-name", ctx.Param("box-name"), &boxName) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter box-name: %s", err)) + } + + ctx.Set("api_key.Scopes", []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetApplicationBoxByName(ctx, applicationId, boxName) + return err +} + // GetAssetByID converts echo context to params. func (w *ServerInterfaceWrapper) GetAssetByID(ctx echo.Context) error { @@ -769,6 +810,7 @@ func RegisterHandlers(router interface { router.GET("/v2/accounts/:address/assets/:asset-id", wrapper.AccountAssetInformation, m...) router.GET("/v2/accounts/:address/transactions/pending", wrapper.GetPendingTransactionsByAddress, m...) router.GET("/v2/applications/:application-id", wrapper.GetApplicationByID, m...) + router.GET("/v2/applications/:application-id/boxes/:box-name", wrapper.GetApplicationBoxByName, m...) router.GET("/v2/assets/:asset-id", wrapper.GetAssetByID, m...) router.GET("/v2/blocks/:round", wrapper.GetBlock, m...) router.GET("/v2/blocks/:round/transactions/:txid/proof", wrapper.GetProof, m...) @@ -788,206 +830,209 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/types.go b/daemon/algod/api/server/v2/generated/types.go index 53983915d6..e8cc2d42a5 100644 --- a/daemon/algod/api/server/v2/generated/types.go +++ b/daemon/algod/api/server/v2/generated/types.go @@ -259,6 +259,16 @@ type AssetParams struct { UrlB64 *[]byte `json:"url-b64,omitempty"` } +// Box defines model for Box. +type Box struct { + + // \[name\] box name, base64 encoded + Name []byte `json:"name"` + + // \[value\] box value, base64 encoded. + Value []byte `json:"value"` +} + // BuildVersion defines model for BuildVersion. type BuildVersion struct { Branch string `json:"branch"` @@ -594,6 +604,9 @@ type BlockResponse struct { Cert *map[string]interface{} `json:"cert,omitempty"` } +// BoxResponse defines model for BoxResponse. +type BoxResponse Box + // CatchpointAbortResponse defines model for CatchpointAbortResponse. type CatchpointAbortResponse struct { diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 9e18ca7d25..cb0750869f 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -24,6 +24,7 @@ import ( "io" "math" "net/http" + "net/url" "strings" "time" @@ -63,6 +64,7 @@ type Handlers struct { type LedgerForAPI interface { LookupAccount(round basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, basics.MicroAlgos, error) LookupLatest(addr basics.Address) (basics.AccountData, basics.Round, basics.MicroAlgos, error) + LookupKv(round basics.Round, key string) (*string, error) ConsensusParams(r basics.Round) (config.ConsensusParams, error) Latest() basics.Round LookupAsset(rnd basics.Round, addr basics.Address, aidx basics.AssetIndex) (ledgercore.AssetResource, error) @@ -1112,6 +1114,33 @@ func (v2 *Handlers) GetApplicationByID(ctx echo.Context, applicationID uint64) e return ctx.JSON(http.StatusOK, response) } +// GetApplicationBoxByName returns the value of an application's box +// (GET /v2/applications/{application-id}/boxes/{box-name}) +func (v2 *Handlers) GetApplicationBoxByName(ctx echo.Context, applicationID uint64, boxName string) error { + appIdx := basics.AppIndex(applicationID) + ledger := v2.Node.LedgerForAPI() + + lastRound := ledger.Latest() + + boxName, err := url.PathUnescape(boxName) + if err != nil { + return badRequest(ctx, err, err.Error(), v2.Log) + } + value, err := ledger.LookupKv(lastRound, logic.MakeBoxKey(appIdx, boxName)) + if err != nil { + return internalError(ctx, err, errFailedLookingUpLedger, v2.Log) + } + + if value == nil { + return notFound(ctx, errors.New(errBoxDoesNotExist), errAppDoesNotExist, v2.Log) + } + response := generated.BoxResponse{ + Name: []byte(boxName), + Value: []byte(*value), + } + return ctx.JSON(http.StatusOK, response) +} + // GetAssetByID returns application information by app idx. // (GET /v2/assets/{asset-id}) func (v2 *Handlers) GetAssetByID(ctx echo.Context, assetID uint64) error { diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index c8c7c7a111..35bf438985 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -18,6 +18,7 @@ package test import ( "encoding/json" + "fmt" "net/http" "net/http/httptest" "testing" @@ -40,6 +41,7 @@ import ( type mockLedger struct { accounts map[basics.Address]basics.AccountData + kvstore map[string]string latest basics.Round } @@ -58,6 +60,13 @@ func (l *mockLedger) LookupLatest(addr basics.Address) (basics.AccountData, basi return ad, l.latest, basics.MicroAlgos{Raw: 0}, nil } +func (l *mockLedger) LookupKv(round basics.Round, key string) (*string, error) { + if value, ok := l.kvstore[key]; ok { + return &value, nil + } + return nil, fmt.Errorf("Key %v does not exist", key) +} + func (l *mockLedger) ConsensusParams(r basics.Round) (config.ConsensusParams, error) { return config.Consensus[protocol.ConsensusFuture], nil } diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index 6439758e2e..8372596078 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -19,6 +19,8 @@ package logic import ( "errors" "fmt" + + "github.com/algorand/go-algorand/data/basics" ) func opBoxCreate(cx *EvalContext) error { @@ -119,3 +121,10 @@ func opBoxDel(cx *EvalContext) error { cx.stack = cx.stack[:last] return cx.Ledger.DelBox(cx.appID, name) } + +// MakeBoxKey creates the key that a box named `name` under app `appIdx` should use. +func MakeBoxKey(appIdx basics.AppIndex, name string) string { + // Reconsider this for something faster. Maybe msgpack encoding of array + // ["bx",appIdx,key]? + return fmt.Sprintf("bx:%d:%s", appIdx, name) +} diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 4c707b6280..45774b96c0 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -164,12 +164,6 @@ func (cs *roundCowState) DelGlobal(appIdx basics.AppIndex, key string) error { return cs.delKey(creator, appIdx, true, key, 0) } -func makeBoxKey(appIdx basics.AppIndex, key string) string { - // Reconsider this for something faster. Maybe msgpack encoding of array - // ["bk",appIdx,key]? - return fmt.Sprintf("bk:%d:%s", appIdx, key) -} - func (cs *roundCowState) kvGet(key string) (string, bool, error) { value, ok := cs.mods.KvMods[key] if !ok { @@ -214,7 +208,11 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), cs.proto.MaxAppKeyLen) } - fullKey := makeBoxKey(appIdx, key) + if size > cs.proto.MaxBoxSize { + return fmt.Errorf("box size too large: %d, maximum is %d", size, cs.proto.MaxBoxSize) + } + + fullKey := logic.MakeBoxKey(appIdx, key) _, ok, err := cs.kvGet(fullKey) if err != nil { return err @@ -223,8 +221,6 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) return fmt.Errorf("book %s exists for %d", key, appIdx) } - // TODO: Choose and enforce a max size - record, err := cs.Get(appIdx.Address(), false) if err != nil { return err @@ -241,7 +237,7 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) } func (cs *roundCowState) GetBox(appIdx basics.AppIndex, key string) (string, error) { - fullKey := makeBoxKey(appIdx, key) + fullKey := logic.MakeBoxKey(appIdx, key) value, ok, err := cs.kvGet(fullKey) if err != nil { return "", err @@ -253,7 +249,7 @@ func (cs *roundCowState) GetBox(appIdx basics.AppIndex, key string) (string, err } func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value string) error { - fullKey := makeBoxKey(appIdx, key) + fullKey := logic.MakeBoxKey(appIdx, key) old, ok, err := cs.kvGet(fullKey) if err != nil { return err @@ -269,7 +265,7 @@ func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value string } func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string) error { - fullKey := makeBoxKey(appIdx, key) + fullKey := logic.MakeBoxKey(appIdx, key) value, ok, err := cs.kvGet(fullKey) if err != nil { From dd969ee39ac510ce916ba749bdee3775dc6056af Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 26 May 2022 15:29:26 -0400 Subject: [PATCH 19/30] unify and imporve extract errors --- data/transactions/logic/box.go | 10 ++----- data/transactions/logic/box_test.go | 2 +- data/transactions/logic/eval.go | 42 +++++++++++++++------------- data/transactions/logic/eval_test.go | 15 +++++----- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index 8372596078..bbe9b62074 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -74,14 +74,10 @@ func opBoxExtract(cx *EvalContext) error { return err } - end := start + length - if start > uint64(len(box)) || end > uint64(len(box)) { - return errors.New("extract range beyond box") - } - - cx.stack[pprev].Bytes = []byte(box[start:end]) + bytes, err := extractCarefully([]byte(box), start, length) + cx.stack[pprev].Bytes = bytes cx.stack = cx.stack[:prev] - return nil + return err } func opBoxReplace(cx *EvalContext) error { diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index df8db69db4..2e6f9e9778 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -69,7 +69,7 @@ func TestBoxReadWrite(t *testing.T) { int 1`, ep) logic.TestApp(t, `byte "self"; int 1; int 4; box_extract; - byte 0x00000000; ==`, ep, "extract range") + byte 0x00000000; ==`, ep, "extraction end 5") // Replace some bytes until past the end, confirm when it fails. logic.TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace; diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index bc6b74f793..533c7b563e 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -3494,24 +3494,30 @@ func opSetByte(cx *EvalContext) error { return nil } -func opExtractImpl(x []byte, start, length int) ([]byte, error) { +func extractCarefully(x []byte, start, length uint64) ([]byte, error) { + if start > uint64(len(x)) { + return nil, fmt.Errorf("extraction start %d beyond length: %d", start, len(x)) + } end := start + length - if start > len(x) || end > len(x) { - return nil, errors.New("extract range beyond length of string") + if end < start { + return nil, fmt.Errorf("extraction end exceeds uint64") + } + if end > uint64(len(x)) { + return nil, fmt.Errorf("extraction end %d beyond length: %d", end, len(x)) } return x[start:end], nil } func opExtract(cx *EvalContext) error { last := len(cx.stack) - 1 - startIdx := cx.program[cx.pc+1] - lengthIdx := cx.program[cx.pc+2] + start := uint64(cx.program[cx.pc+1]) + length := uint64(cx.program[cx.pc+2]) // Shortcut: if length is 0, take bytes from start index to the end - length := int(lengthIdx) if length == 0 { - length = len(cx.stack[last].Bytes) - int(startIdx) + // If length has wrapped, it's because start > len(), so extractCarefully will report + length = uint64(len(cx.stack[last].Bytes) - int(start)) } - bytes, err := opExtractImpl(cx.stack[last].Bytes, int(startIdx), length) + bytes, err := extractCarefully(cx.stack[last].Bytes, start, length) cx.stack[last].Bytes = bytes return err } @@ -3519,14 +3525,12 @@ func opExtract(cx *EvalContext) error { func opExtract3(cx *EvalContext) error { last := len(cx.stack) - 1 // length prev := last - 1 // start - byteArrayIdx := prev - 1 // bytes - startIdx := cx.stack[prev].Uint - lengthIdx := cx.stack[last].Uint - if startIdx > math.MaxInt32 || lengthIdx > math.MaxInt32 { - return errors.New("extract range beyond length of string") - } - bytes, err := opExtractImpl(cx.stack[byteArrayIdx].Bytes, int(startIdx), int(lengthIdx)) - cx.stack[byteArrayIdx].Bytes = bytes + pprev := prev - 1 // bytes + + start := cx.stack[prev].Uint + length := cx.stack[last].Uint + bytes, err := extractCarefully(cx.stack[pprev].Bytes, start, length) + cx.stack[pprev].Bytes = bytes cx.stack = cx.stack[:prev] return err } @@ -3542,11 +3546,11 @@ func convertBytesToInt(x []byte) uint64 { return out } -func opExtractNBytes(cx *EvalContext, n int) error { +func opExtractNBytes(cx *EvalContext, n uint64) error { last := len(cx.stack) - 1 // start prev := last - 1 // bytes - startIdx := cx.stack[last].Uint - bytes, err := opExtractImpl(cx.stack[prev].Bytes, int(startIdx), n) // extract n bytes + start := cx.stack[last].Uint + bytes, err := extractCarefully(cx.stack[prev].Bytes, start, n) // extract n bytes if err != nil { return err } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index dee4fdb6ac..4cdac9812b 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -129,6 +129,7 @@ func defaultEvalParamsWithVersion(version uint64, txns ...transactions.SignedTxn txns = []transactions.SignedTxn{{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}}} } ep := NewEvalParams(transactions.WrapSignedTxnsWithAD(txns), makeTestProtoV(version), &transactions.SpecialAddresses{}) + ep.Trace = &strings.Builder{} if empty { // We made an app type in order to get a full ep, but that sets MinTealVersion=2 ep.TxnGroup[0].Txn.Type = "" // set it back @@ -2299,41 +2300,41 @@ func TestExtractFlop(t *testing.T) { err := testPanics(t, `byte 0xf000000000000000 extract 1 8 len`, 5) - require.Contains(t, err.Error(), "extract range beyond length of string") + require.Contains(t, err.Error(), "extraction end 9") err = testPanics(t, `byte 0xf000000000000000 extract 9 0 len`, 5) - require.Contains(t, err.Error(), "extract range beyond length of string") + require.Contains(t, err.Error(), "extraction start 9") err = testPanics(t, `byte 0xf000000000000000 int 4 int 0xFFFFFFFFFFFFFFFE extract3 len`, 5) - require.Contains(t, err.Error(), "extract range beyond length of string") + require.Contains(t, err.Error(), "extraction end exceeds uint64") err = testPanics(t, `byte 0xf000000000000000 int 100 int 2 extract3 len`, 5) - require.Contains(t, err.Error(), "extract range beyond length of string") + require.Contains(t, err.Error(), "extraction start 100") err = testPanics(t, `byte 0xf000000000000000 int 55 extract_uint16`, 5) - require.Contains(t, err.Error(), "extract range beyond length of string") + require.Contains(t, err.Error(), "extraction start 55") err = testPanics(t, `byte 0xf000000000000000 int 9 extract_uint32`, 5) - require.Contains(t, err.Error(), "extract range beyond length of string") + require.Contains(t, err.Error(), "extraction start 9") err = testPanics(t, `byte 0xf000000000000000 int 1 extract_uint64`, 5) - require.Contains(t, err.Error(), "extract range beyond length of string") + require.Contains(t, err.Error(), "extraction end 9") } func TestLoadStore(t *testing.T) { From ea0a98d5152b934348f0f9ca0a92a96da51ef4ae Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 26 May 2022 17:24:07 -0400 Subject: [PATCH 20/30] replace2 and replace3 opcodes --- data/transactions/logic/assembler_test.go | 6 ++- data/transactions/logic/box.go | 16 +++--- data/transactions/logic/box_test.go | 7 ++- data/transactions/logic/doc.go | 5 +- data/transactions/logic/eval.go | 57 ++++++++++++++++++++ data/transactions/logic/evalStateful_test.go | 2 + data/transactions/logic/eval_test.go | 28 +++++++++- data/transactions/logic/opcodes.go | 7 ++- 8 files changed, 111 insertions(+), 17 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 7bb63a31f6..ced46f038e 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -366,6 +366,8 @@ const boxNonsense = ` box_extract box_replace box_del + replace2 4 + replace3 ` const v7Nonsense = v6Nonsense + ` @@ -394,9 +396,9 @@ bn256_pairing const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" -const v7Compiled = v6Compiled + "5c005d018120af060180070123456789abcd4949050198800301234549498480030123454999499a499b" + boxCompiled +const v7Compiled = v6Compiled + "5e005f018120af060180070123456789abcd4949050198800301234549498480030123454999499a499b" + boxCompiled -const boxCompiled = "b9babbbc" +const boxCompiled = "b9babbbc5c045d" var nonsense = map[uint64]string{ 1: v1Nonsense, diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index bbe9b62074..af8bf8a8ef 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -17,7 +17,6 @@ package logic import ( - "errors" "fmt" "github.com/algorand/go-algorand/data/basics" @@ -85,9 +84,9 @@ func opBoxReplace(cx *EvalContext) error { prev := last - 1 // start pprev := prev - 1 // name - name := string(cx.stack[pprev].Bytes) - start := cx.stack[prev].Uint replacement := cx.stack[last].Bytes + start := cx.stack[prev].Uint + name := string(cx.stack[pprev].Bytes) if !cx.availableBox(name) { return fmt.Errorf("invalid Box reference %v", name) @@ -97,14 +96,13 @@ func opBoxReplace(cx *EvalContext) error { return err } - end := start + uint64(len(replacement)) - if start > uint64(len(box)) || end > uint64(len(box)) { - return errors.New("replace range beyond box") + bytes, err := replaceCarefully([]byte(box), replacement, start) + if err != nil { + return err } - clone := []byte(box) - copy(clone[start:end], replacement) + cx.stack[prev].Bytes = bytes cx.stack = cx.stack[:pprev] - return cx.Ledger.SetBox(cx.appID, name, string(clone)) + return cx.Ledger.SetBox(cx.appID, name, string(bytes)) } func opBoxDel(cx *EvalContext) error { diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 2e6f9e9778..9d65aae763 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -80,7 +80,12 @@ func TestBoxReadWrite(t *testing.T) { byte 0x00303132; ==`, ep) logic.TestApp(t, `byte "self"; int 1; byte 0x30313233; box_replace; byte "self"; int 0; int 4; box_extract; - byte 0x0030313233; ==`, ep, "replace range") + byte 0x0030313233; ==`, ep, "replacement end 5") + + // Replace with different byte in different place. + logic.TestApp(t, `byte "self"; int 0; byte 0x4444; box_replace; + byte "self"; int 0; int 4; box_extract; + byte 0x44443132; ==`, ep) } func TestBoxAcrossTxns(t *testing.T) { diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 33c2cf13fb..b102a48377 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -144,6 +144,8 @@ var opDocByName = map[string]string{ "extract_uint16": "A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails", "extract_uint32": "A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails", "extract_uint64": "A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails", + "replace2": "Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)", + "replace3": "Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A)", "base64_decode": "decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E", "balance": "get balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted.", @@ -234,6 +236,7 @@ var opcodeImmediateNotes = map[string]string{ "substring": "{uint8 start position} {uint8 end position}", "extract": "{uint8 start position} {uint8 length}", + "replace2": "{uint8 start position}", "dig": "{uint8 depth}", "cover": "{uint8 depth}", "uncover": "{uint8 depth}", @@ -331,7 +334,7 @@ func OpDocExtra(opName string) string { // opcodes consecutively, even if their opcode values are not. var OpGroups = map[string][]string{ "Arithmetic": {"sha256", "keccak256", "sha512_256", "sha3_256", "ed25519verify", "ed25519verify_bare", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "bn256_add", "bn256_scalar_mul", "bn256_pairing", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"}, - "Byte Array Manipulation": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64", "base64_decode", "json_ref"}, + "Byte Array Manipulation": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64", "base64_decode", "replace2", "replace3", "json_ref"}, "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%", "bsqrt"}, "Byte Array Logic": {"b|", "b&", "b^", "b~"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"}, diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 533c7b563e..b08a004fe3 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -3535,6 +3535,63 @@ func opExtract3(cx *EvalContext) error { return err } +func replaceCarefully(original []byte, replacement []byte, start uint64) ([]byte, error) { + if start > uint64(len(original)) { + return nil, fmt.Errorf("replacement start %d beyond length: %d", start, len(original)) + } + end := start + uint64(len(replacement)) + if end < start { // impossible because it is sum of two avm value lengths + return nil, fmt.Errorf("replacement end exceeds uint64") + } + + if end > uint64(len(original)) { + return nil, fmt.Errorf("replacement end %d beyond original length: %d", end, len(original)) + } + + // Do NOT use the append trick to make a copy here. + // append(nil, []byte{}...) would return a nil, which means "not a bytearray" to AVM. + clone := make([]byte, len(original)) + copy(clone[:start], original) + copy(clone[start:end], replacement) + copy(clone[end:], original[end:]) + return clone, nil +} + +func opReplace2(cx *EvalContext) error { + last := len(cx.stack) - 1 // replacement + prev := last - 1 // original + + replacement := cx.stack[last].Bytes + start := uint64(cx.program[cx.pc+1]) + original := cx.stack[prev].Bytes + + bytes, err := replaceCarefully(original, replacement, start) + if err != nil { + return err + } + cx.stack[prev].Bytes = bytes + cx.stack = cx.stack[:last] + return err +} + +func opReplace3(cx *EvalContext) error { + last := len(cx.stack) - 1 // replacement + prev := last - 1 // start + pprev := prev - 1 // original + + replacement := cx.stack[last].Bytes + start := cx.stack[prev].Uint + original := cx.stack[pprev].Bytes + + bytes, err := replaceCarefully(original, replacement, start) + if err != nil { + return err + } + cx.stack[pprev].Bytes = bytes + cx.stack = cx.stack[:prev] + return err +} + // We convert the bytes manually here because we need to accept "short" byte arrays. // A single byte is a legal uint64 decoded this way. func convertBytesToInt(x []byte) uint64 { diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 4b5c843dba..83f62960af 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2344,6 +2344,8 @@ func TestReturnTypes(t *testing.T) { "substring": "substring 0 2", "extract_uint32": ": byte 0x0102030405; int 1; extract_uint32", "extract_uint64": ": byte 0x010203040506070809; int 1; extract_uint64", + "replace2": ": byte 0x0102030405; byte 0x0809; replace2 2", + "replace3": ": byte 0x0102030405; int 2; byte 0x0809; replace3", "asset_params_get": "asset_params_get AssetUnitName", "asset_holding_get": "asset_holding_get AssetBalance", "gtxns": "gtxns Sender", diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 4cdac9812b..cd3c4a90dc 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2170,8 +2170,8 @@ int 0x310 func TestStringOps(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + testAccepts(t, `byte 0x123456789abc substring 1 3 byte 0x3456 @@ -2267,6 +2267,7 @@ len`, 2) func TestExtractOp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + testAccepts(t, "byte 0x123456789abc; extract 1 2; byte 0x3456; ==", 5) testAccepts(t, "byte 0x123456789abc; extract 0 6; byte 0x123456789abc; ==", 5) testAccepts(t, "byte 0x123456789abc; extract 3 0; byte 0x789abc; ==", 5) @@ -2337,10 +2338,33 @@ func TestExtractFlop(t *testing.T) { require.Contains(t, err.Error(), "extraction end 9") } -func TestLoadStore(t *testing.T) { +func TestReplace(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() + + testAccepts(t, `byte 0x11111111; byte 0x2222; replace2 0; byte 0x22221111; ==`, 7) + testAccepts(t, `byte 0x11111111; byte 0x2222; replace2 1; byte 0x11222211; ==`, 7) + testAccepts(t, `byte 0x11111111; byte 0x2222; replace2 2; byte 0x11112222; ==`, 7) + testPanics(t, `byte 0x11111111; byte 0x2222; replace2 3; byte 0x11112222; ==`, 7) + + testAccepts(t, `byte 0x11111111; int 0; byte 0x2222; replace3; byte 0x22221111; ==`, 7) + testAccepts(t, `byte 0x11111111; int 1; byte 0x2222; replace3; byte 0x11222211; ==`, 7) + testAccepts(t, `byte 0x11111111; int 2; byte 0x2222; replace3; byte 0x11112222; ==`, 7) + testPanics(t, `byte 0x11111111; int 3; byte 0x2222; replace3; byte 0x11112222; ==`, 7) + testAccepts(t, `byte 0x11111111; int 0; byte 0x; replace3; byte 0x11111111; ==`, 7) + testAccepts(t, `byte 0x11111111; int 1; byte 0x; replace3; byte 0x11111111; ==`, 7) + testAccepts(t, `byte 0x11111111; int 2; byte 0x; replace3; byte 0x11111111; ==`, 7) + testAccepts(t, `byte 0x11111111; int 3; byte 0x; replace3; byte 0x11111111; ==`, 7) + + testAccepts(t, `byte 0x; byte 0x; replace2 0; byte 0x; ==`, 7) + testAccepts(t, `byte 0x; int 0; byte 0x; replace3; byte 0x; ==`, 7) +} + +func TestLoadStore(t *testing.T) { + partitiontest.PartitionTest(t) t.Parallel() + testAccepts(t, "load 3; int 0; ==;", 1) testAccepts(t, `int 37 diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 9952002ee1..0249f70ba9 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -493,8 +493,11 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, proto("bi:i"), 5, opDefault()}, {0x5a, "extract_uint32", opExtract32Bits, proto("bi:i"), 5, opDefault()}, {0x5b, "extract_uint64", opExtract64Bits, proto("bi:i"), 5, opDefault()}, - {0x5c, "base64_decode", opBase64Decode, proto("b:b"), fidoVersion, field("e", &Base64Encodings).costByLength(1, 1, 16)}, - {0x5d, "json_ref", opJSONRef, proto("bb:a"), fidoVersion, field("r", &JSONRefTypes)}, + {0x5c, "replace2", opReplace2, proto("bb:b"), 7, immediates("s")}, + {0x5d, "replace3", opReplace3, proto("bib:b"), 7, opDefault()}, + + {0x5e, "base64_decode", opBase64Decode, proto("b:b"), fidoVersion, field("e", &Base64Encodings).costByLength(1, 1, 16)}, + {0x5f, "json_ref", opJSONRef, proto("bb:a"), fidoVersion, field("r", &JSONRefTypes)}, {0x60, "balance", opBalance, proto("i:i"), 2, only(modeApp)}, {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(modeApp)}, From 88ec16ba91ad0b181f5c55f2fb838a145392521d Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 27 May 2022 14:04:43 -0400 Subject: [PATCH 21/30] codegen and CR --- daemon/algod/api/algod.oas2.json | 2 +- daemon/algod/api/algod.oas3.yml | 2 +- .../algod/api/server/v2/generated/routes.go | 100 +++++++++--------- daemon/algod/api/server/v2/handlers.go | 2 +- data/transactions/json.go | 5 +- data/transactions/logic/README.md | 2 + data/transactions/logic/TEAL_opcodes.md | 18 +++- data/transactions/logic/langspec.json | 25 ++++- data/transactions/logic/teal.tmLanguage.json | 2 +- ledger/internal/boxtxn_test.go | 6 +- 10 files changed, 103 insertions(+), 61 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index ba11ccb17a..48f30337a7 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1311,7 +1311,7 @@ }, "/v2/applications/{application-id}/boxes/{box-name}": { "get": { - "description": "Given an application ID and box name, it returns box information including box name and base64 encoded content.", + "description": "Given an application ID and box name, it returns the box name and value (each base64 encoded).", "produces": [ "application/json" ], diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index c2b5699321..c3a9452f7a 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -2411,7 +2411,7 @@ }, "/v2/applications/{application-id}/boxes/{box-name}": { "get": { - "description": "Given an application ID and box name, it returns box information including box name and base64 encoded content.", + "description": "Given an application ID and box name, it returns the box name and value (each base64 encoded).", "operationId": "GetApplicationBoxByName", "parameters": [ { diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go index 35fcd6a0aa..44378067da 100644 --- a/daemon/algod/api/server/v2/generated/routes.go +++ b/daemon/algod/api/server/v2/generated/routes.go @@ -983,56 +983,56 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index cb0750869f..8517a6435f 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1132,7 +1132,7 @@ func (v2 *Handlers) GetApplicationBoxByName(ctx echo.Context, applicationID uint } if value == nil { - return notFound(ctx, errors.New(errBoxDoesNotExist), errAppDoesNotExist, v2.Log) + return notFound(ctx, errors.New(errBoxDoesNotExist), errBoxDoesNotExist, v2.Log) } response := generated.BoxResponse{ Name: []byte(boxName), diff --git a/data/transactions/json.go b/data/transactions/json.go index 98d1e69c9c..d46961de52 100644 --- a/data/transactions/json.go +++ b/data/transactions/json.go @@ -50,7 +50,10 @@ func (br BoxRef) MarshalJSON() ([]byte, error) { // UnmarshalJSON makes it so BoxRefs read u64 names func (br *BoxRef) UnmarshalJSON(data []byte) error { var x boxRefStringly - protocol.DecodeJSON(data, &x) + err := protocol.DecodeJSON(data, &x) + if err != nil { + return err + } br.Index = x.Index br.Name = string(x.Name) return nil diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index e7e0e4177c..88f11c12ae 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -328,6 +328,8 @@ return stack matches the name of the input value. | `extract_uint32` | A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails | | `extract_uint64` | A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails | | `base64_decode e` | decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E | +| `replace2 s` | Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A) | +| `replace3` | Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A) | | `json_ref r` | return key B's value from a [valid](jsonspec.md) utf-8 encoded json object A | The following opcodes take byte-array values that are interpreted as diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index dc197e5ec1..fbb146c3a9 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -746,9 +746,23 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails - Availability: v5 +## replace2 s + +- Opcode: 0x5c {uint8 start position} +- Stack: ..., A: []byte, B: []byte → ..., []byte +- Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A) +- Availability: v7 + +## replace3 + +- Opcode: 0x5d +- Stack: ..., A: []byte, B: uint64, C: []byte → ..., []byte +- Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A) +- Availability: v7 + ## base64_decode e -- Opcode: 0x5c {uint8 encoding index} +- Opcode: 0x5e {uint8 encoding index} - Stack: ..., A: []byte → ..., []byte - decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E - **Cost**: 1 + 1 per 16 bytes @@ -766,7 +780,7 @@ Decodes A using the base64 encoding E. Specify the encoding with an immediate ar ## json_ref r -- Opcode: 0x5d {string return type} +- Opcode: 0x5f {string return type} - Stack: ..., A: []byte, B: []byte → ..., any - return key B's value from a [valid](jsonspec.md) utf-8 encoded json object A - Availability: v7 diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 7e39716479..8ffecc8536 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -1240,6 +1240,29 @@ }, { "Opcode": 92, + "Name": "replace2", + "Args": "BB", + "Returns": "B", + "Size": 2, + "Doc": "Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)", + "ImmediateNote": "{uint8 start position}", + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 93, + "Name": "replace3", + "Args": "BUB", + "Returns": "B", + "Size": 1, + "Doc": "Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A)", + "Groups": [ + "Byte Array Manipulation" + ] + }, + { + "Opcode": 94, "Name": "base64_decode", "Args": "B", "Returns": "B", @@ -1252,7 +1275,7 @@ ] }, { - "Opcode": 93, + "Opcode": 95, "Name": "json_ref", "Args": "BB", "Returns": ".", diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index e5e27b8894..ef4a1027a7 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -76,7 +76,7 @@ }, { "name": "keyword.operator.teal", - "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|bn256_add|bn256_pairing|bn256_scalar_mul|btoi|concat|divmodw|divw|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|exp|expw|getbit|getbyte|itob|keccak256|len|mulw|setbit|setbyte|sha256|sha3_256|sha512_256|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|extract|extract3|extract_uint16|extract_uint32|extract_uint64|json_ref|substring|substring3|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" + "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|bn256_add|bn256_pairing|bn256_scalar_mul|btoi|concat|divmodw|divw|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|exp|expw|getbit|getbyte|itob|keccak256|len|mulw|setbit|setbyte|sha256|sha3_256|sha512_256|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|extract|extract3|extract_uint16|extract_uint32|extract_uint64|json_ref|replace2|replace3|substring|substring3|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" } ] }, diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go index 0cba0bad0a..e02ff5dd18 100644 --- a/ledger/internal/boxtxn_test.go +++ b/ledger/internal/boxtxn_test.go @@ -278,12 +278,12 @@ func TestBoxRW(t *testing.T) { dl.txn(call.Args("check", "x", "ABC"), "x does not exist") dl.txn(call.Args("create", "x", "\x08")) dl.txn(call.Args("check", "x", "\x00")) // it was cleared - dl.txn(call.Args("set", "x", "ABCDEFGHIJ"), "replace range") + dl.txn(call.Args("set", "x", "ABCDEFGHIJ"), "replacement end 10") dl.txn(call.Args("check", "x", "\x00")) // still clear dl.txn(call.Args("set", "x", "ABCDEFGH")) - dl.txn(call.Args("check", "x", "ABCDEFGH\x00"), "extract range") + dl.txn(call.Args("check", "x", "ABCDEFGH\x00"), "extraction end 9") dl.txn(call.Args("check", "x", "ABCDEFGH")) - dl.txn(call.Args("set", "x", "ABCDEFGHI"), "replace range") + dl.txn(call.Args("set", "x", "ABCDEFGHI"), "replacement end 9") // Advance more than 320 rounds, ensure box is still there for i := 0; i < 330; i++ { From e7b20e983dc2b5205cf7f08b5e48177cc4916cdf Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 31 May 2022 12:55:42 -0400 Subject: [PATCH 22/30] Add proper locking, and loop in case of db update. --- ledger/accountdb.go | 24 ++++++-- ledger/acctupdates.go | 133 ++++++++++++++++++++++++++---------------- 2 files changed, 102 insertions(+), 55 deletions(-) diff --git a/ledger/accountdb.go b/ledger/accountdb.go index 5ce958031b..3c864cb4f2 100644 --- a/ledger/accountdb.go +++ b/ledger/accountdb.go @@ -1876,7 +1876,7 @@ func accountsInitDbQueries(r db.Queryable, w db.Queryable) (*accountsDbQueries, return nil, err } - qs.lookupKvPairStmt, err = r.Prepare("SELECT value FROM kvstore WHERE key = ?") + qs.lookupKvPairStmt, err = r.Prepare("SELECT rnd, value FROM acctrounds LEFT JOIN kvstore ON key = ? WHERE id='acctbase';") if err != nil { return nil, err } @@ -1961,17 +1961,29 @@ func (qs *accountsDbQueries) listCreatables(maxIdx basics.CreatableIndex, maxRes return } -func (qs *accountsDbQueries) lookupKvPair(key string) (value string, ok bool, err error) { +type persistedValue struct { + value *string + round basics.Round +} + +func (qs *accountsDbQueries) lookupKeyValue(key string) (pv persistedValue, err error) { err = db.Retry(func() error { - err := qs.lookupKvPairStmt.QueryRow(key).Scan(&value) + var v sql.NullString + err := qs.lookupKvPairStmt.QueryRow(key).Scan(&pv.round, &v) + fmt.Printf("lookupKeyValue(%s) %+v %+v\n", key, pv, v) if err != nil { + // this should never happen; it indicates that we don't have a current round in the acctrounds table. if err == sql.ErrNoRows { - return nil // value and ok remain zero values + // Return the zero value of data + err = fmt.Errorf("unable to query value for key %v : %w", key, err) } return err } - // value has been set by Scan - ok = true + if v.Valid { // We got a non-null value, so it exists + pv.value = &v.String + return nil + } + // we don't have that key, just return pv with the database round (pv.value==nil) return nil }) return diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 3203467573..561c22dea4 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -320,65 +320,100 @@ func (au *accountUpdates) LookupResource(rnd basics.Round, addr basics.Address, } func (au *accountUpdates) LookupKv(rnd basics.Round, key string) (*string, error) { - // TODO: Learn how locking discipline works in lookupResource / - // LookupWithoutRewards, particularly the optional lock taking. - - // TODO lookupResource / lookupWithoutRewards uses a loop and round checking - // here. It _looks_ like that only loops in case of programming error? - // Maybe it has to do with outstanding writes that haven't hit db yet. Is - // that possible, since we don't trim delta until it happens? - currentDbRound := au.cachedDBRound - currentDeltaLen := len(au.deltas) - offset, err := au.roundOffset(rnd) - if err != nil { - return nil, err + return au.lookupKv(rnd, key, true /* take lock */) +} + +func (au *accountUpdates) lookupKv(rnd basics.Round, key string, synchronized bool) (*string, error) { + needUnlock := false + if synchronized { + au.accountsMu.RLock() + needUnlock = true } + defer func() { + if needUnlock { + au.accountsMu.RUnlock() + } + }() + + // TODO: This loop and round handling is copied from other routines like + // lookupResource. I believe that it is overly cautious, as it always reruns + // the lookup if the DB round does not match the expected round. However, as + // long as the db round has not advanced too far (greater than `rnd`), I + // believe it would be valid to use. In the interest of minimizing changes, + // I'm not doing that now. - // check if we have this key in `kvStore`, as that means the change we - // care about is in kvDeltas (and maybe just kvStore itself) - mval, indeltas := au.kvStore[key] - if indeltas { - // Check if this is the most recent round, in which case, we can - // use a cache of the most recent kvStore state - if offset == uint64(len(au.kvDeltas)) { - return mval.data, nil + for { + currentDbRound := au.cachedDBRound + currentDeltaLen := len(au.deltas) + offset, err := au.roundOffset(rnd) + if err != nil { + return nil, err } - // the key is in the deltas, but we don't know if it appears in the - // delta range of [0..offset], so we'll need to check. Walk deltas - // backwards so later updates take priority. - for i := offset - 1; i > 0; i-- { - mval, ok := au.kvDeltas[i][key] - if ok { - return mval, nil + // check if we have this key in `kvStore`, as that means the change we + // care about is in kvDeltas (and maybe just kvStore itself) + mval, indeltas := au.kvStore[key] + if indeltas { + // Check if this is the most recent round, in which case, we can + // use a cache of the most recent kvStore state + if offset == uint64(len(au.kvDeltas)) { + return mval.data, nil } + + // the key is in the deltas, but we don't know if it appears in the + // delta range of [0..offset], so we'll need to check. Walk deltas + // backwards so later updates take priority. + for i := offset - 1; i > 0; i-- { + mval, ok := au.kvDeltas[i][key] + if ok { + return mval, nil + } + } + } else { + // we know that the key in not in kvDeltas - so there is no point in scanning it. + // we've going to fall back to search in the database, but before doing so, we should + // update the rnd so that it would point to the end of the known delta range. + // ( that would give us the best validity range ) + rnd = currentDbRound + basics.Round(currentDeltaLen) } - } else { - // we know that the key in not in kvDeltas - so there is no point in scanning it. - // we've going to fall back to search in the database, but before doing so, we should - // update the rnd so that it would point to the end of the known delta range. - // ( that would give us the best validity range ) - rnd = currentDbRound + basics.Round(currentDeltaLen) - // TODO: THIS IS POINTLESS FOR KV's current interface. I don't know - // how the validity window is used yet. -jj - } - // OTHER LOOKUPS USE "base" caches here. + // OTHER LOOKUPS USE "base" caches here. + + if synchronized { + au.accountsMu.RUnlock() + needUnlock = false + } - // No updates of this account in kvDeltas; use on-disk DB. The check in - // roundOffset() made sure the round is exactly the one present in the - // on-disk DB. As an optimization, we avoid creating a separate - // transaction here, and directly use a prepared SQL query against the - // database. + // No updates of this account in kvDeltas; use on-disk DB. The check in + // roundOffset() made sure the round is exactly the one present in the + // on-disk DB. + + persistedData, err := au.accountsq.lookupKeyValue(key) + if persistedData.round == currentDbRound { + return persistedData.value, nil + } + + // The db round is unepxected... + if synchronized { + if persistedData.round < currentDbRound { + // Somehow the db is LOWER than it should be. + au.log.Errorf("accountUpdates.lookupKvPair: database round %d is behind in-memory round %d", persistedData.round, currentDbRound) + return nil, &StaleDatabaseRoundError{databaseRound: persistedData.round, memoryRound: currentDbRound} + } + // The db is higher, so a write must have happened. Try again. + au.accountsMu.RLock() + needUnlock = true + // WHY BOTH - seems the goal is just to wait until the au is aware of progress. au.cachedDBRound should be enough? + for currentDbRound >= au.cachedDBRound && currentDeltaLen == len(au.deltas) { + au.accountsReadCond.Wait() + } + } else { + // in non-sync mode, we don't wait since we already assume that we're synchronized. + au.log.Errorf("accountUpdates.lookupKvPair: database round %d mismatching in-memory round %d", persistedData.round, currentDbRound) + return nil, &MismatchingDatabaseRoundError{databaseRound: persistedData.round, memoryRound: currentDbRound} + } - persistedValue, ok, err := au.accountsq.lookupKvPair(key) - if err != nil { - return nil, err - } - if !ok { - return nil, nil } - return &persistedValue, nil } // LookupWithoutRewards returns the account data for a given address at a given round. From beea6dd8bcdb959bbba10ec725596e63fa8fa10d Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 6 Jun 2022 23:39:09 -0400 Subject: [PATCH 23/30] Impose limits on reading and writing of box bytes --- cmd/goal/application.go | 2 +- config/consensus.go | 16 +- data/transactions/application.go | 4 +- data/transactions/json.go | 60 ------- data/transactions/json_test.go | 27 ++- data/transactions/logic/assembler.go | 22 +-- data/transactions/logic/assembler_test.go | 41 ++++- data/transactions/logic/box.go | 96 +++++++--- data/transactions/logic/box_test.go | 122 ++++++++++++- data/transactions/logic/eval.go | 119 +++++++++++-- data/transactions/logic/evalStateful_test.go | 15 +- data/transactions/logic/eval_test.go | 115 +++++------- data/transactions/logic/ledger_test.go | 100 +++++++---- data/transactions/msgp_gen.go | 177 +++---------------- data/transactions/msgp_gen_test.go | 60 ------- data/transactions/transaction.go | 10 +- data/transactions/transaction_test.go | 6 +- ledger/internal/applications.go | 11 +- ledger/internal/boxtxn_test.go | 16 +- 19 files changed, 528 insertions(+), 491 deletions(-) delete mode 100644 data/transactions/json.go diff --git a/cmd/goal/application.go b/cmd/goal/application.go index c29034e119..08497552db 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -359,7 +359,7 @@ func translateBoxRefs(input []boxRef, foreignApps []uint64) []transactions.BoxRe } output[i] = transactions.BoxRef{ Index: index, - Name: string(rawName), + Name: rawName, } } return output diff --git a/config/consensus.go b/config/consensus.go index a86a7871f8..420c2bb9c1 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -342,7 +342,14 @@ type ConsensusParams struct { BoxByteMinBalance uint64 // Number of box references allowed - MaxAppBoxReferences uint64 + MaxAppBoxReferences int + + // Amount added to a txgroup's box I/O budget per box ref supplied. + // For reads: the sum of the sizes of all boxes in the group must be less than I/O budget + // For writes: the sum of the sizes of all boxes created or written must be less than I/O budget + // In both cases, what matters is the sizes of the boxes touched, not the + // number of times they are touched, or the size of the touches. + BytesPerBoxReference uint64 // maximum number of total key/value pairs allowed by a given // LocalStateSchema (and therefore allowed in LocalState) @@ -1163,10 +1170,11 @@ func initConsensusProtocols() { vFuture.EnableSHA256TxnCommitmentHeader = true // Boxes (unlimited global storage) - vFuture.MaxBoxSize = 8096 - vFuture.BoxFlatMinBalance = 2500 - vFuture.BoxByteMinBalance = 400 + vFuture.MaxBoxSize = 4 * 8096 + vFuture.BoxFlatMinBalance = 10000 + vFuture.BoxByteMinBalance = 350 vFuture.MaxAppBoxReferences = 8 + vFuture.BytesPerBoxReference = 8096 Consensus[protocol.ConsensusFuture] = vFuture } diff --git a/data/transactions/application.go b/data/transactions/application.go index 353323f4e6..54b4f79cee 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -122,7 +122,7 @@ type ApplicationCallTxnFields struct { ForeignApps []basics.AppIndex `codec:"apfa,allocbound=encodedMaxForeignApps"` // Boxes are the boxes that can be accessed by this transaction (and others - // in the same group). The Index in the boxRef is the slot of ForeignApps + // in the same group). The Index in the BoxRef is the slot of ForeignApps // that the name is asscoiated with (shifted by 1, so 0 indicates "current // app") Boxes []BoxRef `codec:"apbx,allocbound=encodedMaxBoxes"` @@ -172,7 +172,7 @@ type BoxRef struct { _struct struct{} `codec:",omitempty,omitemptyarray"` Index uint64 `codec:"i"` - Name string `codec:"n"` + Name []byte `codec:"n"` } // Empty indicates whether or not all the fields in the diff --git a/data/transactions/json.go b/data/transactions/json.go deleted file mode 100644 index d46961de52..0000000000 --- a/data/transactions/json.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -/* - This file contains code to adjust the json serialization of - Transaction. Currently the only change from the standard serialization - provided by the codec package is to cause BoxRefs to be serialized with b64 - names, rather than the default for string, which would not properly encode - their binary nature in JSON. - - Had we done it from the beginning, it would be reasonable to do the same for - some other fields that are declared as string, but can contain arbitrary - binary data, such as AssetParams fields like unitname and url. We can't do - that now, to preserve the shape of our REST APIs. But perhaps we can do it - for a v3 REST API. - -*/ - -package transactions - -import "github.com/algorand/go-algorand/protocol" - -type boxRefStringly struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - Index uint64 `codec:"i"` - Name []byte `codec:"n"` -} - -// MarshalJSON makes it so BoxRefs emit b64 encoded names -func (br BoxRef) MarshalJSON() ([]byte, error) { - return protocol.EncodeJSON(boxRefStringly{ - Index: br.Index, - Name: []byte(br.Name), - }), nil -} - -// UnmarshalJSON makes it so BoxRefs read u64 names -func (br *BoxRef) UnmarshalJSON(data []byte) error { - var x boxRefStringly - err := protocol.DecodeJSON(data, &x) - if err != nil { - return err - } - br.Index = x.Index - br.Name = string(x.Name) - return nil -} diff --git a/data/transactions/json_test.go b/data/transactions/json_test.go index d480d0834b..ef969ff206 100644 --- a/data/transactions/json_test.go +++ b/data/transactions/json_test.go @@ -16,6 +16,12 @@ package transactions_test +/* These tests are pretty low-value now. They test something very basic about + our codec for encoding []byte as base64 strings in json. The test were + written we BoxRef contained a string instead of []byte. When that was true, + these tests were more important because there was work that had to be done to + make it happen (implement MarshalJSON and UnmarshalJSON) */ + import ( "strings" "testing" @@ -41,16 +47,16 @@ func compact(data []byte) string { // TestJsonMarshal ensures that BoxRef names are b64 encoded, since they may not be characters. func TestJsonMarshal(t *testing.T) { - marshal := protocol.EncodeJSON(transactions.BoxRef{Index: 4, Name: "joe"}) + marshal := protocol.EncodeJSON(transactions.BoxRef{Index: 4, Name: []byte("joe")}) require.Equal(t, `{"i":4,"n":"am9l"}`, compact(marshal)) - marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 0, Name: "joe"}) + marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 0, Name: []byte("joe")}) require.Equal(t, `{"n":"am9l"}`, compact(marshal)) - marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 1, Name: ""}) + marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 1, Name: []byte("")}) require.Equal(t, `{"i":1}`, compact(marshal)) - marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 0, Name: ""}) + marshal = protocol.EncodeJSON(transactions.BoxRef{Index: 0, Name: []byte("")}) require.Equal(t, `{}`, compact(marshal)) } @@ -59,16 +65,19 @@ func TestJsonUnmarshal(t *testing.T) { var br transactions.BoxRef decode(`{"i":4,"n":"am9l"}`, &br) - require.Equal(t, transactions.BoxRef{Index: 4, Name: "joe"}, br) + require.Equal(t, transactions.BoxRef{Index: 4, Name: []byte("joe")}, br) + br = transactions.BoxRef{} decode(`{"n":"am9l"}`, &br) - require.Equal(t, transactions.BoxRef{Index: 0, Name: "joe"}, br) + require.Equal(t, transactions.BoxRef{Index: 0, Name: []byte("joe")}, br) + br = transactions.BoxRef{} decode(`{"i":4}`, &br) - require.Equal(t, transactions.BoxRef{Index: 4, Name: ""}, br) + require.Equal(t, transactions.BoxRef{Index: 4, Name: nil}, br) + br = transactions.BoxRef{} decode(`{}`, &br) - require.Equal(t, transactions.BoxRef{Index: 0, Name: ""}, br) + require.Equal(t, transactions.BoxRef{Index: 0, Name: nil}, br) } // TestTxnJson tests a few more things about how our Transactions get JSON @@ -82,7 +91,7 @@ func TestTxnJson(t *testing.T) { require.Contains(t, compact(marshal), `"snd":"AEBA`) txn = txntest.Txn{ - Boxes: []transactions.BoxRef{{Index: 3, Name: "john"}}, + Boxes: []transactions.BoxRef{{Index: 3, Name: []byte("john")}}, } marshal = protocol.EncodeJSON(txn.Txn()) require.Contains(t, compact(marshal), `"apbx":[{"i":3,"n":"am9obg=="}]`) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 8c9d5955e1..98d845434a 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -452,7 +452,7 @@ func (ops *OpStream) ByteLiteral(val []byte) { func asmInt(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("int needs one argument") + return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) } // check txn type constants i, ok := txnTypeMap[args[0]] @@ -477,7 +477,7 @@ func asmInt(ops *OpStream, spec *OpSpec, args []string) error { // Explicit invocation of const lookup and push func asmIntC(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("intc operation needs one argument") + return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) } constIndex, err := simpleImm(args[0], "constant") if err != nil { @@ -488,7 +488,7 @@ func asmIntC(ops *OpStream, spec *OpSpec, args []string) error { } func asmByteC(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("bytec operation needs one argument") + return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) } constIndex, err := simpleImm(args[0], "constant") if err != nil { @@ -500,7 +500,7 @@ func asmByteC(ops *OpStream, spec *OpSpec, args []string) error { func asmPushInt(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.errorf("%s needs one argument", spec.Name) + return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) } val, err := strconv.ParseUint(args[0], 0, 64) if err != nil { @@ -514,14 +514,14 @@ func asmPushInt(ops *OpStream, spec *OpSpec, args []string) error { } func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error { if len(args) == 0 { - return ops.errorf("%s operation needs byte literal argument", spec.Name) + return ops.errorf("%s needs byte literal argument", spec.Name) } val, consumed, err := parseBinaryArgs(args) if err != nil { return ops.error(err) } if len(args) != consumed { - return ops.errorf("%s operation with extraneous argument", spec.Name) + return ops.errorf("%s with extraneous argument", spec.Name) } ops.pending.WriteByte(spec.Opcode) var scratch [binary.MaxVarintLen64]byte @@ -680,14 +680,14 @@ func parseStringLiteral(input string) (result []byte, err error) { // byte "this is a string\n" func asmByte(ops *OpStream, spec *OpSpec, args []string) error { if len(args) == 0 { - return ops.errorf("%s operation needs byte literal argument", spec.Name) + return ops.errorf("%s needs byte literal argument", spec.Name) } val, consumed, err := parseBinaryArgs(args) if err != nil { return ops.error(err) } if len(args) != consumed { - return ops.errorf("%s operation with extraneous argument", spec.Name) + return ops.errorf("%s with extraneous argument", spec.Name) } ops.ByteLiteral(val) return nil @@ -773,7 +773,7 @@ func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error { // parses base32-with-checksum account address strings into a byte literal func asmAddr(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("addr operation needs one argument") + return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) } addr, err := basics.UnmarshalChecksumAddress(args[0]) if err != nil { @@ -785,7 +785,7 @@ func asmAddr(ops *OpStream, spec *OpSpec, args []string) error { func asmArg(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("arg operation needs one argument") + return ops.errorf("%s needs one immediate argument, was given %d", spec.Name, len(args)) } val, err := simpleImm(args[0], "argument") if err != nil { @@ -810,7 +810,7 @@ func asmArg(ops *OpStream, spec *OpSpec, args []string) error { func asmBranch(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("branch operation needs label argument") + return ops.errorf("%s needs a single label argument", spec.Name) } ops.referToLabel(ops.pending.Len(), args[0]) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index ced46f038e..158c023aa3 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -763,8 +763,8 @@ func TestAssembleBytes(t *testing.T) { expectedOptimizedConsts := "018006616263646566" bad := [][]string{ - {"byte", "...operation needs byte literal argument"}, - {`byte "john" "doe"`, "...operation with extraneous argument"}, + {"byte", "...needs byte literal argument"}, + {`byte "john" "doe"`, "...with extraneous argument"}, } for v := uint64(1); v <= AssemblerMaxVersion; v++ { @@ -1543,17 +1543,40 @@ func TestConstantArgs(t *testing.T) { t.Parallel() for v := uint64(1); v <= AssemblerMaxVersion; v++ { - testProg(t, "int", v, Expect{1, "int needs one argument"}) - testProg(t, "intc", v, Expect{1, "intc operation needs one argument"}) - testProg(t, "byte", v, Expect{1, "byte operation needs byte literal argument"}) - testProg(t, "bytec", v, Expect{1, "bytec operation needs one argument"}) - testProg(t, "addr", v, Expect{1, "addr operation needs one argument"}) + testProg(t, "int", v, Expect{1, "int needs one immediate argument, was given 0"}) + testProg(t, "int 1 2", v, Expect{1, "int needs one immediate argument, was given 2"}) + testProg(t, "intc", v, Expect{1, "intc needs one immediate argument, was given 0"}) + testProg(t, "intc hi bye", v, Expect{1, "intc needs one immediate argument, was given 2"}) + testProg(t, "byte", v, Expect{1, "byte needs byte literal argument"}) + testProg(t, "bytec", v, Expect{1, "bytec needs one immediate argument, was given 0"}) + testProg(t, "bytec 1 x", v, Expect{1, "bytec needs one immediate argument, was given 2"}) + testProg(t, "addr", v, Expect{1, "addr needs one immediate argument, was given 0"}) + testProg(t, "addr x y", v, Expect{1, "addr needs one immediate argument, was given 2"}) } for v := uint64(3); v <= AssemblerMaxVersion; v++ { - testProg(t, "pushint", v, Expect{1, "pushint needs one argument"}) - testProg(t, "pushbytes", v, Expect{1, "pushbytes operation needs byte literal argument"}) + testProg(t, "pushint", v, Expect{1, "pushint needs one immediate argument, was given 0"}) + testProg(t, "pushint 3 4", v, Expect{1, "pushint needs one immediate argument, was given 2"}) + testProg(t, "pushbytes", v, Expect{1, "pushbytes needs byte literal argument"}) } +} + +func TestBranchArgs(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + for v := uint64(2); v <= AssemblerMaxVersion; v++ { + testProg(t, "b", v, Expect{1, "b needs a single label argument"}) + testProg(t, "b lab1 lab2", v, Expect{1, "b needs a single label argument"}) + testProg(t, "int 1; bz", v, Expect{2, "bz needs a single label argument"}) + testProg(t, "int 1; bz a b", v, Expect{2, "bz needs a single label argument"}) + testProg(t, "int 1; bnz", v, Expect{2, "bnz needs a single label argument"}) + testProg(t, "int 1; bnz c d", v, Expect{2, "bnz needs a single label argument"}) + } + + for v := uint64(4); v <= AssemblerMaxVersion; v++ { + testProg(t, "callsub", v, Expect{1, "callsub needs a single label argument"}) + testProg(t, "callsub one two", v, Expect{1, "callsub needs a single label argument"}) + } } func TestAssembleDisassembleErrors(t *testing.T) { diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index af8bf8a8ef..c83d8e772a 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -22,6 +22,33 @@ import ( "github.com/algorand/go-algorand/data/basics" ) +const ( + boxCreate = iota + boxRead + boxWrite + boxDelete +) + +func (cx *EvalContext) availableBox(name string, operation int, createSize uint64) error { + bt, ok := cx.available.boxes[boxRef{cx.appID, name}] + if !ok { + return fmt.Errorf("invalid Box reference %v", name) + } + switch operation { + case boxCreate: + bt.dirty = true + bt.size = createSize + case boxWrite: + bt.dirty = true + case boxDelete: + bt.size = 0 + case boxRead: + /* nothing to do */ + } + cx.available.boxes[boxRef{cx.appID, name}] = bt + return nil +} + func opBoxCreate(cx *EvalContext) error { last := len(cx.stack) - 1 // name prev := last - 1 // size @@ -29,14 +56,22 @@ func opBoxCreate(cx *EvalContext) error { name := string(cx.stack[last].Bytes) size := cx.stack[prev].Uint - // This is questionable! We need to think about how boxes can be made during - // the txgroup that constructs the app. The app won't be funded at create - // time, but supposing someone uses the "trampoline" technique to fund it in - // a later txn, if an even later txn invokes it, can it create any boxes? - if !cx.availableBox(name) { - return fmt.Errorf("invalid Box reference %v", name) + // Enforce maximums. Currently this is the same as enforced by ledger. If + // these were ever to change in proto, we would need to isolate changes to + // different program versions. (so a v7 app could not see a bigger box than + // expected, for example) + if len(name) > cx.Proto.MaxAppKeyLen { + return fmt.Errorf("name too long: length was %d, maximum is %d", len(name), cx.Proto.MaxAppKeyLen) + } + if size > cx.Proto.MaxBoxSize { + return fmt.Errorf("box size too large: %d, maximum is %d", size, cx.Proto.MaxBoxSize) + } + + err := cx.availableBox(name, boxCreate, size) + if err != nil { + return err } - err := cx.Ledger.NewBox(cx.appID, name, size) + err = cx.Ledger.NewBox(cx.appID, name, size) if err != nil { return err } @@ -45,17 +80,6 @@ func opBoxCreate(cx *EvalContext) error { return nil } -func (cx *EvalContext) availableBox(name string) bool { - if available, ok := cx.available.boxes[cx.appID]; ok { - for _, n := range available { - if name == n { - return true - } - } - } - return false -} - func opBoxExtract(cx *EvalContext) error { last := len(cx.stack) - 1 // length prev := last - 1 // start @@ -65,13 +89,17 @@ func opBoxExtract(cx *EvalContext) error { start := cx.stack[prev].Uint length := cx.stack[last].Uint - if !cx.availableBox(name) { - return fmt.Errorf("invalid Box reference %v", name) + err := cx.availableBox(name, boxRead, 0) + if err != nil { + return err } - box, err := cx.Ledger.GetBox(cx.appID, name) + box, ok, err := cx.Ledger.GetBox(cx.appID, name) if err != nil { return err } + if !ok { + return fmt.Errorf("no such box %#v", name) + } bytes, err := extractCarefully([]byte(box), start, length) cx.stack[pprev].Bytes = bytes @@ -88,13 +116,17 @@ func opBoxReplace(cx *EvalContext) error { start := cx.stack[prev].Uint name := string(cx.stack[pprev].Bytes) - if !cx.availableBox(name) { - return fmt.Errorf("invalid Box reference %v", name) + err := cx.availableBox(name, boxWrite, 0 /* size is already known */) + if err != nil { + return err } - box, err := cx.Ledger.GetBox(cx.appID, name) + box, ok, err := cx.Ledger.GetBox(cx.appID, name) if err != nil { return err } + if !ok { + return fmt.Errorf("no such box %#v", name) + } bytes, err := replaceCarefully([]byte(box), replacement, start) if err != nil { @@ -109,8 +141,9 @@ func opBoxDel(cx *EvalContext) error { last := len(cx.stack) - 1 // name name := string(cx.stack[last].Bytes) - if !cx.availableBox(name) { - return fmt.Errorf("invalid Box reference %v", name) + err := cx.availableBox(name, boxDelete, 0) + if err != nil { + return err } cx.stack = cx.stack[:last] return cx.Ledger.DelBox(cx.appID, name) @@ -118,7 +151,14 @@ func opBoxDel(cx *EvalContext) error { // MakeBoxKey creates the key that a box named `name` under app `appIdx` should use. func MakeBoxKey(appIdx basics.AppIndex, name string) string { - // Reconsider this for something faster. Maybe msgpack encoding of array - // ["bx",appIdx,key]? + // TODO: This format is WRONG! Printf does substitutions for non-unicode Be + // sure to use an encoding that allows for prefixes. For example, msgpack is + // a bad idea, because it encodes the string length ahead of the + // string. "foo" would not be a prefix of "food". A straightforward, + // reasonable encoding might be: bx:[8 byte big endian appid]:[bytes of + // name] The first colon seems important, so all future uses of kvstore have + // an easy way to ensure uniqueness. The second seems less important if the + // appid is fixed length. But maybe the appID should be in ascii, then the + // colon is important. return fmt.Sprintf("bx:%d:%s", appIdx, name) } diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 9d65aae763..266939551b 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -124,25 +124,139 @@ func TestBoxAvailability(t *testing.T) { group := logic.MakeSampleTxnGroup(logic.MakeSampleTxn(), txntest.Txn{ Type: "appl", ApplicationID: 10000, - Boxes: []transactions.BoxRef{{Index: 0, Name: "B"}}, + Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("B")}}, }.SignedTxn()) group[0].Txn.Type = protocol.ApplicationCallTx logic.TestApps(t, []string{ `int 64; byte "self"; box_create; int 1`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, group, 7, ledger, logic.NewExpect(1, "no such app")) + }, group, 7, ledger, logic.NewExpect(1, "no such box")) // B is available if listed by appId in tx[1].Boxes group = logic.MakeSampleTxnGroup(logic.MakeSampleTxn(), txntest.Txn{ Type: "appl", ApplicationID: 10000, ForeignApps: []basics.AppIndex{10000}, - Boxes: []transactions.BoxRef{{Index: 1, Name: "B"}}, + Boxes: []transactions.BoxRef{{Index: 1, Name: []byte("B")}}, }.SignedTxn()) group[0].Txn.Type = protocol.ApplicationCallTx logic.TestApps(t, []string{ `int 64; byte "self"; box_create; int 1`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, - }, group, 7, ledger, logic.NewExpect(1, "no such app")) + }, group, 7, ledger, logic.NewExpect(1, "no such box")) } + +func TestBoxReadBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ledger := logic.NewLedger(nil) + ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) +} + +func TestBoxWriteBudget(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ep, _, ledger := logic.MakeSampleEnv() + ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) + + // Sample tx[0] has two box refs, so write budget is 2*100 + + // Test simple use of one box, less than, equal, or over budget + logic.TestApp(t, `int 4; byte "self"; box_create; + int 1`, ep) + logic.TestApp(t, `byte "self"; box_del; + int 199; byte "self"; box_create; + int 1`, ep) + logic.TestApp(t, `byte "self"; box_del; + int 200; byte "self"; box_create; + int 1`, ep) + logic.TestApp(t, `byte "self"; box_del; + int 201; byte "self"; box_create; + int 1`, ep, "write budget (200) exceeded") + ledger.DelBox(888, "self") // cleanup (doing it in a program would fail b/c the 201 len box exists) + + // Test interplay of two different boxes being created + logic.TestApp(t, `int 4; byte "self"; box_create; + int 4; byte "other"; box_create; + int 1`, ep) + + logic.TestApp(t, `byte "self"; box_del; byte "other"; box_del; + int 4; byte "self"; box_create; + int 196; byte "other"; box_create; + int 1`, ep) + + logic.TestApp(t, `byte "self"; box_del; byte "other"; box_del; + int 6; byte "self"; box_create; + int 196; byte "other"; box_create; + int 1`, ep, "write budget (200) exceeded") + ledger.DelBox(888, "other") + + logic.TestApp(t, `byte "self"; box_del; + int 6; byte "self"; box_create; + int 196; byte "other"; box_create; + byte "self"; box_del; // deletion means we don't pay for write bytes + int 1`, ep) + logic.TestApp(t, `byte "other"; box_del; int 1`, ep) // cleanup (self was already deleted in last test) + + // Create two boxes, that sum to over budget, then test trying to use them together + logic.TestApp(t, `int 101; byte "self"; box_create; + int 1`, ep) + logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + int 101; byte "other"; box_create; + int 1`, ep, "write budget (200) exceeded") + // error was detected, but the TestLedger now has both boxes present + logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + byte "other"; int 1; byte 0x3333; box_replace; + int 1`, ep, "read budget (200) exceeded") + ledger.DelBox(888, "other") + logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + int 10; byte "other"; box_create + int 1`, ep) + // They're now small enough to read and write + logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + byte "other"; int 1; byte 0x3333; box_replace; + int 1`, ep) + // writing twice is no problem (even though it's the big one) + logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + byte "self"; int 50; byte 0x3333; box_replace; + byte "other"; int 1; byte 0x3333; box_replace; + int 1`, ep) + + logic.TestApp(t, `byte "self"; box_del; byte "other"; box_del; int 1`, ep) // cleanup + +} + +func TestIOBudgetGrow(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ep, txn, ledger := logic.MakeSampleEnv() + ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) + ledger.NewBox(888, "self", 101) + ledger.NewBox(888, "other", 101) + + logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + byte "other"; int 1; byte 0x3333; box_replace; + int 1`, ep, "read budget (200) exceeded") + + txn.Boxes = append(txn.Boxes, transactions.BoxRef{}) + // Since we added an empty BoxRef, we can read > 200. + logic.TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; + byte "other"; int 1; int 7; box_extract; pop; + int 1`, ep) + // Add write, for that matter + logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; + byte "other"; int 1; byte 0x3333; box_replace; + int 1`, ep) + + txn.Boxes = append(txn.Boxes, transactions.BoxRef{Name: []byte("another")}) + + // Here we read 202, and write a very different 350 (since we now have 4 brs) + logic.TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; + byte "other"; int 1; int 7; box_extract; pop; + int 350; byte "another"; box_create; + int 1`, ep) +} diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index b08a004fe3..a96c69cb1f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -215,7 +215,7 @@ type LedgerForLogic interface { DelGlobal(appIdx basics.AppIndex, key string) error NewBox(appIdx basics.AppIndex, key string, size uint64) error - GetBox(appIdx basics.AppIndex, key string) (string, error) + GetBox(appIdx basics.AppIndex, key string) (string, bool, error) SetBox(appIdx basics.AppIndex, key string, value string) error DelBox(appIdx basics.AppIndex, key string) error @@ -233,7 +233,21 @@ type resources struct { // boxes are all of the top-level box refs from the txgroup. Most are added // during NewEvalParams(). refs using 0 on an appl create are resolved and // added when the appl executes. - boxes map[basics.AppIndex][]string + boxes map[boxRef]boxTouch +} + +// boxRef is the "hydrated" form of a BoxRef - it has the actual app id, not an index +type boxRef struct { + app basics.AppIndex + name string +} + +// boxTouch tracks whether how many write bytes must be charged for the +// box. touches are marked dirty if they are written to, or created. size is +// set during the initial read I/O check, or at create time. +type boxTouch struct { + dirty bool + size uint64 } // EvalParams contains data that comes into condition evaluation. @@ -279,6 +293,13 @@ type EvalParams struct { // availability across all txns in the group. available *resources + // ioBudget is the number of bytes that the box ref'd boxes can sum to, and + // the number of bytes that created or written boxes may sum to. + ioBudget uint64 + + // readBudgetChecked allows us to only check the read budget once + readBudgetChecked bool + // Caching these here means the hashes can be shared across the TxnGroup // (and inners, because the cache is shared with the inner EvalParams) appAddrCache map[basics.AppIndex]basics.Address @@ -304,19 +325,19 @@ func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.Sign // NewEvalParams creates an EvalParams to use while evaluating a top-level txgroup func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses) *EvalParams { apps := 0 - var allBoxes map[basics.AppIndex][]string + var allBoxes map[boxRef]boxTouch for _, tx := range txgroup { if tx.Txn.Type == protocol.ApplicationCallTx { apps++ if allBoxes == nil && len(tx.Txn.Boxes) > 0 { - allBoxes = make(map[basics.AppIndex][]string) + allBoxes = make(map[boxRef]boxTouch) } for _, br := range tx.Txn.Boxes { var app basics.AppIndex if br.Index == 0 { - // 0 is the "current app". Ignore if this is a create, else use ApplicationID + // "current app": Ignore if this is a create, else use ApplicationID if tx.Txn.ApplicationID == 0 { - // When the create actually happens, and we learn the appID, we'll make it _available_. + // When the create actually happens, and we learn the appID, we'll add it. continue } app = tx.Txn.ApplicationID @@ -326,8 +347,7 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens // now than after returning a nil. app = tx.Txn.ForeignApps[br.Index-1] // shift for the 0=this convention } - appBoxes := allBoxes[app] - allBoxes[app] = append(appBoxes, br.Name) + allBoxes[boxRef{app, string(br.Name)}] = boxTouch{} } } } @@ -664,16 +684,58 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam if cx.txn.Txn.ApplicationID == 0 { for _, br := range cx.txn.Txn.Boxes { if br.Index == 0 { - appBoxes := cx.EvalParams.available.boxes[cx.appID] - cx.EvalParams.available.boxes[cx.appID] = append(appBoxes, br.Name) + cx.EvalParams.available.boxes[boxRef{cx.appID, string(br.Name)}] = boxTouch{} + } + } + } + + // Check the I/O budget for reading if this is the first top-level app call + if cx.caller == nil && !cx.readBudgetChecked { + boxRefCount := uint64(0) // Intentionally counts duplicates + for _, tx := range cx.TxnGroup { + boxRefCount += uint64(len(tx.Txn.Boxes)) + } + cx.ioBudget = boxRefCount * cx.Proto.BytesPerBoxReference + + used := uint64(0) + for br := range cx.available.boxes { + box, ok, err := cx.Ledger.GetBox(br.app, br.name) + if err != nil { + return false, nil, err + } + if !ok { + continue + } + size := uint64(len(box)) + cx.available.boxes[br] = boxTouch{false, size} + + used = basics.AddSaturate(used, size) + if used > cx.ioBudget { + return false, nil, fmt.Errorf("box read budget (%d) exceeded", cx.ioBudget) } } + cx.readBudgetChecked = true } if cx.Trace != nil && cx.caller != nil { fmt.Fprintf(cx.Trace, "--- enter %d %s %v\n", aid, cx.txn.Txn.OnCompletion, cx.txn.Txn.ApplicationArgs) } pass, err := eval(program, &cx) + + // Check the I/O budget for writing. TODO: Only need to do this once if we + // figure out we're the last app call in the group. + if cx.caller == nil { + used := uint64(0) + for _, bt := range cx.available.boxes { + if bt.dirty { + used = basics.AddSaturate(used, bt.size) + } + if used > cx.ioBudget { + return false, nil, fmt.Errorf("box write budget (%d) exceeded", cx.ioBudget) + } + } + } + if cx.Trace != nil && cx.caller != nil { fmt.Fprintf(cx.Trace, "--- exit %d accept=%t\n", aid, pass) } @@ -3881,6 +3943,14 @@ func opAppLocalPut(cx *EvalContext) error { sv := cx.stack[last] key := string(cx.stack[prev].Bytes) + // Enforce maximum key length. Currently this is the same as enforced by + // ledger. If it were ever to change in proto, we would need to isolate + // changes to different program versions. (so a v6 app could not see a + // bigger key, for example) + if len(key) > cx.Proto.MaxAppKeyLen { + return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), cx.Proto.MaxAppKeyLen) + } + addr, accountIdx, err := cx.mutableAccountReference(cx.stack[pprev]) if err != nil { return err @@ -3900,6 +3970,17 @@ func opAppLocalPut(cx *EvalContext) error { } cx.txn.EvalDelta.LocalDeltas[accountIdx][key] = tv.ToValueDelta() } + + // Enforce maximum value length (also enforced by ledger) + if tv.Type == basics.TealBytesType { + if len(tv.Bytes) > cx.Proto.MaxAppBytesValueLen { + return fmt.Errorf("value too long for key 0x%x: length was %d", key, len(tv.Bytes)) + } + if sum := len(key) + len(tv.Bytes); sum > cx.Proto.MaxAppSumKeyValueLens { + return fmt.Errorf("key/value total too long for key 0x%x: sum was %d", key, sum) + } + } + err = cx.Ledger.SetLocal(addr, cx.appID, key, tv, accountIdx) if err != nil { return err @@ -3916,6 +3997,14 @@ func opAppGlobalPut(cx *EvalContext) error { sv := cx.stack[last] key := string(cx.stack[prev].Bytes) + // Enforce maximum key length. Currently this is the same as enforced by + // ledger. If it were ever to change in proto, we would need to isolate + // changes to different program versions. (so a v6 app could not see a + // bigger key, for example) + if len(key) > cx.Proto.MaxAppKeyLen { + return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), cx.Proto.MaxAppKeyLen) + } + // if writing the same value, don't record in EvalDelta, matching ledger // behavior with previous BuildEvalDelta mechanism etv, ok, err := cx.Ledger.GetGlobal(cx.appID, key) @@ -3927,6 +4016,16 @@ func opAppGlobalPut(cx *EvalContext) error { cx.txn.EvalDelta.GlobalDelta[key] = tv.ToValueDelta() } + // Enforce maximum value length (also enforced by ledger) + if tv.Type == basics.TealBytesType { + if len(tv.Bytes) > cx.Proto.MaxAppBytesValueLen { + return fmt.Errorf("value too long for key 0x%x: length was %d", key, len(tv.Bytes)) + } + if sum := len(key) + len(tv.Bytes); sum > cx.Proto.MaxAppSumKeyValueLens { + return fmt.Errorf("key/value total too long for key 0x%x: sum was %d", key, sum) + } + } + err = cx.Ledger.SetGlobal(cx.appID, key, tv) if err != nil { return err diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 83f62960af..70ea6d6c10 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -927,7 +927,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) } } - txn := makeSampleTxn() + txn := makeSampleAppl(888) pre := defaultEvalParamsWithVersion(directRefEnabledVersion-1, txn) require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) now := defaultEvalParamsWithVersion(version, txn) @@ -1294,11 +1294,9 @@ intc_1 func TestAppLocalStateReadWrite(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() - txn := makeSampleTxn() - txn.Txn.ApplicationID = 100 + txn := makeSampleAppl(100) ep := defaultEvalParams(txn) ledger := NewLedger( map[basics.Address]uint64{ @@ -1645,8 +1643,7 @@ int 0x77 == && ` - txn := makeSampleAppl() - txn.Txn.ApplicationID = 100 + txn := makeSampleAppl(100) txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} ep := defaultEvalParams(txn) ledger := NewLedger( @@ -1825,8 +1822,7 @@ app_global_get int 7 == ` - txn := makeSampleTxn() - txn.Txn.ApplicationID = 100 + txn := makeSampleAppl(100) ep := defaultEvalParams(txn) ledger := NewLedger( map[basics.Address]uint64{ @@ -2029,8 +2025,7 @@ err ok: int 1 ` - txn := makeSampleTxn() - txn.Txn.ApplicationID = 100 + txn := makeSampleAppl(100) ep := defaultEvalParams(txn) ledger := NewLedger( map[basics.Address]uint64{ diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index cd3c4a90dc..715702b75d 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -46,12 +46,15 @@ func makeTestProto() *config.ConsensusParams { func makeTestProtoV(version uint64) *config.ConsensusParams { return &config.ConsensusParams{ - LogicSigVersion: version, - LogicSigMaxCost: 20000, - Application: version >= appsEnabledVersion, - MaxAppProgramCost: 700, - MaxAppKeyLen: 64, - MaxAppBytesValueLen: 64, + LogicSigVersion: version, + LogicSigMaxCost: 20000, + Application: version >= appsEnabledVersion, + MaxAppProgramCost: 700, + + MaxAppKeyLen: 64, + MaxAppBytesValueLen: 64, + MaxAppSumKeyValueLens: 128, + // These must be identical to keep an old backward compat test working MinTxnFee: 1001, MinBalance: 1001, @@ -103,6 +106,9 @@ func makeTestProtoV(version uint64) *config.ConsensusParams { SupportBecomeNonParticipatingTransactions: true, UnifyInnerTxIDs: true, + + MaxBoxSize: 1000, + BytesPerBoxReference: 100, } } @@ -138,6 +144,10 @@ func defaultEvalParamsWithVersion(version uint64, txns ...transactions.SignedTxn return ep } +func (ep *EvalParams) isFull() bool { + return ep.available != nil +} + // reset puts an ep back into its original state. This is in *_test.go because // no real code should ever need this. EvalParams should be created to evaluate // a group, and then thrown away. @@ -163,6 +173,7 @@ func (ep *EvalParams) reset() { ep.available.boxes = available.boxes } } + ep.readBudgetChecked = false ep.appAddrCache = make(map[basics.AppIndex]basics.Address) if ep.Trace != nil { ep.Trace = &strings.Builder{} @@ -1037,10 +1048,14 @@ func TestGlobal(t *testing.T) { } } - txn := transactions.SignedTxn{} - txn.Txn.Group = crypto.Digest{0x07, 0x06} + appcall := transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + }, + } + appcall.Txn.Group = crypto.Digest{0x07, 0x06} - ep := defaultEvalParams(txn) + ep := defaultEvalParams(appcall) ep.Ledger = ledger testApp(t, tests[v].program, ep) }) @@ -1539,15 +1554,16 @@ func makeSampleTxn() transactions.SignedTxn { txn.Txn.AssetFrozen = true txn.Txn.ForeignAssets = []basics.AssetIndex{55, 77} txn.Txn.ForeignApps = []basics.AppIndex{56, 100, 111} // 100 must be 2nd, 111 must be present - txn.Txn.Boxes = []transactions.BoxRef{{Index: 0, Name: "self"}, {Index: 0, Name: "other"}} + txn.Txn.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("self")}, {Index: 0, Name: []byte("other")}} txn.Txn.GlobalStateSchema = basics.StateSchema{NumUint: 3, NumByteSlice: 0} txn.Txn.LocalStateSchema = basics.StateSchema{NumUint: 1, NumByteSlice: 2} return txn } -func makeSampleAppl() transactions.SignedTxn { +func makeSampleAppl(app basics.AppIndex) transactions.SignedTxn { sample := makeSampleTxn() sample.Txn.Type = protocol.ApplicationCallTx + sample.Txn.ApplicationID = app return sample } @@ -2433,7 +2449,6 @@ int 5 func TestGload(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() // for simple app-call-only transaction groups @@ -2445,48 +2460,22 @@ func TestGload(t *testing.T) { simpleCase := scratchTestCase{ tealSources: []string{ - ` -int 2 -store 0 -int 1`, - ` -gload 0 0 -int 2 -== -`, + `int 2; store 0; int 1`, + `gload 0 0; int 2; ==`, }, } multipleTxnCase := scratchTestCase{ tealSources: []string{ - ` -byte "txn 1" -store 0 -int 1`, - ` -byte "txn 2" -store 2 -int 1`, - ` -gload 0 0 -byte "txn 1" -== -gload 1 2 -byte "txn 2" -== -&& -`, + `byte "txn 1"; store 0; int 1`, + `byte "txn 2"; store 2; int 1`, + `gload 0 0; byte "txn 1"; ==; gload 1 2; byte "txn 2"; ==; &&`, }, } selfCase := scratchTestCase{ tealSources: []string{ - ` -gload 0 0 -int 2 -store 0 -int 1 -`, + `gload 0 0; int 2; store 0; int 1`, }, errTxn: 0, errContains: "can't use gload on self, use load instead", @@ -2494,14 +2483,8 @@ int 1 laterTxnSlotCase := scratchTestCase{ tealSources: []string{ - ` -gload 1 0 -int 2 -==`, - ` -int 2 -store 0 -int 1`, + `gload 1 0; int 2; ==`, + `int 2; store 0; int 1`, }, errTxn: 0, errContains: "gload can't get future scratch space from txn with index 1", @@ -2559,19 +2542,16 @@ int 1`, failCases := []failureCase{nonAppCall, logicSigCall} for j, failCase := range failCases { t.Run(fmt.Sprintf("j=%d", j), func(t *testing.T) { - program := testProg(t, "gload 0 0", AssemblerMaxVersion).Program - txgroup := []transactions.SignedTxnWithAD{ - {SignedTxn: failCase.firstTxn}, - {}, + appcall := transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + }, } - ep := &EvalParams{ - Proto: makeTestProto(), - TxnGroup: txgroup, - pastScratch: make([]*scratchSpace, 2), - } + ep := defaultEvalParams(failCase.firstTxn, appcall) + program := testProg(t, "gload 0 0", AssemblerMaxVersion).Program switch failCase.runMode { case modeApp: testAppBytes(t, program, ep, failCase.errContains) @@ -3724,7 +3704,6 @@ byte 0x // empty byte constant func TestArgType(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() var sv stackValue @@ -3739,8 +3718,8 @@ func TestArgType(t *testing.T) { func TestApplicationsDisallowOldTeal(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + const source = "int 1" txn := makeSampleTxn() @@ -3759,8 +3738,8 @@ func TestApplicationsDisallowOldTeal(t *testing.T) { func TestAnyRekeyToOrApplicationRaisesMinTealVersion(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + const source = "int 1" // Construct a group of two payments, no rekeying @@ -3809,14 +3788,18 @@ func TestAnyRekeyToOrApplicationRaisesMinTealVersion(t *testing.T) { expected := fmt.Sprintf("program version must be >= %d", cse.validFromVersion) for v := uint64(0); v < cse.validFromVersion; v++ { ops := testProg(t, source, v) - testAppBytes(t, ops.Program, ep, expected, expected) + if ep.isFull() { + testAppBytes(t, ops.Program, ep, expected, expected) + } testLogicBytes(t, ops.Program, ep, expected, expected) } // Should succeed for all versions >= validFromVersion for v := cse.validFromVersion; v <= AssemblerMaxVersion; v++ { ops := testProg(t, source, v) - testAppBytes(t, ops.Program, ep) + if ep.isFull() { + testAppBytes(t, ops.Program, ep) + } testLogicBytes(t, ops.Program, ep) } }) diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 0db9ad1685..6fd9dc455f 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -16,6 +16,20 @@ package logic +/* This Ledger implements LedgerForLogic for unit tests in the logic package. It + does *not* carry the protocol around, so it does *not* enforce the various + limits imposed there. This helps ensure that the logic package itself + enforces those limits, rather than rely on the ledger package. (Which should + also do so, to be defensive.) + + This Ledger is not clever enough to have a good mechanism for making changes + and rolling them back if the program that makes them fails. It just has a + Reset() method that throws away all changes made by programs. Generally, + it's probably best to call Reset() after any error test, though you can keep + testing if you take into account that changes made before the failure will + take effect. +*/ + import ( "errors" "fmt" @@ -37,14 +51,13 @@ type balanceRecord struct { } func newBalanceRecord(addr basics.Address, balance uint64) balanceRecord { - br := balanceRecord{ + return balanceRecord{ addr: addr, balance: balance, locals: make(map[basics.AppIndex]basics.TealKeyValue), holdings: make(map[basics.AssetIndex]basics.AssetHolding), mods: make(map[basics.AppIndex]map[string]basics.ValueDelta), } - return br } // In our test ledger, we don't store the creatables with their @@ -53,7 +66,8 @@ type appParams struct { basics.AppParams Creator basics.Address - boxes map[string]string + boxes map[string]string + boxMods map[string]*string } type asaParams struct { @@ -91,7 +105,7 @@ func (l *Ledger) Reset() { l.balances[addr] = br } for id, app := range l.applications { - app.boxes = nil + app.boxMods = nil l.applications[id] = app } } @@ -331,66 +345,82 @@ func (l *Ledger) DelGlobal(appIdx basics.AppIndex, key string) error { return nil } +// NewBox makes a new box, through the boxMods mechanism. It can be Reset() func (l *Ledger) NewBox(appIdx basics.AppIndex, key string, size uint64) error { params, ok := l.applications[appIdx] if !ok { return fmt.Errorf("no such app %d", appIdx) } - if params.boxes == nil { - params.boxes = make(map[string]string) + if params.boxMods == nil { + params.boxMods = make(map[string]*string) } - if _, ok = params.boxes[key]; ok { - return fmt.Errorf("box already exists %#v", key) + if current, ok := params.boxMods[key]; ok { + if current != nil { + return fmt.Errorf("box already exists 1 %#v %d", key, len(*current)) + } + } else if current, ok := params.boxes[key]; ok { + return fmt.Errorf("box already exists 2 %#v %d", key, len(current)) } - params.boxes[key] = string(make([]byte, size)) + s := string(make([]byte, size)) + params.boxMods[key] = &s l.applications[appIdx] = params return nil } -func (l *Ledger) GetBox(appIdx basics.AppIndex, key string) (string, error) { +func (l *Ledger) GetBox(appIdx basics.AppIndex, key string) (string, bool, error) { params, ok := l.applications[appIdx] if !ok { - return "", fmt.Errorf("no such app %d", appIdx) + return "", false, nil } - if params.boxes == nil { - return "", fmt.Errorf("no such box %#v", key) + if params.boxMods != nil { + if ps, ok := params.boxMods[key]; ok { + if ps == nil { // deletion in mod + return "", false, nil + } + return *ps, true, nil + } } - if box, ok := params.boxes[key]; ok { - return box, nil + if params.boxes == nil { + return "", false, nil } - return "", fmt.Errorf("no such box %#v", key) + box, ok := params.boxes[key] + return box, ok, nil } +// SetBox set a box value through the boxMods mechanism. It can be Reset() func (l *Ledger) SetBox(appIdx basics.AppIndex, key string, value string) error { - params, ok := l.applications[appIdx] + current, ok, err := l.GetBox(appIdx, key) + if err != nil { + return err + } if !ok { - return fmt.Errorf("no such app %d", appIdx) + return fmt.Errorf("no such box %d", appIdx) } - if params.boxes == nil { - return fmt.Errorf("no such box %#v", key) + params := l.applications[appIdx] // assured, based on above + if params.boxMods == nil { + params.boxMods = make(map[string]*string) } - if box, ok := params.boxes[key]; ok { - if len(box) != len(value) { - return fmt.Errorf("wrong box size %#v %d != %d", key, len(box), len(value)) - } - params.boxes[key] = value - return nil + if len(current) != len(value) { + return fmt.Errorf("wrong box size %#v %d != %d", key, len(current), len(value)) } - return fmt.Errorf("no such box %#v", key) + params.boxMods[key] = &value + return nil } +// DelBox deletes a value through moxMods mechanism func (l *Ledger) DelBox(appIdx basics.AppIndex, key string) error { - params, ok := l.applications[appIdx] - if !ok { - return fmt.Errorf("no such app %d", appIdx) + _, ok, err := l.GetBox(appIdx, key) + if err != nil { + return err } - if params.boxes == nil { - return fmt.Errorf("boxes %#v", key) + if !ok { + return fmt.Errorf("no such box %d", appIdx) } - if _, ok := params.boxes[key]; !ok { - return fmt.Errorf("no such box %#v", key) + params := l.applications[appIdx] // assured, based on above + if params.boxMods == nil { + params.boxMods = make(map[string]*string) } - delete(params.boxes, key) + params.boxMods[key] = nil return nil } diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 727044ba64..da65c6f499 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -181,14 +181,6 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // -// boxRefStringly -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // MarshalMsg implements msgp.Marshaler func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { @@ -309,7 +301,7 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { zb0007Len-- zb0007Mask |= 0x2 } - if (*z).Boxes[zb0004].Name == "" { + if len((*z).Boxes[zb0004].Name) == 0 { zb0007Len-- zb0007Mask |= 0x4 } @@ -323,7 +315,7 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) { if (zb0007Mask & 0x4) == 0 { // if not empty // string "n" o = append(o, 0xa1, 0x6e) - o = msgp.AppendString(o, (*z).Boxes[zb0004].Name) + o = msgp.AppendBytes(o, (*z).Boxes[zb0004].Name) } } } @@ -534,7 +526,7 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } if zb0017 > 0 { zb0017-- - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "struct-from-array", "Name") return @@ -570,7 +562,7 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error return } case "n": - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0004, "Name") return @@ -835,7 +827,7 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error } if zb0032 > 0 { zb0032-- - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) if err != nil { err = msgp.WrapError(err, "Boxes", zb0004, "struct-from-array", "Name") return @@ -871,7 +863,7 @@ func (z *ApplicationCallTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error return } case "n": - (*z).Boxes[zb0004].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).Boxes[zb0004].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Boxes[zb0004].Name) if err != nil { err = msgp.WrapError(err, "Boxes", zb0004, "Name") return @@ -997,7 +989,7 @@ func (z *ApplicationCallTxnFields) Msgsize() (s int) { } s += 5 + msgp.ArrayHeaderSize for zb0004 := range (*z).Boxes { - s += 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).Boxes[zb0004].Name) + s += 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).Boxes[zb0004].Name) } s += 5 + msgp.ArrayHeaderSize for zb0005 := range (*z).ForeignAssets { @@ -1768,7 +1760,7 @@ func (z *BoxRef) MarshalMsg(b []byte) (o []byte) { zb0001Len-- zb0001Mask |= 0x2 } - if (*z).Name == "" { + if len((*z).Name) == 0 { zb0001Len-- zb0001Mask |= 0x4 } @@ -1783,7 +1775,7 @@ func (z *BoxRef) MarshalMsg(b []byte) (o []byte) { if (zb0001Mask & 0x4) == 0 { // if not empty // string "n" o = append(o, 0xa1, 0x6e) - o = msgp.AppendString(o, (*z).Name) + o = msgp.AppendBytes(o, (*z).Name) } } return @@ -1817,7 +1809,7 @@ func (z *BoxRef) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0001 > 0 { zb0001-- - (*z).Name, bts, err = msgp.ReadStringBytes(bts) + (*z).Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Name) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Name") return @@ -1853,7 +1845,7 @@ func (z *BoxRef) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "n": - (*z).Name, bts, err = msgp.ReadStringBytes(bts) + (*z).Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Name) if err != nil { err = msgp.WrapError(err, "Name") return @@ -1878,13 +1870,13 @@ func (_ *BoxRef) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *BoxRef) Msgsize() (s int) { - s = 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).Name) + s = 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).Name) return } // MsgIsZero returns whether this is a zero value func (z *BoxRef) MsgIsZero() bool { - return ((*z).Index == 0) && ((*z).Name == "") + return ((*z).Index == 0) && (len((*z).Name) == 0) } // MarshalMsg implements msgp.Marshaler @@ -4847,7 +4839,7 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { zb0008Len-- zb0008Mask |= 0x2 } - if (*z).ApplicationCallTxnFields.Boxes[zb0005].Name == "" { + if len((*z).ApplicationCallTxnFields.Boxes[zb0005].Name) == 0 { zb0008Len-- zb0008Mask |= 0x4 } @@ -4861,7 +4853,7 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { if (zb0008Mask & 0x4) == 0 { // if not empty // string "n" o = append(o, 0xa1, 0x6e) - o = msgp.AppendString(o, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) } } } @@ -5475,7 +5467,7 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0019 > 0 { zb0019-- - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "struct-from-array", "Name") return @@ -5511,7 +5503,7 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "n": - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Boxes", zb0005, "Name") return @@ -5996,7 +5988,7 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0035 > 0 { zb0035-- - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) if err != nil { err = msgp.WrapError(err, "Boxes", zb0005, "struct-from-array", "Name") return @@ -6032,7 +6024,7 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "n": - (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadStringBytes(bts) + (*z).ApplicationCallTxnFields.Boxes[zb0005].Name, bts, err = msgp.ReadBytesBytes(bts, (*z).ApplicationCallTxnFields.Boxes[zb0005].Name) if err != nil { err = msgp.WrapError(err, "Boxes", zb0005, "Name") return @@ -6176,7 +6168,7 @@ func (z *Transaction) Msgsize() (s int) { } s += 5 + msgp.ArrayHeaderSize for zb0005 := range (*z).ApplicationCallTxnFields.Boxes { - s += 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).ApplicationCallTxnFields.Boxes[zb0005].Name) + s += 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.Boxes[zb0005].Name) } s += 5 + msgp.ArrayHeaderSize for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { @@ -6376,132 +6368,3 @@ func (z *Txid) Msgsize() int { func (z *Txid) MsgIsZero() bool { return ((*(crypto.Digest))(z)).MsgIsZero() } - -// MarshalMsg implements msgp.Marshaler -func (z *boxRefStringly) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0001Len := uint32(2) - var zb0001Mask uint8 /* 3 bits */ - if (*z).Index == 0 { - zb0001Len-- - zb0001Mask |= 0x2 - } - if len((*z).Name) == 0 { - zb0001Len-- - zb0001Mask |= 0x4 - } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "i" - o = append(o, 0xa1, 0x69) - o = msgp.AppendUint64(o, (*z).Index) - } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "n" - o = append(o, 0xa1, 0x6e) - o = msgp.AppendBytes(o, (*z).Name) - } - } - return -} - -func (_ *boxRefStringly) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*boxRefStringly) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *boxRefStringly) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0001 > 0 { - zb0001-- - (*z).Index, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Index") - return - } - } - if zb0001 > 0 { - zb0001-- - (*z).Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Name) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Name") - return - } - } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 { - (*z) = boxRefStringly{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "i": - (*z).Index, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "Index") - return - } - case "n": - (*z).Name, bts, err = msgp.ReadBytesBytes(bts, (*z).Name) - if err != nil { - err = msgp.WrapError(err, "Name") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *boxRefStringly) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*boxRefStringly) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *boxRefStringly) Msgsize() (s int) { - s = 1 + 2 + msgp.Uint64Size + 2 + msgp.BytesPrefixSize + len((*z).Name) - return -} - -// MsgIsZero returns whether this is a zero value -func (z *boxRefStringly) MsgIsZero() bool { - return ((*z).Index == 0) && (len((*z).Name) == 0) -} diff --git a/data/transactions/msgp_gen_test.go b/data/transactions/msgp_gen_test.go index 80fc9b755e..bfc997627b 100644 --- a/data/transactions/msgp_gen_test.go +++ b/data/transactions/msgp_gen_test.go @@ -1093,63 +1093,3 @@ func BenchmarkUnmarshalTxGroup(b *testing.B) { } } } - -func TestMarshalUnmarshalboxRefStringly(t *testing.T) { - partitiontest.PartitionTest(t) - v := boxRefStringly{} - bts := v.MarshalMsg(nil) - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func TestRandomizedEncodingboxRefStringly(t *testing.T) { - protocol.RunEncodingTest(t, &boxRefStringly{}) -} - -func BenchmarkMarshalMsgboxRefStringly(b *testing.B) { - v := boxRefStringly{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgboxRefStringly(b *testing.B) { - v := boxRefStringly{} - bts := make([]byte, 0, v.Msgsize()) - bts = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalboxRefStringly(b *testing.B) { - v := boxRefStringly{} - bts := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 16c46702bb..0338431d06 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -438,8 +438,12 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa return fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is %d", proto.MaxAppTxnForeignAssets) } + if len(tx.Boxes) > proto.MaxAppBoxReferences { + return fmt.Errorf("tx.Boxes too long, max number of box references is %d", proto.MaxAppBoxReferences) + } + // Limit the sum of all types of references that bring in account records - if len(tx.Accounts)+len(tx.ForeignApps)+len(tx.ForeignAssets) > proto.MaxAppTotalTxnReferences { + if len(tx.Accounts)+len(tx.ForeignApps)+len(tx.ForeignAssets)+len(tx.Boxes) > proto.MaxAppTotalTxnReferences { return fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = %d", proto.MaxAppTotalTxnReferences) } @@ -460,10 +464,6 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa return fmt.Errorf("app programs too long. max total len %d bytes", pages*proto.MaxAppTotalProgramLen) } - if len(tx.Boxes) > int(proto.MaxAppBoxReferences) { - return fmt.Errorf("tx.Boxes too long, max number of box references is %d", proto.MaxAppBoxReferences) - } - for i, br := range tx.Boxes { // recall 0 is the current app so indexes are shifted, thus test is for greater than, not gte. if br.Index > uint64(len(tx.ForeignApps)) { diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 6f8d77f599..541f94da64 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -533,7 +533,7 @@ func TestWellFormedErrors(t *testing.T) { Header: okHeader, ApplicationCallTxnFields: ApplicationCallTxnFields{ ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: "junk"}}, + Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, }, }, proto: futureProto, @@ -545,7 +545,7 @@ func TestWellFormedErrors(t *testing.T) { Header: okHeader, ApplicationCallTxnFields: ApplicationCallTxnFields{ ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: "junk"}}, + Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, ForeignApps: []basics.AppIndex{1}, }, }, @@ -557,7 +557,7 @@ func TestWellFormedErrors(t *testing.T) { Header: okHeader, ApplicationCallTxnFields: ApplicationCallTxnFields{ ApplicationID: 1, - Boxes: []BoxRef{{Index: 1, Name: "junk"}}, + Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}}, ForeignApps: []basics.AppIndex{1}, }, }, diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 45774b96c0..174f36060a 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -236,16 +236,9 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) return cs.kvPut(fullKey, value) } -func (cs *roundCowState) GetBox(appIdx basics.AppIndex, key string) (string, error) { +func (cs *roundCowState) GetBox(appIdx basics.AppIndex, key string) (string, bool, error) { fullKey := logic.MakeBoxKey(appIdx, key) - value, ok, err := cs.kvGet(fullKey) - if err != nil { - return "", err - } - if !ok { - return "", fmt.Errorf("book %s does not exist for %d", key, appIdx) - } - return value, nil + return cs.kvGet(fullKey) } func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value string) error { diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go index e02ff5dd18..4d46222f61 100644 --- a/ledger/internal/boxtxn_test.go +++ b/ledger/internal/boxtxn_test.go @@ -107,19 +107,19 @@ func TestBoxCreate(t *testing.T) { adam := call.Args("create", "adam") dl.txn(adam, "invalid Box reference adam") - adam.Boxes = []transactions.BoxRef{{Index: 0, Name: "adam"}} + adam.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("adam")}} dl.txn(adam) dl.txn(adam.Args("check", "adam", "\x00\x00")) dl.txgroup("exists", adam.Noted("one"), adam.Noted("two")) bobo := call.Args("create", "bobo") dl.txn(bobo, "invalid Box reference bobo") - bobo.Boxes = []transactions.BoxRef{{Index: 0, Name: "bobo"}} + bobo.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("bobo")}} dl.txn(bobo) dl.txgroup("exists", bobo.Noted("one"), bobo.Noted("two")) dl.beginBlock() chaz := call.Args("create", "chaz") - chaz.Boxes = []transactions.BoxRef{{Index: 0, Name: "chaz"}} + chaz.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("chaz")}} dl.txn(chaz) dl.txn(chaz.Noted("again"), "exists") dl.endBlock() @@ -127,7 +127,7 @@ func TestBoxCreate(t *testing.T) { // new block dl.txn(chaz.Noted("again"), "exists") dogg := call.Args("create", "dogg") - dogg.Boxes = []transactions.BoxRef{{Index: 0, Name: "dogg"}} + dogg.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("dogg")}} dl.txn(dogg, "below min") dl.txn(chaz.Args("delete", "chaz")) dl.txn(chaz.Args("delete", "chaz").Noted("again"), "does not exist") @@ -156,7 +156,7 @@ func TestBoxCreateAvailability(t *testing.T) { Type: "appl", Sender: addrs[0], ApplicationID: 0, // This is a create - Boxes: []transactions.BoxRef{{Index: 0, Name: "hello"}}, + Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("hello")}}, ApprovalProgram: ` int 10 byte "hello" @@ -191,7 +191,7 @@ func TestBoxCreateAvailability(t *testing.T) { Type: "appl", Sender: addrs[0], ApplicationID: 0, // This is a create - Boxes: []transactions.BoxRef{{Index: 0, Name: "hello"}}, + Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("hello")}}, // Note that main() wraps the program so it does not run at creation time. ApprovalProgram: main(` int 10 @@ -265,7 +265,7 @@ func TestBoxRW(t *testing.T) { Type: "appl", Sender: addrs[0], ApplicationID: appIndex, - Boxes: []transactions.BoxRef{{Index: 0, Name: "x"}}, + Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("x")}}, } dl.txn(call.Args("create", "x", "\x10")) // 16 @@ -296,7 +296,7 @@ func TestBoxRW(t *testing.T) { dl.txn(call.Args("create", "yy"), "invalid Box reference yy") withBr := call.Args("create", "yy") - withBr.Boxes = append(withBr.Boxes, transactions.BoxRef{Index: 1, Name: "yy"}) + withBr.Boxes = append(withBr.Boxes, transactions.BoxRef{Index: 1, Name: []byte("yy")}) require.Error(dl.t, withBr.Txn().WellFormed(transactions.SpecialAddresses{}, dl.generator.GenesisProto())) withBr.Boxes[1].Index = 0 dl.txn(withBr) From 207bf1e08926f58b679c2e837ef374653bfc400c Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 7 Jun 2022 14:15:58 -0400 Subject: [PATCH 24/30] back to first values for box MBR --- config/consensus.go | 4 ++-- ledger/accountdb.go | 1 - ledger/internal/boxtxn_test.go | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index 420c2bb9c1..c0314f0a65 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1171,8 +1171,8 @@ func initConsensusProtocols() { // Boxes (unlimited global storage) vFuture.MaxBoxSize = 4 * 8096 - vFuture.BoxFlatMinBalance = 10000 - vFuture.BoxByteMinBalance = 350 + vFuture.BoxFlatMinBalance = 2500 + vFuture.BoxByteMinBalance = 400 vFuture.MaxAppBoxReferences = 8 vFuture.BytesPerBoxReference = 8096 diff --git a/ledger/accountdb.go b/ledger/accountdb.go index 3c864cb4f2..1b34074dbc 100644 --- a/ledger/accountdb.go +++ b/ledger/accountdb.go @@ -1970,7 +1970,6 @@ func (qs *accountsDbQueries) lookupKeyValue(key string) (pv persistedValue, err err = db.Retry(func() error { var v sql.NullString err := qs.lookupKvPairStmt.QueryRow(key).Scan(&pv.round, &v) - fmt.Printf("lookupKeyValue(%s) %+v %+v\n", key, pv, v) if err != nil { // this should never happen; it indicates that we don't have a current round in the acctrounds table. if err == sql.ErrNoRows { diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go index 4d46222f61..04380732d1 100644 --- a/ledger/internal/boxtxn_test.go +++ b/ledger/internal/boxtxn_test.go @@ -176,7 +176,7 @@ func TestBoxCreateAvailability(t *testing.T) { Type: "pay", Sender: addrs[0], Receiver: psychic.Address(), - Amount: 108501, + Amount: 100_000 + 2500 + 15*400, }) dl.txn(&accessInCreate) @@ -275,7 +275,7 @@ func TestBoxRW(t *testing.T) { dl.txn(call.Args("check", "x", "ABCDEFGHIJ\x00")) dl.txn(call.Args("delete", "x")) - dl.txn(call.Args("check", "x", "ABC"), "x does not exist") + dl.txn(call.Args("check", "x", "ABC"), "no such box") dl.txn(call.Args("create", "x", "\x08")) dl.txn(call.Args("check", "x", "\x00")) // it was cleared dl.txn(call.Args("set", "x", "ABCDEFGHIJ"), "replacement end 10") From 23c0346d09a0054de3ba878c48a4b6382470073e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 7 Jun 2022 23:49:34 -0400 Subject: [PATCH 25/30] Allow dry run test to pay for inner txn --- config/consensus.go | 6 ++--- daemon/algod/api/server/v2/dryrun_test.go | 32 ++++++++--------------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index c0314f0a65..ace3f65a53 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -332,13 +332,13 @@ type ConsensusParams struct { // []byte values stored in LocalState or GlobalState key/value stores SchemaBytesMinBalance uint64 - // Maximum length of a box (does not include name length) + // Maximum length of a box (Does not include name/key length. That is capped by MaxAppKeyLen) MaxBoxSize uint64 - // MBR per box created + // MBR per box created (this accounts for a bit of overhead used to store the box bytes) BoxFlatMinBalance uint64 - // MBR per byte in a box + // MBR per byte of box storage. MBR is incremented by BoxByteMinBalance * (len(name)+len(value)) BoxByteMinBalance uint64 // Number of box references allowed diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index cfa87cb986..9ff33b541b 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -1369,9 +1369,9 @@ int 1`) }, Accounts: []generated.Account{ { - Address: sender.String(), - Status: "Online", - Amount: 10000000, + Address: (appIdx + 2).Address().String(), + Status: "Online", + AmountWithoutPendingRewards: 105_000, }, }, } @@ -1379,29 +1379,19 @@ int 1`) var response generated.DryrunResponse doDryrunRequest(&dr, &response) require.Empty(t, response.Error) - require.Equal(t, 3, len(response.Txns)) + require.Len(t, response.Txns, 3) for i, txn := range response.Txns { messages := *txn.AppCallMessages - require.GreaterOrEqual(t, len(messages), 1) - cost := int64(*txn.BudgetConsumed) - int64(*txn.BudgetAdded) - require.NotNil(t, cost) - require.Equal(t, expectedCosts[i], cost) - require.Equal(t, expectedBudgetAdded[i], *txn.BudgetAdded) - statusMatches := false - costExceedFound := false - for _, msg := range messages { - if strings.Contains(msg, "cost budget exceeded") { - costExceedFound = true - } - if msg == test.msg { - statusMatches = true - } - } + require.Contains(t, messages, test.msg, "Wrong result") // PASS or REJECT + if test.msg == "REJECT" { - require.True(t, costExceedFound, "budget error not found in messages") + require.Contains(t, messages[2], "cost budget exceeded", "Failed for a surprise reason") } - require.True(t, statusMatches, "expected status not found in messages") + + cost := int64(*txn.BudgetConsumed) - int64(*txn.BudgetAdded) + require.Equal(t, expectedCosts[i], cost, "txn %d cost", i) + require.Equal(t, expectedBudgetAdded[i], *txn.BudgetAdded, "txn %d added", i) } }) } From 686a1abdc0cc6a8d014723e8228232bc233657ec Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 8 Jun 2022 15:26:13 -0400 Subject: [PATCH 26/30] moar! coverage!!11!! --- data/basics/fields_test.go | 3 --- data/transactions/logic/box_test.go | 37 +++++++++++++++++++++++++++++ data/transactions/teal_test.go | 2 +- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/data/basics/fields_test.go b/data/basics/fields_test.go index 3bd181cc92..2fcabbd4d0 100644 --- a/data/basics/fields_test.go +++ b/data/basics/fields_test.go @@ -172,9 +172,6 @@ func TestBlockFields(t *testing.T) { typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addMapKey(), typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addValue().addField("Bytes"), typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("Logs").addValue(), - - // This exception is for a NEW string field. See transactions/json.go to see how we get b64 output. - typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("ApplicationCallTxnFields").addField("Boxes").addValue().addField("Name"), } seen := make(map[reflect.Type]bool) diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 266939551b..4874504f93 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -17,6 +17,8 @@ package logic_test import ( + "fmt" + "strings" "testing" "github.com/algorand/go-algorand/data/basics" @@ -50,6 +52,32 @@ func TestBoxNewDel(t *testing.T) { `no such box`) } +func TestBoxNewBad(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ep, txn, ledger := logic.MakeSampleEnv() + + ledger.NewApp(txn.Sender, 888, basics.AppParams{}) + logic.TestApp(t, `int 999; byte "self"; box_create; int 1`, ep, "write budget") + ledger.DelBox(888, "self") + + // In test proto, you get 100 I/O budget per boxref + ten := [10]transactions.BoxRef{} + txn.Boxes = append(txn.Boxes, ten[:]...) // write budget is now 11*100 = 1100 + logic.TestApp(t, `int 999; byte "self"; box_create; int 1`, ep) + ledger.DelBox(888, "self") + logic.TestApp(t, `int 1000; byte "self"; box_create; int 1`, ep) + ledger.DelBox(888, "self") + logic.TestApp(t, `int 1001; byte "self"; box_create; int 1`, ep, "box size too large") + + logic.TestApp(t, `int 1000; byte "unknown"; box_create; int 1`, ep, "invalid Box reference") + + long := strings.Repeat("x", 65) + txn.Boxes = []transactions.BoxRef{{Name: []byte(long)}} + logic.TestApp(t, fmt.Sprintf(`int 1000; byte "%s"; box_create; int 1`, long), ep, "name too long") +} + func TestBoxReadWrite(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -86,6 +114,13 @@ func TestBoxReadWrite(t *testing.T) { logic.TestApp(t, `byte "self"; int 0; byte 0x4444; box_replace; byte "self"; int 0; int 4; box_extract; byte 0x44443132; ==`, ep) + + // All bow down to the God of code coverage! + ledger.DelBox(888, "self") + logic.TestApp(t, `byte "self"; int 1; byte 0x3031; box_replace`, ep, + "no such box") + logic.TestApp(t, `byte "junk"; int 1; byte 0x3031; box_replace`, ep, + "invalid Box reference") } func TestBoxAcrossTxns(t *testing.T) { @@ -200,6 +235,8 @@ func TestBoxWriteBudget(t *testing.T) { byte "self"; box_del; // deletion means we don't pay for write bytes int 1`, ep) logic.TestApp(t, `byte "other"; box_del; int 1`, ep) // cleanup (self was already deleted in last test) + logic.TestApp(t, `byte "other"; box_del; int 1`, ep, "no such box") + logic.TestApp(t, `byte "junk"; box_del; int 1`, ep, "invalid Box reference") // Create two boxes, that sum to over budget, then test trying to use them together logic.TestApp(t, `int 101; byte "self"; box_create; diff --git a/data/transactions/teal_test.go b/data/transactions/teal_test.go index 9900368953..e5920d0f18 100644 --- a/data/transactions/teal_test.go +++ b/data/transactions/teal_test.go @@ -192,7 +192,7 @@ func TestEvalDeltaEqual(t *testing.T) { // TestUnchangedAllocBounds ensure that the allocbounds on EvalDelta have not // changed. If they change, EvalDelta.checkAllocBounds must be changed, or at // least reconsidered, as well. We must give plenty of thought to whether a new -// allocound, used by new versions, is compatible with old code. If the change +// allocbound, used by new versions, is compatible with old code. If the change // can only show up in new protocol versions, it should be ok. But if we change // a bound, it will go into effect immediately, not with Protocol upgrade. So we // must be extremely careful that old protocol versions can not emit messages From 7369e0a0c28f4896b68d0d98895e89b7fa63fc00 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 9 Jun 2022 08:56:56 -0400 Subject: [PATCH 27/30] book -> box --- ledger/internal/applications.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 174f36060a..244ff1ab4d 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -218,7 +218,7 @@ func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) return err } if ok { - return fmt.Errorf("book %s exists for %d", key, appIdx) + return fmt.Errorf("box %s exists for %d", key, appIdx) } record, err := cs.Get(appIdx.Address(), false) @@ -248,10 +248,10 @@ func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value string return err } if !ok { - return fmt.Errorf("book %s does not exist for %d", key, appIdx) + return fmt.Errorf("box %s does not exist for %d", key, appIdx) } if len(old) != len(value) { - return fmt.Errorf("book %s is wrong size old:%d != new:%d", + return fmt.Errorf("box %s is wrong size old:%d != new:%d", key, len(old), len(value)) } return cs.kvPut(fullKey, value) @@ -265,7 +265,7 @@ func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string) error { return err } if !ok { - return fmt.Errorf("book %s does not exist for %d", key, appIdx) + return fmt.Errorf("box %s does not exist for %d", key, appIdx) } record, err := cs.Get(appIdx.Address(), false) From 41717128e2fbed764ef5d6131fcc496162e909b3 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 9 Jun 2022 15:47:22 -0400 Subject: [PATCH 28/30] coverage for some box related goal parsing --- cmd/goal/commands.go | 13 +++++++- cmd/goal/formatting_test.go | 61 +++++++++++++++++++++++++++++++++++++ ledger/acctupdates.go | 2 +- 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/cmd/goal/commands.go b/cmd/goal/commands.go index 4f93b6fb4b..4038a90239 100644 --- a/cmd/goal/commands.go +++ b/cmd/goal/commands.go @@ -17,6 +17,7 @@ package main import ( + "flag" "fmt" "io" "io/ioutil" @@ -557,13 +558,23 @@ func reportErrorln(args ...interface{}) { } fmt.Fprintln(os.Stderr, line) } - os.Exit(1) + exit(1) } func reportErrorf(format string, args ...interface{}) { reportErrorln(fmt.Sprintf(format, args...)) } +func exit(code int) { + if flag.Lookup("test.v") == nil { + // normal run + os.Exit(code) + } else { + // testing run. panic, so we can require.Panic + panic(code) + } +} + // writeFile is a wrapper of ioutil.WriteFile which considers the special // case of stdout filename func writeFile(filename string, data []byte, perm os.FileMode) error { diff --git a/cmd/goal/formatting_test.go b/cmd/goal/formatting_test.go index bc3bce6704..725044ebee 100644 --- a/cmd/goal/formatting_test.go +++ b/cmd/goal/formatting_test.go @@ -42,3 +42,64 @@ func TestUnicodePrintable(t *testing.T) { require.Equalf(t, testElement.printableString, printableString, "test string:%s", testElement.testString) } } + +func TestNewAppCallBytes(t *testing.T) { + partitiontest.PartitionTest(t) + acb := newAppCallBytes("str:hello") + require.Equal(t, "str", acb.Encoding) + require.Equal(t, "hello", acb.Value) + _, err := acb.raw() + require.NoError(t, err) + + require.Panics(t, func() { newAppCallBytes("hello") }) + + acb = newAppCallBytes("str:1:2") + require.Equal(t, "str", acb.Encoding) + require.Equal(t, "1:2", acb.Value) + _, err = acb.raw() + require.NoError(t, err) + + acb = newAppCallBytes(":x") + _, err = acb.raw() + require.Error(t, err) +} + +func TestNewBoxRef(t *testing.T) { + partitiontest.PartitionTest(t) + br := newBoxRef("str:hello") + require.EqualValues(t, 0, br.appID) + require.Equal(t, "str", br.name.Encoding) + require.Equal(t, "hello", br.name.Value) + + require.Panics(t, func() { newBoxRef("1,hello") }) + require.Panics(t, func() { newBoxRef("hello") }) + + br = newBoxRef("2,str:hello") + require.EqualValues(t, 2, br.appID) + require.Equal(t, "str", br.name.Encoding) + require.Equal(t, "hello", br.name.Value) +} + +func TestStringsToBoxRefs(t *testing.T) { + brs := stringsToBoxRefs([]string{"77,str:hello", "55,int:6", "int:88"}) + require.EqualValues(t, 77, brs[0].appID) + require.EqualValues(t, 55, brs[1].appID) + require.EqualValues(t, 0, brs[2].appID) + + tbrs := translateBoxRefs(brs, []uint64{55, 77}) + require.EqualValues(t, 2, tbrs[0].Index) + require.EqualValues(t, 1, tbrs[1].Index) + require.EqualValues(t, 0, tbrs[2].Index) + + require.Panics(t, func() { translateBoxRefs(stringsToBoxRefs([]string{"addr:88"}), nil) }) + translateBoxRefs(stringsToBoxRefs([]string{"addr:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"}), nil) + // if we're here, that didn't panic/exit + + tbrs = translateBoxRefs(brs, []uint64{77, 55}) + require.EqualValues(t, 1, tbrs[0].Index) + require.EqualValues(t, 2, tbrs[1].Index) + require.EqualValues(t, 0, tbrs[2].Index) + + require.Panics(t, func() { translateBoxRefs(brs, []uint64{55, 78}) }) + require.Panics(t, func() { translateBoxRefs(brs, []uint64{51, 77}) }) +} diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 561c22dea4..d13e4d3e9c 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -393,7 +393,7 @@ func (au *accountUpdates) lookupKv(rnd basics.Round, key string, synchronized bo return persistedData.value, nil } - // The db round is unepxected... + // The db round is unexpected... if synchronized { if persistedData.round < currentDbRound { // Somehow the db is LOWER than it should be. From 8e7ff74108592a59789d0beaa2fee6636a0e6d71 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Jun 2022 12:19:02 -0400 Subject: [PATCH 29/30] Outlaw zero length box names --- data/transactions/logic/box.go | 11 +++++++---- data/transactions/logic/box_test.go | 3 +++ data/transactions/logic/eval.go | 12 ++++++++---- ledger/internal/applications.go | 7 ++++++- ledger/internal/boxtxn_test.go | 7 ++++--- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index c83d8e772a..8ace5681aa 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -56,10 +56,13 @@ func opBoxCreate(cx *EvalContext) error { name := string(cx.stack[last].Bytes) size := cx.stack[prev].Uint - // Enforce maximums. Currently this is the same as enforced by ledger. If - // these were ever to change in proto, we would need to isolate changes to - // different program versions. (so a v7 app could not see a bigger box than - // expected, for example) + // Enforce length rules. Currently these are the same as enforced by + // ledger. If these were ever to change in proto, we would need to isolate + // changes to different program versions. (so a v7 app could not see a + // bigger box than expected, for example) + if len(name) == 0 { + return fmt.Errorf("box names may not be zero length") + } if len(name) > cx.Proto.MaxAppKeyLen { return fmt.Errorf("name too long: length was %d, maximum is %d", len(name), cx.Proto.MaxAppKeyLen) } diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 4874504f93..5ff62da09d 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -76,6 +76,9 @@ func TestBoxNewBad(t *testing.T) { long := strings.Repeat("x", 65) txn.Boxes = []transactions.BoxRef{{Name: []byte(long)}} logic.TestApp(t, fmt.Sprintf(`int 1000; byte "%s"; box_create; int 1`, long), ep, "name too long") + + txn.Boxes = []transactions.BoxRef{{Name: []byte("")}} // irrelevant, zero check comes first anyway + logic.TestApp(t, `int 1000; byte ""; box_create; int 1`, ep, "zero length") } func TestBoxReadWrite(t *testing.T) { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index a96c69cb1f..d7faa0aa38 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -699,6 +699,11 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam used := uint64(0) for br := range cx.available.boxes { + if len(br.name) == 0 { + // 0 length names are not allowed for actual created boxes, but + // may have bene used to add I/O budget. + continue + } box, ok, err := cx.Ledger.GetBox(br.app, br.name) if err != nil { return false, nil, err @@ -3943,10 +3948,9 @@ func opAppLocalPut(cx *EvalContext) error { sv := cx.stack[last] key := string(cx.stack[prev].Bytes) - // Enforce maximum key length. Currently this is the same as enforced by - // ledger. If it were ever to change in proto, we would need to isolate - // changes to different program versions. (so a v6 app could not see a - // bigger key, for example) + // Enforce key lengths. Now, this is the same as enforced by ledger, but if + // it ever to change in proto, we would need to isolate changes to different + // program versions. (so a v6 app could not see a bigger key, for example) if len(key) > cx.Proto.MaxAppKeyLen { return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), cx.Proto.MaxAppKeyLen) } diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 244ff1ab4d..5ee8d819fe 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -205,7 +205,12 @@ func (cs *roundCowState) kvDel(key string) error { func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, size uint64) error { // Use same limit on key length as for global/local storage if len(key) > cs.proto.MaxAppKeyLen { - return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), cs.proto.MaxAppKeyLen) + return fmt.Errorf("name too long: length was %d, maximum is %d", len(key), cs.proto.MaxAppKeyLen) + } + // This rule is NOT like global/local storage, but seems like it will limit + // confusion, since these are standalone entities. + if len(key) == 0 { + return fmt.Errorf("box names may not be zero length") } if size > cs.proto.MaxBoxSize { diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go index 04380732d1..14853c3974 100644 --- a/ledger/internal/boxtxn_test.go +++ b/ledger/internal/boxtxn_test.go @@ -134,11 +134,12 @@ func TestBoxCreate(t *testing.T) { dl.txn(dogg) dl.txn(bobo.Args("delete", "bobo")) - // empty name is legal + // empty name is illegal empty := call.Args("create", "") - dl.txn(empty, "invalid Box reference") + dl.txn(empty, "box names may not be zero") + // and, of course, that's true even if there's a box ref with the empty name empty.Boxes = []transactions.BoxRef{{}} - dl.txn(empty) + dl.txn(empty, "box names may not be zero") }) } From b8d54d12c22d286e8d12dd2f898cdf8b4332f79e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Jun 2022 15:26:04 -0400 Subject: [PATCH 30/30] delete the kvstore table on reset --- ledger/accountdb.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ledger/accountdb.go b/ledger/accountdb.go index 1b34074dbc..0eff0f5c6d 100644 --- a/ledger/accountdb.go +++ b/ledger/accountdb.go @@ -128,6 +128,7 @@ var accountsResetExprs = []string{ `DROP TABLE IF EXISTS acctrounds`, `DROP TABLE IF EXISTS accounttotals`, `DROP TABLE IF EXISTS accountbase`, + `DROP TABLE IF EXISTS kvstore`, `DROP TABLE IF EXISTS assetcreators`, `DROP TABLE IF EXISTS storedcatchpoints`, `DROP TABLE IF EXISTS catchpointstate`,