summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Lee <64482439+algojohnlee@users.noreply.github.com>2023-01-25 10:55:52 -0500
committerGitHub <noreply@github.com>2023-01-25 10:55:52 -0500
commit39ea2c6078ddbefbb15b7e7e7ca2840c43aa20a3 (patch)
tree66fa710cfb84d4b9dd6ad32b606187a69e3c0ff3
parentafe5b36903268cc6b03e236919d782e9d33bb88d (diff)
parent8dd7febf3ecbb39deeed5ea63d706c7866814627 (diff)
Merge pull request #5055 from Algo-devops-service/relbeta3.14.1v3.14.1-beta
-rw-r--r--.github/workflows/reviewdog.yml3
-rw-r--r--.golangci-warnings.yml4
-rw-r--r--agreement/events_test.go2
-rw-r--r--agreement/persistence_test.go1
-rw-r--r--buildnumber.dat2
-rw-r--r--cmd/goal/formatting_test.go1
-rw-r--r--cmd/tealdbg/README.md20
-rw-r--r--cmd/tealdbg/debugger.go23
-rw-r--r--cmd/tealdbg/debugger_test.go2
-rw-r--r--cmd/tealdbg/dryrunRequest.go17
-rw-r--r--cmd/tealdbg/local.go2
-rw-r--r--cmd/tealdbg/remote.go14
-rw-r--r--config/config_test.go1
-rw-r--r--daemon/algod/api/server/v2/dryrun.go20
-rw-r--r--daemon/algod/api/server/v2/errors.go2
-rw-r--r--data/transactions/logic/assembler.go4
-rw-r--r--data/transactions/logic/debugger.go110
-rw-r--r--data/transactions/logic/debugger_test.go78
-rw-r--r--data/transactions/logic/eval.go104
-rw-r--r--data/transactions/logic/evalStateful_test.go16
-rw-r--r--data/transactions/logic/eval_test.go29
-rw-r--r--data/transactions/logic/fields.go18
-rw-r--r--data/transactions/logic/mocktracer/tracer.go147
-rw-r--r--data/transactions/logic/opcodes.go116
-rw-r--r--data/transactions/logic/tracer.go160
-rw-r--r--data/transactions/logic/tracer_test.go195
-rw-r--r--data/transactions/verify/txn.go30
-rw-r--r--data/transactions/verify/txn_test.go87
-rw-r--r--data/txDupCache.go6
-rw-r--r--data/txntest/defi.go2
-rw-r--r--data/txntest/txn.go8
-rw-r--r--ledger/internal/eval.go28
-rw-r--r--ledger/internal/eval_test.go200
-rw-r--r--ledger/simple_test.go2
-rw-r--r--ledger/simulation/simulator.go25
-rw-r--r--logging/telemetryspec/metric_test.go2
-rw-r--r--node/node_test.go7
-rw-r--r--shared/pingpong/pingpong.go3
-rw-r--r--util/rateLimit.go5
-rw-r--r--util/tcpinfo_linux.go1
40 files changed, 1230 insertions, 267 deletions
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml
index e39a09167..ec1a85943 100644
--- a/.github/workflows/reviewdog.yml
+++ b/.github/workflows/reviewdog.yml
@@ -88,7 +88,6 @@ jobs:
-c .golangci-warnings.yml \
--issues-exit-code 0 \
--allow-parallel-runners > temp_golangci-lint-cgo.txt
- cat temp_golangci-lint-cgo.txt
cat temp_golangci-lint-cgo.txt | reviewdog \
-f=golangci-lint \
@@ -102,4 +101,4 @@ jobs:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: |
curl -X POST --data-urlencode "payload={\"text\": \"Reviewdog failed. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} \"}" $SLACK_WEBHOOK
- if: ${{ failure() && (contains(github.ref_name, 'rel/nightly') || contains(github.ref_name, 'rel/beta') || contains(github.ref_name, 'rel/stable') || contains(github.ref_name, 'master')) }} \ No newline at end of file
+ if: ${{ failure() && (contains(github.ref_name, 'rel/nightly') || contains(github.ref_name, 'rel/beta') || contains(github.ref_name, 'rel/stable') || contains(github.ref_name, 'master')) }}
diff --git a/.golangci-warnings.yml b/.golangci-warnings.yml
index e3b8d22ff..f8d206347 100644
--- a/.golangci-warnings.yml
+++ b/.golangci-warnings.yml
@@ -9,10 +9,8 @@ linters:
- partitiontest
- structcheck
- varcheck
- - unconvert
- unused
-
linters-settings:
custom:
partitiontest:
@@ -55,7 +53,6 @@ issues:
- deadcode
- structcheck
- varcheck
- - unconvert
- unused
# Add all linters here -- Comment this block out for testing linters
- path: test/linttest/lintissues\.go
@@ -63,7 +60,6 @@ issues:
- deadcode
- structcheck
- varcheck
- - unconvert
- unused
- path: crypto/secp256k1/secp256_test\.go
linters:
diff --git a/agreement/events_test.go b/agreement/events_test.go
index 1ea35fa80..c558f0067 100644
--- a/agreement/events_test.go
+++ b/agreement/events_test.go
@@ -21,6 +21,7 @@ import (
"testing"
"github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
"github.com/stretchr/testify/require"
)
@@ -28,6 +29,7 @@ import (
// properly decoded from ConsensusVersionView.
// This test is only needed for agreement state serialization switch from reflection to msgp.
func TestSerializableErrorBackwardCompatibility(t *testing.T) {
+ partitiontest.PartitionTest(t)
encodedEmpty, err := base64.StdEncoding.DecodeString("gqNFcnLAp1ZlcnNpb26jdjEw")
require.NoError(t, err)
diff --git a/agreement/persistence_test.go b/agreement/persistence_test.go
index ef5d5da47..fbd9323b0 100644
--- a/agreement/persistence_test.go
+++ b/agreement/persistence_test.go
@@ -240,6 +240,7 @@ func TestEmptyMapDeserialization(t *testing.T) {
}
func TestDecodeFailures(t *testing.T) {
+ partitiontest.PartitionTest(t)
clock := timers.MakeMonotonicClock(time.Date(2015, 1, 2, 5, 6, 7, 8, time.UTC))
ce := clock.Encode()
log := makeServiceLogger(logging.Base())
diff --git a/buildnumber.dat b/buildnumber.dat
index 573541ac9..d00491fd7 100644
--- a/buildnumber.dat
+++ b/buildnumber.dat
@@ -1 +1 @@
-0
+1
diff --git a/cmd/goal/formatting_test.go b/cmd/goal/formatting_test.go
index 58a8d6d0d..f368c54c9 100644
--- a/cmd/goal/formatting_test.go
+++ b/cmd/goal/formatting_test.go
@@ -72,6 +72,7 @@ func TestNewBoxRef(t *testing.T) {
}
func TestStringsToBoxRefs(t *testing.T) {
+ partitiontest.PartitionTest(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)
diff --git a/cmd/tealdbg/README.md b/cmd/tealdbg/README.md
index b5a7bebbf..14967677f 100644
--- a/cmd/tealdbg/README.md
+++ b/cmd/tealdbg/README.md
@@ -9,6 +9,7 @@
- [Protocol](#protocol)
- [Transaction and Transaction Group](#transaction-and-transaction-group)
- [Balance records](#balance-records)
+ - [Indexer Support](#indexer-support)
- [Execution mode](#execution-mode)
- [Chrome DevTools Frontend Features](#chrome-devtools-frontend-features)
- [Configure the Listener](#configure-the-listener)
@@ -44,7 +45,7 @@ and balance records (see [Setting Debug Context](#setting-debug-context) for det
Remote debugger might be useful for debugging unit tests for TEAL (currently in Golang only) or for hacking **algod** `eval` and breaking on any TEAL evaluation.
The protocol consist of three REST endpoints and one data structure describing the evaluator state.
-See `WebDebuggerHook` and `TestWebDebuggerManual` in [go-algorand sources](https://github.com/algorand/go-algorand/tree/master/data/transactions/logic) for more details.
+See `WebDebugger` and `TestWebDebuggerManual` in [go-algorand sources](https://github.com/algorand/go-algorand/tree/master/data/transactions/logic) for more details.
### Frontends
@@ -206,13 +207,18 @@ Refer to the [Chrome DevTools debugging](https://developers.google.com/web/tools
The evaluator accepts a new `Debugger` parameter described as the interface:
```golang
-type DebuggerHook interface {
+// Debugger is an interface that supports the first version of AVM debuggers.
+// It consists of a set of functions called by eval function during AVM program execution.
+//
+// Deprecated: This interface does not support non-app call or inner transactions. Use EvalTracer
+// instead.
+type Debugger interface {
// Register is fired on program creation
- Register(state *DebugState) error
+ Register(state *DebugState)
// Update is fired on every step
- Update(state *DebugState) error
+ Update(state *DebugState)
// Complete is called when the program exits
- Complete(state *DebugState) error
+ Complete(state *DebugState)
}
```
If `Debugger` is set the evaluator calls `Register` on creation, `Update` on every step and `Complete` on exit.
@@ -251,13 +257,13 @@ The core calls `SessionEnded` on `Complete` call.
If one needs to debug TEAL in as much real environment as possible then do
-1. Add `WebDebuggerHook` to `data/transactions/logic/eval.go`:
+1. Add `WebDebugger` to `data/transactions/logic/eval.go`:
```golang
cx.program = program
// begin new code
debugURL := os.Getenv("TEAL_DEBUGGER_URL")
- cx.Debugger = &WebDebuggerHook{URL: debugURL}
+ cx.Debugger = &WebDebugger{URL: debugURL}
// end new code
if cx.Debugger != nil {
diff --git a/cmd/tealdbg/debugger.go b/cmd/tealdbg/debugger.go
index 38a51f69d..3639a41af 100644
--- a/cmd/tealdbg/debugger.go
+++ b/cmd/tealdbg/debugger.go
@@ -26,6 +26,7 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/logging"
)
// Notification is sent to the client over their websocket connection
@@ -528,7 +529,7 @@ func (d *Debugger) SaveProgram(
}
// Register setups new session and notifies frontends if any
-func (d *Debugger) Register(state *logic.DebugState) error {
+func (d *Debugger) Register(state *logic.DebugState) {
sid := state.ExecID
pcOffset := make(map[int]int, len(state.PCOffset))
for _, pco := range state.PCOffset {
@@ -556,12 +557,17 @@ func (d *Debugger) Register(state *logic.DebugState) error {
// Wait for acknowledgement
<-s.acknowledged
-
- return nil
}
// Update process state update notifications: pauses or continues as needed
-func (d *Debugger) Update(state *logic.DebugState) error {
+func (d *Debugger) Update(state *logic.DebugState) {
+ err := d.update(state)
+ if err != nil {
+ logging.Base().Errorf("error in Update hook: %s", err.Error())
+ }
+}
+
+func (d *Debugger) update(state *logic.DebugState) error {
sid := state.ExecID
s, err := d.getSession(sid)
if err != nil {
@@ -596,7 +602,14 @@ func (d *Debugger) Update(state *logic.DebugState) error {
}
// Complete terminates session and notifies frontends if any
-func (d *Debugger) Complete(state *logic.DebugState) error {
+func (d *Debugger) Complete(state *logic.DebugState) {
+ err := d.complete(state)
+ if err != nil {
+ logging.Base().Errorf("error in Complete hook: %s", err.Error())
+ }
+}
+
+func (d *Debugger) complete(state *logic.DebugState) error {
sid := state.ExecID
s, err := d.getSession(sid)
if err != nil {
diff --git a/cmd/tealdbg/debugger_test.go b/cmd/tealdbg/debugger_test.go
index f97754a51..a73fb3f4f 100644
--- a/cmd/tealdbg/debugger_test.go
+++ b/cmd/tealdbg/debugger_test.go
@@ -102,7 +102,7 @@ func TestDebuggerSimple(t *testing.T) {
debugger.AddAdapter(da)
ep := logic.NewEvalParams(make([]transactions.SignedTxnWithAD, 1), &proto, nil)
- ep.Debugger = debugger
+ ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(debugger)
ep.SigLedger = logic.NoHeaderLedger{}
source := `int 0
diff --git a/cmd/tealdbg/dryrunRequest.go b/cmd/tealdbg/dryrunRequest.go
index 1ff43981a..5f13aa6cf 100644
--- a/cmd/tealdbg/dryrunRequest.go
+++ b/cmd/tealdbg/dryrunRequest.go
@@ -47,23 +47,6 @@ func ddrFromParams(dp *DebugParams) (ddr v2.DryrunRequest, err error) {
return
}
-func convertAccounts(accounts []model.Account) (records []basics.BalanceRecord, err error) {
- for _, a := range accounts {
- var addr basics.Address
- addr, err = basics.UnmarshalChecksumAddress(a.Address)
- if err != nil {
- return
- }
- var ad basics.AccountData
- ad, err = v2.AccountToAccountData(&a)
- if err != nil {
- return
- }
- records = append(records, basics.BalanceRecord{Addr: addr, AccountData: ad})
- }
- return
-}
-
func balanceRecordsFromDdr(ddr *v2.DryrunRequest) (records []basics.BalanceRecord, err error) {
accounts := make(map[basics.Address]basics.AccountData)
for _, a := range ddr.Accounts {
diff --git a/cmd/tealdbg/local.go b/cmd/tealdbg/local.go
index d5673d9ae..8e9d9b81f 100644
--- a/cmd/tealdbg/local.go
+++ b/cmd/tealdbg/local.go
@@ -536,7 +536,7 @@ func (r *LocalRunner) RunAll() error {
// ep.Debugger = r.debugger
// if ep.Debugger != nil // FALSE
if r.debugger != nil {
- ep.Debugger = r.debugger
+ ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(r.debugger)
}
}
diff --git a/cmd/tealdbg/remote.go b/cmd/tealdbg/remote.go
index 1f27b5130..0656e418d 100644
--- a/cmd/tealdbg/remote.go
+++ b/cmd/tealdbg/remote.go
@@ -26,7 +26,7 @@ import (
"github.com/algorand/go-algorand/protocol"
)
-// RemoteHookAdapter provides HTTP transport for WebDebuggerHook
+// RemoteHookAdapter provides HTTP transport for WebDebugger
type RemoteHookAdapter struct {
debugger *Debugger
}
@@ -38,7 +38,7 @@ func MakeRemoteHook(debugger *Debugger) *RemoteHookAdapter {
return r
}
-// Setup adds HTTP handlers for remote WebDebuggerHook
+// Setup adds HTTP handlers for remote WebDebugger
func (rha *RemoteHookAdapter) Setup(router *mux.Router) {
router.HandleFunc("/exec/register", rha.registerHandler).Methods("POST")
router.HandleFunc("/exec/update", rha.updateHandler).Methods("POST")
@@ -59,11 +59,7 @@ func (rha *RemoteHookAdapter) registerHandler(w http.ResponseWriter, r *http.Req
}
// Register, and wait for user to acknowledge registration
- err = rha.debugger.Register(&state)
- if err != nil {
- w.WriteHeader(http.StatusBadRequest)
- return
- }
+ rha.debugger.Register(&state)
// Proceed!
w.WriteHeader(http.StatusOK)
@@ -78,7 +74,7 @@ func (rha *RemoteHookAdapter) updateHandler(w http.ResponseWriter, r *http.Reque
}
// Ask debugger to process and wait to continue
- err = rha.debugger.Update(&state)
+ err = rha.debugger.update(&state)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
@@ -96,7 +92,7 @@ func (rha *RemoteHookAdapter) completeHandler(w http.ResponseWriter, r *http.Req
}
// Ask debugger to process and wait to continue
- err = rha.debugger.Complete(&state)
+ err = rha.debugger.complete(&state)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
diff --git a/config/config_test.go b/config/config_test.go
index ebddd5a57..c2ee070fc 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -582,6 +582,7 @@ func TestGetNonDefaultConfigValues(t *testing.T) {
}
func TestLocal_TxFiltering(t *testing.T) {
+ partitiontest.PartitionTest(t)
cfg := GetDefaultLocal()
// ensure the default
diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go
index 84f1fe070..49a5a427d 100644
--- a/daemon/algod/api/server/v2/dryrun.go
+++ b/daemon/algod/api/server/v2/dryrun.go
@@ -181,24 +181,22 @@ func (ddr *dryrunDebugReceiver) stateToState(state *logic.DebugState) model.Dryr
return st
}
-// Register is fired on program creation (DebuggerHook interface)
-func (ddr *dryrunDebugReceiver) Register(state *logic.DebugState) error {
+// Register is fired on program creation (logic.Debugger interface)
+func (ddr *dryrunDebugReceiver) Register(state *logic.DebugState) {
ddr.disassembly = state.Disassembly
ddr.lines = strings.Split(state.Disassembly, "\n")
- return nil
}
-// Update is fired on every step (DebuggerHook interface)
-func (ddr *dryrunDebugReceiver) Update(state *logic.DebugState) error {
+// Update is fired on every step (logic.Debugger interface)
+func (ddr *dryrunDebugReceiver) Update(state *logic.DebugState) {
st := ddr.stateToState(state)
ddr.history = append(ddr.history, st)
ddr.updateScratch()
- return nil
}
-// Complete is called when the program exits (DebuggerHook interface)
-func (ddr *dryrunDebugReceiver) Complete(state *logic.DebugState) error {
- return ddr.Update(state)
+// Complete is called when the program exits (logic.Debugger interface)
+func (ddr *dryrunDebugReceiver) Complete(state *logic.DebugState) {
+ ddr.Update(state)
}
type dryrunLedger struct {
@@ -421,7 +419,7 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
var result model.DryrunTxnResult
if len(stxn.Lsig.Logic) > 0 {
var debug dryrunDebugReceiver
- ep.Debugger = &debug
+ ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug)
ep.SigLedger = &dl
pass, err := logic.EvalSignature(ti, ep)
var messages []string
@@ -505,7 +503,7 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
messages[0] = fmt.Sprintf("uploaded state did not include app id %d referenced in txn[%d]", appIdx, ti)
} else {
var debug dryrunDebugReceiver
- ep.Debugger = &debug
+ ep.Tracer = logic.MakeEvalTracerDebuggerAdaptor(&debug)
var program []byte
messages = make([]string, 1)
if stxn.Txn.OnCompletion == transactions.ClearStateOC {
diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go
index 2db2d51ed..947a38fa9 100644
--- a/daemon/algod/api/server/v2/errors.go
+++ b/daemon/algod/api/server/v2/errors.go
@@ -29,14 +29,12 @@ var (
errFailedRetrievingLatestBlockHeaderStatus = "failed retrieving latests block header"
errFailedRetrievingSyncRound = "failed retrieving sync round from ledger"
errFailedSettingSyncRound = "failed to set sync round on the ledger"
- errSyncModeNotEnabled = "sync mode must be enabled"
errFailedParsingFormatOption = "failed to parse the format option"
errFailedToParseAddress = "failed to parse the address"
errFailedToParseExclude = "failed to parse exclude"
errFailedToParseTransaction = "failed to parse transaction"
errFailedToParseBlock = "failed to parse block"
errFailedToParseCert = "failed to parse cert"
- errFailedToParseSourcemap = "failed to parse sourcemap"
errFailedToEncodeResponse = "failed to encode response"
errInternalFailure = "internal failure"
errNoValidTxnSpecified = "no valid transaction ID was specified"
diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go
index 9e8da08d1..45489918a 100644
--- a/data/transactions/logic/assembler.go
+++ b/data/transactions/logic/assembler.go
@@ -1907,7 +1907,7 @@ func (ops *OpStream) assemble(text string) error {
if ok {
ops.trace("%3d: %s\t", ops.sourceLine, opstring)
ops.recordSourceLine()
- if spec.Modes == modeApp {
+ if spec.Modes == ModeApp {
ops.HasStatefulOps = true
}
args, returns := spec.Arg.Types, spec.Return.Types
@@ -2803,7 +2803,7 @@ func disassembleInstrumented(program []byte, labels map[int]string) (text string
return
}
op := opsByOpcode[version][program[dis.pc]]
- if op.Modes == modeApp {
+ if op.Modes == ModeApp {
ds.hasStatefulOps = true
}
if op.Name == "" {
diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go
index f4682494b..0a8e013da 100644
--- a/data/transactions/logic/debugger.go
+++ b/data/transactions/logic/debugger.go
@@ -33,19 +33,80 @@ import (
"github.com/algorand/go-algorand/protocol"
)
-// DebuggerHook functions are called by eval function during TEAL program execution
-// if provided
-type DebuggerHook interface {
+// Debugger is an interface that supports the first version of AVM debuggers.
+// It consists of a set of functions called by eval function during AVM program execution.
+//
+// Deprecated: This interface does not support non-app call or inner transactions. Use EvalTracer
+// instead.
+type Debugger interface {
// Register is fired on program creation
- Register(state *DebugState) error
+ Register(state *DebugState)
// Update is fired on every step
- Update(state *DebugState) error
+ Update(state *DebugState)
// Complete is called when the program exits
- Complete(state *DebugState) error
+ Complete(state *DebugState)
+}
+
+type debuggerEvalTracerAdaptor struct {
+ NullEvalTracer
+
+ debugger Debugger
+ txnDepth int
+ debugState *DebugState
+}
+
+// MakeEvalTracerDebuggerAdaptor creates an adaptor that externally adheres to the EvalTracer
+// interface, but drives a Debugger interface
+//
+// Warning: The output EvalTracer is specifically designed to be invoked under the exact same
+// circumstances that the previous Debugger interface was invoked. This means that it will only work
+// properly if you attach it directly to a logic.EvalParams and execute a program. If you attempt to
+// run this EvalTracer under a different entry point (such as by attaching it to a BlockEvaluator),
+// it WILL NOT work properly.
+func MakeEvalTracerDebuggerAdaptor(debugger Debugger) EvalTracer {
+ return &debuggerEvalTracerAdaptor{debugger: debugger}
+}
+
+// BeforeTxnGroup updates inner txn depth
+func (a *debuggerEvalTracerAdaptor) BeforeTxnGroup(ep *EvalParams) {
+ a.txnDepth++
+}
+
+// AfterTxnGroup updates inner txn depth
+func (a *debuggerEvalTracerAdaptor) AfterTxnGroup(ep *EvalParams) {
+ a.txnDepth--
+}
+
+// BeforeProgram invokes the debugger's Register hook
+func (a *debuggerEvalTracerAdaptor) BeforeProgram(cx *EvalContext) {
+ if a.txnDepth > 0 {
+ // only report updates for top-level transactions, for backwards compatibility
+ return
+ }
+ a.debugState = makeDebugState(cx)
+ a.debugger.Register(a.refreshDebugState(cx, nil))
+}
+
+// BeforeOpcode invokes the debugger's Update hook
+func (a *debuggerEvalTracerAdaptor) BeforeOpcode(cx *EvalContext) {
+ if a.txnDepth > 0 {
+ // only report updates for top-level transactions, for backwards compatibility
+ return
+ }
+ a.debugger.Update(a.refreshDebugState(cx, nil))
+}
+
+// AfterProgram invokes the debugger's Complete hook
+func (a *debuggerEvalTracerAdaptor) AfterProgram(cx *EvalContext, evalError error) {
+ if a.txnDepth > 0 {
+ // only report updates for top-level transactions, for backwards compatibility
+ return
+ }
+ a.debugger.Complete(a.refreshDebugState(cx, evalError))
}
-// WebDebuggerHook represents a connection to tealdbg
-type WebDebuggerHook struct {
+// WebDebugger represents a connection to tealdbg
+type WebDebugger struct {
URL string
}
@@ -115,7 +176,7 @@ func makeDebugState(cx *EvalContext) *DebugState {
globals := make([]basics.TealValue, len(globalFieldSpecs))
for _, fs := range globalFieldSpecs {
// Don't try to grab app only fields when evaluating a signature
- if (cx.runModeFlags&modeSig) != 0 && fs.mode == modeApp {
+ if (cx.runModeFlags&ModeSig) != 0 && fs.mode == ModeApp {
continue
}
sv, err := cx.globalFieldToValue(fs)
@@ -126,7 +187,7 @@ func makeDebugState(cx *EvalContext) *DebugState {
}
ds.Globals = globals
- if (cx.runModeFlags & modeApp) != 0 {
+ if (cx.runModeFlags & ModeApp) != 0 {
ds.EvalDelta = cx.txn.EvalDelta
}
@@ -221,8 +282,8 @@ func (d *DebugState) parseCallstack(callstack []frame) []CallFrame {
return callFrames
}
-func (cx *EvalContext) refreshDebugState(evalError error) *DebugState {
- ds := cx.debugState
+func (a *debuggerEvalTracerAdaptor) refreshDebugState(cx *EvalContext, evalError error) *DebugState {
+ ds := a.debugState
// Update pc, line, error, stack, scratch space, callstack,
// and opcode budget
@@ -247,14 +308,14 @@ func (cx *EvalContext) refreshDebugState(evalError error) *DebugState {
ds.OpcodeBudget = cx.remainingBudget()
ds.CallStack = ds.parseCallstack(cx.callstack)
- if (cx.runModeFlags & modeApp) != 0 {
+ if (cx.runModeFlags & ModeApp) != 0 {
ds.EvalDelta = cx.txn.EvalDelta
}
return ds
}
-func (dbg *WebDebuggerHook) postState(state *DebugState, endpoint string) error {
+func (dbg *WebDebugger) postState(state *DebugState, endpoint string) error {
var body bytes.Buffer
enc := protocol.NewJSONEncoder(&body)
err := enc.Encode(state)
@@ -285,7 +346,7 @@ func (dbg *WebDebuggerHook) postState(state *DebugState, endpoint string) error
}
// Register sends state to remote debugger
-func (dbg *WebDebuggerHook) Register(state *DebugState) error {
+func (dbg *WebDebugger) Register(state *DebugState) {
u, err := url.Parse(dbg.URL)
if err != nil {
logging.Base().Errorf("Failed to parse url: %s", err.Error())
@@ -295,15 +356,24 @@ func (dbg *WebDebuggerHook) Register(state *DebugState) error {
if h != "localhost" && h != "127.0.0.1" && h != "::1" {
logging.Base().Warnf("Unsecured communication with non-local debugger: %s", h)
}
- return dbg.postState(state, "exec/register")
+ err = dbg.postState(state, "exec/register")
+ if err != nil {
+ logging.Base().Errorf("Failed to post state to exec/register: %s", err.Error())
+ }
}
// Update sends state to remote debugger
-func (dbg *WebDebuggerHook) Update(state *DebugState) error {
- return dbg.postState(state, "exec/update")
+func (dbg *WebDebugger) Update(state *DebugState) {
+ err := dbg.postState(state, "exec/update")
+ if err != nil {
+ logging.Base().Errorf("Failed to post state to exec/update: %s", err.Error())
+ }
}
// Complete sends state to remote debugger
-func (dbg *WebDebuggerHook) Complete(state *DebugState) error {
- return dbg.postState(state, "exec/complete")
+func (dbg *WebDebugger) Complete(state *DebugState) {
+ err := dbg.postState(state, "exec/complete")
+ if err != nil {
+ logging.Base().Errorf("Failed to post state to exec/complete: %s", err.Error())
+ }
}
diff --git a/data/transactions/logic/debugger_test.go b/data/transactions/logic/debugger_test.go
index 413bc7ea6..26bd940b7 100644
--- a/data/transactions/logic/debugger_test.go
+++ b/data/transactions/logic/debugger_test.go
@@ -26,7 +26,7 @@ import (
"github.com/stretchr/testify/require"
)
-var testProgram string = `intcblock 0 1 1 1 1 5 100
+const debuggerTestProgram string = `intcblock 0 1 1 1 1 5 100
bytecblock 0x414c474f 0x1337 0x2001 0xdeadbeef 0x70077007
bytec 0
sha256
@@ -63,9 +63,8 @@ bytec 4
&&
`
-func TestWebDebuggerManual(t *testing.T) {
+func TestWebDebuggerManual(t *testing.T) { //nolint:paralleltest // Manual test
partitiontest.PartitionTest(t)
- t.Parallel()
debugURL := os.Getenv("TEAL_DEBUGGER_URL")
if len(debugURL) == 0 {
@@ -81,48 +80,79 @@ func TestWebDebuggerManual(t *testing.T) {
tx.SelectionPK[:],
tx.Note,
}
- ep.Debugger = &WebDebuggerHook{URL: debugURL}
- testLogic(t, testProgram, AssemblerMaxVersion, ep)
+ ep.Tracer = MakeEvalTracerDebuggerAdaptor(&WebDebugger{URL: debugURL})
+ testLogic(t, debuggerTestProgram, AssemblerMaxVersion, ep)
}
-type testDbgHook struct {
+type testDebugger struct {
register int
update int
complete int
state *DebugState
}
-func (d *testDbgHook) Register(state *DebugState) error {
+func (d *testDebugger) Register(state *DebugState) {
d.register++
d.state = state
- return nil
}
-func (d *testDbgHook) Update(state *DebugState) error {
+func (d *testDebugger) Update(state *DebugState) {
d.update++
d.state = state
- return nil
}
-func (d *testDbgHook) Complete(state *DebugState) error {
+func (d *testDebugger) Complete(state *DebugState) {
d.complete++
d.state = state
- return nil
}
-func TestDebuggerHook(t *testing.T) {
+func TestDebuggerProgramEval(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- testDbg := testDbgHook{}
- ep := defaultEvalParams()
- ep.Debugger = &testDbg
- testLogic(t, testProgram, AssemblerMaxVersion, ep)
-
- require.Equal(t, 1, testDbg.register)
- require.Equal(t, 1, testDbg.complete)
- require.Greater(t, testDbg.update, 1)
- require.Len(t, testDbg.state.Stack, 1)
+ t.Run("logicsig", func(t *testing.T) {
+ t.Parallel()
+ testDbg := testDebugger{}
+ ep := defaultEvalParams()
+ ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg)
+ testLogic(t, debuggerTestProgram, AssemblerMaxVersion, ep)
+
+ require.Equal(t, 1, testDbg.register)
+ require.Equal(t, 1, testDbg.complete)
+ require.Equal(t, 35, testDbg.update)
+ require.Len(t, testDbg.state.Stack, 1)
+ })
+
+ t.Run("simple app", func(t *testing.T) {
+ t.Parallel()
+ testDbg := testDebugger{}
+ ep := defaultEvalParams()
+ ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg)
+ testApp(t, debuggerTestProgram, ep)
+
+ require.Equal(t, 1, testDbg.register)
+ require.Equal(t, 1, testDbg.complete)
+ require.Equal(t, 35, testDbg.update)
+ require.Len(t, testDbg.state.Stack, 1)
+ })
+
+ t.Run("app with inner txns", func(t *testing.T) {
+ t.Parallel()
+ testDbg := testDebugger{}
+ ep, tx, ledger := MakeSampleEnv()
+
+ // Establish 888 as the app id, and fund it.
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(basics.AppIndex(888).Address(), 200000)
+
+ ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg)
+ testApp(t, innerTxnTestProgram, ep)
+
+ require.Equal(t, 1, testDbg.register)
+ require.Equal(t, 1, testDbg.complete)
+ require.Equal(t, 27, testDbg.update)
+ require.Len(t, testDbg.state.Stack, 1)
+ })
}
func TestLineToPC(t *testing.T) {
@@ -228,9 +258,9 @@ func TestCallStackUpdate(t *testing.T) {
},
}
- testDbg := testDbgHook{}
+ testDbg := testDebugger{}
ep := defaultEvalParams()
- ep.Debugger = &testDbg
+ ep.Tracer = MakeEvalTracerDebuggerAdaptor(&testDbg)
testLogic(t, testCallStackProgram, AssemblerMaxVersion, ep)
require.Equal(t, 1, testDbg.register)
diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go
index f7bb12048..158ca6e08 100644
--- a/data/transactions/logic/eval.go
+++ b/data/transactions/logic/eval.go
@@ -282,8 +282,8 @@ type EvalParams struct {
SigLedger LedgerForSignature
Ledger LedgerForLogic
- // optional debugger
- Debugger DebuggerHook
+ // optional tracer
+ Tracer EvalTracer
// MinAvmVersion is the minimum allowed AVM version of this program.
// The program must reject if its version is less than this version. If
@@ -455,7 +455,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext)
logger: caller.logger,
SigLedger: caller.SigLedger,
Ledger: caller.Ledger,
- Debugger: nil, // See #4438, where this becomes caller.Debugger
+ Tracer: caller.Tracer,
MinAvmVersion: &minAvmVersion,
FeeCredit: caller.FeeCredit,
Specials: caller.Specials,
@@ -474,28 +474,31 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext)
type evalFunc func(cx *EvalContext) error
type checkFunc func(cx *EvalContext) error
-type runMode uint64
+// RunMode is a bitset of logic evaluation modes.
+// There are currently two such modes: Signature and Application.
+type RunMode uint64
const (
- // modeSig is LogicSig execution
- modeSig runMode = 1 << iota
+ // ModeSig is LogicSig execution
+ ModeSig RunMode = 1 << iota
- // modeApp is application/contract execution
- modeApp
+ // ModeApp is application/contract execution
+ ModeApp
// local constant, run in any mode
- modeAny = modeSig | modeApp
+ modeAny = ModeSig | ModeApp
)
-func (r runMode) Any() bool {
+// Any checks if this mode bitset represents any evaluation mode
+func (r RunMode) Any() bool {
return r == modeAny
}
-func (r runMode) String() string {
+func (r RunMode) String() string {
switch r {
- case modeSig:
+ case ModeSig:
return "Signature"
- case modeApp:
+ case ModeApp:
return "Application"
case modeAny:
return "Any"
@@ -547,7 +550,7 @@ type EvalContext struct {
*EvalParams
// determines eval mode: runModeSignature or runModeApplication
- runModeFlags runMode
+ runModeFlags RunMode
// the index of the transaction being evaluated
groupIndex int
@@ -591,9 +594,11 @@ type EvalContext struct {
instructionStarts []bool
programHashCached crypto.Digest
+}
- // Stores state & disassembly for the optional debugger
- debugState *DebugState
+// RunMode returns the evaluation context's mode (signature or application)
+func (cx *EvalContext) RunMode() RunMode {
+ return cx.runModeFlags
}
// StackType describes the type of a value on the operand stack
@@ -700,7 +705,7 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam
}
cx := EvalContext{
EvalParams: params,
- runModeFlags: modeApp,
+ runModeFlags: ModeApp,
groupIndex: gi,
txn: &params.TxnGroup[gi],
appID: aid,
@@ -787,7 +792,7 @@ func EvalSignatureFull(gi int, params *EvalParams) (pass bool, pcx *EvalContext,
}
cx := EvalContext{
EvalParams: params,
- runModeFlags: modeSig,
+ runModeFlags: ModeSig,
groupIndex: gi,
txn: &params.TxnGroup[gi],
}
@@ -834,17 +839,11 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) {
cx.txn.EvalDelta.GlobalDelta = basics.StateDelta{}
cx.txn.EvalDelta.LocalDeltas = make(map[uint64]basics.StateDelta)
- if cx.Debugger != nil {
- cx.debugState = makeDebugState(cx)
- if derr := cx.Debugger.Register(cx.refreshDebugState(err)); derr != nil {
- return false, derr
- }
+ if cx.Tracer != nil {
+ cx.Tracer.BeforeProgram(cx)
defer func() {
- // Ensure we update the debugger before exiting
- errDbg := cx.Debugger.Complete(cx.refreshDebugState(err))
- if err == nil {
- err = errDbg
- }
+ // Ensure we update the tracer before exiting
+ cx.Tracer.AfterProgram(cx, err)
}()
}
@@ -859,13 +858,15 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) {
}
for (err == nil) && (cx.pc < len(cx.program)) {
- if cx.Debugger != nil {
- if derr := cx.Debugger.Update(cx.refreshDebugState(err)); derr != nil {
- return false, derr
- }
+ if cx.Tracer != nil {
+ cx.Tracer.BeforeOpcode(cx)
}
err = cx.step()
+
+ if cx.Tracer != nil {
+ cx.Tracer.AfterOpcode(cx, err)
+ }
}
if err != nil {
if cx.Trace != nil {
@@ -895,17 +896,17 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) {
// these static checks include a cost estimate that must be low enough
// (controlled by params.Proto).
func CheckContract(program []byte, params *EvalParams) error {
- return check(program, params, modeApp)
+ return check(program, params, ModeApp)
}
// CheckSignature should be faster than EvalSignature. It can perform static
// checks and reject programs that are invalid. Prior to v4, these static checks
// include a cost estimate that must be low enough (controlled by params.Proto).
func CheckSignature(gi int, params *EvalParams) error {
- return check(params.TxnGroup[gi].Lsig.Logic, params, modeSig)
+ return check(params.TxnGroup[gi].Lsig.Logic, params, ModeSig)
}
-func check(program []byte, params *EvalParams, mode runMode) (err error) {
+func check(program []byte, params *EvalParams, mode RunMode) (err error) {
defer func() {
if x := recover(); x != nil {
buf := make([]byte, 16*1024)
@@ -1012,7 +1013,7 @@ func (cx *EvalContext) Cost() int {
}
func (cx *EvalContext) remainingBudget() int {
- if cx.runModeFlags == modeSig {
+ if cx.runModeFlags == ModeSig {
return int(cx.Proto.LogicSigMaxCost) - cx.cost
}
@@ -1842,7 +1843,14 @@ func opBytesLt(cx *EvalContext) error {
rhs := nonzero(cx.stack[last].Bytes)
lhs := nonzero(cx.stack[prev].Bytes)
- cx.stack[prev] = boolToSV(len(lhs) < len(rhs) || bytes.Compare(lhs, rhs) < 0)
+ switch {
+ case len(lhs) < len(rhs):
+ cx.stack[prev] = boolToSV(true)
+ case len(lhs) > len(rhs):
+ cx.stack[prev] = boolToSV(false)
+ default:
+ cx.stack[prev] = boolToSV(bytes.Compare(lhs, rhs) < 0)
+ }
cx.stack = cx.stack[:last]
return nil
@@ -2614,7 +2622,7 @@ func (cx *EvalContext) getTxID(txn *transactions.Transaction, groupIndex int, in
func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *txnFieldSpec, arrayFieldIdx uint64, groupIndex int, inner bool) (sv stackValue, err error) {
if fs.effects {
- if cx.runModeFlags == modeSig {
+ if cx.runModeFlags == ModeSig {
return sv, fmt.Errorf("txn[%s] not allowed in current mode", fs.field)
}
if cx.version < txnEffectsVersion && !inner {
@@ -2882,7 +2890,7 @@ func (cx *EvalContext) opTxnImpl(gi uint64, src txnSource, field TxnField, ai ui
case srcGroup:
if fs.effects && gi >= uint64(cx.groupIndex) {
// Test mode so that error is clearer
- if cx.runModeFlags == modeSig {
+ if cx.runModeFlags == ModeSig {
return sv, fmt.Errorf("txn[%s] not allowed in current mode", fs.field)
}
return sv, fmt.Errorf("txn effects can only be read from past txns %d %d", gi, cx.groupIndex)
@@ -5166,7 +5174,16 @@ func opItxnSubmit(cx *EvalContext) error {
}
ep := NewInnerEvalParams(cx.subtxns, cx)
+
+ if ep.Tracer != nil {
+ ep.Tracer.BeforeTxnGroup(ep)
+ }
+
for i := range ep.TxnGroup {
+ if ep.Tracer != nil {
+ ep.Tracer.BeforeTxn(ep, i)
+ }
+
err := cx.Ledger.Perform(i, ep)
if err != nil {
return err
@@ -5174,11 +5191,20 @@ func opItxnSubmit(cx *EvalContext) error {
// This is mostly a no-op, because Perform does its work "in-place", but
// RecordAD has some further responsibilities.
ep.RecordAD(i, ep.TxnGroup[i].ApplyData)
+
+ if ep.Tracer != nil {
+ ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData)
+ }
}
cx.txn.EvalDelta.InnerTxns = append(cx.txn.EvalDelta.InnerTxns, ep.TxnGroup...)
cx.subtxns = nil
// must clear the inner txid cache, otherwise prior inner txids will be returned for this group
cx.innerTxidCache = nil
+
+ if ep.Tracer != nil {
+ ep.Tracer.AfterTxnGroup(ep)
+ }
+
return nil
}
diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go
index 3b755e899..a2f33cc90 100644
--- a/data/transactions/logic/evalStateful_test.go
+++ b/data/transactions/logic/evalStateful_test.go
@@ -197,9 +197,9 @@ pop
bytec_0
log
`
- tests := map[runMode]string{
- modeSig: opcodesRunModeAny + opcodesRunModeSignature,
- modeApp: opcodesRunModeAny + opcodesRunModeApplication,
+ tests := map[RunMode]string{
+ ModeSig: opcodesRunModeAny + opcodesRunModeSignature,
+ ModeApp: opcodesRunModeAny + opcodesRunModeApplication,
}
for mode, test := range tests {
@@ -237,7 +237,7 @@ log
ledger.NewLocal(tx.Sender, 100, "ALGO", algoValue)
ledger.NewAsset(tx.Sender, 5, params)
- if mode == modeSig {
+ if mode == ModeSig {
testLogic(t, test, AssemblerMaxVersion, ep)
} else {
testApp(t, test, ep)
@@ -303,9 +303,9 @@ log
"not allowed in current mode", "not allowed in current mode")
}
- require.Equal(t, runMode(1), modeSig)
- require.Equal(t, runMode(2), modeApp)
- require.True(t, modeAny == modeSig|modeApp)
+ require.Equal(t, RunMode(1), ModeSig)
+ require.Equal(t, RunMode(2), ModeApp)
+ require.True(t, modeAny == ModeSig|ModeApp)
require.True(t, modeAny.Any())
}
@@ -2442,7 +2442,7 @@ func TestReturnTypes(t *testing.T) {
}
byName := OpsByName[LogicVersion]
- for _, m := range []runMode{modeSig, modeApp} {
+ for _, m := range []RunMode{ModeSig, ModeApp} {
for name, spec := range byName {
// Only try an opcode in its modes
if (m & spec.Modes) == 0 {
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index f63eae7da..6731c1982 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -2753,7 +2753,7 @@ func TestGload(t *testing.T) {
// for more complex group transaction cases
type failureCase struct {
firstTxn transactions.SignedTxn
- runMode runMode
+ runMode RunMode
errContains string
}
@@ -2763,7 +2763,7 @@ func TestGload(t *testing.T) {
Type: protocol.PaymentTx,
},
},
- runMode: modeApp,
+ runMode: ModeApp,
errContains: "can't use gload on non-app call txn with index 0",
}
@@ -2773,7 +2773,7 @@ func TestGload(t *testing.T) {
Type: protocol.ApplicationCallTx,
},
},
- runMode: modeSig,
+ runMode: ModeSig,
errContains: "gload not allowed in current mode",
}
@@ -2794,7 +2794,7 @@ func TestGload(t *testing.T) {
program := testProg(t, "gload 0 0", AssemblerMaxVersion).Program
switch failCase.runMode {
- case modeApp:
+ case ModeApp:
testAppBytes(t, program, ep, failCase.errContains)
default:
testLogicBytes(t, program, ep, failCase.errContains, failCase.errContains)
@@ -4918,6 +4918,9 @@ func TestBytesCompare(t *testing.T) {
testPanics(t, "byte 0x10; int 65; bzero; b>", 4)
testAccepts(t, "byte 0x1010; byte 0x10; b<; !", 4)
+ testAccepts(t, "byte 0x2000; byte 0x70; b<; !", 4)
+ testAccepts(t, "byte 0x7000; byte 0x20; b<; !", 4)
+
// All zero input are interesting, because they lead to bytes.Compare being
// called with nils. Show that is correct.
testAccepts(t, "byte 0x10; byte 0x00; b<; !", 4)
@@ -5038,7 +5041,7 @@ func TestLog(t *testing.T) {
msg := strings.Repeat("a", 400)
failCases := []struct {
source string
- runMode runMode
+ runMode RunMode
errContains string
// For cases where assembly errors, we manually put in the bytes
assembledBytes []byte
@@ -5046,44 +5049,44 @@ func TestLog(t *testing.T) {
{
source: fmt.Sprintf(`byte "%s"; log; int 1`, strings.Repeat("a", maxLogSize+1)),
errContains: fmt.Sprintf("> %d bytes limit", maxLogSize),
- runMode: modeApp,
+ runMode: ModeApp,
},
{
source: fmt.Sprintf(`byte "%s"; log; byte "%s"; log; byte "%s"; log; int 1`, msg, msg, msg),
errContains: fmt.Sprintf("> %d bytes limit", maxLogSize),
- runMode: modeApp,
+ runMode: ModeApp,
},
{
source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)),
errContains: "too many log calls",
- runMode: modeApp,
+ runMode: ModeApp,
},
{
source: `int 1; loop: byte "a"; log; int 1; +; dup; int 35; <; bnz loop;`,
errContains: "too many log calls",
- runMode: modeApp,
+ runMode: ModeApp,
},
{
source: fmt.Sprintf(`int 1; loop: byte "%s"; log; int 1; +; dup; int 6; <; bnz loop;`, strings.Repeat(`a`, 400)),
errContains: fmt.Sprintf("> %d bytes limit", maxLogSize),
- runMode: modeApp,
+ runMode: ModeApp,
},
{
source: `load 0; log`,
errContains: "log arg 0 wanted []byte but got uint64",
- runMode: modeApp,
+ runMode: ModeApp,
assembledBytes: []byte{byte(ep.Proto.LogicSigVersion), 0x34, 0x00, 0xb0},
},
{
source: `byte "a logging message"; log; int 1`,
errContains: "log not allowed in current mode",
- runMode: modeSig,
+ runMode: ModeSig,
},
}
for _, c := range failCases {
switch c.runMode {
- case modeApp:
+ case ModeApp:
if c.assembledBytes == nil {
testApp(t, c.source, ep, c.errContains)
} else {
diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go
index dc9678b3d..0af2079a3 100644
--- a/data/transactions/logic/fields.go
+++ b/data/transactions/logic/fields.go
@@ -537,7 +537,7 @@ var GlobalFieldNames [invalidGlobalField]string
type globalFieldSpec struct {
field GlobalField
ftype StackType
- mode runMode
+ mode RunMode
version uint64
doc string
}
@@ -556,7 +556,7 @@ func (fs globalFieldSpec) Version() uint64 {
}
func (fs globalFieldSpec) Note() string {
note := fs.doc
- if fs.mode == modeApp {
+ if fs.mode == ModeApp {
note = addExtra(note, "Application mode only.")
}
// There are no Signature mode only globals
@@ -572,21 +572,21 @@ var globalFieldSpecs = [...]globalFieldSpec{
{GroupSize, StackUint64, modeAny, 0,
"Number of transactions in this atomic transaction group. At least 1"},
{LogicSigVersion, StackUint64, modeAny, 2, "Maximum supported version"},
- {Round, StackUint64, modeApp, 2, "Current round number"},
- {LatestTimestamp, StackUint64, modeApp, 2,
+ {Round, StackUint64, ModeApp, 2, "Current round number"},
+ {LatestTimestamp, StackUint64, ModeApp, 2,
"Last confirmed block UNIX timestamp. Fails if negative"},
- {CurrentApplicationID, StackUint64, modeApp, 2, "ID of current application executing"},
- {CreatorAddress, StackBytes, modeApp, 3,
+ {CurrentApplicationID, StackUint64, ModeApp, 2, "ID of current application executing"},
+ {CreatorAddress, StackBytes, ModeApp, 3,
"Address of the creator of the current application"},
- {CurrentApplicationAddress, StackBytes, modeApp, 5,
+ {CurrentApplicationAddress, StackBytes, ModeApp, 5,
"Address that the current application controls"},
{GroupID, StackBytes, modeAny, 5,
"ID of the transaction group. 32 zero bytes if the transaction is not part of a group."},
{OpcodeBudget, StackUint64, modeAny, 6,
"The remaining cost that can be spent by opcodes in this program."},
- {CallerApplicationID, StackUint64, modeApp, 6,
+ {CallerApplicationID, StackUint64, ModeApp, 6,
"The application ID of the application that called this application. 0 if this application is at the top-level."},
- {CallerApplicationAddress, StackBytes, modeApp, 6,
+ {CallerApplicationAddress, StackBytes, ModeApp, 6,
"The application address of the application that called this application. ZeroAddress if this application is at the top-level."},
}
diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go
new file mode 100644
index 000000000..967798ec9
--- /dev/null
+++ b/data/transactions/logic/mocktracer/tracer.go
@@ -0,0 +1,147 @@
+// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
+
+package mocktracer
+
+import (
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/protocol"
+)
+
+// EventType represents a type of logic.EvalTracer event
+type EventType string
+
+const (
+ // BeforeTxnGroupEvent represents the logic.EvalTracer.BeforeTxnGroup event
+ BeforeTxnGroupEvent EventType = "BeforeTxnGroup"
+ // AfterTxnGroupEvent represents the logic.EvalTracer.AfterTxnGroup event
+ AfterTxnGroupEvent EventType = "AfterTxnGroup"
+ // BeforeTxnEvent represents the logic.EvalTracer.BeforeTxn event
+ BeforeTxnEvent EventType = "BeforeTxn"
+ // AfterTxnEvent represents the logic.EvalTracer.AfterTxn event
+ AfterTxnEvent EventType = "AfterTxn"
+ // BeforeProgramEvent represents the logic.EvalTracer.BeforeProgram event
+ BeforeProgramEvent EventType = "BeforeProgram"
+ // AfterProgramEvent represents the logic.EvalTracer.AfterProgram event
+ AfterProgramEvent EventType = "AfterProgram"
+ // BeforeOpcodeEvent represents the logic.EvalTracer.BeforeOpcode event
+ BeforeOpcodeEvent EventType = "BeforeOpcode"
+ // AfterOpcodeEvent represents the logic.EvalTracer.AfterOpcode event
+ AfterOpcodeEvent EventType = "AfterOpcode"
+)
+
+// Event represents a logic.EvalTracer event
+type Event struct {
+ Type EventType
+
+ // only for BeforeProgram and AfterProgram
+ LogicEvalMode logic.RunMode
+
+ // only for BeforeTxn and AfterTxn
+ TxnType protocol.TxType
+
+ // only for AfterTxn
+ TxnApplyData transactions.ApplyData
+
+ // only for BeforeTxnGroup and AfterTxnGroup
+ GroupSize int
+}
+
+// BeforeTxnGroup creates a new Event with the type BeforeTxnGroupEvent
+func BeforeTxnGroup(groupSize int) Event {
+ return Event{Type: BeforeTxnGroupEvent, GroupSize: groupSize}
+}
+
+// AfterTxnGroup creates a new Event with the type AfterTxnGroupEvent
+func AfterTxnGroup(groupSize int) Event {
+ return Event{Type: AfterTxnGroupEvent, GroupSize: groupSize}
+}
+
+// BeforeProgram creates a new Event with the type BeforeProgramEvent
+func BeforeProgram(mode logic.RunMode) Event {
+ return Event{Type: BeforeProgramEvent, LogicEvalMode: mode}
+}
+
+// BeforeTxn creates a new Event with the type BeforeTxnEvent
+func BeforeTxn(txnType protocol.TxType) Event {
+ return Event{Type: BeforeTxnEvent, TxnType: txnType}
+}
+
+// AfterTxn creates a new Event with the type AfterTxnEvent
+func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData) Event {
+ return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad}
+}
+
+// AfterProgram creates a new Event with the type AfterProgramEvent
+func AfterProgram(mode logic.RunMode) Event {
+ return Event{Type: AfterProgramEvent, LogicEvalMode: mode}
+}
+
+// BeforeOpcode creates a new Event with the type BeforeOpcodeEvent
+func BeforeOpcode() Event {
+ return Event{Type: BeforeOpcodeEvent}
+}
+
+// AfterOpcode creates a new Event with the type AfterOpcodeEvent
+func AfterOpcode() Event {
+ return Event{Type: AfterOpcodeEvent}
+}
+
+// Tracer is a mock tracer that implements logic.EvalTracer
+type Tracer struct {
+ Events []Event
+}
+
+// BeforeTxnGroup mocks the logic.EvalTracer.BeforeTxnGroup method
+func (d *Tracer) BeforeTxnGroup(ep *logic.EvalParams) {
+ d.Events = append(d.Events, BeforeTxnGroup(len(ep.TxnGroup)))
+}
+
+// AfterTxnGroup mocks the logic.EvalTracer.AfterTxnGroup method
+func (d *Tracer) AfterTxnGroup(ep *logic.EvalParams) {
+ d.Events = append(d.Events, AfterTxnGroup(len(ep.TxnGroup)))
+}
+
+// BeforeTxn mocks the logic.EvalTracer.BeforeTxn method
+func (d *Tracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) {
+ d.Events = append(d.Events, BeforeTxn(ep.TxnGroup[groupIndex].Txn.Type))
+}
+
+// AfterTxn mocks the logic.EvalTracer.AfterTxn method
+func (d *Tracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData) {
+ d.Events = append(d.Events, AfterTxn(ep.TxnGroup[groupIndex].Txn.Type, ad))
+}
+
+// BeforeProgram mocks the logic.EvalTracer.BeforeProgram method
+func (d *Tracer) BeforeProgram(cx *logic.EvalContext) {
+ d.Events = append(d.Events, BeforeProgram(cx.RunMode()))
+}
+
+// AfterProgram mocks the logic.EvalTracer.AfterProgram method
+func (d *Tracer) AfterProgram(cx *logic.EvalContext, evalError error) {
+ d.Events = append(d.Events, AfterProgram(cx.RunMode()))
+}
+
+// BeforeOpcode mocks the logic.EvalTracer.BeforeOpcode method
+func (d *Tracer) BeforeOpcode(cx *logic.EvalContext) {
+ d.Events = append(d.Events, BeforeOpcode())
+}
+
+// AfterOpcode mocks the logic.EvalTracer.AfterOpcode method
+func (d *Tracer) AfterOpcode(cx *logic.EvalContext, evalError error) {
+ d.Events = append(d.Events, AfterOpcode())
+}
diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go
index ffce9cf3e..8f47b57ca 100644
--- a/data/transactions/logic/opcodes.go
+++ b/data/transactions/logic/opcodes.go
@@ -121,7 +121,7 @@ type OpDetails struct {
check checkFunc // static check bytecode (and determine size)
refine refineFunc // refine arg/return types based on ProgramKnowledge at assembly time
- Modes runMode // all modes that opcode can run in. i.e (cx.mode & Modes) != 0 allows
+ Modes RunMode // all modes that opcode can run in. i.e (cx.mode & Modes) != 0 allows
FullCost linearCost // if non-zero, the cost of the opcode, no immediates matter
Size int // if non-zero, the known size of opcode. if 0, check() determines.
@@ -221,13 +221,13 @@ func (d OpDetails) costs(cost int) OpDetails {
return d
}
-func only(m runMode) OpDetails {
+func only(m RunMode) OpDetails {
d := detDefault()
d.Modes = m
return d
}
-func (d OpDetails) only(m runMode) OpDetails {
+func (d OpDetails) only(m RunMode) OpDetails {
d.Modes = m
return d
}
@@ -419,7 +419,7 @@ var OpSpecs = []OpSpec{
{0x03, "sha512_256", opSHA512_256, proto("b:b"), 7, unlimitedStorage, costByLength(17, 5, 8)},
*/
- {0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 1, costly(1900).only(modeSig)},
+ {0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 1, costly(1900).only(ModeSig)},
{0x04, "ed25519verify", opEd25519Verify, proto("bbb:i"), 5, costly(1900)},
{0x05, "ecdsa_verify", opEcdsaVerify, proto("bbbbb:i"), 5, costByField("v", &EcdsaCurves, ecdsaVerifyCosts)},
@@ -463,11 +463,11 @@ var OpSpecs = []OpSpec{
{0x29, "bytec_1", opByteConst1, proto(":b"), 1, detDefault()},
{0x2a, "bytec_2", opByteConst2, proto(":b"), 1, detDefault()},
{0x2b, "bytec_3", opByteConst3, proto(":b"), 1, detDefault()},
- {0x2c, "arg", opArg, proto(":b"), 1, immediates("n").only(modeSig).assembler(asmArg)},
- {0x2d, "arg_0", opArg0, proto(":b"), 1, only(modeSig)},
- {0x2e, "arg_1", opArg1, proto(":b"), 1, only(modeSig)},
- {0x2f, "arg_2", opArg2, proto(":b"), 1, only(modeSig)},
- {0x30, "arg_3", opArg3, proto(":b"), 1, only(modeSig)},
+ {0x2c, "arg", opArg, proto(":b"), 1, immediates("n").only(ModeSig).assembler(asmArg)},
+ {0x2d, "arg_0", opArg0, proto(":b"), 1, only(ModeSig)},
+ {0x2e, "arg_1", opArg1, proto(":b"), 1, only(ModeSig)},
+ {0x2f, "arg_2", opArg2, proto(":b"), 1, only(ModeSig)},
+ {0x30, "arg_3", opArg3, proto(":b"), 1, only(ModeSig)},
// txn, gtxn, and gtxns are also implemented as pseudoOps to choose
// between scalar and array version based on number of immediates.
{0x31, "txn", opTxn, proto(":a"), 1, field("f", &TxnScalarFields)},
@@ -481,11 +481,11 @@ var OpSpecs = []OpSpec{
{0x38, "gtxns", opGtxns, proto("i:a"), 3, immediates("f").field("f", &TxnScalarFields)},
{0x39, "gtxnsa", opGtxnsa, proto("i:a"), 3, immediates("f", "i").field("f", &TxnArrayFields)},
// Group scratch space access
- {0x3a, "gload", opGload, proto(":a"), 4, immediates("t", "i").only(modeApp)},
- {0x3b, "gloads", opGloads, proto("i:a"), 4, immediates("i").only(modeApp)},
+ {0x3a, "gload", opGload, proto(":a"), 4, immediates("t", "i").only(ModeApp)},
+ {0x3b, "gloads", opGloads, proto("i:a"), 4, immediates("i").only(ModeApp)},
// Access creatable IDs (consider deprecating, as txn CreatedAssetID, CreatedApplicationID should be enough
- {0x3c, "gaid", opGaid, proto(":i"), 4, immediates("t").only(modeApp)},
- {0x3d, "gaids", opGaids, proto("i:i"), 4, only(modeApp)},
+ {0x3c, "gaid", opGaid, proto(":i"), 4, immediates("t").only(ModeApp)},
+ {0x3d, "gaids", opGaids, proto("i:i"), 4, only(ModeApp)},
// Like load/store, but scratch slot taken from TOS instead of immediate
{0x3e, "loads", opLoads, proto("i:a"), 5, typed(typeLoads)},
@@ -526,31 +526,31 @@ var OpSpecs = []OpSpec{
{0x5e, "base64_decode", opBase64Decode, proto("b:b"), fidoVersion, field("e", &Base64Encodings).costByLength(1, 1, 16, 0)},
{0x5f, "json_ref", opJSONRef, proto("bb:a"), fidoVersion, field("r", &JSONRefTypes).costByLength(25, 2, 7, 1)},
- {0x60, "balance", opBalance, proto("i:i"), 2, only(modeApp)},
- {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(modeApp)},
- {0x61, "app_opted_in", opAppOptedIn, proto("ii:i"), 2, only(modeApp)},
- {0x61, "app_opted_in", opAppOptedIn, proto("ai:i"), directRefEnabledVersion, only(modeApp)},
- {0x62, "app_local_get", opAppLocalGet, proto("ib:a"), 2, only(modeApp)},
- {0x62, "app_local_get", opAppLocalGet, proto("ab:a"), directRefEnabledVersion, only(modeApp)},
- {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:ai"), 2, only(modeApp)},
- {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:ai"), directRefEnabledVersion, only(modeApp)},
- {0x64, "app_global_get", opAppGlobalGet, proto("b:a"), 2, only(modeApp)},
- {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:ai"), 2, only(modeApp)},
- {0x66, "app_local_put", opAppLocalPut, proto("iba:"), 2, only(modeApp)},
- {0x66, "app_local_put", opAppLocalPut, proto("aba:"), directRefEnabledVersion, only(modeApp)},
- {0x67, "app_global_put", opAppGlobalPut, proto("ba:"), 2, only(modeApp)},
- {0x68, "app_local_del", opAppLocalDel, proto("ib:"), 2, only(modeApp)},
- {0x68, "app_local_del", opAppLocalDel, proto("ab:"), directRefEnabledVersion, only(modeApp)},
- {0x69, "app_global_del", opAppGlobalDel, proto("b:"), 2, only(modeApp)},
-
- {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:ai"), 2, field("f", &AssetHoldingFields).only(modeApp)},
- {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:ai"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(modeApp)},
- {0x71, "asset_params_get", opAssetParamsGet, proto("i:ai"), 2, field("f", &AssetParamsFields).only(modeApp)},
- {0x72, "app_params_get", opAppParamsGet, proto("i:ai"), 5, field("f", &AppParamsFields).only(modeApp)},
- {0x73, "acct_params_get", opAcctParamsGet, proto("a:ai"), 6, field("f", &AcctParamsFields).only(modeApp)},
-
- {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(modeApp)},
- {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(modeApp)},
+ {0x60, "balance", opBalance, proto("i:i"), 2, only(ModeApp)},
+ {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)},
+ {0x61, "app_opted_in", opAppOptedIn, proto("ii:i"), 2, only(ModeApp)},
+ {0x61, "app_opted_in", opAppOptedIn, proto("ai:i"), directRefEnabledVersion, only(ModeApp)},
+ {0x62, "app_local_get", opAppLocalGet, proto("ib:a"), 2, only(ModeApp)},
+ {0x62, "app_local_get", opAppLocalGet, proto("ab:a"), directRefEnabledVersion, only(ModeApp)},
+ {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:ai"), 2, only(ModeApp)},
+ {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:ai"), directRefEnabledVersion, only(ModeApp)},
+ {0x64, "app_global_get", opAppGlobalGet, proto("b:a"), 2, only(ModeApp)},
+ {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:ai"), 2, only(ModeApp)},
+ {0x66, "app_local_put", opAppLocalPut, proto("iba:"), 2, only(ModeApp)},
+ {0x66, "app_local_put", opAppLocalPut, proto("aba:"), directRefEnabledVersion, only(ModeApp)},
+ {0x67, "app_global_put", opAppGlobalPut, proto("ba:"), 2, only(ModeApp)},
+ {0x68, "app_local_del", opAppLocalDel, proto("ib:"), 2, only(ModeApp)},
+ {0x68, "app_local_del", opAppLocalDel, proto("ab:"), directRefEnabledVersion, only(ModeApp)},
+ {0x69, "app_global_del", opAppGlobalDel, proto("b:"), 2, only(ModeApp)},
+
+ {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:ai"), 2, field("f", &AssetHoldingFields).only(ModeApp)},
+ {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:ai"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(ModeApp)},
+ {0x71, "asset_params_get", opAssetParamsGet, proto("i:ai"), 2, field("f", &AssetParamsFields).only(ModeApp)},
+ {0x72, "app_params_get", opAppParamsGet, proto("i:ai"), 5, field("f", &AppParamsFields).only(ModeApp)},
+ {0x73, "acct_params_get", opAcctParamsGet, proto("a:ai"), 6, field("f", &AcctParamsFields).only(ModeApp)},
+
+ {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(ModeApp)},
+ {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)},
// Immediate bytes and ints. Smaller code size for single use of constant.
{0x80, "pushbytes", opPushBytes, proto(":b"), 3, constants(asmPushBytes, opPushBytes, "bytes", immBytes)},
@@ -607,33 +607,33 @@ var OpSpecs = []OpSpec{
{0xaf, "bzero", opBytesZero, proto("i:b"), 4, detDefault()},
// AVM "effects"
- {0xb0, "log", opLog, proto("b:"), 5, only(modeApp)},
- {0xb1, "itxn_begin", opTxBegin, proto(":"), 5, only(modeApp)},
- {0xb2, "itxn_field", opItxnField, proto("a:"), 5, immediates("f").typed(typeTxField).field("f", &TxnFields).only(modeApp).assembler(asmItxnField)},
- {0xb3, "itxn_submit", opItxnSubmit, proto(":"), 5, only(modeApp)},
- {0xb4, "itxn", opItxn, proto(":a"), 5, field("f", &TxnScalarFields).only(modeApp).assembler(asmItxn)},
- {0xb5, "itxna", opItxna, proto(":a"), 5, immediates("f", "i").field("f", &TxnArrayFields).only(modeApp)},
- {0xb6, "itxn_next", opItxnNext, proto(":"), 6, only(modeApp)},
- {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)},
+ {0xb0, "log", opLog, proto("b:"), 5, only(ModeApp)},
+ {0xb1, "itxn_begin", opTxBegin, proto(":"), 5, only(ModeApp)},
+ {0xb2, "itxn_field", opItxnField, proto("a:"), 5, immediates("f").typed(typeTxField).field("f", &TxnFields).only(ModeApp).assembler(asmItxnField)},
+ {0xb3, "itxn_submit", opItxnSubmit, proto(":"), 5, only(ModeApp)},
+ {0xb4, "itxn", opItxn, proto(":a"), 5, field("f", &TxnScalarFields).only(ModeApp).assembler(asmItxn)},
+ {0xb5, "itxna", opItxna, proto(":a"), 5, immediates("f", "i").field("f", &TxnArrayFields).only(ModeApp)},
+ {0xb6, "itxn_next", opItxnNext, proto(":"), 6, only(ModeApp)},
+ {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("bi:i"), 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:i"), boxVersion, only(modeApp)},
- {0xbd, "box_len", opBoxLen, proto("b:ii"), boxVersion, only(modeApp)},
- {0xbe, "box_get", opBoxGet, proto("b:bi"), boxVersion, only(modeApp)},
- {0xbf, "box_put", opBoxPut, proto("bb:"), boxVersion, only(modeApp)},
+ {0xb9, "box_create", opBoxCreate, proto("bi:i"), 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:i"), boxVersion, only(ModeApp)},
+ {0xbd, "box_len", opBoxLen, proto("b:ii"), boxVersion, only(ModeApp)},
+ {0xbe, "box_get", opBoxGet, proto("b:bi"), boxVersion, only(ModeApp)},
+ {0xbf, "box_put", opBoxPut, proto("bb:"), 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)},
{0xc2, "gtxnsas", opGtxnsas, proto("ii:a"), 5, field("f", &TxnArrayFields)},
- {0xc3, "args", opArgs, proto("i:b"), 5, only(modeSig)},
- {0xc4, "gloadss", opGloadss, proto("ii:a"), 6, only(modeApp)},
- {0xc5, "itxnas", opItxnas, proto("i:a"), 6, field("f", &TxnArrayFields).only(modeApp)},
- {0xc6, "gitxnas", opGitxnas, proto("i:a"), 6, immediates("t", "f").field("f", &TxnArrayFields).only(modeApp)},
+ {0xc3, "args", opArgs, proto("i:b"), 5, only(ModeSig)},
+ {0xc4, "gloadss", opGloadss, proto("ii:a"), 6, only(ModeApp)},
+ {0xc5, "itxnas", opItxnas, proto("i:a"), 6, field("f", &TxnArrayFields).only(ModeApp)},
+ {0xc6, "gitxnas", opGitxnas, proto("i:a"), 6, immediates("t", "f").field("f", &TxnArrayFields).only(ModeApp)},
// randomness support
{0xd0, "vrf_verify", opVrfVerify, proto("bbb:bi"), randomnessVersion, field("s", &VrfStandards).costs(5700)},
diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go
new file mode 100644
index 000000000..929d5cdb8
--- /dev/null
+++ b/data/transactions/logic/tracer.go
@@ -0,0 +1,160 @@
+// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
+
+package logic
+
+import "github.com/algorand/go-algorand/data/transactions"
+
+// EvalTracer functions are called by eval function during AVM program execution, if a tracer
+// is provided.
+//
+// Refer to the lifecycle graph below for the sequence in which hooks are called.
+//
+// NOTE: Arguments given to Tracer hooks (EvalParams and EvalContext) are passed by reference,
+// they are not copies. It is therefore the responsibility of the tracer implementation to NOT
+// modify the state of the structs passed to them. Additionally, hooks are responsible for copying
+// the information they need from the argument structs. No guarantees are made that the referenced
+// state will not change between hook calls. This decision was made in an effort to reduce the
+// performance impact of tracers.
+//
+// LOGICSIG LIFECYCLE GRAPH
+// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+// β”‚ LogicSig Evaluation β”‚
+// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
+// β”‚ > BeforeProgram β”‚
+// β”‚ β”‚
+// β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
+// β”‚ β”‚ Teal Operation β”‚ β”‚
+// β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
+// β”‚ β”‚ > BeforeOpcode β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ > AfterOpcode β”‚ β”‚
+// β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
+// | ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ β”‚
+// β”‚ β”‚
+// β”‚ > AfterProgram β”‚
+// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+//
+// APP LIFECYCLE GRAPH
+// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+// β”‚ Transaction Evaluation β”‚
+// β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
+// β”‚ > BeforeTxnGroup β”‚
+// β”‚ β”‚
+// β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
+// β”‚ β”‚ > BeforeTxn β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
+// β”‚ β”‚ β”‚ ? App Call β”‚ β”‚ β”‚
+// β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚
+// β”‚ β”‚ β”‚ > BeforeProgram β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ Teal Operation β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ > BeforeOpcode β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ ? Inner Transaction Group β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ > BeforeTxnGroup β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Transaction Evaluation β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ ... β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ > AfterTxnGroup β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ > AfterOpcode β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ β”‚ > AfterProgram β”‚ β”‚ β”‚
+// β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
+// | | ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ β”‚ |
+// β”‚ β”‚ β”‚ β”‚
+// β”‚ β”‚ > AfterTxn β”‚ β”‚
+// β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
+// | ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ ⁞ |
+// β”‚ β”‚
+// β”‚ > AfterTxnGroup β”‚
+// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+type EvalTracer interface {
+ // BeforeTxnGroup is called before a transaction group is executed. This includes both top-level
+ // and inner transaction groups. The argument ep is the EvalParams object for the group; if the
+ // group is an inner group, this is the EvalParams object for the inner group.
+ //
+ // Each transaction within the group calls BeforeTxn and subsequent hooks, as described in the
+ // lifecycle diagram.
+ BeforeTxnGroup(ep *EvalParams)
+
+ // AfterTxnGroup is called after a transaction group has been executed. This includes both
+ // top-level and inner transaction groups. The argument ep is the EvalParams object for the
+ // group; if the group is an inner group, this is the EvalParams object for the inner group.
+ AfterTxnGroup(ep *EvalParams)
+
+ // BeforeTxn is called before a transaction is executed.
+ //
+ // groupIndex refers to the index of the transaction in the transaction group that will be executed.
+ BeforeTxn(ep *EvalParams, groupIndex int)
+
+ // AfterTxn is called after a transaction has been executed.
+ //
+ // groupIndex refers to the index of the transaction in the transaction group that was just executed.
+ // ad is the ApplyData result of the transaction; prefer using this instead of
+ // ep.TxnGroup[groupIndex].ApplyData, since it may not be populated at this point.
+ AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData)
+
+ // BeforeProgram is called before an app or LogicSig program is evaluated.
+ BeforeProgram(cx *EvalContext)
+
+ // AfterProgram is called after an app or LogicSig program is evaluated.
+ AfterProgram(cx *EvalContext, evalError error)
+
+ // BeforeOpcode is called before the op is evaluated
+ BeforeOpcode(cx *EvalContext)
+
+ // AfterOpcode is called after the op has been evaluated
+ AfterOpcode(cx *EvalContext, evalError error)
+}
+
+// NullEvalTracer implements EvalTracer, but all of its hook methods do nothing
+type NullEvalTracer struct{}
+
+// BeforeTxnGroup does nothing
+func (n NullEvalTracer) BeforeTxnGroup(ep *EvalParams) {}
+
+// AfterTxnGroup does nothing
+func (n NullEvalTracer) AfterTxnGroup(ep *EvalParams) {}
+
+// BeforeTxn does nothing
+func (n NullEvalTracer) BeforeTxn(ep *EvalParams, groupIndex int) {}
+
+// AfterTxn does nothing
+func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData) {}
+
+// BeforeProgram does nothing
+func (n NullEvalTracer) BeforeProgram(cx *EvalContext) {}
+
+// AfterProgram does nothing
+func (n NullEvalTracer) AfterProgram(cx *EvalContext, evalError error) {}
+
+// BeforeOpcode does nothing
+func (n NullEvalTracer) BeforeOpcode(cx *EvalContext) {}
+
+// AfterOpcode does nothing
+func (n NullEvalTracer) AfterOpcode(cx *EvalContext, evalError error) {}
diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go
new file mode 100644
index 000000000..c47f3d192
--- /dev/null
+++ b/data/transactions/logic/tracer_test.go
@@ -0,0 +1,195 @@
+// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
+
+package logic
+
+import (
+ "testing"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+const innerTxnTestProgram string = `itxn_begin
+int appl
+itxn_field TypeEnum
+int NoOp
+itxn_field OnCompletion
+byte 0x068101 // #pragma version 6; int 1;
+dup
+itxn_field ApprovalProgram
+itxn_field ClearStateProgram
+itxn_submit
+
+itxn_begin
+int pay
+itxn_field TypeEnum
+int 1
+itxn_field Amount
+global CurrentApplicationAddress
+itxn_field Receiver
+itxn_next
+int pay
+itxn_field TypeEnum
+int 2
+itxn_field Amount
+global CurrentApplicationAddress
+itxn_field Receiver
+itxn_submit
+
+int 1
+`
+
+// can't use mocktracer.Tracer because the import would be circular
+type testEvalTracer struct {
+ beforeTxnGroupCalls int
+ afterTxnGroupCalls int
+
+ beforeTxnCalls int
+ afterTxnCalls int
+
+ beforeProgramCalls int
+ afterProgramCalls int
+ programModes []RunMode
+
+ beforeOpcodeCalls int
+ afterOpcodeCalls int
+}
+
+func (t *testEvalTracer) BeforeTxnGroup(ep *EvalParams) {
+ t.beforeTxnGroupCalls++
+}
+
+func (t *testEvalTracer) AfterTxnGroup(ep *EvalParams) {
+ t.afterTxnGroupCalls++
+}
+
+func (t *testEvalTracer) BeforeTxn(ep *EvalParams, groupIndex int) {
+ t.beforeTxnCalls++
+}
+
+func (t *testEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions.ApplyData) {
+ t.afterTxnCalls++
+}
+
+func (t *testEvalTracer) BeforeProgram(cx *EvalContext) {
+ t.beforeProgramCalls++
+ t.programModes = append(t.programModes, cx.RunMode())
+}
+
+func (t *testEvalTracer) AfterProgram(cx *EvalContext, evalError error) {
+ t.afterProgramCalls++
+}
+
+func (t *testEvalTracer) BeforeOpcode(cx *EvalContext) {
+ t.beforeOpcodeCalls++
+}
+
+func (t *testEvalTracer) AfterOpcode(cx *EvalContext, evalError error) {
+ t.afterOpcodeCalls++
+}
+
+func TestEvalWithTracer(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ t.Run("logicsig", func(t *testing.T) {
+ t.Parallel()
+ testTracer := testEvalTracer{}
+ ep := defaultEvalParams()
+ ep.Tracer = &testTracer
+ testLogic(t, debuggerTestProgram, AssemblerMaxVersion, ep)
+
+ // BeforeTxnGroup/AfterTxnGroup/BeforeTxn/AfterTxn are only called for the inner txns in
+ // this test, not the top-level ones
+ require.Zero(t, testTracer.beforeTxnGroupCalls)
+ require.Zero(t, testTracer.afterTxnGroupCalls)
+ require.Zero(t, testTracer.beforeTxnCalls)
+ require.Zero(t, testTracer.afterTxnCalls)
+
+ require.Equal(t, 1, testTracer.beforeProgramCalls)
+ require.Equal(t, 1, testTracer.afterProgramCalls)
+ require.Equal(t, []RunMode{ModeSig}, testTracer.programModes)
+
+ require.Equal(t, 35, testTracer.beforeOpcodeCalls)
+ require.Equal(t, testTracer.beforeOpcodeCalls, testTracer.afterOpcodeCalls)
+ })
+
+ t.Run("simple app", func(t *testing.T) {
+ t.Parallel()
+ testTracer := testEvalTracer{}
+ ep := defaultEvalParams()
+ ep.Tracer = &testTracer
+ testApp(t, debuggerTestProgram, ep)
+
+ // BeforeTxnGroup/AfterTxnGroup/BeforeTxn/AfterTxn are only called for the inner txns in
+ // this test, not the top-level ones
+ require.Zero(t, testTracer.beforeTxnGroupCalls)
+ require.Zero(t, testTracer.afterTxnGroupCalls)
+ require.Zero(t, testTracer.beforeTxnCalls)
+ require.Zero(t, testTracer.afterTxnCalls)
+
+ require.Equal(t, 1, testTracer.beforeProgramCalls)
+ require.Equal(t, 1, testTracer.afterProgramCalls)
+ require.Equal(t, []RunMode{ModeApp}, testTracer.programModes)
+
+ require.Equal(t, 35, testTracer.beforeOpcodeCalls)
+ require.Equal(t, testTracer.beforeOpcodeCalls, testTracer.afterOpcodeCalls)
+ })
+
+ t.Run("app with inner txns", func(t *testing.T) {
+ t.Parallel()
+ testTracer := testEvalTracer{}
+ ep, tx, ledger := MakeSampleEnv()
+
+ // Establish 888 as the app id, and fund it.
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(basics.AppIndex(888).Address(), 200000)
+
+ ep.Tracer = &testTracer
+ testApp(t, innerTxnTestProgram, ep)
+
+ // BeforeTxnGroup/AfterTxnGroup/BeforeTxn/AfterTxn are only called for the inner txns in
+ // this test, not the top-level ones
+
+ // two groups of inner txns were issued
+ require.Equal(t, 2, testTracer.beforeTxnGroupCalls)
+ require.Equal(t, 2, testTracer.afterTxnGroupCalls)
+
+ // three total inner txns were issued
+ require.Equal(t, 3, testTracer.beforeTxnCalls)
+ require.Equal(t, 3, testTracer.afterTxnCalls)
+
+ require.Equal(t, 2, testTracer.beforeProgramCalls)
+ require.Equal(t, 2, testTracer.afterProgramCalls)
+ require.Equal(t, []RunMode{ModeApp, ModeApp}, testTracer.programModes)
+
+ appCallTealOps := 27
+ innerAppCallTealOps := 1
+ require.Equal(t, appCallTealOps+innerAppCallTealOps, testTracer.beforeOpcodeCalls)
+ require.Equal(t, testTracer.beforeOpcodeCalls, testTracer.afterOpcodeCalls)
+ })
+}
+
+func TestNullEvalTracerIsEvalTracer(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ var tracer EvalTracer = NullEvalTracer{}
+ require.NotNil(t, tracer)
+}
diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go
index 58249bc79..2edd69aa0 100644
--- a/data/transactions/verify/txn.go
+++ b/data/transactions/verify/txn.go
@@ -175,7 +175,7 @@ func (g *GroupContext) Equal(other *GroupContext) bool {
// txnBatchPrep verifies a SignedTxn having no obviously inconsistent data.
// Block-assembly time checks of LogicSig and accounting rules may still block the txn.
// It is the caller responsibility to call batchVerifier.Verify().
-func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) *TxGroupError {
+func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError {
if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) {
return &TxGroupError{err: errRekeyingNotSupported, Reason: TxGroupErrorReasonGeneric}
}
@@ -184,14 +184,23 @@ func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext,
return &TxGroupError{err: err, Reason: TxGroupErrorReasonNotWellFormed}
}
- return stxnCoreChecks(s, txnIdx, groupCtx, verifier)
+ return stxnCoreChecks(s, txnIdx, groupCtx, verifier, evalTracer)
}
// TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data.
func TxnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (groupCtx *GroupContext, err error) {
+ return txnGroup(stxs, contextHdr, cache, ledger, nil)
+}
+
+// TxnGroupWithTracer verifies a []SignedTxn as being signed and having no obviously inconsistent data, while using a tracer.
+func TxnGroupWithTracer(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, evalTracer logic.EvalTracer) (groupCtx *GroupContext, err error) {
+ return txnGroup(stxs, contextHdr, cache, ledger, evalTracer)
+}
+
+func txnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, evalTracer logic.EvalTracer) (groupCtx *GroupContext, err error) {
batchVerifier := crypto.MakeBatchVerifier()
- if groupCtx, err = txnGroupBatchPrep(stxs, contextHdr, ledger, batchVerifier); err != nil {
+ if groupCtx, err = txnGroupBatchPrep(stxs, contextHdr, ledger, batchVerifier, evalTracer); err != nil {
return nil, err
}
@@ -208,7 +217,7 @@ func TxnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader
// txnGroupBatchPrep verifies a []SignedTxn having no obviously inconsistent data.
// it is the caller responsibility to call batchVerifier.Verify()
-func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier) (*GroupContext, error) {
+func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) (*GroupContext, error) {
groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger)
if err != nil {
return nil, err
@@ -217,7 +226,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl
minFeeCount := uint64(0)
feesPaid := uint64(0)
for i, stxn := range stxs {
- prepErr := txnBatchPrep(&stxn, i, groupCtx, verifier)
+ prepErr := txnBatchPrep(&stxn, i, groupCtx, verifier, evalTracer)
if prepErr != nil {
// re-wrap the error with more details
prepErr.err = fmt.Errorf("transaction %+v invalid : %w", stxn, prepErr.err)
@@ -288,7 +297,7 @@ func checkTxnSigTypeCounts(s *transactions.SignedTxn) (sigType sigOrTxnType, err
}
// stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification.
-func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) *TxGroupError {
+func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) *TxGroupError {
sigType, err := checkTxnSigTypeCounts(s)
if err != nil {
return err
@@ -318,7 +327,7 @@ func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex
return nil
case logicSig:
- if err := logicSigVerify(s, txnIdx, groupCtx); err != nil {
+ if err := logicSigVerify(s, txnIdx, groupCtx, evalTracer); err != nil {
return &TxGroupError{err: err, Reason: TxGroupErrorReasonLogicSigFailed}
}
return nil
@@ -428,7 +437,7 @@ func logicSigSanityCheckBatchPrep(txn *transactions.SignedTxn, groupIndex int, g
}
// logicSigVerify checks that the signature is valid, executing the program.
-func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext) error {
+func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *GroupContext, evalTracer logic.EvalTracer) error {
err := LogicSigSanityCheck(txn, groupIndex, groupCtx)
if err != nil {
return err
@@ -442,6 +451,7 @@ func logicSigVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx *Group
TxnGroup: transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns),
MinAvmVersion: &groupCtx.minAvmVersion,
SigLedger: groupCtx.ledger,
+ Tracer: evalTracer,
}
pass, cx, err := logic.EvalSignatureFull(groupIndex, &ep)
if err != nil {
@@ -501,7 +511,7 @@ func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHea
batchVerifier := crypto.MakeBatchVerifierWithHint(len(payset))
for i, signTxnsGrp := range txnGroups {
- groupCtxs[i], grpErr = txnGroupBatchPrep(signTxnsGrp, &blkHeader, ledger, batchVerifier)
+ groupCtxs[i], grpErr = txnGroupBatchPrep(signTxnsGrp, &blkHeader, ledger, batchVerifier, nil)
// abort only if it's a non-cache error.
if grpErr != nil {
return grpErr
@@ -851,7 +861,7 @@ func (sv *StreamVerifier) addVerificationTaskToThePoolNow(ue []*UnverifiedElemen
// TODO: separate operations here, and get the sig verification inside the LogicSig to the batch here
blockHeader := sv.nbw.getBlockHeader()
for _, ue := range ue {
- groupCtx, err := txnGroupBatchPrep(ue.TxnGroup, blockHeader, sv.ledger, batchVerifier)
+ groupCtx, err := txnGroupBatchPrep(ue.TxnGroup, blockHeader, sv.ledger, batchVerifier, nil)
if err != nil {
// verification failed, no need to add the sig to the batch, report the error
sv.sendResult(ue.TxnGroup, ue.BacklogMessage, err)
diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go
index 62cccce19..eb03917fb 100644
--- a/data/transactions/verify/txn_test.go
+++ b/data/transactions/verify/txn_test.go
@@ -36,6 +36,8 @@ import (
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/data/transactions/logic/mocktracer"
+ "github.com/algorand/go-algorand/data/txntest"
"github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
@@ -66,7 +68,7 @@ var spec = transactions.SpecialAddresses{
func verifyTxn(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext) error {
batchVerifier := crypto.MakeBatchVerifier()
- if err := txnBatchPrep(s, txnIdx, groupCtx, batchVerifier); err != nil {
+ if err := txnBatchPrep(s, txnIdx, groupCtx, batchVerifier, nil); err != nil {
return err
}
return batchVerifier.Verify()
@@ -360,6 +362,89 @@ func TestDecodeNil(t *testing.T) {
}
}
+func TestTxnGroupWithTracer(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ proto := config.Consensus[protocol.ConsensusCurrentVersion]
+
+ account := keypair()
+ accountAddr := basics.Address(account.SignatureVerifier)
+
+ ops1, err := logic.AssembleString(`#pragma version 6
+pushint 1`)
+ require.NoError(t, err)
+ program1 := ops1.Program
+ program1Addr := basics.Address(logic.HashProgram(program1))
+
+ ops2, err := logic.AssembleString(`#pragma version 6
+pushbytes "test"
+pop
+pushint 1`)
+ require.NoError(t, err)
+ program2 := ops2.Program
+ program2Addr := basics.Address(logic.HashProgram(program2))
+
+ // this shouldn't be invoked during this test
+ appProgram := "err"
+
+ lsigPay := txntest.Txn{
+ Type: protocol.PaymentTx,
+ Sender: program1Addr,
+ Receiver: accountAddr,
+ Fee: proto.MinTxnFee,
+ }
+
+ normalSigAppCall := txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: accountAddr,
+ ApprovalProgram: appProgram,
+ ClearStateProgram: appProgram,
+ Fee: proto.MinTxnFee,
+ }
+
+ lsigAppCall := txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: program2Addr,
+ ApprovalProgram: appProgram,
+ ClearStateProgram: appProgram,
+ Fee: proto.MinTxnFee,
+ }
+
+ txntest.Group(&lsigPay, &normalSigAppCall, &lsigAppCall)
+
+ txgroup := []transactions.SignedTxn{
+ {
+ Lsig: transactions.LogicSig{
+ Logic: program1,
+ },
+ Txn: lsigPay.Txn(),
+ },
+ normalSigAppCall.Txn().Sign(account),
+ {
+ Lsig: transactions.LogicSig{
+ Logic: program2,
+ },
+ Txn: lsigAppCall.Txn(),
+ },
+ }
+
+ mockTracer := &mocktracer.Tracer{}
+ _, err = TxnGroupWithTracer(txgroup, blockHeader, nil, logic.NoHeaderLedger{}, mockTracer)
+ require.NoError(t, err)
+
+ expectedEvents := []mocktracer.Event{
+ mocktracer.BeforeProgram(logic.ModeSig), // first txn start
+ mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), // first txn LogicSig: 1 op
+ mocktracer.AfterProgram(logic.ModeSig), // first txn end
+ // nothing for second txn (not signed with a LogicSig)
+ mocktracer.BeforeProgram(logic.ModeSig), // third txn start
+ mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(), // third txn LogicSig: 3 ops
+ mocktracer.AfterProgram(logic.ModeSig), // third txn end
+ }
+ require.Equal(t, expectedEvents, mockTracer.Events)
+}
+
func TestPaysetGroups(t *testing.T) {
partitiontest.PartitionTest(t)
diff --git a/data/txDupCache.go b/data/txDupCache.go
index 6e127e2db..96a426dc7 100644
--- a/data/txDupCache.go
+++ b/data/txDupCache.go
@@ -228,6 +228,12 @@ func (c *txSaltedCache) CheckAndPut(msg []byte) (*crypto.Digest, bool) {
// already added to cache between RUnlock() and Lock(), return
return d, found
}
+ } else {
+ // Do another check to see if another copy of the transaction won the race to write it to the cache
+ // Only check current to save a lookup since swaps are rare and no need to re-hash
+ if _, found := c.cur[*d]; found {
+ return d, found
+ }
}
if len(c.cur) >= c.maxSize {
diff --git a/data/txntest/defi.go b/data/txntest/defi.go
index dcdaaaf40..635393a67 100644
--- a/data/txntest/defi.go
+++ b/data/txntest/defi.go
@@ -526,7 +526,7 @@ func CreateTinyManSignedTxGroup(tb testing.TB, txns []Txn) ([]transactions.Signe
ops, err := logic.AssembleString(TmLsig)
require.NoError(tb, err)
- stxns := SignedTxns(&txns[0], &txns[1], &txns[2], &txns[3])
+ stxns := Group(&txns[0], &txns[1], &txns[2], &txns[3])
stxns[1].Lsig.Logic = ops.Program
stxns[3].Lsig.Logic = ops.Program
diff --git a/data/txntest/txn.go b/data/txntest/txn.go
index 8ed1107c6..8e9f699f8 100644
--- a/data/txntest/txn.go
+++ b/data/txntest/txn.go
@@ -287,10 +287,10 @@ func (tx Txn) SignedTxnWithAD() transactions.SignedTxnWithAD {
return transactions.SignedTxnWithAD{SignedTxn: tx.SignedTxn()}
}
-// SignedTxns turns a list of Txns into a slice of SignedTxns with
-// GroupIDs set properly to make them a transaction group. Maybe
-// another name is more approrpriate
-func SignedTxns(txns ...*Txn) []transactions.SignedTxn {
+// Group turns a list of Txns into a slice of SignedTxns with
+// GroupIDs set properly to make them a transaction group. The input
+// Txns are modified with the calculated GroupID.
+func Group(txns ...*Txn) []transactions.SignedTxn {
txgroup := transactions.TxGroup{
TxGroupHashes: make([]crypto.Digest, len(txns)),
}
diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go
index ea5480b0b..1a1a1eea5 100644
--- a/ledger/internal/eval.go
+++ b/ledger/internal/eval.go
@@ -594,6 +594,8 @@ type BlockEvaluator struct {
l LedgerForEvaluator
maxTxnBytesPerBlock int
+
+ Tracer logic.EvalTracer
}
// LedgerForEvaluator defines the ledger interface needed by the evaluator.
@@ -925,14 +927,7 @@ func (eval *BlockEvaluator) Transaction(txn transactions.SignedTxn, ad transacti
// TransactionGroup tentatively adds a new transaction group as part of this block evaluation.
// If the transaction group cannot be added to the block without violating some constraints,
// an error is returned and the block evaluator state is unchanged.
-func (eval *BlockEvaluator) TransactionGroup(txads []transactions.SignedTxnWithAD) error {
- return eval.transactionGroup(txads)
-}
-
-// transactionGroup tentatively executes a group of transactions as part of this block evaluation.
-// If the transaction group cannot be added to the block without violating some constraints,
-// an error is returned and the block evaluator state is unchanged.
-func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWithAD) error {
+func (eval *BlockEvaluator) TransactionGroup(txgroup []transactions.SignedTxnWithAD) error {
// Nothing to do if there are no transactions.
if len(txgroup) == 0 {
return nil
@@ -953,17 +948,30 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit
defer cow.recycle()
evalParams := logic.NewEvalParams(txgroup, &eval.proto, &eval.specials)
+ evalParams.Tracer = eval.Tracer
+
+ if eval.Tracer != nil {
+ eval.Tracer.BeforeTxnGroup(evalParams)
+ }
// Evaluate each transaction in the group
txibs = make([]transactions.SignedTxnInBlock, 0, len(txgroup))
for gi, txad := range txgroup {
var txib transactions.SignedTxnInBlock
+ if eval.Tracer != nil {
+ eval.Tracer.BeforeTxn(evalParams, gi)
+ }
+
err := eval.transaction(txad.SignedTxn, evalParams, gi, txad.ApplyData, cow, &txib)
if err != nil {
return err
}
+ if eval.Tracer != nil {
+ eval.Tracer.AfterTxn(evalParams, gi, txib.ApplyData)
+ }
+
txibs = append(txibs, txib)
if eval.validate {
@@ -1010,6 +1018,10 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit
eval.blockTxBytes += groupTxBytes
cow.commitToParent()
+ if eval.Tracer != nil {
+ eval.Tracer.AfterTxnGroup(evalParams)
+ }
+
return nil
}
diff --git a/ledger/internal/eval_test.go b/ledger/internal/eval_test.go
index 67a29488a..5803a4d61 100644
--- a/ledger/internal/eval_test.go
+++ b/ledger/internal/eval_test.go
@@ -33,11 +33,14 @@ import (
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/crypto/stateproof"
"github.com/algorand/go-algorand/data/basics"
+ basics_testing "github.com/algorand/go-algorand/data/basics/testing"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/stateproofmsg"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/data/transactions/logic/mocktracer"
"github.com/algorand/go-algorand/data/transactions/verify"
+ "github.com/algorand/go-algorand/data/txntest"
"github.com/algorand/go-algorand/ledger/apply"
"github.com/algorand/go-algorand/ledger/ledgercore"
ledgertesting "github.com/algorand/go-algorand/ledger/testing"
@@ -307,8 +310,203 @@ func TestPrivateTransactionGroup(t *testing.T) {
require.Error(t, err) // too many
}
+func tealOpLogs(count int) []mocktracer.Event {
+ var log []mocktracer.Event
+
+ for i := 0; i < count; i++ {
+ log = append(log, mocktracer.BeforeOpcode(), mocktracer.AfterOpcode())
+ }
+
+ return log
+}
+
+func flatten(rows [][]mocktracer.Event) []mocktracer.Event {
+ var out []mocktracer.Event
+ for _, row := range rows {
+ out = append(out, row...)
+ }
+ return out
+}
+
+const innerTxnTestProgram string = `#pragma version 6
+itxn_begin
+int appl
+itxn_field TypeEnum
+int NoOp
+itxn_field OnCompletion
+byte 0x068101 // #pragma version 6; int 1;
+dup
+itxn_field ApprovalProgram
+itxn_field ClearStateProgram
+itxn_submit
+
+itxn_begin
+int pay
+itxn_field TypeEnum
+int 1
+itxn_field Amount
+global CurrentApplicationAddress
+itxn_field Receiver
+itxn_next
+int pay
+itxn_field TypeEnum
+int 2
+itxn_field Amount
+global CurrentApplicationAddress
+itxn_field Receiver
+itxn_submit
+
+int 1
+`
+
+func TestTransactionGroupWithTracer(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ genesisInitState, addrs, keys := ledgertesting.Genesis(10)
+
+ innerAppID := 3
+ innerAppAddress := basics.AppIndex(innerAppID).Address()
+ balances := genesisInitState.Accounts
+ balances[innerAppAddress] = basics_testing.MakeAccountData(basics.Offline, basics.MicroAlgos{Raw: 1000000})
+
+ genesisBalances := bookkeeping.GenesisBalances{
+ Balances: genesisInitState.Accounts,
+ FeeSink: testSinkAddr,
+ RewardsPool: testPoolAddr,
+ Timestamp: 0,
+ }
+ l := newTestLedger(t, genesisBalances)
+
+ blkHeader, err := l.BlockHdr(basics.Round(0))
+ require.NoError(t, err)
+ newBlock := bookkeeping.MakeBlock(blkHeader)
+ eval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0)
+ require.NoError(t, err)
+ eval.validate = true
+ eval.generate = true
+
+ basicProgram := `#pragma version 6
+byte "hello"
+log
+int 1`
+
+ genHash := l.GenesisHash()
+
+ // a basic app call
+ basicAppCallTxn := txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: addrs[0],
+ ApprovalProgram: basicProgram,
+ ClearStateProgram: basicProgram,
+
+ FirstValid: newBlock.Round(),
+ LastValid: newBlock.Round() + 1000,
+ Fee: minFee,
+ GenesisHash: genHash,
+ }
+
+ // a non-app call txn
+ payTxn := txntest.Txn{
+ Type: protocol.PaymentTx,
+ Sender: addrs[1],
+ Receiver: addrs[2],
+ CloseRemainderTo: addrs[3],
+ Amount: 1_000_000,
+
+ FirstValid: newBlock.Round(),
+ LastValid: newBlock.Round() + 1000,
+ Fee: minFee,
+ GenesisHash: genHash,
+ }
+
+ // an app call that spawns inner txns
+ innerAppCallTxn := txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: addrs[0],
+ ApprovalProgram: innerTxnTestProgram,
+ ClearStateProgram: basicProgram,
+
+ FirstValid: newBlock.Round(),
+ LastValid: newBlock.Round() + 1000,
+ Fee: minFee,
+ GenesisHash: genHash,
+ }
+
+ txntest.Group(&basicAppCallTxn, &payTxn, &innerAppCallTxn)
+
+ txgroup := transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{
+ basicAppCallTxn.Txn().Sign(keys[0]),
+ payTxn.Txn().Sign(keys[1]),
+ innerAppCallTxn.Txn().Sign(keys[0]),
+ })
+
+ require.Len(t, eval.block.Payset, 0)
+
+ tracer := &mocktracer.Tracer{}
+ eval.Tracer = tracer
+ err = eval.TransactionGroup(txgroup)
+ require.NoError(t, err)
+
+ require.Len(t, eval.block.Payset, len(txgroup))
+
+ expectedADs := make([]transactions.ApplyData, len(txgroup))
+ for i, txn := range eval.block.Payset {
+ expectedADs[i] = txn.ApplyData
+ }
+
+ expectedEvents := flatten([][]mocktracer.Event{
+ {
+ mocktracer.BeforeTxnGroup(3),
+ mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start basicAppCallTxn
+ mocktracer.BeforeProgram(logic.ModeApp),
+ },
+ tealOpLogs(3),
+ {
+ mocktracer.AfterProgram(logic.ModeApp),
+ mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedADs[0]), // end basicAppCallTxn
+ mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn
+ mocktracer.AfterTxn(protocol.PaymentTx, expectedADs[1]), // end payTxn
+ mocktracer.BeforeTxn(protocol.ApplicationCallTx), // start innerAppCallTxn
+ mocktracer.BeforeProgram(logic.ModeApp),
+ },
+ tealOpLogs(10),
+ {
+ mocktracer.BeforeOpcode(),
+ mocktracer.BeforeTxnGroup(1), // start first itxn group
+ mocktracer.BeforeTxn(protocol.ApplicationCallTx),
+ mocktracer.BeforeProgram(logic.ModeApp),
+ },
+ tealOpLogs(1),
+ {
+ mocktracer.AfterProgram(logic.ModeApp),
+ mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedADs[2].EvalDelta.InnerTxns[0].ApplyData),
+ mocktracer.AfterTxnGroup(1), // end first itxn group
+ mocktracer.AfterOpcode(),
+ },
+ tealOpLogs(14),
+ {
+ mocktracer.BeforeOpcode(),
+ mocktracer.BeforeTxnGroup(2), // start second itxn group
+ mocktracer.BeforeTxn(protocol.PaymentTx),
+ mocktracer.AfterTxn(protocol.PaymentTx, expectedADs[2].EvalDelta.InnerTxns[1].ApplyData),
+ mocktracer.BeforeTxn(protocol.PaymentTx),
+ mocktracer.AfterTxn(protocol.PaymentTx, expectedADs[2].EvalDelta.InnerTxns[2].ApplyData),
+ mocktracer.AfterTxnGroup(2), // end second itxn group
+ mocktracer.AfterOpcode(),
+ },
+ tealOpLogs(1),
+ {
+ mocktracer.AfterProgram(logic.ModeApp),
+ mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedADs[2]), // end innerAppCallTxn
+ mocktracer.AfterTxnGroup(3),
+ },
+ })
+ require.Equal(t, expectedEvents, tracer.Events)
+}
+
// BlockEvaluator.workaroundOverspentRewards() fixed a couple issues on testnet.
-// This is now part of history and has to be re-created when running catchup on testnet. So, test to ensure it keeps happenning.
+// This is now part of history and has to be re-created when running catchup on testnet. So, test to ensure it keeps happening.
func TestTestnetFixup(t *testing.T) {
partitiontest.PartitionTest(t)
diff --git a/ledger/simple_test.go b/ledger/simple_test.go
index 5389931aa..e934e3f01 100644
--- a/ledger/simple_test.go
+++ b/ledger/simple_test.go
@@ -116,7 +116,7 @@ func txgroup(t testing.TB, ledger *Ledger, eval *internal.BlockEvaluator, txns .
for _, txn := range txns {
fillDefaults(t, ledger, eval, txn)
}
- txgroup := txntest.SignedTxns(txns...)
+ txgroup := txntest.Group(txns...)
return eval.TransactionGroup(transactions.WrapSignedTxnsWithAD(txgroup))
}
diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go
index 8cd209df4..b559e989f 100644
--- a/ledger/simulation/simulator.go
+++ b/ledger/simulation/simulator.go
@@ -24,6 +24,7 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/transactions/logic"
"github.com/algorand/go-algorand/data/transactions/verify"
"github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/protocol"
@@ -55,6 +56,18 @@ func (l simulatorLedger) LookupLatest(addr basics.Address) (basics.AccountData,
}
// ==============================
+// > Simulator Tracer
+// ==============================
+
+type evalTracer struct {
+ logic.NullEvalTracer
+}
+
+func makeTracer() logic.EvalTracer {
+ return &evalTracer{}
+}
+
+// ==============================
// > Simulator Errors
// ==============================
@@ -115,7 +128,7 @@ var proxySigner = crypto.PrivateKey{
// check verifies that the transaction is well-formed and has valid or missing signatures.
// An invalid transaction group error is returned if the transaction is not well-formed or there are invalid signatures.
// To make things easier, we support submitting unsigned transactions and will respond whether signatures are missing.
-func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.SignedTxn) (bool, error) {
+func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.SignedTxn, debugger logic.EvalTracer) (bool, error) {
proxySignerSecrets, err := crypto.SecretKeyToSignatureSecrets(proxySigner)
if err != nil {
return false, err
@@ -150,7 +163,7 @@ func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.Sig
}
// Verify the signed transactions are well-formed and have valid signatures
- _, err = verify.TxnGroup(txnsToVerify, &hdr, nil, s.ledger)
+ _, err = verify.TxnGroupWithTracer(txnsToVerify, &hdr, nil, s.ledger, debugger)
if err != nil {
return false, InvalidTxGroupError{SimulatorError{err}}
}
@@ -158,13 +171,14 @@ func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.Sig
return len(missingSigs) != 0, nil
}
-func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.SignedTxn) (*ledgercore.ValidatedBlock, error) {
+func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.SignedTxn, tracer logic.EvalTracer) (*ledgercore.ValidatedBlock, error) {
// s.ledger has 'StartEvaluator' because *data.Ledger is embedded in the simulatorLedger
// and data.Ledger embeds *ledger.Ledger
eval, err := s.ledger.StartEvaluator(hdr, len(stxns), 0)
if err != nil {
return nil, err
}
+ eval.Tracer = tracer
group := transactions.WrapSignedTxnsWithAD(stxns)
@@ -190,13 +204,14 @@ func (s Simulator) Simulate(txgroup []transactions.SignedTxn) (*ledgercore.Valid
}
nextBlock := bookkeeping.MakeBlock(prevBlockHdr)
hdr := nextBlock.BlockHeader
+ simulatorTracer := makeTracer()
// check that the transaction is well-formed and mark whether signatures are missing
- missingSignatures, err := s.check(hdr, txgroup)
+ missingSignatures, err := s.check(hdr, txgroup, simulatorTracer)
if err != nil {
return nil, false, err
}
- vb, err := s.evaluate(hdr, txgroup)
+ vb, err := s.evaluate(hdr, txgroup, simulatorTracer)
return vb, missingSignatures, err
}
diff --git a/logging/telemetryspec/metric_test.go b/logging/telemetryspec/metric_test.go
index 170b6a60c..85693bdba 100644
--- a/logging/telemetryspec/metric_test.go
+++ b/logging/telemetryspec/metric_test.go
@@ -56,6 +56,8 @@ func TestTransactionProcessingTimeDistributionFormatting(t *testing.T) {
}
func TestTransactionProcessingTimeDistributionPrint(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
var decPT transactionProcessingTimeDistribution
expected := "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38]"
require.NoError(t, json.Unmarshal([]byte(expected), &decPT))
diff --git a/node/node_test.go b/node/node_test.go
index 77dfc7772..f58471090 100644
--- a/node/node_test.go
+++ b/node/node_test.go
@@ -21,7 +21,9 @@ import (
"math/rand"
"os"
"path/filepath"
+ "runtime"
"strconv"
+ "strings"
"sync"
"testing"
"time"
@@ -245,6 +247,11 @@ func TestInitialSync(t *testing.T) {
t.Skip("Test takes ~25 seconds.")
}
+ if (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") &&
+ strings.ToUpper(os.Getenv("CIRCLECI")) == "TRUE" {
+ t.Skip("Test is too heavy for amd64 builder running in parallel with other packages")
+ }
+
backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil)
defer backlogPool.Shutdown()
diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go
index 9e6dc27c2..32352bcfc 100644
--- a/shared/pingpong/pingpong.go
+++ b/shared/pingpong/pingpong.go
@@ -14,6 +14,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
+// Package pingpong provides a transaction generating utility for performance testing.
+//
+//nolint:unused,structcheck,deadcode,varcheck // ignore unused pingpong code
package pingpong
import (
diff --git a/util/rateLimit.go b/util/rateLimit.go
index 3fbd50c3f..c4e85c71e 100644
--- a/util/rateLimit.go
+++ b/util/rateLimit.go
@@ -65,9 +65,8 @@ type capacityQueue chan capacity
// ErlCapacityGuard is the structure returned to clients so they can release the capacity when needed
// they also inform the congestion manager of events
type ErlCapacityGuard struct {
- client ErlClient
- cq capacityQueue
- cm CongestionManager
+ cq capacityQueue
+ cm CongestionManager
}
// Release will put capacity back into the queue attached to this capacity guard
diff --git a/util/tcpinfo_linux.go b/util/tcpinfo_linux.go
index 5c332643e..8cf1687ae 100644
--- a/util/tcpinfo_linux.go
+++ b/util/tcpinfo_linux.go
@@ -56,6 +56,7 @@ func getConnTCPInfo(raw syscall.RawConn) (*TCPInfo, error) {
// linuxTCPInfo is based on linux include/uapi/linux/tcp.h struct tcp_info
//revive:disable:var-naming
+//nolint:structcheck // complains about unused fields that are rqeuired to match C tcp_info struct
type linuxTCPInfo struct {
state uint8
ca_state uint8